modernize, improve entities. First pass

This commit is contained in:
Sky Johnson 2025-08-07 18:06:48 -05:00
parent 6df4b00201
commit 7ce87100e6
8 changed files with 186 additions and 347 deletions

View File

@ -1,167 +0,0 @@
# Entity Package
The Entity package provides the core combat and magic systems for EverQuest II server emulation. It extends the base Spawn system with combat capabilities, spell effects, and character statistics management.
## Overview
The Entity system is built on three main components:
1. **InfoStruct** - Comprehensive character statistics and information
2. **SpellEffectManager** - Manages all spell effects, buffs, debuffs, and bonuses
3. **Entity** - Combat-capable spawn with spell casting and pet management
## Architecture
```
Spawn (base class)
└── Entity (combat-capable)
├── Player (player characters)
└── NPC (non-player characters)
```
## Core Components
### InfoStruct
Contains all character statistics including:
- Primary attributes (STR, STA, AGI, WIS, INT)
- Combat stats (attack, mitigation, avoidance)
- Resistances (heat, cold, magic, mental, divine, disease, poison)
- Experience points and currency
- Equipment and weapon information
- Group and encounter settings
**Thread Safety**: All access methods use RWMutex for safe concurrent access.
### SpellEffectManager
Manages four types of spell effects:
1. **Maintained Effects** - Buffs that consume concentration
2. **Spell Effects** - Temporary buffs/debuffs with durations
3. **Detrimental Effects** - Debuffs and harmful effects
4. **Bonus Values** - Stat modifications from various sources
**Key Features**:
- Automatic expiration handling
- Control effect tracking (stun, root, mez, etc.)
- Bonus calculations with class/race/faction requirements
- Thread-safe effect management
### Entity
Combat-capable spawn that extends base Spawn functionality:
**Combat Systems**:
- Health/Power/Savagery management
- Combat state tracking (in combat, casting)
- Damage resistance calculations
- Speed and movement modifiers
**Magic Systems**:
- Spell effect application and removal
- Concentration-based maintained spells
- Bonus stat calculations
- Control effect immunity
**Pet Systems**:
- Multiple pet types (summon, charm, deity, cosmetic)
- Pet ownership and dismissal
- Pet spell tracking
## Usage Examples
### Creating an Entity
```go
entity := NewEntity()
entity.GetInfoStruct().SetName("TestEntity")
entity.GetInfoStruct().SetLevel(50)
entity.GetInfoStruct().SetStr(100.0)
```
### Managing Spell Effects
```go
// Add a maintained spell (buff)
success := entity.AddMaintainedSpell("Heroic Strength", 12345, 300.0, 2)
// Add a temporary effect
entity.AddSpellEffect(54321, casterID, 60.0)
// Add a detrimental effect
entity.AddDetrimentalSpell(99999, attackerID, 30.0, 1)
```
### Stat Calculations
```go
// Get effective stats (base + bonuses)
str := entity.GetStr()
sta := entity.GetSta()
primary := entity.GetPrimaryStat()
// Recalculate all bonuses
entity.CalculateBonuses()
```
### Pet Management
```go
// Set a summon pet
entity.SetPet(petEntity)
// Check pet status
if entity.GetPet() != nil && !entity.IsPetDismissing() {
// Pet is active
}
```
## Constants and Enums
### Pet Types
- `PetTypeSummon` - Summoned pets
- `PetTypeCharm` - Charmed creatures
- `PetTypeDeity` - Deity pets
- `PetTypeCosmetic` - Cosmetic pets
### Control Effects
- `ControlEffectStun` - Cannot move or act
- `ControlEffectRoot` - Cannot move
- `ControlEffectMez` - Mesmerized
- `ControlEffectDaze` - Dazed
- `ControlEffectFear` - Feared
- `ControlEffectSlow` - Movement slowed
- `ControlEffectSnare` - Movement impaired
- `ControlEffectCharm` - Mind controlled
## Thread Safety
All Entity operations are thread-safe using:
- `sync.RWMutex` for read/write operations
- `sync.atomic` for simple state flags
- Separate mutexes for different subsystems to minimize lock contention
## Integration with Spawn System
The Entity extends the base Spawn class and requires:
- `spawn.NewSpawn()` for initialization
- Access to Spawn position and basic methods
- Integration with zone update systems
## Future Extensions
Areas marked with TODO comments for future implementation:
- Complete item and equipment systems
- Combat calculation methods
- Threat and hate management
- Group combat mechanics
- Spell casting systems
- LUA script integration
## Files
- `entity.go` - Main Entity class implementation
- `info_struct.go` - Character statistics and information
- `spell_effects.go` - Spell effect management system
- `README.md` - This documentation file

View File

