package spells import ( "fmt" "sync" ) // Spell represents a complete spell with data, levels, effects, and LUA data // This is the main spell class converted from C++ Spell class type Spell struct { // Core spell data data *SpellData // Spell progression and requirements levels []*LevelArray // Level requirements by class effects []*SpellDisplayEffect // Display effects for tooltips scriptData []*SpellScriptData // Native Go script data // Computed properties (cached for performance) healSpell bool // Cached: is this a healing spell buffSpell bool // Cached: is this a buff spell damageSpell bool // Cached: is this a damage spell controlSpell bool // Cached: is this a control spell offenseSpell bool // Cached: is this an offensive spell copiedSpell bool // Whether this is a copied/derived spell // Runtime state stayLocked bool // Whether spell should stay locked // Thread safety mutex sync.RWMutex } // NewSpell creates a new spell with default spell data func NewSpell() *Spell { return &Spell{ data: NewSpellData(), levels: make([]*LevelArray, 0), effects: make([]*SpellDisplayEffect, 0), scriptData: make([]*SpellScriptData, 0), healSpell: false, buffSpell: false, damageSpell: false, controlSpell: false, offenseSpell: false, copiedSpell: false, stayLocked: false, } } // NewSpellFromData creates a new spell with existing spell data func NewSpellFromData(spellData *SpellData) *Spell { s := NewSpell() s.data = spellData s.computeSpellProperties() return s } // NewSpellCopy creates a copy of an existing spell (for spell upgrades/variants) func NewSpellCopy(hostSpell *Spell, uniqueSpell bool) *Spell { if hostSpell == nil { return NewSpell() } hostSpell.mutex.RLock() defer hostSpell.mutex.RUnlock() s := &Spell{ data: hostSpell.data.Clone(), levels: make([]*LevelArray, 0), effects: make([]*SpellDisplayEffect, 0), scriptData: make([]*SpellScriptData, 0), healSpell: hostSpell.healSpell, buffSpell: hostSpell.buffSpell, damageSpell: hostSpell.damageSpell, controlSpell: hostSpell.controlSpell, offenseSpell: hostSpell.offenseSpell, copiedSpell: true, stayLocked: hostSpell.stayLocked, } // Copy levels for _, level := range hostSpell.levels { newLevel := &LevelArray{ AdventureClass: level.AdventureClass, TradeskillClass: level.TradeskillClass, SpellLevel: level.SpellLevel, ClassicSpellLevel: level.ClassicSpellLevel, } s.levels = append(s.levels, newLevel) } // Copy effects for _, effect := range hostSpell.effects { newEffect := &SpellDisplayEffect{ Percentage: effect.Percentage, Subbullet: effect.Subbullet, Description: effect.Description, NeedsDBSave: effect.NeedsDBSave, } s.effects = append(s.effects, newEffect) } // Copy script data for _, script := range hostSpell.scriptData { newScript := &SpellScriptData{ Type: script.Type, IntValue: script.IntValue, BoolValue: script.BoolValue, FloatValue: script.FloatValue, StringValue: script.StringValue, StringValue2: script.StringValue2, IntValue2: script.IntValue2, FloatValue2: script.FloatValue2, Helper: script.Helper, Handler: script.Handler, NeedsDBSave: script.NeedsDBSave, } s.scriptData = append(s.scriptData, newScript) } // If unique spell, generate new ID if uniqueSpell { // TODO: Generate unique spell ID when spell ID management is implemented // s.data.SetID(GenerateUniqueSpellID()) } return s } // GetSpellData returns the core spell data (thread-safe) func (s *Spell) GetSpellData() *SpellData { s.mutex.RLock() defer s.mutex.RUnlock() return s.data } // GetSpellID returns the spell's unique ID func (s *Spell) GetSpellID() int32 { return s.data.GetID() } // GetName returns the spell's name func (s *Spell) GetName() string { return s.data.GetName() } // GetDescription returns the spell's description func (s *Spell) GetDescription() string { s.mutex.RLock() defer s.mutex.RUnlock() return s.data.Description } // GetSpellTier returns the spell's tier func (s *Spell) GetSpellTier() int8 { return s.data.GetTier() } // GetSpellDuration returns the spell's duration func (s *Spell) GetSpellDuration() int32 { return s.data.GetDuration() } // GetSpellIcon returns the spell's icon ID func (s *Spell) GetSpellIcon() int16 { s.mutex.RLock() defer s.mutex.RUnlock() return s.data.Icon } // GetSpellIconBackdrop returns the spell's icon backdrop func (s *Spell) GetSpellIconBackdrop() int16 { s.mutex.RLock() defer s.mutex.RUnlock() return s.data.IconBackdrop } // GetSpellIconHeroicOp returns the spell's heroic opportunity icon func (s *Spell) GetSpellIconHeroicOp() int16 { s.mutex.RLock() defer s.mutex.RUnlock() return s.data.IconHeroicOp } // AddSpellLevel adds a level requirement for a specific class func (s *Spell) AddSpellLevel(adventureClass, tradeskillClass int8, level int16, classicLevel float32) { s.mutex.Lock() defer s.mutex.Unlock() levelArray := &LevelArray{ AdventureClass: adventureClass, TradeskillClass: tradeskillClass, SpellLevel: level, ClassicSpellLevel: classicLevel, } s.levels = append(s.levels, levelArray) } // AddSpellEffect adds a display effect to the spell func (s *Spell) AddSpellEffect(percentage, subbullet int8, description string) { s.mutex.Lock() defer s.mutex.Unlock() effect := &SpellDisplayEffect{ Percentage: percentage, Subbullet: subbullet, Description: description, NeedsDBSave: true, } s.effects = append(s.effects, effect) } // AddSpellScriptData adds script data to the spell func (s *Spell) AddSpellScriptData(dataType int8, intValue, intValue2 int32, floatValue, floatValue2 float32, boolValue bool, stringValue, stringValue2, helper string, handler SpellScriptHandler) { s.mutex.Lock() defer s.mutex.Unlock() scriptData := &SpellScriptData{ Type: dataType, IntValue: intValue, BoolValue: boolValue, FloatValue: floatValue, StringValue: stringValue, StringValue2: stringValue2, IntValue2: intValue2, FloatValue2: floatValue2, Helper: helper, Handler: handler, NeedsDBSave: true, } s.scriptData = append(s.scriptData, scriptData) } // Convenience methods for adding specific script data types // AddSpellScriptDataInt adds integer script data func (s *Spell) AddSpellScriptDataInt(value, value2 int32, helper string, handler SpellScriptHandler) { s.AddSpellScriptData(SpellScriptDataTypeInt, value, value2, 0.0, 0.0, false, "", "", helper, handler) } // AddSpellScriptDataFloat adds float script data func (s *Spell) AddSpellScriptDataFloat(value, value2 float32, helper string, handler SpellScriptHandler) { s.AddSpellScriptData(SpellScriptDataTypeFloat, 0, 0, value, value2, false, "", "", helper, handler) } // AddSpellScriptDataBool adds boolean script data func (s *Spell) AddSpellScriptDataBool(value bool, helper string, handler SpellScriptHandler) { s.AddSpellScriptData(SpellScriptDataTypeBool, 0, 0, 0.0, 0.0, value, "", "", helper, handler) } // AddSpellScriptDataString adds string script data func (s *Spell) AddSpellScriptDataString(value, value2, helper string, handler SpellScriptHandler) { s.AddSpellScriptData(SpellScriptDataTypeString, 0, 0, 0.0, 0.0, false, value, value2, helper, handler) } // GetSpellLevels returns the spell level requirements func (s *Spell) GetSpellLevels() []*LevelArray { s.mutex.RLock() defer s.mutex.RUnlock() // Return a copy to prevent external modification levels := make([]*LevelArray, len(s.levels)) copy(levels, s.levels) return levels } // GetSpellEffects returns the spell display effects func (s *Spell) GetSpellEffects() []*SpellDisplayEffect { s.mutex.RLock() defer s.mutex.RUnlock() // Return a copy to prevent external modification effects := make([]*SpellDisplayEffect, len(s.effects)) copy(effects, s.effects) return effects } // GetSpellEffectSafe returns a spell effect safely by index func (s *Spell) GetSpellEffectSafe(index int) *SpellDisplayEffect { s.mutex.RLock() defer s.mutex.RUnlock() if index < 0 || index >= len(s.effects) { return nil } return s.effects[index] } // GetScriptData returns the spell's script data func (s *Spell) GetScriptData() []*SpellScriptData { s.mutex.RLock() defer s.mutex.RUnlock() // Return a copy to prevent external modification scriptData := make([]*SpellScriptData, len(s.scriptData)) copy(scriptData, s.scriptData) return scriptData } // Spell classification methods (cached for performance) // IsHealSpell returns whether this is a healing spell func (s *Spell) IsHealSpell() bool { s.mutex.RLock() defer s.mutex.RUnlock() return s.healSpell } // IsBuffSpell returns whether this is a buff spell func (s *Spell) IsBuffSpell() bool { s.mutex.RLock() defer s.mutex.RUnlock() return s.buffSpell } // IsDamageSpell returns whether this is a damage spell func (s *Spell) IsDamageSpell() bool { s.mutex.RLock() defer s.mutex.RUnlock() return s.damageSpell } // IsControlSpell returns whether this is a control spell func (s *Spell) IsControlSpell() bool { s.mutex.RLock() defer s.mutex.RUnlock() return s.controlSpell } // IsOffenseSpell returns whether this is an offensive spell func (s *Spell) IsOffenseSpell() bool { s.mutex.RLock() defer s.mutex.RUnlock() return s.offenseSpell } // IsCopiedSpell returns whether this is a copied spell func (s *Spell) IsCopiedSpell() bool { s.mutex.RLock() defer s.mutex.RUnlock() return s.copiedSpell } // GetStayLocked returns whether the spell should stay locked func (s *Spell) GetStayLocked() bool { s.mutex.RLock() defer s.mutex.RUnlock() return s.stayLocked } // StayLocked sets whether the spell should stay locked func (s *Spell) StayLocked(val bool) { s.mutex.Lock() defer s.mutex.Unlock() s.stayLocked = val } // Cast type checking methods // CastWhileStunned returns whether spell can be cast while stunned func (s *Spell) CastWhileStunned() bool { return s.data.CanCastWhileStunned() } // CastWhileMezzed returns whether spell can be cast while mezzed func (s *Spell) CastWhileMezzed() bool { return s.data.CanCastWhileMezzed() } // CastWhileStifled returns whether spell can be cast while stifled func (s *Spell) CastWhileStifled() bool { return s.data.CanCastWhileStifled() } // CastWhileFeared returns whether spell can be cast while feared func (s *Spell) CastWhileFeared() bool { return s.data.CanCastWhileFeared() } // Requirement checking methods // GetLevelRequired returns the required level for a specific player func (s *Spell) GetLevelRequired(playerClass int8, tradeskillClass int8) int16 { s.mutex.RLock() defer s.mutex.RUnlock() for _, level := range s.levels { if level.AdventureClass == playerClass || level.TradeskillClass == tradeskillClass { return level.SpellLevel } } return 0 // No specific requirement found } // GetHPRequired returns HP required to cast (could be modified by entity stats) func (s *Spell) GetHPRequired() int16 { s.mutex.RLock() defer s.mutex.RUnlock() return s.data.HPReq } // GetPowerRequired returns power required to cast func (s *Spell) GetPowerRequired() float32 { s.mutex.RLock() defer s.mutex.RUnlock() return s.data.PowerReq } // GetSavageryRequired returns savagery required to cast func (s *Spell) GetSavageryRequired() int16 { s.mutex.RLock() defer s.mutex.RUnlock() return s.data.SavageryReq } // GetDissonanceRequired returns dissonance required to cast func (s *Spell) GetDissonanceRequired() int16 { s.mutex.RLock() defer s.mutex.RUnlock() return s.data.DissonanceReq } // computeSpellProperties analyzes the spell to determine its classification func (s *Spell) computeSpellProperties() { // This would analyze spell effects, target types, etc. to determine spell classification // For now, use basic logic based on spell data s.buffSpell = s.data.IsBuffSpell() s.controlSpell = s.data.IsControlSpell() s.offenseSpell = s.data.IsOffenseSpell() s.healSpell = s.data.IsHealSpell() s.damageSpell = s.data.IsDamageSpell() } // String returns a string representation of the spell func (s *Spell) String() string { return fmt.Sprintf("Spell[ID=%d, Name=%s, Tier=%d]", s.GetSpellID(), s.GetName(), s.GetSpellTier()) } // ActiveSpell represents an active spell instance with runtime state // This replaces the C++ LuaSpell class functionality type ActiveSpell struct { // Core identification Spell *Spell // Reference to the spell definition CasterID int32 // ID of the entity casting this spell // Targeting InitialTarget int32 // Original target ID Targets []int32 // Current target IDs // Timing and state Timer SpellTimer // Timer for duration/tick tracking NumCalls int32 // Number of times spell has ticked Restored bool // Whether this spell was restored from DB SlotPos int16 // Spell book slot position // Runtime flags Interrupted bool // Whether spell was interrupted Deleted bool // Whether spell is marked for deletion HasProc bool // Whether spell has proc effects CasterCharID int32 // Character ID of caster (for cross-zone) DamageRemaining int32 // Remaining damage for DOT spells EffectBitmask int32 // Bitmask of active effects // Spell-specific data CustomFunction string // Custom LUA function name ResurrectHP float32 // HP to restore on resurrect ResurrectPower float32 // Power to restore on resurrect // Thread safety mutex sync.RWMutex } // SpellTimer represents timing information for an active spell type SpellTimer struct { StartTime int64 // When the spell started (milliseconds) Duration int32 // Total duration in milliseconds SetAtTrigger int64 // Time when timer was set/triggered } // NewActiveSpell creates a new ActiveSpell instance func NewActiveSpell(spell *Spell, casterID int32) *ActiveSpell { return &ActiveSpell{ Spell: spell, CasterID: casterID, InitialTarget: 0, Targets: make([]int32, 0), Timer: SpellTimer{}, NumCalls: 0, Restored: false, SlotPos: 0, Interrupted: false, Deleted: false, HasProc: false, CasterCharID: 0, DamageRemaining: 0, EffectBitmask: 0, CustomFunction: "", ResurrectHP: 0.0, ResurrectPower: 0.0, } } // GetTargets returns a copy of the target list func (as *ActiveSpell) GetTargets() []int32 { as.mutex.RLock() defer as.mutex.RUnlock() targets := make([]int32, len(as.Targets)) copy(targets, as.Targets) return targets } // AddTarget adds a target to the spell func (as *ActiveSpell) AddTarget(targetID int32) { as.mutex.Lock() defer as.mutex.Unlock() // Check if target already exists for _, id := range as.Targets { if id == targetID { return } } as.Targets = append(as.Targets, targetID) } // RemoveTarget removes a target from the spell func (as *ActiveSpell) RemoveTarget(targetID int32) bool { as.mutex.Lock() defer as.mutex.Unlock() for i, id := range as.Targets { if id == targetID { as.Targets = append(as.Targets[:i], as.Targets[i+1:]...) return true } } return false } // HasTarget checks if the spell targets a specific entity func (as *ActiveSpell) HasTarget(targetID int32) bool { as.mutex.RLock() defer as.mutex.RUnlock() for _, id := range as.Targets { if id == targetID { return true } } return false } // GetTargetCount returns the number of targets func (as *ActiveSpell) GetTargetCount() int { as.mutex.RLock() defer as.mutex.RUnlock() return len(as.Targets) } // ClearTargets removes all targets from the spell func (as *ActiveSpell) ClearTargets() { as.mutex.Lock() defer as.mutex.Unlock() as.Targets = as.Targets[:0] } // SetCustomFunction sets a custom LUA function for this spell instance func (as *ActiveSpell) SetCustomFunction(functionName string) { as.mutex.Lock() defer as.mutex.Unlock() as.CustomFunction = functionName } // GetCustomFunction returns the custom LUA function name func (as *ActiveSpell) GetCustomFunction() string { as.mutex.RLock() defer as.mutex.RUnlock() return as.CustomFunction } // MarkForDeletion marks the spell for removal func (as *ActiveSpell) MarkForDeletion() { as.mutex.Lock() defer as.mutex.Unlock() as.Deleted = true } // IsDeleted returns whether the spell is marked for deletion func (as *ActiveSpell) IsDeleted() bool { as.mutex.RLock() defer as.mutex.RUnlock() return as.Deleted } // SetInterrupted marks the spell as interrupted func (as *ActiveSpell) SetInterrupted(interrupted bool) { as.mutex.Lock() defer as.mutex.Unlock() as.Interrupted = interrupted } // IsInterrupted returns whether the spell was interrupted func (as *ActiveSpell) IsInterrupted() bool { as.mutex.RLock() defer as.mutex.RUnlock() return as.Interrupted } // SetResurrectValues sets HP and power restoration values for resurrect spells func (as *ActiveSpell) SetResurrectValues(hp, power float32) { as.mutex.Lock() defer as.mutex.Unlock() as.ResurrectHP = hp as.ResurrectPower = power } // GetResurrectValues returns HP and power restoration values func (as *ActiveSpell) GetResurrectValues() (float32, float32) { as.mutex.RLock() defer as.mutex.RUnlock() return as.ResurrectHP, as.ResurrectPower } // String returns a string representation of the ActiveSpell func (as *ActiveSpell) String() string { as.mutex.RLock() defer as.mutex.RUnlock() spellName := "Unknown" if as.Spell != nil { spellName = as.Spell.GetName() } return fmt.Sprintf("ActiveSpell[%s, Caster=%d, Targets=%d]", spellName, as.CasterID, len(as.Targets)) }