Compare commits

...

3 Commits

Author SHA1 Message Date
Narcasung 7ecc9016dd feat(tracker): match the client's pin interactions on the marker button
Hover used a Blizzard minimize-button highlight, which read as a red
tint over the pin art and looked nothing like the map. The pins draw
their hover and pressed states from the same UI-QuestPoi-NumberIcons
atlas they draw everything else from, additively blended, so mirror
those cells alongside the ones already being copied.

Add the pressed offset the pins have, nudging the digit or the "?" a
pixel down and right while held, reset on redraw since the pool recycles
buttons and a line can be rebuilt with the mouse still down. Play the
sound the pins play as well: we call WorldMapFrame_SelectQuestFrame
directly, and the sound lives a level above it in WorldMapQuestPOI_OnClick.
2026-07-25 16:31:20 +02:00
Narcasung 0103932b9b fix(tracker): make the objective marker button usable at larger sizes
The button shared the quest gutter with the collapse button, the quest
item buttons and the zone header text, so anything past the default size
was clipped by the tracker edge or drawn on top of its neighbours.

Give it a gutter of its own instead. GetSuperTrackMarginReserve feeds
questMarginLeft, which every layout and width calculation already builds
on, and the quest item buttons and zone labels are shifted by the same
amount. The button then anchors flush left of the line and the tracker
widens to match, so nothing overlaps at any size. Raise the size cap to
70 now that it fits, and drop the hover tooltip.

Also stop relying on the SetSuperTrackedQuestID hook as the only source
of truth. It never fires while the player is a ghost -- the corpse arrow
takes over the marker -- and it has not fired yet on a login or reload,
which left the tracker with no idea what was tracked in both cases. The
client is now asked directly: the watch frame POI buttons flag their own
selection, the map pins say it through their art (the selected variant
sits half a texture above the normal one), and the quest log selection
answers for the login window when no pin is styled yet. Selection calls
are hooked for the same reason, so clicks repaint while dead, and the
requests are coalesced because callers select a quest log entry and
restore the previous one a line later.
2026-07-25 16:19:56 +02:00
Narcasung 6aafd2c112 feat(tracker): add objective marker button to quest lines
Ascension's client backports retail's floating objective marker. This adds
a button to each tracker quest line that points the marker at that quest,
mirroring the quest pin the world map draws for it.

Supertracking is a slave of the map's quest selection: both the map and the
Blizzard watch frame funnel through SelectQuestLogEntry, and calling
C_SuperTrack.SetSuperTrackedQuestID directly only moves the marker until the
next map interaction stomps it. So the button clicks the same POI frame the
client clicks -- the watch frame button when one exists, otherwise the map's
quest frame.

The client dropped GetSuperTrackedQuestID, so the current quest is read by
hooking SetSuperTrackedQuestID instead. Every path ends up there, including
the automatic re-pick on zone change, so the highlight cannot fall out of
sync with tracking changed outside the addon. Caching what we last set would
have gone stale the moment the player used the map.

The button mirrors the pin's own textures rather than picking atlas cells, so
the digit, the "?" completed quests use and the selected variant all follow
whatever the client draws. Quests with no pin get no button, and the map's
POI frames are built on demand so buttons appear without opening the map.

