package ai import ( "encoding/json" "fmt" ) // 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 // No external dependencies needed } // NewAIManager creates a new AI manager func NewAIManager(logger Logger) *AIManager { return &AIManager{ brains: make(map[int32]Brain), activeCount: 0, totalThinks: 0, logger: logger, } } // 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 BrainTypeCustom: // Custom brains need to be created with specific CustomAI implementations // This is a fallback - normally created via NewCustomBrain directly brain = NewBaseBrain(npc, am.logger) 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 } // AI Communication and Packets // SendAIUpdate sends an AI update packet for an NPC func (am *AIManager) SendAIUpdate(client AIClient, npcID int32) error { brain := am.GetBrain(npcID) if brain == nil { return fmt.Errorf("no brain found for NPC %d", npcID) } data := map[string]interface{}{ "npc_id": npcID, "brain_type": brain.GetBrainType(), "state": brain.GetState(), "active": brain.IsActive(), "think_tick": brain.GetThinkTick(), "most_hated": brain.GetMostHated(), "encounter_size": brain.GetEncounterSize(), } packet := CreateAIPacket(AIPacketTypeUpdate, npcID, data) return client.SendPacket(packet) } // SendHateUpdate sends a hate update packet func (am *AIManager) SendHateUpdate(client AIClient, npcID int32, targetID int32, hateValue int32) error { data := map[string]interface{}{ "target_id": targetID, "hate_value": hateValue, } packet := CreateAIPacket(AIPacketTypeHateUpdate, npcID, data) return client.SendPacket(packet) } // SendEncounterUpdate sends an encounter update packet func (am *AIManager) SendEncounterUpdate(client AIClient, npcID int32, encounterSize int) error { data := map[string]interface{}{ "encounter_size": encounterSize, } packet := CreateAIPacket(AIPacketTypeEncounter, npcID, data) return client.SendPacket(packet) } // SendAIStatistics sends AI system statistics func (am *AIManager) SendAIStatistics(client AIClient) error { stats := am.GetStatistics() data := map[string]interface{}{ "total_brains": stats.TotalBrains, "active_brains": stats.ActiveBrains, "total_thinks": stats.TotalThinks, "brains_by_type": stats.BrainsByType, } packet := CreateAIPacket(AIPacketTypeStatistics, 0, data) return client.SendPacket(packet) } // BroadcastAIUpdate broadcasts an AI update to all clients func (am *AIManager) BroadcastAIUpdate(client AIClient, npcID int32) error { brain := am.GetBrain(npcID) if brain == nil { return fmt.Errorf("no brain found for NPC %d", npcID) } data := map[string]interface{}{ "npc_id": npcID, "brain_type": brain.GetBrainType(), "state": brain.GetState(), "active": brain.IsActive(), } packet := CreateAIPacket(AIPacketTypeUpdate, npcID, data) return client.BroadcastPacket(packet) } // ProcessAICommand processes an AI command packet func (am *AIManager) ProcessAICommand(packet *AIPacket) error { if packet.Type != AIPacketTypeCommand { return fmt.Errorf("invalid packet type for command processing") } brain := am.GetBrain(packet.EntityID) if brain == nil { return fmt.Errorf("no brain found for NPC %d", packet.EntityID) } command, ok := packet.Data["command"].(string) if !ok { return fmt.Errorf("missing command in packet data") } switch command { case "set_active": if active, ok := packet.Data["active"].(bool); ok { brain.SetActive(active) } case "add_hate": if targetID, ok := packet.Data["target_id"].(float64); ok { if hateValue, ok := packet.Data["hate_value"].(float64); ok { brain.AddHate(int32(targetID), int32(hateValue)) } } case "clear_hate": if targetID, ok := packet.Data["target_id"].(float64); ok { brain.ClearHateForEntity(int32(targetID)) } else { brain.ClearHate() } case "set_debug_level": if level, ok := packet.Data["debug_level"].(float64); ok { brain.SetDebugLevel(int8(level)) } case "reset_stats": // Reset brain statistics stats := brain.GetStatistics() *stats = *NewBrainStatistics() default: return fmt.Errorf("unknown command: %s", command) } return nil } // Debugging and Utilities // 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) } // 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("-------------------") } // ToJSON converts AI statistics to JSON func (stats *AIStatistics) ToJSON() ([]byte, error) { return json.Marshal(stats) } // ToJSON converts brain statistics to JSON func (stats *BrainStatistics) ToJSON() ([]byte, error) { return json.Marshal(stats) }