Apply local fixes on top of upstream: tracker desync/drag/title, map icon zoom offset
- Compat/HBD.lua: fix world map pin offset math to account for zoom addons (e.g. Magnify) rescaling an ancestor frame instead of WorldMapButton itself; cull pins that fall outside the scroll frame's viewport when zoomed - Compat/Compat.lua: add a short TTL to the chat-parsed objective progress cache so a stale entry can't be reapplied to an unrelated quest - Modules/QuestieLearner.lua: re-point QuestiePlayer.currentQuestlog at the rebuilt quest table after invalidating QuestieDB's quest cache, fixing Tracker freezing on stale objective progress - Modules/Tracker/QuestieTracker.lua: fall back to a formatted quest name when QuestieDB has no title for a quest (e.g. custom server quests) - Modules/Tracker/TrackerBaseFrame.lua: force baseFrame movability on drag start instead of trusting the async-refreshed IsMovable() state - Modules/Tracker/TrackerHeaderFrame.lua: wire up drag on the tracker icon - Modules/Quest/QuestEventHandler.lua: force a full quest log reconciliation when the native quest log is opened Squashed from prior commit-by-commit history to rebuild this working copy on a clean fork of aron-w/Questie-X (restores the native GitHub fork link). Per-fix rationale is preserved in project memory (project_tracker_fixes.md, project_map_icon_offset.md).
This commit is contained in:
+14
-4
@@ -474,6 +474,12 @@ function QuestieCompat.GetServerTime()
|
||||
end
|
||||
|
||||
local questObjectivesCache = {}
|
||||
-- Chat-parsed objective progress (see QuestieCompat.UiInfoMessage below) is only valid for a
|
||||
-- short window: it exists to get ahead of GetQuestLogLeaderBoard's text, which can lag behind
|
||||
-- the chat message on this server. If it isn't consumed quickly, GetQuestLogLeaderBoard has
|
||||
-- almost certainly caught up on its own, so an old entry is more likely a stale/mismatched
|
||||
-- leftover (e.g. two quests sharing the same objective text) than a still-valid override.
|
||||
local QUEST_OBJECTIVE_CACHE_TTL = 3
|
||||
|
||||
local function parseQuestObjective(text)
|
||||
local name, fulfilled, required = string.match(string.gsub(text, "\239\188\154", ":"), "(.*):%s*([%d]+)%s*/%s*([%d]+)")
|
||||
@@ -499,10 +505,14 @@ cLog.GetQuestObjectives = function(questID, questLogIndex)
|
||||
if objectiveType ~= "log" and description then
|
||||
local objectiveName, numFulfilled, numRequired = parseQuestObjective(description)
|
||||
if objectiveName then
|
||||
local fulfilled = questObjectivesCache[objectiveName]
|
||||
if fulfilled then
|
||||
numFulfilled = fulfilled
|
||||
local cachedOverride = questObjectivesCache[objectiveName]
|
||||
if cachedOverride then
|
||||
-- Always drop it on read: a hit is single-use whether or not it's stale,
|
||||
-- otherwise a leftover entry could keep getting reapplied to unrelated reads.
|
||||
questObjectivesCache[objectiveName] = nil
|
||||
if (GetTime() - cachedOverride.time) <= QUEST_OBJECTIVE_CACHE_TTL then
|
||||
numFulfilled = cachedOverride.value
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1738,7 +1748,7 @@ function QuestieCompat.UiInfoMessage(event, message)
|
||||
if string.find(message, pattern) then
|
||||
local objectiveName, numFulfilled = parseQuestObjective(message)
|
||||
if objectiveName and numFulfilled then
|
||||
questObjectivesCache[objectiveName] = numFulfilled
|
||||
questObjectivesCache[objectiveName] = { value = numFulfilled, time = GetTime() }
|
||||
end
|
||||
MinimapIcon:UpdateText(message)
|
||||
end
|
||||
|
||||
+53
-2
@@ -869,8 +869,36 @@ local function HandleWorldMapPin(icon, data)
|
||||
|
||||
if x and y then
|
||||
icon:ClearAllPoints()
|
||||
icon:SetPoint("CENTER", WorldMapButton, "TOPLEFT", x * worldmapWidth, -y * worldmapHeight)
|
||||
-- SetPoint offsets are in units of the icon's own effective scale, not
|
||||
-- WorldMapButton's. worldmapWidth/Height are pre-multiplied by
|
||||
-- WorldMapButton's effective scale (see UpdateWorldMap), so divide back
|
||||
-- out by the icon's effective scale to get the correct local offset.
|
||||
-- Needed because zoom addons (e.g. Magnify, bundled with LootCollector)
|
||||
-- rescale an ancestor frame instead of WorldMapButton itself.
|
||||
local iconEffScale = icon:GetEffectiveScale()
|
||||
icon:SetPoint("CENTER", WorldMapButton, "TOPLEFT", (x * worldmapWidth) / iconEffScale, -(y * worldmapHeight) / iconEffScale)
|
||||
icon:Show()
|
||||
|
||||
-- Questie fix: when zoomed in (e.g. via Magnify/LootCollector), WorldMapButton is
|
||||
-- drawn far larger than the visible window and only WorldMapScrollFrame clips it.
|
||||
-- Pins positioned past the visible edge still render on top of the surrounding UI
|
||||
-- instead of being clipped, so hide them manually once they're outside the
|
||||
-- scroll frame's actual on-screen bounds. GetLeft/Right/Top/Bottom/Center are all
|
||||
-- in a shared scale-independent coordinate space, so no extra scale math needed.
|
||||
-- Only do this when the map is actually drawn larger than its viewport (the zoom
|
||||
-- case) -- at normal size (e.g. just the "show objective" panel active, no zoom)
|
||||
-- the whole map already fits on-screen, so this check should never fire there;
|
||||
-- skip it entirely rather than risk false positives hiding valid icons.
|
||||
if WorldMapScrollFrame and WorldMapScrollFrame:IsVisible() then
|
||||
local scrollWidth, scrollHeight = WorldMapScrollFrame:GetWidth(), WorldMapScrollFrame:GetHeight()
|
||||
if scrollWidth and scrollHeight and (worldmapWidth > scrollWidth + 1 or worldmapHeight > scrollHeight + 1) then
|
||||
local iconCenterX, iconCenterY = icon:GetCenter()
|
||||
local vLeft, vRight, vTop, vBottom = WorldMapScrollFrame:GetLeft(), WorldMapScrollFrame:GetRight(), WorldMapScrollFrame:GetTop(), WorldMapScrollFrame:GetBottom()
|
||||
if iconCenterX and vLeft and (iconCenterX < vLeft or iconCenterX > vRight or iconCenterY > vTop or iconCenterY < vBottom) then
|
||||
icon:Hide()
|
||||
end
|
||||
end
|
||||
end
|
||||
else
|
||||
icon:Hide()
|
||||
end
|
||||
@@ -885,7 +913,11 @@ end
|
||||
local function UpdateWorldMap()
|
||||
if not WorldMapFrame:IsVisible() then return end
|
||||
|
||||
local scale = WorldMapButton:GetScale()
|
||||
-- Use effective (inherited) scale, not WorldMapButton's own scale: zoom addons
|
||||
-- like Magnify (bundled with LootCollector) hardcode WorldMapButton's own scale
|
||||
-- to 1 and instead rescale an ancestor frame (WorldMapDetailFrame), so only
|
||||
-- GetEffectiveScale() reflects the actual on-screen size in all cases.
|
||||
local scale = WorldMapButton:GetEffectiveScale()
|
||||
worldmapWidth = WorldMapButton:GetWidth()*scale
|
||||
worldmapHeight = WorldMapButton:GetHeight()*scale
|
||||
|
||||
@@ -901,8 +933,27 @@ local function UpdateWorldMap()
|
||||
end
|
||||
pins.UpdateWorldMap = UpdateWorldMap
|
||||
|
||||
-- Questie fix: WorldMapButton can be resized/rescaled by things we have no event for
|
||||
-- (this client's "show objective" side panel, third-party map-zoom addons, etc).
|
||||
-- Poll its size/effective-scale each frame like we already do for the minimap, and only
|
||||
-- pay for a full reposition when it actually changed. Effective scale (not own scale) is
|
||||
-- required because zoom addons like Magnify rescale an ancestor frame instead of
|
||||
-- WorldMapButton itself, leaving WorldMapButton:GetScale() frozen at 1.
|
||||
local lastWorldMapRawWidth, lastWorldMapRawHeight, lastWorldMapEffScale
|
||||
local function CheckWorldMapSizeChanged()
|
||||
if not WorldMapFrame:IsVisible() then return end
|
||||
|
||||
local w, h, s = WorldMapButton:GetWidth(), WorldMapButton:GetHeight(), WorldMapButton:GetEffectiveScale()
|
||||
if w ~= lastWorldMapRawWidth or h ~= lastWorldMapRawHeight or s ~= lastWorldMapEffScale then
|
||||
lastWorldMapRawWidth, lastWorldMapRawHeight, lastWorldMapEffScale = w, h, s
|
||||
UpdateWorldMap()
|
||||
end
|
||||
end
|
||||
|
||||
local last_update = 0
|
||||
local function OnUpdateHandler(frame, elapsed)
|
||||
CheckWorldMapSizeChanged()
|
||||
|
||||
last_update = last_update + elapsed
|
||||
if last_update > 1 or queueFullUpdate then
|
||||
UpdateMinimapPins(queueFullUpdate)
|
||||
|
||||
@@ -104,6 +104,21 @@ function QuestEventHandler:RegisterEvents()
|
||||
end)
|
||||
end
|
||||
|
||||
-- Force a full reconciliation whenever the player opens the native quest log.
|
||||
-- QuestLogCache only updates its stored objective counts when it detects a change, so if a
|
||||
-- scan ever ran while the game's own quest log text was still lagging behind the server, the
|
||||
-- Tracker can get stuck showing stale progress with nothing left to trigger a retry. Looking
|
||||
-- at the quest log is the moment the player is most likely to notice the mismatch, so treat it
|
||||
-- as a cue to force a fresh comparison against the live quest log state.
|
||||
local questLogFrame = QuestLogExFrame or ClassicQuestLog or QuestLogFrame
|
||||
if questLogFrame then
|
||||
questLogFrame:HookScript("OnShow", function()
|
||||
Questie:Debug(Questie.DEBUG_DEVELOP, "[Quest Event] Quest log opened: forcing full quest log scan")
|
||||
doFullQuestLogScan = true
|
||||
_QuestEventHandler:QuestLogUpdate()
|
||||
end)
|
||||
end
|
||||
|
||||
-- StaticPopup dialog hooks. Deleteing Quest items do not always trigger a Quest Log Update.
|
||||
hooksecurefunc("StaticPopup_Show", function(...)
|
||||
-- Hook StaticPopup_Show. If we find the "DELETE_ITEM" dialog, check for Quest Items and notify the player.
|
||||
|
||||
@@ -795,6 +795,19 @@ end
|
||||
-- so the second debounce stage would only add redundant latency.
|
||||
local _deferPinRefreshScheduling = false
|
||||
|
||||
-- Clearing QuestieDB.private.questCache[questId] makes the NEXT QuestieDB.GetQuest(questId)
|
||||
-- rebuild a brand new quest table so the just-learned override data takes effect. But
|
||||
-- QuestiePlayer.currentQuestlog[questId] (what the Tracker actually reads every redraw) holds a
|
||||
-- direct reference to the OLD table and is never told about the swap, so it freezes forever on
|
||||
-- whatever the old table last held while every future update lands on the new, unreferenced one.
|
||||
-- Rebuild immediately and re-point currentQuestlog at the new table so nothing is left orphaned.
|
||||
local function _InvalidateQuestCache(questId)
|
||||
QuestieDB.private.questCache[questId] = nil
|
||||
if QuestiePlayer.currentQuestlog[questId] then
|
||||
QuestiePlayer.currentQuestlog[questId] = QuestieDB.GetQuest(questId)
|
||||
end
|
||||
end
|
||||
|
||||
local function _RefreshActiveQuestPins(questIdSet)
|
||||
-- Skip scheduling when the set is empty (avoids no-op timer callbacks)
|
||||
if not next(questIdSet) then return end
|
||||
@@ -1995,7 +2008,7 @@ function QuestieLearner:LearnQuest(questId, data)
|
||||
end
|
||||
end
|
||||
if QuestieDB.private and QuestieDB.private.questCache then
|
||||
QuestieDB.private.questCache[questId] = nil
|
||||
_InvalidateQuestCache(questId)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -2044,7 +2057,7 @@ function QuestieLearner:LearnQuestGiver(questId, entityId, entityType, isStart)
|
||||
end
|
||||
if not found then table.insert(ovrList, entityId) end
|
||||
if QuestieDB.private and QuestieDB.private.questCache then
|
||||
QuestieDB.private.questCache[questId] = nil
|
||||
_InvalidateQuestCache(questId)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -2120,7 +2133,7 @@ function QuestieLearner:LearnQuestObjectiveNPC(questId, npcId, objText, objectiv
|
||||
ovr.objIndex[objectiveIndex] = existing.objIndex[objectiveIndex]
|
||||
end
|
||||
if QuestieDB.private and QuestieDB.private.questCache then
|
||||
QuestieDB.private.questCache[questId] = nil
|
||||
_InvalidateQuestCache(questId)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -2209,7 +2222,7 @@ function QuestieLearner:LearnQuestObjectiveObject(questId, objectId, objText, ob
|
||||
ovr.objIndex[objectiveIndex] = existing.objIndex[objectiveIndex]
|
||||
end
|
||||
if QuestieDB.private and QuestieDB.private.questCache then
|
||||
QuestieDB.private.questCache[questId] = nil
|
||||
_InvalidateQuestCache(questId)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -1039,6 +1039,25 @@ function QuestieTracker:Update()
|
||||
(Questie.db.profile.collapseCompletedQuests and isMinimizable), false)
|
||||
end
|
||||
|
||||
if not coloredQuestName then
|
||||
-- QuestieDB has no "name" entry for this quest (e.g. a custom
|
||||
-- server quest not yet in the static DB), so GetColoredQuestName
|
||||
-- returned nil -- fall back instead of SetText(nil) blanking the
|
||||
-- title line while its objectives still render normally below it.
|
||||
-- Level/tag lookups don't require a DB "name" entry, so reuse the
|
||||
-- same formatting helper as the normal path to keep the "[level]"
|
||||
-- prefix consistent with quests that do have a DB name.
|
||||
local fallbackName = quest.name or tostring(quest.Id)
|
||||
if Questie.db.profile.trackerShowQuestLevel then
|
||||
local level = QuestieLib.GetTbcLevel(quest.Id)
|
||||
fallbackName = QuestieLib:GetQuestString(quest.Id, fallbackName, level, false)
|
||||
end
|
||||
if Questie.db.profile.enableTooltipsQuestID then
|
||||
fallbackName = fallbackName .. " (" .. quest.Id .. ")"
|
||||
end
|
||||
coloredQuestName = "|cFFFFFF00" .. fallbackName .. "|r"
|
||||
end
|
||||
|
||||
line.label:SetText(coloredQuestName)
|
||||
|
||||
-- Check and measure Quest Label text width and update tracker width
|
||||
|
||||
@@ -317,16 +317,17 @@ function TrackerBaseFrame.OnDragStart(frame, button)
|
||||
if TrackerBaseFrame.isMoving ~= true and TrackerBaseFrame.isSizing ~= true then
|
||||
if IsMouseButtonDown(button) and button ~= "MiddleButton" then
|
||||
if (IsControlKeyDown() and Questie.db.profile.trackerLocked and not ChatEdit_GetActiveWindow()) or not Questie.db.profile.trackerLocked then
|
||||
if baseFrame:IsMovable() then
|
||||
Questie:Debug(Questie.DEBUG_DEVELOP, "[TrackerBaseFrame:OnDragStart] - Dragging Started.")
|
||||
TrackerBaseFrame.isMoving = true
|
||||
TrackerBaseFrame.baseFrame.isMoving = true
|
||||
-- Force movability here instead of trusting baseFrame:IsMovable(), which is
|
||||
-- only refreshed asynchronously by TrackerBaseFrame:Update() on unrelated
|
||||
-- tracker events and is almost never in sync with the live Ctrl/lock state
|
||||
-- already checked above at the moment of an actual drag attempt.
|
||||
baseFrame:SetMovable(true)
|
||||
Questie:Debug(Questie.DEBUG_DEVELOP, "[TrackerBaseFrame:OnDragStart] - Dragging Started.")
|
||||
TrackerBaseFrame.isMoving = true
|
||||
TrackerBaseFrame.baseFrame.isMoving = true
|
||||
|
||||
baseFrame:StartMoving()
|
||||
TrackerBaseFrame:Update()
|
||||
else
|
||||
Questie:Debug(Questie.DEBUG_DEVELOP, "[TrackerBaseFrame:OnDragStart] - Frame is not movable!")
|
||||
end
|
||||
baseFrame:StartMoving()
|
||||
TrackerBaseFrame:Update()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -51,6 +51,9 @@ function TrackerHeaderFrame.Initialize(baseFrame)
|
||||
|
||||
questieIcon:EnableMouse(true)
|
||||
questieIcon:RegisterForClicks("LeftButtonUp", "RightButtonUp")
|
||||
questieIcon:RegisterForDrag("LeftButton")
|
||||
questieIcon:SetScript("OnDragStart", TrackerBaseFrame.OnDragStart)
|
||||
questieIcon:SetScript("OnDragStop", TrackerBaseFrame.OnDragStop)
|
||||
|
||||
questieIcon:SetScript("OnClick", function(_, button)
|
||||
if button == "LeftButton" then
|
||||
|
||||
Reference in New Issue
Block a user