656 lines
17 KiB
Go
656 lines
17 KiB
Go
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))
|
|
}
|