387 lines
9.4 KiB
Go
387 lines
9.4 KiB
Go
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
|
|
}
|