fix(quest): skip stale frames in Show/HideQuestIcons instead of crashing

ShowQuestIcons/HideQuestIcons indexed icon (=_G[frameName]) when it can
be nil for a registry entry whose frame was already reset, crashing the
fade pass (attempt to index local 'icon'). Guard icon/icon.data/
icon.data.QuestData and skip stale frames; ShowQuestIcons no longer
error()s on the desync.
This commit is contained in:
Xurkon
2026-06-12 00:57:18 -05:00
parent 49774336cc
commit 152e823c04
2 changed files with 28 additions and 20 deletions
+1
View File
@@ -41,6 +41,7 @@
### Bug Fixes
- **[Fix - Fade Pass Crash On Stale Frame]** Hardened `ShowQuestIcons`/`HideQuestIcons` against a stale-frame desync (`attempt to index local 'icon'`): a frame name lingering in the registry after its frame was reset is now skipped gracefully instead of erroring out of the whole fade pass (and `ShowQuestIcons` no longer `error()`s on the desync), with nil-guards on `icon`/`icon.data`/`icon.data.QuestData`.
- **[Fix - Tooltip Crash On Unit Hover]** Fixed a crash on every unit tooltip (`attempt to call global '_TooltipHasLeftLine'`): the dedupe helper was defined *after* `AddUnitDataToTooltip`, so that function couldn't see the local. It's now defined before all `Add*DataToTooltip` functions.
- **[Tooltip - Robust ID Resolution For Hex/Object GUIDs]** Tooltip NPC IDs are now resolved with the learner's robust GUID parser (`QuestieLearner:GetIdAndTypeFromGUID`, now public) as a fallback when the naive `strsplit("-", guid)` can't parse the GUID — e.g. legacy `0x` hex GUIDs — which previously left the NPC ID line silently unwritten. The Object ID line gained a matching fallback: when a hovered object's name isn't in the lookup, it resolves the ID from the object's GUID via the new `QuestieLearner:GetObjectIdFromGUID`. Both ID lines are also deduped per tooltip.
- **[Tooltip - ID Lines No Longer Vanish On Re-Hover]** The "Item ID" / "NPC ID" lines were only added when the hovered item/unit *changed* (`lastItemId`/`lastGuid` gate). But WoW clears and re-fires `OnTooltipSetItem`/`OnTooltipSetUnit` for the *same* item/unit, rebuilding the tooltip — and the gate skipped re-adding the line on the rebuilt tooltip, so the ID disappeared (e.g. hovering the same quest item twice). The ID lines are now (re)added on every render and deduplicated per tooltip (via `_TooltipHasLeftLine`), so they're always present exactly once.
+27 -20
View File
@@ -120,13 +120,15 @@ function _QuestieQuest:ShowQuestIcons()
while _ do -- this may seem a bit expensive, but its actually really fast due to the order things are checked
---@type IconFrame
local icon = _G[frameName];
if not icon.data then
error("Desync! Icon has not been removed correctly, but has already been reset. Skipping frame \"" ..
frameName .. "\" for quest " .. questId)
if (icon == nil) or (not icon.data) then
-- Desync: the frame was reset/removed but its name lingered in the registry.
-- Skip it gracefully (previously this errored out of the whole show pass).
Questie:Debug(Questie.DEBUG_DEVELOP,
"[QuestieQuest:ShowQuestIcons] Skipping stale frame", tostring(frameName), "for quest", questId)
else
local objectiveString = tostring(questId) .. " " .. tostring(icon.data.ObjectiveIndex)
if (not Questie.db.char.TrackerHiddenObjectives) or (not Questie.db.char.TrackerHiddenObjectives[objectiveString]) then
if icon ~= nil and icon.hidden and (not icon:ShouldBeHidden()) then
if icon.hidden and (not icon:ShouldBeHidden()) then
icon:FakeShow()
if icon.data.lineFrames then
@@ -137,7 +139,7 @@ function _QuestieQuest:ShowQuestIcons()
end
end
end
if (icon.data.QuestData.FadeIcons or (icon.data.ObjectiveData and icon.data.ObjectiveData.FadeIcons)) and icon.data.Type ~= "complete" then
if ((icon.data.QuestData and icon.data.QuestData.FadeIcons) or (icon.data.ObjectiveData and icon.data.ObjectiveData.FadeIcons)) and icon.data.Type ~= "complete" then
icon:FadeOut()
else
icon:FadeIn()
@@ -172,25 +174,30 @@ function _QuestieQuest:HideQuestIcons()
local __, frameName = next(frameList)
while __ do -- this may seem a bit expensive, but its actually really fast due to the order things are checked
local icon = _G[frameName];
if icon ~= nil and (not icon.hidden) and icon:ShouldBeHidden() then -- check for function to make sure its a frame
-- Hides Objective Icons
icon:FakeHide()
-- icon can be nil here if the frame was reset/removed while its name lingered in
-- the registry (a stale entry). Guard every access so a desync just skips the
-- frame instead of erroring out of the whole fade pass.
if icon ~= nil and icon.data then
if (not icon.hidden) and icon:ShouldBeHidden() then -- check for function to make sure its a frame
-- Hides Objective Icons
icon:FakeHide()
-- Hides Objective Tooltips
QuestieTooltips:RemoveQuest(icon.data.Id)
-- Hides Objective Tooltips
QuestieTooltips:RemoveQuest(icon.data.Id)
if icon.data.lineFrames then
local ___, lineIcon = next(icon.data.lineFrames)
while ___ do
lineIcon:FakeHide()
___, lineIcon = next(icon.data.lineFrames, ___)
if icon.data.lineFrames then
local ___, lineIcon = next(icon.data.lineFrames)
while ___ do
lineIcon:FakeHide()
___, lineIcon = next(icon.data.lineFrames, ___)
end
end
end
end
if (icon.data.QuestData.FadeIcons or (icon.data.ObjectiveData and icon.data.ObjectiveData.FadeIcons)) and icon.data.Type ~= "complete" then
icon:FadeOut()
else
icon:FadeIn()
if ((icon.data.QuestData and icon.data.QuestData.FadeIcons) or (icon.data.ObjectiveData and icon.data.ObjectiveData.FadeIcons)) and icon.data.Type ~= "complete" then
icon:FadeOut()
else
icon:FadeIn()
end
end
__, frameName = next(frameList, __)
end