aboutsummaryrefslogtreecommitdiff
path: root/cmd/thoi/main.go
blob: 27b32f4cf10efb23a06aec84d05dcf9db469db87 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
package main

import (
	"flag"
	"fmt"
	"log"
	"os"
	"os/signal"
	"syscall"

	"thehouseoficarus/internal/admin"
	"thehouseoficarus/internal/behavior"
	"thehouseoficarus/internal/config"
	"thehouseoficarus/internal/game"
	"thehouseoficarus/internal/net"
)

func main() {
	configPath := flag.String("config", "config.yaml", "path to config file")
	flag.Parse()

	dataDir := os.Getenv("DATA_DIR")
	if dataDir == "" {
		dataDir = "data"
	}

	cfg, err := config.Load(*configPath)
	if err != nil {
		if os.IsNotExist(err) {
			log.Printf("config file %s not found, creating with defaults", *configPath)
			if err := config.SaveDefault(*configPath); err != nil {
				log.Fatalf("failed to create config: %v", err)
			}
			cfg = config.Default()
		} else {
			log.Fatalf("failed to load config: %v", err)
		}
	}

	if cfg.GameConstants.ShopDefaultRestock > 0 {
		behavior.ShopDefaultRestock = cfg.GameConstants.ShopDefaultRestock
	}

	g := game.New(dataDir, &cfg.DefaultColors, cfg.StartupValidation, cfg.StartingRoom)
	g.Ticks.Start(cfg.TickLength)
	defer g.Ticks.Stop()

	// The master per-tick subscriber. Registered as engine subscription ID 1,
	// the engine fires it before any dynamically-registered combat/aggro/respawn
	// callbacks (engine.processTick sorts subscribers by registration ID). Its
	// body — the canonical per-tick system sequence — is enforced and tested in
	// internal/game/tick_systems.go and documented in
	// building_guide/tick_order.md. Reordering it is a breaking change.
	g.Ticks.Subscribe(1, func() bool {
		g.RunTickSystems()
		return true
	})

	server, err := net.NewServer(cfg)
	if err != nil {
		log.Fatalf("failed to start server: %v", err)
	}
	g.SetHub(server.Hub())

	if cfg.AdminHTTP.Enabled {
		adminSrv, err := admin.NewServer(cfg, false, g.AccountStore, g.World, g.ItemStore, g.ObjectStore, g.MobStore, dataDir, g.CourseStore.ReloadAll, g.RewriteRoomIDs, *configPath)
		if err != nil {
			log.Fatalf("failed to start admin HTTP server: %v", err)
		}
		go func() {
			if err := adminSrv.ListenAndServe(); err != nil {
				log.Printf("admin HTTP server error: %v", err)
			}
		}()
		log.Printf("Admin HTTP listening on :%d", cfg.AdminHTTP.Port)
	}

	if cfg.AdminHTTPS.Enabled {
		adminSrv, err := admin.NewServer(cfg, true, g.AccountStore, g.World, g.ItemStore, g.ObjectStore, g.MobStore, dataDir, g.CourseStore.ReloadAll, g.RewriteRoomIDs, *configPath)
		if err != nil {
			log.Fatalf("failed to start admin HTTPS server: %v", err)
		}
		go func() {
			if err := adminSrv.ListenAndServe(); err != nil {
				log.Printf("admin HTTPS server error: %v", err)
			}
		}()
		log.Printf("Admin HTTPS listening on :%d", cfg.AdminHTTPS.Port)
	}

	if cfg.Telnet.Enabled {
		log.Printf("Telnet listening on :%d", cfg.Telnet.Port)
	}
	if cfg.TelnetTLS.Enabled {
		log.Printf("Telnet+TLS listening on :%d", cfg.TelnetTLS.Port)
	}
	if cfg.HTTP.Enabled {
		log.Printf("HTTP listening on :%d", cfg.HTTP.Port)
	}
	if cfg.HTTPS.Enabled {
		log.Printf("HTTPS listening on :%d", cfg.HTTPS.Port)
	}
	fmt.Println() // blank line after log prefixes

	sigCh := make(chan os.Signal, 1)
	signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)

	go func() {
		<-sigCh
		log.Println("Shutting down...")
		g.Ticks.Stop()
		os.Exit(0)
	}()

	if err := server.ListenAndServe(g.HandleSession); err != nil {
		log.Fatalf("server error: %v", err)
	}
}