eq2go/cmd/login_server/world_server.go

357 lines
8.4 KiB
Go

package main
import (
"fmt"
"log"
"sync"
"time"
)
// WorldServer represents a game world server
type WorldServer struct {
ID int32
Name string
Description string
IPAddress string
Port int
Status string
Population int32
PopulationLevel uint8
Locked bool
Hidden bool
Online bool
CreatedDate time.Time
LastUpdate time.Time
}
// WorldServerStats holds runtime statistics
type WorldServerStats struct {
Population int32
ZonesActive int32
PlayersOnline int32
UptimeSeconds int64
}
// WorldList manages all world servers
type WorldList struct {
servers map[int32]*WorldServer
mutex sync.RWMutex
database *Database
updateTicker *time.Ticker
stopChan chan struct{}
}
// NewWorldList creates a new world list manager
func NewWorldList(database *Database) *WorldList {
return &WorldList{
servers: make(map[int32]*WorldServer),
database: database,
stopChan: make(chan struct{}),
}
}
// Start begins world server monitoring
func (wl *WorldList) Start() {
log.Println("Starting world list monitoring...")
// Load world servers from database
if err := wl.LoadFromDatabase(); err != nil {
log.Printf("Failed to load world servers: %v", err)
}
// Start periodic updates
wl.updateTicker = time.NewTicker(30 * time.Second)
go func() {
for {
select {
case <-wl.updateTicker.C:
wl.UpdateStats()
case <-wl.stopChan:
return
}
}
}()
}
// Stop shuts down world server monitoring
func (wl *WorldList) Stop() {
log.Println("Stopping world list monitoring...")
if wl.updateTicker != nil {
wl.updateTicker.Stop()
}
close(wl.stopChan)
}
// LoadFromDatabase loads world servers from the database
func (wl *WorldList) LoadFromDatabase() error {
servers, err := wl.database.GetWorldServers()
if err != nil {
return fmt.Errorf("failed to load world servers: %w", err)
}
wl.mutex.Lock()
defer wl.mutex.Unlock()
// Clear existing servers
wl.servers = make(map[int32]*WorldServer)
// Add loaded servers
for _, server := range servers {
wl.servers[server.ID] = server
log.Printf("Loaded world server: %s (ID: %d)", server.Name, server.ID)
}
log.Printf("Loaded %d world servers", len(servers))
return nil
}
// GetActiveWorlds returns all online world servers
func (wl *WorldList) GetActiveWorlds() []*WorldServer {
wl.mutex.RLock()
defer wl.mutex.RUnlock()
var active []*WorldServer
for _, server := range wl.servers {
if server.Online && !server.Hidden {
active = append(active, server)
}
}
return active
}
// GetWorld returns a specific world server by ID
func (wl *WorldList) GetWorld(id int32) *WorldServer {
wl.mutex.RLock()
defer wl.mutex.RUnlock()
return wl.servers[id]
}
// GetActiveCount returns the number of online world servers
func (wl *WorldList) GetActiveCount() int {
wl.mutex.RLock()
defer wl.mutex.RUnlock()
count := 0
for _, server := range wl.servers {
if server.Online {
count++
}
}
return count
}
// UpdateServerStatus updates a world server's status
func (wl *WorldList) UpdateServerStatus(id int32, online bool, population int32, locked bool) {
wl.mutex.Lock()
defer wl.mutex.Unlock()
server, exists := wl.servers[id]
if !exists {
log.Printf("Attempted to update unknown server ID: %d", id)
return
}
server.Online = online
server.Population = population
server.Locked = locked
server.LastUpdate = time.Now()
// Update population level
server.PopulationLevel = wl.calculatePopulationLevel(population)
if online {
server.Status = "online"
} else {
server.Status = "offline"
}
log.Printf("Updated server %s: online=%t, population=%d, locked=%t",
server.Name, online, population, locked)
}
// calculatePopulationLevel converts population to display level
func (wl *WorldList) calculatePopulationLevel(population int32) uint8 {
switch {
case population >= 1000:
return 3 // Full
case population >= 500:
return 2 // High
case population >= 100:
return 1 // Medium
default:
return 0 // Low
}
}
// UpdateStats updates world server statistics
func (wl *WorldList) UpdateStats() {
wl.mutex.RLock()
servers := make([]*WorldServer, 0, len(wl.servers))
for _, server := range wl.servers {
servers = append(servers, server)
}
wl.mutex.RUnlock()
// Update statistics for each server
for _, server := range servers {
if server.Online {
stats := &WorldServerStats{
Population: server.Population,
ZonesActive: 0, // Would be updated by world server
PlayersOnline: server.Population,
UptimeSeconds: int64(time.Since(server.LastUpdate).Seconds()),
}
if err := wl.database.UpdateWorldServerStats(server.ID, stats); err != nil {
log.Printf("Failed to update stats for server %d: %v", server.ID, err)
}
}
}
}
// SendPlayRequest sends a character play request to a world server
func (wl *WorldList) SendPlayRequest(world *WorldServer, accountID, charID int32) error {
// In a real implementation, this would establish communication with the world server
// and send the play request. For now, we'll simulate the response.
log.Printf("Sending play request to world server %s for account %d, character %d",
world.Name, accountID, charID)
// Simulate world server response after a short delay
go func() {
time.Sleep(100 * time.Millisecond)
// For demonstration, we'll always succeed
// In reality, the world server would validate the character and respond
accessKey := generateAccessKey()
// This would normally come from the world server's response
wl.HandlePlayResponse(world.ID, accountID, charID, true,
world.IPAddress, world.Port, accessKey)
}()
return nil
}
// HandlePlayResponse processes a play response from a world server
func (wl *WorldList) HandlePlayResponse(worldID, accountID, charID int32,
success bool, ipAddress string, port int, accessKey int32) {
// Find the client that requested this
// This is simplified - in reality you'd track pending requests
log.Printf("Play response from world %d: success=%t, access_key=%d",
worldID, success, accessKey)
// Send response to appropriate client
// This would need to be implemented with proper client tracking
}
// generateAccessKey generates a random access key for world server connections
func generateAccessKey() int32 {
return int32(time.Now().UnixNano() & 0x7FFFFFFF)
}
// AddServer adds a new world server (for dynamic registration)
func (wl *WorldList) AddServer(server *WorldServer) {
wl.mutex.Lock()
defer wl.mutex.Unlock()
wl.servers[server.ID] = server
log.Printf("Added world server: %s (ID: %d)", server.Name, server.ID)
}
// RemoveServer removes a world server
func (wl *WorldList) RemoveServer(id int32) {
wl.mutex.Lock()
defer wl.mutex.Unlock()
if server, exists := wl.servers[id]; exists {
delete(wl.servers, id)
log.Printf("Removed world server: %s (ID: %d)", server.Name, id)
}
}
// GetServerList returns a formatted server list for client packets
func (wl *WorldList) GetServerList() []byte {
wl.mutex.RLock()
defer wl.mutex.RUnlock()
// Build server list packet data
data := make([]byte, 0, 1024)
// Count active servers
activeCount := 0
for _, server := range wl.servers {
if !server.Hidden {
activeCount++
}
}
// Add server count
data = append(data, byte(activeCount))
// Add server data
for _, server := range wl.servers {
if server.Hidden {
continue
}
// Server ID (4 bytes)
serverIDBytes := make([]byte, 4)
serverIDBytes[0] = byte(server.ID)
serverIDBytes[1] = byte(server.ID >> 8)
serverIDBytes[2] = byte(server.ID >> 16)
serverIDBytes[3] = byte(server.ID >> 24)
data = append(data, serverIDBytes...)
// Server name (null-terminated)
data = append(data, []byte(server.Name)...)
data = append(data, 0)
// Server flags
var flags byte
if server.Online {
flags |= 0x01
}
if server.Locked {
flags |= 0x02
}
data = append(data, flags)
// Population level
data = append(data, server.PopulationLevel)
}
return data
}
// GetStats returns world list statistics
func (wl *WorldList) GetStats() map[string]any {
wl.mutex.RLock()
defer wl.mutex.RUnlock()
totalServers := len(wl.servers)
onlineServers := 0
totalPopulation := int32(0)
for _, server := range wl.servers {
if server.Online {
onlineServers++
totalPopulation += server.Population
}
}
return map[string]any{
"total_servers": totalServers,
"online_servers": onlineServers,
"offline_servers": totalServers - onlineServers,
"total_population": totalPopulation,
}
}