package player import ( "time" "eq2emu/internal/entity" ) // WasSentSpawn checks if a spawn was already sent to the player func (p *Player) WasSentSpawn(spawnID int32) bool { p.spawnMutex.Lock() defer p.spawnMutex.Unlock() if state, exists := p.spawnPacketSent[spawnID]; exists { return state == int8(SPAWN_STATE_SENT) } return false } // IsSendingSpawn checks if a spawn is currently being sent func (p *Player) IsSendingSpawn(spawnID int32) bool { p.spawnMutex.Lock() defer p.spawnMutex.Unlock() if state, exists := p.spawnPacketSent[spawnID]; exists { return state == int8(SPAWN_STATE_SENDING) } return false } // IsRemovingSpawn checks if a spawn is being removed func (p *Player) IsRemovingSpawn(spawnID int32) bool { p.spawnMutex.Lock() defer p.spawnMutex.Unlock() if state, exists := p.spawnPacketSent[spawnID]; exists { return state == int8(SPAWN_STATE_REMOVING) } return false } // SetSpawnSentState sets the spawn state for tracking func (p *Player) SetSpawnSentState(spawn *entity.Spawn, state SpawnState) bool { if spawn == nil { return false } p.spawnMutex.Lock() defer p.spawnMutex.Unlock() spawnID := spawn.GetDatabaseID() p.spawnPacketSent[spawnID] = int8(state) // Handle state-specific logic switch state { case SPAWN_STATE_SENT_WAIT: if queueState, exists := p.spawnStateList[spawnID]; exists { queueState.SpawnStateTimer = time.Now().Add(500 * time.Millisecond) } else { p.spawnStateList[spawnID] = &SpawnQueueState{ SpawnStateTimer: time.Now().Add(500 * time.Millisecond), IndexID: p.GetIndexForSpawn(spawn), } } case SPAWN_STATE_REMOVING_SLEEP: if queueState, exists := p.spawnStateList[spawnID]; exists { queueState.SpawnStateTimer = time.Now().Add(10 * time.Second) } else { p.spawnStateList[spawnID] = &SpawnQueueState{ SpawnStateTimer: time.Now().Add(10 * time.Second), IndexID: p.GetIndexForSpawn(spawn), } } } return true } // CheckSpawnStateQueue checks spawn states and updates as needed func (p *Player) CheckSpawnStateQueue() { p.spawnMutex.Lock() defer p.spawnMutex.Unlock() now := time.Now() for spawnID, queueState := range p.spawnStateList { if now.After(queueState.SpawnStateTimer) { if state, exists := p.spawnPacketSent[spawnID]; exists { switch SpawnState(state) { case SPAWN_STATE_SENT_WAIT: p.spawnPacketSent[spawnID] = int8(SPAWN_STATE_SENT) delete(p.spawnStateList, spawnID) case SPAWN_STATE_REMOVING_SLEEP: // TODO: Remove spawn from index p.spawnPacketSent[spawnID] = int8(SPAWN_STATE_REMOVED) delete(p.spawnStateList, spawnID) } } } } } // GetSpawnWithPlayerID returns a spawn by player-specific ID func (p *Player) GetSpawnWithPlayerID(id int32) *entity.Spawn { p.indexMutex.RLock() defer p.indexMutex.RUnlock() if spawn, exists := p.playerSpawnIDMap[id]; exists { return spawn } return nil } // GetIDWithPlayerSpawn returns the player-specific ID for a spawn func (p *Player) GetIDWithPlayerSpawn(spawn *entity.Spawn) int32 { if spawn == nil { return 0 } p.indexMutex.RLock() defer p.indexMutex.RUnlock() if id, exists := p.playerSpawnReverseIDMap[spawn]; exists { return id } return 0 } // GetNextSpawnIndex returns the next available spawn index func (p *Player) GetNextSpawnIndex(spawn *entity.Spawn, setLock bool) int16 { if setLock { p.indexMutex.Lock() defer p.indexMutex.Unlock() } // Start from current index and find next available for i := p.spawnIndex + 1; i != p.spawnIndex; i++ { if i > 9999 { // Wrap around i = 1 } if _, exists := p.playerSpawnIDMap[int32(i)]; !exists { p.spawnIndex = i return i } } // If we've looped all the way around, increment and use it anyway p.spawnIndex++ if p.spawnIndex > 9999 { p.spawnIndex = 1 } return p.spawnIndex } // SetSpawnMap adds a spawn to the player's spawn map func (p *Player) SetSpawnMap(spawn *entity.Spawn) bool { if spawn == nil { return false } p.indexMutex.Lock() defer p.indexMutex.Unlock() // Check if spawn already has an ID if id, exists := p.playerSpawnReverseIDMap[spawn]; exists && id > 0 { return true } // Get next available index index := p.GetNextSpawnIndex(spawn, false) // Set bidirectional mapping p.playerSpawnIDMap[int32(index)] = spawn p.playerSpawnReverseIDMap[spawn] = int32(index) return true } // SetSpawnMapIndex sets a specific index for a spawn func (p *Player) SetSpawnMapIndex(spawn *entity.Spawn, index int32) { p.indexMutex.Lock() defer p.indexMutex.Unlock() p.playerSpawnIDMap[index] = spawn p.playerSpawnReverseIDMap[spawn] = index } // SetSpawnMapAndIndex sets spawn in map and returns the index func (p *Player) SetSpawnMapAndIndex(spawn *entity.Spawn) int16 { if spawn == nil { return 0 } p.indexMutex.Lock() defer p.indexMutex.Unlock() // Check if spawn already has an ID if id, exists := p.playerSpawnReverseIDMap[spawn]; exists && id > 0 { return int16(id) } // Get next available index index := p.GetNextSpawnIndex(spawn, false) // Set bidirectional mapping p.playerSpawnIDMap[int32(index)] = spawn p.playerSpawnReverseIDMap[spawn] = int32(index) return index } // GetSpawnByIndex returns a spawn by its player-specific index func (p *Player) GetSpawnByIndex(index int16) *entity.Spawn { return p.GetSpawnWithPlayerID(int32(index)) } // GetIndexForSpawn returns the player-specific index for a spawn func (p *Player) GetIndexForSpawn(spawn *entity.Spawn) int16 { return int16(p.GetIDWithPlayerSpawn(spawn)) } // WasSpawnRemoved checks if a spawn was removed func (p *Player) WasSpawnRemoved(spawn *entity.Spawn) bool { if spawn == nil { return false } p.spawnMutex.Lock() defer p.spawnMutex.Unlock() spawnID := spawn.GetDatabaseID() if state, exists := p.spawnPacketSent[spawnID]; exists { return state == int8(SPAWN_STATE_REMOVED) } return false } // ResetSpawnPackets resets spawn packet state for a spawn func (p *Player) ResetSpawnPackets(id int32) { p.spawnMutex.Lock() defer p.spawnMutex.Unlock() delete(p.spawnPacketSent, id) delete(p.spawnStateList, id) } // RemoveSpawn removes a spawn from the player's view func (p *Player) RemoveSpawn(spawn *entity.Spawn, deleteSpawn bool) { if spawn == nil { return } // Get the player index for this spawn index := p.GetIDWithPlayerSpawn(spawn) if index == 0 { return } // Remove from spawn maps p.indexMutex.Lock() delete(p.playerSpawnIDMap, index) delete(p.playerSpawnReverseIDMap, spawn) p.indexMutex.Unlock() // Remove spawn packets spawnID := spawn.GetDatabaseID() p.infoMutex.Lock() delete(p.spawnInfoPacketList, spawnID) p.infoMutex.Unlock() p.visMutex.Lock() delete(p.spawnVisPacketList, spawnID) p.visMutex.Unlock() p.posMutex.Lock() delete(p.spawnPosPacketList, spawnID) p.posMutex.Unlock() // Reset spawn state p.ResetSpawnPackets(spawnID) // TODO: Send despawn packet to client if deleteSpawn { // TODO: Actually delete the spawn if requested } } // ShouldSendSpawn determines if a spawn should be sent to player func (p *Player) ShouldSendSpawn(spawn *entity.Spawn) bool { if spawn == nil { return false } // Don't send self if spawn == &p.Entity.Spawn { return false } // Check if already sent if p.WasSentSpawn(spawn.GetDatabaseID()) { return false } // Check distance distance := p.GetDistance(spawn) maxDistance := float32(200.0) // TODO: Get from rule system if distance > maxDistance { return false } // TODO: Check visibility flags, stealth, etc. return true } // SetSpawnDeleteTime sets the time when a spawn should be deleted func (p *Player) SetSpawnDeleteTime(id int32, deleteTime int32) { // TODO: Implement spawn deletion timer } // GetSpawnDeleteTime gets the deletion time for a spawn func (p *Player) GetSpawnDeleteTime(id int32) int32 { // TODO: Implement spawn deletion timer return 0 } // ClearRemovalTimers clears all spawn removal timers func (p *Player) ClearRemovalTimers() { // TODO: Implement spawn deletion timer clearing } // ResetSavedSpawns resets all saved spawn data func (p *Player) ResetSavedSpawns() { p.indexMutex.Lock() p.playerSpawnIDMap = make(map[int32]*entity.Spawn) p.playerSpawnReverseIDMap = make(map[*entity.Spawn]int32) // Re-add self p.playerSpawnIDMap[1] = &p.Entity.Spawn p.playerSpawnReverseIDMap[&p.Entity.Spawn] = 1 p.indexMutex.Unlock() p.spawnMutex.Lock() p.spawnPacketSent = make(map[int32]int8) p.spawnStateList = make(map[int32]*SpawnQueueState) p.spawnMutex.Unlock() p.infoMutex.Lock() p.spawnInfoPacketList = make(map[int32]string) p.infoMutex.Unlock() p.visMutex.Lock() p.spawnVisPacketList = make(map[int32]string) p.visMutex.Unlock() p.posMutex.Lock() p.spawnPosPacketList = make(map[int32]string) p.posMutex.Unlock() } // IsSpawnInRangeList checks if a spawn is in the range list func (p *Player) IsSpawnInRangeList(spawnID int32) bool { p.spawnAggroRangeMutex.RLock() defer p.spawnAggroRangeMutex.RUnlock() _, exists := p.playerAggroRangeSpawns[spawnID] return exists } // SetSpawnInRangeList sets whether a spawn is in range func (p *Player) SetSpawnInRangeList(spawnID int32, inRange bool) { p.spawnAggroRangeMutex.Lock() defer p.spawnAggroRangeMutex.Unlock() if inRange { p.playerAggroRangeSpawns[spawnID] = true } else { delete(p.playerAggroRangeSpawns, spawnID) } } // ProcessSpawnRangeUpdates processes spawn range updates func (p *Player) ProcessSpawnRangeUpdates() { // TODO: Implement spawn range update processing // This would check all spawns in range and update visibility }