597 lines
16 KiB
Go
597 lines
16 KiB
Go
package spells
|
|
|
|
import (
|
|
"fmt"
|
|
"sync"
|
|
"sync/atomic"
|
|
)
|
|
|
|
// SpellScriptTimer represents a timer for spell script execution
|
|
type SpellScriptTimer struct {
|
|
// TODO: Add LuaSpell reference when implemented
|
|
// Spell *LuaSpell // The spell being timed
|
|
SpellID int32 // Spell ID for identification
|
|
CustomFunction string // Custom function to call
|
|
Time int32 // Timer duration
|
|
Caster int32 // Caster entity ID
|
|
Target int32 // Target entity ID
|
|
DeleteWhenDone bool // Whether to delete timer when finished
|
|
}
|
|
|
|
// MasterSpellList manages all spells in the game
|
|
// This replaces the C++ MasterSpellList functionality
|
|
type MasterSpellList struct {
|
|
// Spell storage
|
|
spells map[int32]*Spell // Spells by ID
|
|
spellsByName map[string]*Spell // Spells by name for lookup
|
|
spellsByTier map[int32]map[int8]*Spell // Spells by ID and tier
|
|
|
|
// ID management
|
|
maxSpellID int32 // Highest assigned spell ID
|
|
|
|
// Thread safety
|
|
mutex sync.RWMutex
|
|
}
|
|
|
|
// NewMasterSpellList creates a new master spell list
|
|
func NewMasterSpellList() *MasterSpellList {
|
|
return &MasterSpellList{
|
|
spells: make(map[int32]*Spell),
|
|
spellsByName: make(map[string]*Spell),
|
|
spellsByTier: make(map[int32]map[int8]*Spell),
|
|
maxSpellID: 0,
|
|
}
|
|
}
|
|
|
|
// AddSpell adds a spell to the master list
|
|
func (msl *MasterSpellList) AddSpell(spell *Spell) bool {
|
|
if spell == nil {
|
|
return false
|
|
}
|
|
|
|
msl.mutex.Lock()
|
|
defer msl.mutex.Unlock()
|
|
|
|
spellID := spell.GetSpellID()
|
|
|
|
// Update max spell ID
|
|
if spellID > msl.maxSpellID {
|
|
msl.maxSpellID = spellID
|
|
}
|
|
|
|
// Add to main spell map
|
|
msl.spells[spellID] = spell
|
|
|
|
// Add to name lookup
|
|
name := spell.GetName()
|
|
if name != "" {
|
|
msl.spellsByName[name] = spell
|
|
}
|
|
|
|
// Add to tier lookup
|
|
tier := spell.GetSpellTier()
|
|
if msl.spellsByTier[spellID] == nil {
|
|
msl.spellsByTier[spellID] = make(map[int8]*Spell)
|
|
}
|
|
msl.spellsByTier[spellID][tier] = spell
|
|
|
|
return true
|
|
}
|
|
|
|
// GetSpell retrieves a spell by ID
|
|
func (msl *MasterSpellList) GetSpell(spellID int32) *Spell {
|
|
msl.mutex.RLock()
|
|
defer msl.mutex.RUnlock()
|
|
|
|
return msl.spells[spellID]
|
|
}
|
|
|
|
// GetSpellByName retrieves a spell by name
|
|
func (msl *MasterSpellList) GetSpellByName(name string) *Spell {
|
|
msl.mutex.RLock()
|
|
defer msl.mutex.RUnlock()
|
|
|
|
return msl.spellsByName[name]
|
|
}
|
|
|
|
// GetSpellByIDAndTier retrieves a specific tier of a spell
|
|
func (msl *MasterSpellList) GetSpellByIDAndTier(spellID int32, tier int8) *Spell {
|
|
msl.mutex.RLock()
|
|
defer msl.mutex.RUnlock()
|
|
|
|
if tierMap, exists := msl.spellsByTier[spellID]; exists {
|
|
return tierMap[tier]
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// RemoveSpell removes a spell from the master list
|
|
func (msl *MasterSpellList) RemoveSpell(spellID int32) bool {
|
|
msl.mutex.Lock()
|
|
defer msl.mutex.Unlock()
|
|
|
|
spell, exists := msl.spells[spellID]
|
|
if !exists {
|
|
return false
|
|
}
|
|
|
|
// Remove from main map
|
|
delete(msl.spells, spellID)
|
|
|
|
// Remove from name lookup
|
|
name := spell.GetName()
|
|
if name != "" {
|
|
delete(msl.spellsByName, name)
|
|
}
|
|
|
|
// Remove from tier lookup
|
|
delete(msl.spellsByTier, spellID)
|
|
|
|
return true
|
|
}
|
|
|
|
// GetNewMaxSpellID returns the next available spell ID
|
|
func (msl *MasterSpellList) GetNewMaxSpellID() int32 {
|
|
return atomic.AddInt32(&msl.maxSpellID, 1)
|
|
}
|
|
|
|
// GetSpellCount returns the total number of spells
|
|
func (msl *MasterSpellList) GetSpellCount() int {
|
|
msl.mutex.RLock()
|
|
defer msl.mutex.RUnlock()
|
|
|
|
return len(msl.spells)
|
|
}
|
|
|
|
// GetAllSpells returns a copy of all spells (expensive operation)
|
|
func (msl *MasterSpellList) GetAllSpells() []*Spell {
|
|
msl.mutex.RLock()
|
|
defer msl.mutex.RUnlock()
|
|
|
|
spells := make([]*Spell, 0, len(msl.spells))
|
|
for _, spell := range msl.spells {
|
|
spells = append(spells, spell)
|
|
}
|
|
|
|
return spells
|
|
}
|
|
|
|
// GetSpellsByType returns all spells of a specific type
|
|
func (msl *MasterSpellList) GetSpellsByType(spellType int16) []*Spell {
|
|
msl.mutex.RLock()
|
|
defer msl.mutex.RUnlock()
|
|
|
|
spells := make([]*Spell, 0)
|
|
for _, spell := range msl.spells {
|
|
if spell.GetSpellData().Type == spellType {
|
|
spells = append(spells, spell)
|
|
}
|
|
}
|
|
|
|
return spells
|
|
}
|
|
|
|
// GetSpellsByBookType returns all spells of a specific book type
|
|
func (msl *MasterSpellList) GetSpellsByBookType(bookType int32) []*Spell {
|
|
msl.mutex.RLock()
|
|
defer msl.mutex.RUnlock()
|
|
|
|
spells := make([]*Spell, 0)
|
|
for _, spell := range msl.spells {
|
|
if spell.GetSpellData().SpellBookType == bookType {
|
|
spells = append(spells, spell)
|
|
}
|
|
}
|
|
|
|
return spells
|
|
}
|
|
|
|
// Global spell list instance
|
|
var masterSpellList *MasterSpellList
|
|
var initSpellListOnce sync.Once
|
|
|
|
// GetMasterSpellList returns the global master spell list (singleton)
|
|
func GetMasterSpellList() *MasterSpellList {
|
|
initSpellListOnce.Do(func() {
|
|
masterSpellList = NewMasterSpellList()
|
|
})
|
|
return masterSpellList
|
|
}
|
|
|
|
// SpellCasting represents an active spell casting attempt
|
|
type SpellCasting struct {
|
|
Spell *Spell // The spell being cast
|
|
Caster int32 // Caster entity ID
|
|
Target int32 // Target entity ID
|
|
CastTime int32 // Total cast time
|
|
TimeRemaining int32 // Time remaining in cast
|
|
Interrupted bool // Whether casting was interrupted
|
|
// TODO: Add Entity references when implemented
|
|
// CasterEntity *Entity
|
|
// TargetEntity *Entity
|
|
}
|
|
|
|
// SpellBook represents a character's spell book
|
|
type SpellBook struct {
|
|
// Spell storage organized by type
|
|
spells map[int32]*Spell // All known spells by ID
|
|
spellsByType map[int32][]*Spell // Spells organized by book type
|
|
|
|
// Spell bar/hotbar assignments
|
|
spellBars map[int8]map[int8]*Spell // [bar][slot] = spell
|
|
|
|
// Thread safety
|
|
mutex sync.RWMutex
|
|
}
|
|
|
|
// NewSpellBook creates a new spell book
|
|
func NewSpellBook() *SpellBook {
|
|
return &SpellBook{
|
|
spells: make(map[int32]*Spell),
|
|
spellsByType: make(map[int32][]*Spell),
|
|
spellBars: make(map[int8]map[int8]*Spell),
|
|
}
|
|
}
|
|
|
|
// AddSpell adds a spell to the spell book
|
|
func (sb *SpellBook) AddSpell(spell *Spell) bool {
|
|
if spell == nil {
|
|
return false
|
|
}
|
|
|
|
sb.mutex.Lock()
|
|
defer sb.mutex.Unlock()
|
|
|
|
spellID := spell.GetSpellID()
|
|
|
|
// Check if spell already exists
|
|
if _, exists := sb.spells[spellID]; exists {
|
|
return false
|
|
}
|
|
|
|
// Add to main collection
|
|
sb.spells[spellID] = spell
|
|
|
|
// Add to type collection
|
|
bookType := spell.GetSpellData().SpellBookType
|
|
if sb.spellsByType[bookType] == nil {
|
|
sb.spellsByType[bookType] = make([]*Spell, 0)
|
|
}
|
|
sb.spellsByType[bookType] = append(sb.spellsByType[bookType], spell)
|
|
|
|
return true
|
|
}
|
|
|
|
// RemoveSpell removes a spell from the spell book
|
|
func (sb *SpellBook) RemoveSpell(spellID int32) bool {
|
|
sb.mutex.Lock()
|
|
defer sb.mutex.Unlock()
|
|
|
|
spell, exists := sb.spells[spellID]
|
|
if !exists {
|
|
return false
|
|
}
|
|
|
|
// Remove from main collection
|
|
delete(sb.spells, spellID)
|
|
|
|
// Remove from type collection
|
|
bookType := spell.GetSpellData().SpellBookType
|
|
if spells, exists := sb.spellsByType[bookType]; exists {
|
|
for i, s := range spells {
|
|
if s.GetSpellID() == spellID {
|
|
sb.spellsByType[bookType] = append(spells[:i], spells[i+1:]...)
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
// Remove from spell bars
|
|
for barID, bar := range sb.spellBars {
|
|
for slot, s := range bar {
|
|
if s != nil && s.GetSpellID() == spellID {
|
|
delete(sb.spellBars[barID], slot)
|
|
}
|
|
}
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
// GetSpell retrieves a spell from the spell book
|
|
func (sb *SpellBook) GetSpell(spellID int32) *Spell {
|
|
sb.mutex.RLock()
|
|
defer sb.mutex.RUnlock()
|
|
|
|
return sb.spells[spellID]
|
|
}
|
|
|
|
// HasSpell checks if the spell book contains a specific spell
|
|
func (sb *SpellBook) HasSpell(spellID int32) bool {
|
|
sb.mutex.RLock()
|
|
defer sb.mutex.RUnlock()
|
|
|
|
_, exists := sb.spells[spellID]
|
|
return exists
|
|
}
|
|
|
|
// GetSpellsByType returns all spells of a specific book type
|
|
func (sb *SpellBook) GetSpellsByType(bookType int32) []*Spell {
|
|
sb.mutex.RLock()
|
|
defer sb.mutex.RUnlock()
|
|
|
|
if spells, exists := sb.spellsByType[bookType]; exists {
|
|
// Return a copy to prevent external modification
|
|
result := make([]*Spell, len(spells))
|
|
copy(result, spells)
|
|
return result
|
|
}
|
|
|
|
return make([]*Spell, 0)
|
|
}
|
|
|
|
// SetSpellBarSlot assigns a spell to a specific bar and slot
|
|
func (sb *SpellBook) SetSpellBarSlot(barID, slot int8, spell *Spell) bool {
|
|
sb.mutex.Lock()
|
|
defer sb.mutex.Unlock()
|
|
|
|
// Ensure the spell is in the spell book
|
|
if spell != nil {
|
|
if _, exists := sb.spells[spell.GetSpellID()]; !exists {
|
|
return false
|
|
}
|
|
}
|
|
|
|
// Initialize bar if needed
|
|
if sb.spellBars[barID] == nil {
|
|
sb.spellBars[barID] = make(map[int8]*Spell)
|
|
}
|
|
|
|
// Set the slot
|
|
if spell == nil {
|
|
delete(sb.spellBars[barID], slot)
|
|
} else {
|
|
sb.spellBars[barID][slot] = spell
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
// GetSpellBarSlot retrieves the spell at a specific bar and slot
|
|
func (sb *SpellBook) GetSpellBarSlot(barID, slot int8) *Spell {
|
|
sb.mutex.RLock()
|
|
defer sb.mutex.RUnlock()
|
|
|
|
if bar, exists := sb.spellBars[barID]; exists {
|
|
return bar[slot]
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// GetSpellCount returns the total number of spells in the book
|
|
func (sb *SpellBook) GetSpellCount() int {
|
|
sb.mutex.RLock()
|
|
defer sb.mutex.RUnlock()
|
|
|
|
return len(sb.spells)
|
|
}
|
|
|
|
// String returns a string representation of the spell book
|
|
func (sb *SpellBook) String() string {
|
|
return fmt.Sprintf("SpellBook[Spells=%d, Types=%d]",
|
|
sb.GetSpellCount(), len(sb.spellsByType))
|
|
}
|
|
|
|
// SpellManager manages the master spell list, player spell books, and spell processing
|
|
type SpellManager struct {
|
|
masterList *MasterSpellList // Global spell definitions
|
|
spellBooks map[int32]*SpellBook // Player spell books by character ID
|
|
spellProcess *SpellProcess // Spell processing system
|
|
targeting *SpellTargeting // Spell targeting system
|
|
resourceChecker *SpellResourceChecker // Resource checking system
|
|
mutex sync.RWMutex // Thread safety
|
|
}
|
|
|
|
// NewSpellManager creates a new spell manager
|
|
func NewSpellManager() *SpellManager {
|
|
return &SpellManager{
|
|
masterList: NewMasterSpellList(),
|
|
spellBooks: make(map[int32]*SpellBook),
|
|
spellProcess: NewSpellProcess(),
|
|
targeting: NewSpellTargeting(),
|
|
resourceChecker: NewSpellResourceChecker(),
|
|
}
|
|
}
|
|
|
|
// GetMasterList returns the master spell list
|
|
func (sm *SpellManager) GetMasterList() *MasterSpellList {
|
|
return sm.masterList
|
|
}
|
|
|
|
// GetSpellProcess returns the spell processing system
|
|
func (sm *SpellManager) GetSpellProcess() *SpellProcess {
|
|
return sm.spellProcess
|
|
}
|
|
|
|
// GetTargeting returns the spell targeting system
|
|
func (sm *SpellManager) GetTargeting() *SpellTargeting {
|
|
return sm.targeting
|
|
}
|
|
|
|
// GetResourceChecker returns the resource checking system
|
|
func (sm *SpellManager) GetResourceChecker() *SpellResourceChecker {
|
|
return sm.resourceChecker
|
|
}
|
|
|
|
// GetSpellBook retrieves or creates a spell book for a character
|
|
func (sm *SpellManager) GetSpellBook(characterID int32) *SpellBook {
|
|
sm.mutex.Lock()
|
|
defer sm.mutex.Unlock()
|
|
|
|
if book, exists := sm.spellBooks[characterID]; exists {
|
|
return book
|
|
}
|
|
|
|
// Create new spell book
|
|
book := NewSpellBook()
|
|
sm.spellBooks[characterID] = book
|
|
return book
|
|
}
|
|
|
|
// RemoveSpellBook removes a character's spell book (when they log out)
|
|
func (sm *SpellManager) RemoveSpellBook(characterID int32) {
|
|
sm.mutex.Lock()
|
|
defer sm.mutex.Unlock()
|
|
|
|
delete(sm.spellBooks, characterID)
|
|
}
|
|
|
|
// ProcessSpells processes all active spells (should be called regularly)
|
|
func (sm *SpellManager) ProcessSpells() {
|
|
sm.spellProcess.Process()
|
|
}
|
|
|
|
// CastSpell attempts to cast a spell for a character
|
|
func (sm *SpellManager) CastSpell(casterID, targetID, spellID int32) error {
|
|
// Get the spell from master list
|
|
spell := sm.masterList.GetSpell(spellID)
|
|
if spell == nil {
|
|
return fmt.Errorf("spell %d not found", spellID)
|
|
}
|
|
|
|
// Create LuaSpell instance
|
|
luaSpell := NewLuaSpell(spell, casterID)
|
|
luaSpell.InitialTarget = targetID
|
|
|
|
// Check resources
|
|
results := sm.resourceChecker.CheckAllResources(luaSpell, 0, 0)
|
|
for _, result := range results {
|
|
if !result.HasSufficient {
|
|
return fmt.Errorf("insufficient resources: %s", result.ErrorMessage)
|
|
}
|
|
}
|
|
|
|
// Get targets
|
|
targetResult := sm.targeting.GetSpellTargets(luaSpell, nil)
|
|
if targetResult.ErrorCode != 0 {
|
|
return fmt.Errorf("targeting failed: %s", targetResult.ErrorMessage)
|
|
}
|
|
|
|
if len(targetResult.ValidTargets) == 0 {
|
|
return fmt.Errorf("no valid targets found")
|
|
}
|
|
|
|
// TODO: Add spell to cast queue or process immediately
|
|
// This would integrate with the entity system to actually cast the spell
|
|
|
|
return nil
|
|
}
|
|
|
|
// InterruptSpell interrupts a spell being cast by an entity
|
|
func (sm *SpellManager) InterruptSpell(entityID, spellID int32, errorCode int16, cancel, fromMovement bool) {
|
|
sm.spellProcess.Interrupt(entityID, spellID, errorCode, cancel, fromMovement)
|
|
}
|
|
|
|
// AddSpellToQueue adds a spell to a player's casting queue
|
|
func (sm *SpellManager) AddSpellToQueue(spellID, casterID, targetID int32, priority int32) {
|
|
sm.spellProcess.AddSpellToQueue(spellID, casterID, targetID, priority)
|
|
}
|
|
|
|
// RemoveSpellFromQueue removes a spell from a player's casting queue
|
|
func (sm *SpellManager) RemoveSpellFromQueue(spellID, casterID int32) bool {
|
|
return sm.spellProcess.RemoveSpellFromQueue(spellID, casterID)
|
|
}
|
|
|
|
// IsSpellReady checks if a spell is ready to cast (not on cooldown)
|
|
func (sm *SpellManager) IsSpellReady(spellID, casterID int32) bool {
|
|
return sm.spellProcess.IsReady(spellID, casterID)
|
|
}
|
|
|
|
// GetSpellRecastTime returns remaining recast time for a spell
|
|
func (sm *SpellManager) GetSpellRecastTime(spellID, casterID int32) int32 {
|
|
remaining := sm.spellProcess.GetRecastTimeRemaining(spellID, casterID)
|
|
return int32(remaining.Milliseconds())
|
|
}
|
|
|
|
// CanCastSpell performs comprehensive checks to determine if a spell can be cast
|
|
func (sm *SpellManager) CanCastSpell(casterID, targetID, spellID int32) (bool, string) {
|
|
// Check if spell exists
|
|
spell := sm.masterList.GetSpell(spellID)
|
|
if spell == nil {
|
|
return false, "Spell not found"
|
|
}
|
|
|
|
// Check if spell is ready (not on cooldown)
|
|
if !sm.spellProcess.IsReady(spellID, casterID) {
|
|
remaining := sm.spellProcess.GetRecastTimeRemaining(spellID, casterID)
|
|
return false, fmt.Sprintf("Spell on cooldown for %d seconds", int(remaining.Seconds()))
|
|
}
|
|
|
|
// Create temporary LuaSpell for resource checks
|
|
luaSpell := NewLuaSpell(spell, casterID)
|
|
luaSpell.InitialTarget = targetID
|
|
|
|
// Check resources
|
|
results := sm.resourceChecker.CheckAllResources(luaSpell, 0, 0)
|
|
for _, result := range results {
|
|
if !result.HasSufficient {
|
|
return false, result.ErrorMessage
|
|
}
|
|
}
|
|
|
|
// Check targeting
|
|
targetResult := sm.targeting.GetSpellTargets(luaSpell, nil)
|
|
if targetResult.ErrorCode != 0 {
|
|
return false, targetResult.ErrorMessage
|
|
}
|
|
|
|
if len(targetResult.ValidTargets) == 0 {
|
|
return false, "No valid targets"
|
|
}
|
|
|
|
return true, ""
|
|
}
|
|
|
|
// GetSpellInfo returns comprehensive information about a spell
|
|
func (sm *SpellManager) GetSpellInfo(spellID int32) map[string]interface{} {
|
|
spell := sm.masterList.GetSpell(spellID)
|
|
if spell == nil {
|
|
return nil
|
|
}
|
|
|
|
info := make(map[string]interface{})
|
|
|
|
// Basic spell info
|
|
info["id"] = spell.GetSpellID()
|
|
info["name"] = spell.GetName()
|
|
info["description"] = spell.GetDescription()
|
|
info["tier"] = spell.GetSpellTier()
|
|
info["type"] = spell.GetSpellData().SpellBookType
|
|
|
|
// Resource requirements
|
|
info["resources"] = sm.resourceChecker.GetResourceSummary(spell)
|
|
|
|
// Targeting info
|
|
info["targeting"] = sm.targeting.GetTargetingInfo(spell)
|
|
|
|
// Classification
|
|
info["is_buff"] = spell.IsBuffSpell()
|
|
info["is_debuff"] = !spell.IsBuffSpell() && spell.IsControlSpell()
|
|
info["is_heal"] = spell.IsHealSpell()
|
|
info["is_damage"] = spell.IsDamageSpell()
|
|
info["is_offensive"] = spell.IsOffenseSpell()
|
|
|
|
return info
|
|
}
|
|
|
|
// GetActiveSpellCount returns the number of active spells being processed
|
|
func (sm *SpellManager) GetActiveSpellCount() int {
|
|
return sm.spellProcess.GetActiveSpellCount()
|
|
}
|
|
|
|
// Shutdown gracefully shuts down the spell manager
|
|
func (sm *SpellManager) Shutdown() {
|
|
sm.spellProcess.RemoveAllSpells(false)
|
|
} |