eq2go/internal/npc/ai/ai_test.go

1624 lines
42 KiB
Go

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()
}
}