package ai import ( "fmt" "testing" "time" ) // Mock implementations for testing // MockNPC provides a mock NPC implementation for testing type MockNPC struct { id int32 name string hp int32 totalHP int32 inCombat bool target Entity isPet bool owner Entity x, y, z float32 distance float32 following bool followTarget Spawn runningBack bool mezzedOrStunned bool casting bool dazed bool feared bool stifled bool inWater bool waterCreature bool flyingCreature bool attackAllowed bool primaryWeaponReady bool secondaryWeaponReady bool castPercentage int8 nextSpell Spell nextBuffSpell Spell checkLoS bool pauseMovementTimer bool runbackLocation *MovementLocation runbackDistance float32 shouldCallRunback bool spawnScript string } func NewMockNPC(id int32, name string) *MockNPC { return &MockNPC{ id: id, name: name, hp: 100, totalHP: 100, inCombat: false, isPet: false, x: 0, y: 0, z: 0, distance: 10.0, following: false, runningBack: false, mezzedOrStunned: false, casting: false, dazed: false, feared: false, stifled: false, inWater: false, waterCreature: false, flyingCreature: false, attackAllowed: true, primaryWeaponReady: true, secondaryWeaponReady: true, castPercentage: 25, checkLoS: true, pauseMovementTimer: false, runbackDistance: 0, shouldCallRunback: false, spawnScript: "", } } // Implement NPC interface func (m *MockNPC) GetID() int32 { return m.id } func (m *MockNPC) GetName() string { return m.name } func (m *MockNPC) GetHP() int32 { return m.hp } func (m *MockNPC) GetTotalHP() int32 { return m.totalHP } func (m *MockNPC) SetHP(hp int32) { m.hp = hp } func (m *MockNPC) IsAlive() bool { return m.hp > 0 } func (m *MockNPC) GetInCombat() bool { return m.inCombat } func (m *MockNPC) InCombat(val bool) { m.inCombat = val } func (m *MockNPC) GetTarget() Entity { return m.target } func (m *MockNPC) SetTarget(target Entity) { m.target = target } func (m *MockNPC) IsPet() bool { return m.isPet } func (m *MockNPC) GetOwner() Entity { return m.owner } func (m *MockNPC) GetX() float32 { return m.x } func (m *MockNPC) GetY() float32 { return m.y } func (m *MockNPC) GetZ() float32 { return m.z } func (m *MockNPC) GetDistance(target Entity) float32 { return m.distance } func (m *MockNPC) FaceTarget(target Entity, followCaller bool) {} func (m *MockNPC) IsFollowing() bool { return m.following } func (m *MockNPC) SetFollowing(val bool) { m.following = val } func (m *MockNPC) GetFollowTarget() Spawn { return m.followTarget } func (m *MockNPC) SetFollowTarget(target Spawn, range_ float32) { m.followTarget = target } func (m *MockNPC) CalculateRunningLocation(clear bool) {} func (m *MockNPC) ClearRunningLocations() {} func (m *MockNPC) IsRunningBack() bool { return m.runningBack } func (m *MockNPC) GetRunbackLocation() *MovementLocation { return m.runbackLocation } func (m *MockNPC) GetRunbackDistance() float32 { return m.runbackDistance } func (m *MockNPC) Runback(distance float32) { m.runningBack = true } func (m *MockNPC) ShouldCallRunback() bool { return m.shouldCallRunback } func (m *MockNPC) SetCallRunback(val bool) { m.shouldCallRunback = val } func (m *MockNPC) IsMezzedOrStunned() bool { return m.mezzedOrStunned } func (m *MockNPC) IsCasting() bool { return m.casting } func (m *MockNPC) IsDazed() bool { return m.dazed } func (m *MockNPC) IsFeared() bool { return m.feared } func (m *MockNPC) IsStifled() bool { return m.stifled } func (m *MockNPC) InWater() bool { return m.inWater } func (m *MockNPC) IsWaterCreature() bool { return m.waterCreature } func (m *MockNPC) IsFlyingCreature() bool { return m.flyingCreature } func (m *MockNPC) AttackAllowed(target Entity) bool { return m.attackAllowed } func (m *MockNPC) PrimaryWeaponReady() bool { return m.primaryWeaponReady } func (m *MockNPC) SecondaryWeaponReady() bool { return m.secondaryWeaponReady } func (m *MockNPC) SetPrimaryLastAttackTime(time int64) {} func (m *MockNPC) SetSecondaryLastAttackTime(time int64) {} func (m *MockNPC) MeleeAttack(target Entity, distance float32, primary bool) {} func (m *MockNPC) GetCastPercentage() int8 { return m.castPercentage } func (m *MockNPC) GetNextSpell(target Entity, distance float32) Spell { return m.nextSpell } func (m *MockNPC) GetNextBuffSpell(target Spawn) Spell { return m.nextBuffSpell } func (m *MockNPC) SetCastOnAggroCompleted(val bool) {} func (m *MockNPC) CheckLoS(target Entity) bool { return m.checkLoS } func (m *MockNPC) IsPauseMovementTimerActive() bool { return m.pauseMovementTimer } func (m *MockNPC) SetEncounterState(state int8) {} func (m *MockNPC) GetSpawnScript() string { return m.spawnScript } func (m *MockNPC) KillSpawn(npc NPC) {} // Implement Entity interface (extends Spawn) func (m *MockNPC) IsPlayer() bool { return false } func (m *MockNPC) IsBot() bool { return false } // MockEntity provides a mock Entity implementation for testing type MockEntity struct { id int32 name string hp int32 totalHP int32 x, y, z float32 isPlayer bool isBot bool isPet bool owner Entity inWater bool } func NewMockEntity(id int32, name string) *MockEntity { return &MockEntity{ id: id, name: name, hp: 100, totalHP: 100, x: 0, y: 0, z: 0, isPlayer: false, isBot: false, isPet: false, inWater: false, } } func (m *MockEntity) GetID() int32 { return m.id } func (m *MockEntity) GetName() string { return m.name } func (m *MockEntity) GetHP() int32 { return m.hp } func (m *MockEntity) GetTotalHP() int32 { return m.totalHP } func (m *MockEntity) GetX() float32 { return m.x } func (m *MockEntity) GetY() float32 { return m.y } func (m *MockEntity) GetZ() float32 { return m.z } func (m *MockEntity) IsPlayer() bool { return m.isPlayer } func (m *MockEntity) IsBot() bool { return m.isBot } func (m *MockEntity) IsPet() bool { return m.isPet } func (m *MockEntity) GetOwner() Entity { return m.owner } func (m *MockEntity) InWater() bool { return m.inWater } // MockSpell provides a mock Spell implementation for testing type MockSpell struct { id int32 name string friendly bool castTime int32 recoveryTime int32 range_ float32 minRange float32 } func NewMockSpell(id int32, name string) *MockSpell { return &MockSpell{ id: id, name: name, friendly: false, castTime: 1000, recoveryTime: 2000, range_: 30.0, minRange: 0.0, } } func (m *MockSpell) GetSpellID() int32 { return m.id } func (m *MockSpell) GetName() string { return m.name } func (m *MockSpell) IsFriendlySpell() bool { return m.friendly } func (m *MockSpell) GetCastTime() int32 { return m.castTime } func (m *MockSpell) GetRecoveryTime() int32 { return m.recoveryTime } func (m *MockSpell) GetRange() float32 { return m.range_ } func (m *MockSpell) GetMinRange() float32 { return m.minRange } // MockLogger provides a mock Logger implementation for testing type MockLogger struct { messages []string } func NewMockLogger() *MockLogger { return &MockLogger{ messages: make([]string, 0), } } func (m *MockLogger) LogInfo(message string, args ...any) { m.messages = append(m.messages, "INFO: "+message) } func (m *MockLogger) LogError(message string, args ...any) { m.messages = append(m.messages, "ERROR: "+message) } func (m *MockLogger) LogDebug(message string, args ...any) { m.messages = append(m.messages, "DEBUG: "+message) } func (m *MockLogger) LogWarning(message string, args ...any) { m.messages = append(m.messages, "WARNING: "+message) } func (m *MockLogger) GetMessages() []string { return m.messages } // MockLuaInterface provides a mock LuaInterface implementation for testing type MockLuaInterface struct { executed bool lastScript string lastFunction string shouldError bool } func NewMockLuaInterface() *MockLuaInterface { return &MockLuaInterface{ executed: false, shouldError: false, } } func (m *MockLuaInterface) RunSpawnScript(script, function string, npc NPC, target Entity) error { m.executed = true m.lastScript = script m.lastFunction = function if m.shouldError { return fmt.Errorf("mock lua error") } return nil } func (m *MockLuaInterface) WasExecuted() bool { return m.executed } func (m *MockLuaInterface) SetShouldError(shouldError bool) { m.shouldError = shouldError } // Tests for HateEntry and HateList func TestNewHateEntry(t *testing.T) { entityID := int32(123) hateValue := int32(500) entry := NewHateEntry(entityID, hateValue) if entry.EntityID != entityID { t.Errorf("Expected entity ID %d, got %d", entityID, entry.EntityID) } if entry.HateValue != hateValue { t.Errorf("Expected hate value %d, got %d", hateValue, entry.HateValue) } if entry.LastUpdated == 0 { t.Error("Expected LastUpdated to be set") } } func TestNewHateEntryMinValue(t *testing.T) { entityID := int32(123) hateValue := int32(0) // Below minimum entry := NewHateEntry(entityID, hateValue) if entry.HateValue != MinHateValue { t.Errorf("Expected hate value to be adjusted to minimum %d, got %d", MinHateValue, entry.HateValue) } } func TestNewHateList(t *testing.T) { hateList := NewHateList() if hateList == nil { t.Fatal("NewHateList returned nil") } if hateList.Size() != 0 { t.Errorf("Expected empty hate list, got size %d", hateList.Size()) } } func TestHateListAddHate(t *testing.T) { hateList := NewHateList() entityID := int32(123) hateValue := int32(500) hateList.AddHate(entityID, hateValue) if hateList.Size() != 1 { t.Errorf("Expected hate list size 1, got %d", hateList.Size()) } retrievedHate := hateList.GetHate(entityID) if retrievedHate != hateValue { t.Errorf("Expected hate value %d, got %d", hateValue, retrievedHate) } } func TestHateListAddHateUpdate(t *testing.T) { hateList := NewHateList() entityID := int32(123) initialHate := int32(500) additionalHate := int32(200) hateList.AddHate(entityID, initialHate) hateList.AddHate(entityID, additionalHate) if hateList.Size() != 1 { t.Errorf("Expected hate list size 1, got %d", hateList.Size()) } expectedTotal := initialHate + additionalHate retrievedHate := hateList.GetHate(entityID) if retrievedHate != expectedTotal { t.Errorf("Expected hate value %d, got %d", expectedTotal, retrievedHate) } } func TestHateListGetMostHated(t *testing.T) { hateList := NewHateList() entity1 := int32(1) entity2 := int32(2) entity3 := int32(3) hateList.AddHate(entity1, 100) hateList.AddHate(entity2, 500) // Highest hate hateList.AddHate(entity3, 200) mostHated := hateList.GetMostHated() if mostHated != entity2 { t.Errorf("Expected most hated entity %d, got %d", entity2, mostHated) } } func TestHateListGetHatePercentage(t *testing.T) { hateList := NewHateList() entity1 := int32(1) entity2 := int32(2) hateList.AddHate(entity1, 300) // 75% of total hate (300/400) hateList.AddHate(entity2, 100) // 25% of total hate (100/400) percentage1 := hateList.GetHatePercentage(entity1) percentage2 := hateList.GetHatePercentage(entity2) if percentage1 != 75 { t.Errorf("Expected entity1 hate percentage 75, got %d", percentage1) } if percentage2 != 25 { t.Errorf("Expected entity2 hate percentage 25, got %d", percentage2) } } func TestHateListRemoveHate(t *testing.T) { hateList := NewHateList() entityID := int32(123) hateList.AddHate(entityID, 500) hateList.RemoveHate(entityID) if hateList.Size() != 0 { t.Errorf("Expected empty hate list after removal, got size %d", hateList.Size()) } retrievedHate := hateList.GetHate(entityID) if retrievedHate != 0 { t.Errorf("Expected hate value 0 after removal, got %d", retrievedHate) } } func TestHateListClear(t *testing.T) { hateList := NewHateList() hateList.AddHate(1, 100) hateList.AddHate(2, 200) hateList.AddHate(3, 300) hateList.Clear() if hateList.Size() != 0 { t.Errorf("Expected empty hate list after clear, got size %d", hateList.Size()) } } func TestHateListGetAllEntries(t *testing.T) { hateList := NewHateList() entity1 := int32(1) entity2 := int32(2) hate1 := int32(100) hate2 := int32(200) hateList.AddHate(entity1, hate1) hateList.AddHate(entity2, hate2) entries := hateList.GetAllEntries() if len(entries) != 2 { t.Errorf("Expected 2 entries, got %d", len(entries)) } if entries[entity1].HateValue != hate1 { t.Errorf("Expected entity1 hate %d, got %d", hate1, entries[entity1].HateValue) } if entries[entity2].HateValue != hate2 { t.Errorf("Expected entity2 hate %d, got %d", hate2, entries[entity2].HateValue) } } // Tests for EncounterEntry and EncounterList func TestNewEncounterEntry(t *testing.T) { entityID := int32(123) characterID := int32(456) isPlayer := true isBot := false entry := NewEncounterEntry(entityID, characterID, isPlayer, isBot) if entry.EntityID != entityID { t.Errorf("Expected entity ID %d, got %d", entityID, entry.EntityID) } if entry.CharacterID != characterID { t.Errorf("Expected character ID %d, got %d", characterID, entry.CharacterID) } if entry.IsPlayer != isPlayer { t.Errorf("Expected IsPlayer %v, got %v", isPlayer, entry.IsPlayer) } if entry.IsBot != isBot { t.Errorf("Expected IsBot %v, got %v", isBot, entry.IsBot) } if entry.AddedTime == 0 { t.Error("Expected AddedTime to be set") } } func TestNewEncounterList(t *testing.T) { encounterList := NewEncounterList() if encounterList == nil { t.Fatal("NewEncounterList returned nil") } if encounterList.Size() != 0 { t.Errorf("Expected empty encounter list, got size %d", encounterList.Size()) } if encounterList.HasPlayerInEncounter() { t.Error("Expected no players in encounter initially") } } func TestEncounterListAddEntity(t *testing.T) { encounterList := NewEncounterList() entityID := int32(123) characterID := int32(456) success := encounterList.AddEntity(entityID, characterID, true, false) if !success { t.Error("Expected AddEntity to succeed") } if encounterList.Size() != 1 { t.Errorf("Expected encounter list size 1, got %d", encounterList.Size()) } if !encounterList.IsEntityInEncounter(entityID) { t.Error("Entity should be in encounter") } if !encounterList.IsPlayerInEncounter(characterID) { t.Error("Player should be in encounter") } if !encounterList.HasPlayerInEncounter() { t.Error("Should have player in encounter") } } func TestEncounterListAddEntityDuplicate(t *testing.T) { encounterList := NewEncounterList() entityID := int32(123) success1 := encounterList.AddEntity(entityID, 0, false, false) success2 := encounterList.AddEntity(entityID, 0, false, false) // Duplicate if !success1 { t.Error("Expected first AddEntity to succeed") } if success2 { t.Error("Expected second AddEntity to fail (duplicate)") } if encounterList.Size() != 1 { t.Errorf("Expected encounter list size 1 after duplicate add, got %d", encounterList.Size()) } } func TestEncounterListRemoveEntity(t *testing.T) { encounterList := NewEncounterList() entityID := int32(123) characterID := int32(456) encounterList.AddEntity(entityID, characterID, true, false) encounterList.RemoveEntity(entityID) if encounterList.Size() != 0 { t.Errorf("Expected empty encounter list after removal, got size %d", encounterList.Size()) } if encounterList.IsEntityInEncounter(entityID) { t.Error("Entity should not be in encounter after removal") } if encounterList.IsPlayerInEncounter(characterID) { t.Error("Player should not be in encounter after removal") } if encounterList.HasPlayerInEncounter() { t.Error("Should not have player in encounter after removal") } } func TestEncounterListCountPlayerBots(t *testing.T) { encounterList := NewEncounterList() encounterList.AddEntity(1, 101, true, false) // Player encounterList.AddEntity(2, 0, false, true) // Bot encounterList.AddEntity(3, 0, false, false) // NPC count := encounterList.CountPlayerBots() if count != 2 { t.Errorf("Expected 2 players/bots, got %d", count) } } func TestEncounterListGetAllEntityIDs(t *testing.T) { encounterList := NewEncounterList() entity1 := int32(1) entity2 := int32(2) entity3 := int32(3) encounterList.AddEntity(entity1, 0, false, false) encounterList.AddEntity(entity2, 0, false, false) encounterList.AddEntity(entity3, 0, false, false) entityIDs := encounterList.GetAllEntityIDs() if len(entityIDs) != 3 { t.Errorf("Expected 3 entity IDs, got %d", len(entityIDs)) } // Check that all IDs are present (order doesn't matter) idMap := make(map[int32]bool) for _, id := range entityIDs { idMap[id] = true } if !idMap[entity1] || !idMap[entity2] || !idMap[entity3] { t.Error("Expected all entity IDs to be present") } } func TestEncounterListClear(t *testing.T) { encounterList := NewEncounterList() encounterList.AddEntity(1, 101, true, false) encounterList.AddEntity(2, 0, false, false) encounterList.Clear() if encounterList.Size() != 0 { t.Errorf("Expected empty encounter list after clear, got size %d", encounterList.Size()) } if encounterList.HasPlayerInEncounter() { t.Error("Should not have player in encounter after clear") } } // Tests for BrainState func TestNewBrainState(t *testing.T) { state := NewBrainState() if state == nil { t.Fatal("NewBrainState returned nil") } if state.GetState() != AIStateIdle { t.Errorf("Expected initial state %d, got %d", AIStateIdle, state.GetState()) } if !state.IsActive() { t.Error("Expected brain state to be active initially") } if state.GetThinkTick() != DefaultThinkTick { t.Errorf("Expected think tick %d, got %d", DefaultThinkTick, state.GetThinkTick()) } if state.GetDebugLevel() != DebugLevelNone { t.Errorf("Expected debug level %d, got %d", DebugLevelNone, state.GetDebugLevel()) } } func TestBrainStateSetState(t *testing.T) { state := NewBrainState() newState := AIStateCombat state.SetState(newState) if state.GetState() != newState { t.Errorf("Expected state %d, got %d", newState, state.GetState()) } } func TestBrainStateSetActive(t *testing.T) { state := NewBrainState() state.SetActive(false) if state.IsActive() { t.Error("Expected brain state to be inactive") } state.SetActive(true) if !state.IsActive() { t.Error("Expected brain state to be active") } } func TestBrainStateSetThinkTick(t *testing.T) { state := NewBrainState() // Test normal value newTick := int32(500) state.SetThinkTick(newTick) if state.GetThinkTick() != newTick { t.Errorf("Expected think tick %d, got %d", newTick, state.GetThinkTick()) } // Test minimum value state.SetThinkTick(0) if state.GetThinkTick() != 1 { t.Errorf("Expected think tick to be adjusted to minimum 1, got %d", state.GetThinkTick()) } // Test maximum value state.SetThinkTick(MaxThinkTick + 1000) if state.GetThinkTick() != MaxThinkTick { t.Errorf("Expected think tick to be adjusted to maximum %d, got %d", MaxThinkTick, state.GetThinkTick()) } } func TestBrainStateSetLastThink(t *testing.T) { state := NewBrainState() timestamp := time.Now().UnixMilli() state.SetLastThink(timestamp) if state.GetLastThink() != timestamp { t.Errorf("Expected last think timestamp %d, got %d", timestamp, state.GetLastThink()) } } func TestBrainStateSpellRecovery(t *testing.T) { state := NewBrainState() // Test future recovery time futureTime := time.Now().UnixMilli() + 5000 state.SetSpellRecovery(futureTime) if state.GetSpellRecovery() != futureTime { t.Errorf("Expected spell recovery time %d, got %d", futureTime, state.GetSpellRecovery()) } if state.HasRecovered() { t.Error("Expected brain to not have recovered yet") } // Test past recovery time pastTime := time.Now().UnixMilli() - 5000 state.SetSpellRecovery(pastTime) if !state.HasRecovered() { t.Error("Expected brain to have recovered") } } func TestBrainStateDebugLevel(t *testing.T) { state := NewBrainState() debugLevel := DebugLevelVerbose state.SetDebugLevel(debugLevel) if state.GetDebugLevel() != debugLevel { t.Errorf("Expected debug level %d, got %d", debugLevel, state.GetDebugLevel()) } } // Tests for BrainStatistics func TestNewBrainStatistics(t *testing.T) { stats := NewBrainStatistics() if stats == nil { t.Fatal("NewBrainStatistics returned nil") } if stats.ThinkCycles != 0 { t.Errorf("Expected think cycles 0, got %d", stats.ThinkCycles) } if stats.SpellsCast != 0 { t.Errorf("Expected spells cast 0, got %d", stats.SpellsCast) } if stats.MeleeAttacks != 0 { t.Errorf("Expected melee attacks 0, got %d", stats.MeleeAttacks) } if stats.AverageThinkTime != 0.0 { t.Errorf("Expected average think time 0.0, got %f", stats.AverageThinkTime) } if stats.LastThinkTime == 0 { t.Error("Expected LastThinkTime to be set") } } // Tests for BaseBrain func TestNewBaseBrain(t *testing.T) { npc := NewMockNPC(123, "TestNPC") logger := NewMockLogger() brain := NewBaseBrain(npc, logger) if brain == nil { t.Fatal("NewBaseBrain returned nil") } if brain.GetBrainType() != BrainTypeDefault { t.Errorf("Expected brain type %d, got %d", BrainTypeDefault, brain.GetBrainType()) } if !brain.IsActive() { t.Error("Expected brain to be active initially") } if brain.GetBody() != npc { t.Error("Expected brain body to be the provided NPC") } } func TestBaseBrainHateManagement(t *testing.T) { npc := NewMockNPC(123, "TestNPC") logger := NewMockLogger() brain := NewBaseBrain(npc, logger) entityID := int32(456) hateValue := int32(500) // Add hate brain.AddHate(entityID, hateValue) if brain.GetHate(entityID) != hateValue { t.Errorf("Expected hate value %d, got %d", hateValue, brain.GetHate(entityID)) } if brain.GetMostHated() != entityID { t.Errorf("Expected most hated entity %d, got %d", entityID, brain.GetMostHated()) } // Clear hate for entity brain.ClearHateForEntity(entityID) if brain.GetHate(entityID) != 0 { t.Errorf("Expected hate value 0 after clearing, got %d", brain.GetHate(entityID)) } // Add hate again and clear all brain.AddHate(entityID, hateValue) brain.ClearHate() if brain.GetHate(entityID) != 0 { t.Errorf("Expected hate value 0 after clearing all, got %d", brain.GetHate(entityID)) } } func TestBaseBrainEncounterManagement(t *testing.T) { npc := NewMockNPC(123, "TestNPC") logger := NewMockLogger() brain := NewBaseBrain(npc, logger) entityID := int32(456) characterID := int32(789) // Add to encounter success := brain.AddToEncounter(entityID, characterID, true, false) if !success { t.Error("Expected AddToEncounter to succeed") } if !brain.IsEntityInEncounter(entityID) { t.Error("Entity should be in encounter") } if !brain.IsPlayerInEncounter(characterID) { t.Error("Player should be in encounter") } if !brain.HasPlayerInEncounter() { t.Error("Should have player in encounter") } if brain.GetEncounterSize() != 1 { t.Errorf("Expected encounter size 1, got %d", brain.GetEncounterSize()) } // Check loot allowed if !brain.CheckLootAllowed(entityID) { t.Error("Loot should be allowed for entity in encounter") } // Clear encounter brain.ClearEncounter() if brain.IsEntityInEncounter(entityID) { t.Error("Entity should not be in encounter after clear") } if brain.GetEncounterSize() != 0 { t.Errorf("Expected encounter size 0 after clear, got %d", brain.GetEncounterSize()) } } func TestBaseBrainStatistics(t *testing.T) { npc := NewMockNPC(123, "TestNPC") logger := NewMockLogger() brain := NewBaseBrain(npc, logger) // Get initial statistics stats := brain.GetStatistics() if stats == nil { t.Fatal("GetStatistics returned nil") } // Reset statistics brain.ResetStatistics() // Get statistics again newStats := brain.GetStatistics() if newStats.ThinkCycles != 0 { t.Errorf("Expected think cycles 0 after reset, got %d", newStats.ThinkCycles) } } func TestBaseBrainSetBody(t *testing.T) { npc1 := NewMockNPC(123, "TestNPC1") npc2 := NewMockNPC(456, "TestNPC2") logger := NewMockLogger() brain := NewBaseBrain(npc1, logger) if brain.GetBody() != npc1 { t.Error("Expected brain body to be npc1 initially") } brain.SetBody(npc2) if brain.GetBody() != npc2 { t.Error("Expected brain body to be npc2 after SetBody") } } // Tests for Brain Variants func TestNewCombatPetBrain(t *testing.T) { npc := NewMockNPC(123, "TestPet") npc.isPet = true logger := NewMockLogger() brain := NewCombatPetBrain(npc, logger) if brain == nil { t.Fatal("NewCombatPetBrain returned nil") } if brain.GetBrainType() != BrainTypeCombatPet { t.Errorf("Expected brain type %d, got %d", BrainTypeCombatPet, brain.GetBrainType()) } } func TestNewNonCombatPetBrain(t *testing.T) { npc := NewMockNPC(123, "TestPet") npc.isPet = true logger := NewMockLogger() brain := NewNonCombatPetBrain(npc, logger) if brain == nil { t.Fatal("NewNonCombatPetBrain returned nil") } if brain.GetBrainType() != BrainTypeNonCombatPet { t.Errorf("Expected brain type %d, got %d", BrainTypeNonCombatPet, brain.GetBrainType()) } } func TestNewBlankBrain(t *testing.T) { npc := NewMockNPC(123, "TestNPC") logger := NewMockLogger() brain := NewBlankBrain(npc, logger) if brain == nil { t.Fatal("NewBlankBrain returned nil") } if brain.GetBrainType() != BrainTypeBlank { t.Errorf("Expected brain type %d, got %d", BrainTypeBlank, brain.GetBrainType()) } if brain.GetThinkTick() != BlankBrainTick { t.Errorf("Expected think tick %d, got %d", BlankBrainTick, brain.GetThinkTick()) } } func TestBlankBrainThink(t *testing.T) { npc := NewMockNPC(123, "TestNPC") logger := NewMockLogger() brain := NewBlankBrain(npc, logger) // Blank brain Think should do nothing and not error err := brain.Think() if err != nil { t.Errorf("Expected no error from blank brain Think, got: %v", err) } } func TestNewLuaBrain(t *testing.T) { npc := NewMockNPC(123, "TestNPC") npc.spawnScript = "test_script.lua" logger := NewMockLogger() luaInterface := NewMockLuaInterface() brain := NewLuaBrain(npc, logger, luaInterface) if brain == nil { t.Fatal("NewLuaBrain returned nil") } if brain.GetBrainType() != BrainTypeLua { t.Errorf("Expected brain type %d, got %d", BrainTypeLua, brain.GetBrainType()) } } func TestLuaBrainThink(t *testing.T) { npc := NewMockNPC(123, "TestNPC") npc.spawnScript = "test_script.lua" logger := NewMockLogger() luaInterface := NewMockLuaInterface() brain := NewLuaBrain(npc, logger, luaInterface) err := brain.Think() if err != nil { t.Errorf("Expected no error from Lua brain Think, got: %v", err) } if !luaInterface.WasExecuted() { t.Error("Expected Lua interface to be executed") } if luaInterface.lastFunction != "Think" { t.Errorf("Expected Lua function 'Think', got '%s'", luaInterface.lastFunction) } } func TestLuaBrainThinkError(t *testing.T) { npc := NewMockNPC(123, "TestNPC") npc.spawnScript = "test_script.lua" logger := NewMockLogger() luaInterface := NewMockLuaInterface() luaInterface.SetShouldError(true) brain := NewLuaBrain(npc, logger, luaInterface) err := brain.Think() if err == nil { t.Error("Expected error from Lua brain Think") } } func TestNewDumbFirePetBrain(t *testing.T) { npc := NewMockNPC(123, "TestPet") target := NewMockEntity(456, "Target") expireTime := int32(10000) // 10 seconds logger := NewMockLogger() brain := NewDumbFirePetBrain(npc, target, expireTime, logger) if brain == nil { t.Fatal("NewDumbFirePetBrain returned nil") } if brain.GetBrainType() != BrainTypeDumbFire { t.Errorf("Expected brain type %d, got %d", BrainTypeDumbFire, brain.GetBrainType()) } // Should have max hate for target if brain.GetHate(target.GetID()) != MaxHateValue { t.Errorf("Expected max hate for target, got %d", brain.GetHate(target.GetID())) } if brain.IsExpired() { t.Error("Dumbfire pet should not be expired immediately") } } func TestDumbFirePetBrainExpiry(t *testing.T) { npc := NewMockNPC(123, "TestPet") target := NewMockEntity(456, "Target") expireTime := int32(100) // 100ms logger := NewMockLogger() brain := NewDumbFirePetBrain(npc, target, expireTime, logger) // Wait for expiry time.Sleep(150 * time.Millisecond) if !brain.IsExpired() { t.Error("Dumbfire pet should be expired") } } func TestDumbFirePetBrainExtendExpireTime(t *testing.T) { npc := NewMockNPC(123, "TestPet") target := NewMockEntity(456, "Target") expireTime := int32(1000) logger := NewMockLogger() brain := NewDumbFirePetBrain(npc, target, expireTime, logger) originalExpireTime := brain.GetExpireTime() extension := int32(5000) brain.ExtendExpireTime(extension) newExpireTime := brain.GetExpireTime() if newExpireTime != originalExpireTime+int64(extension) { t.Errorf("Expected expire time %d, got %d", originalExpireTime+int64(extension), newExpireTime) } } // Tests for CreateBrain factory function func TestCreateBrainDefault(t *testing.T) { npc := NewMockNPC(123, "TestNPC") logger := NewMockLogger() brain := CreateBrain(npc, BrainTypeDefault, logger) if brain == nil { t.Fatal("CreateBrain returned nil") } if brain.GetBrainType() != BrainTypeDefault { t.Errorf("Expected brain type %d, got %d", BrainTypeDefault, brain.GetBrainType()) } } func TestCreateBrainCombatPet(t *testing.T) { npc := NewMockNPC(123, "TestPet") logger := NewMockLogger() brain := CreateBrain(npc, BrainTypeCombatPet, logger) if brain.GetBrainType() != BrainTypeCombatPet { t.Errorf("Expected brain type %d, got %d", BrainTypeCombatPet, brain.GetBrainType()) } } func TestCreateBrainLua(t *testing.T) { npc := NewMockNPC(123, "TestNPC") logger := NewMockLogger() luaInterface := NewMockLuaInterface() brain := CreateBrain(npc, BrainTypeLua, logger, luaInterface) if brain.GetBrainType() != BrainTypeLua { t.Errorf("Expected brain type %d, got %d", BrainTypeLua, brain.GetBrainType()) } } func TestCreateBrainDumbFire(t *testing.T) { npc := NewMockNPC(123, "TestPet") target := NewMockEntity(456, "Target") expireTime := int32(10000) logger := NewMockLogger() brain := CreateBrain(npc, BrainTypeDumbFire, logger, target, expireTime) if brain.GetBrainType() != BrainTypeDumbFire { t.Errorf("Expected brain type %d, got %d", BrainTypeDumbFire, brain.GetBrainType()) } } // Tests for AIManager func TestNewAIManager(t *testing.T) { logger := NewMockLogger() luaInterface := NewMockLuaInterface() manager := NewAIManager(logger, luaInterface) if manager == nil { t.Fatal("NewAIManager returned nil") } if manager.GetBrainCount() != 0 { t.Errorf("Expected brain count 0, got %d", manager.GetBrainCount()) } if manager.GetActiveCount() != 0 { t.Errorf("Expected active count 0, got %d", manager.GetActiveCount()) } if manager.GetTotalThinks() != 0 { t.Errorf("Expected total thinks 0, got %d", manager.GetTotalThinks()) } } func TestAIManagerAddBrain(t *testing.T) { logger := NewMockLogger() luaInterface := NewMockLuaInterface() manager := NewAIManager(logger, luaInterface) npc := NewMockNPC(123, "TestNPC") brain := NewBaseBrain(npc, logger) npcID := npc.GetID() err := manager.AddBrain(npcID, brain) if err != nil { t.Errorf("Expected no error adding brain, got: %v", err) } if manager.GetBrainCount() != 1 { t.Errorf("Expected brain count 1, got %d", manager.GetBrainCount()) } if manager.GetActiveCount() != 1 { t.Errorf("Expected active count 1, got %d", manager.GetActiveCount()) } retrievedBrain := manager.GetBrain(npcID) if retrievedBrain != brain { t.Error("Retrieved brain should be the same as added brain") } } func TestAIManagerAddBrainDuplicate(t *testing.T) { logger := NewMockLogger() luaInterface := NewMockLuaInterface() manager := NewAIManager(logger, luaInterface) npc := NewMockNPC(123, "TestNPC") brain1 := NewBaseBrain(npc, logger) brain2 := NewBaseBrain(npc, logger) npcID := npc.GetID() err1 := manager.AddBrain(npcID, brain1) err2 := manager.AddBrain(npcID, brain2) // Duplicate if err1 != nil { t.Errorf("Expected no error adding first brain, got: %v", err1) } if err2 == nil { t.Error("Expected error adding duplicate brain") } if manager.GetBrainCount() != 1 { t.Errorf("Expected brain count 1 after duplicate add, got %d", manager.GetBrainCount()) } } func TestAIManagerRemoveBrain(t *testing.T) { logger := NewMockLogger() luaInterface := NewMockLuaInterface() manager := NewAIManager(logger, luaInterface) npc := NewMockNPC(123, "TestNPC") brain := NewBaseBrain(npc, logger) npcID := npc.GetID() manager.AddBrain(npcID, brain) manager.RemoveBrain(npcID) if manager.GetBrainCount() != 0 { t.Errorf("Expected brain count 0 after removal, got %d", manager.GetBrainCount()) } if manager.GetActiveCount() != 0 { t.Errorf("Expected active count 0 after removal, got %d", manager.GetActiveCount()) } retrievedBrain := manager.GetBrain(npcID) if retrievedBrain != nil { t.Error("Retrieved brain should be nil after removal") } } func TestAIManagerCreateBrainForNPC(t *testing.T) { logger := NewMockLogger() luaInterface := NewMockLuaInterface() manager := NewAIManager(logger, luaInterface) npc := NewMockNPC(123, "TestNPC") err := manager.CreateBrainForNPC(npc, BrainTypeDefault) if err != nil { t.Errorf("Expected no error creating brain, got: %v", err) } brain := manager.GetBrain(npc.GetID()) if brain == nil { t.Error("Expected brain to be created") } if brain.GetBrainType() != BrainTypeDefault { t.Errorf("Expected brain type %d, got %d", BrainTypeDefault, brain.GetBrainType()) } } func TestAIManagerSetBrainActive(t *testing.T) { logger := NewMockLogger() luaInterface := NewMockLuaInterface() manager := NewAIManager(logger, luaInterface) npc := NewMockNPC(123, "TestNPC") brain := NewBaseBrain(npc, logger) npcID := npc.GetID() manager.AddBrain(npcID, brain) // Initially active if manager.GetActiveCount() != 1 { t.Errorf("Expected active count 1, got %d", manager.GetActiveCount()) } // Set inactive manager.SetBrainActive(npcID, false) if manager.GetActiveCount() != 0 { t.Errorf("Expected active count 0, got %d", manager.GetActiveCount()) } // Set active again manager.SetBrainActive(npcID, true) if manager.GetActiveCount() != 1 { t.Errorf("Expected active count 1, got %d", manager.GetActiveCount()) } } func TestAIManagerGetBrainsByType(t *testing.T) { logger := NewMockLogger() luaInterface := NewMockLuaInterface() manager := NewAIManager(logger, luaInterface) npc1 := NewMockNPC(123, "TestNPC1") npc2 := NewMockNPC(456, "TestNPC2") npc3 := NewMockNPC(789, "TestNPC3") brain1 := NewBaseBrain(npc1, logger) brain2 := NewCombatPetBrain(npc2, logger) brain3 := NewBlankBrain(npc3, logger) manager.AddBrain(npc1.GetID(), brain1) manager.AddBrain(npc2.GetID(), brain2) manager.AddBrain(npc3.GetID(), brain3) defaultBrains := manager.GetBrainsByType(BrainTypeDefault) if len(defaultBrains) != 1 { t.Errorf("Expected 1 default brain, got %d", len(defaultBrains)) } petBrains := manager.GetBrainsByType(BrainTypeCombatPet) if len(petBrains) != 1 { t.Errorf("Expected 1 combat pet brain, got %d", len(petBrains)) } blankBrains := manager.GetBrainsByType(BrainTypeBlank) if len(blankBrains) != 1 { t.Errorf("Expected 1 blank brain, got %d", len(blankBrains)) } } func TestAIManagerClearAllBrains(t *testing.T) { logger := NewMockLogger() luaInterface := NewMockLuaInterface() manager := NewAIManager(logger, luaInterface) npc1 := NewMockNPC(123, "TestNPC1") npc2 := NewMockNPC(456, "TestNPC2") brain1 := NewBaseBrain(npc1, logger) brain2 := NewBaseBrain(npc2, logger) manager.AddBrain(npc1.GetID(), brain1) manager.AddBrain(npc2.GetID(), brain2) manager.ClearAllBrains() if manager.GetBrainCount() != 0 { t.Errorf("Expected brain count 0 after clear, got %d", manager.GetBrainCount()) } if manager.GetActiveCount() != 0 { t.Errorf("Expected active count 0 after clear, got %d", manager.GetActiveCount()) } } func TestAIManagerGetStatistics(t *testing.T) { logger := NewMockLogger() luaInterface := NewMockLuaInterface() manager := NewAIManager(logger, luaInterface) npc1 := NewMockNPC(123, "TestNPC1") npc2 := NewMockNPC(456, "TestNPC2") brain1 := NewBaseBrain(npc1, logger) brain2 := NewCombatPetBrain(npc2, logger) manager.AddBrain(npc1.GetID(), brain1) manager.AddBrain(npc2.GetID(), brain2) stats := manager.GetStatistics() if stats == nil { t.Fatal("GetStatistics returned nil") } if stats.TotalBrains != 2 { t.Errorf("Expected total brains 2, got %d", stats.TotalBrains) } if stats.ActiveBrains != 2 { t.Errorf("Expected active brains 2, got %d", stats.ActiveBrains) } if len(stats.BrainsByType) == 0 { t.Error("Expected brain types to be populated") } } // Tests for utility functions func TestGetBrainTypeName(t *testing.T) { testCases := []struct { brainType int8 expected string }{ {BrainTypeDefault, "default"}, {BrainTypeCombatPet, "combat_pet"}, {BrainTypeNonCombatPet, "non_combat_pet"}, {BrainTypeBlank, "blank"}, {BrainTypeLua, "lua"}, {BrainTypeDumbFire, "dumbfire"}, {99, "unknown"}, // Unknown type } for _, tc := range testCases { result := getBrainTypeName(tc.brainType) if result != tc.expected { t.Errorf("Expected brain type name '%s' for type %d, got '%s'", tc.expected, tc.brainType, result) } } } func TestCurrentTimeMillis(t *testing.T) { before := time.Now().UnixMilli() result := currentTimeMillis() after := time.Now().UnixMilli() if result < before || result > after { t.Errorf("Expected current time to be between %d and %d, got %d", before, after, result) } } // Tests for AIBrainAdapter func TestNewAIBrainAdapter(t *testing.T) { npc := NewMockNPC(123, "TestNPC") logger := NewMockLogger() adapter := NewAIBrainAdapter(npc, logger) if adapter == nil { t.Fatal("NewAIBrainAdapter returned nil") } if adapter.GetNPC() != npc { t.Error("Expected adapter NPC to be the provided NPC") } } func TestAIBrainAdapterSetupDefaultBrain(t *testing.T) { npc := NewMockNPC(123, "TestNPC") logger := NewMockLogger() adapter := NewAIBrainAdapter(npc, logger) brain := adapter.SetupDefaultBrain() if brain == nil { t.Fatal("SetupDefaultBrain returned nil") } if brain.GetBrainType() != BrainTypeDefault { t.Errorf("Expected brain type %d, got %d", BrainTypeDefault, brain.GetBrainType()) } } func TestAIBrainAdapterSetupPetBrain(t *testing.T) { npc := NewMockNPC(123, "TestPet") logger := NewMockLogger() adapter := NewAIBrainAdapter(npc, logger) // Test combat pet brain combatBrain := adapter.SetupPetBrain(true) if combatBrain.GetBrainType() != BrainTypeCombatPet { t.Errorf("Expected combat pet brain type %d, got %d", BrainTypeCombatPet, combatBrain.GetBrainType()) } // Test non-combat pet brain nonCombatBrain := adapter.SetupPetBrain(false) if nonCombatBrain.GetBrainType() != BrainTypeNonCombatPet { t.Errorf("Expected non-combat pet brain type %d, got %d", BrainTypeNonCombatPet, nonCombatBrain.GetBrainType()) } } func TestAIBrainAdapterProcessAI(t *testing.T) { npc := NewMockNPC(123, "TestNPC") logger := NewMockLogger() adapter := NewAIBrainAdapter(npc, logger) brain := NewBaseBrain(npc, logger) err := adapter.ProcessAI(brain) if err != nil { t.Errorf("Expected no error processing AI, got: %v", err) } // Test with nil brain err = adapter.ProcessAI(nil) if err == nil { t.Error("Expected error processing AI with nil brain") } // Test with inactive brain brain.SetActive(false) err = adapter.ProcessAI(brain) if err != nil { t.Errorf("Expected no error processing inactive AI, got: %v", err) } } // Tests for HateListDebugger func TestNewHateListDebugger(t *testing.T) { logger := NewMockLogger() debugger := NewHateListDebugger(logger) if debugger == nil { t.Fatal("NewHateListDebugger returned nil") } } func TestHateListDebuggerPrintHateList(t *testing.T) { logger := NewMockLogger() debugger := NewHateListDebugger(logger) hateList := make(map[int32]*HateEntry) hateList[123] = &HateEntry{EntityID: 123, HateValue: 500} hateList[456] = &HateEntry{EntityID: 456, HateValue: 300} debugger.PrintHateList("TestNPC", hateList) messages := logger.GetMessages() if len(messages) == 0 { t.Error("Expected debug messages to be logged") } } func TestHateListDebuggerPrintEncounterList(t *testing.T) { logger := NewMockLogger() debugger := NewHateListDebugger(logger) encounterList := make(map[int32]*EncounterEntry) encounterList[123] = &EncounterEntry{EntityID: 123, IsPlayer: true} encounterList[456] = &EncounterEntry{EntityID: 456, IsBot: true} debugger.PrintEncounterList("TestNPC", encounterList) messages := logger.GetMessages() if len(messages) == 0 { t.Error("Expected debug messages to be logged") } } // Benchmark tests func BenchmarkHateListAddHate(b *testing.B) { hateList := NewHateList() b.ResetTimer() for i := 0; i < b.N; i++ { hateList.AddHate(int32(i), 100) } } func BenchmarkHateListGetMostHated(b *testing.B) { hateList := NewHateList() // Populate with some data for i := 0; i < 100; i++ { hateList.AddHate(int32(i), int32(i*10)) } b.ResetTimer() for i := 0; i < b.N; i++ { hateList.GetMostHated() } } func BenchmarkBrainThink(b *testing.B) { npc := NewMockNPC(123, "TestNPC") logger := NewMockLogger() brain := NewBaseBrain(npc, logger) b.ResetTimer() for i := 0; i < b.N; i++ { brain.Think() } } func BenchmarkEncounterListAddEntity(b *testing.B) { encounterList := NewEncounterList() b.ResetTimer() for i := 0; i < b.N; i++ { encounterList.AddEntity(int32(i), int32(i*10), i%2 == 0, i%3 == 0) } } func BenchmarkAIManagerProcessAllBrains(b *testing.B) { logger := NewMockLogger() luaInterface := NewMockLuaInterface() manager := NewAIManager(logger, luaInterface) // Add some brains for i := 0; i < 10; i++ { npc := NewMockNPC(int32(i), "TestNPC") brain := NewBaseBrain(npc, logger) manager.AddBrain(int32(i), brain) } b.ResetTimer() for i := 0; i < b.N; i++ { manager.ProcessAllBrains() } }