fix(tooltip): collapse stacked Ascension quest-progress lines (#9)

The Ascension server appends a new progress line on every objective
update instead of replacing it, so tooltips stacked 0/8 1/8 2/8 3/8 for
one objective. New always-on DedupeAscensionProgressLines collapses each
stack to its most-progressed line. It only hides a line when the same
tooltip holds another progress line with the same objective text AND
denominator, so other addons' lines and Questie's own single-line
objectives are never touched. Separate from the opt-in full-strip option
(#16). Unit-tested in Tests/QuestieTooltipDedupe_spec.lua.
This commit is contained in:
Xurkon
2026-06-11 07:43:57 -05:00
parent dc155b6583
commit 77a39b512c
2 changed files with 67 additions and 6 deletions
+1
View File
@@ -40,6 +40,7 @@
### Bug Fixes ### Bug Fixes
- **[Tooltip - Collapse Stacked Ascension Quest-Progress Lines]** (#9) Tooltips could pile up a stack of progress lines for the same objective — `0/8`, `1/8`, `2/8`, `3/8` — because the Ascension server appends a new progress line on every objective update instead of replacing the old one. A new always-on pass (`DedupeAscensionProgressLines`) collapses each stack to its single most-progressed line. It only ever hides a line when the same tooltip holds another progress line with the same objective text and denominator, so it never touches other addons' tooltip lines or Questie's own single-line objectives (unit-tested). This is separate from the opt-in "Hide Ascension quest progress lines" option (#16), which removes such lines entirely.
- **[Map - Completed Quest Pins Stuck On The World Map Until Reload]** (#9) A completed quest/objective's pin could stay on the world map (while the minimap correctly cleared it) until a `/reload`. Cause: a draw-queue race. The world-map and minimap draw queues are processed independently in `ProcessQueue`, so when a frame was unloaded while still queued (e.g. an objective that completes the same tick its pins were queued), the frame's minimap-queue entry could trigger the deferred `Unload` first — removing both icons and clearing the unload flag — and then a later map-queue entry would re-add the world-map icon. Because `Unload` had already nil'd the `questIdFrames`/`_G` reference, that re-added pin was orphaned and survived until `/reload`. `Unload` now drops the frame's pending draw-queue entries (new `QuestieMap:DequeueFrameDrawCalls`), so an unloaded frame can never be re-added to a map — keeping both maps in sync. - **[Map - Completed Quest Pins Stuck On The World Map Until Reload]** (#9) A completed quest/objective's pin could stay on the world map (while the minimap correctly cleared it) until a `/reload`. Cause: a draw-queue race. The world-map and minimap draw queues are processed independently in `ProcessQueue`, so when a frame was unloaded while still queued (e.g. an objective that completes the same tick its pins were queued), the frame's minimap-queue entry could trigger the deferred `Unload` first — removing both icons and clearing the unload flag — and then a later map-queue entry would re-add the world-map icon. Because `Unload` had already nil'd the `questIdFrames`/`_G` reference, that re-added pin was orphaned and survived until `/reload`. `Unload` now drops the frame's pending draw-queue entries (new `QuestieMap:DequeueFrameDrawCalls`), so an unloaded frame can never be re-added to a map — keeping both maps in sync.
- **[Map - Quest-Type Filters Now Truly Apply To The Minimap]** (#11) Filtered quest types (e.g. dungeon quests) could still show on the minimap while correctly hidden on the world map. The previous fix made the minimap `FadeLogic` re-check `ShouldBeHidden` only when deciding whether to *re-show* an already-hidden icon — so an icon that was already visible (or that HBD's pin renderer showed on coming into range) was never hidden. `FadeLogic` now proactively calls `ShouldBeHidden` for every in-range minimap icon and `FakeHide`s it when filtered, in both minimap fade paths (quest icons and townsfolk/manual icons). The same path also picked up the #17 minimap-radius cutoff gating it was missing. - **[Map - Quest-Type Filters Now Truly Apply To The Minimap]** (#11) Filtered quest types (e.g. dungeon quests) could still show on the minimap while correctly hidden on the world map. The previous fix made the minimap `FadeLogic` re-check `ShouldBeHidden` only when deciding whether to *re-show* an already-hidden icon — so an icon that was already visible (or that HBD's pin renderer showed on coming into range) was never hidden. `FadeLogic` now proactively calls `ShouldBeHidden` for every in-range minimap icon and `FakeHide`s it when filtered, in both minimap fade paths (quest icons and townsfolk/manual icons). The same path also picked up the #17 minimap-radius cutoff gating it was missing.
- **[Learner - Kills Never Recorded Spawn Coordinates]** The combat-log kill handler computed the player's spawn position inside `if credited then ... end`, but `credited` was read **before** it was assigned (the `local credited = ...` came several lines later), so it was always `nil` — the position-capture block never ran. Every killed NPC was saved with `spawnSource="fallback"` and no `[7]` spawns regardless of kill count, so in learner-only mode their pins never appeared (and re-killing didn't help). Moved the `credited` computation above the position capture, and guard against a `0,0` position being stored. The learner's `GetPlayerCoords` (used by quest-giver/object learning) also now uses the robust `QuestieCompat.GetCurrentPlayerPosition()` (handles `SetMapToCurrentZone` and the Sunstrider parent/child coordinate correction) instead of the raw `GetPlayerMapPosition`, returning `nil` when no valid position exists so no `0,0` pins are recorded. Newly killed mobs now record real coordinates and their learner pins persist. **Hardening:** the kill spawn is now keyed under the same map space the coordinates were captured in (`GetCurrentPlayerPosition`'s mapId) rather than a separately-derived `GetZoneId()` that can disagree with the coordinate space on subzones; a throttled debug warning surfaces credited kills that yield no usable position; and a regression test (`Tests/QuestieLearnerKillCapture_spec.lua`) drives the real kill handler end-to-end and asserts capture, the `0,0` guard, and the per-mode display contract (learner shows the learned spawn, static never exposes learner spawns). - **[Learner - Kills Never Recorded Spawn Coordinates]** The combat-log kill handler computed the player's spawn position inside `if credited then ... end`, but `credited` was read **before** it was assigned (the `local credited = ...` came several lines later), so it was always `nil` — the position-capture block never ran. Every killed NPC was saved with `spawnSource="fallback"` and no `[7]` spawns regardless of kill count, so in learner-only mode their pins never appeared (and re-killing didn't help). Moved the `credited` computation above the position capture, and guard against a `0,0` position being stored. The learner's `GetPlayerCoords` (used by quest-giver/object learning) also now uses the robust `QuestieCompat.GetCurrentPlayerPosition()` (handles `SetMapToCurrentZone` and the Sunstrider parent/child coordinate correction) instead of the raw `GetPlayerMapPosition`, returning `nil` when no valid position exists so no `0,0` pins are recorded. Newly killed mobs now record real coordinates and their learner pins persist. **Hardening:** the kill spawn is now keyed under the same map space the coordinates were captured in (`GetCurrentPlayerPosition`'s mapId) rather than a separately-derived `GetZoneId()` that can disagree with the coordinate space on subzones; a throttled debug warning surfaces credited kills that yield no usable position; and a regression test (`Tests/QuestieLearnerKillCapture_spec.lua`) drives the real kill handler end-to-end and asserts capture, the `0,0` guard, and the per-mode display contract (learner shows the learned spawn, static never exposes learner spawns).
+66 -6
View File
@@ -252,17 +252,77 @@ local function _AddQuestStarterDropsToTooltip(npcId)
end end
end end
-- Collapses STACKED Ascension quest-progress lines for the same objective.
-- The Ascension server appends a new progress line on every objective update instead of
-- replacing the previous one, so a tooltip can pile up "0/8", "1/8", "2/8", "3/8" for one
-- objective (#9). This keeps only the most-progressed line of each stack.
--
-- It is SAFE to run unconditionally (unlike the full strip below): it only hides a line
-- when the SAME tooltip holds another progress line with the same objective text AND the
-- same denominator. Other addons — and Questie's own single-line objectives — never produce
-- two such duplicates, so their tooltip lines are never touched.
function _QuestieTooltips:DedupeAscensionProgressLines(tooltip, numLines)
local frameName = tooltip:GetName()
if not frameName then return end
local groups = {}
for i = 2, numLines do
local fontString = _G[frameName .. "TextLeft" .. i]
local text = fontString and fontString:GetText()
if text then
local clean = string.gsub(text, "|[cC]%x%x%x%x%x%x%x%x", "")
clean = string.gsub(clean, "|[rR]", "")
clean = string.match(clean, "^%s*(.-)%s*$") or clean
-- Strip an optional leading bullet/dash so "- 2/8 ..." matches "2/8 ...".
local body = string.match(clean, "^%-%s*(.+)$") or clean
local current, total, label = string.match(body, "^(%d+)%s*/%s*(%d+)%s*(.-)$")
if current and total then
local key = (label or "") .. "@@" .. total
local group = groups[key]
if not group then
groups[key] = { indices = { i }, bestIndex = i, bestCurrent = tonumber(current) }
else
table.insert(group.indices, i)
if tonumber(current) >= group.bestCurrent then
group.bestCurrent = tonumber(current)
group.bestIndex = i
end
end
end
end
end
for _, group in pairs(groups) do
if table.getn(group.indices) > 1 then
for _, idx in ipairs(group.indices) do
if idx ~= group.bestIndex then
local fontString = _G[frameName .. "TextLeft" .. idx]
if fontString then
fontString:SetText("")
fontString:Hide()
end
end
end
end
end
end
function _QuestieTooltips:HideAscensionQuestLines(tooltip) function _QuestieTooltips:HideAscensionQuestLines(tooltip)
if not Questie.db.profile.enableTooltips then return end if not Questie.db.profile.enableTooltips then return end
-- Opt-in only. This strips lines matching quest-objective patterns ("N/M", "[N] ...")
-- from tooltips to hide Ascension's server-injected quest progress. It runs on every
-- tooltip, so by default it must NOT touch them — otherwise it clobbers other tooltip
-- addons' lines that happen to look like "N/M" (durability, stack counts, etc.). Users
-- who want the Ascension quest-spam hidden can enable it explicitly. (#16)
if not Questie.db.profile.hideAscensionTooltipQuestLines then return end
local numLines = tooltip:NumLines() local numLines = tooltip:NumLines()
if not numLines or numLines < 1 then return end if not numLines or numLines < 1 then return end
-- Always collapse stacked duplicate progress lines for the same objective. This is the
-- safe, targeted fix for the "0/8 1/8 2/8 ..." pile-up and never touches other addons.
_QuestieTooltips:DedupeAscensionProgressLines(tooltip, numLines)
-- Opt-in only. The full strip removes EVERY line matching a quest-objective pattern
-- ("N/M", "[N] ...") to hide all of Ascension's server-injected quest progress. It runs
-- on every tooltip, so by default it must NOT touch them — otherwise it clobbers other
-- tooltip addons' lines that happen to look like "N/M" (durability, stack counts, etc.).
-- Users who want all Ascension quest-spam gone can enable it explicitly. (#16)
if not Questie.db.profile.hideAscensionTooltipQuestLines then return end
for i = 2, numLines do for i = 2, numLines do
local fontString = _G[tooltip:GetName() .. "TextLeft" .. i] local fontString = _G[tooltip:GetName() .. "TextLeft" .. i]
if fontString then if fontString then