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.
This commit is contained in:
Xurkon
2026-05-16 08:36:47 -05:00
parent 3fff65b66f
commit d3bcb6da49
2 changed files with 53 additions and 1 deletions
+8 -1
View File
@@ -15,12 +15,19 @@ local _QuestieDB = QuestieDB.private
-- 1. override[stringKey] (string-keyed, e.g. from wotlkNPCFixes) -- 1. override[stringKey] (string-keyed, e.g. from wotlkNPCFixes)
-- 2. override[intKey] (numeric-keyed, e.g. from QuestieLearner / AscensionDB) -- 2. override[intKey] (numeric-keyed, e.g. from QuestieLearner / AscensionDB)
-- 3. rawdata[intKey] (fallback to compiled DB) -- 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) local function _MergeOverride(result, override, rawdata, keyMap)
for stringKey, intKey in pairs(keyMap) do for stringKey, intKey in pairs(keyMap) do
if override[stringKey] ~= nil then if override[stringKey] ~= nil then
result[stringKey] = override[stringKey] result[stringKey] = override[stringKey]
elseif override[intKey] ~= nil then elseif override[intKey] ~= nil and not IsEmptyTable(override[intKey]) then
result[stringKey] = override[intKey] result[stringKey] = override[intKey]
elseif rawdata then elseif rawdata then
result[stringKey] = rawdata[intKey] result[stringKey] = rawdata[intKey]
+45
View File
@@ -308,6 +308,46 @@ local function _RefreshActiveQuestPins(questIdSet)
end end
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. -- CrossLinkAfterNPC: called when a new NPC is first learned.
-- Scans all learned quests for any reference to this npcId and stitches -- 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 if isNew then
Questie:Debug(Questie.DEBUG_LEARNER, "[QuestieLearner] New NPC learned:", npcId, name or "?") Questie:Debug(Questie.DEBUG_LEARNER, "[QuestieLearner] New NPC learned:", npcId, name or "?")
CrossLinkAfterNPC(npcId) 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 end
_Learner:BroadcastIfCommsAvailable("NPC", npcId, existing) _Learner:BroadcastIfCommsAvailable("NPC", npcId, existing)
end end