eq2go/internal/entity/manager.go

651 lines
18 KiB
Go

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
}