diff --git a/CoaExporter/CoaExporter.toc b/CoaExporter/CoaExporter.toc index 869853f..63386df 100644 --- a/CoaExporter/CoaExporter.toc +++ b/CoaExporter/CoaExporter.toc @@ -3,7 +3,7 @@ ## Notes: Per-character export (talents/gear/mystic enchants/scrolls) + game-data catalog dump (skills/dispels/passives/talents) for db.exil.es ## Author: Subd from CoA / Exiles EU ## Version: 1.0.0 -## SavedVariables: CoaExporterSaved, CoaExporterConfig, CoaExporterScrollCache, CoaExporterCatalog, CoaExporterItemCache +## SavedVariables: CoaExporterSaved, CoaExporterConfig, CoaExporterScrollCache, CoaExporterCatalog Util\Json.lua Data\ScrollCatalog.lua @@ -14,7 +14,6 @@ Collectors\Gear.lua Collectors\Enchants.lua Collectors\MysticScrolls.lua Collectors\MysticScrollProbe.lua -Collectors\Items.lua Catalogs\Common.lua Catalogs\Skills.lua diff --git a/CoaExporter/Collectors/Items.lua b/CoaExporter/Collectors/Items.lua deleted file mode 100644 index e549572..0000000 --- a/CoaExporter/Collectors/Items.lua +++ /dev/null @@ -1,231 +0,0 @@ --- CoaExporter / Collectors / Items.lua --- --- Async item-ID scanner. Forces the client to fetch item templates from the --- server for a list/range of item IDs, captures the resolved data, and (as a --- side effect) populates the client's itemcache.wdb so a server-side --- re-import can pick the items up too. --- --- Why this exists: CoA's per-class character-creation starting gear lives at --- item ids 2000000+. The client only caches an item once a character has --- actually seen it in-game, so items nobody has viewed are absent from both --- the cache and db.exil.es. Calling GetItemInfo()/SetHyperlink() on an --- unseen id triggers ITEM_QUERY_SINGLE to the server, which fills the cache. --- --- Usage: --- /coae items scan → scan the default CoA custom block --- /coae items scan missing → scan the known-missing id list (below) --- /coae items scan 2000000 2000200 → scan an explicit inclusive range --- /coae items scan 2089618 → scan a single id --- /coae items export → JSON of everything resolved so far --- /coae items status → resolved/seen counts --- /coae items reset → clear the cache --- --- Design mirrors Collectors/MysticScrolls.lua: a pass fires a batch of --- GetItemInfo requests, waits ~5s, then re-scans and records whatever --- resolved; unresolved ids are retried up to maxAttempts. Results accumulate --- in the CoaExporterItemCache SavedVariable across sessions. - -CoaExporter = _G.CoaExporter or {} -_G.CoaExporter = CoaExporter -local AE = CoaExporter - -CoaExporterItemCache = CoaExporterItemCache or { - entries = {}, - meta = { lastScanAt = nil, lastRange = nil, resolved = 0, unresolved = {} }, -} - --- Default scan target: the CoA character-creation block plus the one known --- outlier (2089618). Range is inclusive. Keep this aligned with the gaps the --- db.exil.es item_character_creation mapping is still missing. -AE.ItemsDefaultFrom = 2000000 -AE.ItemsDefaultTo = 2000099 -AE.ItemsExtras = { 2089618 } - --- Known-missing ids (absent from db.exil.es as of 2026-06-06). `/coae items --- scan missing` targets exactly these. -AE.ItemsMissing = { - 2000003, 2000006, - 2000027, 2000028, 2000029, 2000030, - 2000034, 2000035, 2000036, 2000037, - 2000048, 2000049, 2000050, - 2089618, -} - -local function EnsureScanner() - if not AE._itemScanner then - AE._itemScanner = CreateFrame("GameTooltip", "CoaExpItemsTT", nil, "GameTooltipTemplate") - AE._itemScanner:SetOwner(WorldFrame, "ANCHOR_NONE") - end - return AE._itemScanner -end - -local function TipLines(tt) - local out = {} - for i = 1, tt:NumLines() do - local left = _G[tt:GetName() .. "TextLeft" .. i] - if left then - local t = left:GetText() - if t and t ~= "" then out[#out+1] = t end - end - end - return out -end - -local function ScanItemTooltip(itemID) - local tt = EnsureScanner() - tt:ClearLines() - tt:SetHyperlink("item:" .. itemID) - return TipLines(tt) -end - --- Returns true when the item is resolved (now or already cached). On a miss --- it pokes the server (tooltip hyperlink) so the next pass can pick it up. -local function TryResolve(itemID) - local cache = CoaExporterItemCache.entries - if cache[itemID] and cache[itemID].name then - return true - end - local name, link, quality, iLevel, reqLevel, class, subclass, maxStack, - equipSlot, texture, sellPrice = GetItemInfo(itemID) - if not name then - -- Trigger the server-side ITEM_QUERY_SINGLE; this is also what writes - -- the row into itemcache.wdb. - GameTooltip:SetOwner(WorldFrame, "ANCHOR_NONE") - GameTooltip:SetHyperlink("item:" .. itemID) - GameTooltip:Hide() - return false - end - local itemLines = ScanItemTooltip(itemID) - local spellName, spellRank - if GetItemSpell then - spellName, spellRank = GetItemSpell(itemID) - end - cache[itemID] = { - itemID = itemID, - name = name, - link = link, - quality = quality, - itemLevel = iLevel, - reqLevel = reqLevel, - class = class, -- localized item class ("Armor", "Weapon") - subClass = subclass, -- localized subclass ("Cloth", "Polearm") - maxStack = maxStack, - equipSlot = equipSlot, -- INVTYPE_* string - icon = texture and ((texture:match("[Ii]nterface\\[Ii]cons\\(.+)") or texture):lower()) or nil, - sellPrice = sellPrice, -- nil on stock 3.3.5; present on Ascension's client - spellName = spellName, - spellRank = spellRank, - tooltip = table.concat(itemLines, "\n"), - fetchedAt = time(), - } - return true -end - --- One synchronous pass over `ids`. Returns counts + the still-unresolved list. -function AE.ItemsScanPass(ids) - local total = #ids - local newlyResolved = 0 - local unresolved = {} - for _, id in ipairs(ids) do - if TryResolve(id) then - newlyResolved = newlyResolved + 1 - else - unresolved[#unresolved+1] = id - end - end - CoaExporterItemCache.meta.lastScanAt = time() - CoaExporterItemCache.meta.resolved = newlyResolved - CoaExporterItemCache.meta.unresolved = unresolved - return { total = total, resolved = newlyResolved, unresolved = #unresolved } -end - --- Multi-pass scan with delays so the server has time to answer queries. -function AE.ItemsStartScan(ids, callback) - local attempts = 0 - local maxAttempts = 5 - local function step() - attempts = attempts + 1 - local stats = AE.ItemsScanPass(ids) - DEFAULT_CHAT_FRAME:AddMessage(string.format( - "CoaExporter items: pass %d/%d - resolved this pass %d/%d (still unresolved: %d)", - attempts, maxAttempts, stats.resolved, stats.total, stats.unresolved)) - if stats.unresolved > 0 and attempts < maxAttempts then - local t = CreateFrame("Frame") - local elapsed = 0 - t:SetScript("OnUpdate", function(self, dt) - elapsed = elapsed + dt - if elapsed > 5 then - self:SetScript("OnUpdate", nil) - step() - end - end) - else - if stats.unresolved > 0 then - DEFAULT_CHAT_FRAME:AddMessage(string.format( - "CoaExporter items: %d id(s) never resolved - likely no server-side template (dead ids).", - stats.unresolved)) - end - if callback then callback(stats) end - end - end - step() -end - --- Build the id list for a `/coae items scan ` invocation. -function AE.ItemsResolveTargets(arg) - arg = arg and arg:match("^%s*(.-)%s*$") or "" - if arg == "" then - local ids = {} - for id = AE.ItemsDefaultFrom, AE.ItemsDefaultTo do ids[#ids+1] = id end - for _, id in ipairs(AE.ItemsExtras) do ids[#ids+1] = id end - return ids, string.format("%d-%d+extras", AE.ItemsDefaultFrom, AE.ItemsDefaultTo) - end - if arg == "missing" then - local ids = {} - for _, id in ipairs(AE.ItemsMissing) do ids[#ids+1] = id end - return ids, "missing" - end - local from, to = arg:match("^(%d+)%s+(%d+)$") - if from then - from, to = tonumber(from), tonumber(to) - if to < from then from, to = to, from end - -- Guard against a runaway range. - if (to - from) > 20000 then to = from + 20000 end - local ids = {} - for id = from, to do ids[#ids+1] = id end - return ids, string.format("%d-%d", from, to) - end - local single = arg:match("^(%d+)$") - if single then - return { tonumber(single) }, single - end - return nil, nil -end - -function AE.ItemsExport() - local out = { - schemaVersion = 1, - exportedAt = date("!%Y-%m-%dT%H:%M:%SZ"), - cacheMeta = CoaExporterItemCache.meta, - entries = {}, - } - for _, rec in pairs(CoaExporterItemCache.entries) do - table.insert(out.entries, rec) - end - return out -end - -function AE.ItemsReset() - CoaExporterItemCache = { - entries = {}, - meta = { lastScanAt = nil, lastRange = nil, resolved = 0, unresolved = {} }, - } -end - -function AE.ItemsCount() - local n = 0 - for _ in pairs(CoaExporterItemCache.entries) do n = n + 1 end - return n -end - -AE._loadedItems = true diff --git a/CoaExporter/Core.lua b/CoaExporter/Core.lua index af90b89..99d6877 100644 --- a/CoaExporter/Core.lua +++ b/CoaExporter/Core.lua @@ -381,8 +381,6 @@ CoaExporter commands: /coae catalog all|skills|talents|icons /coae catalog dispels [class]|passives [class]|status /coae scrolls scan|export|reset|status -/coae items scan [missing | | ] -/coae items export|reset|status /coae sv on|off (SavedVariables for character export) /coae debug /coae help @@ -483,55 +481,6 @@ local function HandleScrolls(rest) 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 | | ]") - 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 @@ -540,11 +489,10 @@ local function HandleDebug() 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", + add(string.format("- Loaded: talents=%s spellbook=%s gear=%s enchants=%s scrolls=%s probe=%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))) @@ -575,7 +523,6 @@ local function HandleDebug() 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", @@ -606,7 +553,6 @@ local function Dispatch(msg) 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")