Improve tracker, routes, and tooltips

This commit is contained in:
Rivi-s
2026-09-05 15:42:03 -07:00
parent dcf5570e01
commit 4654084bfa
5 changed files with 142 additions and 16 deletions
+45 -2
View File
@@ -701,6 +701,18 @@ end
-- ShowExtendedTooltip
-- Draws quest informations into a tooltip
local MAX_TOOLTIP_DESCRIPTION = 500
local function GetTooltipDescription(text)
local formatted = pfDatabase:FormatQuestText(text)
if strlen(formatted) <= MAX_TOOLTIP_DESCRIPTION then
return formatted
end
-- Extended descriptions are useful context, but a few custom quests contain
-- several full paragraphs. Keep tooltips readable without hiding objectives.
return strsub(formatted, 1, MAX_TOOLTIP_DESCRIPTION) .. "..."
end
function pfDatabase:ShowExtendedTooltip(id, tooltip, parent, anchor, offx, offy)
local tooltip = tooltip or GameTooltip
local parent = parent or this
@@ -770,7 +782,7 @@ function pfDatabase:ShowExtendedTooltip(id, tooltip, parent, anchor, offx, offy)
-- details
if locales["D"] and locales["D"] ~= "" then
tooltip:AddLine(" ")
tooltip:AddLine(pfDatabase:FormatQuestText(locales["D"]), 0.6, 0.6, 0.6, true)
tooltip:AddLine(GetTooltipDescription(locales["D"]), 0.6, 0.6, 0.6, true)
end
end
@@ -2020,7 +2032,9 @@ function pfDatabase:GetQuestIDs(qid)
if header or not title then
return
end
local identifier = title .. ":" .. (level or "") .. ":" .. (objective or "") .. ":" .. (text or "")
-- Version this key when resolver rules change so stale same-title matches
-- do not keep bypassing the improved live-objective disambiguation.
local identifier = "objective-v2:" .. title .. ":" .. (level or "") .. ":" .. (objective or "") .. ":" .. (text or "")
-- always make sure the quest-cache exists
pfQuest_questcache = pfQuest_questcache or {}
@@ -2037,6 +2051,24 @@ function pfDatabase:GetQuestIDs(qid)
local best = 0
local results = {}
-- Several quest chains reuse both title and level (The Defias Brotherhood
-- is a prominent example). The description fallback can tie on Turtle's
-- altered text, so collect actual item objectives from the quest log and
-- use them as a definitive stage discriminator below.
local objectiveItems = {}
local boardCount = GetNumQuestLeaderBoards(qid) or 0
for board = 1, boardCount do
local boardText, boardType = GetQuestLogLeaderBoard(board, qid)
if boardType == "item" and boardText then
local _, _, itemName = strfind(boardText, "^(.-):")
if itemName then
for itemId in pairs(pfDatabase:GetIDByName(itemName, "items")) do
objectiveItems[itemId] = true
end
end
end
end
local tcount = 0
-- check if multiple quests share the same name
for id, data in pairs(pfDB["quests"]["loc"]) do
@@ -2105,6 +2137,17 @@ function pfDatabase:GetQuestIDs(qid)
-- check description and calculate score
score = score + max(24 - lev(pfDatabase:FormatQuestText(pfDB.quests.loc[id]["D"]), text, 24), 0)
-- A matching live item objective distinguishes same-name stages even
-- when their title, level, and Turtle quest text are otherwise equal.
if quests[id]["obj"] and quests[id]["obj"]["I"] then
for _, itemId in pairs(quests[id]["obj"]["I"]) do
if objectiveItems[itemId] then
score = score + 64
break
end
end
end
end
if score > best then
+20 -8
View File
@@ -1081,21 +1081,31 @@ function pfMap:UpdateNodes()
-- reset tracker
pfQuest.tracker.Reset()
-- reset route
pfQuest.route:Reset()
-- Continent and world views do not resolve to a zone map ID. Extensions can
-- render their own pins there, but the core zone-node renderer must not use
-- nil as a dirty-map key.
if not map then
pfQuest.route:Clear()
pfMap.lastRouteMap = nil
for _, pin in pairs(pfMap.pins) do
pin:Hide()
end
pfMap.lastUpdateZone = nil
pfMap.mapJustOpened = nil
if pfQuest.tracker and pfQuest.tracker.DoLayout then
pfQuest.tracker.DoLayout()
end
return
end
-- A tracker/UI refresh can call UpdateNodes without changing any map node.
-- Keep the existing route in that case; resetting it redraws the path every
-- couple of seconds even though its inputs are unchanged.
if pfMap.lastRouteMap ~= map or pfMap.dirtyMaps[map] then
pfQuest.route:Reset()
pfMap.lastRouteMap = map
end
-- refresh all nodes
local n_pins, n_skipped = 0, 0
-- hoist map dimensions: same for every pin this call, and if the map
@@ -1141,6 +1151,13 @@ function pfMap:UpdateNodes()
pfQuest.route:AddPoint({ x, y, pfMap.pins[i] })
end
-- Populate the tracker even when the matching map pin is hidden by a
-- display preference. Hidden objective spawns are still active quests
-- and must remain visible in Current Zone Only mode.
for title, node in pairs(pfMap.pins[i].node) do
pfQuest.tracker.ButtonAdd(title, node)
end
-- hide cluster nodes if set
if pfQuest_config["showcluster"] == "0" and pfMap.pins[i].cluster then
pfMap.pins[i]:Hide()
@@ -1148,11 +1165,6 @@ function pfMap:UpdateNodes()
elseif pfQuest_config["showspawn"] == "0" and addon == "PFQUEST" and not pfMap.pins[i].texture then
pfMap.pins[i]:Hide()
else
-- populate quest list on map
for title, node in pairs(pfMap.pins[i].node) do
pfQuest.tracker.ButtonAdd(title, node)
end
local px = x / 100 * mapW
local py = y / 100 * mapH
+1 -1
View File
@@ -3,7 +3,7 @@
## Author: Shagu, txtsd
## Notes: A Questhelper and Database
## Notes-ruRU: Облегченный помощник заданий и база данных
## Version: 8.0.1
## Version: 8.0.2
## OptionalDeps: pfUI
## SavedVariables: pfQuest_questcache
## SavedVariablesPerCharacter: pfQuest_config, pfBrowser_fav, pfQuest_history, pfQuest_colors, pfQuest_server, pfQuest_track
+4
View File
@@ -262,6 +262,10 @@ pfQuest:SetScript("OnUpdate", function()
local meta = { ["addon"] = "PFQUEST", ["qlogid"] = entry[3] }
local t1 = GetTime()
pfDatabase:SearchQuestID(entry[2], meta)
-- SearchQuestID marks map nodes dirty, but does not itself request a
-- render. Queue one after the quest batch settles so the tracker
-- receives newly found same-zone objectives immediately.
pfMap.queue_update = GetTime()
pfQuest:Debug(format("|cffff8800TIMER SearchQuestID: %.4fs", GetTime() - t1))
end
end
+72 -5
View File
@@ -34,7 +34,16 @@ local function GetNearest(xstart, ystart, db, blacklist)
-- Use squared distance for comparison (avoid sqrt)
local distSq = x * x + y * y
if not nearest or distSq < nearest then
local replace = not nearest or distSq < nearest
if distSq == nearest and best then
local previous = db[best]
-- Keep equal-distance choices stable even when db was rebuilt through
-- pairs(). Coordinates are sufficient; the index is a final tie-break.
replace = data[1] < previous[1]
or (data[1] == previous[1] and (data[2] < previous[2] or (data[2] == previous[2] and id < best)))
end
if replace then
nearest = distSq
best = id
end
@@ -157,14 +166,26 @@ pfQuest.route.coords = {}
pfQuest.route.Reset = function(self)
self.coords = {}
self.firstnode = nil
self.recalculate = true
self.lastDrawX = nil
self.lastDrawY = nil
self.lastDrawNode = nil
end
pfQuest.route.Clear = function(self)
self:Reset()
ClearPath(objectivepath)
ClearPath(playerpath)
ClearPath(mplayerpath)
if self.arrow then
self.arrow:Hide()
end
end
pfQuest.route.AddPoint = function(self, tbl)
table.insert(self.coords, tbl)
self.firstnode = nil
self.recalculate = true
end
local targetTitle, targetCluster, targetLayer, targetTexture = nil, nil, nil, nil
@@ -185,6 +206,7 @@ pfQuest.route.SetTarget = function(node, default)
targetCluster = node and node.cluster or nil
targetLayer = node and node.layer or nil
targetTexture = node and node.texture or nil
pfQuest.route.recalculate = true
end
pfQuest.route.IsTarget = function(node)
@@ -203,8 +225,29 @@ pfQuest.route.IsTarget = function(node)
end
local lastpos, completed = 0, 0
local automaticTargetKey = nil
local function TargetKey(data)
if not data then return nil end
local node = data[3]
return tostring(data[1]) .. ":" .. tostring(data[2]) .. ":" .. tostring(node and (node.title or node.spawn) or "")
end
local function sortfunc(a, b)
return a[4] < b[4]
-- Distances are rounded to two decimals, so ties are common. UpdateNodes
-- rebuilds its list through pairs(), whose order is undefined; without a
-- deterministic tie-breaker the first route target can flip every refresh.
if a[4] ~= b[4] then
return a[4] < b[4]
end
if a[1] ~= b[1] then
return a[1] < b[1]
end
if a[2] ~= b[2] then
return a[2] < b[2]
end
local an = a[3] and (a[3].title or a[3].spawn or "") or ""
local bn = b[3] and (b[3].title or b[3].spawn or "") or ""
return tostring(an) < tostring(bn)
end
pfQuest.route:SetScript("OnUpdate", function()
local xplayer, yplayer = GetPlayerMapPosition("player")
@@ -235,10 +278,32 @@ pfQuest.route:SetScript("OnUpdate", function()
this.coords[id][4] = ceil(math.sqrt(x * x + y * y) * 100) / 100
end
end
-- sort all coords by distance only once per second
if not this.recalculate or this.recalculate < GetTime() then
-- Reorder only when the available route nodes or an explicit target change.
-- Re-sorting every second while the player moves causes route flicker and
-- expensive map redraws without improving the selected objective.
if this.recalculate then
table.sort(this.coords, sortfunc)
-- Map-position updates have slight jitter. Keep the current automatic
-- target while it remains close to the best candidate, otherwise two
-- nearby objectives can continuously trade places and redraw the route.
if not targetTitle and automaticTargetKey and this.coords[1] then
local previousIndex
for id, data in ipairs(this.coords) do
if TargetKey(data) == automaticTargetKey then
previousIndex = id
break
end
end
if previousIndex and previousIndex > 1 then
local previous = this.coords[previousIndex]
if previous[4] <= this.coords[1][4] + 0.5 then
table.remove(this.coords, previousIndex)
table.insert(this.coords, 1, previous)
end
end
end
-- order list on custom targets
if targetTitle and this.coords[1] and not pfQuest.route.IsTarget(this.coords[1][3]) then
local target = nil
@@ -266,7 +331,9 @@ pfQuest.route:SetScript("OnUpdate", function()
end
end
this.recalculate = GetTime() + 1
automaticTargetKey = not targetTitle and TargetKey(this.coords[1]) or nil
this.recalculate = nil
end
-- show arrow when route exists and is stable