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