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 luaData []*LUAData // LUA 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), luaData: make([]*LUAData, 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), luaData: make([]*LUAData, 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 LUA data for _, lua := range hostSpell.luaData { newLua := &LUAData{ Type: lua.Type, IntValue: lua.IntValue, BoolValue: lua.BoolValue, FloatValue: lua.FloatValue, StringValue: lua.StringValue, StringValue2: lua.StringValue2, IntValue2: lua.IntValue2, FloatValue2: lua.FloatValue2, StringHelper: lua.StringHelper, NeedsDBSave: lua.NeedsDBSave, } s.luaData = append(s.luaData, newLua) } // 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) } // AddSpellLuaData adds LUA data to the spell func (s *Spell) AddSpellLuaData(dataType int8, intValue, intValue2 int32, floatValue, floatValue2 float32, boolValue bool, stringValue, stringValue2, helper string) { s.mutex.Lock() defer s.mutex.Unlock() luaData := &LUAData{ Type: dataType, IntValue: intValue, BoolValue: boolValue, FloatValue: floatValue, StringValue: stringValue, StringValue2: stringValue2, IntValue2: intValue2, FloatValue2: floatValue2, StringHelper: helper, NeedsDBSave: true, } s.luaData = append(s.luaData, luaData) } // Convenience methods for adding specific LUA data types // AddSpellLuaDataInt adds integer LUA data func (s *Spell) AddSpellLuaDataInt(value, value2 int32, helper string) { s.AddSpellLuaData(SpellLUADataTypeInt, value, value2, 0.0, 0.0, false, "", "", helper) } // AddSpellLuaDataFloat adds float LUA data func (s *Spell) AddSpellLuaDataFloat(value, value2 float32, helper string) { s.AddSpellLuaData(SpellLUADataTypeFloat, 0, 0, value, value2, false, "", "", helper) } // AddSpellLuaDataBool adds boolean LUA data func (s *Spell) AddSpellLuaDataBool(value bool, helper string) { s.AddSpellLuaData(SpellLUADataTypeBool, 0, 0, 0.0, 0.0, value, "", "", helper) } // AddSpellLuaDataString adds string LUA data func (s *Spell) AddSpellLuaDataString(value, value2, helper string) { s.AddSpellLuaData(SpellLUADataTypeString, 0, 0, 0.0, 0.0, false, value, value2, helper) } // 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] } // GetLUAData returns the spell's LUA data func (s *Spell) GetLUAData() []*LUAData { s.mutex.RLock() defer s.mutex.RUnlock() // Return a copy to prevent external modification luaData := make([]*LUAData, len(s.luaData)) copy(luaData, s.luaData) return luaData } // 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()) } // LuaSpell represents an active spell instance with runtime state // This is converted from the C++ LuaSpell class type LuaSpell 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 } // NewLuaSpell creates a new LuaSpell instance func NewLuaSpell(spell *Spell, casterID int32) *LuaSpell { return &LuaSpell{ 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 (ls *LuaSpell) GetTargets() []int32 { ls.mutex.RLock() defer ls.mutex.RUnlock() targets := make([]int32, len(ls.Targets)) copy(targets, ls.Targets) return targets } // AddTarget adds a target to the spell func (ls *LuaSpell) AddTarget(targetID int32) { ls.mutex.Lock() defer ls.mutex.Unlock() // Check if target already exists for _, id := range ls.Targets { if id == targetID { return } } ls.Targets = append(ls.Targets, targetID) } // RemoveTarget removes a target from the spell func (ls *LuaSpell) RemoveTarget(targetID int32) bool { ls.mutex.Lock() defer ls.mutex.Unlock() for i, id := range ls.Targets { if id == targetID { ls.Targets = append(ls.Targets[:i], ls.Targets[i+1:]...) return true } } return false } // HasTarget checks if the spell targets a specific entity func (ls *LuaSpell) HasTarget(targetID int32) bool { ls.mutex.RLock() defer ls.mutex.RUnlock() for _, id := range ls.Targets { if id == targetID { return true } } return false } // GetTargetCount returns the number of targets func (ls *LuaSpell) GetTargetCount() int { ls.mutex.RLock() defer ls.mutex.RUnlock() return len(ls.Targets) } // ClearTargets removes all targets from the spell func (ls *LuaSpell) ClearTargets() { ls.mutex.Lock() defer ls.mutex.Unlock() ls.Targets = ls.Targets[:0] } // SetCustomFunction sets a custom LUA function for this spell instance func (ls *LuaSpell) SetCustomFunction(functionName string) { ls.mutex.Lock() defer ls.mutex.Unlock() ls.CustomFunction = functionName } // GetCustomFunction returns the custom LUA function name func (ls *LuaSpell) GetCustomFunction() string { ls.mutex.RLock() defer ls.mutex.RUnlock() return ls.CustomFunction } // MarkForDeletion marks the spell for removal func (ls *LuaSpell) MarkForDeletion() { ls.mutex.Lock() defer ls.mutex.Unlock() ls.Deleted = true } // IsDeleted returns whether the spell is marked for deletion func (ls *LuaSpell) IsDeleted() bool { ls.mutex.RLock() defer ls.mutex.RUnlock() return ls.Deleted } // SetInterrupted marks the spell as interrupted func (ls *LuaSpell) SetInterrupted(interrupted bool) { ls.mutex.Lock() defer ls.mutex.Unlock() ls.Interrupted = interrupted } // IsInterrupted returns whether the spell was interrupted func (ls *LuaSpell) IsInterrupted() bool { ls.mutex.RLock() defer ls.mutex.RUnlock() return ls.Interrupted } // SetResurrectValues sets HP and power restoration values for resurrect spells func (ls *LuaSpell) SetResurrectValues(hp, power float32) { ls.mutex.Lock() defer ls.mutex.Unlock() ls.ResurrectHP = hp ls.ResurrectPower = power } // GetResurrectValues returns HP and power restoration values func (ls *LuaSpell) GetResurrectValues() (float32, float32) { ls.mutex.RLock() defer ls.mutex.RUnlock() return ls.ResurrectHP, ls.ResurrectPower } // String returns a string representation of the LuaSpell func (ls *LuaSpell) String() string { ls.mutex.RLock() defer ls.mutex.RUnlock() spellName := "Unknown" if ls.Spell != nil { spellName = ls.Spell.GetName() } return fmt.Sprintf("LuaSpell[%s, Caster=%d, Targets=%d]", spellName, ls.CasterID, len(ls.Targets)) }