TotemBar v0.2.5
This commit is contained in:
@@ -0,0 +1,175 @@
|
||||
-- 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
|
||||
Reference in New Issue
Block a user