eq2go/internal/world/zone_list.go

372 lines
7.7 KiB
Go

package world
import (
"fmt"
"sync"
"time"
)
// ZoneServer represents a single zone instance
type ZoneServer struct {
ID int32
Name string
InstanceID int32
ZoneFile string
Description string
MOTD string
// Zone properties
MinLevel int16
MaxLevel int16
MinVersion int16
XPModifier float32
CityZone bool
WeatherAllowed bool
// Safe location
SafeX float32
SafeY float32
SafeZ float32
SafeHeading float32
// Zone state
IsRunning bool
IsShuttingDown bool
Population int32
CreatedTime time.Time
// Clients in zone
clients map[int32]*Client
clientMutex sync.RWMutex
// Zone processing
lastProcess time.Time
processInterval time.Duration
mutex sync.RWMutex
}
// ZoneList manages all active zones
type ZoneList struct {
zones map[int32]*ZoneServer
zonesByName map[string][]*ZoneServer // Multiple instances per zone name
instances map[int32]*ZoneServer // Instance ID to zone mapping
nextInstanceID int32
mutex sync.RWMutex
}
// NewZoneList creates a new zone list
func NewZoneList() *ZoneList {
return &ZoneList{
zones: make(map[int32]*ZoneServer),
zonesByName: make(map[string][]*ZoneServer),
instances: make(map[int32]*ZoneServer),
nextInstanceID: 1,
}
}
// Add adds a zone to the list
func (zl *ZoneList) Add(zone *ZoneServer) error {
zl.mutex.Lock()
defer zl.mutex.Unlock()
if _, exists := zl.zones[zone.ID]; exists {
return fmt.Errorf("zone with ID %d already exists", zone.ID)
}
// Assign instance ID if not set
if zone.InstanceID == 0 {
zone.InstanceID = zl.nextInstanceID
zl.nextInstanceID++
}
// Add to maps
zl.zones[zone.ID] = zone
zl.zonesByName[zone.Name] = append(zl.zonesByName[zone.Name], zone)
zl.instances[zone.InstanceID] = zone
zone.CreatedTime = time.Now()
zone.IsRunning = true
return nil
}
// Remove removes a zone from the list
func (zl *ZoneList) Remove(zoneID int32) {
zl.mutex.Lock()
defer zl.mutex.Unlock()
zone, exists := zl.zones[zoneID]
if !exists {
return
}
// Remove from zones map
delete(zl.zones, zoneID)
// Remove from instances map
delete(zl.instances, zone.InstanceID)
// Remove from name map
if zones, ok := zl.zonesByName[zone.Name]; ok {
newZones := make([]*ZoneServer, 0, len(zones)-1)
for _, z := range zones {
if z.ID != zoneID {
newZones = append(newZones, z)
}
}
if len(newZones) > 0 {
zl.zonesByName[zone.Name] = newZones
} else {
delete(zl.zonesByName, zone.Name)
}
}
}
// GetByID returns a zone by its ID
func (zl *ZoneList) GetByID(zoneID int32) *ZoneServer {
zl.mutex.RLock()
defer zl.mutex.RUnlock()
return zl.zones[zoneID]
}
// GetByName returns all zones with the given name
func (zl *ZoneList) GetByName(name string) []*ZoneServer {
zl.mutex.RLock()
defer zl.mutex.RUnlock()
zones := zl.zonesByName[name]
result := make([]*ZoneServer, len(zones))
copy(result, zones)
return result
}
// GetByInstanceID returns a zone by its instance ID
func (zl *ZoneList) GetByInstanceID(instanceID int32) *ZoneServer {
zl.mutex.RLock()
defer zl.mutex.RUnlock()
return zl.instances[instanceID]
}
// GetByLowestPopulation returns the zone instance with the lowest population
func (zl *ZoneList) GetByLowestPopulation(zoneName string) *ZoneServer {
zl.mutex.RLock()
defer zl.mutex.RUnlock()
zones := zl.zonesByName[zoneName]
if len(zones) == 0 {
return nil
}
lowestPop := zones[0]
for _, zone := range zones[1:] {
if zone.Population < lowestPop.Population && zone.IsRunning && !zone.IsShuttingDown {
lowestPop = zone
}
}
return lowestPop
}
// Count returns the total number of zones
func (zl *ZoneList) Count() int32 {
zl.mutex.RLock()
defer zl.mutex.RUnlock()
return int32(len(zl.zones))
}
// CountInstances returns the number of instance zones
func (zl *ZoneList) CountInstances() int32 {
zl.mutex.RLock()
defer zl.mutex.RUnlock()
count := int32(0)
for _, zone := range zl.zones {
if zone.InstanceID > 0 {
count++
}
}
return count
}
// GetTotalPopulation returns the total population across all zones
func (zl *ZoneList) GetTotalPopulation() int32 {
zl.mutex.RLock()
defer zl.mutex.RUnlock()
total := int32(0)
for _, zone := range zl.zones {
total += zone.Population
}
return total
}
// ProcessAll processes all zones
func (zl *ZoneList) ProcessAll() {
zl.mutex.RLock()
zones := make([]*ZoneServer, 0, len(zl.zones))
for _, zone := range zl.zones {
zones = append(zones, zone)
}
zl.mutex.RUnlock()
for _, zone := range zones {
if zone.IsRunning && !zone.IsShuttingDown {
zone.Process()
}
}
}
// SendTimeUpdate sends time update to all zones
func (zl *ZoneList) SendTimeUpdate(worldTime *WorldTime) {
zl.mutex.RLock()
defer zl.mutex.RUnlock()
for _, zone := range zl.zones {
if zone.IsRunning {
// TODO: Send time update packet to all clients in zone
}
}
}
// CheckHealth checks the health of all zones
func (zl *ZoneList) CheckHealth() {
zl.mutex.RLock()
zones := make([]*ZoneServer, 0, len(zl.zones))
for _, zone := range zl.zones {
zones = append(zones, zone)
}
zl.mutex.RUnlock()
now := time.Now()
for _, zone := range zones {
zone.mutex.Lock()
// Check if zone has been processing
if zone.IsRunning && now.Sub(zone.lastProcess) > 30*time.Second {
fmt.Printf("Warning: Zone %s (%d) has not processed in %v\n",
zone.Name, zone.ID, now.Sub(zone.lastProcess))
}
zone.mutex.Unlock()
}
}
// CleanupDead removes zones that are no longer running
func (zl *ZoneList) CleanupDead() {
zl.mutex.Lock()
defer zl.mutex.Unlock()
toRemove := make([]int32, 0)
for id, zone := range zl.zones {
if !zone.IsRunning && zone.Population == 0 {
toRemove = append(toRemove, id)
}
}
for _, id := range toRemove {
zl.Remove(id)
fmt.Printf("Cleaned up dead zone ID %d\n", id)
}
}
// ShutdownAll shuts down all zones
func (zl *ZoneList) ShutdownAll() {
zl.mutex.RLock()
zones := make([]*ZoneServer, 0, len(zl.zones))
for _, zone := range zl.zones {
zones = append(zones, zone)
}
zl.mutex.RUnlock()
for _, zone := range zones {
zone.Shutdown()
}
}
// Process handles zone processing
func (z *ZoneServer) Process() {
z.mutex.Lock()
defer z.mutex.Unlock()
if !z.IsRunning || z.IsShuttingDown {
return
}
now := time.Now()
if now.Sub(z.lastProcess) < z.processInterval {
return
}
z.lastProcess = now
// TODO: Implement zone processing
// - Process spawns
// - Process spell timers
// - Process movement
// - Process combat
// - Process respawns
// - Send updates to clients
}
// Shutdown gracefully shuts down the zone
func (z *ZoneServer) Shutdown() {
z.mutex.Lock()
defer z.mutex.Unlock()
if z.IsShuttingDown {
return
}
z.IsShuttingDown = true
// Notify all clients
z.clientMutex.RLock()
for _, client := range z.clients {
client.SendSimpleMessage("Zone is shutting down...")
}
z.clientMutex.RUnlock()
// TODO: Save zone state
// TODO: Disconnect all clients
// TODO: Clean up resources
z.IsRunning = false
}
// AddClient adds a client to the zone
func (z *ZoneServer) AddClient(client *Client) {
z.clientMutex.Lock()
defer z.clientMutex.Unlock()
if z.clients == nil {
z.clients = make(map[int32]*Client)
}
z.clients[client.CharacterID] = client
z.Population++
}
// RemoveClient removes a client from the zone
func (z *ZoneServer) RemoveClient(characterID int32) {
z.clientMutex.Lock()
defer z.clientMutex.Unlock()
if _, exists := z.clients[characterID]; exists {
delete(z.clients, characterID)
z.Population--
}
}
// GetClient returns a client by character ID
func (z *ZoneServer) GetClient(characterID int32) *Client {
z.clientMutex.RLock()
defer z.clientMutex.RUnlock()
return z.clients[characterID]
}