176 lines
6.8 KiB
Lua
176 lines
6.8 KiB
Lua
-- TotemBar - core/spellindex.lua
|
|
-- A single cached name -> spellbook-slot index, replacing the four
|
|
-- separate linear GetSpellName(i, BOOKTYPE_SPELL) scans that used to be
|
|
-- duplicated across ui.lua (FindSpellIndexByName), core/cast.lua
|
|
-- (findSpellIndexByName), core/manacost.lua (findHighestRankSlot) and
|
|
-- core/known.lua (scanSpellbook/highestKnownRank). Each of those walked
|
|
-- the WHOLE spellbook on every call; SPELL_UPDATE_COOLDOWN/cast events
|
|
-- fire several of them per totem drop, so a 4-totem-drop macro press
|
|
-- could re-walk the book several times in one frame (perf review
|
|
-- 2026-07-16, TotemBar finding #14: ui.lua:210 + cast.lua:175 +
|
|
-- manacost.lua:116).
|
|
--
|
|
-- buildSpellIndex() below is PURE (no WoW API) and offline-tested: given
|
|
-- a flat array of {name=, rank=} entries in spellbook slot order (array
|
|
-- index N == what GetSpellName(N, BOOKTYPE_SPELL) returned for slot N),
|
|
-- it returns a name -> record table where each record is:
|
|
-- bookIndex - the FIRST (lowest) slot with this name. Mirrors the
|
|
-- old FindSpellIndexByName/findSpellIndexByName
|
|
-- first-match behavior (icon/cooldown/"is it known"
|
|
-- lookups don't care which rank's slot they read --
|
|
-- GetSpellTexture/GetSpellCooldown are identical across
|
|
-- ranks of the same totem).
|
|
-- maxRankIndex - the slot of the HIGHEST parsed rank. Mirrors the old
|
|
-- manacost.lua findHighestRankSlot: this is the slot
|
|
-- CastSpellByName actually casts and the one
|
|
-- tooltip/mana-cost scans must read. Falls back to the
|
|
-- LAST matching slot if no entry's rank string for this
|
|
-- name ever parsed a number (matches the old
|
|
-- fallback -- ranks are listed ascending, so the last
|
|
-- slot is still the best guess).
|
|
-- maxRank - the highest parsed rank NUMBER, or nil if none of
|
|
-- this name's entries carried a parseable rank. Mirrors
|
|
-- the old known.lua highestKnownRank() return value.
|
|
|
|
TotemBar = TotemBar or {}
|
|
|
|
function TotemBar.buildSpellIndex(entries)
|
|
local idx = {}
|
|
if not entries then
|
|
return idx
|
|
end
|
|
for i = 1, table.getn(entries) do
|
|
local e = entries[i]
|
|
local name = e and e.name
|
|
if name then
|
|
local rec = idx[name]
|
|
if not rec then
|
|
rec = { bookIndex = i, maxRankIndex = i, maxRank = nil }
|
|
idx[name] = rec
|
|
end
|
|
local _, _, numStr = string.find(e.rank or "", "(%d+)")
|
|
local num = numStr and tonumber(numStr)
|
|
if num and (not rec.maxRank or num > rec.maxRank) then
|
|
rec.maxRank = num
|
|
rec.maxRankIndex = i
|
|
elseif not rec.maxRank then
|
|
-- No rank number has parsed for this name in any entry
|
|
-- seen so far; keep sliding the fallback to the LAST
|
|
-- matching slot (mirrors the old findHighestRankSlot
|
|
-- fallback for names whose rank string never parses).
|
|
rec.maxRankIndex = i
|
|
end
|
|
end
|
|
end
|
|
return idx
|
|
end
|
|
|
|
-- ===== WoW-API layer (not offline-executed) =====
|
|
-- Guarded by CreateFrame's presence, same as manacost.lua's own event
|
|
-- block, so dofile-ing this module under plain Lua (offline tests)
|
|
-- never errors.
|
|
|
|
local cachedEntries = nil -- flat array {name=, rank=}, 1..n, book order
|
|
local cachedIndex = nil -- name -> {bookIndex, maxRankIndex, maxRank}
|
|
|
|
local function scanLiveSpellbook()
|
|
local entries = {}
|
|
local i = 1
|
|
while true do
|
|
local name, rank = GetSpellName(i, BOOKTYPE_SPELL)
|
|
if not name then
|
|
break
|
|
end
|
|
entries[i] = { name = name, rank = rank }
|
|
i = i + 1
|
|
end
|
|
return entries
|
|
end
|
|
|
|
local function ensureBuilt()
|
|
if cachedIndex then
|
|
return
|
|
end
|
|
cachedEntries = scanLiveSpellbook()
|
|
cachedIndex = TotemBar.buildSpellIndex(cachedEntries)
|
|
end
|
|
|
|
-- Drops the cache; the next accessor call below rebuilds it from a
|
|
-- fresh scan (lazy -- nothing re-scans until actually asked for). Wired
|
|
-- to SPELLS_CHANGED below; also safe to call manually.
|
|
function TotemBar.invalidateSpellIndex()
|
|
cachedEntries = nil
|
|
cachedIndex = nil
|
|
end
|
|
|
|
-- Returns the cached name -> record index, building it lazily on first
|
|
-- access. Lazy-build matters at login/reload: the spellbook
|
|
-- (GetSpellName/GetSpellTexture) isn't reliably populated as early as
|
|
-- ADDON_LOADED fires (see ui.lua's own SPELLS_CHANGED comment) -- an
|
|
-- eager build at file-load time could cache an empty book.
|
|
function TotemBar.getSpellIndex()
|
|
ensureBuilt()
|
|
return cachedIndex
|
|
end
|
|
|
|
-- Mirrors old FindSpellIndexByName (ui.lua) / findSpellIndexByName
|
|
-- (cast.lua): first-match spellbook slot, or nil if unknown.
|
|
function TotemBar.findSpellIndex(name)
|
|
if not name then
|
|
return nil
|
|
end
|
|
local rec = TotemBar.getSpellIndex()[name]
|
|
return rec and rec.bookIndex
|
|
end
|
|
|
|
-- Mirrors old manacost.lua findHighestRankSlot: the slot of the
|
|
-- highest-known rank, or nil if unknown.
|
|
function TotemBar.findHighestRankSlot(name)
|
|
if not name then
|
|
return nil
|
|
end
|
|
local rec = TotemBar.getSpellIndex()[name]
|
|
return rec and rec.maxRankIndex
|
|
end
|
|
|
|
-- Mirrors old known.lua highestKnownRank: the highest known rank
|
|
-- NUMBER, or nil if unknown / no rank string ever parsed.
|
|
function TotemBar.highestKnownRank(name)
|
|
if not name then
|
|
return nil
|
|
end
|
|
local rec = TotemBar.getSpellIndex()[name]
|
|
return rec and rec.maxRank
|
|
end
|
|
|
|
-- Mirrors old known.lua scanSpellbook: flat array of every spellbook
|
|
-- entry's name, ONE PER SLOT (a multi-rank totem appears once per rank
|
|
-- it has) -- ShowFlyout's knownTotems() filter and PrintScan's raw dump
|
|
-- both rely on that shape. The by-name index above intentionally
|
|
-- collapses ranks together, so this stays a separate view over the same
|
|
-- cached scan rather than something derived from the index.
|
|
function TotemBar.scanSpellbook()
|
|
ensureBuilt()
|
|
local names = {}
|
|
for i = 1, table.getn(cachedEntries) do
|
|
names[i] = cachedEntries[i].name
|
|
end
|
|
return names
|
|
end
|
|
|
|
-- SPELLS_CHANGED is the event this codebase already relies on elsewhere
|
|
-- (manacost.lua's manaCache, ui.lua's icon refresh) for "spellbook
|
|
-- changed" -- fires on learning a new spell/rank and on talent changes.
|
|
-- LEARNED_SPELL_IN_TAB was checked and does not exist as a 1.12 client
|
|
-- event (not in this project's confirmed WoW 1.12 event list; it's a
|
|
-- later-expansion API) -- SPELLS_CHANGED alone is the correct signal.
|
|
if CreateFrame then
|
|
local siEvents = CreateFrame("Frame", "TotemBarSpellIndexEventFrame", UIParent)
|
|
siEvents:RegisterEvent("SPELLS_CHANGED")
|
|
siEvents:SetScript("OnEvent", function()
|
|
if event == "SPELLS_CHANGED" then
|
|
TotemBar.invalidateSpellIndex()
|
|
end
|
|
end)
|
|
end
|