package main import ( "encoding/json" "flag" "fmt" "log" "os" "os/signal" "syscall" ) const ( ConfigFile = "world_config.json" Version = "0.1.0" ) // printHeader displays the EQ2Emu banner and copyright info func printHeader() { fmt.Println("EQ2Emulator World Server") fmt.Printf("Version: %s\n", Version) fmt.Println() fmt.Println("Copyright (C) 2007-2026 EQ2Emulator Development Team") fmt.Println("https://www.eq2emu.com") fmt.Println() fmt.Println("EQ2Emulator is free software licensed under the GNU GPL v3") fmt.Println("See LICENSE file for details") fmt.Println() } // loadConfig loads configuration from JSON file with command line overrides func loadConfig() (*WorldConfig, error) { // Default configuration config := &WorldConfig{ ListenAddr: "0.0.0.0", ListenPort: 9000, MaxClients: 1000, BufferSize: 8192, WebAddr: "0.0.0.0", WebPort: 8080, DatabasePath: "world.db", XPRate: 1.0, TSXPRate: 1.0, VitalityRate: 1.0, LogLevel: "info", ThreadedLoad: true, } // Load from config file if it exists if data, err := os.ReadFile(ConfigFile); err == nil { if err := json.Unmarshal(data, config); err != nil { return nil, fmt.Errorf("failed to parse config file: %w", err) } log.Printf("Loaded configuration from %s", ConfigFile) } else { log.Printf("Config file %s not found, using defaults", ConfigFile) } // Command line overrides flag.StringVar(&config.ListenAddr, "listen-addr", config.ListenAddr, "UDP listen address") flag.IntVar(&config.ListenPort, "listen-port", config.ListenPort, "UDP listen port") flag.IntVar(&config.MaxClients, "max-clients", config.MaxClients, "Maximum client connections") flag.StringVar(&config.WebAddr, "web-addr", config.WebAddr, "Web server address") flag.IntVar(&config.WebPort, "web-port", config.WebPort, "Web server port") flag.StringVar(&config.DatabasePath, "db-path", config.DatabasePath, "Database file path") flag.StringVar(&config.LogLevel, "log-level", config.LogLevel, "Log level (debug, info, warn, error)") flag.BoolVar(&config.ThreadedLoad, "threaded-load", config.ThreadedLoad, "Use threaded loading") flag.Parse() return config, nil } // saveConfig saves the current configuration to file func saveConfig(config *WorldConfig) error { data, err := json.MarshalIndent(config, "", " ") if err != nil { return fmt.Errorf("failed to marshal config: %w", err) } if err := os.WriteFile(ConfigFile, data, 0644); err != nil { return fmt.Errorf("failed to write config file: %w", err) } return nil } // setupSignalHandlers sets up graceful shutdown on SIGINT/SIGTERM func setupSignalHandlers(world *World) <-chan os.Signal { sigChan := make(chan os.Signal, 1) signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM) go func() { sig := <-sigChan log.Printf("Received signal %v, initiating graceful shutdown...", sig) world.Shutdown() }() return sigChan } func main() { printHeader() // Load configuration config, err := loadConfig() if err != nil { log.Fatalf("Configuration error: %v", err) } // Save config file with any command line overrides if err := saveConfig(config); err != nil { log.Printf("Warning: failed to save config: %v", err) } // Create world server instance world, err := NewWorld(config) if err != nil { log.Fatalf("Failed to create world server: %v", err) } // Initialize all components log.Println("Initializing EQ2Emulator World Server...") if err := world.Initialize(); err != nil { log.Fatalf("Failed to initialize world server: %v", err) } // Setup signal handlers for graceful shutdown setupSignalHandlers(world) // Run the server log.Println("Starting World Server...") if err := world.Run(); err != nil { log.Fatalf("World server error: %v", err) } log.Println("World Server stopped gracefully") }