package entity import ( "fmt" "sync" "sync/atomic" "time" "eq2emu/internal/packets" ) // Manager provides centralized management for the entity system // following the SIMPLIFICATION.md methodology while maintaining C++ API compatibility type Manager struct { // Thread safety mutex sync.RWMutex // Performance monitoring stats struct { // Entity operations EntitiesCreated int64 EntitiesDestroyed int64 EntitiesUpdated int64 // Spell effect operations SpellEffectsAdded int64 SpellEffectsRemoved int64 BonusCalculations int64 // Combat operations DamageCalculations int64 HealingCalculations int64 CombatActions int64 // Packet operations PacketsSent int64 PacketErrors int64 VerbRequests int64 // Cache performance CacheHits int64 CacheMisses int64 } // Configuration config struct { EnableStatistics bool EnableValidation bool EnableCombatLogging bool MaxEntitiesPerZone int EffectCleanupInterval time.Duration BonusCalculationInterval time.Duration PacketBuilderTimeout time.Duration EnablePetManagement bool EnableSpellEffectCaching bool } } // NewManager creates a new entity manager with default configuration func NewManager() *Manager { m := &Manager{} // Set default configuration m.config.EnableStatistics = true m.config.EnableValidation = true m.config.EnableCombatLogging = false m.config.MaxEntitiesPerZone = 10000 m.config.EffectCleanupInterval = 30 * time.Second m.config.BonusCalculationInterval = 5 * time.Second m.config.PacketBuilderTimeout = 30 * time.Second m.config.EnablePetManagement = true m.config.EnableSpellEffectCaching = true return m } // === Entity Creation and Management === // CreateEntity creates a new Entity with proper initialization func (m *Manager) CreateEntity() *Entity { entity := NewEntity() if m.config.EnableStatistics { atomic.AddInt64(&m.stats.EntitiesCreated, 1) } return entity } // DestroyEntity properly cleans up an entity func (m *Manager) DestroyEntity(entity *Entity) error { if entity == nil { return fmt.Errorf("entity cannot be nil") } // Clean up spell effects entity.DeleteSpellEffects(false) // Clean up pets if entity.GetPet() != nil { entity.SetPet(nil) } if entity.GetCharmedPet() != nil { entity.SetCharmedPet(nil) } if entity.GetDeityPet() != nil { entity.SetDeityPet(nil) } if entity.GetCosmeticPet() != nil { entity.SetCosmeticPet(nil) } if m.config.EnableStatistics { atomic.AddInt64(&m.stats.EntitiesDestroyed, 1) } return nil } // === C++ API Compatibility Methods === // GetEntityInfoStruct returns an entity's info structure (C++ API compatibility) func (m *Manager) GetEntityInfoStruct(entity *Entity) *InfoStruct { if entity == nil { return nil } return entity.GetInfoStruct() } // SetEntityInfoStruct updates an entity's info structure (C++ API compatibility) func (m *Manager) SetEntityInfoStruct(entity *Entity, info *InfoStruct) { if entity != nil && info != nil { entity.SetInfoStruct(info) } } // IsEntityInCombat returns whether an entity is in combat (C++ API compatibility) func (m *Manager) IsEntityInCombat(entity *Entity) bool { if entity == nil { return false } return entity.IsInCombat() } // SetEntityInCombat updates an entity's combat state (C++ API compatibility) func (m *Manager) SetEntityInCombat(entity *Entity, inCombat bool) { if entity != nil { entity.SetInCombat(inCombat) if m.config.EnableStatistics && inCombat { atomic.AddInt64(&m.stats.CombatActions, 1) } } } // IsEntityCasting returns whether an entity is casting (C++ API compatibility) func (m *Manager) IsEntityCasting(entity *Entity) bool { if entity == nil { return false } return entity.IsCasting() } // SetEntityCasting updates an entity's casting state (C++ API compatibility) func (m *Manager) SetEntityCasting(entity *Entity, casting bool) { if entity != nil { entity.SetCasting(casting) } } // === Spell Effect Management === // AddMaintainedSpell adds a maintained spell to an entity func (m *Manager) AddMaintainedSpell(entity *Entity, name string, spellID int32, duration float32, concentration int8) bool { if entity == nil { return false } success := entity.AddMaintainedSpell(name, spellID, duration, concentration) if m.config.EnableStatistics && success { atomic.AddInt64(&m.stats.SpellEffectsAdded, 1) } return success } // RemoveMaintainedSpell removes a maintained spell from an entity func (m *Manager) RemoveMaintainedSpell(entity *Entity, spellID int32) bool { if entity == nil { return false } success := entity.RemoveMaintainedSpell(spellID) if m.config.EnableStatistics && success { atomic.AddInt64(&m.stats.SpellEffectsRemoved, 1) } return success } // AddSpellEffect adds a temporary spell effect to an entity func (m *Manager) AddSpellEffect(entity *Entity, spellID int32, casterID int32, duration float32) bool { if entity == nil { return false } success := entity.AddSpellEffect(spellID, casterID, duration) if m.config.EnableStatistics && success { atomic.AddInt64(&m.stats.SpellEffectsAdded, 1) } return success } // RemoveSpellEffect removes a spell effect from an entity func (m *Manager) RemoveSpellEffect(entity *Entity, spellID int32) bool { if entity == nil { return false } success := entity.RemoveSpellEffect(spellID) if m.config.EnableStatistics && success { atomic.AddInt64(&m.stats.SpellEffectsRemoved, 1) } return success } // AddDetrimentalSpell adds a detrimental effect to an entity func (m *Manager) AddDetrimentalSpell(entity *Entity, spellID int32, casterID int32, duration float32, detType int8) { if entity != nil { entity.AddDetrimentalSpell(spellID, casterID, duration, detType) if m.config.EnableStatistics { atomic.AddInt64(&m.stats.SpellEffectsAdded, 1) } } } // RemoveDetrimentalSpell removes a detrimental effect from an entity func (m *Manager) RemoveDetrimentalSpell(entity *Entity, spellID int32, casterID int32) bool { if entity == nil { return false } success := entity.RemoveDetrimentalSpell(spellID, casterID) if m.config.EnableStatistics && success { atomic.AddInt64(&m.stats.SpellEffectsRemoved, 1) } return success } // CalculateEntityBonuses recalculates all bonuses for an entity func (m *Manager) CalculateEntityBonuses(entity *Entity) { if entity != nil { entity.CalculateBonuses() if m.config.EnableStatistics { atomic.AddInt64(&m.stats.BonusCalculations, 1) } } } // === Pet Management === // SetEntityPet assigns a summon pet to an entity func (m *Manager) SetEntityPet(entity *Entity, pet *Entity) { if entity != nil { entity.SetPet(pet) } } // GetEntityPet returns an entity's summon pet func (m *Manager) GetEntityPet(entity *Entity) *Entity { if entity == nil { return nil } return entity.GetPet() } // SetEntityCharmedPet assigns a charmed pet to an entity func (m *Manager) SetEntityCharmedPet(entity *Entity, pet *Entity) { if entity != nil { entity.SetCharmedPet(pet) } } // GetEntityCharmedPet returns an entity's charmed pet func (m *Manager) GetEntityCharmedPet(entity *Entity) *Entity { if entity == nil { return nil } return entity.GetCharmedPet() } // DismissPet marks a pet for dismissal func (m *Manager) DismissPet(pet *Entity) { if pet != nil { pet.SetPetDismissing(true) } } // === Movement and Position === // UpdateEntityPosition updates an entity's position tracking func (m *Manager) UpdateEntityPosition(entity *Entity) { if entity != nil { // Update last position from current position x := entity.GetX() y := entity.GetY() z := entity.GetZ() heading := entity.GetHeading() entity.SetLastPosition(x, y, z, heading) } } // HasEntityMoved checks if an entity has moved since last update func (m *Manager) HasEntityMoved(entity *Entity) bool { if entity == nil { return false } return entity.HasMoved() } // GetEntityEffectiveSpeed calculates an entity's current effective speed func (m *Manager) GetEntityEffectiveSpeed(entity *Entity) float32 { if entity == nil { return 0.0 } return entity.CalculateEffectiveSpeed() } // === Stat Calculations === // GetEntityPrimaryStat returns an entity's highest primary stat func (m *Manager) GetEntityPrimaryStat(entity *Entity) int16 { if entity == nil { return 0 } return entity.GetPrimaryStat() } // GetEntityResistance returns an entity's resistance to a specific damage type func (m *Manager) GetEntityResistance(entity *Entity, resistanceType string) int16 { if entity == nil { return 0 } switch resistanceType { case "heat": return entity.GetHeatResistance() case "cold": return entity.GetColdResistance() case "magic": return entity.GetMagicResistance() case "mental": return entity.GetMentalResistance() case "divine": return entity.GetDivineResistance() case "disease": return entity.GetDiseaseResistance() case "poison": return entity.GetPoisonResistance() default: return 0 } } // === Packet Building Methods === // SendEntityVerbsRequest builds and returns entity verbs request packet data (C++ API compatibility) func (m *Manager) SendEntityVerbsRequest(entityID int32, clientVersion uint32) ([]byte, error) { packet, exists := packets.GetPacket("WS_EntityVerbsRequest") if !exists { if m.config.EnableStatistics { atomic.AddInt64(&m.stats.PacketErrors, 1) } return nil, fmt.Errorf("failed to get WS_EntityVerbsRequest packet structure: packet not found") } data := map[string]interface{}{ "entity_id": entityID, } builder := packets.NewPacketBuilder(packet, clientVersion, 0) packetData, err := builder.Build(data) if err != nil { if m.config.EnableStatistics { atomic.AddInt64(&m.stats.PacketErrors, 1) } return nil, fmt.Errorf("failed to build entity verbs request packet: %v", err) } if m.config.EnableStatistics { atomic.AddInt64(&m.stats.PacketsSent, 1) atomic.AddInt64(&m.stats.VerbRequests, 1) } return packetData, nil } // SendEntityVerbsResponse builds and returns entity verbs response packet data (C++ API compatibility) func (m *Manager) SendEntityVerbsResponse(entityID int32, clientVersion uint32, verbs []map[string]interface{}) ([]byte, error) { packet, exists := packets.GetPacket("WS_EntityVerbsResponse") if !exists { if m.config.EnableStatistics { atomic.AddInt64(&m.stats.PacketErrors, 1) } return nil, fmt.Errorf("failed to get WS_EntityVerbsResponse packet structure: packet not found") } data := map[string]interface{}{ "entity_id": entityID, "num_verbs": len(verbs), "verbs_array": verbs, } builder := packets.NewPacketBuilder(packet, clientVersion, 0) packetData, err := builder.Build(data) if err != nil { if m.config.EnableStatistics { atomic.AddInt64(&m.stats.PacketErrors, 1) } return nil, fmt.Errorf("failed to build entity verbs response packet: %v", err) } if m.config.EnableStatistics { atomic.AddInt64(&m.stats.PacketsSent, 1) } return packetData, nil } // SendEntityVerbsVerb builds and returns entity verb action packet data (C++ API compatibility) func (m *Manager) SendEntityVerbsVerb(entityID int32, targetID int32, clientVersion uint32, verbID int32, verbName string) ([]byte, error) { packet, exists := packets.GetPacket("WS_EntityVerbsVerb") if !exists { // Try backup packet structure packet, exists = packets.GetPacket("WS_EntityVerbsVerbBackup") if !exists { if m.config.EnableStatistics { atomic.AddInt64(&m.stats.PacketErrors, 1) } return nil, fmt.Errorf("failed to get WS_EntityVerbsVerb packet structure: packet not found") } } data := map[string]interface{}{ "entity_id": entityID, "target_id": targetID, "verb_id": verbID, "verb_name": verbName, } builder := packets.NewPacketBuilder(packet, clientVersion, 0) packetData, err := builder.Build(data) if err != nil { if m.config.EnableStatistics { atomic.AddInt64(&m.stats.PacketErrors, 1) } return nil, fmt.Errorf("failed to build entity verb action packet: %v", err) } if m.config.EnableStatistics { atomic.AddInt64(&m.stats.PacketsSent, 1) } return packetData, nil } // === Effect Processing === // ProcessEntityEffects handles periodic effect processing for an entity func (m *Manager) ProcessEntityEffects(entity *Entity) { if entity != nil { entity.ProcessEffects() } } // CleanupEntityEffects removes expired effects from an entity func (m *Manager) CleanupEntityEffects(entity *Entity) { if entity != nil { entity.ProcessEffects() // This includes cleanup } } // === Statistics and Monitoring === // GetStatistics returns comprehensive manager statistics func (m *Manager) GetStatistics() map[string]interface{} { m.mutex.RLock() defer m.mutex.RUnlock() stats := make(map[string]interface{}) // Entity stats (using atomic loads) stats["entities_created"] = atomic.LoadInt64(&m.stats.EntitiesCreated) stats["entities_destroyed"] = atomic.LoadInt64(&m.stats.EntitiesDestroyed) stats["entities_updated"] = atomic.LoadInt64(&m.stats.EntitiesUpdated) // Spell effect stats (using atomic loads) stats["spell_effects_added"] = atomic.LoadInt64(&m.stats.SpellEffectsAdded) stats["spell_effects_removed"] = atomic.LoadInt64(&m.stats.SpellEffectsRemoved) stats["bonus_calculations"] = atomic.LoadInt64(&m.stats.BonusCalculations) // Combat stats (using atomic loads) stats["damage_calculations"] = atomic.LoadInt64(&m.stats.DamageCalculations) stats["healing_calculations"] = atomic.LoadInt64(&m.stats.HealingCalculations) stats["combat_actions"] = atomic.LoadInt64(&m.stats.CombatActions) // Packet stats (using atomic loads) stats["packets_sent"] = atomic.LoadInt64(&m.stats.PacketsSent) stats["packet_errors"] = atomic.LoadInt64(&m.stats.PacketErrors) stats["verb_requests"] = atomic.LoadInt64(&m.stats.VerbRequests) // Cache stats (using atomic loads) cacheHits := atomic.LoadInt64(&m.stats.CacheHits) cacheMisses := atomic.LoadInt64(&m.stats.CacheMisses) stats["cache_hits"] = cacheHits stats["cache_misses"] = cacheMisses if cacheHits+cacheMisses > 0 { hitRate := float64(cacheHits) / float64(cacheHits+cacheMisses) * 100 stats["cache_hit_rate"] = fmt.Sprintf("%.2f%%", hitRate) } // Configuration stats["config_statistics_enabled"] = m.config.EnableStatistics stats["config_validation_enabled"] = m.config.EnableValidation stats["config_combat_logging_enabled"] = m.config.EnableCombatLogging stats["config_pet_management_enabled"] = m.config.EnablePetManagement return stats } // ResetStatistics clears all performance counters func (m *Manager) ResetStatistics() { atomic.StoreInt64(&m.stats.EntitiesCreated, 0) atomic.StoreInt64(&m.stats.EntitiesDestroyed, 0) atomic.StoreInt64(&m.stats.EntitiesUpdated, 0) atomic.StoreInt64(&m.stats.SpellEffectsAdded, 0) atomic.StoreInt64(&m.stats.SpellEffectsRemoved, 0) atomic.StoreInt64(&m.stats.BonusCalculations, 0) atomic.StoreInt64(&m.stats.DamageCalculations, 0) atomic.StoreInt64(&m.stats.HealingCalculations, 0) atomic.StoreInt64(&m.stats.CombatActions, 0) atomic.StoreInt64(&m.stats.PacketsSent, 0) atomic.StoreInt64(&m.stats.PacketErrors, 0) atomic.StoreInt64(&m.stats.VerbRequests, 0) atomic.StoreInt64(&m.stats.CacheHits, 0) atomic.StoreInt64(&m.stats.CacheMisses, 0) } // === Configuration Management === // SetConfiguration updates manager configuration func (m *Manager) SetConfiguration(config map[string]interface{}) { m.mutex.Lock() defer m.mutex.Unlock() if val, ok := config["enable_statistics"].(bool); ok { m.config.EnableStatistics = val } if val, ok := config["enable_validation"].(bool); ok { m.config.EnableValidation = val } if val, ok := config["enable_combat_logging"].(bool); ok { m.config.EnableCombatLogging = val } if val, ok := config["max_entities_per_zone"].(int); ok { m.config.MaxEntitiesPerZone = val } if val, ok := config["effect_cleanup_interval"].(time.Duration); ok { m.config.EffectCleanupInterval = val } if val, ok := config["enable_pet_management"].(bool); ok { m.config.EnablePetManagement = val } if val, ok := config["enable_spell_effect_caching"].(bool); ok { m.config.EnableSpellEffectCaching = val } } // GetConfiguration returns current manager configuration func (m *Manager) GetConfiguration() map[string]interface{} { m.mutex.RLock() defer m.mutex.RUnlock() return map[string]interface{}{ "enable_statistics": m.config.EnableStatistics, "enable_validation": m.config.EnableValidation, "enable_combat_logging": m.config.EnableCombatLogging, "max_entities_per_zone": m.config.MaxEntitiesPerZone, "effect_cleanup_interval": m.config.EffectCleanupInterval, "bonus_calculation_interval": m.config.BonusCalculationInterval, "packet_builder_timeout": m.config.PacketBuilderTimeout, "enable_pet_management": m.config.EnablePetManagement, "enable_spell_effect_caching": m.config.EnableSpellEffectCaching, } } // === Validation and Health Checks === // ValidateEntity performs comprehensive entity validation func (m *Manager) ValidateEntity(entity *Entity) []string { var issues []string if entity == nil { return []string{"entity is nil"} } info := entity.GetInfoStruct() if info == nil { issues = append(issues, "entity info struct is nil") return issues } if info.GetLevel() < 0 { issues = append(issues, fmt.Sprintf("invalid level: %d", info.GetLevel())) } if info.GetName() == "" { issues = append(issues, "entity name is empty") } if entity.GetTotalHP() < 0 { issues = append(issues, fmt.Sprintf("invalid total HP: %d", entity.GetTotalHP())) } if entity.GetTotalPower() < 0 { issues = append(issues, fmt.Sprintf("invalid total power: %d", entity.GetTotalPower())) } return issues } // IsHealthy returns true if the manager is in a healthy state func (m *Manager) IsHealthy() bool { m.mutex.RLock() defer m.mutex.RUnlock() // Manager is always considered healthy for entities // as it's primarily a utility manager return true }