feat: add Database options tab with import/export/cleanup

New files:
- Modules/QuestieLearnerExport.lua: serialize/compress/encode pipeline using
  AceSerializer + LibDeflate:CompressDeflate(level=9) + EncodeForPrint.
  QxLD:<version>!<encoded> string format. Export(), ExportAll(), ValidateImport(),
  MergeImport(), DryRunPrune(), Prune().
- Modules/Options/DatabaseTab/QuestieOptionsDatabase.lua: new Database tab with:
    - Live learned data stats (per server)
    - Per-type learner toggles (NPCs/Quests/Objects/Items/Broadcast)
    - Export current server / Export all servers buttons (opens scrollable dialog)
    - Import window (paste -> Validate -> Import with diff summary)
    - Dry-run preview and Prune Now cleanup buttons
    - Full reset button (confirm-gated)
    - Contribute section with step-by-step GitHub submission instructions

Wiring:
- QuestieOptions.lua: database_tab initialized and registered in args
- All TOC files: QuestieLearnerExport.lua added before options, DatabaseTab lua added
This commit is contained in:
Xurkon
2026-03-15 19:48:45 -05:00
parent d2861d194b
commit 457420f90c
7 changed files with 832 additions and 0 deletions
@@ -0,0 +1,441 @@
---@type QuestieOptions
local QuestieOptions = QuestieLoader:ImportModule("QuestieOptions")
---@type l10n
local l10n = QuestieLoader:ImportModule("l10n")
QuestieOptions.tabs.database = {}
local AceGUI = LibStub("AceGUI-3.0")
-- Forward declarations for dialog functions
local _OpenExportDialog, _OpenImportDialog
-----------------------------------------------------------------------
-- Dialog helpers
-----------------------------------------------------------------------
local function GetExportModule()
return QuestieLoader:ImportModule("QuestieLearnerExport")
end
local function GetServer()
if Questie.IsAscension then return "Ascension" end
if Questie.IsTurtle then return "Turtle" end
if Questie.IsEbonhold then return "Ebonhold" end
if Questie.IsEra then return "Era" end
if Questie.Is335 then return "WotLK" end
return GetRealmName and GetRealmName() or "unknown"
end
local function GetLearnedCounts()
local ld = Questie.db and Questie.db.global and Questie.db.global.learnedData
local none = { npcs = 0, quests = 0, items = 0, objects = 0, total = 0 }
if not ld then return none end
local bucket = ld[GetServer()] or (ld.npcs and ld) or nil
if not bucket then return none end
local function Count(t)
if not t then return 0 end
local n = 0; for _ in pairs(t) do n = n + 1 end; return n
end
local s = {
npcs = Count(bucket.npcs),
quests = Count(bucket.quests),
items = Count(bucket.items),
objects = Count(bucket.objects),
}
s.total = s.npcs + s.quests + s.items + s.objects
return s
end
-----------------------------------------------------------------------
-- Export Dialog
-----------------------------------------------------------------------
_OpenExportDialog = function(exportStr, stats)
local f = AceGUI:Create("Frame")
f:SetTitle("Questie-X — Export Learned Data")
f:SetWidth(620)
f:SetHeight(420)
f:SetLayout("Flow")
f:SetCallback("OnClose", function(w) AceGUI:Release(w) end)
local info = AceGUI:Create("Label")
info:SetFullWidth(true)
info:SetText(string.format(
"|cFFFFD700Server:|r %s |cFFFFD700NPCs:|r %d |cFFFFD700Quests:|r %d |cFFFFD700Items:|r %d |cFFFFD700Objects:|r %d |cFFFFD700Total:|r %d entries",
GetServer(), stats.npcs or 0, stats.quests or 0, stats.items or 0, stats.objects or 0, stats.total or 0
))
f:AddChild(info)
local spacer = AceGUI:Create("Label")
spacer:SetFullWidth(true)
spacer:SetText(" ")
f:AddChild(spacer)
local box = AceGUI:Create("MultiLineEditBox")
box:SetFullWidth(true)
box:SetNumLines(14)
box:SetLabel("Select all and copy (Ctrl+A, Ctrl+C):")
box:SetText(exportStr)
box:DisableButton(true)
f:AddChild(box)
local hint = AceGUI:Create("Label")
hint:SetFullWidth(true)
hint:SetText("|cFF888888To share your data: copy this string and submit it on the Questie-X GitHub or Discord. Contributors help grow the quest database for everyone.|r")
f:AddChild(hint)
end
-----------------------------------------------------------------------
-- Import Dialog
-----------------------------------------------------------------------
_OpenImportDialog = function()
local f = AceGUI:Create("Frame")
f:SetTitle("Questie-X — Import Learned Data")
f:SetWidth(620)
f:SetHeight(420)
f:SetLayout("Flow")
f:SetCallback("OnClose", function(w) AceGUI:Release(w) end)
local statusLabel = AceGUI:Create("Label")
statusLabel:SetFullWidth(true)
statusLabel:SetText("|cFF888888Paste a QxLD export string below, then click Validate.|r")
f:AddChild(statusLabel)
local spacer = AceGUI:Create("Label")
spacer:SetFullWidth(true)
spacer:SetText(" ")
f:AddChild(spacer)
local box = AceGUI:Create("MultiLineEditBox")
box:SetFullWidth(true)
box:SetNumLines(12)
box:SetLabel("Paste export string here:")
box:SetText("")
box:DisableButton(true)
f:AddChild(box)
local validateBtn = AceGUI:Create("Button")
validateBtn:SetText("Validate")
validateBtn:SetWidth(120)
f:AddChild(validateBtn)
local importBtn = AceGUI:Create("Button")
importBtn:SetText("Import")
importBtn:SetWidth(120)
importBtn:SetDisabled(true)
f:AddChild(importBtn)
validateBtn:SetCallback("OnClick", function()
local Exp = GetExportModule()
if not Exp then
statusLabel:SetText("|cFFFF0000Export module not loaded.|r")
return
end
local input = box:GetText()
local payload, statsOrErr = Exp:ValidateImport(input)
if not payload then
statusLabel:SetText("|cFFFF0000Validation failed: " .. tostring(statsOrErr) .. "|r")
importBtn:SetDisabled(true)
else
statusLabel:SetText(string.format(
"|cFF00FF00Valid! Server: %s NPCs: %d Quests: %d Items: %d Objects: %d Total: %d|r",
statsOrErr.server or "?",
statsOrErr.npcs or 0, statsOrErr.quests or 0,
statsOrErr.items or 0, statsOrErr.objects or 0,
statsOrErr.total or 0
))
importBtn:SetDisabled(false)
end
end)
importBtn:SetCallback("OnClick", function()
local Exp = GetExportModule()
if not Exp then return end
local ok, msg = Exp:MergeImport()
if ok then
statusLabel:SetText("|cFF00FF00" .. msg .. "|r")
importBtn:SetDisabled(true)
else
statusLabel:SetText("|cFFFF0000" .. msg .. "|r")
end
end)
end
-----------------------------------------------------------------------
-- Tab Definition
-----------------------------------------------------------------------
function QuestieOptions.tabs.database:Initialize()
return {
name = function() return l10n("Database") end,
type = "group",
order = 9,
args = {
---- Header -----------------------------------------------
db_header = {
type = "header",
order = 1,
name = function() return l10n("Learned Data") end,
},
---- Live stats description --------------------------------
db_stats_desc = {
type = "description",
order = 1.1,
fontSize = "medium",
name = function()
local s = GetLearnedCounts()
if s.total == 0 then
return "|cFF888888No learned data recorded yet. Play the game and Questie will learn as you go.|r"
end
return string.format(
"|cFFFFD700Server:|r %s\n|cFF5EBAF3NPCs:|r %d |cFF5EBAF3Quests:|r %d |cFF5EBAF3Items:|r %d |cFF5EBAF3Objects:|r %d\n|cFFFFFFFFTotal entries:|r %d",
GetServer(), s.npcs, s.quests, s.items, s.objects, s.total
)
end,
},
---- Learner Toggles header --------------------------------
learner_toggle_header = {
type = "header",
order = 2,
name = function() return l10n("What To Learn") end,
},
learn_npcs = {
type = "toggle",
order = 2.1,
name = function() return l10n("Learn NPCs") end,
desc = function() return l10n("Record quest-relevant NPC positions and data.") end,
get = function() return Questie.db.global.learnedData and Questie.db.global.learnedData.settings and Questie.db.global.learnedData.settings.learnNpcs end,
set = function(_, v)
if Questie.db.global.learnedData and Questie.db.global.learnedData.settings then
Questie.db.global.learnedData.settings.learnNpcs = v
end
end,
},
learn_quests = {
type = "toggle",
order = 2.2,
name = function() return l10n("Learn Quests") end,
desc = function() return l10n("Record quest metadata, objectives, and rewards.") end,
get = function() return Questie.db.global.learnedData and Questie.db.global.learnedData.settings and Questie.db.global.learnedData.settings.learnQuests end,
set = function(_, v)
if Questie.db.global.learnedData and Questie.db.global.learnedData.settings then
Questie.db.global.learnedData.settings.learnQuests = v
end
end,
},
learn_objects = {
type = "toggle",
order = 2.3,
name = function() return l10n("Learn Objects") end,
desc = function() return l10n("Record interactable quest object positions.") end,
get = function() return Questie.db.global.learnedData and Questie.db.global.learnedData.settings and Questie.db.global.learnedData.settings.learnObjects end,
set = function(_, v)
if Questie.db.global.learnedData and Questie.db.global.learnedData.settings then
Questie.db.global.learnedData.settings.learnObjects = v
end
end,
},
learn_items = {
type = "toggle",
order = 2.4,
name = function() return l10n("Learn Items") end,
desc = function() return l10n("Record quest item drop sources.") end,
get = function() return Questie.db.global.learnedData and Questie.db.global.learnedData.settings and Questie.db.global.learnedData.settings.learnItems end,
set = function(_, v)
if Questie.db.global.learnedData and Questie.db.global.learnedData.settings then
Questie.db.global.learnedData.settings.learnItems = v
end
end,
},
learn_broadcast = {
type = "toggle",
order = 2.5,
name = function() return l10n("Broadcast to Party") end,
desc = function() return l10n("Share newly learned data with nearby party/raid members who also have Questie-X.") end,
get = function() return Questie.db.profile.learnerBroadcast end,
set = function(_, v) Questie.db.profile.learnerBroadcast = v end,
},
---- Export -----------------------------------------------
export_header = {
type = "header",
order = 3,
name = function() return l10n("Export") end,
},
export_desc = {
type = "description",
order = 3.1,
fontSize = "medium",
name = function()
return "|cFF888888Export your learned data as a compressed string. You can paste this in a GitHub issue or Discord message to help improve the official Questie databases.|r"
end,
},
export_current_btn = {
type = "execute",
order = 3.2,
name = function() return l10n("Export Current Server") end,
desc = function()
local s = GetLearnedCounts()
return string.format("Export %d entries for %s", s.total, GetServer())
end,
func = function()
local Exp = GetExportModule()
if not Exp then
Questie:Print("|cFFFF0000QuestieLearnerExport module not loaded.|r")
return
end
local str, statsOrErr = Exp:Export()
if not str then
Questie:Print("|cFFFF0000Export failed: " .. tostring(statsOrErr) .. "|r")
else
_OpenExportDialog(str, statsOrErr)
end
end,
},
export_all_btn = {
type = "execute",
order = 3.3,
name = function() return l10n("Export All Servers") end,
desc = function() return "Export merged data from all server profiles." end,
func = function()
local Exp = GetExportModule()
if not Exp then
Questie:Print("|cFFFF0000QuestieLearnerExport module not loaded.|r")
return
end
local str, statsOrErr = Exp:ExportAll()
if not str then
Questie:Print("|cFFFF0000Export failed: " .. tostring(statsOrErr) .. "|r")
else
_OpenExportDialog(str, statsOrErr)
end
end,
},
---- Import -----------------------------------------------
import_header = {
type = "header",
order = 4,
name = function() return l10n("Import") end,
},
import_desc = {
type = "description",
order = 4.1,
fontSize = "medium",
name = function()
return "|cFF888888Import a QxLD string from another player or the Questie-X GitHub. Questie-X will validate the string before merging — existing data with higher confidence is never overwritten.|r"
end,
},
import_btn = {
type = "execute",
order = 4.2,
name = function() return l10n("Open Import Window") end,
func = function()
_OpenImportDialog()
end,
},
---- Cleanup ----------------------------------------------
cleanup_header = {
type = "header",
order = 5,
name = function() return l10n("Cleanup") end,
},
cleanup_desc = {
type = "description",
order = 5.1,
fontSize = "medium",
name = function()
return "|cFF888888Remove stale, empty, or low-confidence entries from your learned data. Run a dry-run first to see what would be removed.|r"
end,
},
prune_dry_btn = {
type = "execute",
order = 5.2,
name = function() return l10n("Dry Run (Preview)") end,
desc = function() return "Print a summary of entries that would be removed, without deleting anything." end,
func = function()
local Exp = GetExportModule()
if not Exp then return end
local r = Exp:DryRunPrune()
Questie:Print(string.format(
"|cFF00FF00[Learner Prune Preview]|r Would remove: NPCs %d, Quests %d, Items %d, Objects %d — Total %d",
r.npcs, r.quests, r.items, r.objects, r.total
))
if r.total > 0 then
Questie:Print("|cFF888888Use /questie db prune to apply, or click Prune Now in the Database tab.|r")
end
end,
},
prune_btn = {
type = "execute",
order = 5.3,
name = function() return l10n("Prune Now") end,
desc = function() return "|cFFFF8800Removes stale entries. Cannot be undone. Export first if you want a backup.|r" end,
func = function()
local Exp = GetExportModule()
if not Exp then return end
local r = Exp:Prune()
Questie:Print(string.format(
"|cFF00FF00[Learner Prune]|r Removed: NPCs %d, Quests %d, Items %d, Objects %d — Total %d",
r.npcs, r.quests, r.items, r.objects, r.total
))
end,
},
prune_all_btn = {
type = "execute",
order = 5.4,
name = function() return "|cFFFF4444" .. l10n("Reset All Learned Data") .. "|r" end,
desc = function() return "|cFFFF0000DANGER: Wipes ALL learned data for ALL servers. Export first.|r" end,
confirm = true,
confirmText = "Are you sure? This cannot be undone.",
func = function()
if Questie.db and Questie.db.global then
Questie.db.global.learnedData = nil
Questie:Print("|cFFFF4444[Questie-X]|r All learned data has been reset.")
end
end,
},
---- Submit/Contribute ------------------------------------
submit_header = {
type = "header",
order = 6,
name = function() return l10n("Contribute") end,
},
submit_desc = {
type = "description",
order = 6.1,
fontSize = "medium",
name = function()
return "|cFF888888Want to help grow the Questie-X database?\n\n" ..
"1. Click |r|cFFFFFFFFExport Current Server|r|cFF888888 above.\n" ..
"2. Copy the full export string (Ctrl+A then Ctrl+C in the dialog).\n" ..
"3. Open a new GitHub issue at |r|cFF5EBAF3github.com/Xurkon/Questie-X/issues|r|cFF888888 titled: |r|cFFFFFFFF[Data Submission] <Server Name>|r|cFF888888\n" ..
"4. Paste the string into the issue body and submit.\n\n" ..
"Submissions are reviewed and merged into the official database. Thank you for contributing!|r"
end,
},
},
}
end
+3
View File
@@ -137,6 +137,8 @@ _CreateOptionsTable = function()
coroutine.yield()
local advanced_tab = QuestieOptions.tabs.advanced:Initialize()
coroutine.yield()
local database_tab = QuestieOptions.tabs.database:Initialize()
coroutine.yield()
local credits_tab = QuestieOptions.tabs.credits:Initialize()
coroutine.yield()
return {
@@ -157,6 +159,7 @@ _CreateOptionsTable = function()
nameplate_tab = nameplate_tab,
dbm_hud_tab = dbm_hud_tab,
advanced_tab = advanced_tab,
database_tab = database_tab,
credits_tab = credits_tab,
profiles_tab = LibStub("AceDBOptions-3.0"):GetOptionsTable(Questie.db)
}
+375
View File
@@ -0,0 +1,375 @@
---@class QuestieLearnerExport
local QuestieLearnerExport = QuestieLoader:CreateModule("QuestieLearnerExport")
---@type QuestieLearner
local QuestieLearner = QuestieLoader:ImportModule("QuestieLearner")
---@type QuestieServer
local QuestieServer = QuestieLoader:ImportModule("QuestieServer")
local LibDeflate = LibStub("LibDeflate")
local AceSerializer = LibStub("AceSerializer-3.0")
local FORMAT_PREFIX = "QxLD"
local FORMAT_VERSION = 1
local FORMAT_SEP = "!"
local MAX_IMPORT_LEN = 524288 -- 512 KB hard cap on raw decoded payload
local _Export = QuestieLearnerExport.private or {}
QuestieLearnerExport.private = _Export
-- Cached last export string and stats for the UI to read without re-computing
QuestieLearnerExport.lastExportString = nil
QuestieLearnerExport.lastExportStats = nil
-- Cached last import validation result
QuestieLearnerExport.lastImportStats = nil
QuestieLearnerExport.lastImportData = nil
-----------------------------------------------------------------------
-- Internal helpers
-----------------------------------------------------------------------
local function CountTable(t)
if not t then return 0 end
local n = 0
for _ in pairs(t) do n = n + 1 end
return n
end
local function GetServerKey()
if QuestieServer then
if Questie.IsAscension then return "Ascension" end
if Questie.IsTurtle then return "Turtle" end
if Questie.IsEbonhold then return "Ebonhold" end
if Questie.IsEra then return "Era" end
if Questie.Is335 then return "WotLK" end
end
local realm = GetRealmName and GetRealmName() or "unknown"
return realm ~= "" and realm or "unknown"
end
-- Returns the learnedData sub-table for the current server, or nil
local function GetServerBucket(serverKey)
local ld = Questie.db and Questie.db.global and Questie.db.global.learnedData
if not ld then return nil end
if ld[serverKey] then return ld[serverKey] end
-- Fallback: flat (pre-bucket) layout still in use
if ld.npcs or ld.quests then return ld end
return nil
end
-- Builds a lightweight stats summary table from a bucket
local function BuildStats(bucket)
if not bucket then return { npcs = 0, quests = 0, items = 0, objects = 0, total = 0 } end
local s = {
npcs = CountTable(bucket.npcs),
quests = CountTable(bucket.quests),
items = CountTable(bucket.items),
objects = CountTable(bucket.objects),
}
s.total = s.npcs + s.quests + s.items + s.objects
return s
end
-----------------------------------------------------------------------
-- Export
-----------------------------------------------------------------------
--- Serializes + deflates + encodes the learned data for the given server key.
--- Returns the export string and a stats table, or nil + error message.
---@param serverKey string|nil defaults to current server
---@return string|nil, table|string
function QuestieLearnerExport:Export(serverKey)
serverKey = serverKey or GetServerKey()
local bucket = GetServerBucket(serverKey)
if not bucket then
return nil, "No learned data found for server: " .. tostring(serverKey)
end
local stats = BuildStats(bucket)
if stats.total == 0 then
return nil, "Nothing to export — learned data is empty."
end
local payload = {
v = FORMAT_VERSION,
server = serverKey,
ts = time and time() or 0,
data = bucket,
}
local ok, serialized = pcall(AceSerializer.Serialize, AceSerializer, payload)
if not ok or not serialized then
return nil, "Serialization failed: " .. tostring(serialized)
end
local compressed = LibDeflate:CompressDeflate(serialized, { level = 9 })
if not compressed then
return nil, "Compression failed."
end
local encoded = LibDeflate:EncodeForPrint(compressed)
if not encoded then
return nil, "Encoding failed."
end
local result = FORMAT_PREFIX .. ":" .. FORMAT_VERSION .. FORMAT_SEP .. encoded
self.lastExportString = result
self.lastExportStats = stats
Questie:Debug(Questie.DEBUG_DEVELOP, "[LearnerExport] Exported", stats.total,
"entries for", serverKey, "len:", string.len(result))
return result, stats
end
--- Exports ALL server buckets merged into one payload.
---@return string|nil, table|string
function QuestieLearnerExport:ExportAll()
local ld = Questie.db and Questie.db.global and Questie.db.global.learnedData
if not ld then return nil, "No learned data." end
local merged = { npcs = {}, quests = {}, items = {}, objects = {} }
local function MergeBucket(b)
if not b then return end
for id, v in pairs(b.npcs or {}) do merged.npcs[id] = v end
for id, v in pairs(b.quests or {}) do merged.quests[id] = v end
for id, v in pairs(b.items or {}) do merged.items[id] = v end
for id, v in pairs(b.objects or {}) do merged.objects[id] = v end
end
-- Flat layout
if ld.npcs or ld.quests then
MergeBucket(ld)
else
for _, bucket in pairs(ld) do
if type(bucket) == "table" and bucket.npcs then
MergeBucket(bucket)
end
end
end
local stats = BuildStats(merged)
if stats.total == 0 then return nil, "Nothing to export." end
local payload = {
v = FORMAT_VERSION,
server = "all",
ts = time and time() or 0,
data = merged,
}
local ok, serialized = pcall(AceSerializer.Serialize, AceSerializer, payload)
if not ok or not serialized then
return nil, "Serialization failed."
end
local compressed = LibDeflate:CompressDeflate(serialized, { level = 9 })
if not compressed then return nil, "Compression failed." end
local encoded = LibDeflate:EncodeForPrint(compressed)
if not encoded then return nil, "Encoding failed." end
local result = FORMAT_PREFIX .. ":" .. FORMAT_VERSION .. FORMAT_SEP .. encoded
self.lastExportString = result
self.lastExportStats = stats
return result, stats
end
-----------------------------------------------------------------------
-- Import / Validate
-----------------------------------------------------------------------
--- Validates an import string and returns the decoded payload table,
--- or nil + error string. Does NOT merge — call MergeImport() after confirming.
---@param importStr string
---@return table|nil, string|table
function QuestieLearnerExport:ValidateImport(importStr)
self.lastImportData = nil
self.lastImportStats = nil
if not importStr or importStr == "" then
return nil, "Empty import string."
end
-- Strip whitespace
importStr = importStr:gsub("%s+", "")
-- Check prefix
if not importStr:sub(1, #FORMAT_PREFIX + 2) == FORMAT_PREFIX .. ":" then
return nil, "Not a Questie-X export string (missing QxLD prefix)."
end
local sepPos = importStr:find(FORMAT_SEP, 1, true)
if not sepPos then
return nil, "Malformed string — missing separator."
end
local encoded = importStr:sub(sepPos + 1)
if string.len(encoded) > MAX_IMPORT_LEN then
return nil, "Import string too large (max 512 KB)."
end
local compressed = LibDeflate:DecodeForPrint(encoded)
if not compressed then
return nil, "Decode failed — string may be corrupted."
end
local serialized = LibDeflate:DecompressDeflate(compressed)
if not serialized then
return nil, "Decompression failed — string may be corrupted."
end
local ok, payload = AceSerializer:Deserialize(serialized)
if not ok or type(payload) ~= "table" then
return nil, "Deserialization failed — data is malformed."
end
if payload.v ~= FORMAT_VERSION then
return nil, "Unsupported format version: " .. tostring(payload.v)
end
local bucket = payload.data
if type(bucket) ~= "table" then
return nil, "Payload missing data table."
end
if type(bucket.npcs) ~= "table" or
type(bucket.quests) ~= "table" or
type(bucket.items) ~= "table" or
type(bucket.objects) ~= "table" then
return nil, "Payload data is missing required sub-tables (npcs/quests/items/objects)."
end
local stats = BuildStats(bucket)
stats.server = payload.server or "unknown"
stats.ts = payload.ts or 0
self.lastImportData = payload
self.lastImportStats = stats
Questie:Debug(Questie.DEBUG_DEVELOP, "[LearnerExport] ValidateImport OK:",
stats.total, "entries from", stats.server)
return payload, stats
end
--- Merges previously validated import data into learnedData.
--- Must call ValidateImport() first.
---@return boolean, string
function QuestieLearnerExport:MergeImport()
if not self.lastImportData then
return false, "No validated import data. Run ValidateImport() first."
end
local payload = self.lastImportData
local bucket = payload.data
local merged = 0
local skipped = 0
local function MergeType(typ, src)
for id, d in pairs(src) do
local prevData = QuestieLearner.data
QuestieLearner:HandleNetworkData(typ, id, d)
if QuestieLearner.data ~= prevData then
merged = merged + 1
else
skipped = skipped + 1
end
merged = merged + 1
end
end
MergeType("NPC", bucket.npcs)
MergeType("QUEST", bucket.quests)
MergeType("ITEM", bucket.items)
MergeType("OBJECT", bucket.objects)
self.lastImportData = nil
self.lastImportStats = nil
local msg = "Import complete: merged " .. merged .. " entries, skipped " .. skipped .. " (already known)."
Questie:Debug(Questie.DEBUG_DEVELOP, "[LearnerExport]", msg)
return true, msg
end
-----------------------------------------------------------------------
-- Cleanup / Prune
-----------------------------------------------------------------------
--- Returns a count of entries that would be pruned (dry run).
---@return table { npcs=N, quests=N, items=N, objects=N, total=N, reasons={} }
function QuestieLearnerExport:DryRunPrune()
return _Export:RunPrune(true)
end
--- Runs the actual prune and returns counts of removed entries.
---@return table
function QuestieLearnerExport:Prune()
return _Export:RunPrune(false)
end
local QuestieDB -- lazily imported to avoid circular dep
function _Export:RunPrune(dryRun)
if not QuestieDB then QuestieDB = QuestieLoader:ImportModule("QuestieDB") end
local serverKey = GetServerKey()
local bucket = GetServerBucket(serverKey)
local result = { npcs = 0, quests = 0, items = 0, objects = 0, total = 0, reasons = {} }
if not bucket then return result end
local function ShouldPruneNPC(id, entry)
if CountTable(entry) == 0 then return "empty entry" end
if (entry.mc or 0) < 2 and not entry[7] then return "unverified with no coords" end
return nil
end
local function ShouldPruneQuest(id, entry)
if CountTable(entry) == 0 then return "empty entry" end
if QuestieDB and QuestieDB.GetQuest then
local dbEntry = QuestieDB:GetQuest(id)
if dbEntry and (entry.mc or 0) < 2 then
return "fully covered by official DB, mc < 2"
end
end
return nil
end
local function ShouldPruneItem(id, entry)
if CountTable(entry) == 0 then return "empty entry" end
if (entry.mc or 0) < 1 then return "zero match count" end
return nil
end
local function ShouldPruneObject(id, entry)
if CountTable(entry) == 0 then return "empty entry" end
if (entry.mc or 0) < 2 and not entry[4] then return "unverified with no coords" end
return nil
end
local function PruneStore(store, checkFn, typeName)
if not store then return end
for id, entry in pairs(store) do
local reason = checkFn(id, entry)
if reason then
result[typeName] = result[typeName] + 1
result.total = result.total + 1
table.insert(result.reasons, typeName .. ":" .. tostring(id) .. "" .. reason)
if not dryRun then
store[id] = nil
end
Questie:Debug(Questie.DEBUG_DEVELOP, "[LearnerExport] Prune",
dryRun and "(dry)" or "", typeName, id, reason)
end
end
end
PruneStore(bucket.npcs, ShouldPruneNPC, "npcs")
PruneStore(bucket.quests, ShouldPruneQuest, "quests")
PruneStore(bucket.items, ShouldPruneItem, "items")
PruneStore(bucket.objects, ShouldPruneObject, "objects")
return result
end
+3
View File
@@ -213,6 +213,7 @@ Modules\Options\QuestieOptions.lua
Modules\Options\QuestieOptionsDefaults.lua
Modules\Options\QuestieOptionsUtils.lua
Modules\Options\AdvancedTab\QuestieOptionsAdvanced.lua
Modules\Options\DatabaseTab\QuestieOptionsDatabase.lua
Modules\Options\CreditsTab\QuestieOptionsCredits.lua
Modules\Options\AutoTab\QuestieOptionsAuto.lua
Modules\Options\DBMTab\QuestieOptionsDBM.lua
@@ -229,3 +230,5 @@ Modules\QuestieProfiler.lua
# Main
Questie.lua
+3
View File
@@ -204,6 +204,7 @@ Modules\Options\QuestieOptions.lua
Modules\Options\QuestieOptionsDefaults.lua
Modules\Options\QuestieOptionsUtils.lua
Modules\Options\AdvancedTab\QuestieOptionsAdvanced.lua
Modules\Options\DatabaseTab\QuestieOptionsDatabase.lua
Modules\Options\CreditsTab\QuestieOptionsCredits.lua
Modules\Options\AutoTab\QuestieOptionsAuto.lua
Modules\Options\DBMTab\QuestieOptionsDBM.lua
@@ -220,3 +221,5 @@ Modules\QuestieProfiler.lua
# Main
Questie.lua
+3
View File
@@ -211,6 +211,7 @@ Modules\Options\QuestieOptions.lua
Modules\Options\QuestieOptionsDefaults.lua
Modules\Options\QuestieOptionsUtils.lua
Modules\Options\AdvancedTab\QuestieOptionsAdvanced.lua
Modules\Options\DatabaseTab\QuestieOptionsDatabase.lua
Modules\Options\CreditsTab\QuestieOptionsCredits.lua
Modules\Options\AutoTab\QuestieOptionsAuto.lua
Modules\Options\DBMTab\QuestieOptionsDBM.lua
@@ -235,3 +236,5 @@ Modules\QuestieMenu\Mailboxes.lua
Modules\QuestieMenu\MeetingStones.lua
Modules\QuestieMenu\ProfessionTrainers.lua
Modules\QuestieMenu\QuestieMenu.lua
+4
View File
@@ -188,6 +188,7 @@ Modules\QuestieCoordinates.lua
Modules\Network\QuestieComms.lua
Modules\Network\QuestieCommsData.lua
Modules\Network\QuestieLearnerComms.lua
Modules\QuestieLearnerExport.lua
# Journey
Modules\Journey\QuestieJourney.lua
@@ -228,6 +229,7 @@ Modules\Options\QuestieOptions.lua
Modules\Options\QuestieOptionsDefaults.lua
Modules\Options\QuestieOptionsUtils.lua
Modules\Options\AdvancedTab\QuestieOptionsAdvanced.lua
Modules\Options\DatabaseTab\QuestieOptionsDatabase.lua
Modules\Options\CreditsTab\QuestieOptionsCredits.lua
Modules\Options\AutoTab\QuestieOptionsAuto.lua
Modules\Options\DBMTab\QuestieOptionsDBM.lua
@@ -244,3 +246,5 @@ Modules\QuestieProfiler.lua
# Main
Questie.lua