simplify/enhance entity
This commit is contained in:
parent
4b32b0e3ee
commit
2b233535aa
@ -831,11 +831,259 @@ func (info *InfoStruct) Clone() *InfoStruct {
|
|||||||
info.mutex.RLock()
|
info.mutex.RLock()
|
||||||
defer info.mutex.RUnlock()
|
defer info.mutex.RUnlock()
|
||||||
|
|
||||||
clone := &InfoStruct{}
|
clone := &InfoStruct{
|
||||||
*clone = *info // Copy all fields
|
// Basic character information
|
||||||
|
name: info.name,
|
||||||
|
class1: info.class1,
|
||||||
|
class2: info.class2,
|
||||||
|
class3: info.class3,
|
||||||
|
race: info.race,
|
||||||
|
gender: info.gender,
|
||||||
|
level: info.level,
|
||||||
|
maxLevel: info.maxLevel,
|
||||||
|
effectiveLevel: info.effectiveLevel,
|
||||||
|
tradeskillLevel: info.tradeskillLevel,
|
||||||
|
tradeskillMaxLevel: info.tradeskillMaxLevel,
|
||||||
|
|
||||||
// Reset the mutex in the clone to avoid sharing the same mutex
|
// Concentration system
|
||||||
clone.mutex = sync.RWMutex{}
|
curConcentration: info.curConcentration,
|
||||||
|
maxConcentration: info.maxConcentration,
|
||||||
|
|
||||||
|
// Combat statistics
|
||||||
|
curAttack: info.curAttack,
|
||||||
|
attackBase: info.attackBase,
|
||||||
|
curMitigation: info.curMitigation,
|
||||||
|
maxMitigation: info.maxMitigation,
|
||||||
|
mitigationBase: info.mitigationBase,
|
||||||
|
mitigationMod: info.mitigationMod,
|
||||||
|
|
||||||
|
// Avoidance statistics
|
||||||
|
avoidanceDisplay: info.avoidanceDisplay,
|
||||||
|
curAvoidance: info.curAvoidance,
|
||||||
|
baseAvoidancePct: info.baseAvoidancePct,
|
||||||
|
avoidanceBase: info.avoidanceBase,
|
||||||
|
maxAvoidance: info.maxAvoidance,
|
||||||
|
parry: info.parry,
|
||||||
|
parryBase: info.parryBase,
|
||||||
|
deflection: info.deflection,
|
||||||
|
deflectionBase: info.deflectionBase,
|
||||||
|
block: info.block,
|
||||||
|
blockBase: info.blockBase,
|
||||||
|
|
||||||
|
// Primary attributes
|
||||||
|
str: info.str,
|
||||||
|
sta: info.sta,
|
||||||
|
agi: info.agi,
|
||||||
|
wis: info.wis,
|
||||||
|
intel: info.intel,
|
||||||
|
strBase: info.strBase,
|
||||||
|
staBase: info.staBase,
|
||||||
|
agiBase: info.agiBase,
|
||||||
|
wisBase: info.wisBase,
|
||||||
|
intelBase: info.intelBase,
|
||||||
|
|
||||||
|
// Resistances
|
||||||
|
heat: info.heat,
|
||||||
|
cold: info.cold,
|
||||||
|
magic: info.magic,
|
||||||
|
mental: info.mental,
|
||||||
|
divine: info.divine,
|
||||||
|
disease: info.disease,
|
||||||
|
poison: info.poison,
|
||||||
|
heatBase: info.heatBase,
|
||||||
|
coldBase: info.coldBase,
|
||||||
|
magicBase: info.magicBase,
|
||||||
|
mentalBase: info.mentalBase,
|
||||||
|
divineBase: info.divineBase,
|
||||||
|
diseaseBase: info.diseaseBase,
|
||||||
|
poisonBase: info.poisonBase,
|
||||||
|
elementalBase: info.elementalBase,
|
||||||
|
noxiousBase: info.noxiousBase,
|
||||||
|
arcaneBase: info.arcaneBase,
|
||||||
|
|
||||||
|
// Currency
|
||||||
|
coinCopper: info.coinCopper,
|
||||||
|
coinSilver: info.coinSilver,
|
||||||
|
coinGold: info.coinGold,
|
||||||
|
coinPlat: info.coinPlat,
|
||||||
|
bankCoinCopper: info.bankCoinCopper,
|
||||||
|
bankCoinSilver: info.bankCoinSilver,
|
||||||
|
bankCoinGold: info.bankCoinGold,
|
||||||
|
bankCoinPlat: info.bankCoinPlat,
|
||||||
|
statusPoints: info.statusPoints,
|
||||||
|
|
||||||
|
// Character details
|
||||||
|
deity: info.deity,
|
||||||
|
weight: info.weight,
|
||||||
|
maxWeight: info.maxWeight,
|
||||||
|
|
||||||
|
// Tradeskill classes
|
||||||
|
tradeskillClass1: info.tradeskillClass1,
|
||||||
|
tradeskillClass2: info.tradeskillClass2,
|
||||||
|
tradeskillClass3: info.tradeskillClass3,
|
||||||
|
|
||||||
|
// Account and character age
|
||||||
|
accountAgeBase: info.accountAgeBase,
|
||||||
|
|
||||||
|
// Combat and damage
|
||||||
|
absorb: info.absorb,
|
||||||
|
|
||||||
|
// Experience points
|
||||||
|
xp: info.xp,
|
||||||
|
xpNeeded: info.xpNeeded,
|
||||||
|
xpDebt: info.xpDebt,
|
||||||
|
xpYellow: info.xpYellow,
|
||||||
|
xpYellowVitalityBar: info.xpYellowVitalityBar,
|
||||||
|
xpBlueVitalityBar: info.xpBlueVitalityBar,
|
||||||
|
xpBlue: info.xpBlue,
|
||||||
|
tsXp: info.tsXp,
|
||||||
|
tsXpNeeded: info.tsXpNeeded,
|
||||||
|
tradeskillExpYellow: info.tradeskillExpYellow,
|
||||||
|
tradeskillExpBlue: info.tradeskillExpBlue,
|
||||||
|
xpVitality: info.xpVitality,
|
||||||
|
tradeskillXpVitality: info.tradeskillXpVitality,
|
||||||
|
|
||||||
|
// Flags and states
|
||||||
|
flags: info.flags,
|
||||||
|
flags2: info.flags2,
|
||||||
|
|
||||||
|
// Specialized mitigation
|
||||||
|
mitigationSkill1: info.mitigationSkill1,
|
||||||
|
mitigationSkill2: info.mitigationSkill2,
|
||||||
|
mitigationSkill3: info.mitigationSkill3,
|
||||||
|
mitigationPve: info.mitigationPve,
|
||||||
|
mitigationPvp: info.mitigationPvp,
|
||||||
|
|
||||||
|
// Combat modifiers
|
||||||
|
abilityModifier: info.abilityModifier,
|
||||||
|
criticalMitigation: info.criticalMitigation,
|
||||||
|
blockChance: info.blockChance,
|
||||||
|
uncontestedParry: info.uncontestedParry,
|
||||||
|
uncontestedBlock: info.uncontestedBlock,
|
||||||
|
uncontestedDodge: info.uncontestedDodge,
|
||||||
|
uncontestedRiposte: info.uncontestedRiposte,
|
||||||
|
critChance: info.critChance,
|
||||||
|
critBonus: info.critBonus,
|
||||||
|
potency: info.potency,
|
||||||
|
hateMod: info.hateMod,
|
||||||
|
reuseSpeed: info.reuseSpeed,
|
||||||
|
castingSpeed: info.castingSpeed,
|
||||||
|
recoverySpeed: info.recoverySpeed,
|
||||||
|
spellReuseSpeed: info.spellReuseSpeed,
|
||||||
|
spellMultiAttack: info.spellMultiAttack,
|
||||||
|
|
||||||
|
// Size and physical attributes
|
||||||
|
sizeMod: info.sizeMod,
|
||||||
|
ignoreSizeModCalc: info.ignoreSizeModCalc,
|
||||||
|
dps: info.dps,
|
||||||
|
dpsMultiplier: info.dpsMultiplier,
|
||||||
|
attackSpeed: info.attackSpeed,
|
||||||
|
haste: info.haste,
|
||||||
|
multiAttack: info.multiAttack,
|
||||||
|
flurry: info.flurry,
|
||||||
|
meleeAe: info.meleeAe,
|
||||||
|
strikethrough: info.strikethrough,
|
||||||
|
accuracy: info.accuracy,
|
||||||
|
offensiveSpeed: info.offensiveSpeed,
|
||||||
|
|
||||||
|
// Environmental
|
||||||
|
rain: info.rain,
|
||||||
|
wind: info.wind,
|
||||||
|
alignment: info.alignment,
|
||||||
|
|
||||||
|
// Pet information
|
||||||
|
petId: info.petId,
|
||||||
|
petName: info.petName,
|
||||||
|
petHealthPct: info.petHealthPct,
|
||||||
|
petPowerPct: info.petPowerPct,
|
||||||
|
petMovement: info.petMovement,
|
||||||
|
petBehavior: info.petBehavior,
|
||||||
|
|
||||||
|
// Character abilities
|
||||||
|
vision: info.vision,
|
||||||
|
breatheUnderwater: info.breatheUnderwater,
|
||||||
|
biography: info.biography,
|
||||||
|
drunk: info.drunk,
|
||||||
|
|
||||||
|
// Regeneration
|
||||||
|
powerRegen: info.powerRegen,
|
||||||
|
hpRegen: info.hpRegen,
|
||||||
|
powerRegenOverride: info.powerRegenOverride,
|
||||||
|
hpRegenOverride: info.hpRegenOverride,
|
||||||
|
|
||||||
|
// Movement types
|
||||||
|
waterType: info.waterType,
|
||||||
|
flyingType: info.flyingType,
|
||||||
|
|
||||||
|
// Special flags
|
||||||
|
noInterrupt: info.noInterrupt,
|
||||||
|
interactionFlag: info.interactionFlag,
|
||||||
|
tag1: info.tag1,
|
||||||
|
mood: info.mood,
|
||||||
|
|
||||||
|
// Weapon timing
|
||||||
|
rangeLastAttackTime: info.rangeLastAttackTime,
|
||||||
|
primaryLastAttackTime: info.primaryLastAttackTime,
|
||||||
|
secondaryLastAttackTime: info.secondaryLastAttackTime,
|
||||||
|
primaryAttackDelay: info.primaryAttackDelay,
|
||||||
|
secondaryAttackDelay: info.secondaryAttackDelay,
|
||||||
|
rangedAttackDelay: info.rangedAttackDelay,
|
||||||
|
|
||||||
|
// Weapon information
|
||||||
|
primaryWeaponType: info.primaryWeaponType,
|
||||||
|
secondaryWeaponType: info.secondaryWeaponType,
|
||||||
|
rangedWeaponType: info.rangedWeaponType,
|
||||||
|
primaryWeaponDmgLow: info.primaryWeaponDmgLow,
|
||||||
|
primaryWeaponDmgHigh: info.primaryWeaponDmgHigh,
|
||||||
|
secondaryWeaponDmgLow: info.secondaryWeaponDmgLow,
|
||||||
|
secondaryWeaponDmgHigh: info.secondaryWeaponDmgHigh,
|
||||||
|
rangedWeaponDmgLow: info.rangedWeaponDmgLow,
|
||||||
|
rangedWeaponDmgHigh: info.rangedWeaponDmgHigh,
|
||||||
|
wieldType: info.wieldType,
|
||||||
|
attackType: info.attackType,
|
||||||
|
primaryWeaponDelay: info.primaryWeaponDelay,
|
||||||
|
secondaryWeaponDelay: info.secondaryWeaponDelay,
|
||||||
|
rangedWeaponDelay: info.rangedWeaponDelay,
|
||||||
|
|
||||||
|
// Weapon overrides
|
||||||
|
overridePrimaryWeapon: info.overridePrimaryWeapon,
|
||||||
|
overrideSecondaryWeapon: info.overrideSecondaryWeapon,
|
||||||
|
overrideRangedWeapon: info.overrideRangedWeapon,
|
||||||
|
|
||||||
|
// NPC specific
|
||||||
|
friendlyTargetNpc: info.friendlyTargetNpc,
|
||||||
|
lastClaimTime: info.lastClaimTime,
|
||||||
|
|
||||||
|
// Encounter system
|
||||||
|
engagedEncounter: info.engagedEncounter,
|
||||||
|
lockableEncounter: info.lockableEncounter,
|
||||||
|
|
||||||
|
// Player flags
|
||||||
|
firstWorldLogin: info.firstWorldLogin,
|
||||||
|
reloadPlayerSpells: info.reloadPlayerSpells,
|
||||||
|
|
||||||
|
// Group settings
|
||||||
|
groupLootMethod: info.groupLootMethod,
|
||||||
|
groupLootItemsRarity: info.groupLootItemsRarity,
|
||||||
|
groupAutoSplit: info.groupAutoSplit,
|
||||||
|
groupDefaultYell: info.groupDefaultYell,
|
||||||
|
groupAutolock: info.groupAutolock,
|
||||||
|
groupLockMethod: info.groupLockMethod,
|
||||||
|
groupSoloAutolock: info.groupSoloAutolock,
|
||||||
|
groupAutoLootMethod: info.groupAutoLootMethod,
|
||||||
|
assistAutoAttack: info.assistAutoAttack,
|
||||||
|
|
||||||
|
// Action state
|
||||||
|
actionState: info.actionState,
|
||||||
|
|
||||||
|
// Spell and ability reductions
|
||||||
|
spellMulticastChance: info.spellMulticastChance,
|
||||||
|
maxSpellReductionOverride: info.maxSpellReductionOverride,
|
||||||
|
maxChaseDistance: info.maxChaseDistance,
|
||||||
|
|
||||||
|
// Thread safety - new mutex for the clone
|
||||||
|
mutex: sync.RWMutex{},
|
||||||
|
}
|
||||||
|
|
||||||
// Copy the account age bonus array
|
// Copy the account age bonus array
|
||||||
copy(clone.accountAgeBonus[:], info.accountAgeBonus[:])
|
copy(clone.accountAgeBonus[:], info.accountAgeBonus[:])
|
||||||
|
651
internal/entity/manager.go
Normal file
651
internal/entity/manager.go
Normal file
@ -0,0 +1,651 @@
|
|||||||
|
package entity
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"eq2emu/internal/packets"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Manager provides centralized management for the entity system
|
||||||
|
// following the SIMPLIFICATION.md methodology while maintaining C++ API compatibility
|
||||||
|
type Manager struct {
|
||||||
|
// Thread safety
|
||||||
|
mutex sync.RWMutex
|
||||||
|
|
||||||
|
// Performance monitoring
|
||||||
|
stats struct {
|
||||||
|
// Entity operations
|
||||||
|
EntitiesCreated int64
|
||||||
|
EntitiesDestroyed int64
|
||||||
|
EntitiesUpdated int64
|
||||||
|
|
||||||
|
// Spell effect operations
|
||||||
|
SpellEffectsAdded int64
|
||||||
|
SpellEffectsRemoved int64
|
||||||
|
BonusCalculations int64
|
||||||
|
|
||||||
|
// Combat operations
|
||||||
|
DamageCalculations int64
|
||||||
|
HealingCalculations int64
|
||||||
|
CombatActions int64
|
||||||
|
|
||||||
|
// Packet operations
|
||||||
|
PacketsSent int64
|
||||||
|
PacketErrors int64
|
||||||
|
VerbRequests int64
|
||||||
|
|
||||||
|
// Cache performance
|
||||||
|
CacheHits int64
|
||||||
|
CacheMisses int64
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configuration
|
||||||
|
config struct {
|
||||||
|
EnableStatistics bool
|
||||||
|
EnableValidation bool
|
||||||
|
EnableCombatLogging bool
|
||||||
|
MaxEntitiesPerZone int
|
||||||
|
EffectCleanupInterval time.Duration
|
||||||
|
BonusCalculationInterval time.Duration
|
||||||
|
PacketBuilderTimeout time.Duration
|
||||||
|
EnablePetManagement bool
|
||||||
|
EnableSpellEffectCaching bool
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewManager creates a new entity manager with default configuration
|
||||||
|
func NewManager() *Manager {
|
||||||
|
m := &Manager{}
|
||||||
|
|
||||||
|
// Set default configuration
|
||||||
|
m.config.EnableStatistics = true
|
||||||
|
m.config.EnableValidation = true
|
||||||
|
m.config.EnableCombatLogging = false
|
||||||
|
m.config.MaxEntitiesPerZone = 10000
|
||||||
|
m.config.EffectCleanupInterval = 30 * time.Second
|
||||||
|
m.config.BonusCalculationInterval = 5 * time.Second
|
||||||
|
m.config.PacketBuilderTimeout = 30 * time.Second
|
||||||
|
m.config.EnablePetManagement = true
|
||||||
|
m.config.EnableSpellEffectCaching = true
|
||||||
|
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
// === Entity Creation and Management ===
|
||||||
|
|
||||||
|
// CreateEntity creates a new Entity with proper initialization
|
||||||
|
func (m *Manager) CreateEntity() *Entity {
|
||||||
|
entity := NewEntity()
|
||||||
|
|
||||||
|
if m.config.EnableStatistics {
|
||||||
|
atomic.AddInt64(&m.stats.EntitiesCreated, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
return entity
|
||||||
|
}
|
||||||
|
|
||||||
|
// DestroyEntity properly cleans up an entity
|
||||||
|
func (m *Manager) DestroyEntity(entity *Entity) error {
|
||||||
|
if entity == nil {
|
||||||
|
return fmt.Errorf("entity cannot be nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean up spell effects
|
||||||
|
entity.DeleteSpellEffects(false)
|
||||||
|
|
||||||
|
// Clean up pets
|
||||||
|
if entity.GetPet() != nil {
|
||||||
|
entity.SetPet(nil)
|
||||||
|
}
|
||||||
|
if entity.GetCharmedPet() != nil {
|
||||||
|
entity.SetCharmedPet(nil)
|
||||||
|
}
|
||||||
|
if entity.GetDeityPet() != nil {
|
||||||
|
entity.SetDeityPet(nil)
|
||||||
|
}
|
||||||
|
if entity.GetCosmeticPet() != nil {
|
||||||
|
entity.SetCosmeticPet(nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
if m.config.EnableStatistics {
|
||||||
|
atomic.AddInt64(&m.stats.EntitiesDestroyed, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// === C++ API Compatibility Methods ===
|
||||||
|
|
||||||
|
// GetEntityInfoStruct returns an entity's info structure (C++ API compatibility)
|
||||||
|
func (m *Manager) GetEntityInfoStruct(entity *Entity) *InfoStruct {
|
||||||
|
if entity == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return entity.GetInfoStruct()
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetEntityInfoStruct updates an entity's info structure (C++ API compatibility)
|
||||||
|
func (m *Manager) SetEntityInfoStruct(entity *Entity, info *InfoStruct) {
|
||||||
|
if entity != nil && info != nil {
|
||||||
|
entity.SetInfoStruct(info)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsEntityInCombat returns whether an entity is in combat (C++ API compatibility)
|
||||||
|
func (m *Manager) IsEntityInCombat(entity *Entity) bool {
|
||||||
|
if entity == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return entity.IsInCombat()
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetEntityInCombat updates an entity's combat state (C++ API compatibility)
|
||||||
|
func (m *Manager) SetEntityInCombat(entity *Entity, inCombat bool) {
|
||||||
|
if entity != nil {
|
||||||
|
entity.SetInCombat(inCombat)
|
||||||
|
if m.config.EnableStatistics && inCombat {
|
||||||
|
atomic.AddInt64(&m.stats.CombatActions, 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsEntityCasting returns whether an entity is casting (C++ API compatibility)
|
||||||
|
func (m *Manager) IsEntityCasting(entity *Entity) bool {
|
||||||
|
if entity == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return entity.IsCasting()
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetEntityCasting updates an entity's casting state (C++ API compatibility)
|
||||||
|
func (m *Manager) SetEntityCasting(entity *Entity, casting bool) {
|
||||||
|
if entity != nil {
|
||||||
|
entity.SetCasting(casting)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// === Spell Effect Management ===
|
||||||
|
|
||||||
|
// AddMaintainedSpell adds a maintained spell to an entity
|
||||||
|
func (m *Manager) AddMaintainedSpell(entity *Entity, name string, spellID int32, duration float32, concentration int8) bool {
|
||||||
|
if entity == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
success := entity.AddMaintainedSpell(name, spellID, duration, concentration)
|
||||||
|
|
||||||
|
if m.config.EnableStatistics && success {
|
||||||
|
atomic.AddInt64(&m.stats.SpellEffectsAdded, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
return success
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveMaintainedSpell removes a maintained spell from an entity
|
||||||
|
func (m *Manager) RemoveMaintainedSpell(entity *Entity, spellID int32) bool {
|
||||||
|
if entity == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
success := entity.RemoveMaintainedSpell(spellID)
|
||||||
|
|
||||||
|
if m.config.EnableStatistics && success {
|
||||||
|
atomic.AddInt64(&m.stats.SpellEffectsRemoved, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
return success
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddSpellEffect adds a temporary spell effect to an entity
|
||||||
|
func (m *Manager) AddSpellEffect(entity *Entity, spellID int32, casterID int32, duration float32) bool {
|
||||||
|
if entity == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
success := entity.AddSpellEffect(spellID, casterID, duration)
|
||||||
|
|
||||||
|
if m.config.EnableStatistics && success {
|
||||||
|
atomic.AddInt64(&m.stats.SpellEffectsAdded, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
return success
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveSpellEffect removes a spell effect from an entity
|
||||||
|
func (m *Manager) RemoveSpellEffect(entity *Entity, spellID int32) bool {
|
||||||
|
if entity == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
success := entity.RemoveSpellEffect(spellID)
|
||||||
|
|
||||||
|
if m.config.EnableStatistics && success {
|
||||||
|
atomic.AddInt64(&m.stats.SpellEffectsRemoved, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
return success
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddDetrimentalSpell adds a detrimental effect to an entity
|
||||||
|
func (m *Manager) AddDetrimentalSpell(entity *Entity, spellID int32, casterID int32, duration float32, detType int8) {
|
||||||
|
if entity != nil {
|
||||||
|
entity.AddDetrimentalSpell(spellID, casterID, duration, detType)
|
||||||
|
|
||||||
|
if m.config.EnableStatistics {
|
||||||
|
atomic.AddInt64(&m.stats.SpellEffectsAdded, 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveDetrimentalSpell removes a detrimental effect from an entity
|
||||||
|
func (m *Manager) RemoveDetrimentalSpell(entity *Entity, spellID int32, casterID int32) bool {
|
||||||
|
if entity == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
success := entity.RemoveDetrimentalSpell(spellID, casterID)
|
||||||
|
|
||||||
|
if m.config.EnableStatistics && success {
|
||||||
|
atomic.AddInt64(&m.stats.SpellEffectsRemoved, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
return success
|
||||||
|
}
|
||||||
|
|
||||||
|
// CalculateEntityBonuses recalculates all bonuses for an entity
|
||||||
|
func (m *Manager) CalculateEntityBonuses(entity *Entity) {
|
||||||
|
if entity != nil {
|
||||||
|
entity.CalculateBonuses()
|
||||||
|
|
||||||
|
if m.config.EnableStatistics {
|
||||||
|
atomic.AddInt64(&m.stats.BonusCalculations, 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// === Pet Management ===
|
||||||
|
|
||||||
|
// SetEntityPet assigns a summon pet to an entity
|
||||||
|
func (m *Manager) SetEntityPet(entity *Entity, pet *Entity) {
|
||||||
|
if entity != nil {
|
||||||
|
entity.SetPet(pet)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetEntityPet returns an entity's summon pet
|
||||||
|
func (m *Manager) GetEntityPet(entity *Entity) *Entity {
|
||||||
|
if entity == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return entity.GetPet()
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetEntityCharmedPet assigns a charmed pet to an entity
|
||||||
|
func (m *Manager) SetEntityCharmedPet(entity *Entity, pet *Entity) {
|
||||||
|
if entity != nil {
|
||||||
|
entity.SetCharmedPet(pet)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetEntityCharmedPet returns an entity's charmed pet
|
||||||
|
func (m *Manager) GetEntityCharmedPet(entity *Entity) *Entity {
|
||||||
|
if entity == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return entity.GetCharmedPet()
|
||||||
|
}
|
||||||
|
|
||||||
|
// DismissPet marks a pet for dismissal
|
||||||
|
func (m *Manager) DismissPet(pet *Entity) {
|
||||||
|
if pet != nil {
|
||||||
|
pet.SetPetDismissing(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// === Movement and Position ===
|
||||||
|
|
||||||
|
// UpdateEntityPosition updates an entity's position tracking
|
||||||
|
func (m *Manager) UpdateEntityPosition(entity *Entity) {
|
||||||
|
if entity != nil {
|
||||||
|
// Update last position from current position
|
||||||
|
x := entity.GetX()
|
||||||
|
y := entity.GetY()
|
||||||
|
z := entity.GetZ()
|
||||||
|
heading := entity.GetHeading()
|
||||||
|
|
||||||
|
entity.SetLastPosition(x, y, z, heading)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// HasEntityMoved checks if an entity has moved since last update
|
||||||
|
func (m *Manager) HasEntityMoved(entity *Entity) bool {
|
||||||
|
if entity == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return entity.HasMoved()
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetEntityEffectiveSpeed calculates an entity's current effective speed
|
||||||
|
func (m *Manager) GetEntityEffectiveSpeed(entity *Entity) float32 {
|
||||||
|
if entity == nil {
|
||||||
|
return 0.0
|
||||||
|
}
|
||||||
|
return entity.CalculateEffectiveSpeed()
|
||||||
|
}
|
||||||
|
|
||||||
|
// === Stat Calculations ===
|
||||||
|
|
||||||
|
// GetEntityPrimaryStat returns an entity's highest primary stat
|
||||||
|
func (m *Manager) GetEntityPrimaryStat(entity *Entity) int16 {
|
||||||
|
if entity == nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return entity.GetPrimaryStat()
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetEntityResistance returns an entity's resistance to a specific damage type
|
||||||
|
func (m *Manager) GetEntityResistance(entity *Entity, resistanceType string) int16 {
|
||||||
|
if entity == nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
switch resistanceType {
|
||||||
|
case "heat":
|
||||||
|
return entity.GetHeatResistance()
|
||||||
|
case "cold":
|
||||||
|
return entity.GetColdResistance()
|
||||||
|
case "magic":
|
||||||
|
return entity.GetMagicResistance()
|
||||||
|
case "mental":
|
||||||
|
return entity.GetMentalResistance()
|
||||||
|
case "divine":
|
||||||
|
return entity.GetDivineResistance()
|
||||||
|
case "disease":
|
||||||
|
return entity.GetDiseaseResistance()
|
||||||
|
case "poison":
|
||||||
|
return entity.GetPoisonResistance()
|
||||||
|
default:
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// === Packet Building Methods ===
|
||||||
|
|
||||||
|
// SendEntityVerbsRequest builds and returns entity verbs request packet data (C++ API compatibility)
|
||||||
|
func (m *Manager) SendEntityVerbsRequest(entityID int32, clientVersion uint32) ([]byte, error) {
|
||||||
|
packet, exists := packets.GetPacket("WS_EntityVerbsRequest")
|
||||||
|
if !exists {
|
||||||
|
if m.config.EnableStatistics {
|
||||||
|
atomic.AddInt64(&m.stats.PacketErrors, 1)
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("failed to get WS_EntityVerbsRequest packet structure: packet not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
data := map[string]interface{}{
|
||||||
|
"entity_id": entityID,
|
||||||
|
}
|
||||||
|
|
||||||
|
builder := packets.NewPacketBuilder(packet, clientVersion, 0)
|
||||||
|
packetData, err := builder.Build(data)
|
||||||
|
if err != nil {
|
||||||
|
if m.config.EnableStatistics {
|
||||||
|
atomic.AddInt64(&m.stats.PacketErrors, 1)
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("failed to build entity verbs request packet: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if m.config.EnableStatistics {
|
||||||
|
atomic.AddInt64(&m.stats.PacketsSent, 1)
|
||||||
|
atomic.AddInt64(&m.stats.VerbRequests, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
return packetData, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SendEntityVerbsResponse builds and returns entity verbs response packet data (C++ API compatibility)
|
||||||
|
func (m *Manager) SendEntityVerbsResponse(entityID int32, clientVersion uint32, verbs []map[string]interface{}) ([]byte, error) {
|
||||||
|
packet, exists := packets.GetPacket("WS_EntityVerbsResponse")
|
||||||
|
if !exists {
|
||||||
|
if m.config.EnableStatistics {
|
||||||
|
atomic.AddInt64(&m.stats.PacketErrors, 1)
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("failed to get WS_EntityVerbsResponse packet structure: packet not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
data := map[string]interface{}{
|
||||||
|
"entity_id": entityID,
|
||||||
|
"num_verbs": len(verbs),
|
||||||
|
"verbs_array": verbs,
|
||||||
|
}
|
||||||
|
|
||||||
|
builder := packets.NewPacketBuilder(packet, clientVersion, 0)
|
||||||
|
packetData, err := builder.Build(data)
|
||||||
|
if err != nil {
|
||||||
|
if m.config.EnableStatistics {
|
||||||
|
atomic.AddInt64(&m.stats.PacketErrors, 1)
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("failed to build entity verbs response packet: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if m.config.EnableStatistics {
|
||||||
|
atomic.AddInt64(&m.stats.PacketsSent, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
return packetData, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SendEntityVerbsVerb builds and returns entity verb action packet data (C++ API compatibility)
|
||||||
|
func (m *Manager) SendEntityVerbsVerb(entityID int32, targetID int32, clientVersion uint32, verbID int32, verbName string) ([]byte, error) {
|
||||||
|
packet, exists := packets.GetPacket("WS_EntityVerbsVerb")
|
||||||
|
if !exists {
|
||||||
|
// Try backup packet structure
|
||||||
|
packet, exists = packets.GetPacket("WS_EntityVerbsVerbBackup")
|
||||||
|
if !exists {
|
||||||
|
if m.config.EnableStatistics {
|
||||||
|
atomic.AddInt64(&m.stats.PacketErrors, 1)
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("failed to get WS_EntityVerbsVerb packet structure: packet not found")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data := map[string]interface{}{
|
||||||
|
"entity_id": entityID,
|
||||||
|
"target_id": targetID,
|
||||||
|
"verb_id": verbID,
|
||||||
|
"verb_name": verbName,
|
||||||
|
}
|
||||||
|
|
||||||
|
builder := packets.NewPacketBuilder(packet, clientVersion, 0)
|
||||||
|
packetData, err := builder.Build(data)
|
||||||
|
if err != nil {
|
||||||
|
if m.config.EnableStatistics {
|
||||||
|
atomic.AddInt64(&m.stats.PacketErrors, 1)
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("failed to build entity verb action packet: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if m.config.EnableStatistics {
|
||||||
|
atomic.AddInt64(&m.stats.PacketsSent, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
return packetData, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// === Effect Processing ===
|
||||||
|
|
||||||
|
// ProcessEntityEffects handles periodic effect processing for an entity
|
||||||
|
func (m *Manager) ProcessEntityEffects(entity *Entity) {
|
||||||
|
if entity != nil {
|
||||||
|
entity.ProcessEffects()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CleanupEntityEffects removes expired effects from an entity
|
||||||
|
func (m *Manager) CleanupEntityEffects(entity *Entity) {
|
||||||
|
if entity != nil {
|
||||||
|
entity.ProcessEffects() // This includes cleanup
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// === Statistics and Monitoring ===
|
||||||
|
|
||||||
|
// GetStatistics returns comprehensive manager statistics
|
||||||
|
func (m *Manager) GetStatistics() map[string]interface{} {
|
||||||
|
m.mutex.RLock()
|
||||||
|
defer m.mutex.RUnlock()
|
||||||
|
|
||||||
|
stats := make(map[string]interface{})
|
||||||
|
|
||||||
|
// Entity stats (using atomic loads)
|
||||||
|
stats["entities_created"] = atomic.LoadInt64(&m.stats.EntitiesCreated)
|
||||||
|
stats["entities_destroyed"] = atomic.LoadInt64(&m.stats.EntitiesDestroyed)
|
||||||
|
stats["entities_updated"] = atomic.LoadInt64(&m.stats.EntitiesUpdated)
|
||||||
|
|
||||||
|
// Spell effect stats (using atomic loads)
|
||||||
|
stats["spell_effects_added"] = atomic.LoadInt64(&m.stats.SpellEffectsAdded)
|
||||||
|
stats["spell_effects_removed"] = atomic.LoadInt64(&m.stats.SpellEffectsRemoved)
|
||||||
|
stats["bonus_calculations"] = atomic.LoadInt64(&m.stats.BonusCalculations)
|
||||||
|
|
||||||
|
// Combat stats (using atomic loads)
|
||||||
|
stats["damage_calculations"] = atomic.LoadInt64(&m.stats.DamageCalculations)
|
||||||
|
stats["healing_calculations"] = atomic.LoadInt64(&m.stats.HealingCalculations)
|
||||||
|
stats["combat_actions"] = atomic.LoadInt64(&m.stats.CombatActions)
|
||||||
|
|
||||||
|
// Packet stats (using atomic loads)
|
||||||
|
stats["packets_sent"] = atomic.LoadInt64(&m.stats.PacketsSent)
|
||||||
|
stats["packet_errors"] = atomic.LoadInt64(&m.stats.PacketErrors)
|
||||||
|
stats["verb_requests"] = atomic.LoadInt64(&m.stats.VerbRequests)
|
||||||
|
|
||||||
|
// Cache stats (using atomic loads)
|
||||||
|
cacheHits := atomic.LoadInt64(&m.stats.CacheHits)
|
||||||
|
cacheMisses := atomic.LoadInt64(&m.stats.CacheMisses)
|
||||||
|
stats["cache_hits"] = cacheHits
|
||||||
|
stats["cache_misses"] = cacheMisses
|
||||||
|
|
||||||
|
if cacheHits+cacheMisses > 0 {
|
||||||
|
hitRate := float64(cacheHits) / float64(cacheHits+cacheMisses) * 100
|
||||||
|
stats["cache_hit_rate"] = fmt.Sprintf("%.2f%%", hitRate)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configuration
|
||||||
|
stats["config_statistics_enabled"] = m.config.EnableStatistics
|
||||||
|
stats["config_validation_enabled"] = m.config.EnableValidation
|
||||||
|
stats["config_combat_logging_enabled"] = m.config.EnableCombatLogging
|
||||||
|
stats["config_pet_management_enabled"] = m.config.EnablePetManagement
|
||||||
|
|
||||||
|
return stats
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResetStatistics clears all performance counters
|
||||||
|
func (m *Manager) ResetStatistics() {
|
||||||
|
atomic.StoreInt64(&m.stats.EntitiesCreated, 0)
|
||||||
|
atomic.StoreInt64(&m.stats.EntitiesDestroyed, 0)
|
||||||
|
atomic.StoreInt64(&m.stats.EntitiesUpdated, 0)
|
||||||
|
atomic.StoreInt64(&m.stats.SpellEffectsAdded, 0)
|
||||||
|
atomic.StoreInt64(&m.stats.SpellEffectsRemoved, 0)
|
||||||
|
atomic.StoreInt64(&m.stats.BonusCalculations, 0)
|
||||||
|
atomic.StoreInt64(&m.stats.DamageCalculations, 0)
|
||||||
|
atomic.StoreInt64(&m.stats.HealingCalculations, 0)
|
||||||
|
atomic.StoreInt64(&m.stats.CombatActions, 0)
|
||||||
|
atomic.StoreInt64(&m.stats.PacketsSent, 0)
|
||||||
|
atomic.StoreInt64(&m.stats.PacketErrors, 0)
|
||||||
|
atomic.StoreInt64(&m.stats.VerbRequests, 0)
|
||||||
|
atomic.StoreInt64(&m.stats.CacheHits, 0)
|
||||||
|
atomic.StoreInt64(&m.stats.CacheMisses, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// === Configuration Management ===
|
||||||
|
|
||||||
|
// SetConfiguration updates manager configuration
|
||||||
|
func (m *Manager) SetConfiguration(config map[string]interface{}) {
|
||||||
|
m.mutex.Lock()
|
||||||
|
defer m.mutex.Unlock()
|
||||||
|
|
||||||
|
if val, ok := config["enable_statistics"].(bool); ok {
|
||||||
|
m.config.EnableStatistics = val
|
||||||
|
}
|
||||||
|
if val, ok := config["enable_validation"].(bool); ok {
|
||||||
|
m.config.EnableValidation = val
|
||||||
|
}
|
||||||
|
if val, ok := config["enable_combat_logging"].(bool); ok {
|
||||||
|
m.config.EnableCombatLogging = val
|
||||||
|
}
|
||||||
|
if val, ok := config["max_entities_per_zone"].(int); ok {
|
||||||
|
m.config.MaxEntitiesPerZone = val
|
||||||
|
}
|
||||||
|
if val, ok := config["effect_cleanup_interval"].(time.Duration); ok {
|
||||||
|
m.config.EffectCleanupInterval = val
|
||||||
|
}
|
||||||
|
if val, ok := config["enable_pet_management"].(bool); ok {
|
||||||
|
m.config.EnablePetManagement = val
|
||||||
|
}
|
||||||
|
if val, ok := config["enable_spell_effect_caching"].(bool); ok {
|
||||||
|
m.config.EnableSpellEffectCaching = val
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetConfiguration returns current manager configuration
|
||||||
|
func (m *Manager) GetConfiguration() map[string]interface{} {
|
||||||
|
m.mutex.RLock()
|
||||||
|
defer m.mutex.RUnlock()
|
||||||
|
|
||||||
|
return map[string]interface{}{
|
||||||
|
"enable_statistics": m.config.EnableStatistics,
|
||||||
|
"enable_validation": m.config.EnableValidation,
|
||||||
|
"enable_combat_logging": m.config.EnableCombatLogging,
|
||||||
|
"max_entities_per_zone": m.config.MaxEntitiesPerZone,
|
||||||
|
"effect_cleanup_interval": m.config.EffectCleanupInterval,
|
||||||
|
"bonus_calculation_interval": m.config.BonusCalculationInterval,
|
||||||
|
"packet_builder_timeout": m.config.PacketBuilderTimeout,
|
||||||
|
"enable_pet_management": m.config.EnablePetManagement,
|
||||||
|
"enable_spell_effect_caching": m.config.EnableSpellEffectCaching,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// === Validation and Health Checks ===
|
||||||
|
|
||||||
|
// ValidateEntity performs comprehensive entity validation
|
||||||
|
func (m *Manager) ValidateEntity(entity *Entity) []string {
|
||||||
|
var issues []string
|
||||||
|
|
||||||
|
if entity == nil {
|
||||||
|
return []string{"entity is nil"}
|
||||||
|
}
|
||||||
|
|
||||||
|
info := entity.GetInfoStruct()
|
||||||
|
if info == nil {
|
||||||
|
issues = append(issues, "entity info struct is nil")
|
||||||
|
return issues
|
||||||
|
}
|
||||||
|
|
||||||
|
if info.GetLevel() < 0 {
|
||||||
|
issues = append(issues, fmt.Sprintf("invalid level: %d", info.GetLevel()))
|
||||||
|
}
|
||||||
|
|
||||||
|
if info.GetName() == "" {
|
||||||
|
issues = append(issues, "entity name is empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
if entity.GetTotalHP() < 0 {
|
||||||
|
issues = append(issues, fmt.Sprintf("invalid total HP: %d", entity.GetTotalHP()))
|
||||||
|
}
|
||||||
|
|
||||||
|
if entity.GetTotalPower() < 0 {
|
||||||
|
issues = append(issues, fmt.Sprintf("invalid total power: %d", entity.GetTotalPower()))
|
||||||
|
}
|
||||||
|
|
||||||
|
return issues
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsHealthy returns true if the manager is in a healthy state
|
||||||
|
func (m *Manager) IsHealthy() bool {
|
||||||
|
m.mutex.RLock()
|
||||||
|
defer m.mutex.RUnlock()
|
||||||
|
|
||||||
|
// Manager is always considered healthy for entities
|
||||||
|
// as it's primarily a utility manager
|
||||||
|
return true
|
||||||
|
}
|
802
internal/entity/manager_test.go
Normal file
802
internal/entity/manager_test.go
Normal file
@ -0,0 +1,802 @@
|
|||||||
|
package entity
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func createTestManager() *Manager {
|
||||||
|
return NewManager()
|
||||||
|
}
|
||||||
|
|
||||||
|
func createTestEntity() *Entity {
|
||||||
|
entity := NewEntity()
|
||||||
|
|
||||||
|
// Set some basic info for testing
|
||||||
|
info := entity.GetInfoStruct()
|
||||||
|
info.SetName("Test Entity")
|
||||||
|
info.SetLevel(10)
|
||||||
|
info.SetClass1(1) // Fighter
|
||||||
|
info.SetRace(1) // Human
|
||||||
|
info.SetGender(1) // Male
|
||||||
|
info.SetStr(50.0)
|
||||||
|
info.SetSta(45.0)
|
||||||
|
info.SetAgi(40.0)
|
||||||
|
info.SetWis(35.0)
|
||||||
|
info.SetIntel(30.0)
|
||||||
|
|
||||||
|
// Set HP and Power for testing
|
||||||
|
entity.SetTotalHP(1000)
|
||||||
|
entity.SetHP(1000)
|
||||||
|
entity.SetTotalPower(500)
|
||||||
|
entity.SetPower(500)
|
||||||
|
|
||||||
|
return entity
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test Manager Creation and Configuration
|
||||||
|
func TestManagerCreation(t *testing.T) {
|
||||||
|
manager := NewManager()
|
||||||
|
if manager == nil {
|
||||||
|
t.Fatal("NewManager returned nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check default configuration
|
||||||
|
config := manager.GetConfiguration()
|
||||||
|
if config["enable_statistics"].(bool) != true {
|
||||||
|
t.Error("Expected statistics to be enabled by default")
|
||||||
|
}
|
||||||
|
if config["enable_validation"].(bool) != true {
|
||||||
|
t.Error("Expected validation to be enabled by default")
|
||||||
|
}
|
||||||
|
if config["enable_pet_management"].(bool) != true {
|
||||||
|
t.Error("Expected pet management to be enabled by default")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test Entity Creation and Destruction
|
||||||
|
func TestEntityCreationAndDestruction(t *testing.T) {
|
||||||
|
manager := createTestManager()
|
||||||
|
|
||||||
|
// Test entity creation
|
||||||
|
entity := manager.CreateEntity()
|
||||||
|
if entity == nil {
|
||||||
|
t.Fatal("CreateEntity returned nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
if entity.GetInfoStruct() == nil {
|
||||||
|
t.Error("Entity info struct is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check initial state
|
||||||
|
if entity.IsInCombat() {
|
||||||
|
t.Error("New entity should not be in combat")
|
||||||
|
}
|
||||||
|
if entity.IsCasting() {
|
||||||
|
t.Error("New entity should not be casting")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test entity destruction
|
||||||
|
err := manager.DestroyEntity(entity)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("DestroyEntity failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test destroying nil entity
|
||||||
|
err = manager.DestroyEntity(nil)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("Expected error when destroying nil entity")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test Entity State Management
|
||||||
|
func TestEntityStateManagement(t *testing.T) {
|
||||||
|
manager := createTestManager()
|
||||||
|
entity := createTestEntity()
|
||||||
|
|
||||||
|
// Test combat state
|
||||||
|
if manager.IsEntityInCombat(entity) {
|
||||||
|
t.Error("Entity should not be in combat initially")
|
||||||
|
}
|
||||||
|
|
||||||
|
manager.SetEntityInCombat(entity, true)
|
||||||
|
if !manager.IsEntityInCombat(entity) {
|
||||||
|
t.Error("Entity should be in combat after setting")
|
||||||
|
}
|
||||||
|
|
||||||
|
manager.SetEntityInCombat(entity, false)
|
||||||
|
if manager.IsEntityInCombat(entity) {
|
||||||
|
t.Error("Entity should not be in combat after unsetting")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test casting state
|
||||||
|
if manager.IsEntityCasting(entity) {
|
||||||
|
t.Error("Entity should not be casting initially")
|
||||||
|
}
|
||||||
|
|
||||||
|
manager.SetEntityCasting(entity, true)
|
||||||
|
if !manager.IsEntityCasting(entity) {
|
||||||
|
t.Error("Entity should be casting after setting")
|
||||||
|
}
|
||||||
|
|
||||||
|
manager.SetEntityCasting(entity, false)
|
||||||
|
if manager.IsEntityCasting(entity) {
|
||||||
|
t.Error("Entity should not be casting after unsetting")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test with nil entity
|
||||||
|
if manager.IsEntityInCombat(nil) {
|
||||||
|
t.Error("Nil entity should not be in combat")
|
||||||
|
}
|
||||||
|
if manager.IsEntityCasting(nil) {
|
||||||
|
t.Error("Nil entity should not be casting")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test InfoStruct Management
|
||||||
|
func TestInfoStructManagement(t *testing.T) {
|
||||||
|
manager := createTestManager()
|
||||||
|
entity := createTestEntity()
|
||||||
|
|
||||||
|
// Test getting info struct
|
||||||
|
info := manager.GetEntityInfoStruct(entity)
|
||||||
|
if info == nil {
|
||||||
|
t.Fatal("GetEntityInfoStruct returned nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
if info.GetName() != "Test Entity" {
|
||||||
|
t.Errorf("Expected 'Test Entity', got '%s'", info.GetName())
|
||||||
|
}
|
||||||
|
if info.GetLevel() != 10 {
|
||||||
|
t.Errorf("Expected level 10, got %d", info.GetLevel())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test setting new info struct
|
||||||
|
newInfo := NewInfoStruct()
|
||||||
|
newInfo.SetName("Updated Entity")
|
||||||
|
newInfo.SetLevel(20)
|
||||||
|
|
||||||
|
manager.SetEntityInfoStruct(entity, newInfo)
|
||||||
|
|
||||||
|
updatedInfo := manager.GetEntityInfoStruct(entity)
|
||||||
|
if updatedInfo.GetName() != "Updated Entity" {
|
||||||
|
t.Error("Info struct was not updated")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test with nil entity
|
||||||
|
nilInfo := manager.GetEntityInfoStruct(nil)
|
||||||
|
if nilInfo != nil {
|
||||||
|
t.Error("Expected nil for nil entity info struct")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test Spell Effect Management
|
||||||
|
func TestSpellEffectManagement(t *testing.T) {
|
||||||
|
manager := createTestManager()
|
||||||
|
entity := createTestEntity()
|
||||||
|
|
||||||
|
// Test adding maintained spell
|
||||||
|
success := manager.AddMaintainedSpell(entity, "Test Buff", 1001, 60.0, 1)
|
||||||
|
if !success {
|
||||||
|
t.Error("Failed to add maintained spell")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test getting maintained spell
|
||||||
|
effect := entity.GetMaintainedSpell(1001)
|
||||||
|
if effect == nil {
|
||||||
|
t.Error("Failed to retrieve maintained spell")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test removing maintained spell
|
||||||
|
success = manager.RemoveMaintainedSpell(entity, 1001)
|
||||||
|
if !success {
|
||||||
|
t.Error("Failed to remove maintained spell")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify removal
|
||||||
|
effect = entity.GetMaintainedSpell(1001)
|
||||||
|
if effect != nil {
|
||||||
|
t.Error("Maintained spell was not removed")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test adding temporary spell effect
|
||||||
|
success = manager.AddSpellEffect(entity, 2001, 12345, 30.0)
|
||||||
|
if !success {
|
||||||
|
t.Error("Failed to add spell effect")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test removing spell effect
|
||||||
|
success = manager.RemoveSpellEffect(entity, 2001)
|
||||||
|
if !success {
|
||||||
|
t.Error("Failed to remove spell effect")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test detrimental spell
|
||||||
|
manager.AddDetrimentalSpell(entity, 3001, 12345, 45.0, 1)
|
||||||
|
|
||||||
|
// Test removing detrimental spell
|
||||||
|
success = manager.RemoveDetrimentalSpell(entity, 3001, 12345)
|
||||||
|
if !success {
|
||||||
|
t.Error("Failed to remove detrimental spell")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test with nil entity
|
||||||
|
if manager.AddMaintainedSpell(nil, "Test", 1, 1.0, 1) {
|
||||||
|
t.Error("Should not be able to add spell to nil entity")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test Pet Management
|
||||||
|
func TestPetManagement(t *testing.T) {
|
||||||
|
manager := createTestManager()
|
||||||
|
entity := createTestEntity()
|
||||||
|
pet := createTestEntity()
|
||||||
|
pet.GetInfoStruct().SetName("Test Pet")
|
||||||
|
|
||||||
|
// Test setting summon pet
|
||||||
|
manager.SetEntityPet(entity, pet)
|
||||||
|
retrievedPet := manager.GetEntityPet(entity)
|
||||||
|
if retrievedPet != pet {
|
||||||
|
t.Error("Failed to set/get summon pet")
|
||||||
|
}
|
||||||
|
|
||||||
|
if pet.GetOwner() != entity.GetID() {
|
||||||
|
t.Error("Pet owner was not set correctly")
|
||||||
|
}
|
||||||
|
if pet.GetPetType() != PetTypeSummon {
|
||||||
|
t.Error("Pet type was not set correctly")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test charmed pet
|
||||||
|
charmedPet := createTestEntity()
|
||||||
|
charmedPet.GetInfoStruct().SetName("Charmed Pet")
|
||||||
|
|
||||||
|
manager.SetEntityCharmedPet(entity, charmedPet)
|
||||||
|
retrievedCharmed := manager.GetEntityCharmedPet(entity)
|
||||||
|
if retrievedCharmed != charmedPet {
|
||||||
|
t.Error("Failed to set/get charmed pet")
|
||||||
|
}
|
||||||
|
|
||||||
|
if charmedPet.GetPetType() != PetTypeCharm {
|
||||||
|
t.Error("Charmed pet type was not set correctly")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test dismissing pet
|
||||||
|
manager.DismissPet(pet)
|
||||||
|
if !pet.IsPetDismissing() {
|
||||||
|
t.Error("Pet was not marked for dismissal")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test with nil entities
|
||||||
|
if manager.GetEntityPet(nil) != nil {
|
||||||
|
t.Error("Expected nil for nil entity pet")
|
||||||
|
}
|
||||||
|
if manager.GetEntityCharmedPet(nil) != nil {
|
||||||
|
t.Error("Expected nil for nil entity charmed pet")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test Movement and Position
|
||||||
|
func TestMovementAndPosition(t *testing.T) {
|
||||||
|
manager := createTestManager()
|
||||||
|
entity := createTestEntity()
|
||||||
|
|
||||||
|
// Set initial position
|
||||||
|
entity.SetX(100.0)
|
||||||
|
entity.SetY(200.0, false)
|
||||||
|
entity.SetZ(300.0)
|
||||||
|
entity.SetHeading(150, 150) // Heading is int16, not float
|
||||||
|
|
||||||
|
// Update position tracking
|
||||||
|
manager.UpdateEntityPosition(entity)
|
||||||
|
|
||||||
|
// Check if position was recorded
|
||||||
|
lastX, lastY, lastZ, lastHeading := entity.GetLastPosition()
|
||||||
|
if lastX == -1 || lastY == -1 || lastZ == -1 || lastHeading == -1 {
|
||||||
|
t.Error("Position was not updated correctly")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test movement detection (should not have moved yet)
|
||||||
|
if manager.HasEntityMoved(entity) {
|
||||||
|
t.Error("Entity should not have moved yet")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move entity
|
||||||
|
entity.SetX(101.0)
|
||||||
|
|
||||||
|
// Now should detect movement
|
||||||
|
if !manager.HasEntityMoved(entity) {
|
||||||
|
t.Error("Entity movement was not detected")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test speed calculation
|
||||||
|
entity.SetMaxSpeed(10.0)
|
||||||
|
entity.SetBaseSpeed(8.0)
|
||||||
|
entity.SetSpeedMultiplier(1.5)
|
||||||
|
|
||||||
|
speed := manager.GetEntityEffectiveSpeed(entity)
|
||||||
|
expected := float32(8.0 * 1.5) // base * multiplier
|
||||||
|
if speed != expected {
|
||||||
|
t.Errorf("Expected speed %.2f, got %.2f", expected, speed)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test with nil entity
|
||||||
|
if manager.HasEntityMoved(nil) {
|
||||||
|
t.Error("Nil entity should not have moved")
|
||||||
|
}
|
||||||
|
if manager.GetEntityEffectiveSpeed(nil) != 0.0 {
|
||||||
|
t.Error("Nil entity speed should be 0.0")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test Stat Calculations
|
||||||
|
func TestStatCalculations(t *testing.T) {
|
||||||
|
manager := createTestManager()
|
||||||
|
entity := createTestEntity()
|
||||||
|
|
||||||
|
// Test primary stat calculation
|
||||||
|
primaryStat := manager.GetEntityPrimaryStat(entity)
|
||||||
|
if primaryStat != 50 { // STR is highest at 50.0
|
||||||
|
t.Errorf("Expected primary stat 50, got %d", primaryStat)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test resistance calculations
|
||||||
|
info := entity.GetInfoStruct()
|
||||||
|
info.SetResistance("heat", 25)
|
||||||
|
info.SetResistance("cold", 30)
|
||||||
|
|
||||||
|
heatResist := manager.GetEntityResistance(entity, "heat")
|
||||||
|
if heatResist != 25 {
|
||||||
|
t.Errorf("Expected heat resistance 25, got %d", heatResist)
|
||||||
|
}
|
||||||
|
|
||||||
|
coldResist := manager.GetEntityResistance(entity, "cold")
|
||||||
|
if coldResist != 30 {
|
||||||
|
t.Errorf("Expected cold resistance 30, got %d", coldResist)
|
||||||
|
}
|
||||||
|
|
||||||
|
unknownResist := manager.GetEntityResistance(entity, "unknown")
|
||||||
|
if unknownResist != 0 {
|
||||||
|
t.Errorf("Expected unknown resistance 0, got %d", unknownResist)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test with nil entity
|
||||||
|
if manager.GetEntityPrimaryStat(nil) != 0 {
|
||||||
|
t.Error("Nil entity primary stat should be 0")
|
||||||
|
}
|
||||||
|
if manager.GetEntityResistance(nil, "heat") != 0 {
|
||||||
|
t.Error("Nil entity resistance should be 0")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test Bonus Calculations
|
||||||
|
func TestBonusCalculations(t *testing.T) {
|
||||||
|
manager := createTestManager()
|
||||||
|
entity := createTestEntity()
|
||||||
|
|
||||||
|
// Calculate bonuses (this should recalculate all stats)
|
||||||
|
manager.CalculateEntityBonuses(entity)
|
||||||
|
|
||||||
|
// Stats should be recalculated (may be different due to effects)
|
||||||
|
newStr := entity.GetStr()
|
||||||
|
|
||||||
|
// Since we don't have spell effects, stats should remain the same or reset to base
|
||||||
|
if newStr < 0 {
|
||||||
|
t.Error("Strength should not be negative after bonus calculation")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test Packet Building (these would normally require packet definitions)
|
||||||
|
func TestPacketBuilding(t *testing.T) {
|
||||||
|
manager := createTestManager()
|
||||||
|
|
||||||
|
entityID := int32(12345)
|
||||||
|
clientVersion := uint32(57048)
|
||||||
|
|
||||||
|
// Test entity verbs request - should fail without packet definitions
|
||||||
|
_, err := manager.SendEntityVerbsRequest(entityID, clientVersion)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("Expected error for missing packet definition, but got none")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test entity verbs response - should fail without packet definitions
|
||||||
|
verbs := []map[string]interface{}{
|
||||||
|
{"verb_id": 1, "verb_name": "examine"},
|
||||||
|
{"verb_id": 2, "verb_name": "attack"},
|
||||||
|
}
|
||||||
|
_, err = manager.SendEntityVerbsResponse(entityID, clientVersion, verbs)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("Expected error for missing packet definition, but got none")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test entity verb action - should fail without packet definitions
|
||||||
|
_, err = manager.SendEntityVerbsVerb(entityID, 67890, clientVersion, 1, "examine")
|
||||||
|
if err == nil {
|
||||||
|
t.Error("Expected error for missing packet definition, but got none")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test Effect Processing
|
||||||
|
func TestEffectProcessing(t *testing.T) {
|
||||||
|
manager := createTestManager()
|
||||||
|
entity := createTestEntity()
|
||||||
|
|
||||||
|
// Add a spell effect
|
||||||
|
manager.AddSpellEffect(entity, 1001, 12345, 30.0)
|
||||||
|
|
||||||
|
// Process effects (should handle cleanup, etc.)
|
||||||
|
manager.ProcessEntityEffects(entity)
|
||||||
|
|
||||||
|
// Cleanup effects
|
||||||
|
manager.CleanupEntityEffects(entity)
|
||||||
|
|
||||||
|
// Test with nil entity (should not crash)
|
||||||
|
manager.ProcessEntityEffects(nil)
|
||||||
|
manager.CleanupEntityEffects(nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test Statistics and Monitoring
|
||||||
|
func TestStatisticsAndMonitoring(t *testing.T) {
|
||||||
|
manager := createTestManager()
|
||||||
|
entity := createTestEntity()
|
||||||
|
|
||||||
|
// Perform some operations to generate statistics
|
||||||
|
manager.CreateEntity()
|
||||||
|
manager.SetEntityInCombat(entity, true)
|
||||||
|
manager.AddSpellEffect(entity, 1001, 12345, 30.0)
|
||||||
|
manager.RemoveSpellEffect(entity, 1001)
|
||||||
|
manager.CalculateEntityBonuses(entity)
|
||||||
|
|
||||||
|
stats := manager.GetStatistics()
|
||||||
|
if stats == nil {
|
||||||
|
t.Fatal("Statistics returned nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check some expected statistics exist
|
||||||
|
if _, exists := stats["entities_created"]; !exists {
|
||||||
|
t.Error("Expected entities_created statistic")
|
||||||
|
}
|
||||||
|
if _, exists := stats["spell_effects_added"]; !exists {
|
||||||
|
t.Error("Expected spell_effects_added statistic")
|
||||||
|
}
|
||||||
|
if _, exists := stats["spell_effects_removed"]; !exists {
|
||||||
|
t.Error("Expected spell_effects_removed statistic")
|
||||||
|
}
|
||||||
|
if _, exists := stats["bonus_calculations"]; !exists {
|
||||||
|
t.Error("Expected bonus_calculations statistic")
|
||||||
|
}
|
||||||
|
if _, exists := stats["combat_actions"]; !exists {
|
||||||
|
t.Error("Expected combat_actions statistic")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that some statistics have non-zero values
|
||||||
|
if stats["entities_created"].(int64) < 1 {
|
||||||
|
t.Error("Expected at least 1 entity created")
|
||||||
|
}
|
||||||
|
if stats["spell_effects_added"].(int64) < 1 {
|
||||||
|
t.Error("Expected at least 1 spell effect added")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test reset statistics
|
||||||
|
manager.ResetStatistics()
|
||||||
|
newStats := manager.GetStatistics()
|
||||||
|
if newStats["entities_created"].(int64) != 0 {
|
||||||
|
t.Error("Expected entities created to be reset to 0")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test Configuration Management
|
||||||
|
func TestConfigurationManagement(t *testing.T) {
|
||||||
|
manager := createTestManager()
|
||||||
|
|
||||||
|
// Test default configuration
|
||||||
|
config := manager.GetConfiguration()
|
||||||
|
if !config["enable_statistics"].(bool) {
|
||||||
|
t.Error("Expected statistics to be enabled by default")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test configuration update
|
||||||
|
newConfig := map[string]interface{}{
|
||||||
|
"enable_statistics": false,
|
||||||
|
"enable_combat_logging": true,
|
||||||
|
"max_entities_per_zone": 5000,
|
||||||
|
}
|
||||||
|
manager.SetConfiguration(newConfig)
|
||||||
|
|
||||||
|
updatedConfig := manager.GetConfiguration()
|
||||||
|
if updatedConfig["enable_statistics"].(bool) {
|
||||||
|
t.Error("Expected statistics to be disabled after update")
|
||||||
|
}
|
||||||
|
if !updatedConfig["enable_combat_logging"].(bool) {
|
||||||
|
t.Error("Expected combat logging to be enabled after update")
|
||||||
|
}
|
||||||
|
if updatedConfig["max_entities_per_zone"].(int) != 5000 {
|
||||||
|
t.Error("Expected max_entities_per_zone to be updated to 5000")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test Validation and Health Checks
|
||||||
|
func TestValidationAndHealthChecks(t *testing.T) {
|
||||||
|
manager := createTestManager()
|
||||||
|
entity := createTestEntity()
|
||||||
|
|
||||||
|
// Test health check
|
||||||
|
if !manager.IsHealthy() {
|
||||||
|
t.Error("Manager should be healthy")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test entity validation
|
||||||
|
issues := manager.ValidateEntity(entity)
|
||||||
|
if len(issues) > 0 {
|
||||||
|
t.Errorf("Expected no validation issues, got %d: %v", len(issues), issues)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test validation with invalid entity
|
||||||
|
invalidEntity := createTestEntity()
|
||||||
|
invalidEntity.GetInfoStruct().SetLevel(-1)
|
||||||
|
invalidEntity.GetInfoStruct().SetName("")
|
||||||
|
invalidEntity.SetTotalHP(-100)
|
||||||
|
|
||||||
|
validationIssues := manager.ValidateEntity(invalidEntity)
|
||||||
|
if len(validationIssues) == 0 {
|
||||||
|
t.Error("Expected validation issues for invalid entity")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check specific issues (should have at least 3 issues)
|
||||||
|
if len(validationIssues) < 3 {
|
||||||
|
t.Errorf("Expected at least 3 validation issues, got %d: %v", len(validationIssues), validationIssues)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test validation with nil entity
|
||||||
|
nilIssues := manager.ValidateEntity(nil)
|
||||||
|
if len(nilIssues) != 1 || nilIssues[0] != "entity is nil" {
|
||||||
|
t.Error("Expected 'entity is nil' issue for nil entity")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test Thread Safety (basic test)
|
||||||
|
func TestThreadSafety(t *testing.T) {
|
||||||
|
manager := createTestManager()
|
||||||
|
entity := createTestEntity()
|
||||||
|
|
||||||
|
// Perform concurrent operations
|
||||||
|
done := make(chan bool, 10)
|
||||||
|
|
||||||
|
// Start multiple goroutines performing different operations
|
||||||
|
for i := 0; i < 10; i++ {
|
||||||
|
go func(id int) {
|
||||||
|
defer func() { done <- true }()
|
||||||
|
|
||||||
|
// Mix of read and write operations
|
||||||
|
manager.GetEntityInfoStruct(entity)
|
||||||
|
manager.SetEntityInCombat(entity, true)
|
||||||
|
manager.SetEntityInCombat(entity, false)
|
||||||
|
manager.GetStatistics()
|
||||||
|
manager.CalculateEntityBonuses(entity)
|
||||||
|
manager.ProcessEntityEffects(entity)
|
||||||
|
}(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for all goroutines to complete
|
||||||
|
for i := 0; i < 10; i++ {
|
||||||
|
<-done
|
||||||
|
}
|
||||||
|
|
||||||
|
// Manager should still be functional
|
||||||
|
if !manager.IsHealthy() {
|
||||||
|
t.Error("Manager appears corrupted after concurrent access")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test Entity Stat Methods
|
||||||
|
func TestEntityStatMethods(t *testing.T) {
|
||||||
|
entity := createTestEntity()
|
||||||
|
|
||||||
|
// Test individual stat getters
|
||||||
|
if entity.GetStr() != 50 {
|
||||||
|
t.Errorf("Expected STR 50, got %d", entity.GetStr())
|
||||||
|
}
|
||||||
|
if entity.GetSta() != 45 {
|
||||||
|
t.Errorf("Expected STA 45, got %d", entity.GetSta())
|
||||||
|
}
|
||||||
|
if entity.GetAgi() != 40 {
|
||||||
|
t.Errorf("Expected AGI 40, got %d", entity.GetAgi())
|
||||||
|
}
|
||||||
|
if entity.GetWis() != 35 {
|
||||||
|
t.Errorf("Expected WIS 35, got %d", entity.GetWis())
|
||||||
|
}
|
||||||
|
if entity.GetIntel() != 30 {
|
||||||
|
t.Errorf("Expected INT 30, got %d", entity.GetIntel())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test primary stat calculation
|
||||||
|
primary := entity.GetPrimaryStat()
|
||||||
|
if primary != 50 { // STR is highest
|
||||||
|
t.Errorf("Expected primary stat 50, got %d", primary)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test Entity Health and Power
|
||||||
|
func TestEntityHealthAndPower(t *testing.T) {
|
||||||
|
entity := createTestEntity()
|
||||||
|
|
||||||
|
// Test initial values
|
||||||
|
if entity.GetTotalHP() != 1000 {
|
||||||
|
t.Errorf("Expected total HP 1000, got %d", entity.GetTotalHP())
|
||||||
|
}
|
||||||
|
if entity.GetHP() != 1000 {
|
||||||
|
t.Errorf("Expected current HP 1000, got %d", entity.GetHP())
|
||||||
|
}
|
||||||
|
if entity.GetTotalPower() != 500 {
|
||||||
|
t.Errorf("Expected total power 500, got %d", entity.GetTotalPower())
|
||||||
|
}
|
||||||
|
if entity.GetPower() != 500 {
|
||||||
|
t.Errorf("Expected current power 500, got %d", entity.GetPower())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test alive state
|
||||||
|
if !entity.IsAlive() {
|
||||||
|
t.Error("Entity should be alive with positive HP")
|
||||||
|
}
|
||||||
|
if entity.IsDead() {
|
||||||
|
t.Error("Entity should not be dead with positive HP")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test setting HP to 0
|
||||||
|
entity.SetHP(0)
|
||||||
|
entity.SetAlive(false) // Need to explicitly set alive state
|
||||||
|
if entity.IsAlive() {
|
||||||
|
t.Error("Entity should not be alive with 0 HP")
|
||||||
|
}
|
||||||
|
if !entity.IsDead() {
|
||||||
|
t.Error("Entity should be dead with 0 HP")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test InfoStruct Methods
|
||||||
|
func TestInfoStructMethods(t *testing.T) {
|
||||||
|
info := NewInfoStruct()
|
||||||
|
|
||||||
|
// Test basic properties
|
||||||
|
info.SetName("Test Character")
|
||||||
|
if info.GetName() != "Test Character" {
|
||||||
|
t.Error("Name was not set correctly")
|
||||||
|
}
|
||||||
|
|
||||||
|
info.SetLevel(25)
|
||||||
|
if info.GetLevel() != 25 {
|
||||||
|
t.Error("Level was not set correctly")
|
||||||
|
}
|
||||||
|
|
||||||
|
info.SetClass1(5)
|
||||||
|
if info.GetClass1() != 5 {
|
||||||
|
t.Error("Class was not set correctly")
|
||||||
|
}
|
||||||
|
|
||||||
|
info.SetRace(2)
|
||||||
|
if info.GetRace() != 2 {
|
||||||
|
t.Error("Race was not set correctly")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test concentration system
|
||||||
|
info.SetMaxConcentration(10)
|
||||||
|
if info.GetMaxConcentration() != 10 {
|
||||||
|
t.Error("Max concentration was not set correctly")
|
||||||
|
}
|
||||||
|
|
||||||
|
success := info.AddConcentration(5)
|
||||||
|
if !success {
|
||||||
|
t.Error("Failed to add concentration")
|
||||||
|
}
|
||||||
|
if info.GetCurConcentration() != 5 {
|
||||||
|
t.Error("Current concentration was not updated correctly")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to add too much concentration
|
||||||
|
success = info.AddConcentration(10)
|
||||||
|
if success {
|
||||||
|
t.Error("Should not be able to add more concentration than available")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove concentration
|
||||||
|
info.RemoveConcentration(3)
|
||||||
|
if info.GetCurConcentration() != 2 {
|
||||||
|
t.Error("Concentration was not removed correctly")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test coin system
|
||||||
|
info.AddCoins(1234567) // 1.23 plat, 45 gold, 67 copper
|
||||||
|
totalCoins := info.GetCoins()
|
||||||
|
if totalCoins != 1234567 {
|
||||||
|
t.Errorf("Expected %d total coins, got %d", 1234567, totalCoins)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test removing coins
|
||||||
|
success = info.RemoveCoins(100000)
|
||||||
|
if !success {
|
||||||
|
t.Error("Failed to remove coins")
|
||||||
|
}
|
||||||
|
|
||||||
|
newTotal := info.GetCoins()
|
||||||
|
if newTotal != 1134567 {
|
||||||
|
t.Errorf("Expected %d coins after removal, got %d", 1134567, newTotal)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to remove more coins than available
|
||||||
|
success = info.RemoveCoins(2000000)
|
||||||
|
if success {
|
||||||
|
t.Error("Should not be able to remove more coins than available")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test InfoStruct Clone
|
||||||
|
func TestInfoStructClone(t *testing.T) {
|
||||||
|
original := NewInfoStruct()
|
||||||
|
original.SetName("Original")
|
||||||
|
original.SetLevel(50)
|
||||||
|
original.SetStr(100.0)
|
||||||
|
original.AddCoins(5000)
|
||||||
|
|
||||||
|
clone := original.Clone()
|
||||||
|
if clone == nil {
|
||||||
|
t.Fatal("Clone returned nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify clone has same data
|
||||||
|
if clone.GetName() != original.GetName() {
|
||||||
|
t.Error("Clone has different name")
|
||||||
|
}
|
||||||
|
if clone.GetLevel() != original.GetLevel() {
|
||||||
|
t.Error("Clone has different level")
|
||||||
|
}
|
||||||
|
if clone.GetStr() != original.GetStr() {
|
||||||
|
t.Error("Clone has different strength")
|
||||||
|
}
|
||||||
|
if clone.GetCoins() != original.GetCoins() {
|
||||||
|
t.Error("Clone has different coins")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify clone is independent
|
||||||
|
clone.SetName("Modified Clone")
|
||||||
|
if original.GetName() == "Modified Clone" {
|
||||||
|
t.Error("Modifying clone affected original")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Benchmark tests
|
||||||
|
func BenchmarkCreateEntity(b *testing.B) {
|
||||||
|
manager := createTestManager()
|
||||||
|
b.ResetTimer()
|
||||||
|
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
manager.CreateEntity()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkGetEntityInfoStruct(b *testing.B) {
|
||||||
|
manager := createTestManager()
|
||||||
|
entity := createTestEntity()
|
||||||
|
b.ResetTimer()
|
||||||
|
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
manager.GetEntityInfoStruct(entity)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkCalculateEntityBonuses(b *testing.B) {
|
||||||
|
manager := createTestManager()
|
||||||
|
entity := createTestEntity()
|
||||||
|
b.ResetTimer()
|
||||||
|
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
manager.CalculateEntityBonuses(entity)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkGetStatistics(b *testing.B) {
|
||||||
|
manager := createTestManager()
|
||||||
|
b.ResetTimer()
|
||||||
|
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
manager.GetStatistics()
|
||||||
|
}
|
||||||
|
}
|
@ -128,6 +128,11 @@ const (
|
|||||||
OP_EqCollectionFilterCmd
|
OP_EqCollectionFilterCmd
|
||||||
OP_EqCollectionItemCmd
|
OP_EqCollectionItemCmd
|
||||||
|
|
||||||
|
// Entity system opcodes (additional ones beyond existing)
|
||||||
|
OP_EntityVerbsRequestMsg
|
||||||
|
OP_EntityVerbsReplyMsg
|
||||||
|
OP_EntityVerbsVerbMsg
|
||||||
|
|
||||||
// Add more opcodes as needed...
|
// Add more opcodes as needed...
|
||||||
_maxInternalOpcode // Sentinel value
|
_maxInternalOpcode // Sentinel value
|
||||||
)
|
)
|
||||||
@ -217,6 +222,11 @@ var OpcodeNames = map[InternalOpcode]string{
|
|||||||
OP_EqCollectionUpdateCmd: "OP_EqCollectionUpdateCmd",
|
OP_EqCollectionUpdateCmd: "OP_EqCollectionUpdateCmd",
|
||||||
OP_EqCollectionFilterCmd: "OP_EqCollectionFilterCmd",
|
OP_EqCollectionFilterCmd: "OP_EqCollectionFilterCmd",
|
||||||
OP_EqCollectionItemCmd: "OP_EqCollectionItemCmd",
|
OP_EqCollectionItemCmd: "OP_EqCollectionItemCmd",
|
||||||
|
|
||||||
|
// Entity system opcodes (additional ones beyond existing)
|
||||||
|
OP_EntityVerbsRequestMsg: "OP_EntityVerbsRequestMsg",
|
||||||
|
OP_EntityVerbsReplyMsg: "OP_EntityVerbsReplyMsg",
|
||||||
|
OP_EntityVerbsVerbMsg: "OP_EntityVerbsVerbMsg",
|
||||||
}
|
}
|
||||||
|
|
||||||
// OpcodeManager handles the mapping between client-specific opcodes and internal opcodes
|
// OpcodeManager handles the mapping between client-specific opcodes and internal opcodes
|
||||||
|
Loading…
x
Reference in New Issue
Block a user