eq2go/internal/login/world_list.go

317 lines
7.2 KiB
Go

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
}