package spells import ( "sync" "time" ) // BonusValues represents a stat bonus from equipment, spells, or other sources // Moved from entity package to centralize spell-related structures type BonusValues struct { SpellID int32 // ID of spell providing this bonus Tier int8 // Tier of the bonus Type int16 // Type of bonus (stat type) Value float32 // Bonus value ClassReq int64 // Required class bitmask RaceReq []int16 // Required race IDs FactionReq []int16 // Required faction IDs // TODO: Add LuaSpell reference when spell system is implemented // LuaSpell *LuaSpell // Associated Lua spell } // NewBonusValues creates a new bonus value entry func NewBonusValues(spellID int32, bonusType int16, value float32) *BonusValues { return &BonusValues{ SpellID: spellID, Tier: 1, Type: bonusType, Value: value, ClassReq: 0, RaceReq: make([]int16, 0), FactionReq: make([]int16, 0), } } // MeetsRequirements checks if an entity meets the requirements for this bonus func (bv *BonusValues) MeetsRequirements(entityClass int64, race int16, factionID int32) bool { // Check class requirement if bv.ClassReq != 0 && (entityClass&bv.ClassReq) == 0 { return false } // Check race requirement if len(bv.RaceReq) > 0 { raceMatch := false for _, reqRace := range bv.RaceReq { if reqRace == race { raceMatch = true break } } if !raceMatch { return false } } // Check faction requirement if len(bv.FactionReq) > 0 { factionMatch := false for _, reqFaction := range bv.FactionReq { if reqFaction == int16(factionID) { factionMatch = true break } } if !factionMatch { return false } } return true } // MaintainedEffects represents a buff that is actively maintained on an entity // Moved from entity package to centralize spell-related structures type MaintainedEffects struct { Name [60]byte // Name of the spell Target int32 // Target entity ID TargetType int8 // Type of target SpellID int32 // Spell ID InheritedSpellID int32 // Inherited spell ID (for spell upgrades) SlotPos int32 // Slot position in maintained effects Icon int16 // Icon to display IconBackdrop int16 // Icon backdrop ConcUsed int8 // Concentration used Tier int8 // Spell tier TotalTime float32 // Total duration of effect ExpireTimestamp int32 // When the effect expires // TODO: Add LuaSpell reference when spell system is implemented // Spell *LuaSpell // Associated Lua spell } // NewMaintainedEffects creates a new maintained effect func NewMaintainedEffects(name string, spellID int32, duration float32) *MaintainedEffects { effect := &MaintainedEffects{ Target: 0, TargetType: 0, SpellID: spellID, InheritedSpellID: 0, SlotPos: 0, Icon: 0, IconBackdrop: 0, ConcUsed: 0, Tier: 1, TotalTime: duration, ExpireTimestamp: int32(time.Now().Unix()) + int32(duration), } // Copy name to fixed-size array nameBytes := []byte(name) copy(effect.Name[:], nameBytes) return effect } // GetName returns the spell name as a string func (me *MaintainedEffects) GetName() string { // Find the null terminator nameBytes := me.Name[:] for i, b := range nameBytes { if b == 0 { return string(nameBytes[:i]) } } return string(nameBytes) } // IsExpired checks if the maintained effect has expired func (me *MaintainedEffects) IsExpired() bool { if me.TotalTime <= 0 { return false // Permanent effect } return int32(time.Now().Unix()) >= me.ExpireTimestamp } // GetTimeRemaining returns the time remaining for this effect func (me *MaintainedEffects) GetTimeRemaining() float32 { if me.TotalTime <= 0 { return -1 // Permanent effect } remaining := float32(me.ExpireTimestamp - int32(time.Now().Unix())) if remaining < 0 { return 0 } return remaining } // SpellEffects represents a temporary spell effect on an entity // Moved from entity package to centralize spell-related structures type SpellEffects struct { SpellID int32 // Spell ID InheritedSpellID int32 // Inherited spell ID // TODO: Add Entity reference when implemented // Caster *Entity // Entity that cast the spell CasterID int32 // ID of caster entity (temporary) TotalTime float32 // Total duration ExpireTimestamp int32 // When effect expires Icon int16 // Icon to display IconBackdrop int16 // Icon backdrop Tier int8 // Spell tier // TODO: Add LuaSpell reference when spell system is implemented // Spell *LuaSpell // Associated Lua spell } // NewSpellEffects creates a new spell effect func NewSpellEffects(spellID int32, casterID int32, duration float32) *SpellEffects { return &SpellEffects{ SpellID: spellID, InheritedSpellID: 0, CasterID: casterID, TotalTime: duration, ExpireTimestamp: int32(time.Now().Unix()) + int32(duration), Icon: 0, IconBackdrop: 0, Tier: 1, } } // IsExpired checks if the spell effect has expired func (se *SpellEffects) IsExpired() bool { if se.TotalTime <= 0 { return false // Permanent effect } return int32(time.Now().Unix()) >= se.ExpireTimestamp } // GetTimeRemaining returns the time remaining for this effect func (se *SpellEffects) GetTimeRemaining() float32 { if se.TotalTime <= 0 { return -1 // Permanent effect } remaining := float32(se.ExpireTimestamp - int32(time.Now().Unix())) if remaining < 0 { return 0 } return remaining } // DetrimentalEffects represents a debuff or harmful effect on an entity // Moved from entity package to centralize spell-related structures type DetrimentalEffects struct { SpellID int32 // Spell ID InheritedSpellID int32 // Inherited spell ID // TODO: Add Entity reference when implemented // Caster *Entity // Entity that cast the spell CasterID int32 // ID of caster entity (temporary) ExpireTimestamp int32 // When effect expires Icon int16 // Icon to display IconBackdrop int16 // Icon backdrop Tier int8 // Spell tier DetType int8 // Detrimental type Incurable bool // Cannot be cured ControlEffect int8 // Control effect type TotalTime float32 // Total duration // TODO: Add LuaSpell reference when spell system is implemented // Spell *LuaSpell // Associated Lua spell } // NewDetrimentalEffects creates a new detrimental effect func NewDetrimentalEffects(spellID int32, casterID int32, duration float32) *DetrimentalEffects { return &DetrimentalEffects{ SpellID: spellID, InheritedSpellID: 0, CasterID: casterID, ExpireTimestamp: int32(time.Now().Unix()) + int32(duration), Icon: 0, IconBackdrop: 0, Tier: 1, DetType: 0, Incurable: false, ControlEffect: 0, TotalTime: duration, } } // IsExpired checks if the detrimental effect has expired func (de *DetrimentalEffects) IsExpired() bool { if de.TotalTime <= 0 { return false // Permanent effect } return int32(time.Now().Unix()) >= de.ExpireTimestamp } // GetTimeRemaining returns the time remaining for this effect func (de *DetrimentalEffects) GetTimeRemaining() float32 { if de.TotalTime <= 0 { return -1 // Permanent effect } remaining := float32(de.ExpireTimestamp - int32(time.Now().Unix())) if remaining < 0 { return 0 } return remaining } // IsControlEffect checks if this detrimental is a control effect func (de *DetrimentalEffects) IsControlEffect() bool { return de.ControlEffect > 0 } // SpellEffectManager manages all spell effects for an entity // Moved from entity package to centralize spell-related structures type SpellEffectManager struct { // Maintained effects (buffs that use concentration) maintainedEffects [30]*MaintainedEffects maintainedMutex sync.RWMutex // Temporary spell effects (buffs/debuffs with durations) spellEffects [45]*SpellEffects effectsMutex sync.RWMutex // Detrimental effects (debuffs) detrimentalEffects []DetrimentalEffects detrimentalMutex sync.RWMutex // Control effects organized by type controlEffects map[int8][]*DetrimentalEffects controlMutex sync.RWMutex // Detrimental count by type for stacking limits detCountList map[int8]int8 countMutex sync.RWMutex // Bonus list for stat modifications bonusList []*BonusValues bonusMutex sync.RWMutex // Immunity list organized by effect type immunities map[int8][]*DetrimentalEffects immunityMutex sync.RWMutex } // NewSpellEffectManager creates a new spell effect manager func NewSpellEffectManager() *SpellEffectManager { return &SpellEffectManager{ maintainedEffects: [30]*MaintainedEffects{}, spellEffects: [45]*SpellEffects{}, detrimentalEffects: make([]DetrimentalEffects, 0), controlEffects: make(map[int8][]*DetrimentalEffects), detCountList: make(map[int8]int8), bonusList: make([]*BonusValues, 0), immunities: make(map[int8][]*DetrimentalEffects), } } // AddMaintainedEffect adds a maintained effect to an available slot func (sem *SpellEffectManager) AddMaintainedEffect(effect *MaintainedEffects) bool { sem.maintainedMutex.Lock() defer sem.maintainedMutex.Unlock() // Find an empty slot for i := 0; i < len(sem.maintainedEffects); i++ { if sem.maintainedEffects[i] == nil { effect.SlotPos = int32(i) sem.maintainedEffects[i] = effect return true } } return false // No available slots } // RemoveMaintainedEffect removes a maintained effect by spell ID func (sem *SpellEffectManager) RemoveMaintainedEffect(spellID int32) bool { sem.maintainedMutex.Lock() defer sem.maintainedMutex.Unlock() for i := 0; i < len(sem.maintainedEffects); i++ { if sem.maintainedEffects[i] != nil && sem.maintainedEffects[i].SpellID == spellID { sem.maintainedEffects[i] = nil return true } } return false } // GetMaintainedEffect retrieves a maintained effect by spell ID func (sem *SpellEffectManager) GetMaintainedEffect(spellID int32) *MaintainedEffects { sem.maintainedMutex.RLock() defer sem.maintainedMutex.RUnlock() for _, effect := range sem.maintainedEffects { if effect != nil && effect.SpellID == spellID { return effect } } return nil } // GetAllMaintainedEffects returns a copy of all active maintained effects func (sem *SpellEffectManager) GetAllMaintainedEffects() []*MaintainedEffects { sem.maintainedMutex.RLock() defer sem.maintainedMutex.RUnlock() effects := make([]*MaintainedEffects, 0) for _, effect := range sem.maintainedEffects { if effect != nil { effects = append(effects, effect) } } return effects } // AddSpellEffect adds a temporary spell effect to an available slot func (sem *SpellEffectManager) AddSpellEffect(effect *SpellEffects) bool { sem.effectsMutex.Lock() defer sem.effectsMutex.Unlock() // Find an empty slot for i := 0; i < len(sem.spellEffects); i++ { if sem.spellEffects[i] == nil { sem.spellEffects[i] = effect return true } } return false // No available slots } // RemoveSpellEffect removes a spell effect by spell ID func (sem *SpellEffectManager) RemoveSpellEffect(spellID int32) bool { sem.effectsMutex.Lock() defer sem.effectsMutex.Unlock() for i := 0; i < len(sem.spellEffects); i++ { if sem.spellEffects[i] != nil && sem.spellEffects[i].SpellID == spellID { sem.spellEffects[i] = nil return true } } return false } // GetSpellEffect retrieves a spell effect by spell ID func (sem *SpellEffectManager) GetSpellEffect(spellID int32) *SpellEffects { sem.effectsMutex.RLock() defer sem.effectsMutex.RUnlock() for _, effect := range sem.spellEffects { if effect != nil && effect.SpellID == spellID { return effect } } return nil } // AddDetrimentalEffect adds a detrimental effect func (sem *SpellEffectManager) AddDetrimentalEffect(effect DetrimentalEffects) { sem.detrimentalMutex.Lock() defer sem.detrimentalMutex.Unlock() sem.detrimentalEffects = append(sem.detrimentalEffects, effect) // Update detrimental count sem.countMutex.Lock() sem.detCountList[effect.DetType]++ sem.countMutex.Unlock() // Add to control effects if applicable if effect.IsControlEffect() { sem.controlMutex.Lock() if sem.controlEffects[effect.ControlEffect] == nil { sem.controlEffects[effect.ControlEffect] = make([]*DetrimentalEffects, 0) } sem.controlEffects[effect.ControlEffect] = append(sem.controlEffects[effect.ControlEffect], &effect) sem.controlMutex.Unlock() } } // RemoveDetrimentalEffect removes a detrimental effect by spell ID and caster func (sem *SpellEffectManager) RemoveDetrimentalEffect(spellID int32, casterID int32) bool { sem.detrimentalMutex.Lock() defer sem.detrimentalMutex.Unlock() for i, effect := range sem.detrimentalEffects { if effect.SpellID == spellID && effect.CasterID == casterID { // Remove from slice sem.detrimentalEffects = append(sem.detrimentalEffects[:i], sem.detrimentalEffects[i+1:]...) // Update detrimental count sem.countMutex.Lock() sem.detCountList[effect.DetType]-- if sem.detCountList[effect.DetType] <= 0 { delete(sem.detCountList, effect.DetType) } sem.countMutex.Unlock() // Remove from control effects if applicable if effect.IsControlEffect() { sem.removeFromControlEffects(effect.ControlEffect, spellID, casterID) } return true } } return false } // removeFromControlEffects removes a control effect from the control effects map func (sem *SpellEffectManager) removeFromControlEffects(controlType int8, spellID int32, casterID int32) { sem.controlMutex.Lock() defer sem.controlMutex.Unlock() if effects, exists := sem.controlEffects[controlType]; exists { for i, effect := range effects { if effect.SpellID == spellID && effect.CasterID == casterID { sem.controlEffects[controlType] = append(effects[:i], effects[i+1:]...) if len(sem.controlEffects[controlType]) == 0 { delete(sem.controlEffects, controlType) } break } } } } // GetDetrimentalEffect retrieves a detrimental effect by spell ID and caster func (sem *SpellEffectManager) GetDetrimentalEffect(spellID int32, casterID int32) *DetrimentalEffects { sem.detrimentalMutex.RLock() defer sem.detrimentalMutex.RUnlock() for i, effect := range sem.detrimentalEffects { if effect.SpellID == spellID && effect.CasterID == casterID { return &sem.detrimentalEffects[i] } } return nil } // HasControlEffect checks if the entity has a specific control effect active func (sem *SpellEffectManager) HasControlEffect(controlType int8) bool { sem.controlMutex.RLock() defer sem.controlMutex.RUnlock() effects, exists := sem.controlEffects[controlType] return exists && len(effects) > 0 } // AddBonus adds a stat bonus func (sem *SpellEffectManager) AddBonus(bonus *BonusValues) { sem.bonusMutex.Lock() defer sem.bonusMutex.Unlock() sem.bonusList = append(sem.bonusList, bonus) } // RemoveBonus removes a stat bonus by spell ID func (sem *SpellEffectManager) RemoveBonus(spellID int32) bool { sem.bonusMutex.Lock() defer sem.bonusMutex.Unlock() for i, bonus := range sem.bonusList { if bonus.SpellID == spellID { sem.bonusList = append(sem.bonusList[:i], sem.bonusList[i+1:]...) return true } } return false } // GetBonusValue calculates the total bonus value for a specific stat type func (sem *SpellEffectManager) GetBonusValue(bonusType int16, entityClass int64, race int16, factionID int32) float32 { sem.bonusMutex.RLock() defer sem.bonusMutex.RUnlock() var total float32 = 0 for _, bonus := range sem.bonusList { if bonus.Type == bonusType && bonus.MeetsRequirements(entityClass, race, factionID) { total += bonus.Value } } return total } // CleanupExpiredEffects removes all expired effects func (sem *SpellEffectManager) CleanupExpiredEffects() { // Clean maintained effects sem.maintainedMutex.Lock() for i, effect := range sem.maintainedEffects { if effect != nil && effect.IsExpired() { sem.maintainedEffects[i] = nil } } sem.maintainedMutex.Unlock() // Clean spell effects sem.effectsMutex.Lock() for i, effect := range sem.spellEffects { if effect != nil && effect.IsExpired() { sem.spellEffects[i] = nil } } sem.effectsMutex.Unlock() // Clean detrimental effects sem.detrimentalMutex.Lock() newDetrimentals := make([]DetrimentalEffects, 0) for _, effect := range sem.detrimentalEffects { if !effect.IsExpired() { newDetrimentals = append(newDetrimentals, effect) } else { // Update count sem.countMutex.Lock() sem.detCountList[effect.DetType]-- if sem.detCountList[effect.DetType] <= 0 { delete(sem.detCountList, effect.DetType) } sem.countMutex.Unlock() // Remove from control effects if effect.IsControlEffect() { sem.removeFromControlEffects(effect.ControlEffect, effect.SpellID, effect.CasterID) } } } sem.detrimentalEffects = newDetrimentals sem.detrimentalMutex.Unlock() } // ClearAllEffects removes all spell effects func (sem *SpellEffectManager) ClearAllEffects() { sem.maintainedMutex.Lock() for i := range sem.maintainedEffects { sem.maintainedEffects[i] = nil } sem.maintainedMutex.Unlock() sem.effectsMutex.Lock() for i := range sem.spellEffects { sem.spellEffects[i] = nil } sem.effectsMutex.Unlock() sem.detrimentalMutex.Lock() sem.detrimentalEffects = make([]DetrimentalEffects, 0) sem.detrimentalMutex.Unlock() sem.controlMutex.Lock() sem.controlEffects = make(map[int8][]*DetrimentalEffects) sem.controlMutex.Unlock() sem.countMutex.Lock() sem.detCountList = make(map[int8]int8) sem.countMutex.Unlock() sem.bonusMutex.Lock() sem.bonusList = make([]*BonusValues, 0) sem.bonusMutex.Unlock() }