317 lines
7.2 KiB
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
|
|
} |