diff --git a/.gitignore b/.gitignore index 0ba7f82..159c3f8 100644 --- a/.gitignore +++ b/.gitignore @@ -19,6 +19,6 @@ go.work # Test builds -./world_config.json -./world_server -./world.db +/world_config.json +/world_server +/world.db diff --git a/cmd/world_server/main.go b/cmd/world_server/main.go new file mode 100644 index 0000000..5ad4fe9 --- /dev/null +++ b/cmd/world_server/main.go @@ -0,0 +1,213 @@ +package main + +import ( + "encoding/json" + "flag" + "fmt" + "log" + "os" + "os/signal" + "syscall" + + "eq2emu/internal/world" +) + +const defaultConfigFile = "world_config.json" + +var ( + configFile = flag.String("config", defaultConfigFile, "Path to configuration file") + listenAddr = flag.String("listen-addr", "", "Override listen address") + listenPort = flag.Int("listen-port", 0, "Override listen port") + webPort = flag.Int("web-port", 0, "Override web interface port") + dbPath = flag.String("db", "", "Override database path") + logLevel = flag.String("log-level", "", "Override log level (debug, info, warn, error)") + serverName = flag.String("name", "", "Override server name") + xpRate = flag.Float64("xp-rate", 0, "Override XP rate") + showVersion = flag.Bool("version", false, "Show version and exit") +) + +// Version information (set at build time) +var ( + Version = "1.0.0-dev" + BuildTime = "unknown" + GitCommit = "unknown" +) + +func main() { + flag.Parse() + + if *showVersion { + fmt.Printf("EQ2Go World Server\n") + fmt.Printf("Version: %s\n", Version) + fmt.Printf("Build Time: %s\n", BuildTime) + fmt.Printf("Git Commit: %s\n", GitCommit) + os.Exit(0) + } + + // Load configuration + config, err := loadConfig(*configFile) + if err != nil { + log.Fatalf("Failed to load configuration: %v", err) + } + + // Apply command-line overrides + applyOverrides(config) + + // Print startup banner + printBanner(config) + + // Create world server + worldServer, err := world.NewWorld(config) + if err != nil { + log.Fatalf("Failed to create world server: %v", err) + } + + // Start world server + if err := worldServer.Start(); err != nil { + log.Fatalf("Failed to start world server: %v", err) + } + + // Setup signal handlers + sigChan := make(chan os.Signal, 1) + signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM) + + // Run server in background + go worldServer.Process() + + // Wait for shutdown signal + sig := <-sigChan + fmt.Printf("\nReceived signal: %v\n", sig) + + // Graceful shutdown + if err := worldServer.Stop(); err != nil { + log.Printf("Error during shutdown: %v", err) + } +} + +// loadConfig loads the configuration from file +func loadConfig(filename string) (*world.WorldConfig, error) { + // Check if config file exists + if _, err := os.Stat(filename); os.IsNotExist(err) { + // Create default configuration + config := createDefaultConfig() + + // Save default configuration + if err := saveConfig(filename, config); err != nil { + return nil, fmt.Errorf("failed to save default config: %w", err) + } + + fmt.Printf("Created default configuration file: %s\n", filename) + return config, nil + } + + // Load existing configuration + file, err := os.Open(filename) + if err != nil { + return nil, fmt.Errorf("failed to open config file: %w", err) + } + defer file.Close() + + var config world.WorldConfig + decoder := json.NewDecoder(file) + if err := decoder.Decode(&config); err != nil { + return nil, fmt.Errorf("failed to decode config: %w", err) + } + + return &config, nil +} + +// saveConfig saves the configuration to file +func saveConfig(filename string, config *world.WorldConfig) error { + file, err := os.Create(filename) + if err != nil { + return fmt.Errorf("failed to create config file: %w", err) + } + defer file.Close() + + encoder := json.NewEncoder(file) + encoder.SetIndent("", " ") + if err := encoder.Encode(config); err != nil { + return fmt.Errorf("failed to encode config: %w", err) + } + + return nil +} + +// createDefaultConfig creates a default configuration +func createDefaultConfig() *world.WorldConfig { + return &world.WorldConfig{ + // Network settings + ListenAddr: "0.0.0.0", + ListenPort: 9000, + MaxClients: 1000, + + // Web interface settings + WebAddr: "0.0.0.0", + WebPort: 8080, + WebCertFile: "", + WebKeyFile: "", + WebKeyPassword: "", + + // Database settings + DatabasePath: "world.db", + + // Server settings + ServerName: "EQ2Go World Server", + ServerMOTD: "Welcome to EQ2Go!", + LogLevel: "info", + + // Game settings + XPRate: 1.0, + TSXPRate: 1.0, + CoinRate: 1.0, + LootRate: 1.0, + + // Login server settings + LoginServerAddr: "127.0.0.1", + LoginServerPort: 5999, + LoginServerKey: "", + } +} + +// applyOverrides applies command-line overrides to the configuration +func applyOverrides(config *world.WorldConfig) { + if *listenAddr != "" { + config.ListenAddr = *listenAddr + } + if *listenPort > 0 { + config.ListenPort = *listenPort + } + if *webPort > 0 { + config.WebPort = *webPort + } + if *dbPath != "" { + config.DatabasePath = *dbPath + } + if *logLevel != "" { + config.LogLevel = *logLevel + } + if *serverName != "" { + config.ServerName = *serverName + } + if *xpRate > 0 { + config.XPRate = float32(*xpRate) + } +} + +// printBanner prints the server startup banner +func printBanner(config *world.WorldConfig) { + fmt.Println("================================================================================") + fmt.Println(" EQ2Go World Server") + fmt.Println("================================================================================") + fmt.Printf("Version: %s\n", Version) + fmt.Printf("Server Name: %s\n", config.ServerName) + fmt.Printf("Listen Address: %s:%d\n", config.ListenAddr, config.ListenPort) + fmt.Printf("Web Interface: %s:%d\n", config.WebAddr, config.WebPort) + fmt.Printf("Database: %s\n", config.DatabasePath) + fmt.Printf("Log Level: %s\n", config.LogLevel) + fmt.Printf("XP Rate: %.1fx\n", config.XPRate) + fmt.Printf("TS XP Rate: %.1fx\n", config.TSXPRate) + fmt.Printf("Coin Rate: %.1fx\n", config.CoinRate) + fmt.Printf("Loot Rate: %.1fx\n", config.LootRate) + fmt.Println("================================================================================") +} \ No newline at end of file