240 lines
6.1 KiB
Go
240 lines
6.1 KiB
Go
package login
|
|
|
|
import (
|
|
"fmt"
|
|
"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",
|
|
c.DatabaseUsername,
|
|
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
|
|
}
|