package login import ( "context" "fmt" "log" "net/http" "sync" "time" "eq2emu/internal/udp" ) // Server represents the login server instance type Server struct { config *ServerConfig database *LoginDB udpServer *udp.Server webServer *http.Server clientList *ClientList worldList *WorldList running bool startTime time.Time // Synchronization mu sync.RWMutex stopChan chan struct{} wg sync.WaitGroup } // NewServer creates a new login server instance func NewServer(config *ServerConfig) (*Server, error) { if config == nil { return nil, fmt.Errorf("configuration cannot be nil") } // Validate configuration if err := config.Validate(); err != nil { return nil, fmt.Errorf("invalid configuration: %w", err) } // Create database connection db, err := NewLoginDB(config.DatabaseDSN) if err != nil { return nil, fmt.Errorf("failed to initialize database: %w", err) } // Create login server instance first server := &Server{ config: config, database: db, clientList: NewClientList(), worldList: NewWorldList(), running: false, stopChan: make(chan struct{}), } // Create UDP server for EverQuest II protocol addr := fmt.Sprintf("%s:%d", config.ListenAddr, config.ListenPort) udpConfig := udp.Config{ MaxConnections: config.MaxClients, BufferSize: 1024 * 64, // 64KB buffer EnableCompression: true, EnableEncryption: true, } udpServer, err := udp.NewServer(addr, server.handleUDPPacket, udpConfig) if err != nil { return nil, fmt.Errorf("failed to create UDP server: %w", err) } server.udpServer = udpServer // Initialize web server if enabled if config.WebPort > 0 { server.initWebServer() } return server, nil } // Start starts the login server func (s *Server) Start() error { s.mu.Lock() defer s.mu.Unlock() if s.running { return fmt.Errorf("server is already running") } log.Printf("Starting login server on %s:%d", s.config.ListenAddr, s.config.ListenPort) // Start UDP server (it doesn't return an error in this implementation) go s.udpServer.Start() // Start web server if configured if s.webServer != nil { s.wg.Add(1) go func() { defer s.wg.Done() addr := fmt.Sprintf("%s:%d", s.config.WebAddr, s.config.WebPort) log.Printf("Starting web server on %s", addr) if err := s.webServer.ListenAndServe(); err != nil && err != http.ErrServerClosed { log.Printf("Web server error: %v", err) } }() } // Initialize world servers for _, worldInfo := range s.config.WorldServers { world := NewWorldServer(worldInfo) s.worldList.Add(world) } s.running = true s.startTime = time.Now() log.Println("Login server started successfully") return nil } // Stop stops the login server gracefully func (s *Server) Stop() error { s.mu.Lock() defer s.mu.Unlock() if !s.running { return nil } log.Println("Stopping login server...") // Signal shutdown close(s.stopChan) // Stop UDP server s.udpServer.Stop() // Stop web server if s.webServer != nil { ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() if err := s.webServer.Shutdown(ctx); err != nil { log.Printf("Error stopping web server: %v", err) } } // Disconnect all clients s.clientList.DisconnectAll("Server shutdown") // Shutdown world servers s.worldList.Shutdown() // Close database if err := s.database.Close(); err != nil { log.Printf("Error closing database: %v", err) } // Wait for all goroutines to finish s.wg.Wait() s.running = false log.Println("Login server stopped") return nil } // Process runs the main server processing loop func (s *Server) Process() { ticker := time.NewTicker(time.Millisecond * 50) // 20 FPS processing defer ticker.Stop() statsTicker := time.NewTicker(time.Minute) // Statistics every minute defer statsTicker.Stop() for { select { case <-s.stopChan: return case <-ticker.C: // Process clients s.clientList.Process() // Process world servers s.worldList.Process() case <-statsTicker.C: // Update statistics s.updateStatistics() } } } // handleUDPPacket handles incoming UDP packets from clients func (s *Server) handleUDPPacket(conn *udp.Connection, packet *udp.ApplicationPacket) { // Find or create client for this connection client := s.clientList.GetByConnection(conn) if client == nil { client = NewClient(conn, s.database) s.clientList.Add(client) log.Printf("New client connected from %s", conn.GetClientAddr()) } // Process packet if err := client.HandlePacket(packet.Data); err != nil { log.Printf("Error handling packet from %s: %v", conn.GetClientAddr(), err) } } // updateStatistics updates server statistics func (s *Server) updateStatistics() { s.mu.RLock() defer s.mu.RUnlock() if !s.running { return } clientCount := s.clientList.Count() worldCount := s.worldList.Count() uptime := time.Since(s.startTime) log.Printf("Stats - Clients: %d, Worlds: %d, Uptime: %v", clientCount, worldCount, uptime.Truncate(time.Second)) // Update database statistics if err := s.database.UpdateServerStats("login", clientCount, worldCount); err != nil { log.Printf("Failed to update server stats: %v", err) } } // initWebServer initializes the web interface server func (s *Server) initWebServer() { mux := http.NewServeMux() // Register web routes mux.HandleFunc("/", s.handleWebRoot) mux.HandleFunc("/api/status", s.handleAPIStatus) mux.HandleFunc("/api/clients", s.handleAPIClients) mux.HandleFunc("/api/worlds", s.handleAPIWorlds) s.webServer = &http.Server{ Addr: fmt.Sprintf("%s:%d", s.config.WebAddr, s.config.WebPort), Handler: mux, ReadTimeout: 30 * time.Second, WriteTimeout: 30 * time.Second, IdleTimeout: 120 * time.Second, } } // IsRunning returns whether the server is currently running func (s *Server) IsRunning() bool { s.mu.RLock() defer s.mu.RUnlock() return s.running } // GetUptime returns how long the server has been running func (s *Server) GetUptime() time.Duration { s.mu.RLock() defer s.mu.RUnlock() if !s.running { return 0 } return time.Since(s.startTime) } // GetClientCount returns the current number of connected clients func (s *Server) GetClientCount() int { return s.clientList.Count() } // GetWorldCount returns the current number of registered world servers func (s *Server) GetWorldCount() int { return s.worldList.Count() }