@ -20,7 +20,7 @@ func BenchmarkEntityCreation(b *testing.B) {
// BenchmarkEntityCombatState measures combat state operations
func BenchmarkEntityCombatState(b *testing.B) {
entity := NewEntity()
b.Run("Sequential", func(b *testing.B) {
for i := 0; i < b.N; i++ {
entity.SetInCombat(true)
@ -28,7 +28,7 @@ func BenchmarkEntityCombatState(b *testing.B) {
entity.SetInCombat(false)
}
})
b.Run("Parallel", func(b *testing.B) {
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
@ -43,7 +43,7 @@ func BenchmarkEntityCombatState(b *testing.B) {
// BenchmarkEntityCastingState measures casting state operations
func BenchmarkEntityCastingState(b *testing.B) {
entity := NewEntity()
b.Run("Sequential", func(b *testing.B) {
for i := 0; i < b.N; i++ {
entity.SetCasting(true)
@ -51,7 +51,7 @@ func BenchmarkEntityCastingState(b *testing.B) {
entity.SetCasting(false)
}
})
b.Run("Parallel", func(b *testing.B) {
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
@ -67,14 +67,14 @@ func BenchmarkEntityCastingState(b *testing.B) {
func BenchmarkEntityStatCalculations(b *testing.B) {
entity := NewEntity()
info := entity.GetInfoStruct()
// Set up some base stats
info.SetStr(100.0)
info.SetSta(100.0)
info.SetAgi(100.0)
info.SetWis(100.0)
info.SetIntel(100.0)
b.Run("GetStats", func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = entity.GetStr()
@ -84,7 +84,7 @@ func BenchmarkEntityStatCalculations(b *testing.B) {
_ = entity.GetIntel()
}
})
b.Run("GetStatsParallel", func(b *testing.B) {
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
@ -96,13 +96,13 @@ func BenchmarkEntityStatCalculations(b *testing.B) {
}
})
})
b.Run("GetPrimaryStat", func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = entity.GetPrimaryStat()
}
})
b.Run("CalculateBonuses", func(b *testing.B) {
for i := 0; i < b.N; i++ {
entity.CalculateBonuses()
@ -113,7 +113,7 @@ func BenchmarkEntityStatCalculations(b *testing.B) {
// BenchmarkEntitySpellEffects measures spell effect operations
func BenchmarkEntitySpellEffects(b *testing.B) {
entity := NewEntity()
b.Run("AddRemoveSpellEffect", func(b *testing.B) {
for i := 0; i < b.N; i++ {
spellID := int32(i + 1000)
@ -121,7 +121,7 @@ func BenchmarkEntitySpellEffects(b *testing.B) {
entity.RemoveSpellEffect(spellID)
}
})
b.Run("AddRemoveSpellEffectParallel", func(b *testing.B) {
var counter int64
b.RunParallel(func(pb *testing.PB) {
@ -140,7 +140,7 @@ func BenchmarkEntityMaintainedSpells(b *testing.B) {
entity := NewEntity()
info := entity.GetInfoStruct()
info.SetMaxConcentration(1000) // Large pool for benchmarking
b.Run("AddRemoveMaintainedSpell", func(b *testing.B) {
for i := 0; i < b.N; i++ {
spellID := int32(i + 2000)
@ -149,7 +149,7 @@ func BenchmarkEntityMaintainedSpells(b *testing.B) {
}
}
})
b.Run("AddRemoveMaintainedSpellParallel", func(b *testing.B) {
var counter int64
b.RunParallel(func(pb *testing.PB) {
@ -167,14 +167,14 @@ func BenchmarkEntityMaintainedSpells(b *testing.B) {
// BenchmarkInfoStructBasicOps measures basic InfoStruct operations
func BenchmarkInfoStructBasicOps(b *testing.B) {
info := NewInfoStruct()
b.Run("SetGetName", func(b *testing.B) {
for i := 0; i < b.N; i++ {
info.SetName("BenchmarkCharacter")
_ = info.GetName()
}
})
b.Run("SetGetNameParallel", func(b *testing.B) {
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
@ -183,14 +183,14 @@ func BenchmarkInfoStructBasicOps(b *testing.B) {
}
})
})
b.Run("SetGetLevel", func(b *testing.B) {
for i := 0; i < b.N; i++ {
info.SetLevel(int16(i % 100))
_ = info.GetLevel()
}
})
b.Run("SetGetStats", func(b *testing.B) {
for i := 0; i < b.N; i++ {
val := float32(i % 1000)
@ -199,7 +199,7 @@ func BenchmarkInfoStructBasicOps(b *testing.B) {
info.SetAgi(val)
info.SetWis(val)
info.SetIntel(val)
_ = info.GetStr()
_ = info.GetSta()
_ = info.GetAgi()
@ -213,7 +213,7 @@ func BenchmarkInfoStructBasicOps(b *testing.B) {
func BenchmarkInfoStructConcentration(b *testing.B) {
info := NewInfoStruct()
info.SetMaxConcentration(1000)
b.Run("AddRemoveConcentration", func(b *testing.B) {
for i := 0; i < b.N; i++ {
amount := int16(i%10 + 1)
@ -222,7 +222,7 @@ func BenchmarkInfoStructConcentration(b *testing.B) {
}
}
})
b.Run("AddRemoveConcentrationParallel", func(b *testing.B) {
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
@ -238,7 +238,7 @@ func BenchmarkInfoStructConcentration(b *testing.B) {
// BenchmarkInfoStructCoins measures coin operations
func BenchmarkInfoStructCoins(b *testing.B) {
info := NewInfoStruct()
b.Run("AddRemoveCoins", func(b *testing.B) {
for i := 0; i < b.N; i++ {
amount := int32(i%10000 + 1)
@ -246,7 +246,7 @@ func BenchmarkInfoStructCoins(b *testing.B) {
info.RemoveCoins(amount / 2)
}
})
b.Run("AddRemoveCoinsParallel", func(b *testing.B) {
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
@ -256,7 +256,7 @@ func BenchmarkInfoStructCoins(b *testing.B) {
}
})
})
b.Run("GetCoins", func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = info.GetCoins()
@ -268,7 +268,7 @@ func BenchmarkInfoStructCoins(b *testing.B) {
func BenchmarkInfoStructResistances(b *testing.B) {
info := NewInfoStruct()
resistTypes := []string{"heat", "cold", "magic", "mental", "divine", "disease", "poison"}
b.Run("SetGetResistances", func(b *testing.B) {
for i := 0; i < b.N; i++ {
resistType := resistTypes[i%len(resistTypes)]
@ -277,7 +277,7 @@ func BenchmarkInfoStructResistances(b *testing.B) {
_ = info.GetResistance(resistType)
}
})
b.Run("SetGetResistancesParallel", func(b *testing.B) {
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
@ -293,7 +293,7 @@ func BenchmarkInfoStructResistances(b *testing.B) {
// BenchmarkInfoStructClone measures clone operations
func BenchmarkInfoStructClone(b *testing.B) {
info := NewInfoStruct()
// Set up some state to clone
info.SetName("Original Character")
info.SetLevel(50)
@ -304,9 +304,9 @@ func BenchmarkInfoStructClone(b *testing.B) {
info.SetIntel(105.0)
info.AddConcentration(5)
info.AddCoins(50000)
b.ResetTimer()
for i := 0; i < b.N; i++ {
clone := info.Clone()
_ = clone
@ -316,7 +316,7 @@ func BenchmarkInfoStructClone(b *testing.B) {
// BenchmarkEntityPetManagement measures pet management operations
func BenchmarkEntityPetManagement(b *testing.B) {
entity := NewEntity()
b.Run("SetGetPet", func(b *testing.B) {
for i := 0; i < b.N; i++ {
pet := NewEntity()
@ -325,7 +325,7 @@ func BenchmarkEntityPetManagement(b *testing.B) {
entity.SetPet(nil)
}
})
b.Run("SetGetPetParallel", func(b *testing.B) {
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
@ -336,23 +336,23 @@ func BenchmarkEntityPetManagement(b *testing.B) {
}
})
})
b.Run("AllPetTypes", func(b *testing.B) {
for i := 0; i < b.N; i++ {
pet := NewEntity()
entity.SetPet(pet)
_ = entity.GetPet()
entity.SetCharmedPet(pet)
_ = entity.GetCharmedPet()
entity.SetDeityPet(pet)
_ = entity.GetDeityPet()
entity.SetCosmeticPet(pet)
_ = entity.GetCosmeticPet()
// Clear all pets
entity.SetPet(nil)
entity.SetCharmedPet(nil)
@ -366,7 +366,7 @@ func BenchmarkEntityPetManagement(b *testing.B) {
func BenchmarkConcurrentWorkload(b *testing.B) {
numEntities := 100
entities := make([]*Entity, numEntities)
// Create entities
for i := 0; i < numEntities; i++ {
entities[i] = NewEntity()
@ -375,15 +375,15 @@ func BenchmarkConcurrentWorkload(b *testing.B) {
info.SetName("Entity" + string(rune('A'+i%26)))
info.SetLevel(int16(i%100 + 1))
}
b.ResetTimer()
b.Run("MixedOperations", func(b *testing.B) {
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
entityIdx := rand.Intn(numEntities)
entity := entities[entityIdx]
switch rand.Intn(10) {
case 0, 1: // Combat state changes (20%)
entity.SetInCombat(rand.Intn(2) == 1)
@ -430,7 +430,7 @@ func BenchmarkMemoryAllocation(b *testing.B) {
_ = entity
}
})
b.Run("InfoStructAllocation", func(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
@ -438,15 +438,15 @@ func BenchmarkMemoryAllocation(b *testing.B) {
_ = info
}
})
b.Run("CloneAllocation", func(b *testing.B) {
info := NewInfoStruct()
info.SetName("Test")
info.SetLevel(50)
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
clone := info.Clone()
_ = clone
@ -459,7 +459,7 @@ func BenchmarkContention(b *testing.B) {
entity := NewEntity()
info := entity.GetInfoStruct()
info.SetMaxConcentration(10) // Low limit to create contention
b.Run("HighContentionConcentration", func(b *testing.B) {
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
@ -471,7 +471,7 @@ func BenchmarkContention(b *testing.B) {
}
})
})
b.Run("HighContentionSpellEffects", func(b *testing.B) {
var spellCounter int64
b.RunParallel(func(pb *testing.PB) {
@ -488,11 +488,11 @@ func BenchmarkContention(b *testing.B) {
// BenchmarkScalability tests performance as load increases
func BenchmarkScalability(b *testing.B) {
goroutineCounts := []int{1, 2, 4, 8, 16, 32, 64}
for _, numGoroutines := range goroutineCounts {
b.Run(fmt.Sprintf("Goroutines_%d", numGoroutines), func(b *testing.B) {
entity := NewEntity()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
entity.SetInCombat(true)
@ -503,4 +503,3 @@ func BenchmarkScalability(b *testing.B) {
})
}
}

View File

@ -20,12 +20,12 @@ func TestEntityConcurrencyStress(t *testing.T) {
// Test 1: Concurrent combat and casting state changes
t.Run("CombatCastingStates", func(t *testing.T) {
var combatOps, castingOps int64
wg.Add(numGoroutines)
for i := 0; i < numGoroutines; i++ {
go func() {
defer wg.Done()
for j := 0; j < operationsPerGoroutine; j++ {
// Combat state operations
entity.SetInCombat(true)
@ -33,7 +33,7 @@ func TestEntityConcurrencyStress(t *testing.T) {
atomic.AddInt64(&combatOps, 1)
}
entity.SetInCombat(false)
// Casting state operations
entity.SetCasting(true)
if entity.IsCasting() {
@ -44,26 +44,26 @@ func TestEntityConcurrencyStress(t *testing.T) {
}()
}
wg.Wait()
t.Logf("Combat operations: %d, Casting operations: %d", combatOps, castingOps)
})
// Test 2: Concurrent spell effect operations
t.Run("SpellEffects", func(t *testing.T) {
var addOps, removeOps int64
wg.Add(numGoroutines)
for i := 0; i < numGoroutines; i++ {
go func(goroutineID int) {
defer wg.Done()
for j := 0; j < operationsPerGoroutine; j++ {
spellID := int32(goroutineID*1000 + j)
if entity.AddSpellEffect(spellID, int32(goroutineID), 30.0) {
atomic.AddInt64(&addOps, 1)
}
if entity.RemoveSpellEffect(spellID) {
atomic.AddInt64(&removeOps, 1)
}
@ -71,28 +71,28 @@ func TestEntityConcurrencyStress(t *testing.T) {
}(i)
}
wg.Wait()
t.Logf("Spell effect adds: %d, removes: %d", addOps, removeOps)
})
// Test 3: Concurrent maintained spell operations with concentration management
t.Run("MaintainedSpells", func(t *testing.T) {
var addOps, removeOps, concentrationFailures int64
wg.Add(numGoroutines)
for i := 0; i < numGoroutines; i++ {
go func(goroutineID int) {
defer wg.Done()
for j := 0; j < operationsPerGoroutine/10; j++ { // Fewer ops due to concentration limits
spellID := int32(goroutineID*100 + j + 10000)
if entity.AddMaintainedSpell("Stress Test Spell", spellID, 60.0, 1) {
atomic.AddInt64(&addOps, 1)
// Small delay to increase contention
time.Sleep(time.Microsecond)
if entity.RemoveMaintainedSpell(spellID) {
atomic.AddInt64(&removeOps, 1)
}
@ -103,10 +103,10 @@ func TestEntityConcurrencyStress(t *testing.T) {
}(i)
}
wg.Wait()
t.Logf("Maintained spell adds: %d, removes: %d, concentration failures: %d",
t.Logf("Maintained spell adds: %d, removes: %d, concentration failures: %d",
addOps, removeOps, concentrationFailures)
// Verify concentration was properly managed
currentConc := info.GetCurConcentration()
if currentConc != 0 {
@ -117,23 +117,23 @@ func TestEntityConcurrencyStress(t *testing.T) {
// Test 4: Concurrent stat calculations with bonuses
t.Run("StatCalculations", func(t *testing.T) {
var statReads int64
// Set some base stats
info.SetStr(100.0)
info.SetSta(100.0)
info.SetAgi(100.0)
info.SetWis(100.0)
info.SetIntel(100.0)
wg.Add(numGoroutines)
for i := 0; i < numGoroutines; i++ {
go func(goroutineID int) {
defer wg.Done()
for j := 0; j < operationsPerGoroutine; j++ {
// Add some stat bonuses
entity.AddStatBonus(int32(goroutineID*1000+j), 1, float32(j%10))
// Read stats (these involve bonus calculations)
_ = entity.GetStr()
_ = entity.GetSta()
@ -141,31 +141,31 @@ func TestEntityConcurrencyStress(t *testing.T) {
_ = entity.GetWis()
_ = entity.GetIntel()
_ = entity.GetPrimaryStat()
atomic.AddInt64(&statReads, 6)
// Trigger bonus recalculation
entity.CalculateBonuses()
}
}(i)
}
wg.Wait()
t.Logf("Stat reads: %d", statReads)
})
// Test 5: Concurrent pet management
t.Run("PetManagement", func(t *testing.T) {
var petOps int64
wg.Add(numGoroutines)
for i := 0; i < numGoroutines; i++ {
go func() {
defer wg.Done()
for j := 0; j < operationsPerGoroutine/10; j++ {
pet := NewEntity()
// Test different pet types
switch j % 4 {
case 0:
@ -185,13 +185,13 @@ func TestEntityConcurrencyStress(t *testing.T) {
_ = entity.GetCosmeticPet()
entity.SetCosmeticPet(nil)
}
atomic.AddInt64(&petOps, 1)
}
}()
}
wg.Wait()
t.Logf("Pet operations: %d", petOps)
})
}
@ -208,23 +208,23 @@ func TestInfoStructConcurrencyStress(t *testing.T) {
// Test 1: Concurrent basic property access
t.Run("BasicProperties", func(t *testing.T) {
var nameOps, levelOps, statOps int64
wg.Add(numGoroutines)
for i := 0; i < numGoroutines; i++ {
go func(goroutineID int) {
defer wg.Done()
for j := 0; j < operationsPerGoroutine; j++ {
// Name operations
info.SetName("TestChar" + string(rune('A'+goroutineID%26)))
_ = info.GetName()
atomic.AddInt64(&nameOps, 1)
// Level operations
info.SetLevel(int16(j % 100))
_ = info.GetLevel()
atomic.AddInt64(&levelOps, 1)
// Stat operations
info.SetStr(float32(j))
_ = info.GetStr()
@ -233,28 +233,28 @@ func TestInfoStructConcurrencyStress(t *testing.T) {
}(i)
}
wg.Wait()
t.Logf("Name ops: %d, Level ops: %d, Stat ops: %d", nameOps, levelOps, statOps)
})
// Test 2: Concurrent concentration management
t.Run("ConcentrationManagement", func(t *testing.T) {
var addSuccesses, addFailures, removes int64
wg.Add(numGoroutines)
for i := 0; i < numGoroutines; i++ {
go func() {
defer wg.Done()
for j := 0; j < operationsPerGoroutine; j++ {
amount := int16(j%5 + 1) // 1-5 concentration points
if info.AddConcentration(amount) {
atomic.AddInt64(&addSuccesses, 1)
// Small delay to increase contention
time.Sleep(time.Microsecond)
info.RemoveConcentration(amount)
atomic.AddInt64(&removes, 1)
} else {
@ -264,10 +264,10 @@ func TestInfoStructConcurrencyStress(t *testing.T) {
}()
}
wg.Wait()
t.Logf("Concentration adds: %d, failures: %d, removes: %d",
t.Logf("Concentration adds: %d, failures: %d, removes: %d",
addSuccesses, addFailures, removes)
// Verify final state
finalConc := info.GetCurConcentration()
if finalConc < 0 || finalConc > info.GetMaxConcentration() {
@ -278,19 +278,19 @@ func TestInfoStructConcurrencyStress(t *testing.T) {
// Test 3: Concurrent coin operations
t.Run("CoinOperations", func(t *testing.T) {
var addOps, removeSuccesses, removeFailures int64
wg.Add(numGoroutines)
for i := 0; i < numGoroutines; i++ {
go func(goroutineID int) {
defer wg.Done()
for j := 0; j < operationsPerGoroutine; j++ {
amount := int32((goroutineID*1000 + j) % 10000)
// Add coins
info.AddCoins(amount)
atomic.AddInt64(&addOps, 1)
// Try to remove some coins
removeAmount := amount / 2
if info.RemoveCoins(removeAmount) {
@ -298,17 +298,17 @@ func TestInfoStructConcurrencyStress(t *testing.T) {
} else {
atomic.AddInt64(&removeFailures, 1)
}
// Read total coins
_ = info.GetCoins()
}
}(i)
}
wg.Wait()
t.Logf("Coin adds: %d, remove successes: %d, failures: %d",
t.Logf("Coin adds: %d, remove successes: %d, failures: %d",
addOps, removeSuccesses, removeFailures)
// Verify coins are non-negative
finalCoins := info.GetCoins()
if finalCoins < 0 {
@ -320,16 +320,16 @@ func TestInfoStructConcurrencyStress(t *testing.T) {
t.Run("ResistanceOperations", func(t *testing.T) {
var resistOps int64
resistTypes := []string{"heat", "cold", "magic", "mental", "divine", "disease", "poison"}
wg.Add(numGoroutines)
for i := 0; i < numGoroutines; i++ {
go func(goroutineID int) {
defer wg.Done()
for j := 0; j < operationsPerGoroutine; j++ {
resistType := resistTypes[j%len(resistTypes)]
value := int16(j % 100)
info.SetResistance(resistType, value)
_ = info.GetResistance(resistType)
atomic.AddInt64(&resistOps, 1)
@ -337,25 +337,25 @@ func TestInfoStructConcurrencyStress(t *testing.T) {
}(i)
}
wg.Wait()
t.Logf("Resistance operations: %d", resistOps)
})
// Test 5: Concurrent clone operations
t.Run("CloneOperations", func(t *testing.T) {
var cloneOps int64
// Set some initial state
info.SetName("Original")
info.SetLevel(50)
info.SetStr(100.0)
info.AddConcentration(5)
wg.Add(numGoroutines)
for i := 0; i < numGoroutines; i++ {
go func() {
defer wg.Done()
for j := 0; j < operationsPerGoroutine/10; j++ { // Fewer clones as they're expensive
clone := info.Clone()
if clone != nil {
@ -370,7 +370,7 @@ func TestInfoStructConcurrencyStress(t *testing.T) {
}()
}
wg.Wait()
t.Logf("Clone operations: %d", cloneOps)
})
}
@ -393,24 +393,24 @@ func TestRaceConditionDetection(t *testing.T) {
for i := 0; i < numGoroutines; i++ {
go func(id int) {
defer wg.Done()
for j := 0; j < 50; j++ {
// Mix of read and write operations that could race
entity.SetInCombat(id%2 == 0)
isInCombat := entity.IsInCombat()
entity.SetCasting(j%2 == 0)
isCasting := entity.IsCasting()
// Stats with bonus calculations
info.SetStr(float32(id + j))
str := entity.GetStr()
// Concentration with potential for contention
if info.AddConcentration(1) {
info.RemoveConcentration(1)
}
// Use the values to prevent optimization
_ = isInCombat
_ = isCasting
@ -425,21 +425,21 @@ func TestRaceConditionDetection(t *testing.T) {
func TestConcurrencyCleanup(t *testing.T) {
entity := NewEntity()
info := entity.GetInfoStruct()
// Verify entity is in clean state after stress tests
if entity.IsInCombat() {
t.Error("Entity should not be in combat after tests")
}
if entity.IsCasting() {
t.Error("Entity should not be casting after tests")
}
if info.GetCurConcentration() < 0 {
t.Error("Concentration should not be negative")
}
if info.GetCoins() < 0 {
t.Error("Coins should not be negative")
}
}
}

41
internal/entity/doc.go Normal file
View File

@ -0,0 +1,41 @@
// Package entity provides the core combat and magic systems for EverQuest II server emulation.
// It extends the base Spawn system with combat capabilities, spell effects, and character statistics management.
//
// Basic Usage:
//
// entity := entity.NewEntity()
// entity.GetInfoStruct().SetName("TestEntity")
// entity.GetInfoStruct().SetLevel(50)
// entity.GetInfoStruct().SetStr(100.0)
//
// Managing Spell Effects:
//
// // Add a maintained spell (buff)
// success := entity.AddMaintainedSpell("Heroic Strength", 12345, 300.0, 2)
//
// // Add a temporary effect
// entity.AddSpellEffect(54321, casterID, 60.0)
//
// // Add a detrimental effect
// entity.AddDetrimentalSpell(99999, attackerID, 30.0, 1)
//
// Stat Calculations:
//
// // Get effective stats (base + bonuses)
// str := entity.GetStr()
// sta := entity.GetSta()
// primary := entity.GetPrimaryStat()
//
// // Recalculate all bonuses
// entity.CalculateBonuses()
//
// Pet Management:
//
// // Set a summon pet
// entity.SetPet(petEntity)
//
// // Check pet status
// if entity.GetPet() != nil && !entity.IsPetDismissing() {
// // Pet is active
// }
package entity

View File

@ -7,6 +7,7 @@ import (
"eq2emu/internal/common"
"eq2emu/internal/spawn"
"eq2emu/internal/spells"
)
// Combat and pet types
@ -43,7 +44,7 @@ type Entity struct {
regenPowerRate int16 // Power regeneration rate
// Spell and effect management
spellEffectManager *SpellEffectManager
spellEffectManager *spells.SpellEffectManager
// Pet system
pet *Entity // Summon pet
@ -106,7 +107,7 @@ func NewEntity() *Entity {
lastHeading: -1,
regenHpRate: 0,
regenPowerRate: 0,
spellEffectManager: NewSpellEffectManager(),
spellEffectManager: spells.NewSpellEffectManager(),
pet: nil,
charmedPet: nil,
deityPet: nil,
@ -357,7 +358,7 @@ func (e *Entity) AddMaintainedSpell(name string, spellID int32, duration float32
return false
}
effect := NewMaintainedEffects(name, spellID, duration)
effect := spells.NewMaintainedEffects(name, spellID, duration)
effect.ConcUsed = concentration
e.maintainedMutex.Lock()
@ -390,7 +391,7 @@ func (e *Entity) RemoveMaintainedSpell(spellID int32) bool {
}
// GetMaintainedSpell retrieves a maintained spell effect
func (e *Entity) GetMaintainedSpell(spellID int32) *MaintainedEffects {
func (e *Entity) GetMaintainedSpell(spellID int32) *spells.MaintainedEffects {
e.maintainedMutex.RLock()
defer e.maintainedMutex.RUnlock()
@ -399,7 +400,7 @@ func (e *Entity) GetMaintainedSpell(spellID int32) *MaintainedEffects {
// AddSpellEffect adds a temporary spell effect
func (e *Entity) AddSpellEffect(spellID int32, casterID int32, duration float32) bool {
effect := NewSpellEffects(spellID, casterID, duration)
effect := spells.NewSpellEffects(spellID, casterID, duration)
e.spellEffectMutex.Lock()
defer e.spellEffectMutex.Unlock()
@ -417,7 +418,7 @@ func (e *Entity) RemoveSpellEffect(spellID int32) bool {
// AddDetrimentalSpell adds a detrimental effect
func (e *Entity) AddDetrimentalSpell(spellID int32, casterID int32, duration float32, detType int8) {
effect := NewDetrimentalEffects(spellID, casterID, duration)
effect := spells.NewDetrimentalEffects(spellID, casterID, duration)
effect.DetType = detType
e.detrimentalMutex.Lock()
@ -435,7 +436,7 @@ func (e *Entity) RemoveDetrimentalSpell(spellID int32, casterID int32) bool {
}
// GetDetrimentalEffect retrieves a detrimental effect
func (e *Entity) GetDetrimentalEffect(spellID int32, casterID int32) *DetrimentalEffects {
func (e *Entity) GetDetrimentalEffect(spellID int32, casterID int32) *spells.DetrimentalEffects {
e.detrimentalMutex.RLock()
defer e.detrimentalMutex.RUnlock()
@ -451,13 +452,13 @@ func (e *Entity) HasControlEffect(controlType int8) bool {
// AddSkillBonus adds a skill-related bonus
func (e *Entity) AddSkillBonus(spellID int32, skillID int32, value float32) {
bonus := NewBonusValues(spellID, int16(skillID+100), value) // Skill bonuses use type 100+
bonus := spells.NewBonusValues(spellID, int16(skillID+100), value) // Skill bonuses use type 100+
e.spellEffectManager.AddBonus(bonus)
}
// AddStatBonus adds a stat bonus
func (e *Entity) AddStatBonus(spellID int32, statType int16, value float32) {
bonus := NewBonusValues(spellID, statType, value)
bonus := spells.NewBonusValues(spellID, statType, value)
e.spellEffectManager.AddBonus(bonus)
}

View File

@ -4,6 +4,8 @@ import (
"sync"
"testing"
"time"
"eq2emu/internal/spells"
)
func TestNewEntity(t *testing.T) {
@ -68,7 +70,7 @@ func TestEntityIsEntity(t *testing.T) {
func TestEntityInfoStruct(t *testing.T) {
entity := NewEntity()
stats := entity.GetInfoStruct()
if stats == nil {
t.Error("Expected InfoStruct to be initialized")
@ -92,7 +94,7 @@ func TestEntityInfoStruct(t *testing.T) {
func TestEntityClient(t *testing.T) {
entity := NewEntity()
// Base Entity should return nil for GetClient
client := entity.GetClient()
if client != nil {
@ -628,7 +630,7 @@ func TestEntityControlEffects(t *testing.T) {
// Test has control effect - should work without panicking
// The actual implementation depends on the spell effect manager
hasStun := entity.HasControlEffect(ControlEffectStun)
hasStun := entity.HasControlEffect(spells.ControlEffectStun)
_ = hasStun // We can't easily test the actual value without setting up effects
}
@ -651,7 +653,7 @@ func TestEntityConstants(t *testing.T) {
}
// Test control effect constants (re-exported from spells package)
if ControlEffectStun == 0 && ControlEffectRoot == 0 {
if spells.ControlEffectStun == 0 && spells.ControlEffectRoot == 0 {
t.Error("Control effect constants should be non-zero")
}
}
}

View File

@ -374,14 +374,14 @@ func TestInfoStructConcurrency(t *testing.T) {
for i := 0; i < numGoroutines; i++ {
go func(index int) {
defer wg.Done()
// Each goroutine sets unique values
info.SetName("Character" + string(rune('A'+index)))
_ = info.GetName()
info.SetLevel(int16(10 + index))
_ = info.GetLevel()
info.SetStr(float32(10.0 + float32(index)))
_ = info.GetStr()
}(i)
@ -393,7 +393,7 @@ func TestInfoStructConcurrency(t *testing.T) {
for i := 0; i < numGoroutines; i++ {
go func() {
defer wg.Done()
// Try to add concentration
if info.AddConcentration(1) {
// If successful, remove it after a short delay
@ -413,10 +413,10 @@ func TestInfoStructConcurrency(t *testing.T) {
for i := 0; i < numGoroutines; i++ {
go func(amount int32) {
defer wg.Done()
info.AddCoins(amount)
_ = info.GetCoins()
// Try to remove some coins
info.RemoveCoins(amount / 2)
}(int32(100 + i))
@ -428,10 +428,10 @@ func TestInfoStructConcurrency(t *testing.T) {
for i := 0; i < numGoroutines; i++ {
go func(value int16) {
defer wg.Done()
info.SetResistance("heat", value)
_ = info.GetResistance("heat")
info.SetResistance("cold", value+1)
_ = info.GetResistance("cold")
}(int16(i))
@ -445,7 +445,7 @@ func TestInfoStructLargeValues(t *testing.T) {
// Test with large coin amounts
largeCoinAmount := int32(2000000000) // 2 billion copper
info.AddCoins(largeCoinAmount)
totalCoins := info.GetCoins()
if totalCoins != largeCoinAmount {
t.Errorf("Expected large coin amount %d, got %d", largeCoinAmount, totalCoins)
@ -464,11 +464,11 @@ func TestInfoStructLargeValues(t *testing.T) {
// Test with maximum values
info.SetMaxConcentration(32767) // Max int16
info.SetLevel(32767)
if info.GetMaxConcentration() != 32767 {
t.Errorf("Expected max concentration 32767, got %d", info.GetMaxConcentration())
}
if info.GetLevel() != 32767 {
t.Errorf("Expected level 32767, got %d", info.GetLevel())
}
@ -511,4 +511,4 @@ func TestInfoStructEdgeCases(t *testing.T) {
if info.GetName() != longName {
t.Error("Expected to handle very long names")
}
}
}

View File

@ -1,37 +0,0 @@
package entity
// DEPRECATED: This file now imports spell effect structures from the spells package.
// The spell effect management has been moved to internal/spells for better organization.
import (
"eq2emu/internal/spells"
)
// Re-export spell effect types for backward compatibility
// These will eventually be removed in favor of direct imports from spells package
type BonusValues = spells.BonusValues
type MaintainedEffects = spells.MaintainedEffects
type SpellEffects = spells.SpellEffects
type DetrimentalEffects = spells.DetrimentalEffects
type SpellEffectManager = spells.SpellEffectManager
// Re-export constructor functions
var NewBonusValues = spells.NewBonusValues
var NewMaintainedEffects = spells.NewMaintainedEffects
var NewSpellEffects = spells.NewSpellEffects
var NewDetrimentalEffects = spells.NewDetrimentalEffects
var NewSpellEffectManager = spells.NewSpellEffectManager
// Re-export constants
const (
ControlEffectStun = spells.ControlEffectStun
ControlEffectRoot = spells.ControlEffectRoot
ControlEffectMez = spells.ControlEffectMez
ControlEffectDaze = spells.ControlEffectDaze
ControlEffectFear = spells.ControlEffectFear
ControlEffectSlow = spells.ControlEffectSlow
ControlEffectSnare = spells.ControlEffectSnare
ControlEffectCharm = spells.ControlEffectCharm
ControlMaxEffects = spells.ControlMaxEffects
)