New Collectors/Items.lua mirrors the MysticScrolls async scanner: calls GetItemInfo()/SetHyperlink() over an id list/range so the client fetches the templates from the server. This both populates itemcache.wdb (for the server-side re-import) and captures name/quality/ilvl/slot/icon/spell/ tooltip into the CoaExporterItemCache SavedVariable for direct export. Commands: /coae items scan scan the CoA custom block (2000000-2000099 + 2089618) /coae items scan missing scan the known-missing id list /coae items scan <from> <to> scan an explicit inclusive range /coae items scan <id> single id /coae items export|reset|status Motivation: CoA per-class character-creation gear (ids 2000000+) is only cached once a character has viewed it, so unseen items are absent from both the client cache and db.exil.es. This scans them in without needing to roll one character per class.
649 lines
26 KiB
Lua
649 lines
26 KiB
Lua
-- CoaExporter - Core
|
|
local ADDON_NAME = ...
|
|
CoaExporter = CoaExporter or {}
|
|
local AE = CoaExporter
|
|
|
|
CoaExporterConfig = CoaExporterConfig or {}
|
|
CoaExporterConfig.enableSavedVariables = CoaExporterConfig.enableSavedVariables == true and true or false
|
|
CoaExporterSaved = CoaExporterSaved or {}
|
|
|
|
local function Norm(s)
|
|
if s == nil then return nil end
|
|
s = tostring(s):lower()
|
|
s = s:match("^%s*(.-)%s*$") or s
|
|
if s == "" then return nil end
|
|
return s
|
|
end
|
|
|
|
local function SafeCall(fn, ...)
|
|
local ok, r = pcall(fn, ...)
|
|
if ok then return r end
|
|
return nil
|
|
end
|
|
|
|
-- ===== Per-character export =====
|
|
|
|
function AE:AssembleExport(which)
|
|
which = Norm(which)
|
|
local nowIso = date("!%Y-%m-%dT%H:%M:%SZ")
|
|
local version, build, buildDate, toc = GetBuildInfo()
|
|
|
|
local name = UnitName("player") or ""
|
|
local level = UnitLevel("player") or 0
|
|
local _, class = UnitClass("player")
|
|
local _, race = UnitRace("player")
|
|
local faction = UnitFactionGroup("player") or ""
|
|
local realm = GetRealmName and GetRealmName() or (GetCVar and GetCVar("realmName")) or ""
|
|
|
|
local out = {
|
|
schemaVersion = 1,
|
|
exportedAt = nowIso,
|
|
client = { interface = toc or 30300, version = version, build = build, buildDate = buildDate },
|
|
character = { name = name or "", realm = realm or "", level = level or 0, class = class or "", race = race or "", faction = faction or "" },
|
|
}
|
|
|
|
local wantAll = (which == nil) or (which == "all")
|
|
|
|
if wantAll or which == "talents" then
|
|
if AE.CollectTalents then out.talents = SafeCall(AE.CollectTalents) or {} end
|
|
end
|
|
if wantAll or which == "spellbook" then
|
|
if AE.CollectSpellbook then out.spellbook = SafeCall(AE.CollectSpellbook) or {} end
|
|
end
|
|
if wantAll or which == "gear" then
|
|
if AE.CollectGear then out.gear = SafeCall(AE.CollectGear) or {} end
|
|
end
|
|
if wantAll or which == "enchants" then
|
|
if AE.CollectMysticEnchants then out.mysticEnchants = SafeCall(AE.CollectMysticEnchants) or {} end
|
|
end
|
|
|
|
return out
|
|
end
|
|
|
|
function AE:ShowExport(text, titleText)
|
|
local show = _G.CoaExporter_ShowExportFrame
|
|
if type(show) == "function" then
|
|
show(text, titleText)
|
|
return
|
|
end
|
|
|
|
if not self._fallbackFrame then
|
|
local f = CreateFrame("Frame", "CoaExporterFallbackFrame", UIParent, "DialogBoxFrame")
|
|
f:SetSize(700, 500)
|
|
f:SetPoint("CENTER")
|
|
f:SetMovable(true)
|
|
f:EnableMouse(true)
|
|
f:RegisterForDrag("LeftButton")
|
|
f:SetScript("OnDragStart", f.StartMoving)
|
|
f:SetScript("OnDragStop", f.StopMovingOrSizing)
|
|
|
|
local title = f:CreateFontString(nil, "OVERLAY", "GameFontHighlightLarge")
|
|
title:SetPoint("TOP", 0, -8)
|
|
f.title = title
|
|
|
|
local scroll = CreateFrame("ScrollFrame", "CoaExporterFallbackScroll", f, "UIPanelScrollFrameTemplate")
|
|
scroll:SetPoint("TOPLEFT", 16, -36)
|
|
scroll:SetPoint("BOTTOMRIGHT", -32, 16)
|
|
|
|
local edit = CreateFrame("EditBox", "CoaExporterFallbackEdit", scroll)
|
|
edit:SetMultiLine(true)
|
|
edit:SetAutoFocus(true)
|
|
edit:SetFontObject(ChatFontNormal)
|
|
edit:SetWidth(640)
|
|
edit:SetScript("OnEscapePressed", function(self) self:ClearFocus() end)
|
|
edit:SetScript("OnEditFocusGained", function(self) self:HighlightText() end)
|
|
scroll:SetScrollChild(edit)
|
|
|
|
f.editBox = edit
|
|
f.scrollFrame = scroll
|
|
|
|
local close = CreateFrame("Button", nil, f, "UIPanelCloseButton")
|
|
close:SetPoint("TOPRIGHT", -4, -4)
|
|
|
|
self._fallbackFrame = f
|
|
|
|
if type(_G.CoaExporter_ShowExportFrame) ~= "function" then
|
|
_G.CoaExporter_ShowExportFrame = function(t, ti)
|
|
local ff = AE._fallbackFrame
|
|
if not ff then return end
|
|
ff:Show()
|
|
ff.editBox:SetText(t or "")
|
|
ff.title:SetText(ti or "CoaExporter (Ctrl+C)")
|
|
ff.editBox:HighlightText()
|
|
ff.editBox:SetFocus()
|
|
end
|
|
end
|
|
|
|
DEFAULT_CHAT_FRAME:AddMessage("CoaExporter: using fallback export window (UI module not loaded).")
|
|
end
|
|
|
|
local f = self._fallbackFrame
|
|
f:Show()
|
|
f.editBox:SetText(text or "")
|
|
f.title:SetText(titleText or "CoaExporter (Ctrl+C)")
|
|
f.editBox:HighlightText()
|
|
f.editBox:SetFocus()
|
|
end
|
|
|
|
function AE:Export(which)
|
|
local normWhich = Norm(which)
|
|
local data = self:AssembleExport(normWhich)
|
|
local function tiny_json_encode(v)
|
|
local tv = type(v)
|
|
if tv == 'nil' then return 'null' end
|
|
if tv == 'boolean' then return v and 'true' or 'false' end
|
|
if tv == 'number' then return tostring(v) end
|
|
if tv == 'string' then
|
|
local s = v:gsub('\\', '\\\\'):gsub('"', '\\"'):gsub('\n', '\\n'):gsub('\r', '\\r'):gsub('\t', '\\t')
|
|
return '"' .. s .. '"'
|
|
end
|
|
if tv == 'table' then
|
|
local n = 0
|
|
for k,_ in pairs(v) do if type(k) ~= 'number' then n = -1 break else n = math.max(n, k) end end
|
|
if n >= 1 then
|
|
local parts = {}
|
|
for i=1,n do parts[#parts+1] = tiny_json_encode(v[i]) end
|
|
return '[' .. table.concat(parts, ',') .. ']'
|
|
else
|
|
local parts = {}
|
|
for k,val in pairs(v) do parts[#parts+1] = tiny_json_encode(tostring(k)) .. ':' .. tiny_json_encode(val) end
|
|
return '{' .. table.concat(parts, ',') .. '}'
|
|
end
|
|
end
|
|
return 'null'
|
|
end
|
|
local encoder = _G.CoaExporter_Json_Encode or tiny_json_encode
|
|
local json = encoder(data)
|
|
|
|
local title = "CoaExporter"
|
|
if normWhich and normWhich ~= "all" then title = title .. " - " .. normWhich end
|
|
self:ShowExport(json, title .. " (Ctrl+C)")
|
|
|
|
if CoaExporterConfig.enableSavedVariables then
|
|
local key = (data.character.realm or "") .. ":" .. (data.character.name or "")
|
|
CoaExporterSaved[key] = data
|
|
DEFAULT_CHAT_FRAME:AddMessage("CoaExporter: export saved to SavedVariables for " .. key)
|
|
end
|
|
end
|
|
|
|
-- ===== Markdown generators =====
|
|
|
|
function AE:GenerateMarkdownGear()
|
|
local gear = self.CollectGear and self.CollectGear() or { slots = {} }
|
|
local ench = self.CollectMysticEnchants and self.CollectMysticEnchants() or { perSlot = {}, active = {} }
|
|
|
|
local bySlot = {}
|
|
for _, s in ipairs(gear.slots or {}) do bySlot[s.slot] = s end
|
|
local mystPer = {}
|
|
for _, e in ipairs(ench.perSlot or {}) do mystPer[e.slot] = e.name end
|
|
|
|
local ORDER = {
|
|
{1,"Head"},{2,"Neck"},{3,"Shoulders"},{15,"Back"},{5,"Chest"},
|
|
{9,"Wrist"},{16,"Mainhand"},{17,"Offhand"},{18,"Wand"},{10,"Gloves"},
|
|
{6,"Belt"},{7,"Legs"},{8,"Boots"},{11,"Ring 1"},{12,"Ring 2"},
|
|
{13,"Trinket 1"},{14,"Trinket 2"},
|
|
}
|
|
|
|
local function md_link(name, itemId)
|
|
if (itemId or 0) > 0 and name and name ~= "" then
|
|
return string.format("[%s](https://db.ascension.gg/?item=%d)", name, itemId)
|
|
elseif name and name ~= "" then
|
|
return name
|
|
else
|
|
return "-"
|
|
end
|
|
end
|
|
|
|
local lines = {}
|
|
table.insert(lines, "| Slot | Item | Location | Enchant | Alternative |")
|
|
table.insert(lines, "|------|------|----------|---------|-------------|")
|
|
for _, ent in ipairs(ORDER) do
|
|
local slotId, label = ent[1], ent[2]
|
|
local s = bySlot[slotId]
|
|
local itemCell, enchantCell = "-", "-"
|
|
if s then
|
|
itemCell = md_link(s.name, s.itemId)
|
|
local gearEnch = s.enchantName
|
|
if gearEnch and gearEnch ~= "" then
|
|
enchantCell = gearEnch
|
|
else
|
|
local myst = mystPer[slotId]
|
|
if myst and myst ~= "" then enchantCell = "Mystic: " .. myst end
|
|
end
|
|
end
|
|
table.insert(lines, string.format("| **%s** | %s | - | %s | - |", label, itemCell, enchantCell))
|
|
end
|
|
return table.concat(lines, "\n") .. "\n"
|
|
end
|
|
|
|
function AE:GenerateMarkdownEnchants()
|
|
local ench = self.CollectMysticEnchants and self.CollectMysticEnchants() or { enchants = {} }
|
|
|
|
local lines = {}
|
|
table.insert(lines, "## Mystic Enchants\n")
|
|
if #(ench.enchants or {}) == 0 then
|
|
table.insert(lines, "*No Mystic Enchants equipped.*\n")
|
|
return table.concat(lines, "\n")
|
|
end
|
|
|
|
local enchantRows = {}
|
|
for _, e in ipairs(ench.enchants or {}) do
|
|
local slot = e.slot or 0
|
|
local quality, qualityOrder
|
|
if slot >= 11 then
|
|
quality = '<span class="artifact">Artifact</span>'; qualityOrder = 1
|
|
elseif slot == 1 then
|
|
quality = '<span class="legendary">Legendary</span>'; qualityOrder = 2
|
|
elseif slot >= 2 and slot <= 4 then
|
|
quality = '<span class="epic">Epic</span>'; qualityOrder = 3
|
|
else
|
|
quality = '<span class="rare">Rare</span>'; qualityOrder = 4
|
|
end
|
|
|
|
local spellID = e.spellID or 0
|
|
local name = e.name or "Unknown"
|
|
local icon = e.icon or ""
|
|
local iconImg = icon ~= "" and string.format('<img src="/icons/spells/%s.png" width="20" height="20" style="vertical-align: middle;" /> ', icon) or ""
|
|
local link = spellID > 0 and string.format("[%s](https://db.ascension.gg/?spell=%d)", name, spellID) or name
|
|
table.insert(enchantRows, {
|
|
quality = quality, qualityOrder = qualityOrder, slot = slot,
|
|
text = string.format("| %s | %s**%s** |", quality, iconImg, link),
|
|
})
|
|
end
|
|
|
|
table.sort(enchantRows, function(a, b)
|
|
if a.qualityOrder ~= b.qualityOrder then return a.qualityOrder < b.qualityOrder end
|
|
return a.slot < b.slot
|
|
end)
|
|
|
|
table.insert(lines, "| Quality | Enchant |")
|
|
table.insert(lines, "|---------|---------|")
|
|
for _, row in ipairs(enchantRows) do table.insert(lines, row.text) end
|
|
return table.concat(lines, "\n")
|
|
end
|
|
|
|
function AE:GenerateMarkdownSpellbook()
|
|
local sb = self.CollectSpellbook and self.CollectSpellbook() or { tabs = {} }
|
|
|
|
local lines = {}
|
|
table.insert(lines, "## Spellbook\n")
|
|
|
|
if not sb.tabs or #sb.tabs == 0 then
|
|
table.insert(lines, "*Spellbook not available - run `/coae debug` to check collector.*\n")
|
|
return table.concat(lines, "\n")
|
|
end
|
|
|
|
table.insert(lines, string.format("Total: **%d** spells across %d tab(s).\n",
|
|
sb.totalSpells or 0, #sb.tabs))
|
|
|
|
for _, tab in ipairs(sb.tabs) do
|
|
if tab.spells and #tab.spells > 0 then
|
|
table.insert(lines, string.format("### %s (%d)", tab.name or "Tab", #tab.spells))
|
|
table.insert(lines, "")
|
|
table.insert(lines, "| Icon | Spell | Rank |")
|
|
table.insert(lines, "|------|-------|------|")
|
|
for _, sp in ipairs(tab.spells) do
|
|
local icon = sp.icon or ""
|
|
local iconImg = icon ~= "" and string.format(
|
|
'<img src="/icons/spells/%s.png" width="20" height="20" style="vertical-align: middle;" />',
|
|
icon) or ""
|
|
local link = (sp.spellID or 0) > 0
|
|
and string.format("[%s](https://db.ascension.gg/?spell=%d)", sp.name or "", sp.spellID)
|
|
or (sp.name or "")
|
|
local rank = (sp.rank ~= nil and sp.rank ~= "") and tostring(sp.rank) or "-"
|
|
table.insert(lines, string.format("| %s | %s | %s |", iconImg, link, rank))
|
|
end
|
|
table.insert(lines, "")
|
|
end
|
|
end
|
|
|
|
return table.concat(lines, "\n")
|
|
end
|
|
|
|
function AE:GenerateMarkdownFull()
|
|
local data = self:AssembleExport("all") or {}
|
|
local char = data.character or {}
|
|
local cls = (char.class or ""):lower()
|
|
local classIcon = string.format("/icons/classes/classicon_%s.png", cls ~= "" and cls or "unknown")
|
|
|
|
local lines = {}
|
|
table.insert(lines, string.format("", char.class or "Class", classIcon))
|
|
table.insert(lines, string.format("By %s", char.name or "?"))
|
|
table.insert(lines, "")
|
|
table.insert(lines, string.format("Character: **%s** - Level %d %s %s on %s",
|
|
char.name or "?", char.level or 0, char.race or "?", char.class or "?", char.realm or "?"))
|
|
table.insert(lines, "")
|
|
table.insert(lines, "<!-- Optional intro paragraph: describe the build, content target, philosophy. -->")
|
|
table.insert(lines, "")
|
|
table.insert(lines, "> **Note:** Exported by CoaExporter - gear, enchants, and talents are auto-populated.")
|
|
table.insert(lines, "> Items marked with unknown db.ascension.gg IDs are custom Ascension loot.")
|
|
table.insert(lines, "> {.is-info}")
|
|
table.insert(lines, "")
|
|
|
|
table.insert(lines, "## Gear")
|
|
table.insert(lines, "")
|
|
table.insert(lines, self:GenerateMarkdownGear())
|
|
|
|
table.insert(lines, self:GenerateMarkdownEnchants())
|
|
table.insert(lines, "")
|
|
|
|
table.insert(lines, "## Talents")
|
|
table.insert(lines, "")
|
|
local talents = data.talents or {}
|
|
local selected = talents.selected or {}
|
|
local buildString = talents.buildString
|
|
local list = talents.talents or selected
|
|
if buildString and buildString ~= "" then
|
|
table.insert(lines, string.format("**Ascension ExportBuild:** `%s`", buildString))
|
|
table.insert(lines, "")
|
|
table.insert(lines, string.format(
|
|
"<!-- The exil.es talent-calc uses a different build format. " ..
|
|
"Until the converter is in place, generate the iframe URL from " ..
|
|
"the exil.es calc UI by importing the ExportBuild string above, " ..
|
|
"then paste the resulting src= here:"))
|
|
table.insert(lines, string.format(
|
|
" <iframe src=\"/static/talents/?build=%s.XX_YY_ZZ..&embed=true\" " ..
|
|
"width=\"100%%\" height=\"840\" style=\"border:0;border-radius:8px;\"></iframe>",
|
|
(cls ~= "" and cls or "druid")))
|
|
table.insert(lines, "-->")
|
|
table.insert(lines, "")
|
|
end
|
|
|
|
if list and #list > 0 then
|
|
table.insert(lines, string.format("Known talents: **%d** entries.", #list))
|
|
table.insert(lines, "")
|
|
table.insert(lines, "| ID | Name | Rank |")
|
|
table.insert(lines, "|----|------|------|")
|
|
for _, t in ipairs(list) do
|
|
local nm = (t.name and t.name ~= "") and t.name or "*(unresolved)*"
|
|
local rk = (t.rank or 0) > 0 and tostring(t.rank) or "?"
|
|
local mr = (t.maxRank or 0) > 0 and tostring(t.maxRank) or "?"
|
|
table.insert(lines, string.format("| %d | %s | %s/%s |", t.id or 0, nm, rk, mr))
|
|
end
|
|
else
|
|
table.insert(lines, "*No talent data - run `/coae debug` to check collector.*")
|
|
end
|
|
|
|
-- Spellbook section
|
|
table.insert(lines, "")
|
|
table.insert(lines, self:GenerateMarkdownSpellbook())
|
|
|
|
return table.concat(lines, "\n")
|
|
end
|
|
|
|
-- ===== Slash command =====
|
|
|
|
local HELP = [[
|
|
CoaExporter commands:
|
|
|
|
/coae export all|talents|spellbook|gear|enchants
|
|
/coae export mdgear|mdenchants|mdspellbook|md (full wiki)
|
|
/coae catalog all|skills|talents|icons
|
|
/coae catalog dispels [class]|passives [class]|status
|
|
/coae scrolls scan|export|reset|status
|
|
/coae items scan [missing | <from> <to> | <id>]
|
|
/coae items export|reset|status
|
|
/coae sv on|off (SavedVariables for character export)
|
|
/coae debug
|
|
/coae help
|
|
]]
|
|
|
|
local function HandleExport(rest)
|
|
rest = Norm(rest) or "all"
|
|
if rest == "mdgear" then
|
|
AE:ShowExport(AE:GenerateMarkdownGear(), "CoaExporter - Markdown Gear (Ctrl+C)")
|
|
elseif rest == "mdenchants" then
|
|
AE:ShowExport(AE:GenerateMarkdownEnchants(), "CoaExporter - Markdown Enchants (Ctrl+C)")
|
|
elseif rest == "mdspellbook" then
|
|
AE:ShowExport(AE:GenerateMarkdownSpellbook(), "CoaExporter - Markdown Spellbook (Ctrl+C)")
|
|
elseif rest == "md" or rest == "mdfull" or rest == "wiki" then
|
|
AE:ShowExport(AE:GenerateMarkdownFull(), "CoaExporter - Wiki Markdown (Ctrl+C)")
|
|
elseif rest == "all" or rest == "talents" or rest == "spellbook" or rest == "gear" or rest == "enchants" then
|
|
AE:Export(rest)
|
|
else
|
|
DEFAULT_CHAT_FRAME:AddMessage("CoaExporter: unknown export target '" .. tostring(rest) .. "'")
|
|
end
|
|
end
|
|
|
|
local function HandleCatalog(rest)
|
|
rest = Norm(rest) or "status"
|
|
local sub, arg = rest:match("^(%S+)%s*(.*)$")
|
|
sub = sub or rest
|
|
|
|
if sub == "all" or sub == "skills" or sub == "talents" or sub == "icons" then
|
|
if not AE.Catalog or not AE.Catalog.Run then
|
|
DEFAULT_CHAT_FRAME:AddMessage("CoaExporter: catalog module not loaded")
|
|
return
|
|
end
|
|
AE.Catalog.Run(sub == "all" and "all" or sub)
|
|
elseif sub == "dispels" then
|
|
if AE.CatalogListDispels then AE.CatalogListDispels(arg) end
|
|
elseif sub == "passives" then
|
|
if AE.CatalogListPassives then AE.CatalogListPassives(arg) end
|
|
elseif sub == "status" then
|
|
local meta = (CoaExporterCatalog and CoaExporterCatalog._meta) or {}
|
|
local nSkills, nDispels, nPassives, nTalents = 0, 0, 0, 0
|
|
if CoaExporterCatalog then
|
|
for _ in pairs(CoaExporterCatalog.skills or {}) do nSkills = nSkills + 1 end
|
|
for _ in pairs(CoaExporterCatalog.dispels or {}) do nDispels = nDispels + 1 end
|
|
for _ in pairs(CoaExporterCatalog.levelPassives or {}) do nPassives = nPassives + 1 end
|
|
for _ in pairs(CoaExporterCatalog.talents or {}) do nTalents = nTalents + 1 end
|
|
end
|
|
DEFAULT_CHAT_FRAME:AddMessage(string.format(
|
|
"CoaExporter catalog: lastScan=%s filter=%s | skills=%d cls dispels=%d cls passives=%d cls talents=%d cls",
|
|
tostring(meta.lastScanAt or "never"), tostring(meta.filter or "-"),
|
|
nSkills, nDispels, nPassives, nTalents))
|
|
else
|
|
DEFAULT_CHAT_FRAME:AddMessage("CoaExporter: /coae catalog [all|skills|talents|icons|dispels|passives|status]")
|
|
end
|
|
end
|
|
|
|
local function HandleScrolls(rest)
|
|
rest = Norm(rest) or "status"
|
|
if rest == "scan" then
|
|
if not AE.ScrollsStartScan then
|
|
DEFAULT_CHAT_FRAME:AddMessage("CoaExporter: MysticScrolls collector not loaded")
|
|
return
|
|
end
|
|
DEFAULT_CHAT_FRAME:AddMessage(string.format(
|
|
"CoaExporter scrolls: scanning %d scrolls, ~20s...",
|
|
AE.ScrollCatalogTotal or 0))
|
|
AE.ScrollsStartScan(function(stats)
|
|
DEFAULT_CHAT_FRAME:AddMessage(string.format(
|
|
"CoaExporter scrolls: scan complete - %d resolved, %d unresolved",
|
|
stats.total - stats.unresolved, stats.unresolved))
|
|
end)
|
|
elseif rest == "export" then
|
|
if not AE.ScrollsExport then
|
|
DEFAULT_CHAT_FRAME:AddMessage("CoaExporter: MysticScrolls collector not loaded")
|
|
return
|
|
end
|
|
local data = AE.ScrollsExport()
|
|
local encoder = _G.CoaExporter_Json_Encode
|
|
if not encoder then
|
|
DEFAULT_CHAT_FRAME:AddMessage("CoaExporter: JSON encoder missing")
|
|
return
|
|
end
|
|
AE:ShowExport(encoder(data), "CoaExporter - Scrolls (Ctrl+C)")
|
|
elseif rest == "reset" then
|
|
if AE.ScrollsReset then AE.ScrollsReset() end
|
|
DEFAULT_CHAT_FRAME:AddMessage("CoaExporter scrolls: cache cleared")
|
|
elseif rest == "status" then
|
|
local meta = CoaExporterScrollCache and CoaExporterScrollCache.meta or {}
|
|
local resolved = 0
|
|
if CoaExporterScrollCache then
|
|
for _ in pairs(CoaExporterScrollCache.entries or {}) do resolved = resolved + 1 end
|
|
end
|
|
DEFAULT_CHAT_FRAME:AddMessage(string.format(
|
|
"CoaExporter scrolls: %d/%d resolved (last scan: %s)",
|
|
resolved, AE.ScrollCatalogTotal or 0,
|
|
meta.lastScanAt and date("%Y-%m-%d %H:%M:%S", meta.lastScanAt) or "never"))
|
|
else
|
|
DEFAULT_CHAT_FRAME:AddMessage("CoaExporter: /coae scrolls [scan|export|reset|status]")
|
|
end
|
|
end
|
|
|
|
local function HandleItems(rest)
|
|
rest = rest or ""
|
|
local sub, arg = rest:match("^(%S*)%s*(.*)$")
|
|
sub = (sub or ""):lower()
|
|
if sub == "" or sub == "scan" then
|
|
if not AE.ItemsStartScan then
|
|
DEFAULT_CHAT_FRAME:AddMessage("CoaExporter: items collector not loaded")
|
|
return
|
|
end
|
|
local ids, label = AE.ItemsResolveTargets(arg)
|
|
if not ids then
|
|
DEFAULT_CHAT_FRAME:AddMessage("CoaExporter: /coae items scan [missing | <from> <to> | <id>]")
|
|
return
|
|
end
|
|
CoaExporterItemCache.meta.lastRange = label
|
|
DEFAULT_CHAT_FRAME:AddMessage(string.format(
|
|
"CoaExporter items: scanning %d id(s) [%s], up to ~25s...", #ids, label))
|
|
AE.ItemsStartScan(ids, function(stats)
|
|
DEFAULT_CHAT_FRAME:AddMessage(string.format(
|
|
"CoaExporter items: scan complete - %d cached total, %d unresolved this run. "
|
|
.. "Log out to flush itemcache.wdb, then '/coae items export'.",
|
|
AE.ItemsCount and AE.ItemsCount() or 0, stats.unresolved))
|
|
end)
|
|
elseif sub == "export" then
|
|
if not AE.ItemsExport then
|
|
DEFAULT_CHAT_FRAME:AddMessage("CoaExporter: items collector not loaded")
|
|
return
|
|
end
|
|
local encoder = _G.CoaExporter_Json_Encode
|
|
if not encoder then
|
|
DEFAULT_CHAT_FRAME:AddMessage("CoaExporter: JSON encoder missing")
|
|
return
|
|
end
|
|
AE:ShowExport(encoder(AE.ItemsExport()), "CoaExporter - Items (Ctrl+C)")
|
|
elseif sub == "reset" then
|
|
if AE.ItemsReset then AE.ItemsReset() end
|
|
DEFAULT_CHAT_FRAME:AddMessage("CoaExporter items: cache cleared")
|
|
elseif sub == "status" then
|
|
local meta = CoaExporterItemCache and CoaExporterItemCache.meta or {}
|
|
DEFAULT_CHAT_FRAME:AddMessage(string.format(
|
|
"CoaExporter items: %d cached (last range: %s, last scan: %s)",
|
|
AE.ItemsCount and AE.ItemsCount() or 0,
|
|
tostring(meta.lastRange or "-"),
|
|
meta.lastScanAt and date("%Y-%m-%d %H:%M:%S", meta.lastScanAt) or "never"))
|
|
else
|
|
DEFAULT_CHAT_FRAME:AddMessage("CoaExporter: /coae items [scan|export|reset|status]")
|
|
end
|
|
end
|
|
|
|
local function HandleDebug()
|
|
local lines = {}
|
|
local function add(m) table.insert(lines, m) end
|
|
|
|
add("CoaExporter debug:")
|
|
add(string.format("- AddOn: %s", tostring(ADDON_NAME)))
|
|
add(string.format("- UI: %s", type(_G.CoaExporter_ShowExportFrame) == "function" and "yes" or "no"))
|
|
add(string.format("- JSON: %s", type(_G.CoaExporter_Json_Encode) == "function" and "yes" or "no"))
|
|
add(string.format("- Loaded: talents=%s spellbook=%s gear=%s enchants=%s scrolls=%s probe=%s items=%s catCommon=%s catSkills=%s catTalents=%s",
|
|
tostring(AE._loadedTalents or false), tostring(AE._loadedSpellbook or false),
|
|
tostring(AE._loadedGear or false), tostring(AE._loadedEnchants or false),
|
|
tostring(AE._loadedMysticScrolls or false), tostring(AE._loadedMysticScrollProbe or false),
|
|
tostring(AE._loadedItems or false),
|
|
tostring(AE._loadedCatalogCommon or false), tostring(AE._loadedCatalogSkills or false),
|
|
tostring(AE._loadedCatalogTalents or false)))
|
|
|
|
if type(AE.CollectTalents) == "function" then
|
|
local t = SafeCall(AE.CollectTalents) or {}
|
|
add(string.format("Talents: OK, selected=%d, build=%s",
|
|
#(t.selected or {}), tostring(t.buildString or "-")))
|
|
else
|
|
add("Talents: MISSING")
|
|
end
|
|
if type(AE.CollectSpellbook) == "function" then
|
|
local sb = SafeCall(AE.CollectSpellbook) or {}
|
|
add(string.format("Spellbook: OK, tabs=%d, spells=%d",
|
|
#(sb.tabs or {}), sb.totalSpells or 0))
|
|
else
|
|
add("Spellbook: MISSING")
|
|
end
|
|
if type(AE.CollectGear) == "function" then
|
|
local g = SafeCall(AE.CollectGear) or {}
|
|
add(string.format("Gear: OK, slots=%d", #(g.slots or {})))
|
|
else
|
|
add("Gear: MISSING")
|
|
end
|
|
if type(AE.CollectMysticEnchants) == "function" then
|
|
local e = SafeCall(AE.CollectMysticEnchants) or {}
|
|
add(string.format("MysticEnchants: OK, count=%d", #(e.enchants or {})))
|
|
else
|
|
add("MysticEnchants: MISSING")
|
|
end
|
|
add(string.format("ScrollCatalog: %d entries", AE.ScrollCatalogTotal or 0))
|
|
add(string.format("Items: %d cached", AE.ItemsCount and AE.ItemsCount() or 0))
|
|
|
|
local meta = (CoaExporterCatalog and CoaExporterCatalog._meta) or {}
|
|
add(string.format("Catalog: lastScan=%s filter=%s",
|
|
tostring(meta.lastScanAt or "never"), tostring(meta.filter or "-")))
|
|
|
|
AE:ShowExport(table.concat(lines, "\n"), "CoaExporter - Debug")
|
|
end
|
|
|
|
local function HandleSv(rest)
|
|
if rest == "on" then
|
|
CoaExporterConfig.enableSavedVariables = true
|
|
DEFAULT_CHAT_FRAME:AddMessage("CoaExporter: SavedVariables export ENABLED")
|
|
elseif rest == "off" then
|
|
CoaExporterConfig.enableSavedVariables = false
|
|
DEFAULT_CHAT_FRAME:AddMessage("CoaExporter: SavedVariables export DISABLED")
|
|
else
|
|
DEFAULT_CHAT_FRAME:AddMessage("CoaExporter: /coae sv on|off")
|
|
end
|
|
end
|
|
|
|
local function Dispatch(msg)
|
|
msg = (msg or ""):lower()
|
|
if msg == "" or msg == "help" then
|
|
AE:ShowExport(HELP, "CoaExporter - Help")
|
|
return
|
|
end
|
|
local cmd, rest = msg:match("^(%S+)%s*(.*)$")
|
|
if cmd == "export" then return HandleExport(rest) end
|
|
if cmd == "catalog" then return HandleCatalog(rest) end
|
|
if cmd == "scrolls" then return HandleScrolls(rest) end
|
|
if cmd == "items" then return HandleItems(rest) end
|
|
if cmd == "debug" then return HandleDebug() end
|
|
if cmd == "sv" then return HandleSv(Norm(rest)) end
|
|
DEFAULT_CHAT_FRAME:AddMessage("CoaExporter: unknown command. Type /coae help")
|
|
end
|
|
|
|
SLASH_COAE1 = "/coae"
|
|
SLASH_COAE2 = "/coaexp"
|
|
SLASH_COAE3 = "/ascx" -- back-compat: AscensionExporter users
|
|
SLASH_COAE4 = "/asxc"
|
|
SlashCmdList["COAE"] = Dispatch
|
|
|
|
-- Legacy CoA_*Exporter slash compatibility shims
|
|
SLASH_COAESKILLDUMP1 = "/skilldump"
|
|
SlashCmdList["COAESKILLDUMP"] = function()
|
|
if AE.Catalog and AE.Catalog.Run then AE.Catalog.Run("skills") end
|
|
end
|
|
|
|
SLASH_COAEDISPELS1 = "/dispels"
|
|
SlashCmdList["COAEDISPELS"] = function(arg)
|
|
if AE.CatalogListDispels then AE.CatalogListDispels(arg) end
|
|
end
|
|
|
|
SLASH_COAEPASSIVES1 = "/passives"
|
|
SlashCmdList["COAEPASSIVES"] = function(arg)
|
|
if AE.CatalogListPassives then AE.CatalogListPassives(arg) end
|
|
end
|
|
|
|
SLASH_COAETALENTDUMP1 = "/talentdumpall"
|
|
SLASH_COAETALENTDUMP2 = "/talentdump"
|
|
SlashCmdList["COAETALENTDUMP"] = function()
|
|
if AE.Catalog and AE.Catalog.Run then AE.Catalog.Run("talents") end
|
|
end
|
|
|
|
local f = CreateFrame("Frame")
|
|
f:RegisterEvent("ADDON_LOADED")
|
|
f:SetScript("OnEvent", function(_, event, addon)
|
|
if event == "ADDON_LOADED" and addon == "CoaExporter" then
|
|
CoaExporterConfig.enableSavedVariables = CoaExporterConfig.enableSavedVariables and true or false
|
|
end
|
|
end)
|