package login import ( "log" "sync" "time" ) // WorldList manages connected world servers type WorldList struct { worlds map[int]*WorldServer byName map[string]*WorldServer mu sync.RWMutex heartbeatTicker *time.Ticker } // WorldServer represents a connected world server type WorldServer struct { info WorldServerInfo connection interface{} // TODO: Replace with actual connection type lastPing time.Time isConnected bool mu sync.RWMutex } // NewWorldList creates a new world list func NewWorldList() *WorldList { wl := &WorldList{ worlds: make(map[int]*WorldServer), byName: make(map[string]*WorldServer), heartbeatTicker: time.NewTicker(30 * time.Second), } return wl } // NewWorldServer creates a new world server instance func NewWorldServer(info WorldServerInfo) *WorldServer { return &WorldServer{ info: info, lastPing: time.Now(), isConnected: false, } } // Add adds a world server to the list func (wl *WorldList) Add(world *WorldServer) { if world == nil { return } wl.mu.Lock() defer wl.mu.Unlock() // Remove any existing world with the same ID if existing, exists := wl.worlds[world.info.ID]; exists { existing.Disconnect("Replaced by new registration") delete(wl.byName, existing.info.Name) } // Remove any existing world with the same name if existing, exists := wl.byName[world.info.Name]; exists { existing.Disconnect("Name conflict") delete(wl.worlds, existing.info.ID) } wl.worlds[world.info.ID] = world wl.byName[world.info.Name] = world log.Printf("Added world server: %s (ID: %d)", world.info.Name, world.info.ID) } // Remove removes a world server from the list func (wl *WorldList) Remove(world *WorldServer) { if world == nil { return } wl.mu.Lock() defer wl.mu.Unlock() delete(wl.worlds, world.info.ID) delete(wl.byName, world.info.Name) log.Printf("Removed world server: %s (ID: %d)", world.info.Name, world.info.ID) } // GetByID returns a world server by ID func (wl *WorldList) GetByID(id int) *WorldServer { wl.mu.RLock() defer wl.mu.RUnlock() return wl.worlds[id] } // GetByName returns a world server by name func (wl *WorldList) GetByName(name string) *WorldServer { wl.mu.RLock() defer wl.mu.RUnlock() return wl.byName[name] } // GetAvailableWorlds returns a list of worlds available for login func (wl *WorldList) GetAvailableWorlds() []WorldServerInfo { wl.mu.RLock() defer wl.mu.RUnlock() var available []WorldServerInfo for _, world := range wl.worlds { if !world.info.IsHidden() && world.IsOnline() { available = append(available, world.info) } } return available } // GetAllWorlds returns all world servers func (wl *WorldList) GetAllWorlds() []WorldServerInfo { wl.mu.RLock() defer wl.mu.RUnlock() worlds := make([]WorldServerInfo, 0, len(wl.worlds)) for _, world := range wl.worlds { worlds = append(worlds, world.info) } return worlds } // Process processes the world list func (wl *WorldList) Process() { select { case <-wl.heartbeatTicker.C: wl.checkHeartbeats() default: // No heartbeat check needed this cycle } } // checkHeartbeats checks for world servers that haven't sent heartbeats func (wl *WorldList) checkHeartbeats() { wl.mu.Lock() defer wl.mu.Unlock() timeout := 2 * time.Minute var toRemove []*WorldServer for _, world := range wl.worlds { if world.IsTimedOut(timeout) { toRemove = append(toRemove, world) } } // Remove timed out worlds for _, world := range toRemove { world.SetStatus("down") log.Printf("World server %s (ID: %d) timed out", world.info.Name, world.info.ID) } } // UpdateWorldStatus updates the status of a world server func (wl *WorldList) UpdateWorldStatus(id int, status string, population int) { world := wl.GetByID(id) if world == nil { return } world.UpdateStatus(status, population) log.Printf("World server %s (ID: %d) status updated: %s (%d players)", world.info.Name, world.info.ID, status, population) } // Count returns the number of registered world servers func (wl *WorldList) Count() int { wl.mu.RLock() defer wl.mu.RUnlock() return len(wl.worlds) } // Shutdown shuts down the world list func (wl *WorldList) Shutdown() { if wl.heartbeatTicker != nil { wl.heartbeatTicker.Stop() } wl.mu.Lock() defer wl.mu.Unlock() // Disconnect all world servers for _, world := range wl.worlds { world.Disconnect("Login server shutdown") } // Clear maps wl.worlds = make(map[int]*WorldServer) wl.byName = make(map[string]*WorldServer) } // WorldServer methods // GetInfo returns the world server information func (ws *WorldServer) GetInfo() WorldServerInfo { ws.mu.RLock() defer ws.mu.RUnlock() return ws.info } // UpdateInfo updates the world server information func (ws *WorldServer) UpdateInfo(info WorldServerInfo) { ws.mu.Lock() defer ws.mu.Unlock() ws.info = info } // UpdateStatus updates the world server status and population func (ws *WorldServer) UpdateStatus(status string, population int) { ws.mu.Lock() defer ws.mu.Unlock() ws.info.Status = status ws.info.Population = population ws.info.LastHeartbeat = time.Now().Unix() ws.lastPing = time.Now() } // SetStatus sets the world server status func (ws *WorldServer) SetStatus(status string) { ws.mu.Lock() defer ws.mu.Unlock() ws.info.Status = status } // IsOnline returns whether the world server is online func (ws *WorldServer) IsOnline() bool { ws.mu.RLock() defer ws.mu.RUnlock() return ws.info.IsOnline() && ws.isConnected } // IsConnected returns whether the world server is connected func (ws *WorldServer) IsConnected() bool { ws.mu.RLock() defer ws.mu.RUnlock() return ws.isConnected } // SetConnected sets the connection status func (ws *WorldServer) SetConnected(connected bool) { ws.mu.Lock() defer ws.mu.Unlock() ws.isConnected = connected if connected { ws.info.Status = "up" } else { ws.info.Status = "down" } } // IsTimedOut returns whether the world server has timed out func (ws *WorldServer) IsTimedOut(timeout time.Duration) bool { ws.mu.RLock() defer ws.mu.RUnlock() return time.Since(ws.lastPing) > timeout } // Disconnect disconnects the world server func (ws *WorldServer) Disconnect(reason string) { ws.mu.Lock() defer ws.mu.Unlock() log.Printf("Disconnecting world server %s (ID: %d): %s", ws.info.Name, ws.info.ID, reason) ws.isConnected = false ws.info.Status = "down" ws.info.Population = 0 // TODO: Close actual connection } // Ping updates the last ping time func (ws *WorldServer) Ping() { ws.mu.Lock() defer ws.mu.Unlock() ws.lastPing = time.Now() ws.info.LastHeartbeat = time.Now().Unix() } // GetLastPing returns the last ping time func (ws *WorldServer) GetLastPing() time.Time { ws.mu.RLock() defer ws.mu.RUnlock() return ws.lastPing } // CanAcceptPlayer returns whether the world server can accept a new player func (ws *WorldServer) CanAcceptPlayer() bool { ws.mu.RLock() defer ws.mu.RUnlock() if !ws.IsOnline() || ws.info.IsLocked() { return false } // Check population limit return ws.info.Population < ws.info.MaxPlayers } // GetConnectionString returns the connection string for this world server func (ws *WorldServer) GetConnectionString() (string, int) { ws.mu.RLock() defer ws.mu.RUnlock() return ws.info.Address, ws.info.Port }