package player import ( "sort" "sync" "eq2emu/internal/spells" ) // AddSpellBookEntry adds a spell to the player's spell book func (p *Player) AddSpellBookEntry(spellID int32, tier int8, slot int32, spellType int32, timer int32, saveNeeded bool) { p.spellsBookMutex.Lock() defer p.spellsBookMutex.Unlock() // Check if spell already exists for _, entry := range p.spells { if entry.SpellID == spellID && entry.Tier == tier { // Update existing entry entry.Slot = slot entry.Type = spellType entry.Timer = timer entry.SaveNeeded = saveNeeded return } } // Create new entry entry := &SpellBookEntry{ SpellID: spellID, Tier: tier, Slot: slot, Type: spellType, Timer: timer, SaveNeeded: saveNeeded, Player: p, Visible: true, InUse: false, } p.spells = append(p.spells, entry) } // GetSpellBookSpell returns a spell book entry by spell ID func (p *Player) GetSpellBookSpell(spellID int32) *SpellBookEntry { p.spellsBookMutex.RLock() defer p.spellsBookMutex.RUnlock() for _, entry := range p.spells { if entry.SpellID == spellID { return entry } } return nil } // GetSpellsSaveNeeded returns spells that need saving to database func (p *Player) GetSpellsSaveNeeded() []*SpellBookEntry { p.spellsBookMutex.RLock() defer p.spellsBookMutex.RUnlock() var needSave []*SpellBookEntry for _, entry := range p.spells { if entry.SaveNeeded { needSave = append(needSave, entry) } } return needSave } // GetFreeSpellBookSlot returns the next free spell book slot for a type func (p *Player) GetFreeSpellBookSlot(spellType int32) int32 { p.spellsBookMutex.RLock() defer p.spellsBookMutex.RUnlock() // Find highest slot for this type var maxSlot int32 = -1 for _, entry := range p.spells { if entry.Type == spellType && entry.Slot > maxSlot { maxSlot = entry.Slot } } return maxSlot + 1 } // GetSpellBookSpellIDBySkill returns spell IDs for a given skill func (p *Player) GetSpellBookSpellIDBySkill(skillID int32) []int32 { p.spellsBookMutex.RLock() defer p.spellsBookMutex.RUnlock() var spellIDs []int32 for _, entry := range p.spells { // TODO: Check if spell matches skill // spell := master_spell_list.GetSpell(entry.SpellID) // if spell != nil && spell.GetSkillID() == skillID { // spellIDs = append(spellIDs, entry.SpellID) // } } return spellIDs } // HasSpell checks if player has a spell func (p *Player) HasSpell(spellID int32, tier int8, includeHigherTiers bool, includePossibleScribe bool) bool { p.spellsBookMutex.RLock() defer p.spellsBookMutex.RUnlock() for _, entry := range p.spells { if entry.SpellID == spellID { if tier == 255 || entry.Tier == tier { return true } if includeHigherTiers && entry.Tier > tier { return true } } } if includePossibleScribe { // TODO: Check if player can scribe this spell } return false } // GetSpellTier returns the tier of a spell the player has func (p *Player) GetSpellTier(spellID int32) int8 { p.spellsBookMutex.RLock() defer p.spellsBookMutex.RUnlock() var highestTier int8 = 0 for _, entry := range p.spells { if entry.SpellID == spellID && entry.Tier > highestTier { highestTier = entry.Tier } } return highestTier } // GetSpellSlot returns the slot of a spell func (p *Player) GetSpellSlot(spellID int32) int8 { entry := p.GetSpellBookSpell(spellID) if entry != nil { return int8(entry.Slot) } return -1 } // SetSpellStatus sets the status of a spell func (p *Player) SetSpellStatus(spell *spells.Spell, status int8) { if spell == nil { return } entry := p.GetSpellBookSpell(spell.GetSpellID()) if entry != nil { p.AddSpellStatus(entry, int16(status), true, 0) } } // RemoveSpellStatus removes a status from a spell func (p *Player) RemoveSpellStatus(spell *spells.Spell, status int8) { if spell == nil { return } entry := p.GetSpellBookSpell(spell.GetSpellID()) if entry != nil { p.RemoveSpellStatusEntry(entry, int16(status), true, 0) } } // AddSpellStatus adds a status to a spell entry func (p *Player) AddSpellStatus(spell *SpellBookEntry, value int16, modifyRecast bool, recast int16) { if spell == nil { return } p.spellsBookMutex.Lock() defer p.spellsBookMutex.Unlock() spell.Status |= int8(value) if modifyRecast { spell.Recast = recast spell.RecastAvailable = 0 // TODO: Calculate actual time } } // RemoveSpellStatusEntry removes a status from a spell entry func (p *Player) RemoveSpellStatusEntry(spell *SpellBookEntry, value int16, modifyRecast bool, recast int16) { if spell == nil { return } p.spellsBookMutex.Lock() defer p.spellsBookMutex.Unlock() spell.Status &= ^int8(value) if modifyRecast { spell.Recast = recast spell.RecastAvailable = 0 } } // RemoveSpellBookEntry removes a spell from the spell book func (p *Player) RemoveSpellBookEntry(spellID int32, removePassivesFromList bool) { p.spellsBookMutex.Lock() defer p.spellsBookMutex.Unlock() for i, entry := range p.spells { if entry.SpellID == spellID { // Remove from slice p.spells = append(p.spells[:i], p.spells[i+1:]...) if removePassivesFromList { // TODO: Remove from passive list p.RemovePassive(spellID, entry.Tier, true) } break } } } // DeleteSpellBook deletes spells from the spell book based on type func (p *Player) DeleteSpellBook(typeSelection int8) { p.spellsBookMutex.Lock() defer p.spellsBookMutex.Unlock() var keep []*SpellBookEntry for _, entry := range p.spells { deleteIt := false // Check type flags if typeSelection&DELETE_TRADESKILLS != 0 { // TODO: Check if tradeskill spell } if typeSelection&DELETE_SPELLS != 0 { // TODO: Check if spell } if typeSelection&DELETE_COMBAT_ART != 0 { // TODO: Check if combat art } if typeSelection&DELETE_ABILITY != 0 { // TODO: Check if ability } if typeSelection&DELETE_NOT_SHOWN != 0 && !entry.Visible { deleteIt = true } if !deleteIt { keep = append(keep, entry) } } p.spells = keep } // ResortSpellBook resorts the spell book func (p *Player) ResortSpellBook(sortBy, order, pattern, maxlvlOnly, bookType int32) { p.spellsBookMutex.Lock() defer p.spellsBookMutex.Unlock() // Filter spells based on criteria var filtered []*SpellBookEntry for _, entry := range p.spells { // TODO: Apply filters based on pattern, maxlvlOnly, bookType filtered = append(filtered, entry) } // Sort based on sortBy and order switch sortBy { case 0: // By name if order == 0 { sort.Slice(filtered, func(i, j int) bool { return SortSpellEntryByName(filtered[i], filtered[j]) }) } else { sort.Slice(filtered, func(i, j int) bool { return SortSpellEntryByNameReverse(filtered[i], filtered[j]) }) } case 1: // By level if order == 0 { sort.Slice(filtered, func(i, j int) bool { return SortSpellEntryByLevel(filtered[i], filtered[j]) }) } else { sort.Slice(filtered, func(i, j int) bool { return SortSpellEntryByLevelReverse(filtered[i], filtered[j]) }) } case 2: // By category if order == 0 { sort.Slice(filtered, func(i, j int) bool { return SortSpellEntryByCategory(filtered[i], filtered[j]) }) } else { sort.Slice(filtered, func(i, j int) bool { return SortSpellEntryByCategoryReverse(filtered[i], filtered[j]) }) } } // Reassign slots for i, entry := range filtered { entry.Slot = int32(i) } } // Spell sorting functions func SortSpellEntryByName(s1, s2 *SpellBookEntry) bool { // TODO: Get spell names and compare return s1.SpellID < s2.SpellID } func SortSpellEntryByNameReverse(s1, s2 *SpellBookEntry) bool { return !SortSpellEntryByName(s1, s2) } func SortSpellEntryByLevel(s1, s2 *SpellBookEntry) bool { // TODO: Get spell levels and compare return s1.Tier < s2.Tier } func SortSpellEntryByLevelReverse(s1, s2 *SpellBookEntry) bool { return !SortSpellEntryByLevel(s1, s2) } func SortSpellEntryByCategory(s1, s2 *SpellBookEntry) bool { // TODO: Get spell categories and compare return s1.Type < s2.Type } func SortSpellEntryByCategoryReverse(s1, s2 *SpellBookEntry) bool { return !SortSpellEntryByCategory(s1, s2) } // LockAllSpells locks all non-tradeskill spells func (p *Player) LockAllSpells() { p.spellsBookMutex.Lock() defer p.spellsBookMutex.Unlock() p.allSpellsLocked = true for _, entry := range p.spells { // TODO: Check if not tradeskill spell entry.Status |= SPELL_STATUS_LOCK } } // UnlockAllSpells unlocks all non-tradeskill spells func (p *Player) UnlockAllSpells(modifyRecast bool, exception *spells.Spell) { p.spellsBookMutex.Lock() defer p.spellsBookMutex.Unlock() p.allSpellsLocked = false exceptionID := int32(0) if exception != nil { exceptionID = exception.GetSpellID() } for _, entry := range p.spells { if entry.SpellID != exceptionID { // TODO: Check if not tradeskill spell entry.Status &= ^SPELL_STATUS_LOCK if modifyRecast { entry.RecastAvailable = 0 } } } } // LockSpell locks a spell and all linked spells func (p *Player) LockSpell(spell *spells.Spell, recast int16) { if spell == nil { return } // Lock the main spell entry := p.GetSpellBookSpell(spell.GetSpellID()) if entry != nil { p.AddSpellStatus(entry, SPELL_STATUS_LOCK, true, recast) } // TODO: Lock all spells with shared timer } // UnlockSpell unlocks a spell and all linked spells func (p *Player) UnlockSpell(spell *spells.Spell) { if spell == nil { return } p.UnlockSpellByID(spell.GetSpellID(), spell.GetSpellData().LinkedTimerID) } // UnlockSpellByID unlocks a spell by ID func (p *Player) UnlockSpellByID(spellID, linkedTimerID int32) { // Unlock the main spell entry := p.GetSpellBookSpell(spellID) if entry != nil { p.RemoveSpellStatusEntry(entry, SPELL_STATUS_LOCK, true, 0) } // TODO: Unlock all spells with shared timer if linkedTimerID > 0 { // Get all spells with this timer and unlock them } } // LockTSSpells locks tradeskill spells and unlocks combat spells func (p *Player) LockTSSpells() { p.spellsBookMutex.Lock() defer p.spellsBookMutex.Unlock() for _, entry := range p.spells { // TODO: Check if tradeskill spell // if spell.IsTradeskill() { // entry.Status |= SPELL_STATUS_LOCK // } else { // entry.Status &= ^SPELL_STATUS_LOCK // } } } // UnlockTSSpells unlocks tradeskill spells and locks combat spells func (p *Player) UnlockTSSpells() { p.spellsBookMutex.Lock() defer p.spellsBookMutex.Unlock() for _, entry := range p.spells { // TODO: Check if tradeskill spell // if spell.IsTradeskill() { // entry.Status &= ^SPELL_STATUS_LOCK // } else { // entry.Status |= SPELL_STATUS_LOCK // } } } // QueueSpell queues a spell for casting func (p *Player) QueueSpell(spell *spells.Spell) { if spell == nil { return } entry := p.GetSpellBookSpell(spell.GetSpellID()) if entry != nil { p.AddSpellStatus(entry, SPELL_STATUS_QUEUE, false, 0) } } // UnQueueSpell removes a spell from the queue func (p *Player) UnQueueSpell(spell *spells.Spell) { if spell == nil { return } entry := p.GetSpellBookSpell(spell.GetSpellID()) if entry != nil { p.RemoveSpellStatusEntry(entry, SPELL_STATUS_QUEUE, false, 0) } } // GetSpellBookSpellsByTimer returns all spells with a given timer func (p *Player) GetSpellBookSpellsByTimer(spell *spells.Spell, timerID int32) []*spells.Spell { var timerSpells []*spells.Spell p.spellsBookMutex.RLock() defer p.spellsBookMutex.RUnlock() // TODO: Find all spells with matching timer // for _, entry := range p.spells { // spell := master_spell_list.GetSpell(entry.SpellID) // if spell != nil && spell.GetTimerID() == timerID { // timerSpells = append(timerSpells, spell) // } // } return timerSpells } // AddPassiveSpell adds a passive spell func (p *Player) AddPassiveSpell(id int32, tier int8) { for _, spellID := range p.passiveSpells { if spellID == id { return // Already have it } } p.passiveSpells = append(p.passiveSpells, id) } // RemovePassive removes a passive spell func (p *Player) RemovePassive(id int32, tier int8, removeFromList bool) { // TODO: Remove passive effects if removeFromList { for i, spellID := range p.passiveSpells { if spellID == id { p.passiveSpells = append(p.passiveSpells[:i], p.passiveSpells[i+1:]...) break } } } } // ApplyPassiveSpells applies all passive spells func (p *Player) ApplyPassiveSpells() { // TODO: Cast all passive spells for _, spellID := range p.passiveSpells { // Get spell and cast it } } // RemoveAllPassives removes all passive spell effects func (p *Player) RemoveAllPassives() { // TODO: Remove all passive effects p.passiveSpells = nil } // GetSpellSlotMappingCount returns the number of spell slots func (p *Player) GetSpellSlotMappingCount() int16 { p.spellsBookMutex.RLock() defer p.spellsBookMutex.RUnlock() return int16(len(p.spells)) } // GetSpellPacketCount returns the spell packet count func (p *Player) GetSpellPacketCount() int16 { return p.spellCount } // AddMaintainedSpell adds a maintained spell effect func (p *Player) AddMaintainedSpell(luaSpell *spells.LuaSpell) { // TODO: Add to maintained effects } // RemoveMaintainedSpell removes a maintained spell effect func (p *Player) RemoveMaintainedSpell(luaSpell *spells.LuaSpell) { // TODO: Remove from maintained effects } // AddSpellEffect adds a spell effect func (p *Player) AddSpellEffect(luaSpell *spells.LuaSpell, overrideExpireTime int32) { // TODO: Add spell effect } // RemoveSpellEffect removes a spell effect func (p *Player) RemoveSpellEffect(luaSpell *spells.LuaSpell) { // TODO: Remove spell effect } // GetFreeMaintainedSpellSlot returns a free maintained spell slot func (p *Player) GetFreeMaintainedSpellSlot() *spells.MaintainedEffects { // TODO: Find free slot in maintained effects return nil } // GetMaintainedSpell returns a maintained spell by ID func (p *Player) GetMaintainedSpell(id int32, onCharLoad bool) *spells.MaintainedEffects { // TODO: Find maintained spell return nil } // GetMaintainedSpellBySlot returns a maintained spell by slot func (p *Player) GetMaintainedSpellBySlot(slot int8) *spells.MaintainedEffects { // TODO: Find maintained spell by slot return nil } // GetMaintainedSpells returns all maintained spells func (p *Player) GetMaintainedSpells() *spells.MaintainedEffects { // TODO: Return maintained effects array return nil } // GetFreeSpellEffectSlot returns a free spell effect slot func (p *Player) GetFreeSpellEffectSlot() *spells.SpellEffects { // TODO: Find free slot in spell effects return nil } // GetSpellEffects returns all spell effects func (p *Player) GetSpellEffects() *spells.SpellEffects { // TODO: Return spell effects array return nil } // SaveSpellEffects saves spell effects to database func (p *Player) SaveSpellEffects() { if p.stopSaveSpellEffects { return } // TODO: Save spell effects to database } // GetTierUp returns the next tier for a given tier func (p *Player) GetTierUp(tier int16) int16 { switch tier { case 0: return 1 case 1: return 2 case 2: return 3 case 3: return 4 case 4: return 5 case 5: return 6 case 6: return 7 case 7: return 8 case 8: return 9 case 9: return 10 default: return tier + 1 } }