From d3bcb6da49d48858be16ea7fbf5f7ea7b53e689c Mon Sep 17 00:00:00 2001 From: Xurkon Date: Sat, 16 May 2026 08:36:47 -0500 Subject: [PATCH] fix: empty override tables no longer wipe base DB spawns; real-time pin refresh on NPC learn Two fixes for QuestieLearner real-time pin rendering: 1. _MergeOverride now skips empty tables (IsEmptyTable guard). QuestieLearner stores spawns={} before any coords are captured. Previously _MergeOverride treated {} as valid data and replaced the base DB's real spawn coordinates with an empty table, breaking pins for all NPCs that had been seen but not killed (mouseovers, etc.). 2. _InvalidateSpawnListsForNPC triggers after LearnNPC adds new spawn data. The quest objective system caches spawnList once per objective. When a kill adds new coordinates, the cached list is stale. The new helper: - Scans all active quest objectives for references to the NPC - Clears objective.spawnList and objective.AlreadySpawned - Calls QuestieQuest:UpdateQuest to rebuild pins with fresh data This makes learned pins appear in real-time without /reload. --- Database/QuestieDB.lua | 9 +++++++- Modules/QuestieLearner.lua | 45 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+), 1 deletion(-) diff --git a/Database/QuestieDB.lua b/Database/QuestieDB.lua index 829a88b..445491f 100644 --- a/Database/QuestieDB.lua +++ b/Database/QuestieDB.lua @@ -15,12 +15,19 @@ local _QuestieDB = QuestieDB.private -- 1. override[stringKey] (string-keyed, e.g. from wotlkNPCFixes) -- 2. override[intKey] (numeric-keyed, e.g. from QuestieLearner / AscensionDB) -- 3. rawdata[intKey] (fallback to compiled DB) +-- +-- Empty tables from QuestieLearner (e.g. spawns={}) are treated as "not present" +-- so the base DB's real spawn data is preserved until actual coordinates are learned. ------------------------------------------------------------------------ +local function IsEmptyTable(val) + return type(val) == "table" and next(val) == nil +end + local function _MergeOverride(result, override, rawdata, keyMap) for stringKey, intKey in pairs(keyMap) do if override[stringKey] ~= nil then result[stringKey] = override[stringKey] - elseif override[intKey] ~= nil then + elseif override[intKey] ~= nil and not IsEmptyTable(override[intKey]) then result[stringKey] = override[intKey] elseif rawdata then result[stringKey] = rawdata[intKey] diff --git a/Modules/QuestieLearner.lua b/Modules/QuestieLearner.lua index f40f40f..4eb8370 100644 --- a/Modules/QuestieLearner.lua +++ b/Modules/QuestieLearner.lua @@ -308,6 +308,46 @@ local function _RefreshActiveQuestPins(questIdSet) end end +-- Invalidates cached objective.spawnList for any active quest whose objectives +-- reference the given npcId. This forces the map system to rebuild spawn lists +-- from QuestieDB on the next update, picking up newly learned coordinates. +local function _InvalidateSpawnListsForNPC(npcId) + if not QuestieQuest or not QuestiePlayer or not QuestiePlayer.currentQuestlog then return end + local timer = (C_Timer) or (QuestieCompat and QuestieCompat.C_Timer) + local questsToRefresh = {} + for questId, _ in pairs(QuestiePlayer.currentQuestlog) do + local quest = QuestieDB.GetQuest and QuestieDB.GetQuest(questId) + if quest and quest.Objectives then + for _, objective in pairs(quest.Objectives) do + local shouldInvalidate = false + -- Monster objectives reference NPCs directly in spawnList keys + if objective.spawnList then + if objective.spawnList[npcId] then + shouldInvalidate = true + end + -- Also check killcredit IdList + if not shouldInvalidate and objective.IdList then + for _, id in ipairs(objective.IdList) do + if id == npcId then shouldInvalidate = true; break end + end + end + end + -- Fallback: if objective Id matches the NPC (some objectives use NPC as their primary Id) + if not shouldInvalidate and objective.Id == npcId then + shouldInvalidate = true + end + if shouldInvalidate then + objective.spawnList = nil + objective.AlreadySpawned = nil + questsToRefresh[questId] = true + break -- one invalidation per quest is enough + end + end + end + end + _RefreshActiveQuestPins(questsToRefresh) +end + ------------------------------------------------------------------------ -- CrossLinkAfterNPC: called when a new NPC is first learned. -- Scans all learned quests for any reference to this npcId and stitches @@ -645,6 +685,11 @@ function QuestieLearner:LearnNPC(npcId, name, level, subName, npcFlags, factionS if isNew then Questie:Debug(Questie.DEBUG_LEARNER, "[QuestieLearner] New NPC learned:", npcId, name or "?") CrossLinkAfterNPC(npcId) + else + -- Existing NPC got new spawn data: invalidate cached spawnLists + -- for any active quest objective that references this NPC so the + -- map system rebuilds them with fresh data on next update. + _InvalidateSpawnListsForNPC(npcId) end _Learner:BroadcastIfCommsAvailable("NPC", npcId, existing) end