package zone import ( "fmt" "log" "time" "eq2emu/internal/common" "eq2emu/internal/spawn" ) // ZoneServerConfig holds configuration for creating a zone server type ZoneServerConfig struct { ZoneName string ZoneFile string ZoneSkyFile string ZoneDescription string ZoneID int32 InstanceID int32 InstanceType InstanceType DatabasePath string MaxPlayers int32 MinLevel int16 MaxLevel int16 SafeX float32 SafeY float32 SafeZ float32 SafeHeading float32 LoadMaps bool EnableWeather bool EnablePathfinding bool } // Initialize initializes the zone server with all required systems func (zs *ZoneServer) Initialize(config *ZoneServerConfig) error { zs.masterZoneLock.Lock() defer zs.masterZoneLock.Unlock() if zs.isInitialized.Load() { return fmt.Errorf("zone server already initialized") } log.Printf("%s Initializing zone server '%s' (ID: %d)", LogPrefixZone, config.ZoneName, config.ZoneID) // Set basic configuration zs.zoneName = config.ZoneName zs.zoneFile = config.ZoneFile zs.zoneSkyFile = config.ZoneSkyFile zs.zoneDescription = config.ZoneDescription zs.zoneID = config.ZoneID zs.instanceID = config.InstanceID zs.instanceType = config.InstanceType zs.isInstance = config.InstanceType != InstanceTypeNone // Set zone limits zs.minimumLevel = config.MinLevel zs.maximumLevel = config.MaxLevel // Set safe coordinates zs.safeX = config.SafeX zs.safeY = config.SafeY zs.safeZ = config.SafeZ zs.safeHeading = config.SafeHeading // Initialize timers if err := zs.initializeTimers(); err != nil { return fmt.Errorf("failed to initialize timers: %v", err) } // Load zone data from database if err := zs.loadZoneData(); err != nil { return fmt.Errorf("failed to load zone data: %v", err) } // Initialize pathfinding if enabled if config.EnablePathfinding { if err := zs.initializePathfinding(); err != nil { log.Printf("%s Warning: failed to initialize pathfinding: %v", LogPrefixZone, err) // Don't fail initialization, just log warning } } // Load maps if enabled if config.LoadMaps { if err := zs.loadZoneMaps(); err != nil { log.Printf("%s Warning: failed to load zone maps: %v", LogPrefixZone, err) // Don't fail initialization, just log warning } } // Initialize weather if enabled if config.EnableWeather { zs.initializeWeather() } // Initialize movement manager zs.movementMgr = NewMobMovementManager(zs) // Start processing threads zs.startProcessingThreads() zs.loadingData = false zs.isInitialized.Store(true) log.Printf("%s Zone server '%s' initialized successfully", LogPrefixZone, zs.zoneName) return nil } // Process performs the main zone processing loop func (zs *ZoneServer) Process() error { if zs.zoneShuttingDown.Load() { return fmt.Errorf("zone is shutting down") } if !zs.isInitialized.Load() { return fmt.Errorf("zone not initialized") } // Update watchdog timestamp zs.watchdogTimestamp = int32(time.Now().Unix()) // Process clients zs.processClients() // Process spawns zs.processSpawns() // Process timers zs.processTimers() // Process movement if zs.movementMgr != nil { zs.movementMgr.Process() } // Process spell effects if zs.spellProcess != nil { zs.spellProcess.ProcessSpellEffects() } // Process pending spawn changes zs.processSpawnChanges() // Process proximity checks zs.processProximityChecks() // Process weather updates zs.processWeather() // Clean up expired data zs.cleanupExpiredData() return nil } // SpawnProcess performs spawn-specific processing func (zs *ZoneServer) SpawnProcess() error { if zs.zoneShuttingDown.Load() { return fmt.Errorf("zone is shutting down") } // Process spawn locations for respawns zs.processSpawnLocations() // Process spawn movement zs.processSpawnMovement() // Process NPC AI zs.processNPCAI() // Process combat zs.processCombat() // Check for dead spawns to remove zs.checkDeadSpawnRemoval() // Process spawn script timers zs.processSpawnScriptTimers() return nil } // AddClient adds a client to the zone func (zs *ZoneServer) AddClient(client Client) error { if zs.zoneShuttingDown.Load() { return fmt.Errorf("zone is shutting down") } zs.clientListLock.Lock() defer zs.clientListLock.Unlock() // Check zone capacity if len(zs.clients) >= int(zs.GetMaxPlayers()) { return fmt.Errorf("zone is full") } // Check client requirements if !zs.canClientEnter(client) { return fmt.Errorf("client does not meet zone requirements") } // Add client to list zs.clients = append(zs.clients, client) zs.numPlayers = int32(len(zs.clients)) zs.lifetimeClientCount++ log.Printf("%s Client %s entered zone '%s' (%d/%d players)", LogPrefixZone, client.GetPlayerName(), zs.zoneName, zs.numPlayers, zs.GetMaxPlayers()) // Initialize client in zone go zs.initializeClientInZone(client) return nil } // RemoveClient removes a client from the zone func (zs *ZoneServer) RemoveClient(client Client) { zs.clientListLock.Lock() defer zs.clientListLock.Unlock() // Find and remove client for i, c := range zs.clients { if c.GetID() == client.GetID() { // Remove from slice zs.clients = append(zs.clients[:i], zs.clients[i+1:]...) zs.numPlayers = int32(len(zs.clients)) log.Printf("%s Client %s left zone '%s' (%d/%d players)", LogPrefixZone, client.GetPlayerName(), zs.zoneName, zs.numPlayers, zs.GetMaxPlayers()) // Clean up client-specific data zs.cleanupClientData(client) break } } } // AddSpawn adds a spawn to the zone func (zs *ZoneServer) AddSpawn(spawn *spawn.Spawn) error { if zs.zoneShuttingDown.Load() { return fmt.Errorf("zone is shutting down") } zs.spawnListLock.Lock() defer zs.spawnListLock.Unlock() // Add to spawn list zs.spawnList[spawn.GetID()] = spawn // Add to appropriate grid zs.addSpawnToGrid(spawn) // Mark as changed for client updates zs.markSpawnChanged(spawn.GetID()) log.Printf("%s Added spawn '%s' (ID: %d) to zone '%s'", LogPrefixZone, spawn.GetName(), spawn.GetID(), zs.zoneName) return nil } // RemoveSpawn removes a spawn from the zone func (zs *ZoneServer) RemoveSpawn(spawnID int32, deleteSpawn bool) error { zs.spawnListLock.Lock() defer zs.spawnListLock.Unlock() spawn, exists := zs.spawnList[spawnID] if !exists { return fmt.Errorf("spawn %d not found", spawnID) } // Remove from grids zs.removeSpawnFromGrid(spawn) // Clean up spawn data zs.cleanupSpawnData(spawn) // Remove from spawn list delete(zs.spawnList, spawnID) // Mark for client removal zs.markSpawnForRemoval(spawnID) log.Printf("%s Removed spawn '%s' (ID: %d) from zone '%s'", LogPrefixZone, spawn.GetName(), spawn.GetID(), zs.zoneName) return nil } // GetSpawn retrieves a spawn by ID func (zs *ZoneServer) GetSpawn(spawnID int32) *spawn.Spawn { zs.spawnListLock.RLock() defer zs.spawnListLock.RUnlock() return zs.spawnList[spawnID] } // GetSpawnsByRange retrieves all spawns within range of a position func (zs *ZoneServer) GetSpawnsByRange(x, y, z, maxRange float32) []*spawn.Spawn { zs.spawnListLock.RLock() defer zs.spawnListLock.RUnlock() var nearbySpawns []*spawn.Spawn maxRangeSquared := maxRange * maxRange for _, spawn := range zs.spawnList { spawnX, spawnY, spawnZ, _ := spawn.GetPosition() distSquared := Distance3DSquared(x, y, z, spawnX, spawnY, spawnZ) if distSquared <= maxRangeSquared { nearbySpawns = append(nearbySpawns, spawn) } } return nearbySpawns } // GetGridsByLocation retrieves grid IDs that contain the specified location func (zs *ZoneServer) GetGridsByLocation(x, y, z, distance float32) []int32 { // Calculate grid boundaries minGridX := int32((x - distance) / DefaultGridSize) maxGridX := int32((x + distance) / DefaultGridSize) minGridY := int32((y - distance) / DefaultGridSize) maxGridY := int32((y + distance) / DefaultGridSize) var gridIDs []int32 for gridX := minGridX; gridX <= maxGridX; gridX++ { for gridY := minGridY; gridY <= maxGridY; gridY++ { gridID := gridX*1000 + gridY // Simple grid ID calculation if gridID >= 0 && gridID <= MaxGridID { gridIDs = append(gridIDs, gridID) } } } return gridIDs } // SendZoneSpawns sends all visible spawns to a client func (zs *ZoneServer) SendZoneSpawns(client Client) error { playerX, playerY, playerZ, _, _ := client.GetPosition() // Get spawns in range spawns := zs.GetSpawnsByRange(playerX, playerY, playerZ, SendSpawnDistance) log.Printf("%s Sending %d spawns to client %s", LogPrefixZone, len(spawns), client.GetPlayerName()) // Send each spawn for _, spawn := range spawns { if client.CanSeeSpawn(spawn) { zs.sendSpawnToClient(client, spawn) } } return nil } // ProcessWeather handles zone-wide weather changes func (zs *ZoneServer) ProcessWeather() { if !zs.weatherEnabled || !zs.weatherAllowed { return } currentTime := int32(time.Now().Unix()) // Check if it's time for a weather change if currentTime-zs.weatherLastChangedTime < zs.weatherFrequency { return } // Roll for weather change if zs.weatherChangeChance > 0 { // Simple random roll (0-100) roll := currentTime % 100 if int8(roll) > zs.weatherChangeChance { return } } // Calculate weather change var change float32 switch zs.weatherType { case WeatherTypeNormal: change = zs.weatherChangeAmount case WeatherTypeDynamic: // Dynamic weather with random offset change = zs.weatherChangeAmount + (float32(currentTime%100)/100.0-0.5)*zs.weatherDynamicOffset case WeatherTypeRandom: // Completely random change change = (float32(currentTime%100)/100.0 - 0.5) * 2.0 * zs.weatherMaxSeverity case WeatherTypeChaotic: // Chaotic weather with large swings change = (float32(currentTime%100)/100.0 - 0.5) * 4.0 * zs.weatherMaxSeverity } // Apply pattern switch zs.weatherPattern { case WeatherPatternDecreasing: change = -abs(change) case WeatherPatternIncreasing: change = abs(change) // WeatherPatternRandom uses calculated change as-is } // Update weather severity newSeverity := zs.weatherCurrentSeverity + change // Clamp to bounds if newSeverity < zs.weatherMinSeverity { newSeverity = zs.weatherMinSeverity } if newSeverity > zs.weatherMaxSeverity { newSeverity = zs.weatherMaxSeverity } // Check if severity actually changed if newSeverity != zs.weatherCurrentSeverity { zs.weatherCurrentSeverity = newSeverity zs.rain = newSeverity // Send weather update to all clients zs.sendWeatherUpdate() log.Printf("%s Weather changed to %.2f in zone '%s'", LogPrefixWeather, newSeverity, zs.zoneName) } zs.weatherLastChangedTime = currentTime } // SetRain sets the rain level in the zone func (zs *ZoneServer) SetRain(val float32) { zs.rain = val zs.weatherCurrentSeverity = val zs.sendWeatherUpdate() } // GetZoneID returns the zone ID func (zs *ZoneServer) GetZoneID() int32 { return zs.zoneID } // GetZoneName returns the zone name func (zs *ZoneServer) GetZoneName() string { return zs.zoneName } // GetInstanceID returns the instance ID func (zs *ZoneServer) GetInstanceID() int32 { return zs.instanceID } // GetInstanceType returns the instance type func (zs *ZoneServer) GetInstanceType() InstanceType { return zs.instanceType } // IsInstanceZone returns whether this is an instance zone func (zs *ZoneServer) IsInstanceZone() bool { return zs.isInstance } // GetNumPlayers returns the current number of players func (zs *ZoneServer) GetNumPlayers() int32 { return zs.numPlayers } // GetMaxPlayers returns the maximum number of players allowed func (zs *ZoneServer) GetMaxPlayers() int32 { if zs.isInstance { // Instance zones have different limits based on type switch zs.instanceType { case InstanceTypeGroupLockout, InstanceTypeGroupPersist: return 6 case InstanceTypeRaidLockout, InstanceTypeRaidPersist: return 24 case InstanceTypeSoloLockout, InstanceTypeSoloPersist: return 1 case InstanceTypeTradeskill, InstanceTypePublic: return DefaultMaxPlayers case InstanceTypePersonalHouse: return 10 case InstanceTypeGuildHouse: return 50 case InstanceTypeQuest: return 6 } } return DefaultMaxPlayers } // GetSafePosition returns the safe position coordinates func (zs *ZoneServer) GetSafePosition() (x, y, z, heading float32) { return zs.safeX, zs.safeY, zs.safeZ, zs.safeHeading } // SetSafePosition sets the safe position coordinates func (zs *ZoneServer) SetSafePosition(x, y, z, heading float32) { zs.safeX = x zs.safeY = y zs.safeZ = z zs.safeHeading = heading } // IsLocked returns whether the zone is locked func (zs *ZoneServer) IsLocked() bool { return zs.locked } // SetLocked sets the zone lock state func (zs *ZoneServer) SetLocked(locked bool) { zs.locked = locked if locked { log.Printf("%s Zone '%s' has been locked", LogPrefixZone, zs.zoneName) } else { log.Printf("%s Zone '%s' has been unlocked", LogPrefixZone, zs.zoneName) } } // GetWatchdogTime returns the last watchdog timestamp func (zs *ZoneServer) GetWatchdogTime() int32 { return zs.watchdogTimestamp } // Private helper methods func (zs *ZoneServer) initializeTimers() error { zs.aggroTimer = common.NewTimer(AggroCheckInterval) zs.charsheetChanges = common.NewTimer(CharsheetUpdateInterval) zs.clientSave = common.NewTimer(ClientSaveInterval) zs.locationProxTimer = common.NewTimer(LocationProximityInterval) zs.movementTimer = common.NewTimer(MovementUpdateInterval) zs.regenTimer = common.NewTimer(DefaultTimerInterval) zs.respawnTimer = common.NewTimer(RespawnCheckInterval) zs.shutdownTimer = common.NewTimer(0) // Disabled by default zs.spawnRangeTimer = common.NewTimer(SpawnRangeUpdateInterval) zs.spawnUpdateTimer = common.NewTimer(DefaultTimerInterval) zs.syncGameTimer = common.NewTimer(DefaultTimerInterval) zs.trackingTimer = common.NewTimer(TrackingUpdateInterval) zs.weatherTimer = common.NewTimer(WeatherUpdateInterval) zs.widgetTimer = common.NewTimer(WidgetUpdateInterval) return nil } func (zs *ZoneServer) loadZoneData() error { // TODO: Load zone data from database // This would include spawn locations, NPCs, objects, etc. log.Printf("%s Loading zone data for '%s'", LogPrefixZone, zs.zoneName) return nil } func (zs *ZoneServer) initializePathfinding() error { // TODO: Initialize pathfinding system log.Printf("%s Initializing pathfinding for zone '%s'", LogPrefixPathfind, zs.zoneName) return nil } func (zs *ZoneServer) loadZoneMaps() error { // TODO: Load zone maps and collision data log.Printf("%s Loading maps for zone '%s'", LogPrefixMap, zs.zoneName) return nil } func (zs *ZoneServer) initializeWeather() { zs.weatherEnabled = true zs.weatherType = WeatherTypeNormal zs.weatherFrequency = DefaultWeatherFrequency zs.weatherMinSeverity = DefaultWeatherMinSeverity zs.weatherMaxSeverity = DefaultWeatherMaxSeverity zs.weatherChangeAmount = DefaultWeatherChangeAmount zs.weatherDynamicOffset = DefaultWeatherDynamicOffset zs.weatherChangeChance = DefaultWeatherChangeChance zs.weatherPattern = WeatherPatternRandom zs.weatherCurrentSeverity = 0.0 zs.weatherLastChangedTime = int32(time.Now().Unix()) log.Printf("%s Weather system initialized for zone '%s'", LogPrefixWeather, zs.zoneName) } func (zs *ZoneServer) startProcessingThreads() { zs.spawnThreadActive = true zs.combatThreadActive = true zs.clientThreadActive = true log.Printf("%s Started processing threads for zone '%s'", LogPrefixZone, zs.zoneName) } func (zs *ZoneServer) canClientEnter(client Client) bool { // Check level requirements player := client.GetPlayer() if player != nil { level := player.GetLevel() if zs.minimumLevel > 0 && level < zs.minimumLevel { return false } if zs.maximumLevel > 0 && level > zs.maximumLevel { return false } } // Check client version version := client.GetClientVersion() if zs.minimumVersion > 0 && int16(version) < zs.minimumVersion { return false } // Check if zone is locked if zs.locked { return false } return true } func (zs *ZoneServer) initializeClientInZone(client Client) { // Send zone information zs.sendZoneInfo(client) // Send all visible spawns zs.SendZoneSpawns(client) // Send weather update if zs.weatherEnabled { zs.sendWeatherUpdateToClient(client) } // Send time update zs.sendTimeUpdateToClient(client) } func (zs *ZoneServer) processClients() { // Process each client zs.clientListLock.RLock() clients := make([]Client, len(zs.clients)) copy(clients, zs.clients) zs.clientListLock.RUnlock() for _, client := range clients { if client.IsLoadingZone() { continue } // Update spawn visibility zs.updateClientSpawnVisibility(client) } } func (zs *ZoneServer) processSpawns() { // Process spawn updates and changes zs.spawnListLock.RLock() spawns := make([]*spawn.Spawn, 0, len(zs.spawnList)) for _, spawn := range zs.spawnList { spawns = append(spawns, spawn) } zs.spawnListLock.RUnlock() for _, spawn := range spawns { // Process spawn logic here _ = spawn // Placeholder } } func (zs *ZoneServer) processTimers() { // Check and process all timers if zs.aggroTimer.Check() { zs.processAggroChecks() } if zs.respawnTimer.Check() { zs.processRespawns() } if zs.widgetTimer.Check() { zs.processWidgets() } // Add other timer checks... } func (zs *ZoneServer) processSpawnChanges() { zs.changedSpawnsLock.Lock() defer zs.changedSpawnsLock.Unlock() if len(zs.changedSpawns) == 0 { return } // Send changes to all clients zs.clientListLock.RLock() clients := make([]Client, len(zs.clients)) copy(clients, zs.clients) zs.clientListLock.RUnlock() for spawnID := range zs.changedSpawns { spawn := zs.GetSpawn(spawnID) if spawn != nil { for _, client := range clients { if client.CanSeeSpawn(spawn) { zs.sendSpawnUpdateToClient(client, spawn) } } } } // Clear changed spawns zs.changedSpawns = make(map[int32]bool) } func (zs *ZoneServer) processProximityChecks() { // Process player and location proximity if zs.locationProxTimer.Check() { zs.checkLocationProximity() zs.checkPlayerProximity() } } func (zs *ZoneServer) processWeather() { if zs.weatherTimer.Check() { zs.ProcessWeather() } } func (zs *ZoneServer) cleanupExpiredData() { // Clean up dead spawns, expired timers, etc. zs.cleanupDeadSpawns() zs.cleanupExpiredTimers() } // Helper functions for various processing tasks func (zs *ZoneServer) processSpawnLocations() { // TODO: Process spawn location respawns } func (zs *ZoneServer) processSpawnMovement() { // TODO: Process NPC movement } func (zs *ZoneServer) processNPCAI() { // TODO: Process NPC AI } func (zs *ZoneServer) processCombat() { // TODO: Process combat } func (zs *ZoneServer) checkDeadSpawnRemoval() { // TODO: Check for dead spawns to remove } func (zs *ZoneServer) processSpawnScriptTimers() { // TODO: Process spawn script timers } func (zs *ZoneServer) processAggroChecks() { // TODO: Process aggro checks } func (zs *ZoneServer) processRespawns() { // TODO: Process respawns } func (zs *ZoneServer) processWidgets() { // TODO: Process widget timers } func (zs *ZoneServer) checkLocationProximity() { // TODO: Check location proximity } func (zs *ZoneServer) checkPlayerProximity() { // TODO: Check player proximity } func (zs *ZoneServer) cleanupDeadSpawns() { // TODO: Clean up dead spawns } func (zs *ZoneServer) cleanupExpiredTimers() { // TODO: Clean up expired timers } func (zs *ZoneServer) cleanupClientData(client Client) { // TODO: Clean up client-specific data } func (zs *ZoneServer) cleanupSpawnData(spawn *spawn.Spawn) { // TODO: Clean up spawn-specific data } func (zs *ZoneServer) addSpawnToGrid(spawn *spawn.Spawn) { // TODO: Add spawn to grid system } func (zs *ZoneServer) removeSpawnFromGrid(spawn *spawn.Spawn) { // TODO: Remove spawn from grid system } func (zs *ZoneServer) markSpawnChanged(spawnID int32) { zs.changedSpawnsLock.Lock() defer zs.changedSpawnsLock.Unlock() zs.changedSpawns[spawnID] = true } func (zs *ZoneServer) markSpawnForRemoval(spawnID int32) { zs.pendingSpawnRemoveLock.Lock() defer zs.pendingSpawnRemoveLock.Unlock() zs.pendingSpawnRemove[spawnID] = true } func (zs *ZoneServer) sendSpawnToClient(client Client, spawn *spawn.Spawn) { // TODO: Send spawn packet to client } func (zs *ZoneServer) sendSpawnUpdateToClient(client Client, spawn *spawn.Spawn) { // TODO: Send spawn update packet to client } func (zs *ZoneServer) sendZoneInfo(client Client) { // TODO: Send zone info packet to client } func (zs *ZoneServer) sendWeatherUpdate() { // TODO: Send weather update to all clients } func (zs *ZoneServer) sendWeatherUpdateToClient(client Client) { // TODO: Send weather update to specific client } func (zs *ZoneServer) sendTimeUpdateToClient(client Client) { // TODO: Send time update to client } func (zs *ZoneServer) updateClientSpawnVisibility(client Client) { // TODO: Update spawn visibility for client } func abs(x float32) float32 { if x < 0 { return -x } return x }