package login import ( "fmt" "net/url" "strings" ) // ServerConfig represents the login server configuration type ServerConfig struct { // Network settings ListenAddr string `json:"listen_addr"` ListenPort int `json:"listen_port"` MaxClients int `json:"max_clients"` // Web interface settings WebAddr string `json:"web_addr"` WebPort int `json:"web_port"` WebCertFile string `json:"web_cert_file"` WebKeyFile string `json:"web_key_file"` WebKeyPassword string `json:"web_key_password"` WebUser string `json:"web_user"` WebPassword string `json:"web_password"` // Database settings DatabaseAddress string `json:"database_address"` // MySQL server address DatabasePort int `json:"database_port"` // MySQL server port DatabaseUsername string `json:"database_username"` // MySQL username DatabasePassword string `json:"database_password"` // MySQL password DatabaseName string `json:"database_name"` // MySQL database name // Server settings ServerName string `json:"server_name"` LogLevel string `json:"log_level"` // World servers configuration WorldServers []WorldServerInfo `json:"world_servers"` } // WorldServerInfo represents information about a world server type WorldServerInfo struct { ID int `json:"id"` Name string `json:"name"` Address string `json:"address"` Port int `json:"port"` AdminPort int `json:"admin_port"` Key string `json:"key"` Status string `json:"status"` // "up", "down", "locked" Population int `json:"population"` // Current player count MaxPlayers int `json:"max_players"` // Maximum allowed players Description string `json:"description"` // Server flags Locked bool `json:"locked"` Hidden bool `json:"hidden"` // Connection tracking LastHeartbeat int64 `json:"last_heartbeat"` } // Validate validates the server configuration func (c *ServerConfig) Validate() error { if c.ListenAddr == "" { return fmt.Errorf("listen_addr is required") } if c.ListenPort <= 0 || c.ListenPort > 65535 { return fmt.Errorf("listen_port must be between 1 and 65535") } if c.MaxClients <= 0 { c.MaxClients = 1000 // Default value } // Database configuration validation if c.DatabaseAddress == "" { c.DatabaseAddress = "localhost" // Default to localhost } if c.DatabasePort <= 0 { c.DatabasePort = 3306 // Default MySQL port } if c.DatabaseUsername == "" { return fmt.Errorf("database_username is required") } if c.DatabasePassword == "" { return fmt.Errorf("database_password is required") } if c.DatabaseName == "" { return fmt.Errorf("database_name is required") } if c.ServerName == "" { c.ServerName = "EQ2Go Login Server" } // Validate log level logLevel := strings.ToLower(c.LogLevel) switch logLevel { case "debug", "info", "warn", "error": c.LogLevel = logLevel case "": c.LogLevel = "info" // Default default: return fmt.Errorf("invalid log_level: %s (must be debug, info, warn, or error)", c.LogLevel) } // Validate web configuration if c.WebPort > 0 { if c.WebPort <= 0 || c.WebPort > 65535 { return fmt.Errorf("web_port must be between 1 and 65535") } if c.WebAddr == "" { c.WebAddr = "0.0.0.0" } // If TLS files are specified, both cert and key must be provided if c.WebCertFile != "" && c.WebKeyFile == "" { return fmt.Errorf("web_key_file is required when web_cert_file is specified") } if c.WebKeyFile != "" && c.WebCertFile == "" { return fmt.Errorf("web_cert_file is required when web_key_file is specified") } } // Validate world servers for i, ws := range c.WorldServers { if err := ws.Validate(); err != nil { return fmt.Errorf("world_server[%d]: %w", i, err) } } return nil } // BuildDatabaseDSN builds a MySQL DSN string from the database configuration func (c *ServerConfig) BuildDatabaseDSN() string { // Format: username:password@tcp(address:port)/database?parseTime=true&charset=utf8mb4 return fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?parseTime=true&charset=utf8mb4", url.QueryEscape(c.DatabaseUsername), url.QueryEscape(c.DatabasePassword), c.DatabaseAddress, c.DatabasePort, c.DatabaseName) } // Validate validates a world server configuration func (w *WorldServerInfo) Validate() error { if w.ID <= 0 { return fmt.Errorf("id must be positive") } if w.Name == "" { return fmt.Errorf("name is required") } if w.Address == "" { return fmt.Errorf("address is required") } if w.Port <= 0 || w.Port > 65535 { return fmt.Errorf("port must be between 1 and 65535") } if w.AdminPort <= 0 || w.AdminPort > 65535 { return fmt.Errorf("admin_port must be between 1 and 65535") } if w.AdminPort == w.Port { return fmt.Errorf("admin_port cannot be the same as port") } if w.MaxPlayers <= 0 { w.MaxPlayers = 1000 // Default value } // Normalize status status := strings.ToLower(w.Status) switch status { case "up", "down", "locked": w.Status = status case "": w.Status = "down" // Default default: return fmt.Errorf("invalid status: %s (must be up, down, or locked)", w.Status) } return nil } // IsOnline returns whether the world server is currently online func (w *WorldServerInfo) IsOnline() bool { return w.Status == "up" } // IsLocked returns whether the world server is locked func (w *WorldServerInfo) IsLocked() bool { return w.Locked || w.Status == "locked" } // IsHidden returns whether the world server should be hidden from the server list func (w *WorldServerInfo) IsHidden() bool { return w.Hidden } // GetPopulationPercentage returns the current population as a percentage of max capacity func (w *WorldServerInfo) GetPopulationPercentage() float64 { if w.MaxPlayers <= 0 { return 0 } return float64(w.Population) / float64(w.MaxPlayers) * 100 } // GetPopulationLevel returns a string representation of the population level func (w *WorldServerInfo) GetPopulationLevel() string { pct := w.GetPopulationPercentage() switch { case pct >= 95: return "FULL" case pct >= 80: return "HIGH" case pct >= 50: return "MEDIUM" case pct >= 25: return "LOW" default: return "LIGHT" } } // Clone creates a deep copy of the WorldServerInfo func (w *WorldServerInfo) Clone() *WorldServerInfo { clone := *w return &clone }