621 lines
18 KiB
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()
|
|
} |