simplify/enhance entity

This commit is contained in:
Sky Johnson 2025-08-23 19:05:01 -05:00
parent 4b32b0e3ee
commit 2b233535aa
4 changed files with 1715 additions and 4 deletions

View File

@ -831,11 +831,259 @@ func (info *InfoStruct) Clone() *InfoStruct {
info.mutex.RLock()
defer info.mutex.RUnlock()
clone := &InfoStruct{}
*clone = *info // Copy all fields
clone := &InfoStruct{
// 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
clone.mutex = sync.RWMutex{}
// Concentration system
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(clone.accountAgeBonus[:], info.accountAgeBonus[:])

651
internal/entity/manager.go Normal file
View 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
}

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

View File

@ -128,6 +128,11 @@ const (
OP_EqCollectionFilterCmd
OP_EqCollectionItemCmd
// Entity system opcodes (additional ones beyond existing)
OP_EntityVerbsRequestMsg
OP_EntityVerbsReplyMsg
OP_EntityVerbsVerbMsg
// Add more opcodes as needed...
_maxInternalOpcode // Sentinel value
)
@ -217,6 +222,11 @@ var OpcodeNames = map[InternalOpcode]string{
OP_EqCollectionUpdateCmd: "OP_EqCollectionUpdateCmd",
OP_EqCollectionFilterCmd: "OP_EqCollectionFilterCmd",
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