372 lines
7.7 KiB
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]
|
|
} |