fix(map): completed quest pins stuck on world map until reload (#9)

Draw-queue race: ProcessQueue processes the world-map and minimap queues
independently. When a frame was unloaded while still queued, its
minimap-queue entry could trigger the deferred Unload first (removing
both icons and clearing _needsUnload), then a later map-queue entry
re-added the world-map icon -- orphaned, since Unload had nil'd the
questIdFrames/_G reference, so it lingered until /reload while the
minimap cleared. 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.
This commit is contained in:
Xurkon
2026-06-11 07:13:01 -05:00
parent 48a948af72
commit dc155b6583
3 changed files with 30 additions and 0 deletions
+1
View File
@@ -40,6 +40,7 @@
### Bug Fixes
- **[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.
- **[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).
- **[Map - Minimap Icon Alignment At Non-1 UI Scales]** (#6) Reverted the attempted physical-pixel snapping fix and corrected the underlying minimap coordinate math instead. HBD pins already use `SetPoint` offsets in the minimap frame's coordinate space, so multiplying the minimap half-width by `GetScale()` made Questie `?`/`!` pins drift away from the native minimap quest blips whenever UI/minimap scale was not 1.0. Minimap pins now combine the live minimap view radius with unscaled `GetWidth()/2` and `GetHeight()/2`, which keeps placement resolution-independent across fractional UI scales.
+7
View File
@@ -327,8 +327,15 @@ end
function _Qframe:Unload()
if not self._loaded then
self._needsUnload = true
-- Drop any pending draw-queue entries so this frame can't be re-added to a map
-- after it was unloaded while still queued (see DequeueFrameDrawCalls). Without
-- this a completed quest's world-map pin could linger until /reload. (#9)
QuestieMap:DequeueFrameDrawCalls(self)
return -- icon is still in the draw queue
end
-- Even when already loaded, the frame may have been re-queued for a redraw; clear those
-- pending draw calls so they don't re-add the icon after this unload. (#9)
QuestieMap:DequeueFrameDrawCalls(self)
self._needsUnload = nil
self._loaded = nil
--Questie:Debug(Questie.DEBUG_SPAM, "[_Qframe:Unload]")
+22
View File
@@ -216,6 +216,28 @@ local minimapDrawQueue = {};
QuestieMap._mapDrawQueue = mapDrawQueue
QuestieMap._minimapDrawQueue = minimapDrawQueue
--- Removes any still-pending world-map / minimap draw calls for a frame.
--- The two draw queues are processed independently in ProcessQueue, so a frame that is
--- unloaded while still queued (e.g. a quest objective that completes the same tick its
--- pins were queued) could otherwise be re-added to the WORLD MAP by a later map-queue
--- entry AFTER it was already unloaded via its minimap-queue entry. Because Unload nils
--- the questIdFrames/_G reference, that re-added world-map pin becomes orphaned and stays
--- until /reload — while the minimap pin was correctly removed. Dropping the queued draw
--- calls on Unload keeps both maps in sync. (#9)
function QuestieMap:DequeueFrameDrawCalls(frame)
if not frame then return end
for i = #mapDrawQueue, 1, -1 do
if mapDrawQueue[i] and mapDrawQueue[i][2] == frame then
tremove(mapDrawQueue, i)
end
end
for i = #minimapDrawQueue, 1, -1 do
if minimapDrawQueue[i] and minimapDrawQueue[i][2] == frame then
tremove(minimapDrawQueue, i)
end
end
end
--- Called at startup (Stage 3) and on PLAYER_ENTERING_WORLD to reset the draw queue.
function QuestieMap:InitializeQueue()
Questie:Debug(Questie.DEBUG_DEVELOP, "[QuestieMap] Starting draw queue timer!")