845 lines
21 KiB
Go
845 lines
21 KiB
Go
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
|
|
}
|