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]any { spell := sm.masterList.GetSpell(spellID) if spell == nil { return nil } info := make(map[string]any) // 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) }