-- CoaExporter / Catalogs / Common.lua -- -- Shared engine for catalog dumps. Walks -- C_CharacterAdvancement.GetAllEntries() in 100-entry batches via OnUpdate -- and dispatches each entry to every registered collector. Used by -- Catalogs/Skills.lua and Catalogs/Talents.lua so we only walk the (large) -- entry list once. CoaExporter = _G.CoaExporter or {} _G.CoaExporter = CoaExporter local AE = CoaExporter AE.Catalog = AE.Catalog or {} local C = AE.Catalog C._collectors = C._collectors or {} C._isRunning = false -- Shared SavedVariable for catalog output. Each collector writes its own -- top-level key (skills, dispels, levelPassives, talents, ...). CoaExporterCatalog = CoaExporterCatalog or {} local TalentScanner local function GetCatalogTooltip(spellId) if not TalentScanner then TalentScanner = CreateFrame("GameTooltip", "CoaExpCatalogScanner", nil, "GameTooltipTemplate") end local scanner = TalentScanner scanner:SetOwner(UIParent, "ANCHOR_NONE") scanner:ClearLines() local ok = pcall(function() scanner:SetHyperlink("spell:" .. spellId) end) if not ok or scanner:NumLines() == 0 then return "No description" end local text = "" for i = 1, scanner:NumLines() do local line = _G["CoaExpCatalogScannerTextLeft"..i] if line then local t = line:GetText() if t and t ~= "" then text = text .. t .. "\n" end end end return text end C.GetCatalogTooltip = GetCatalogTooltip local function log(msg) DEFAULT_CHAT_FRAME:AddMessage("CoaExp catalog: " .. tostring(msg)) end C._log = log -- Collectors register a handler that gets called for each valid entry. -- spec = { -- name = "skills", -- identifier (also used as SV key) -- onStart = function(ctx) end, -- reset state -- onEntry = function(ctx, entry, info) end, -- onFinish = function(ctx) end, -- write into CoaExporterCatalog[name] -- } function C.Register(spec) assert(spec and spec.name, "catalog collector needs a name") C._collectors[spec.name] = spec end local function activeCollectors(filter) local out = {} if filter == nil or filter == "all" then for _, c in pairs(C._collectors) do out[#out+1] = c end elseif type(filter) == "string" then local one = C._collectors[filter] if one then out[#out+1] = one end end table.sort(out, function(a, b) return (a.name or "") < (b.name or "") end) return out end -- One-pass scan. filter: "all", "skills", "talents", or nil. function C.Run(filter, callback) if C._isRunning then log("already running, ignoring") return false end if not C_CharacterAdvancement or not C_CharacterAdvancement.GetAllEntries then log("ERROR: C_CharacterAdvancement.GetAllEntries missing - are you on Ascension 3.3.5?") return false end local collectors = activeCollectors(filter) if #collectors == 0 then log("no collectors matched filter: " .. tostring(filter)) return false end local ctx = { startedAt = date(), filter = filter or "all" } for _, col in ipairs(collectors) do if col.onStart then col.onStart(ctx) end end local allEntries = C_CharacterAdvancement.GetAllEntries() local entryKeys = {} for k in pairs(allEntries) do table.insert(entryKeys, k) end local total = #entryKeys local idx = 1 log(string.format("scanning %d entries for [%s]", total, ctx.filter)) C._isRunning = true local frame = CreateFrame("Frame") frame:SetScript("OnUpdate", function(self) if not C._isRunning then self:SetScript("OnUpdate", nil) return end local batch = 100 local processed = 0 while processed < batch and idx <= total do local k = entryKeys[idx] local v = allEntries[k] local entry if type(v) == "table" and v.Class then entry = v else local id = tonumber(k) or (type(v) == "table" and v.ID) if id then entry = C_CharacterAdvancement.GetEntryByInternalID(id) end end if entry and entry.Class then -- Build a normalized info bag every collector can use. local spellId = 0 local allSpells = {} if entry.Spells then if type(entry.Spells) == "table" then spellId = tonumber(entry.Spells[1]) or 0 for _, sp in ipairs(entry.Spells) do table.insert(allSpells, tonumber(sp) or 0) end else spellId = tonumber(entry.Spells) or 0 table.insert(allSpells, spellId) end end local name, _, icon if spellId > 0 then name, _, icon = GetSpellInfo(spellId) end local tooltip = GetCatalogTooltip(spellId > 0 and spellId or entry.ID) if not name or name == "" then name = tooltip:match("^([^\n]+)") or ("ID:" .. tostring(entry.ID)) end local tierVal = tonumber(entry.PositionY) or tonumber(entry.Row) or tonumber(entry.y) or 0 local colVal = tonumber(entry.PositionX) or tonumber(entry.Column) or tonumber(entry.x) or tonumber(entry.Col) or 0 -- Garbage filter: tier=0/col=0/no-icon AND no-spell entries local valid = true if tierVal == 0 and colVal == 0 and (not icon or icon == "") then valid = false end if spellId == 0 and (not icon or icon == "") then valid = false end if valid then local info = { cls = tostring(entry.Class), tab = tostring(entry.Tab or "General"), entryType = tostring(entry.Type or "Talent"), name = name, spellId = spellId, allSpells = allSpells, icon = icon or "", tier = tierVal, col = colVal, tooltip = tooltip, } for _, col in ipairs(collectors) do if col.onEntry then col.onEntry(ctx, entry, info) end end end end idx = idx + 1 processed = processed + 1 end if idx > total then C._isRunning = false self:SetScript("OnUpdate", nil) for _, col in ipairs(collectors) do if col.onFinish then col.onFinish(ctx) end end CoaExporterCatalog._meta = { lastScanAt = ctx.startedAt, filter = ctx.filter, totalEntries = total, } log(string.format("done. %d entries processed; /reload to flush SavedVariables.", total)) if callback then callback(ctx) end else local pct = math.floor((idx / total) * 100) if pct > 0 and pct % 10 == 0 and (ctx._lastPct or -1) ~= pct then ctx._lastPct = pct log(string.format(" %d%% (%d/%d)", pct, idx, total)) end end end) return true end AE._loadedCatalogCommon = true