package ai import ( "fmt" "time" ) // Logger interface for AI logging type Logger interface { LogInfo(message string, args ...any) LogError(message string, args ...any) LogDebug(message string, args ...any) LogWarning(message string, args ...any) } // NPC interface defines the required NPC functionality for AI type NPC interface { // Basic NPC information GetID() int32 GetName() string GetHP() int32 GetTotalHP() int32 SetHP(int32) IsAlive() bool // Combat state GetInCombat() bool InCombat(bool) GetTarget() Entity SetTarget(Entity) // Pet functionality IsPet() bool GetOwner() Entity // Movement and positioning GetX() float32 GetY() float32 GetZ() float32 GetDistance(Entity) float32 FaceTarget(Entity, bool) IsFollowing() bool SetFollowing(bool) GetFollowTarget() Spawn SetFollowTarget(Spawn, float32) CalculateRunningLocation(bool) ClearRunningLocations() // Runback functionality IsRunningBack() bool GetRunbackLocation() *MovementLocation GetRunbackDistance() float32 Runback(float32) ShouldCallRunback() bool SetCallRunback(bool) // Status effects IsMezzedOrStunned() bool IsCasting() bool IsDazed() bool IsFeared() bool IsStifled() bool InWater() bool IsWaterCreature() bool IsFlyingCreature() bool // Combat mechanics AttackAllowed(Entity) bool PrimaryWeaponReady() bool SecondaryWeaponReady() bool SetPrimaryLastAttackTime(int64) SetSecondaryLastAttackTime(int64) MeleeAttack(Entity, float32, bool) // Spell casting GetCastPercentage() int8 GetNextSpell(Entity, float32) Spell GetNextBuffSpell(Spawn) Spell SetCastOnAggroCompleted(bool) CheckLoS(Entity) bool // Movement pausing IsPauseMovementTimerActive() bool // Encounter state SetEncounterState(int8) // Scripts GetSpawnScript() string // Utility KillSpawn(NPC) } // Entity interface for combat entities type Entity interface { Spawn GetID() int32 GetName() string GetHP() int32 GetTotalHP() int32 IsPlayer() bool IsBot() bool IsPet() bool GetOwner() Entity InWater() bool } // Spawn interface for basic spawn functionality type Spawn interface { GetID() int32 GetName() string GetX() float32 GetY() float32 GetZ() float32 } // Spell interface for spell data type Spell interface { GetSpellID() int32 GetName() string IsFriendlySpell() bool GetCastTime() int32 GetRecoveryTime() int32 GetRange() float32 GetMinRange() float32 } // MovementLocation represents a location for movement/runback type MovementLocation struct { X float32 Y float32 Z float32 GridID int32 Stage int32 } // LuaInterface defines Lua script integration type LuaInterface interface { RunSpawnScript(script, function string, npc NPC, target Entity) error } // Zone interface for zone-related AI operations type Zone interface { GetSpawnByID(int32) Spawn ProcessSpell(spell Spell, caster NPC, target Spawn) error CallSpawnScript(npc NPC, scriptType string, args ...any) error } // AIManager provides high-level management of the AI system type AIManager struct { brains map[int32]Brain // Map of NPC ID to brain activeCount int64 // Number of active brains totalThinks int64 // Total think cycles processed logger Logger // Logger for AI events luaInterface LuaInterface // Lua script interface } // NewAIManager creates a new AI manager func NewAIManager(logger Logger, luaInterface LuaInterface) *AIManager { return &AIManager{ brains: make(map[int32]Brain), activeCount: 0, totalThinks: 0, logger: logger, luaInterface: luaInterface, } } // AddBrain adds a brain for an NPC func (am *AIManager) AddBrain(npcID int32, brain Brain) error { if brain == nil { return fmt.Errorf("brain cannot be nil") } if _, exists := am.brains[npcID]; exists { return fmt.Errorf("brain already exists for NPC %d", npcID) } am.brains[npcID] = brain if brain.IsActive() { am.activeCount++ } if am.logger != nil { am.logger.LogDebug("Added brain for NPC %d (type: %d)", npcID, brain.GetBrainType()) } return nil } // RemoveBrain removes a brain for an NPC func (am *AIManager) RemoveBrain(npcID int32) { if brain, exists := am.brains[npcID]; exists { if brain.IsActive() { am.activeCount-- } delete(am.brains, npcID) if am.logger != nil { am.logger.LogDebug("Removed brain for NPC %d", npcID) } } } // GetBrain retrieves a brain for an NPC func (am *AIManager) GetBrain(npcID int32) Brain { return am.brains[npcID] } // CreateBrainForNPC creates and adds the appropriate brain for an NPC func (am *AIManager) CreateBrainForNPC(npc NPC, brainType int8, options ...any) error { if npc == nil { return fmt.Errorf("NPC cannot be nil") } npcID := npc.GetID() // Create brain based on type var brain Brain switch brainType { case BrainTypeCombatPet: brain = NewCombatPetBrain(npc, am.logger) case BrainTypeNonCombatPet: brain = NewNonCombatPetBrain(npc, am.logger) case BrainTypeBlank: brain = NewBlankBrain(npc, am.logger) case BrainTypeLua: brain = NewLuaBrain(npc, am.logger, am.luaInterface) case BrainTypeDumbFire: if len(options) >= 2 { if target, ok := options[0].(Entity); ok { if expireTime, ok := options[1].(int32); ok { brain = NewDumbFirePetBrain(npc, target, expireTime, am.logger) } } } if brain == nil { return fmt.Errorf("invalid options for dumbfire brain") } default: brain = NewBaseBrain(npc, am.logger) } return am.AddBrain(npcID, brain) } // ProcessAllBrains runs think cycles for all active brains func (am *AIManager) ProcessAllBrains() { currentTime := currentTimeMillis() for npcID, brain := range am.brains { if !brain.IsActive() { continue } // Check if it's time to think lastThink := brain.GetLastThink() thinkTick := brain.GetThinkTick() if currentTime-lastThink >= int64(thinkTick) { if err := brain.Think(); err != nil { if am.logger != nil { am.logger.LogError("Brain think error for NPC %d: %v", npcID, err) } } am.totalThinks++ } } } // SetBrainActive sets the active state of a brain func (am *AIManager) SetBrainActive(npcID int32, active bool) { if brain := am.brains[npcID]; brain != nil { wasActive := brain.IsActive() brain.SetActive(active) // Update active count if wasActive && !active { am.activeCount-- } else if !wasActive && active { am.activeCount++ } } } // GetActiveCount returns the number of active brains func (am *AIManager) GetActiveCount() int64 { return am.activeCount } // GetTotalThinks returns the total number of think cycles processed func (am *AIManager) GetTotalThinks() int64 { return am.totalThinks } // GetBrainCount returns the total number of brains func (am *AIManager) GetBrainCount() int { return len(am.brains) } // GetBrainsByType returns all brains of a specific type func (am *AIManager) GetBrainsByType(brainType int8) []Brain { var result []Brain for _, brain := range am.brains { if brain.GetBrainType() == brainType { result = append(result, brain) } } return result } // ClearAllBrains removes all brains func (am *AIManager) ClearAllBrains() { am.brains = make(map[int32]Brain) am.activeCount = 0 if am.logger != nil { am.logger.LogInfo("Cleared all AI brains") } } // GetStatistics returns overall AI system statistics func (am *AIManager) GetStatistics() *AIStatistics { return &AIStatistics{ TotalBrains: len(am.brains), ActiveBrains: int(am.activeCount), TotalThinks: am.totalThinks, BrainsByType: am.getBrainCountsByType(), } } // getBrainCountsByType returns counts of brains by type func (am *AIManager) getBrainCountsByType() map[string]int { counts := make(map[string]int) for _, brain := range am.brains { typeName := getBrainTypeName(brain.GetBrainType()) counts[typeName]++ } return counts } // AIStatistics contains AI system statistics type AIStatistics struct { TotalBrains int `json:"total_brains"` ActiveBrains int `json:"active_brains"` TotalThinks int64 `json:"total_thinks"` BrainsByType map[string]int `json:"brains_by_type"` } // AIBrainAdapter provides NPC functionality for brains type AIBrainAdapter struct { npc NPC logger Logger } // NewAIBrainAdapter creates a new brain adapter func NewAIBrainAdapter(npc NPC, logger Logger) *AIBrainAdapter { return &AIBrainAdapter{ npc: npc, logger: logger, } } // GetNPC returns the adapted NPC func (aba *AIBrainAdapter) GetNPC() NPC { return aba.npc } // ProcessAI processes AI for the NPC using its brain func (aba *AIBrainAdapter) ProcessAI(brain Brain) error { if brain == nil { return fmt.Errorf("brain is nil") } if !brain.IsActive() { return nil } return brain.Think() } // SetupDefaultBrain sets up a default brain for the NPC func (aba *AIBrainAdapter) SetupDefaultBrain() Brain { return NewBaseBrain(aba.npc, aba.logger) } // SetupPetBrain sets up a pet brain based on pet type func (aba *AIBrainAdapter) SetupPetBrain(combatPet bool) Brain { if combatPet { return NewCombatPetBrain(aba.npc, aba.logger) } return NewNonCombatPetBrain(aba.npc, aba.logger) } // Utility functions // getBrainTypeName returns the string name for a brain type func getBrainTypeName(brainType int8) string { switch brainType { case BrainTypeDefault: return "default" case BrainTypeCombatPet: return "combat_pet" case BrainTypeNonCombatPet: return "non_combat_pet" case BrainTypeBlank: return "blank" case BrainTypeLua: return "lua" case BrainTypeDumbFire: return "dumbfire" default: return "unknown" } } // currentTimeMillis returns current time in milliseconds func currentTimeMillis() int64 { return time.Now().UnixMilli() } // HateListDebugger provides debugging functionality for hate lists type HateListDebugger struct { logger Logger } // NewHateListDebugger creates a new hate list debugger func NewHateListDebugger(logger Logger) *HateListDebugger { return &HateListDebugger{ logger: logger, } } // PrintHateList prints a formatted hate list func (hld *HateListDebugger) PrintHateList(npcName string, hateList map[int32]*HateEntry) { if hld.logger == nil { return } hld.logger.LogInfo("%s's Hate List", npcName) hld.logger.LogInfo("-------------------") if len(hateList) == 0 { hld.logger.LogInfo("(empty)") } else { for entityID, entry := range hateList { hld.logger.LogInfo("Entity %d: %d hate", entityID, entry.HateValue) } } hld.logger.LogInfo("-------------------") } // PrintEncounterList prints a formatted encounter list func (hld *HateListDebugger) PrintEncounterList(npcName string, encounterList map[int32]*EncounterEntry) { if hld.logger == nil { return } hld.logger.LogInfo("%s's Encounter List", npcName) hld.logger.LogInfo("-------------------") if len(encounterList) == 0 { hld.logger.LogInfo("(empty)") } else { for entityID, entry := range encounterList { entryType := "NPC" if entry.IsPlayer { entryType = "Player" } else if entry.IsBot { entryType = "Bot" } hld.logger.LogInfo("Entity %d (%s)", entityID, entryType) } } hld.logger.LogInfo("-------------------") }