Adds trackerShowSuperTrackButton and trackerSuperTrackButtonSize.
2026-07-25 01:50:03 +02:00
6 changed files with 590 additions and 16 deletions
@@ -102,6 +102,20 @@ local trackerOptionsLocales = {
["frFR"] = "Affiche le niveau des quêtes avec le titre des quêtes.", ["frFR"] = "Affiche le niveau des quêtes avec le titre des quêtes.",
}, },
--------------------------------------------------------- ---------------------------------------------------------
["Show Objective Marker Button"] = {
["enUS"] = true,
},
["When this is checked, quests that can be reached by the floating objective marker get a button in the Questie Tracker that points the marker at them."] = {
["enUS"] = true,
},
---------------------------------------------------------
["Objective Marker Button Size"] = {
["enUS"] = true,
},
["The size of the objective marker button shown next to each quest in the Questie Tracker."] = {
["enUS"] = true,
},
---------------------------------------------------------
["Auto Minimize Completed Quests"] = { ["Auto Minimize Completed Quests"] = {
["ptBR"] = "Minimizar missões concluídas", ["ptBR"] = "Minimizar missões concluídas",
["ruRU"] = "Свернуть выполненные", ["ruRU"] = "Свернуть выполненные",
@@ -113,6 +113,8 @@ function QuestieOptionsDefaults:Load()
autoTrackQuests = true, autoTrackQuests = true,
trackerShowCompleteQuests = true, trackerShowCompleteQuests = true,
trackerShowQuestLevel = true, trackerShowQuestLevel = true,
trackerShowSuperTrackButton = true,
trackerSuperTrackButtonSize = 25,
collapseCompletedQuests = false, collapseCompletedQuests = false,
hideCompletedQuestObjectives = false, hideCompletedQuestObjectives = false,
hideBlizzardCompletionText = false, hideBlizzardCompletionText = false,
@@ -13,6 +13,8 @@ local TrackerBaseFrame = QuestieLoader:ImportModule("TrackerBaseFrame")
local TrackerLinePool = QuestieLoader:ImportModule("TrackerLinePool") local TrackerLinePool = QuestieLoader:ImportModule("TrackerLinePool")
---@type TrackerQuestTimers ---@type TrackerQuestTimers
local TrackerQuestTimers = QuestieLoader:ImportModule("TrackerQuestTimers") local TrackerQuestTimers = QuestieLoader:ImportModule("TrackerQuestTimers")
---@type TrackerUtils
local TrackerUtils = QuestieLoader:ImportModule("TrackerUtils")
---@type QuestieArrow ---@type QuestieArrow
local QuestieArrow = QuestieLoader:ImportModule("QuestieArrow") local QuestieArrow = QuestieLoader:ImportModule("QuestieArrow")
@@ -175,6 +177,37 @@ function QuestieOptions.tabs.tracker:Initialize()
QuestieTracker:Update() QuestieTracker:Update()
end end
}, },
showSuperTrackButton = {
type = "toggle",
order = 5,
width = 1.5,
name = function() return l10n('Show Objective Marker Button') end,
desc = function() return l10n('When this is checked, quests that can be reached by the floating objective marker get a button in the Questie Tracker that points the marker at them.') end,
hidden = function() return not TrackerUtils:IsSuperTrackAvailable() end,
disabled = function() return not Questie.db.profile.trackerEnabled end,
get = function() return Questie.db.profile.trackerShowSuperTrackButton end,
set = function(_, value)
Questie.db.profile.trackerShowSuperTrackButton = value
QuestieTracker:Update()
end
},
superTrackButtonSize = {
type = "range",
order = 6,
width = 1.5,
name = function() return l10n('Objective Marker Button Size') end,
desc = function() return l10n('The size of the objective marker button shown next to each quest in the Questie Tracker.') end,
hidden = function() return not TrackerUtils:IsSuperTrackAvailable() end,
disabled = function() return (not Questie.db.profile.trackerEnabled) or (not Questie.db.profile.trackerShowSuperTrackButton) end,
min = 8,
max = 70,
step = 1,
get = function() return Questie.db.profile.trackerSuperTrackButtonSize end,
set = function(_, value)
Questie.db.profile.trackerSuperTrackButtonSize = value
QuestieTracker:Update()
end
},
showQuestTimer = { showQuestTimer = {
type = "toggle", type = "toggle",
order = 3, order = 3,
+30 -16
View File
@@ -335,6 +335,10 @@ function QuestieTracker.Initialize()
Questie.db.profile.trackerSetpoint = "TOPLEFT" Questie.db.profile.trackerSetpoint = "TOPLEFT"
end end
-- Tracks what the client considers supertracked. Permanent by design: hooksecurefunc cannot be
-- undone, and the value has to stay correct even while the Questie tracker is disabled.
TrackerUtils:InitSuperTrackHook()
if (not Questie.db.profile.trackerEnabled) then if (not Questie.db.profile.trackerEnabled) then
-- The Tracker is disabled, no need to continue -- The Tracker is disabled, no need to continue
return return
@@ -841,7 +845,11 @@ function QuestieTracker:Update()
-- Setup local QuestieTracker:Update vars -- Setup local QuestieTracker:Update vars
local trackerFontSizeZone = Questie.db.profile.trackerFontSizeZone local trackerFontSizeZone = Questie.db.profile.trackerFontSizeZone
local trackerFontSizeQuest = Questie.db.profile.trackerFontSizeQuest local trackerFontSizeQuest = Questie.db.profile.trackerFontSizeQuest
local questMarginLeft = (trackerMarginLeft + trackerMarginRight) - (18 - trackerFontSizeQuest) -- The supertrack button sits at the very left of a quest line, so the whole quest list is
-- indented past it: otherwise it is clipped by the tracker's left edge and collides with the
-- quest item buttons, which share that gutter.
local superTrackMarginLeft = TrackerLinePool.GetSuperTrackMarginReserve()
local questMarginLeft = (trackerMarginLeft + trackerMarginRight) - (18 - trackerFontSizeQuest) + superTrackMarginLeft
local objectiveMarginLeft = questMarginLeft + trackerFontSizeQuest local objectiveMarginLeft = questMarginLeft + trackerFontSizeQuest
local questItemButtonSize = 12 + trackerFontSizeQuest local questItemButtonSize = 12 + trackerFontSizeQuest
local objectiveColor = Questie.db.profile.trackerColorObjectives local objectiveColor = Questie.db.profile.trackerColorObjectives
@@ -891,9 +899,11 @@ function QuestieTracker:Update()
line.criteriaMark:Hide() line.criteriaMark:Hide()
line.playButton:Hide() line.playButton:Hide()
-- Setup Zone Label -- Setup Zone Label. Indented like the quest lines below it: the supertrack
-- buttons overflow their own line at larger sizes, and the zone label is the
-- only text that would otherwise share that gutter with them.
line.label:ClearAllPoints() line.label:ClearAllPoints()
line.label:SetPoint("TOPLEFT", line, "TOPLEFT", 0, 0) line.label:SetPoint("TOPLEFT", line, "TOPLEFT", superTrackMarginLeft, 0)
-- Set Zone Title and default Min/Max states -- Set Zone Title and default Min/Max states
if Questie.db.char.collapsedZones[zoneName] then if Questie.db.char.collapsedZones[zoneName] then
@@ -920,19 +930,19 @@ function QuestieTracker:Update()
-- Check and measure Zone Label text width and update tracker width -- Check and measure Zone Label text width and update tracker width
QuestieTracker:UpdateWidth(line.label:GetStringWidth() + trackerMarginLeft + QuestieTracker:UpdateWidth(line.label:GetStringWidth() + trackerMarginLeft +
trackerMarginRight) superTrackMarginLeft + trackerMarginRight)
-- Set Zone Label and Line widths -- Set Zone Label and Line widths
line.label:SetWidth(trackerBaseFrame:GetWidth() - trackerMarginLeft - trackerMarginRight) line.label:SetWidth(trackerBaseFrame:GetWidth() - trackerMarginLeft - superTrackMarginLeft - trackerMarginRight)
line:SetWidth(line.label:GetWidth()) line:SetWidth(line.label:GetWidth() + superTrackMarginLeft)
-- Compare largest text Label in the tracker with current Label, then save widest width -- Compare largest text Label in the tracker with current Label, then save widest width
trackerLineWidth = math.max(trackerLineWidth, trackerLineWidth = math.max(trackerLineWidth,
line.label:GetStringWidth() + trackerMarginLeft) line.label:GetStringWidth() + trackerMarginLeft + superTrackMarginLeft)
-- Setup Min/Max Button -- Setup Min/Max Button
line.expandZone:ClearAllPoints() line.expandZone:ClearAllPoints()
line.expandZone:SetPoint("TOPLEFT", line, "TOPLEFT", 0, 0) line.expandZone:SetPoint("TOPLEFT", line, "TOPLEFT", superTrackMarginLeft, 0)
line.expandZone:SetWidth(line.label:GetWidth()) line.expandZone:SetWidth(line.label:GetWidth())
line.expandZone:SetHeight(line.label:GetHeight()) line.expandZone:SetHeight(line.label:GetHeight())
line.expandZone:Show() line.expandZone:Show()
@@ -1093,6 +1103,10 @@ function QuestieTracker:Update()
-- Adds the AI_VoiceOver Play Buttons -- Adds the AI_VoiceOver Play Buttons
line.playButton:SetPlayButton(questId) line.playButton:SetPlayButton(questId)
-- Adds the button that points the floating objective marker at this quest.
-- Must run after SetPlayButton, since it anchors around the play button.
line.superTrackButton:SetSuperTrackButton(questId)
local usableQIB = false local usableQIB = false
local sourceItemId = QuestieDB.QueryQuestSingle(quest.Id, "sourceItemId") local sourceItemId = QuestieDB.QueryQuestSingle(quest.Id, "sourceItemId")
local isLiveSourceItem = false local isLiveSourceItem = false
@@ -1167,7 +1181,7 @@ function QuestieTracker:Update()
end end
-- Attach button to Quest Title linePool -- Attach button to Quest Title linePool
button:SetPoint("TOPLEFT", button.line, "TOPLEFT", 0, 0) button:SetPoint("TOPLEFT", button.line, "TOPLEFT", superTrackMarginLeft, 0)
button:SetParent(button.line) button:SetParent(button.line)
button:Show() button:Show()
@@ -1275,7 +1289,7 @@ function QuestieTracker:Update()
-- Attach button to Quest Title linePool -- Attach button to Quest Title linePool
altButton:SetPoint("TOPLEFT", altButton.line, "TOPLEFT", altButton:SetPoint("TOPLEFT", altButton.line, "TOPLEFT",
2 + questItemButtonSize, 0) superTrackMarginLeft + 2 + questItemButtonSize, 0)
altButton:SetParent(altButton.line) altButton:SetParent(altButton.line)
altButton:Show() altButton:Show()
@@ -1634,9 +1648,9 @@ function QuestieTracker:Update()
line.criteriaMark:Hide() line.criteriaMark:Hide()
line.playButton:Hide() line.playButton:Hide()
-- Setup Zone Label -- Setup Zone Label (indented past the supertrack button gutter, as above)
line.label:ClearAllPoints() line.label:ClearAllPoints()
line.label:SetPoint("TOPLEFT", line, "TOPLEFT", 0, 0) line.label:SetPoint("TOPLEFT", line, "TOPLEFT", superTrackMarginLeft, 0)
-- Set Zone Title and Min/Max states -- Set Zone Title and Min/Max states
if Questie.db.char.collapsedZones[zoneName] then if Questie.db.char.collapsedZones[zoneName] then
@@ -1665,15 +1679,15 @@ function QuestieTracker:Update()
-- Check and measure Zone Label text width and update tracker width -- Check and measure Zone Label text width and update tracker width
QuestieTracker:UpdateWidth(line.label:GetStringWidth() + trackerMarginLeft + QuestieTracker:UpdateWidth(line.label:GetStringWidth() + trackerMarginLeft +
trackerMarginRight) superTrackMarginLeft + trackerMarginRight)
-- Set Zone Label and Line widths -- Set Zone Label and Line widths
line.label:SetWidth(trackerBaseFrame:GetWidth() - trackerMarginLeft - trackerMarginRight) line.label:SetWidth(trackerBaseFrame:GetWidth() - trackerMarginLeft - superTrackMarginLeft - trackerMarginRight)
line:SetWidth(line.label:GetWidth()) line:SetWidth(line.label:GetWidth() + superTrackMarginLeft)
-- Compare largest text Label in the tracker with current Label, then save widest width -- Compare largest text Label in the tracker with current Label, then save widest width
trackerLineWidth = math.max(trackerLineWidth, trackerLineWidth = math.max(trackerLineWidth,
line.label:GetStringWidth() + trackerMarginLeft) line.label:GetStringWidth() + trackerMarginLeft + superTrackMarginLeft)
-- Setup Min/Max Button -- Setup Min/Max Button
line.expandZone:ClearAllPoints() line.expandZone:ClearAllPoints()
+244
View File
@@ -35,6 +35,67 @@ local l10n = QuestieLoader:ImportModule("l10n")
local C_Timer = QuestieCompat.C_Timer local C_Timer = QuestieCompat.C_Timer
local C_QuestLog = QuestieCompat.C_QuestLog local C_QuestLog = QuestieCompat.C_QuestLog
local GetQuestLogIndexByID = QuestieCompat.GetQuestLogIndexByID local GetQuestLogIndexByID = QuestieCompat.GetQuestLogIndexByID
-- Copies one texture region of a quest pin onto our button. The texture file travels with the
-- coordinates because the client swaps files between states, and the region is only shown when the
-- pin itself shows it -- the pin uses "number" for in-progress quests and "turnin" for completed
-- ones, never both.
local function MirrorPinRegion(destination, source, size, pinSize)
if (not source) or (not source:IsShown()) or (not source:GetTexture()) then
destination:Hide()
return
end
-- Regions are not all the size of the pin -- the "?" is drawn larger than a digit -- so scale
-- them by how the pin itself was scaled instead of stretching each one to the button.
local scale = (pinSize and pinSize > 0) and (size / pinSize) or 1
local width = (source:GetWidth() or 0) * scale
local height = (source:GetHeight() or 0) * scale
if width <= 0 or height <= 0 then
width = size
height = size
end
destination:SetWidth(width)
destination:SetHeight(height)
destination:SetTexture(source:GetTexture())
destination:SetTexCoord(source:GetTexCoord())
destination:Show()
end
-- The pin atlases stack the selected (yellow circle, black digits) variant of every cell exactly
-- half a texture above the normal one. The client only restyles its pins during the world map's own
-- selection pass, which has not run yet right after a login or a reload, so the variant is forced
-- here rather than taken on trust from the pin.
local function ApplySelectedVariant(texture, selected)
local topLeftX, topLeftY, bottomLeftX, bottomLeftY, topRightX, topRightY, bottomRightX, bottomRightY = texture:GetTexCoord()
if (not topLeftY) or (not bottomLeftY) or (bottomLeftY - topLeftY) > 0.5 then
return
end
local offset
if selected and topLeftY >= 0.5 then
offset = -0.5
elseif (not selected) and bottomLeftY <= 0.5 then
offset = 0.5
else
return
end
texture:SetTexCoord(topLeftX, topLeftY + offset, bottomLeftX, bottomLeftY + offset,
topRightX, topRightY + offset, bottomRightX, bottomRightY + offset)
end
-- Whole-button states (hover, pressed) rather than the regions drawn inside them, so these fill the
-- button on their own and only need the atlas cell copied across.
local function MirrorPinButtonTexture(destination, source)
if (not destination) or (not source) or (not source.GetTexture) or (not source:GetTexture()) then
return
end
destination:SetTexture(source:GetTexture())
destination:SetTexCoord(source:GetTexCoord())
end
local function GetNumLines(label) local function GetNumLines(label)
if label.GetNumLines then if label.GetNumLines then
return label:GetNumLines() return label:GetNumLines()
@@ -54,6 +115,20 @@ local linePool = {}
local buttonPool = {} local buttonPool = {}
local lineMarginLeft = 10 local lineMarginLeft = 10
-- Gutter given to the supertrack button, which sits flush with the left edge of a quest line. The
-- collapse button, the quest item buttons and the labels are all shifted right by this much, so
-- nothing lands on top of it and nothing hangs over the tracker's left edge. Fed into
-- questMarginLeft, which every width calculation already builds on, so the tracker widens to match.
function TrackerLinePool.GetSuperTrackMarginReserve()
if (not Questie.db.profile.trackerShowSuperTrackButton) or (not TrackerUtils:IsSuperTrackAvailable()) then
return 0
end
-- Reserved even for quests whose button is hidden, so the list does not shift around as quests
-- come in and out of the supertrackable set.
return (Questie.db.profile.trackerSuperTrackButtonSize or 25) + 4
end
---@param questFrame Frame ---@param questFrame Frame
function TrackerLinePool.Initialize(questFrame) function TrackerLinePool.Initialize(questFrame)
local trackerQuestFrame = questFrame local trackerQuestFrame = questFrame
@@ -404,6 +479,151 @@ function TrackerLinePool.Initialize(questFrame)
line.playButton = playButton line.playButton = playButton
-- create supertrack buttons for the Ascension floating objective marker
local superTrackButton = CreateFrame("Button", "linePool.superTrackButton" .. i, line)
superTrackButton:SetWidth(25)
superTrackButton:SetHeight(25)
superTrackButton:SetHitRectInsets(1, 1, 1, 1)
-- Hover and pressed states come from the pin atlas too, additively blended, exactly as the
-- client's own pins do it. RefreshSuperTrackButton mirrors the pin's cells over these.
superTrackButton:SetHighlightTexture("Interface\\WorldMap\\UI-QuestPoi-NumberIcons", "ADD")
superTrackButton:GetHighlightTexture():SetTexCoord(0.625, 0.75, 0.375, 0.5)
superTrackButton:SetPushedTexture("Interface\\WorldMap\\UI-QuestPoi-NumberIcons")
-- Same texture and atlas sub-rect the client's own quest POI pins use, so the tracker button
-- reads as native rather than as an addon icon.
superTrackButton:SetNormalTexture("Interface\\WorldMap\\UI-QuestPoi-NumberIcons")
superTrackButton:GetNormalTexture():SetTexCoord(0.5, 0.625, 0.875, 1)
-- The client marks the selected pin with this glow rather than by swapping the icon, so the
-- tracker button highlights the same way the map pin does.
superTrackButton.glow = superTrackButton:CreateTexture(nil, "BACKGROUND")
superTrackButton.glow:SetTexture("Interface\\WorldMap\\UI-QuestPoi-IconGlow")
superTrackButton.glow:SetBlendMode("ADD")
superTrackButton.glow:SetPoint("CENTER", superTrackButton, "CENTER", 0, 0)
superTrackButton.glow:Hide()
-- The digit printed inside the pin is another cell of the same atlas, drawn over the icon.
superTrackButton.number = superTrackButton:CreateTexture(nil, "OVERLAY")
superTrackButton.number:SetPoint("CENTER", superTrackButton, "CENTER", 0, 0)
superTrackButton.number:Hide()
-- Completed quests draw a "?" from this separate region instead of a digit.
superTrackButton.turnin = superTrackButton:CreateTexture(nil, "OVERLAY")
superTrackButton.turnin:SetPoint("CENTER", superTrackButton, "CENTER", 0, 0)
superTrackButton.turnin:Hide()
-- The quest id is remembered even while the button is hidden. POI frames only exist once the
-- world map has built them, so a quest that looks unreachable while the tracker is drawing
-- can become reachable later -- without the id we would have nothing left to re-check.
-- The pin's pressed state nudges what is drawn inside it down and to the right; the pushed
-- texture covers the circle, this covers the digit and the "?".
superTrackButton.SetPressedOffset = function(self, pressed)
local offset = pressed and 1 or 0
self.number:ClearAllPoints()
self.number:SetPoint("CENTER", self, "CENTER", offset, -offset)
self.turnin:ClearAllPoints()
self.turnin:SetPoint("CENTER", self, "CENTER", offset, -offset)
end
superTrackButton.SetSuperTrackButton = function(self, questId)
self.questId = questId
self:RefreshSuperTrackButton()
end
superTrackButton.RefreshSuperTrackButton = function(self)
-- No map pin means the client has nothing to point the marker at (quest in another zone,
-- or a quest without map coordinates), so there is nothing to offer.
local pin = self.questId and TrackerUtils:GetSuperTrackPin(self.questId)
if (not pin) or (not Questie.db.profile.trackerShowSuperTrackButton) then
self:Hide()
return
end
local buttonSize = Questie.db.profile.trackerSuperTrackButtonSize or 25
self:SetWidth(buttonSize)
self:SetHeight(buttonSize)
self.glow:SetWidth(buttonSize * 1.4)
self.glow:SetHeight(buttonSize * 1.4)
-- Mirror the pin rather than picking atlas cells ourselves, so whatever the client
-- decides to draw -- digit, "?", selected variant -- shows up here unchanged. The
-- texture file has to be copied along with the coordinates: completed quests swap in a
-- different file for these slots, and coordinates from one file applied to another
-- sample nonsense.
local isSuperTracked = TrackerUtils:GetSuperTrackedQuestId() == self.questId
local pinTexture = pin.GetNormalTexture and pin:GetNormalTexture()
if pinTexture then
local ownTexture = self:GetNormalTexture()
ownTexture:SetTexture(pinTexture:GetTexture())
ownTexture:SetTexCoord(pinTexture:GetTexCoord())
ApplySelectedVariant(ownTexture, isSuperTracked)
end
MirrorPinButtonTexture(self:GetHighlightTexture(), pin.GetHighlightTexture and pin:GetHighlightTexture())
local ownPushed = self:GetPushedTexture()
MirrorPinButtonTexture(ownPushed, pin.GetPushedTexture and pin:GetPushedTexture())
if ownPushed then
ApplySelectedVariant(ownPushed, isSuperTracked)
end
local pinSize = pin:GetWidth()
MirrorPinRegion(self.number, pin.number, buttonSize, pinSize)
MirrorPinRegion(self.turnin, pin.turnin, buttonSize, pinSize)
if self.number:IsShown() then
ApplySelectedVariant(self.number, isSuperTracked)
end
-- Undo any leftover pressed offset: the pool recycles buttons, and a line can be redrawn
-- while the mouse is still held down.
self:SetPressedOffset(false)
-- Flush with the left edge of the line, in the gutter GetSuperTrackMarginReserve keeps
-- clear. Anchored to the top rather than centred on the line so the button stays level
-- with the first row of a quest title that wraps, matching the collapse button.
local fontSizeQuest = Questie.db.profile.trackerFontSizeQuest
self:ClearAllPoints()
self:SetPoint("TOPLEFT", line, "TOPLEFT", 0, (buttonSize - fontSizeQuest) / 2 + 1)
-- The icon carries the selected variant itself; the glow is the one part the pin draws
-- as a separate texture.
if isSuperTracked then
self.glow:Show()
else
self.glow:Hide()
end
-- Has to sit above the tracker backdrop, which is what swallows a frame left at level 0.
self:SetFrameLevel(line:GetFrameLevel() + 10)
self:Show()
end
superTrackButton:EnableMouse(true)
superTrackButton:RegisterForClicks("LeftButtonUp")
superTrackButton:SetScript("OnMouseDown", function(self)
self:SetPressedOffset(true)
end)
superTrackButton:SetScript("OnMouseUp", function(self)
self:SetPressedOffset(false)
end)
superTrackButton:SetScript("OnClick", function(self)
if self.questId then
-- Same sound the client plays for its own quest pins.
PlaySound("igMainMenuOptionCheckBoxOn")
TrackerUtils:SetSuperTrackedQuest(self.questId)
end
end)
superTrackButton:Hide()
line.superTrackButton = superTrackButton
-- create expanding buttons for quests with objectives -- create expanding buttons for quests with objectives
local expandQuest = CreateFrame("Button", "linePool.expandQuest" .. i, line) local expandQuest = CreateFrame("Button", "linePool.expandQuest" .. i, line)
expandQuest.texture = expandQuest:CreateTexture(nil, "OVERLAY", nil, 0) expandQuest.texture = expandQuest:CreateTexture(nil, "OVERLAY", nil, 0)
@@ -731,11 +951,29 @@ function TrackerLinePool.ResetLinesForChange()
line.playButton:SetAlpha(0) line.playButton:SetAlpha(0)
line.playButton:Hide() line.playButton:Hide()
end end
if line.superTrackButton then
line.superTrackButton.questId = nil
line.superTrackButton:Hide()
end
end end
lineIndex = 0 lineIndex = 0
end end
-- Re-evaluates the supertrack buttons without rebuilding the tracker. Driven by the
-- SetSuperTrackedQuestID hook, which also fires on map open/close -- that is what makes buttons
-- appear once the world map has built its POI frames, since the tracker itself does not redraw then.
function TrackerLinePool.UpdateSuperTrackButtons()
-- Rebuild the map's POI frames first so a zone change is picked up even with the map closed.
TrackerUtils:PrimeSuperTrackFrames()
for _, line in pairs(linePool) do
if line.superTrackButton and line.superTrackButton.questId then
line.superTrackButton:RefreshSuperTrackButton()
end
end
end
function TrackerLinePool.ResetButtonsForChange() function TrackerLinePool.ResetButtonsForChange()
if TrackerBaseFrame.isSizing == true or TrackerBaseFrame.isMoving == true then if TrackerBaseFrame.isSizing == true or TrackerBaseFrame.isMoving == true then
Questie:Debug(Questie.DEBUG_SPAM, "[TrackerLinePool:ResetButtonsForChange]") Questie:Debug(Questie.DEBUG_SPAM, "[TrackerLinePool:ResetButtonsForChange]")
@@ -1098,6 +1336,12 @@ end
TrackerLinePool.SetMode = function(self, mode) TrackerLinePool.SetMode = function(self, mode)
if mode ~= self.mode then if mode ~= self.mode then
self.mode = mode self.mode = mode
-- Lines are recycled between zone headers, quest titles and objectives. Only quest title
-- lines own a supertrack button, so drop it whenever a line takes on another role.
if mode ~= "quest" and self.superTrackButton then
self.superTrackButton.questId = nil
self.superTrackButton:Hide()
end
if mode == "zone" then if mode == "zone" then
local trackerFontSizeZone = Questie.db.profile.trackerFontSizeZone local trackerFontSizeZone = Questie.db.profile.trackerFontSizeZone
self.label:SetFont((LSM30 and LSM30.Fetch and LSM30:Fetch("font", Questie.db.profile.trackerFontZone)) or Questie.db.profile.trackerFontZone, trackerFontSizeZone, Questie.db.profile.trackerFontOutline) self.label:SetFont((LSM30 and LSM30.Fetch and LSM30:Fetch("font", Questie.db.profile.trackerFontZone)) or Questie.db.profile.trackerFontZone, trackerFontSizeZone, Questie.db.profile.trackerFontOutline)
+267
View File
@@ -1265,6 +1265,273 @@ function TrackerUtils:GetSortedQuestIds()
return sortedQuestIds, questDetails return sortedQuestIds, questDetails
end end
-- Ascension's 3.3.5 client backports the retail floating objective marker ("SuperTracker").
-- Supertracking is a slave of the quest log selection: the world map and the Blizzard watch frame
-- both funnel through SelectQuestLogEntry -> SuperTrackerUtil.SetToBestSuperTrackingType. Calling
-- C_SuperTrack.SetSuperTrackedQuestID directly only moves the marker until the next map interaction
-- stomps it, so we click the same POI frames the client itself clicks.
local superTrackedQuestId
local superTrackHooked
local superTrackRefreshing
local superTrackRefreshPending
local superTrackEventFrame
-- Refreshing rebuilds the map's POI frames, which can land back in the very hooks that asked for the
-- refresh, so this is the only way the buttons are ever repainted. Requests are also coalesced:
-- callers like TrackerQuestTimers select a quest log entry and immediately restore the previous one,
-- so reading the selection on the first of those calls would catch a state the client is about to
-- undo. Waiting a tick means the burst has settled.
local function RefreshSuperTrackButtons()
if superTrackRefreshing or superTrackRefreshPending then
return
end
superTrackRefreshPending = true
C_Timer.After(0.05, function()
superTrackRefreshPending = false
superTrackRefreshing = true
TrackerLinePool.UpdateSuperTrackButtons()
superTrackRefreshing = false
end)
end
function TrackerUtils:IsSuperTrackAvailable()
return (C_SuperTrack ~= nil) and ((WorldMapFrame_SelectQuestFrame ~= nil) or (WatchFrameQuestPOI_OnClick ~= nil))
end
-- Most paths that change the supertracked quest end up in SetSuperTrackedQuestID -- our own button,
-- world map pins, the map quest list, the Blizzard tracker, and the automatic re-pick that happens
-- when the map switches zone -- so hooking it stands in for the GetSuperTrackedQuestID getter this
-- client dropped. It goes quiet while the player is a ghost, though: the corpse arrow takes the
-- marker over, so no quest is ever handed to it even though the map keeps selecting one. The quest
-- selection itself is therefore hooked as well, and that is what keeps the buttons honest while
-- dead. Caching what we last set would go stale the moment the player changed it by other means.
function TrackerUtils:InitSuperTrackHook()
if superTrackHooked or (not C_SuperTrack) then
return
end
superTrackHooked = true
hooksecurefunc(C_SuperTrack, "SetSuperTrackedQuestID", function(questId)
superTrackedQuestId = questId
RefreshSuperTrackButtons()
end)
if C_SuperTrack.ClearSuperTracker then
hooksecurefunc(C_SuperTrack, "ClearSuperTracker", function()
superTrackedQuestId = nil
RefreshSuperTrackButtons()
end)
end
if WorldMapFrame_SelectQuestFrame then
hooksecurefunc("WorldMapFrame_SelectQuestFrame", RefreshSuperTrackButtons)
end
if WatchFrameQuestPOI_OnClick then
hooksecurefunc("WatchFrameQuestPOI_OnClick", RefreshSuperTrackButtons)
end
-- Everything that supertracks a quest goes through the quest log selection, map or no map, alive
-- or dead -- including opening a quest in the quest log window, which no other hook here sees.
if SelectQuestLogEntry then
hooksecurefunc("SelectQuestLogEntry", RefreshSuperTrackButtons)
end
-- Nothing at all fires on a login or a reload: the map has not been touched, so the hooks above
-- stay silent and the tracker draws before the client has styled its pins. These events are the
-- only prompt to go back and look.
superTrackEventFrame = CreateFrame("Frame")
superTrackEventFrame:RegisterEvent("PLAYER_ENTERING_WORLD")
superTrackEventFrame:RegisterEvent("PLAYER_UNGHOST")
superTrackEventFrame:RegisterEvent("PLAYER_ALIVE")
superTrackEventFrame:RegisterEvent("PLAYER_DEAD")
superTrackEventFrame:SetScript("OnEvent", function()
RefreshSuperTrackButtons()
-- The client fills in its POI frames a moment after entering the world, so the immediate
-- pass above can still come up empty.
C_Timer.After(2, RefreshSuperTrackButtons)
end)
end
-- The hook only hears about the changes the client itself makes, and while the player is a ghost the
-- floating marker is disabled: selecting a quest then never reaches SetSuperTrackedQuestID, so the
-- hook reports nothing after a login or a reload in that state and goes stale after any click. The
-- client's own frames still know. Watch frame POI buttons say so outright, and the world map's quest
-- frames say it through their art: the atlases stack the selected (yellow) variant of a cell half a
-- texture above the normal one, so a pin drawn from the upper half is the supertracked one.
local function FindSelectedQuestId()
for i = 1, 30 do
local firstInRow = _G["poiWatchFrameLines" .. i .. "_1"]
if not firstInRow then
break
end
for j = 1, 5 do
local poiButton = (j == 1) and firstInRow or _G["poiWatchFrameLines" .. i .. "_" .. j]
if not poiButton then
break
end
if poiButton.isSelected and poiButton.questId then
return poiButton.questId
end
end
end
for i = 1, 25 do
local questFrame = _G["WorldMapQuestFrame" .. i]
if not questFrame then
break
end
local pin = questFrame.ownPOI or questFrame.poiIcon
local pinTexture = pin and pin.GetNormalTexture and pin:GetNormalTexture()
if pinTexture and questFrame.questId then
local _, topY = pinTexture:GetTexCoord()
if topY and topY < 0.5 then
return questFrame.questId
end
end
end
-- Right after a login or a reload no pin is styled at all: the client marks them the first time
-- the world map is opened. The quest log selection is what it reads when it gets there, so it
-- answers for the gap in between.
local selection = GetQuestLogSelection and GetQuestLogSelection()
if selection and selection > 0 then
-- GetQuestIDFromLogIndex, not the raw API: GetQuestLogTitle is the compat wrapper here,
-- which normalises the client's 9 return values down to 8.
local questId = QuestieCompat.GetQuestIDFromLogIndex(selection)
if questId and questId ~= 0 then
return questId
end
end
return nil
end
-- The client is asked before the hook: the hook cannot see a ghost's selection changes at all, so
-- its value is the fallback for when no POI frame exists to read (another zone, or frames not built
-- yet), not the source of truth.
function TrackerUtils:GetSuperTrackedQuestId()
return FindSelectedQuestId() or superTrackedQuestId
end
-- The world map builds its quest POI frames lazily, so right after login -- or after a zone change
-- with the map still closed -- there is nothing to match a quest against and every button would
-- hide itself. The client can build them without the map being shown, and doing so does not disturb
-- which quest is currently supertracked. Skipped while the map is open so we never fight the player.
function TrackerUtils:PrimeSuperTrackFrames()
if WorldMapFrame and WorldMapFrame:IsShown() then
return
end
if WorldMapFrame_UpdateQuests then
WorldMapFrame_UpdateQuests()
end
end
---@return table|nil frame, function|nil clickHandler
local function GetSuperTrackFrame(questId)
if (not questId) or questId == 0 then
return nil
end
-- Blizzard watch frame POI buttons carry the quest id directly. Questie empties the Blizzard
-- watch list (see QuestieTracker:AQW_Insert) so only a handful of quests ever have one. Both
-- scans below stop at the first gap: these frames are created in order, so a missing index
-- means there are no further ones and this runs once per quest line per redraw.
if WatchFrameQuestPOI_OnClick then
for i = 1, 30 do
local firstInRow = _G["poiWatchFrameLines" .. i .. "_1"]
if not firstInRow then
break
end
for j = 1, 5 do
local poiButton = (j == 1) and firstInRow or _G["poiWatchFrameLines" .. i .. "_" .. j]
if not poiButton then
break
end
if poiButton.questId == questId then
return poiButton, WatchFrameQuestPOI_OnClick
end
end
end
end
-- World map quest frames cover every quest with a POI on the currently viewed map, which is the
-- wider net of the two. Pass the WorldMapQuestFrame itself and never its poiIcon --
-- WorldMapFrame_SelectQuestFrame indexes questFrame.poiIcon and errors on the POI frame.
if WorldMapFrame_SelectQuestFrame then
if not _G["WorldMapQuestFrame1"] then
TrackerUtils:PrimeSuperTrackFrames()
end
for i = 1, 25 do
local questFrame = _G["WorldMapQuestFrame" .. i]
if not questFrame then
break
end
if questFrame.questId == questId then
return questFrame, WorldMapFrame_SelectQuestFrame
end
end
end
return nil
end
-- A quest can only be supertracked while it has a POI frame, so quests in another zone or without
-- map coordinates simply have no button.
function TrackerUtils:CanSuperTrackQuest(questId)
return GetSuperTrackFrame(questId) ~= nil
end
-- The map pin the client draws for this quest. Copying its texture coordinates is what keeps the
-- tracker button identical to the pin: the number, the "?" shown for completed quests and the
-- black-on-yellow selected variant all follow along without us mapping atlas cells by hand. The
-- number is also the pin's position among quests that actually have a POI, which is not the same as
-- the quest frame index -- another reason to read it from the client instead of deriving it.
---@return table|nil
function TrackerUtils:GetSuperTrackPin(questId)
if (not questId) or questId == 0 or (not WorldMapFrame_SelectQuestFrame) then
return nil
end
if not _G["WorldMapQuestFrame1"] then
TrackerUtils:PrimeSuperTrackFrames()
end
for i = 1, 25 do
local questFrame = _G["WorldMapQuestFrame" .. i]
if not questFrame then
break
end
if questFrame.questId == questId then
-- ownPOI is the pin drawn in the map's quest list, poiIcon the one on the map itself.
-- The list version is the one we mirror: it keeps the circular background on completed
-- quests, where the map version draws a bare "?".
return questFrame.ownPOI or questFrame.poiIcon
end
end
return nil
end
function TrackerUtils:SetSuperTrackedQuest(questId)
local frame, clickHandler = GetSuperTrackFrame(questId)
if not frame then
return false
end
clickHandler(frame)
-- Refresh here rather than leaning on the SetSuperTrackedQuestID hook: it stays silent while the
-- player is a ghost, which would leave the button we just clicked looking untouched.
RefreshSuperTrackButtons()
return true
end
function TrackerUtils:IsVoiceOverLoaded() function TrackerUtils:IsVoiceOverLoaded()
-- Require not just that the VoiceOver addons are loaded, but that the runtime -- Require not just that the VoiceOver addons are loaded, but that the runtime
-- structure we index actually exists. Some VoiceOver builds (e.g. on Elune) expose -- structure we index actually exists. Some VoiceOver builds (e.g. on Elune) expose