eq2go/internal/spells/spell_effects.go

621 lines
18 KiB
Go

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