From 30fe4b83a90a36468c1caa29abbe329d08b49da6 Mon Sep 17 00:00:00 2001 From: Narcasung Date: Mon, 17 Aug 2026 15:57:17 +0200 Subject: [PATCH] fix(tracker): name and group quests built from partial records Quests assembled from a Learner entry or a questDataOverrides entry that never captured every field reach the tracker with nil fields, since GetQuest copies rawdata key by key. Two of those show: a nil name printed as the quest id, and a nil zoneOrSort that sent _GetZoneName down its very first line, `if not zoneOrSort then return "Unknown Zone" end`, before it could consult the quest log header the earlier fix added. Those objects are cached for the session, so neither repaired itself. GetQuest now fills a missing name from the quest log and defaults zoneOrSort to 0, the value every caller already reads as "no zone on file" -- and which some of them require, `quest.zoneOrSort > 0` erroring outright on nil. _GetZoneName treats nil the same way rather than short-circuiting, which also stops a nil quest from labelling its group Unknown Zone under the sort modes that do not group by zone at all. The tracker asks the quest log for a title before printing an id, so quests already cached without a name come out right too, and the live-fallback builder stops discarding the override data it just looked up. --- Database/QuestieDB.lua | 48 ++++++++++++++++++++++++++---- Modules/Tracker/QuestieTracker.lua | 20 +++++++++++-- Modules/Tracker/TrackerUtils.lua | 24 ++++++++++++++- 3 files changed, 84 insertions(+), 8 deletions(-) diff --git a/Database/QuestieDB.lua b/Database/QuestieDB.lua index 44ce3b7..72b38f0 100644 --- a/Database/QuestieDB.lua +++ b/Database/QuestieDB.lua @@ -1785,16 +1785,36 @@ function QuestieDB.GetQuest(questId, ...) -- /dump QuestieDB.GetQuest(867) end return nil end - -- Build a minimal live-fallback quest from the quest log so the tracker still works + -- Build a minimal live-fallback quest from the quest log so the tracker still works. + -- The override that got us here is partial by nature -- a Learner record of a single + -- field, a correction -- but whatever it does carry beats guessing, and the quest object + -- built here is cached for the session, so anything left blank stays blank. local logEntry = QuestLogCache.GetQuest(questId) if not logEntry then return nil end + local function NonEmpty(text) + if text and text ~= "" then return text end + return nil + end + local overrideName = NonEmpty(overrideData[QuestieDB.questKeys.name]) + local overrideLevel = overrideData[QuestieDB.questKeys.questLevel] + local overrideZone = overrideData[QuestieDB.questKeys.zoneOrSort] + local cachedTitle = NonEmpty(logEntry.title) + local liveTitle + if (not overrideName) and (not cachedTitle) and GetQuestLogIndexByID and GetQuestLogTitle then + -- QuestLogCache is a snapshot and can be missing the title of a quest the client + -- has since filled in, which is what leaves a quest showing as its own id. + local questLogIndex = GetQuestLogIndexByID(questId) + if questLogIndex and questLogIndex > 0 then + liveTitle = NonEmpty(GetQuestLogTitle(questLogIndex)) + end + end local fallback = { Id = questId, - name = logEntry.title or tostring(questId), - level = logEntry.level or 0, - questLevel = logEntry.level or 0, + name = overrideName or cachedTitle or liveTitle or tostring(questId), + level = overrideLevel or logEntry.level or 0, + questLevel = overrideLevel or logEntry.level or 0, requiredLevel = 0, - zoneOrSort = 0, + zoneOrSort = overrideZone or 0, questFlags = 0, specialFlags = 0, Starts = { CreatureStarts = {}, ObjectStarts = {}, ItemStarts = {} }, @@ -1916,6 +1936,24 @@ function QuestieDB.GetQuest(questId, ...) -- /dump QuestieDB.GetQuest(867) if learnerRecord.objIndex and not QO.objIndex then QO.objIndex = learnerRecord.objIndex end end + -- A record that never captured a name -- a Learner entry written from an objective update, an + -- override carrying a single key -- leaves QO.name nil, and this object is cached for the rest + -- of the session, so everything downstream ends up printing the quest id. Ask the client. + if ((not QO.name) or QO.name == "") and GetQuestLogIndexByID and GetQuestLogTitle then + local questLogIndex = GetQuestLogIndexByID(questId) + if questLogIndex and questLogIndex > 0 then + local logTitle = GetQuestLogTitle(questLogIndex) + if logTitle and logTitle ~= "" then + QO.name = logTitle + end + end + end + + -- Same story for the zone: partial records leave it nil, and callers all treat it as a number + -- (`quest.zoneOrSort > 0` errors outright on nil). 0 is the value everything already reads as + -- "no zone on file", which sends the tracker to the quest log for one. + QO.zoneOrSort = QO.zoneOrSort or 0 + local questLevel, requiredLevel = QuestieLib.GetTbcLevel(questId) QO.level = questLevel QO.requiredLevel = requiredLevel diff --git a/Modules/Tracker/QuestieTracker.lua b/Modules/Tracker/QuestieTracker.lua index 03e9c12..f53f479 100644 --- a/Modules/Tracker/QuestieTracker.lua +++ b/Modules/Tracker/QuestieTracker.lua @@ -63,6 +63,22 @@ local function GetWrappedWidth(label) return label:GetWidth() end +-- A quest built from partial data can reach the tracker with no name, or with the id standing in +-- for one, and it is cached that way for the session. Ask the quest log before printing a number. +local function GetDisplayableQuestName(quest) + local questName = quest.name + + if (not questName) or questName == "" or questName == tostring(quest.Id) then + questName = TrackerUtils:GetQuestLogTitleById(quest.Id) or questName + end + + if (not questName) or questName == "" then + questName = tostring(quest.Id) + end + + return questName +end + local LSM30 = LibStub and LibStub("LibSharedMedia-3.0", true) -- Local Vars @@ -1031,7 +1047,7 @@ function QuestieTracker:Update() if quest.isFallback or quest._isLogFallback then -- Quest not in DB: use the name stored on the fallback object - local questName = quest.name or tostring(quest.Id) + local questName = GetDisplayableQuestName(quest) if Questie.db.profile.trackerShowQuestLevel and quest.level and quest.level > 0 then questName = "[" .. quest.level .. "] " .. questName end @@ -1065,7 +1081,7 @@ function QuestieTracker:Update() -- which queries the static DB only: for a quest with no DB entry it -- silently defaults to level 1, and Ascension's scaling then scales -- that wrong base instead of the quest's real level. - local fallbackName = quest.name or tostring(quest.Id) + local fallbackName = GetDisplayableQuestName(quest) if Questie.db.profile.trackerShowQuestLevel and quest.level and quest.level > 0 then fallbackName = QuestieLib:GetQuestString(quest.Id, fallbackName, quest.level, false) end diff --git a/Modules/Tracker/TrackerUtils.lua b/Modules/Tracker/TrackerUtils.lua index 0066776..7165cca 100644 --- a/Modules/Tracker/TrackerUtils.lua +++ b/Modules/Tracker/TrackerUtils.lua @@ -809,7 +809,13 @@ local function _GetZoneName(zoneOrSort, questId, zoneNameOverride) if zoneNameOverride and zoneNameOverride ~= "" then return zoneNameOverride end - if not zoneOrSort then return "Unknown Zone" end + + -- A quest assembled from a partial record -- a Learner entry that never captured the field, + -- an override carrying a single key -- reaches here with no zoneOrSort at all. That is the + -- same "nothing to look up" case as 0, and the quest log below still knows where the client + -- files the quest, so it must not short-circuit to Unknown Zone ahead of that. + zoneOrSort = zoneOrSort or 0 + local zoneName local sortObj = Questie.db.profile.trackerSortObjectives if sortObj == "byZone" or sortObj == "byZoneComplete" or sortObj == "byZoneCompleteReversed" or sortObj == "byZonePlayerProximity" or sortObj == "byZonePlayerProximityReversed" then @@ -865,6 +871,22 @@ local function _GetZoneName(zoneOrSort, questId, zoneNameOverride) return zoneName end +-- The client's own title for a quest, or nil if the player does not have it. Quest objects can +-- reach the tracker without a usable name -- a questDataOverrides entry that carries no name +-- field, a QuestLogCache row read before the client had filled the title in -- and since those +-- objects are cached for the session, the name never repairs itself. The log always knows. +function TrackerUtils:GetQuestLogTitleById(questId) + local questLogIndex = GetQuestLogIndexForQuest(questId) + if not questLogIndex then return nil end + + local title = GetQuestLogTitle(questLogIndex) + if title and title ~= "" then + return title + end + + return nil +end + -- IsComplete must be a method (called as quest:IsComplete()), and it reads the state the last -- refresh stored rather than closing over the state at build time -- these quests outlive many -- redraws, and a captured value would still claim the quest is unfinished after it is turned in.