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.
This commit is contained in:
2026-07-30 18:26:42 +02:00
parent c6019ea823
commit de270a0e8e
2 changed files with 42 additions and 7 deletions
+13 -2
View File
@@ -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)
+29 -5
View File
@@ -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)