From de270a0e8e6924a1d39f4c00627c2131949dab74 Mon Sep 17 00:00:00 2001 From: Narcasung Date: Thu, 30 Jul 2026 18:26:42 +0200 Subject: [PATCH] fix(tracker): drop quests the player no longer has Ascension's special quests are finished by the server the moment their objectives are met, and one of them stayed in the tracker for the rest of the session: currentQuestlog is only as good as the removal events that maintain it, and nothing landed for that quest. The tracker now checks the quest log itself, which it already walks once per draw for the zone headers, and skips anything that is no longer in it, dropping the fallback object built for it as well. Skipped rather than pruned, so a redraw that catches the log mid-refresh does not throw away state Questie is about to want back. Three things on the event side that let it get that far: - CleanupRemovedQuestsFallback now also runs after a full quest log scan. UpdateAllQuests only inspects quests still in the log, so for as long as something kept asking for full scans, nothing looked for removals. - A removed quest counts as completed when the server has it flagged complete. QuestieDB.IsComplete cannot answer for a quest the database has never heard of, so these were filed as abandoned instead. - The removal scan type-checks the quest id before comparing it, so a stray string key cannot error out the pass for every other quest. --- Modules/Quest/QuestEventHandler.lua | 15 +++++++++++-- Modules/Tracker/TrackerUtils.lua | 34 ++++++++++++++++++++++++----- 2 files changed, 42 insertions(+), 7 deletions(-) diff --git a/Modules/Quest/QuestEventHandler.lua b/Modules/Quest/QuestEventHandler.lua index dc3b5a6..d5b02d4 100644 --- a/Modules/Quest/QuestEventHandler.lua +++ b/Modules/Quest/QuestEventHandler.lua @@ -553,6 +553,10 @@ function _QuestEventHandler:QuestLogUpdate() doFullQuestLogScan = false -- Function call updates doFullQuestLogScan. Order matters. _QuestEventHandler:UpdateAllQuests() + -- Also on this path: UpdateAllQuests only looks at quests still in the log, so a removal + -- that fired no event of its own would sit there unnoticed for as long as full scans keep + -- being asked for. + _QuestEventHandler:CleanupRemovedQuestsFallback() else _QuestEventHandler:CleanupRemovedQuestsFallback() QuestieCombatQueue:Queue(function() @@ -666,7 +670,9 @@ function _QuestEventHandler:CleanupRemovedQuestsFallback() if QuestiePlayer and QuestiePlayer.currentQuestlog then local removedQuestIds = {} for questId in pairs(QuestiePlayer.currentQuestlog) do - if questId and questId > 0 and (not gameQuestIds[questId]) then + -- Typed check: a stray string key (saved variables have produced them) would other- + -- wise error on the comparison and take the whole pass down with it. + if type(questId) == "number" and questId > 0 and (not gameQuestIds[questId]) then removedQuestIds[#removedQuestIds + 1] = questId end end @@ -679,7 +685,12 @@ function _QuestEventHandler:CleanupRemovedQuestsFallback() local wasTurnedIn = questLog[questId] and questLog[questId].state == QUEST_LOG_STATES.QUEST_TURNED_IN local wasAlreadyComplete = Questie.db.char.complete and Questie.db.char.complete[questId] local completeAtRemoval = QuestieDB.IsComplete(questId) - local shouldComplete = wasTurnedIn or wasAlreadyComplete or completeAtRemoval == 1 + -- The server's own record, and the only one that knows anything about a quest the + -- database has never heard of: QuestieDB.IsComplete cannot answer for those, so an + -- Ascension quest the server finished by itself would otherwise be filed as abandoned. + local serverFlaggedComplete = IsQuestFlaggedCompleted and IsQuestFlaggedCompleted(questId) + local shouldComplete = wasTurnedIn or wasAlreadyComplete or completeAtRemoval == 1 or + serverFlaggedComplete QuestLogCache.RemoveQuest(questId) QuestieQuest:SetObjectivesDirty(questId) diff --git a/Modules/Tracker/TrackerUtils.lua b/Modules/Tracker/TrackerUtils.lua index 38c2a09..06ba909 100644 --- a/Modules/Tracker/TrackerUtils.lua +++ b/Modules/Tracker/TrackerUtils.lua @@ -681,8 +681,13 @@ end -- the tracker asks per quest and redraws often -- walking the log once per quest is quadratic. local questLogHeaders = {} +-- Every questId the log currently holds, headers aside. The tracker draws from currentQuestlog, +-- and this is what says whether the player still has a given quest. +local questLogQuestIds = {} + local function BuildQuestLogHeaders() local headers = {} + local questIds = {} local header for i = 1, (GetNumQuestLogEntries and GetNumQuestLogEntries() or 0) do @@ -691,17 +696,21 @@ local function BuildQuestLogHeaders() if title and title ~= "" then header = title end - elseif logId and header then - headers[logId] = header + elseif logId then + questIds[logId] = true + if header then + headers[logId] = header + end end end questLogHeaders = headers + questLogQuestIds = questIds end -- Returns the header title string, or nil if the quest is not in the log under one. local function GetQuestLogZoneName(questId) - if not questLogHeaders[questId] then + if not questLogQuestIds[questId] then -- Asked about a quest the last pass did not see, so the log has moved on since. BuildQuestLogHeaders() end @@ -837,8 +846,16 @@ function TrackerUtils:GetSortedQuestIds() local questDetails = {} local sortObj = Questie.db.profile.trackerSortObjectives - -- One walk of the quest log for the whole draw, so the per-quest zone lookups below are reads. + -- One walk of the quest log for the whole draw, so the per-quest lookups below are reads. BuildQuestLogHeaders() + + -- currentQuestlog is only as good as the removal events that maintain it, and a quest the + -- server finishes on its own -- Ascension's auto-complete quests -- can leave the log without + -- any of them landing, which strands the quest in the tracker for the rest of the session. The + -- log is the authority on what the player still has, so anything missing from it is skipped. + -- Skipped rather than pruned: a redraw that catches the log mid-refresh would otherwise throw + -- away state Questie is about to want back. + local questLogIsReadable = next(questLogQuestIds) ~= nil -- Update quest objectives for questId, quest in pairs(QuestiePlayer.currentQuestlog) do @@ -911,7 +928,14 @@ function TrackerUtils:GetSortedQuestIds() end end - if type(quest) == "table" and quest.IsComplete and quest.Objectives then + local isInQuestLog = (not questLogIsReadable) or (questLogQuestIds[qid] == true) + if not isInQuestLog then + -- Left over from a removal nothing told the tracker about, so the object built for it + -- goes too -- otherwise it would still be here to serve the next draw. + TrackerUtils._fallbackQuests[qid] = nil + end + + if isInQuestLog and type(quest) == "table" and quest.IsComplete and quest.Objectives then -- Insert Quest Ids into sortedQuestIds table tinsert(sortedQuestIds, qid)