Files
florian.berthold 6badf0e7ea Add per-character spellbook collector
New Collectors/Spellbook.lua walks GetNumSpellTabs() and emits one entry
per tab with name, texture, offset, numSpells, and a spells[] array of
{ slot, name, rank, spellID, icon }.

3.3.5/Ascension API differences are handled defensively: name comes
from GetSpellBookItemInfo or vanilla GetSpellName; spellID is parsed
out of GetSpellLink (3.3.5 doesn't return it from GetSpellBookItemInfo);
icon falls back to GetSpellTexture if GetSpellInfo doesn't have it yet.

Wiring:
  - CoaExporter.toc: load Collectors/Spellbook.lua after Talents
  - Core.lua: AssembleExport() includes spellbook for all + spellbook
  - Core.lua: HandleExport() accepts /coae export spellbook
  - Core.lua: debug output shows tab + spell counts
  - UI/ExportFrame.lua: "Spellbook" button in the Character section
2026-05-07 12:08:23 +02:00

104 lines
3.4 KiB
Lua

-- CoaExporter - Spellbook collector
--
-- Walks the player's spell book (BOOKTYPE_SPELL) tab by tab and emits
-- name + rank + spellID + icon for every learned spell. Pet book is
-- skipped — it's not part of the build/guide use-case.
--
-- 3.3.5 / Ascension API quirks handled defensively:
-- - GetSpellBookItemInfo(slot, "spell") returns (name, subName) on
-- vanilla 3.3.5 and (name, rank, [stuff]) on newer/private servers.
-- We treat the second return as a rank string regardless.
-- - GetSpellBookItemInfo doesn't return a spellID on 3.3.5. The reliable
-- way to get the ID is to parse it out of GetSpellLink(slot, "spell")
-- which yields "|cff71d5ff|Hspell:NNNN|h[Name]|h|r".
-- - Some entries can be category headers / flyouts; if no link or
-- spellID, we still record the name (it might be a known-spell that
-- just doesn't link, e.g. some passives).
CoaExporter = _G.CoaExporter or {}
_G.CoaExporter = CoaExporter
local AE = CoaExporter
local BOOKTYPE = (BOOKTYPE_SPELL or "spell")
local function safeCall(fn, ...)
if type(fn) ~= "function" then return nil end
local ok, a, b, c = pcall(fn, ...)
if not ok then return nil end
return a, b, c
end
local function spellIdFromLink(link)
if not link or link == "" then return 0 end
local id = string.match(link, "spell:(%d+)")
return tonumber(id) or 0
end
local function iconPath(icon)
if not icon or icon == "" then return "" end
local p = icon:match("Interface\\Icons\\(.+)") or icon
return tostring(p):lower()
end
function AE.CollectSpellbook()
local out = { tabs = {}, totalSpells = 0 }
if type(GetNumSpellTabs) ~= "function" then
out.error = "GetNumSpellTabs not available"
return out
end
local numTabs = GetNumSpellTabs() or 0
for tabIndex = 1, numTabs do
local tabName, tabTexture, offset, numSpells = safeCall(GetSpellTabInfo, tabIndex)
offset = offset or 0
numSpells = numSpells or 0
local tab = {
index = tabIndex,
name = tabName or ("Tab " .. tabIndex),
texture = iconPath(tabTexture),
offset = offset,
numSpells = numSpells,
spells = {},
}
for slot = offset + 1, offset + numSpells do
local name, subOrRank = safeCall(GetSpellBookItemInfo, slot, BOOKTYPE)
if (not name or name == "") and type(GetSpellName) == "function" then
-- vanilla 3.3.5 fallback
name, subOrRank = safeCall(GetSpellName, slot, BOOKTYPE)
end
local link = safeCall(GetSpellLink, slot, BOOKTYPE)
local spellID = spellIdFromLink(link)
local icon
if spellID > 0 then
local _, _, ic = GetSpellInfo(spellID)
icon = ic
end
if (not icon) and type(GetSpellTexture) == "function" then
icon = safeCall(GetSpellTexture, slot, BOOKTYPE)
end
if name and name ~= "" then
table.insert(tab.spells, {
slot = slot,
name = name,
rank = subOrRank or "",
spellID = spellID,
icon = iconPath(icon),
})
out.totalSpells = out.totalSpells + 1
end
end
table.insert(out.tabs, tab)
end
return out
end
AE._loadedSpellbook = true