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))
}