mirror of
https://github.com/rivi-s/pfQuest.git
synced 2026-11-10 20:17:36 +00:00
1464 lines
54 KiB
Lua
1464 lines
54 KiB
Lua
-- multi api compat
|
|
local compat = pfQuestCompat
|
|
local _, _, _, client = GetBuildInfo()
|
|
client = client or 11200
|
|
local _G = client == 11200 and getfenv(0) or _G
|
|
|
|
-- Performance: cache frequently-used globals
|
|
local pairs, ipairs, next = pairs, ipairs, next
|
|
local strfind = strfind
|
|
local format = string.format
|
|
local getn, insert, concat = table.getn, table.insert, table.concat
|
|
local tostring, tonumber, type = tostring, tonumber, type
|
|
local GetTime = GetTime
|
|
local UnitLevel = UnitLevel
|
|
|
|
pfQuest = CreateFrame("Frame")
|
|
pfQuest.RebuildConfigUI = function()
|
|
return pfQuestConfig:RebuildConfigUI()
|
|
end
|
|
pfQuest.icons = {}
|
|
pfQuest_global = pfQuest_global or {}
|
|
|
|
pfQuest.defaultdburl = "https://database.ravencraft.io/?quest="
|
|
|
|
-- The Quest Log's Show button should lead to the quest hub when one is
|
|
-- known. Objective spawn density can otherwise send it to a nearby zone
|
|
-- with more creatures, even when the quest begins and ends elsewhere.
|
|
local function GetQuestHubMap(id)
|
|
local quest = pfDB.quests and pfDB.quests.data and pfDB.quests.data[id]
|
|
if not quest then
|
|
return
|
|
end
|
|
|
|
local function findMap(relation)
|
|
local maps, bestMap, bestCount = {}, nil, 0
|
|
if not relation then
|
|
return
|
|
end
|
|
|
|
for _, kind in ipairs({ "U", "O" }) do
|
|
local entries = relation[kind]
|
|
local database = kind == "U" and pfDB.units and pfDB.units.data or pfDB.objects and pfDB.objects.data
|
|
if entries and database then
|
|
for _, entry in ipairs(entries) do
|
|
local record = database[entry]
|
|
if record and record.coords then
|
|
for _, coord in ipairs(record.coords) do
|
|
local zone = coord[3]
|
|
if zone and zone > 0 then
|
|
maps[zone] = (maps[zone] or 0) + 1
|
|
-- Turtle can add a second copy of an existing quest NPC in a
|
|
-- custom zone. Keep the data's first location when counts tie.
|
|
if maps[zone] > bestCount then
|
|
bestMap, bestCount = zone, maps[zone]
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
return bestMap
|
|
end
|
|
|
|
return findMap(quest["end"]) or findMap(quest["start"])
|
|
end
|
|
|
|
function pfQuest:GetDatabaseURL()
|
|
local url = pfQuest_global["dburl"]
|
|
url = url and url ~= "" and url or self.defaultdburl
|
|
-- A plain database homepage links directly to the matching quest ID;
|
|
-- custom query prefixes continue to work as entered.
|
|
if strsub(url, -1) == "/" then
|
|
url = url .. "?quest="
|
|
end
|
|
return url
|
|
end
|
|
|
|
function pfQuest:Debug(msg)
|
|
-- only show debug output if enabled
|
|
if not pfQuest_config.debug and pfQuest.debugwin then
|
|
pfQuest.debugwin:Hide()
|
|
return
|
|
elseif not pfQuest_config.debug then
|
|
return
|
|
end
|
|
|
|
if not pfQuest.debugwin then
|
|
pfQuest.debugwin = CreateFrame("ScrollingMessageFrame", nil, UIParent)
|
|
pfQuest.debugwin:SetWidth(320)
|
|
pfQuest.debugwin:SetHeight(320)
|
|
pfQuest.debugwin:SetPoint("RIGHT", -42, 0)
|
|
local font = pfUI and pfUI.font_default or STANDARD_TEXT_FONT
|
|
local size = tonumber(pfQuest_config["trackerfontsize"]) or 12
|
|
pfQuest.debugwin:SetFont(font, size, "OUTLINE")
|
|
pfQuest.debugwin:SetFading(false)
|
|
pfQuest.debugwin:SetMaxLines(150)
|
|
pfQuest.debugwin:SetJustifyH("RIGHT")
|
|
pfQuest.debugwin:SetJustifyV("CENTER")
|
|
end
|
|
|
|
local font = pfUI and pfUI.font_default or STANDARD_TEXT_FONT
|
|
local size = tonumber(pfQuest_config["trackerfontsize"]) or 12
|
|
pfQuest.debugwin:SetFont(font, size, "OUTLINE")
|
|
pfQuest.debugwin:AddMessage(msg)
|
|
pfQuest.debugwin:Show()
|
|
end
|
|
|
|
function pfQuest:SortedPairs(t, index, reverse)
|
|
-- collect the keys
|
|
local keys = {}
|
|
for k, v in pairs(t) do
|
|
if v then
|
|
keys[table.getn(keys) + 1] = k
|
|
end
|
|
end
|
|
|
|
local order
|
|
if reverse then
|
|
order = function(t, a, b)
|
|
return t[a][index] < t[b][index]
|
|
end
|
|
else
|
|
order = function(t, a, b)
|
|
return t[a][index] > t[b][index]
|
|
end
|
|
end
|
|
table.sort(keys, function(a, b)
|
|
return order(t, a, b)
|
|
end)
|
|
|
|
-- return the iterator function
|
|
local i = 0
|
|
return function()
|
|
i = i + 1
|
|
if keys[i] then
|
|
return keys[i], t[keys[i]]
|
|
end
|
|
end
|
|
end
|
|
|
|
pfQuest.queue = {}
|
|
pfQuest.queueCount = 0 -- Track queue size to avoid O(n) tsize() calls
|
|
pfQuest.abandon = ""
|
|
pfQuest.questlog = {}
|
|
pfQuest.questlog_tmp = {}
|
|
|
|
-- Helper to add to queue with count tracking
|
|
local function queueAdd(entry)
|
|
insert(pfQuest.queue, entry)
|
|
pfQuest.queueCount = pfQuest.queueCount + 1
|
|
end
|
|
|
|
-- Source filtering changes belong on the established bounded quest queue.
|
|
function pfQuest:RefreshItemSourceSettings()
|
|
for id, quest in pairs(self.questlog or {}) do
|
|
if quest.qlogid and quest.title then
|
|
queueAdd({ quest.title, id, quest.qlogid, "RELOAD" })
|
|
end
|
|
end
|
|
end
|
|
|
|
local skillstate = ""
|
|
pfQuest:RegisterEvent("QUEST_WATCH_UPDATE")
|
|
pfQuest:RegisterEvent("QUEST_LOG_UPDATE")
|
|
pfQuest:RegisterEvent("QUEST_FINISHED")
|
|
pfQuest:RegisterEvent("PLAYER_LEVEL_UP")
|
|
pfQuest:RegisterEvent("PLAYER_ENTERING_WORLD")
|
|
pfQuest:RegisterEvent("SKILL_LINES_CHANGED")
|
|
pfQuest:RegisterEvent("ADDON_LOADED")
|
|
-- ClassicAPI supplies explicit accept/turn-in notifications. The legacy
|
|
-- QUEST_LOG_UPDATE route remains registered for ordinary 1.12 clients.
|
|
if pfQuestCompat.optional and pfQuestCompat.optional.questEvents then
|
|
pfQuest:RegisterEvent("QUEST_ACCEPTED")
|
|
pfQuest:RegisterEvent("QUEST_TURNED_IN")
|
|
pfQuest:RegisterEvent("QUEST_REMOVED")
|
|
end
|
|
pfQuest:SetScript("OnEvent", function()
|
|
if event == "ADDON_LOADED" then
|
|
if arg1 == "pfQuest" or arg1 == "pfQuest-tbc" or arg1 == "pfQuest-wotlk" then
|
|
pfQuest:AddQuestLogIntegration()
|
|
pfQuest:AddWorldMapIntegration()
|
|
this.lock = GetTime() + 10
|
|
else
|
|
return
|
|
end
|
|
elseif event == "QUEST_TURNED_IN" then
|
|
-- ClassicAPI reports the completed quest ID directly. Record it here so
|
|
-- instant auto turn-ins are not lost when a quest enters and leaves the
|
|
-- log between two legacy QUEST_LOG_UPDATE scans.
|
|
local questid = tonumber(arg1)
|
|
if questid then
|
|
pfQuest_history[questid] = { time(), UnitLevel("player") }
|
|
if pfJournal then pfJournal.dirty = true end
|
|
end
|
|
elseif event == "SKILL_LINES_CHANGED" then
|
|
-- Use table.concat to avoid string concatenation garbage
|
|
local skillParts = {}
|
|
for i = 0, GetNumSkillLines() do
|
|
skillParts[i + 1] = GetSkillLineInfo(i) or ""
|
|
end
|
|
local skills = concat(skillParts)
|
|
|
|
-- update quest givers when new skills or
|
|
-- professions became available
|
|
if skills ~= skillstate then
|
|
pfQuest.updateQuestGivers = true
|
|
skillstate = skills
|
|
end
|
|
elseif event == "PLAYER_LEVEL_UP" or event == "PLAYER_ENTERING_WORLD" then
|
|
pfQuest.updateQuestGivers = true
|
|
else
|
|
pfQuest.updateQuestLog = true
|
|
end
|
|
|
|
if event == "QUEST_LOG_UPDATE" then
|
|
-- lock initial scan during incoming events
|
|
if this.lock and this.lock > GetTime() then
|
|
this.lock = GetTime() + 1.5
|
|
end
|
|
end
|
|
end)
|
|
|
|
pfQuest:SetScript("OnUpdate", function()
|
|
if this.lock and this.lock > GetTime() then
|
|
return
|
|
end
|
|
if not pfDatabase.localized then
|
|
return
|
|
end
|
|
|
|
if (this.tick or 0.05) > GetTime() then
|
|
return
|
|
else
|
|
this.tick = GetTime() + 0.05
|
|
end
|
|
|
|
-- pfUI applies the initial Quest Log selection just after OnShow. Refresh
|
|
-- pfQuest once on the following tick so its controls do not require a
|
|
-- manual quest-selection change before appearing.
|
|
if this.questLogOpenRefreshAt and this.questLogOpenRefreshAt <= GetTime() then
|
|
this.questLogOpenRefreshAt = nil
|
|
if QuestLogFrame and QuestLogFrame:IsShown() then
|
|
pfQuest:UpdateQuestlog()
|
|
QuestLog_Update()
|
|
end
|
|
end
|
|
|
|
-- Game events refresh the quest log immediately. Keep a slow fallback for
|
|
-- clients that miss an event, but do not rebuild every active quest once
|
|
-- per second while the player is idle.
|
|
if (this.qlogtick or 60) < GetTime() then
|
|
local t0 = GetTime()
|
|
if pfQuest:UpdateQuestlog() then
|
|
-- The map renderer is not always active while playing. Refresh the
|
|
-- standalone tracker here too, so newly accepted unwatched quests do
|
|
-- not have to wait for a map update before appearing.
|
|
if pfQuest.tracker then
|
|
pfQuest.tracker.Reset()
|
|
pfQuest.tracker.DoLayout()
|
|
end
|
|
this.currentZoneRefreshAt = GetTime() + 0.75
|
|
pfQuest:Debug(format("Update Quest|cff33ffccLog|r [|cffff3333Tick|r] %.4fs", GetTime() - t0))
|
|
end
|
|
this.qlogtick = GetTime() + 60
|
|
end
|
|
|
|
if this.updateQuestLog == true and pfQuest.queueCount == 0 then
|
|
local t0 = GetTime()
|
|
if pfQuest:UpdateQuestlog() then
|
|
if pfQuest.tracker then
|
|
pfQuest.tracker.Reset()
|
|
pfQuest.tracker.DoLayout()
|
|
end
|
|
this.currentZoneRefreshAt = GetTime() + 0.75
|
|
end
|
|
pfQuest:Debug(format("Update Quest|cff33ffccLog %.4fs", GetTime() - t0))
|
|
this.updateQuestLog = false
|
|
end
|
|
|
|
if this.updateQuestGivers == true then
|
|
pfQuest:Debug("Update Quest|cff33ffcc Givers")
|
|
-- A fresh profile shows the mode selector during login. Do not generate
|
|
-- the complete available-questgiver map behind that selector; it can add
|
|
-- thousands of nodes while the client is still loading.
|
|
if pfQuest_config["welcome"] == "1" and pfQuest_config["trackingmethod"] ~= 4 and pfQuest_config["allquestgivers"] == "1" then
|
|
local meta = { ["addon"] = "PFQUEST" }
|
|
local t0 = GetTime()
|
|
pfDatabase:SearchQuests(meta)
|
|
pfQuest:Debug(format("|cffff3333TIMER SearchQuests: %.4fs", GetTime() - t0))
|
|
end
|
|
this.updateQuestGivers = false
|
|
end
|
|
|
|
-- Turtle can send QUEST_LOG_UPDATE before a newly accepted quest has fully
|
|
-- populated its objective data. Retry that one new entry shortly afterward
|
|
-- so its map nodes and standalone tracker do not depend on opening the map.
|
|
if this.questRetry and this.questRetry.at <= GetTime() then
|
|
local retry = this.questRetry
|
|
this.questRetry = nil
|
|
local active = pfQuest.questlog[retry.questid]
|
|
if active and active.qlogid == retry.qlogid and pfQuest_config["trackingmethod"] ~= 4 then
|
|
-- This pass exists only because Turtle can publish a NEW quest before
|
|
-- its objectives are ready. Keep the first successful render intact:
|
|
-- AddNode merges matching coordinates, while deleting here could erase
|
|
-- a valid objective set when the delayed payload is still incomplete.
|
|
-- Regular RELOAD events retain the full delete-and-rebuild behavior.
|
|
local retryMaps = pfDatabase:SearchQuestID(retry.questid, { ["addon"] = "PFQUEST", ["qlogid"] = retry.qlogid })
|
|
-- Current Zone Only normally receives entries while UpdateNodes walks
|
|
-- rendered pins. Confirm the quest is truly in the log before adding a
|
|
-- same-zone fallback for item/object objectives.
|
|
if tonumber(pfQuest_config["trackingmethod"]) == 5 and retryMaps then
|
|
local currentMap = pfMap:GetMapID(GetCurrentMapContinent(), GetCurrentMapZone())
|
|
if currentMap and retryMaps[currentMap] then
|
|
-- UpdateNodes rebuilds Current Zone Only from its visible pins. Keep
|
|
-- confirmed item/object quests that belong to this map in a small
|
|
-- map-scoped cache so that rebuild cannot immediately clear them.
|
|
pfMap.currentZoneTracker = pfMap.currentZoneTracker or {}
|
|
pfMap.currentZoneTracker[currentMap] = pfMap.currentZoneTracker[currentMap] or {}
|
|
pfMap.currentZoneTracker[currentMap][retry.questid] = retry.title
|
|
end
|
|
end
|
|
pfMap.queue_update = GetTime()
|
|
end
|
|
end
|
|
|
|
-- The server can issue its final QUEST_LOG_UPDATE after the initial node
|
|
-- scan. In Current Zone Only mode, rebuild the active quest nodes once the
|
|
-- queue has been quiet for a moment. This is event-driven (not per frame)
|
|
-- and leaves the tracker untouched.
|
|
if this.currentZoneRefreshAt and this.currentZoneRefreshAt <= GetTime() and pfQuest.queueCount == 0 then
|
|
this.currentZoneRefreshAt = nil
|
|
if tonumber(pfQuest_config["trackingmethod"]) == 5 then
|
|
for questid, data in pairs(pfQuest.questlog) do
|
|
if type(questid) == "number" and data.qlogid then
|
|
pfDatabase:SearchQuestID(questid, { ["addon"] = "PFQUEST", ["qlogid"] = data.qlogid })
|
|
end
|
|
end
|
|
pfMap.queue_update = GetTime()
|
|
end
|
|
end
|
|
|
|
if pfQuest.queueCount == 0 then
|
|
return
|
|
end
|
|
|
|
-- process queue
|
|
for id, entry in pairs(this.queue) do
|
|
-- questgivers only need refreshing when quests are added or removed,
|
|
-- not when objectives change (RELOAD). track this before clearing the entry.
|
|
if entry[4] == "NEW" or entry[4] == "REMOVE" then
|
|
this.needsQuestGiverUpdate = true
|
|
end
|
|
|
|
-- remove quest
|
|
if entry[4] == "REMOVE" then
|
|
pfQuest:Debug("|cffff5555Remove Quest: " .. entry[1] .. " (" .. entry[2] .. ")")
|
|
|
|
-- write pfQuest.questlog history
|
|
local abandoned = entry[1] == pfQuest.abandon
|
|
if abandoned then
|
|
pfQuest_history[entry[2]] = nil
|
|
else
|
|
pfQuest_history[entry[2]] = { time(), UnitLevel("player") }
|
|
end
|
|
-- Mark journal dirty when history changes
|
|
if pfJournal then
|
|
pfJournal.dirty = true
|
|
end
|
|
|
|
if pfQuest_config["trackingmethod"] ~= 4 then
|
|
-- delete nodes by title
|
|
local t0 = GetTime()
|
|
pfMap:DeleteNode("PFQUEST", entry[1])
|
|
|
|
-- also delete nodes by quest ids for servers with different names
|
|
if entry[2] and pfDB["quests"]["loc"][entry[2]] and pfDB["quests"]["loc"][entry[2]].T then
|
|
pfMap:DeleteNode("PFQUEST", pfDB["quests"]["loc"][entry[2]].T)
|
|
end
|
|
pfQuest:Debug(format("|cffffff00TIMER DeleteNode(REMOVE): %.4fs", GetTime() - t0))
|
|
end
|
|
|
|
-- Multiple quests can leave the log in one unordered queue batch. An
|
|
-- unrelated removal must not consume the identity of the quest the
|
|
-- player explicitly abandoned.
|
|
if abandoned then pfQuest.abandon = "" end
|
|
elseif entry[4] == "REINDEX" then
|
|
-- Accepting one quest can shift the Quest Log index of every quest
|
|
-- below it. The quest data itself has not changed, so patch the
|
|
-- cached qlogid on existing map nodes instead of a full delete/rebuild.
|
|
pfQuest:Debug("Reindex Quest: " .. entry[1] .. " (" .. entry[2] .. ")")
|
|
if pfMap and pfMap.nodes and pfMap.nodes.PFQUEST then
|
|
for _, coordinates in pairs(pfMap.nodes.PFQUEST) do
|
|
for _, titles in pairs(coordinates) do
|
|
for _, node in pairs(titles) do
|
|
if node and tonumber(node.questid) == tonumber(entry[2]) then
|
|
node.qlogid = entry[3]
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
pfMap.queue_update = GetTime()
|
|
else
|
|
if entry[4] == "NEW" then
|
|
pfQuest:Debug("|cff55ff55New Quest: " .. entry[1] .. " (" .. entry[2] .. ")")
|
|
else
|
|
pfQuest:Debug("|cffffff55Update Quest: " .. entry[1] .. " (" .. entry[2] .. ")")
|
|
end
|
|
|
|
-- update quest nodes
|
|
if pfQuest_config["trackingmethod"] ~= 4 then
|
|
-- delete node by title
|
|
local t0 = GetTime()
|
|
pfMap:DeleteNode("PFQUEST", entry[1])
|
|
|
|
-- delete nodes by quest ids for servers with different names
|
|
if entry[2] and pfDB["quests"]["loc"][entry[2]] and pfDB["quests"]["loc"][entry[2]].T then
|
|
pfMap:DeleteNode("PFQUEST", pfDB["quests"]["loc"][entry[2]].T)
|
|
end
|
|
pfQuest:Debug(format("|cffffff00TIMER DeleteNode(NEW/RELOAD): %.4fs", GetTime() - t0))
|
|
|
|
-- skip quest objective detection on manual and tracked mode
|
|
if
|
|
pfQuest_config["trackingmethod"] ~= 3
|
|
and (pfQuest_config["trackingmethod"] ~= 2 or IsQuestWatched(entry[3]))
|
|
then
|
|
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()
|
|
if entry[4] == "NEW" then
|
|
this.questRetry = {
|
|
questid = entry[2],
|
|
qlogid = entry[3],
|
|
title = entry[1],
|
|
at = GetTime() + 0.5,
|
|
}
|
|
end
|
|
pfQuest:Debug(format("|cffff8800TIMER SearchQuestID: %.4fs", GetTime() - t1))
|
|
end
|
|
end
|
|
end
|
|
|
|
if pfDatabase:QuestChangesBreadcrumbAvailability(entry[2]) then
|
|
this.needsQuestGiverUpdate = true
|
|
end
|
|
|
|
-- remove entry from queue and decrement counter
|
|
pfQuest.queue[id] = nil
|
|
pfQuest.queueCount = pfQuest.queueCount - 1
|
|
|
|
-- only return when other entries exist
|
|
-- otherwise, continue and update questgivers
|
|
if pfQuest.queueCount > 0 then
|
|
return
|
|
end
|
|
end
|
|
|
|
-- trigger questgiver update only when needed
|
|
if pfQuest.queueCount == 0 then
|
|
this.updateQuestLog = true
|
|
if this.needsQuestGiverUpdate then
|
|
this.updateQuestGivers = true
|
|
this.needsQuestGiverUpdate = false
|
|
end
|
|
end
|
|
end)
|
|
|
|
local questlog_flip, questlog_flop = {}, {}
|
|
function pfQuest:UpdateQuestlog()
|
|
-- initialize flip flop if not yet defined
|
|
pfQuest.questlog_tmp = pfQuest.questlog_tmp or questlog_flip
|
|
|
|
local _, numQuests = GetNumQuestLogEntries()
|
|
local found = 0
|
|
local change = nil
|
|
|
|
-- iterate over all quests
|
|
for qlogid = 1, 40 do
|
|
local title, _, _, header, _, complete = compat.GetQuestLogTitle(qlogid)
|
|
local objectives = GetNumQuestLeaderBoards(qlogid)
|
|
local watched, questid, state
|
|
|
|
if title and not header then
|
|
-- A legacy same-title lookup can select hidden quest rows. While the
|
|
-- Quest Log is open, keep background refreshes read-only so collapsed
|
|
-- categories remain collapsed through progress and completion updates.
|
|
local preserveSelection = QuestLogFrame and QuestLogFrame:IsShown()
|
|
questid = pfDatabase:GetQuestIDs(qlogid, preserveSelection)
|
|
questid = questid and tonumber(questid[1]) or title
|
|
watched = IsQuestWatched(qlogid)
|
|
|
|
-- build state string using table.concat (avoid string concat garbage)
|
|
-- Completion can change without adding or removing objective rows (for
|
|
-- example, a simple talk/report quest). Keep it in the change token so
|
|
-- its ender pin is rebuilt from the client state.
|
|
local stateParts = { watched and "track" or "", complete and "complete" or "incomplete" }
|
|
if objectives then
|
|
for i = 1, objectives, 1 do
|
|
local text, _, done = GetQuestLogLeaderBoard(i, qlogid)
|
|
stateParts[getn(stateParts) + 1] = i
|
|
stateParts[getn(stateParts) + 1] = text or ""
|
|
stateParts[getn(stateParts) + 1] = done and "done" or "todo"
|
|
end
|
|
end
|
|
state = concat(stateParts)
|
|
|
|
-- add new quest to the questlog
|
|
if not pfQuest.questlog[questid] then
|
|
queueAdd({ title, questid, qlogid, "NEW" })
|
|
pfQuest.questlog_tmp[questid] = {
|
|
title = title,
|
|
qlogid = qlogid,
|
|
state = state,
|
|
}
|
|
change = true
|
|
elseif pfQuest.questlog[questid].qlogid ~= qlogid then
|
|
-- Accepting one quest can shift the Quest Log index of every quest
|
|
-- below it. The quest data itself has not changed, so keep its map
|
|
-- nodes and update only the index used for live objective reads.
|
|
queueAdd({ title, questid, qlogid, "REINDEX" })
|
|
pfQuest.questlog_tmp[questid] = pfQuest.questlog[questid]
|
|
pfQuest.questlog_tmp[questid].qlogid = qlogid
|
|
pfQuest.questlog_tmp[questid].state = state
|
|
change = true
|
|
elseif pfQuest.questlog[questid].state ~= state then
|
|
queueAdd({ title, questid, qlogid, "RELOAD" })
|
|
pfQuest.questlog_tmp[questid] = pfQuest.questlog[questid]
|
|
pfQuest.questlog_tmp[questid].qlogid = qlogid
|
|
pfQuest.questlog_tmp[questid].state = state
|
|
change = true
|
|
else
|
|
pfQuest.questlog_tmp[questid] = pfQuest.questlog[questid]
|
|
end
|
|
|
|
found = found + 1
|
|
if found >= numQuests then
|
|
break
|
|
end
|
|
end
|
|
end
|
|
|
|
-- quest removal events
|
|
for questid, data in pairs(pfQuest.questlog) do
|
|
if not pfQuest.questlog_tmp[questid] then
|
|
queueAdd({ data.title, questid, nil, "REMOVE" })
|
|
change = true
|
|
end
|
|
end
|
|
|
|
-- set questlog to current flip flop
|
|
pfQuest.questlog = pfQuest.questlog_tmp
|
|
|
|
-- switch tmp to the other flip flop
|
|
if pfQuest.questlog_tmp == questlog_flip then
|
|
pfQuest.questlog_tmp = questlog_flop
|
|
else
|
|
pfQuest.questlog_tmp = questlog_flip
|
|
end
|
|
|
|
-- clear next temporary questlog entries
|
|
for k, v in pairs(pfQuest.questlog_tmp) do
|
|
pfQuest.questlog_tmp[k] = nil
|
|
end
|
|
|
|
return change
|
|
end
|
|
|
|
function pfQuest:ResetAll()
|
|
-- force reload all quests
|
|
pfMap:DeleteNode("PFQUEST")
|
|
pfQuest.questlog = {}
|
|
pfQuest.updateQuestLog = true
|
|
pfQuest.updateQuestGivers = true
|
|
-- pfMap.nodes["PFQUEST"] is now empty; tell SearchQuests to start fresh
|
|
if pfDatabase then
|
|
for id in pairs(pfDatabase.lastQuestGiversSet) do
|
|
pfDatabase.lastQuestGiversSet[id] = nil
|
|
end
|
|
end
|
|
end
|
|
|
|
-- register popup dialog to copy urls
|
|
StaticPopupDialogs["PFQUEST_URLCOPY"] = {
|
|
text = "|cff33ffccpf|cffffffffQuest " .. pfQuest_Loc["Online Search"],
|
|
button1 = "Close",
|
|
hasEditBox = 1,
|
|
hasWideEditBox = 1,
|
|
timeout = 0,
|
|
exclusive = 1,
|
|
whileDead = 1,
|
|
hideOnEscape = 1,
|
|
OnShow = function()
|
|
local editBox = _G[this:GetName() .. "WideEditBox"]
|
|
editBox:SetText(StaticPopupDialogs["PFQUEST_URLCOPY"].data)
|
|
editBox:HighlightText()
|
|
end,
|
|
OnHide = function()
|
|
_G[this:GetName() .. "WideEditBox"]:SetText("")
|
|
end,
|
|
EditBoxOnEnterPressed = function()
|
|
this:GetParent():Hide()
|
|
end,
|
|
EditBoxOnEscapePressed = function()
|
|
this:GetParent():Hide()
|
|
end,
|
|
EditBoxOnTextChanged = function()
|
|
this:SetText(StaticPopupDialogs["PFQUEST_URLCOPY"].data)
|
|
this:HighlightText()
|
|
end,
|
|
}
|
|
|
|
function pfQuest:AddQuestLogIntegration()
|
|
if pfQuest_config["questlogbuttons"] == "0" then
|
|
return
|
|
end
|
|
|
|
local dockFrame = EQL3_QuestLogDetailScrollChildFrame
|
|
or ShaguQuest_QuestLogDetailScrollChildFrame
|
|
or QuestLogDetailScrollChildFrame
|
|
local dockTitle = EQL3_QuestLogDescriptionTitle
|
|
or ShaguQuest_QuestLogDescriptionTitle
|
|
or pfQuestCompat.QuestLogDescriptionTitle
|
|
|
|
dockTitle:SetHeight(dockTitle:GetHeight() + 30)
|
|
dockTitle:SetJustifyV("BOTTOM")
|
|
|
|
pfQuest.buttonOnline = pfQuest.buttonOnline or CreateFrame("Button", "pfQuestOnline", dockFrame)
|
|
pfQuest.buttonOnline:SetParent(UIParent)
|
|
pfQuest.buttonOnline:SetFrameStrata("DIALOG")
|
|
pfQuest.buttonOnline:SetFrameLevel(100)
|
|
pfQuest.buttonOnline:SetWidth(18)
|
|
pfQuest.buttonOnline:SetHeight(15)
|
|
pfQuest.buttonOnline:ClearAllPoints()
|
|
pfQuest.buttonOnline:SetPoint("RIGHT", QuestLogQuestCount, "LEFT", -30, 0)
|
|
pfQuest.buttonOnline:SetScript("OnClick", function()
|
|
if pfUI and pfUI.chat then
|
|
pfUI.chat.urlcopy.text:SetText(pfQuest:GetDatabaseURL() .. (this:GetID() or 0))
|
|
pfUI.chat.urlcopy:Show()
|
|
else
|
|
StaticPopupDialogs["PFQUEST_URLCOPY"].data = pfQuest:GetDatabaseURL() .. (this:GetID() or 0)
|
|
local dialog = StaticPopup_Show("PFQUEST_URLCOPY")
|
|
_G[dialog:GetName() .. "Button1"]:ClearAllPoints()
|
|
_G[dialog:GetName() .. "Button1"]:SetPoint("BOTTOM", dialog, "BOTTOM", 0, 16)
|
|
_G[dialog:GetName() .. "WideEditBox"]:SetScript("OnTextChanged", StaticPopup_EditBoxOnTextChanged)
|
|
dialog:SetWidth(420)
|
|
end
|
|
end)
|
|
|
|
pfQuest.buttonOnline.txt = pfQuest.buttonOnline:CreateFontString("pfQuestIDButton", "HIGH", "GameFontWhite")
|
|
pfQuest.buttonOnline.txt:SetAllPoints(pfQuest.buttonOnline)
|
|
pfQuest.buttonOnline.txt:SetJustifyH("RIGHT")
|
|
pfQuest.buttonOnline.txt:SetText("|cff000000[|cffaa2222?|cff000000]")
|
|
|
|
pfQuest.buttonLanguage = pfQuest.buttonLanguage or CreateFrame("Button", "pfQuestLanguage", dockFrame)
|
|
pfQuest.buttonLanguage:SetParent(UIParent)
|
|
pfQuest.buttonLanguage:SetFrameStrata("DIALOG")
|
|
pfQuest.buttonLanguage:SetFrameLevel(100)
|
|
pfQuest.buttonLanguage:SetWidth(75)
|
|
pfQuest.buttonLanguage:SetHeight(15)
|
|
pfQuest.buttonLanguage:ClearAllPoints()
|
|
pfQuest.buttonLanguage:SetPoint("RIGHT", pfQuest.buttonOnline, "LEFT", 0, 0)
|
|
|
|
pfQuest.buttonLanguage.txt = pfQuest.buttonLanguage:CreateFontString("pfQuestIDButton", "HIGH", "GameFontWhite")
|
|
pfQuest.buttonLanguage.txt:SetAllPoints(pfQuest.buttonLanguage)
|
|
pfQuest.buttonLanguage.txt:SetJustifyH("RIGHT")
|
|
pfQuest.buttonLanguage.txt:SetText("|cff000000[|cff3333ff" .. pfQuest_Loc["Translate"] .. "|cff000000]")
|
|
|
|
-- ARIALN.TTF is bundled with the 1.12 client and includes Cyrillic glyphs.
|
|
-- Keep the replacement scoped to the quest-log text that pfQuest translates.
|
|
local translationFontStrings = {
|
|
EQL3_QuestLogQuestTitle or pfQuestCompat.QuestLogQuestTitle,
|
|
EQL3_QuestLogObjectivesText or pfQuestCompat.QuestLogObjectivesText,
|
|
EQL3_QuestLogQuestDescription or pfQuestCompat.QuestLogQuestDescription,
|
|
}
|
|
local translationFonts = {}
|
|
for index, fontString in ipairs(translationFontStrings) do
|
|
local font, size, flags = fontString:GetFont()
|
|
translationFonts[index] = { font = font, size = size, flags = flags }
|
|
end
|
|
|
|
local function SetQuestTranslationFont(language)
|
|
if pfQuest.translationFontLanguage == language then return end
|
|
|
|
for index, fontString in ipairs(translationFontStrings) do
|
|
local original = translationFonts[index]
|
|
if language == "ruRU" then
|
|
fontString:SetFont("Fonts\\ARIALN.TTF", original.size, original.flags)
|
|
else
|
|
fontString:SetFont(original.font, original.size, original.flags)
|
|
end
|
|
end
|
|
|
|
pfQuest.translationFontLanguage = language
|
|
end
|
|
|
|
pfQuest.buttonLanguage:SetScript("OnClick", function()
|
|
UIDropDownMenu_Initialize(self, function()
|
|
local func = function()
|
|
pfQuest_config.translate = this.value
|
|
end
|
|
local info = {}
|
|
info.text = "|cffaaaaaa" .. pfQuest_Loc["Reset Language"]
|
|
info.value = nil
|
|
info.func = func
|
|
UIDropDownMenu_AddButton(info)
|
|
|
|
for loc, caption in pairs(pfDB.locales) do
|
|
local info = {}
|
|
info.text = caption
|
|
info.value = loc
|
|
info.func = func
|
|
UIDropDownMenu_AddButton(info)
|
|
end
|
|
end)
|
|
ToggleDropDownMenu(1, nil, self, "cursor", 3, -3)
|
|
end)
|
|
|
|
pfQuest.buttonLanguage:SetScript("OnUpdate", function()
|
|
-- The controls live on UIParent so they can sit in pfUI's header. Never
|
|
-- leave them behind if another addon closes the Quest Log without hiding
|
|
-- its frame through the normal path.
|
|
if not QuestLogFrame:IsShown() then
|
|
pfQuest.buttonOnline:Hide()
|
|
pfQuest.buttonLanguage:Hide()
|
|
return
|
|
end
|
|
|
|
local id = pfQuest.buttonOnline:GetID()
|
|
local lang = pfQuest_config.translate
|
|
|
|
if this.translate ~= pfQuest_config.translate then
|
|
pfQuest.buttonLanguage.txt:SetText(
|
|
"|cff000000[|cff3333ff"
|
|
.. (pfDB.locales[pfQuest_config.translate] or "|cff3333ff" .. pfQuest_Loc["Translate"])
|
|
.. "|cff000000]"
|
|
)
|
|
this.translate = pfQuest_config.translate
|
|
SetQuestTranslationFont(pfQuest_config.translate)
|
|
QuestLog_UpdateQuestDetails(true)
|
|
return
|
|
end
|
|
|
|
if id and pfDB["quests"][lang] and pfDB["quests"][lang][id] then
|
|
local QuestLogQuestTitle = EQL3_QuestLogQuestTitle or pfQuestCompat.QuestLogQuestTitle
|
|
local QuestLogObjectivesText = EQL3_QuestLogObjectivesText or pfQuestCompat.QuestLogObjectivesText
|
|
local QuestLogQuestDescription = EQL3_QuestLogQuestDescription or pfQuestCompat.QuestLogQuestDescription
|
|
local QuestLogDetailScrollFrame = EQL3_QuestLogDetailScrollFrame or QuestLogDetailScrollFrame
|
|
|
|
QuestLogQuestTitle:SetText(pfDatabase:FormatQuestText(pfDB["quests"][lang][id]["T"]))
|
|
QuestLogObjectivesText:SetText(pfDatabase:FormatQuestText(pfDB["quests"][lang][id]["O"]))
|
|
QuestLogQuestDescription:SetText(pfDatabase:FormatQuestText(pfDB["quests"][lang][id]["D"]))
|
|
QuestLogDetailScrollFrame:UpdateScrollChildRect()
|
|
end
|
|
end)
|
|
|
|
-- pfUI finishes its Quest Log layout during OnShow. Refresh afterwards so
|
|
-- the language and database-link controls are visible on the first open.
|
|
if not pfQuest.questLogOnShowHook then
|
|
local function HookFrameScript(frame, script, handler)
|
|
-- Some Turtle clients expose HookScript as a non-callable frame field.
|
|
-- Preserve the existing script and append ours directly instead.
|
|
local previous = frame:GetScript(script)
|
|
frame:SetScript(script, function()
|
|
if previous then previous() end
|
|
handler()
|
|
end)
|
|
end
|
|
|
|
HookFrameScript(QuestLogFrame, "OnShow", function()
|
|
pfQuest.questLogOpenRefreshAt = GetTime() + 0.15
|
|
end)
|
|
HookFrameScript(QuestLogFrame, "OnHide", function()
|
|
pfQuest.buttonOnline:Hide()
|
|
pfQuest.buttonLanguage:Hide()
|
|
end)
|
|
HookFrameScript(QuestLogFrameCloseButton, "OnClick", function()
|
|
pfQuest.buttonOnline:Hide()
|
|
pfQuest.buttonLanguage:Hide()
|
|
end)
|
|
pfQuest.questLogOnShowHook = true
|
|
end
|
|
|
|
pfQuest.buttonShow = pfQuest.buttonShow or CreateFrame("Button", "pfQuestShow", dockFrame, "UIPanelButtonTemplate")
|
|
pfQuest.buttonShow:SetWidth(70)
|
|
pfQuest.buttonShow:SetHeight(20)
|
|
pfQuest.buttonShow:SetText(pfQuest_Loc["Show"])
|
|
pfQuest.buttonShow:SetPoint("TOP", dockTitle, "TOP", -110, 0)
|
|
pfQuest.buttonShow:SetScript("OnClick", function()
|
|
local questIndex = GetQuestLogSelection()
|
|
local questids = pfDatabase:GetQuestIDs(questIndex)
|
|
local title, _, _, header, _, complete = compat.GetQuestLogTitle(questIndex)
|
|
local id = questids and tonumber(questids[1])
|
|
if header or not id then
|
|
return
|
|
end
|
|
|
|
local maps, meta = {}, { ["addon"] = "PFQUEST", ["qlogid"] = questIndex }
|
|
maps = pfDatabase:SearchQuestID(id, meta, maps)
|
|
pfMap:ShowMapID(GetQuestHubMap(id) or pfDatabase:GetBestMap(maps))
|
|
end)
|
|
|
|
pfQuest.buttonHide = pfQuest.buttonHide or CreateFrame("Button", "pfQuestHide", dockFrame, "UIPanelButtonTemplate")
|
|
pfQuest.buttonHide:SetWidth(70)
|
|
pfQuest.buttonHide:SetHeight(20)
|
|
pfQuest.buttonHide:SetText(pfQuest_Loc["Hide"])
|
|
pfQuest.buttonHide:SetPoint("TOP", dockTitle, "TOP", -37, 0)
|
|
pfQuest.buttonHide:SetScript("OnClick", function()
|
|
local questIndex = GetQuestLogSelection()
|
|
local title, _, _, header, _, complete = compat.GetQuestLogTitle(questIndex)
|
|
if header then
|
|
return
|
|
end
|
|
|
|
pfMap:DeleteNode("PFQUEST", title)
|
|
end)
|
|
|
|
pfQuest.buttonClean = pfQuest.buttonClean or CreateFrame("Button", "pfQuestClean", dockFrame, "UIPanelButtonTemplate")
|
|
pfQuest.buttonClean:SetWidth(70)
|
|
pfQuest.buttonClean:SetHeight(20)
|
|
pfQuest.buttonClean:SetText(pfQuest_Loc["Clean"])
|
|
pfQuest.buttonClean:SetPoint("TOP", dockTitle, "TOP", 37, 0)
|
|
pfQuest.buttonClean:SetScript("OnClick", function()
|
|
pfMap:DeleteNode("PFQUEST")
|
|
-- A manually selected map pin owns the route arrow until its explicit
|
|
-- target is cleared. Cleaning the pins must also release that target and
|
|
-- discard the cached route to the now-hidden quest marker.
|
|
if pfQuest.route then
|
|
pfQuest.route.SetTarget(nil)
|
|
pfQuest.route:Clear()
|
|
end
|
|
end)
|
|
|
|
pfQuest.buttonReset = pfQuest.buttonReset or CreateFrame("Button", "pfQuestReset", dockFrame, "UIPanelButtonTemplate")
|
|
pfQuest.buttonReset:SetWidth(70)
|
|
pfQuest.buttonReset:SetHeight(20)
|
|
pfQuest.buttonReset:SetText(pfQuest_Loc["Reset"])
|
|
pfQuest.buttonReset:SetPoint("TOP", dockTitle, "TOP", 110, 0)
|
|
pfQuest.buttonReset:SetScript("OnClick", function()
|
|
pfQuest:ResetAll()
|
|
end)
|
|
|
|
-- use pfUI buttons in native mode
|
|
if not pfUI.api.emulated then
|
|
pfUI.api.SkinButton(pfQuest.buttonShow)
|
|
pfUI.api.SkinButton(pfQuest.buttonHide)
|
|
pfUI.api.SkinButton(pfQuest.buttonClean)
|
|
pfUI.api.SkinButton(pfQuest.buttonReset)
|
|
end
|
|
end
|
|
|
|
function pfQuest:AddWorldMapIntegration()
|
|
if pfQuest_config["worldmapmenu"] == "0" then
|
|
return
|
|
end
|
|
|
|
-- Quest Display Selection
|
|
pfQuest.mapButton = CreateFrame("Frame", "pfQuestMapDropdown", WorldMapButton, "UIDropDownMenuTemplate")
|
|
pfQuest.mapButton:ClearAllPoints()
|
|
pfQuest.mapButton:SetPoint("TOPRIGHT", 0, -10)
|
|
pfQuest.mapButton:SetScript("OnShow", function()
|
|
pfQuest.mapButton.current = tonumber(pfQuest_config["trackingmethod"])
|
|
-- Version 8.0.23 briefly exposed a sixth filter while it was being
|
|
-- evaluated. It was removed before release, but that value can remain
|
|
-- in a test character's saved settings. A dropdown with no matching
|
|
-- entry keeps whatever text it last drew (often a zone name), so repair
|
|
-- the setting before building the menu.
|
|
if not pfQuest.mapButton.current or pfQuest.mapButton.current < 1 or pfQuest.mapButton.current > 5 then
|
|
pfQuest.mapButton.current = 1
|
|
pfQuest_config["trackingmethod"] = "1"
|
|
end
|
|
pfQuest.mapButton:UpdateMenu()
|
|
-- This is a custom dropdown, so pfUI's map module never sees it during
|
|
-- its normal Blizzard-control skin pass. Skin it after initialization to
|
|
-- match the Continent and Zone selectors without requiring pfUI on clean
|
|
-- clients.
|
|
if not pfQuest.mapButton.pfUISkinned and pfUI and pfUI.api and pfUI.api.SkinDropDown then
|
|
pfUI.api.SkinDropDown(pfQuest.mapButton, nil, nil, nil, true)
|
|
pfQuest.mapButton.pfUISkinned = true
|
|
end
|
|
end)
|
|
|
|
pfQuest.mapButton.point = "TOPLEFT"
|
|
pfQuest.mapButton.relativePoint = "BOTTOMLEFT"
|
|
|
|
-- Keep the available-quest difficulty filter with the other World Map
|
|
-- controls. It belongs to the map frame rather than the zoomed map canvas,
|
|
-- so Magnify cannot clip it when the map is scaled.
|
|
pfQuest.mapLevelButton = CreateFrame("Frame", "pfQuestMapLevelDropdown", WorldMapFrame, "UIDropDownMenuTemplate")
|
|
pfQuest.mapLevelButton.point = "TOPLEFT"
|
|
pfQuest.mapLevelButton.relativePoint = "BOTTOMLEFT"
|
|
pfQuest.mapLevelButton:SetFrameStrata(pfQuest.mapButton:GetFrameStrata())
|
|
pfQuest.mapLevelButton:SetFrameLevel(pfQuest.mapButton:GetFrameLevel() + 10)
|
|
local mapLevelTemplateButton = pfQuest.mapLevelButton.Button or _G["pfQuestMapLevelDropdownButton"]
|
|
if mapLevelTemplateButton then
|
|
mapLevelTemplateButton:SetFrameLevel(pfQuest.mapLevelButton:GetFrameLevel() + 2)
|
|
end
|
|
|
|
local function PositionMapLevelButton()
|
|
if pfQuest.mapLevelButton.layoutAnchor ~= pfQuest.mapButton then
|
|
pfQuest.mapLevelButton.layoutAnchor = pfQuest.mapButton
|
|
pfQuest.mapLevelButton:ClearAllPoints()
|
|
pfQuest.mapLevelButton:SetPoint("TOPRIGHT", pfQuest.mapButton, "BOTTOMRIGHT", 0, 0)
|
|
end
|
|
|
|
local filterMarkers = _G["ModernMapMarkersFilter_Blizz"]
|
|
if filterMarkers and filterMarkers.pfQuestLayoutAnchor ~= pfQuest.mapLevelButton then
|
|
filterMarkers.pfQuestLayoutAnchor = pfQuest.mapLevelButton
|
|
filterMarkers:ClearAllPoints()
|
|
filterMarkers:SetPoint("TOPRIGHT", pfQuest.mapLevelButton, "BOTTOMRIGHT", 0, 0)
|
|
end
|
|
end
|
|
PositionMapLevelButton()
|
|
|
|
local levelModes = {
|
|
{ value = "red", name = "Red", color = "|cffff2020" },
|
|
{ value = "orange", name = "Orange", color = "|cffff8040" },
|
|
{ value = "yellow", name = "Yellow", color = "|cffffff00" },
|
|
{ value = "green", name = "Green", color = "|cff40c040" },
|
|
{ value = "gray", name = "Grey", color = "|cff999999" },
|
|
}
|
|
|
|
local function GetSelectedLevelRange()
|
|
local selected = pfQuest_config["questpinlevelrange"] or "off"
|
|
if selected == "all" then
|
|
selected = "off"
|
|
pfQuest_config["questpinlevelrange"] = "off"
|
|
end
|
|
return selected
|
|
end
|
|
|
|
local function CreateLevelRangeEntries()
|
|
local selected = GetSelectedLevelRange()
|
|
local direction = pfQuest_config["questpinleveldirection"] == "higher" and "Higher" or "Lower"
|
|
for index, mode in ipairs(levelModes) do
|
|
-- Use a fresh table because the legacy dropdown reuses its row frames.
|
|
local info = {}
|
|
info.text = mode.color .. mode.name .. " & " .. direction .. "|r"
|
|
info.value = mode.value
|
|
info.checked = selected == mode.value
|
|
info.func = function()
|
|
local selectedValue = this and this.value
|
|
if not selectedValue then return end
|
|
local current = GetSelectedLevelRange()
|
|
-- Clicking the active range again disables the feature and restores
|
|
-- normal pfQuest high/low-level filtering.
|
|
pfQuest_config["questpinlevelrange"] = current == selectedValue and "off" or selectedValue
|
|
CloseDropDownMenus()
|
|
pfQuest.mapLevelButton:UpdateMenu()
|
|
pfQuest:ResetAll()
|
|
end
|
|
UIDropDownMenu_AddButton(info)
|
|
end
|
|
end
|
|
|
|
-- Register the initializer once. Reinitializing a legacy dropdown while
|
|
-- another window owns the shared dropdown rows can replace that window's
|
|
-- labels with Level Range entries (for example, profession filters).
|
|
UIDropDownMenu_Initialize(pfQuest.mapLevelButton, CreateLevelRangeEntries)
|
|
|
|
function pfQuest.mapLevelButton:UpdateMenu()
|
|
local selected = GetSelectedLevelRange()
|
|
local selectedID
|
|
for index, mode in ipairs(levelModes) do
|
|
if selected == mode.value then selectedID = index end
|
|
end
|
|
|
|
pfQuest.mapLevelButton.current = selectedID
|
|
if client >= 30300 then
|
|
UIDropDownMenu_SetWidth(pfQuest.mapLevelButton, 120)
|
|
UIDropDownMenu_SetButtonWidth(pfQuest.mapLevelButton, 125)
|
|
UIDropDownMenu_JustifyText(pfQuest.mapLevelButton, "RIGHT")
|
|
else
|
|
UIDropDownMenu_SetWidth(120, pfQuest.mapLevelButton)
|
|
UIDropDownMenu_SetButtonWidth(125, pfQuest.mapLevelButton)
|
|
UIDropDownMenu_JustifyText("RIGHT", pfQuest.mapLevelButton)
|
|
end
|
|
pfQuest.mapLevelButton.currentLabel = "Level Range"
|
|
local direction = pfQuest_config["questpinleveldirection"] == "higher" and "higher" or "lower"
|
|
if pfQuest.mapLevelButton.directionArrow then
|
|
pfQuest.mapLevelButton.directionArrow:SetText(direction == "higher" and "|cffffcc00^|r" or "|cffffcc00v|r")
|
|
end
|
|
-- Moving this control out of the map canvas prevents the legacy template
|
|
-- from repainting its selected caption automatically.
|
|
if client >= 30300 then
|
|
UIDropDownMenu_SetText(pfQuest.mapLevelButton, "Level Range")
|
|
else
|
|
UIDropDownMenu_SetText("Level Range", pfQuest.mapLevelButton)
|
|
end
|
|
-- The stock template anchors its text to the map canvas. This dropdown is
|
|
-- intentionally in WorldMapFrame, so anchor the existing text child to
|
|
-- its own control instead of creating a separate display frame.
|
|
local text = pfQuest.mapLevelButton.Text or _G["pfQuestMapLevelDropdownText"]
|
|
local mapText = pfQuest.mapButton.Text or _G["pfQuestMapDropdownText"]
|
|
if text then
|
|
text:ClearAllPoints()
|
|
text:SetPoint("RIGHT", pfQuest.mapLevelButton, "RIGHT", -42, 0)
|
|
text:SetWidth(110)
|
|
text:SetJustifyH("RIGHT")
|
|
text:SetDrawLayer("OVERLAY")
|
|
text:SetAlpha(1)
|
|
text:SetText("Level Range")
|
|
text:Show()
|
|
if mapText and mapText.GetFont then
|
|
local font, size, flags = mapText:GetFont()
|
|
if font then text:SetFont(font, size, flags) end
|
|
local r, g, b, a = mapText:GetTextColor()
|
|
text:SetTextColor(r, g, b, a)
|
|
end
|
|
text:Show()
|
|
end
|
|
|
|
end
|
|
|
|
local function ToggleLevelRangeDirection()
|
|
local direction = pfQuest_config["questpinleveldirection"] == "higher" and "higher" or "lower"
|
|
pfQuest_config["questpinleveldirection"] = direction == "higher" and "lower" or "higher"
|
|
CloseDropDownMenus()
|
|
pfQuest.mapLevelButton:UpdateMenu()
|
|
if GetSelectedLevelRange() ~= "off" then pfQuest:ResetAll() end
|
|
end
|
|
|
|
local function ShowLevelRangeHelp(owner)
|
|
GameTooltip:SetOwner(owner, "ANCHOR_LEFT")
|
|
GameTooltip:SetText("Level Range")
|
|
GameTooltip:AddLine("Right-click: Reverse direction", 1, 1, 1)
|
|
GameTooltip:Show()
|
|
end
|
|
|
|
-- Keep the indicator above pfUI's dropdown backdrop and make the left-side
|
|
-- marker itself usable for reversing the filter.
|
|
pfQuest.mapLevelButton.directionIndicator = CreateFrame("Button", nil, pfQuest.mapLevelButton)
|
|
pfQuest.mapLevelButton.directionIndicator:SetWidth(18)
|
|
pfQuest.mapLevelButton.directionIndicator:SetHeight(18)
|
|
pfQuest.mapLevelButton.directionIndicator:SetPoint("LEFT", pfQuest.mapLevelButton, "LEFT", 18, 1)
|
|
pfQuest.mapLevelButton.directionIndicator:SetFrameStrata(pfQuest.mapLevelButton:GetFrameStrata())
|
|
pfQuest.mapLevelButton.directionIndicator:SetFrameLevel(pfQuest.mapLevelButton:GetFrameLevel() + 20)
|
|
pfQuest.mapLevelButton.directionIndicator:RegisterForClicks("RightButtonUp")
|
|
pfQuest.mapLevelButton.directionIndicator:SetScript("OnClick", ToggleLevelRangeDirection)
|
|
pfQuest.mapLevelButton.directionIndicator:SetScript("OnEnter", function() ShowLevelRangeHelp(this) end)
|
|
pfQuest.mapLevelButton.directionIndicator:SetScript("OnLeave", function() GameTooltip:Hide() end)
|
|
pfQuest.mapLevelButton.directionArrow = pfQuest.mapLevelButton.directionIndicator:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
|
|
pfQuest.mapLevelButton.directionArrow:SetAllPoints(pfQuest.mapLevelButton.directionIndicator)
|
|
pfQuest.mapLevelButton.directionArrow:SetJustifyH("CENTER")
|
|
|
|
local function ApplyMapLevelButtonSkin()
|
|
if not (pfUI and pfUI.api and pfUI.api.SkinDropDown) then return end
|
|
if not pfQuest.mapLevelButton.pfUISkinned then
|
|
pfUI.api.SkinDropDown(pfQuest.mapLevelButton, nil, nil, nil, true)
|
|
pfQuest.mapLevelButton.pfUISkinned = true
|
|
end
|
|
end
|
|
|
|
local function SyncMapLevelScale()
|
|
local parentScale = WorldMapFrame:GetEffectiveScale()
|
|
local sourceScale = pfQuest.mapButton:GetEffectiveScale()
|
|
if parentScale > 0 and sourceScale > 0 then
|
|
pfQuest.mapLevelButton:SetScale(sourceScale / parentScale)
|
|
end
|
|
end
|
|
|
|
pfQuest.mapLevelButton:SetScript("OnShow", function()
|
|
PositionMapLevelButton()
|
|
ApplyMapLevelButtonSkin()
|
|
pfQuest.mapLevelButton:UpdateMenu()
|
|
SyncMapLevelScale()
|
|
end)
|
|
|
|
-- The legacy dropdown reuses its list rows. Refresh their boolean checked
|
|
-- state immediately before each open, rather than during a selection.
|
|
local levelToggle = pfQuest.mapLevelButton.Button or _G["pfQuestMapLevelDropdownButton"]
|
|
if levelToggle and not levelToggle.pfQuestLevelMenuHook then
|
|
local previous = levelToggle:GetScript("OnClick")
|
|
local previousEnter = levelToggle:GetScript("OnEnter")
|
|
local previousLeave = levelToggle:GetScript("OnLeave")
|
|
levelToggle:RegisterForClicks("LeftButtonUp", "RightButtonUp")
|
|
levelToggle:SetScript("OnClick", function()
|
|
if arg1 == "RightButton" then
|
|
ToggleLevelRangeDirection()
|
|
return
|
|
end
|
|
pfQuest.mapLevelButton:UpdateMenu()
|
|
if previous then previous() end
|
|
end)
|
|
levelToggle:SetScript("OnEnter", function()
|
|
if previousEnter then previousEnter() end
|
|
ShowLevelRangeHelp(this)
|
|
end)
|
|
levelToggle:SetScript("OnLeave", function()
|
|
if previousLeave then previousLeave() end
|
|
GameTooltip:Hide()
|
|
end)
|
|
levelToggle.pfQuestLevelMenuHook = true
|
|
end
|
|
|
|
pfQuest.mapLevelButton.lastMapScale = nil
|
|
pfQuest.mapLevelButton:SetScript("OnUpdate", function()
|
|
PositionMapLevelButton()
|
|
local scale = pfQuest.mapButton:GetEffectiveScale()
|
|
if scale ~= pfQuest.mapLevelButton.lastMapScale then
|
|
pfQuest.mapLevelButton.lastMapScale = scale
|
|
SyncMapLevelScale()
|
|
end
|
|
end)
|
|
|
|
function pfQuest.mapButton:UpdateMenu()
|
|
local function CreateEntries()
|
|
local info = {}
|
|
info.text = pfQuest_Loc["All Quests"]
|
|
info.checked = false
|
|
info.func = function()
|
|
UIDropDownMenu_SetSelectedID(pfQuest.mapButton, this:GetID(), 0)
|
|
pfQuest_config["trackingmethod"] = this:GetID()
|
|
pfQuest:ResetAll()
|
|
end
|
|
UIDropDownMenu_AddButton(info)
|
|
|
|
local info = {}
|
|
info.text = pfQuest_Loc["Tracked Quests"]
|
|
info.checked = false
|
|
info.func = function()
|
|
UIDropDownMenu_SetSelectedID(pfQuest.mapButton, this:GetID(), 0)
|
|
pfQuest_config["trackingmethod"] = this:GetID()
|
|
pfQuest:ResetAll()
|
|
end
|
|
UIDropDownMenu_AddButton(info)
|
|
|
|
local info = {}
|
|
info.text = pfQuest_Loc["Manual Selection"]
|
|
info.checked = false
|
|
info.func = function()
|
|
UIDropDownMenu_SetSelectedID(pfQuest.mapButton, this:GetID(), 0)
|
|
pfQuest_config["trackingmethod"] = this:GetID()
|
|
pfQuest:ResetAll()
|
|
end
|
|
UIDropDownMenu_AddButton(info)
|
|
|
|
local info = {}
|
|
info.text = pfQuest_Loc["Hide Quests"]
|
|
info.checked = false
|
|
info.func = function()
|
|
UIDropDownMenu_SetSelectedID(pfQuest.mapButton, this:GetID(), 0)
|
|
pfQuest_config["trackingmethod"] = this:GetID()
|
|
pfQuest:ResetAll()
|
|
end
|
|
UIDropDownMenu_AddButton(info)
|
|
|
|
local info = {}
|
|
info.text = pfQuest_Loc["Current Zone Only"] or "Current Zone Only"
|
|
info.checked = false
|
|
info.func = function()
|
|
UIDropDownMenu_SetSelectedID(pfQuest.mapButton, this:GetID(), 0)
|
|
pfQuest_config["trackingmethod"] = this:GetID()
|
|
pfQuest:ResetAll()
|
|
end
|
|
UIDropDownMenu_AddButton(info)
|
|
end
|
|
|
|
UIDropDownMenu_Initialize(pfQuest.mapButton, CreateEntries)
|
|
if client >= 30300 then
|
|
UIDropDownMenu_SetWidth(pfQuest.mapButton, 120)
|
|
UIDropDownMenu_SetButtonWidth(pfQuest.mapButton, 125)
|
|
UIDropDownMenu_JustifyText(pfQuest.mapButton, "RIGHT")
|
|
else
|
|
UIDropDownMenu_SetWidth(120, pfQuest.mapButton)
|
|
UIDropDownMenu_SetButtonWidth(125, pfQuest.mapButton)
|
|
UIDropDownMenu_JustifyText("RIGHT", pfQuest.mapButton)
|
|
end
|
|
UIDropDownMenu_SetSelectedID(pfQuest.mapButton, pfQuest.mapButton.current)
|
|
end
|
|
end
|
|
|
|
-- [[ Hook UI Functions ]] --
|
|
-- Set certain events on quest watch
|
|
local pfHookRemoveQuestWatch = RemoveQuestWatch
|
|
RemoveQuestWatch = function(questIndex)
|
|
local ret = pfHookRemoveQuestWatch(questIndex)
|
|
|
|
if questIndex then
|
|
local title, _, _, header, _, complete = compat.GetQuestLogTitle(questIndex)
|
|
pfMap:DeleteNode("PFQUEST", title)
|
|
end
|
|
|
|
pfQuest.updateQuestLog = true
|
|
pfQuest.updateQuestGivers = true
|
|
|
|
return ret
|
|
end
|
|
|
|
-- Set certain events on quest unwatch
|
|
local pfHookAddQuestWatch = AddQuestWatch
|
|
AddQuestWatch = function(questIndex)
|
|
local ret = pfHookAddQuestWatch(questIndex)
|
|
pfQuest.updateQuestLog = true
|
|
pfQuest.updateQuestGivers = true
|
|
return ret
|
|
end
|
|
|
|
-- Save the abandoned questname to remove from history
|
|
local HookAbandonQuest = AbandonQuest
|
|
AbandonQuest = function()
|
|
pfQuest.abandon = GetAbandonQuestName()
|
|
HookAbandonQuest()
|
|
end
|
|
|
|
local function UpdateQuestLevel(button, id)
|
|
local title, level, tag, header = compat.GetQuestLogTitle(id)
|
|
if header or not title then
|
|
return
|
|
end
|
|
button:SetText(" [" .. (level or "??") .. (tag and "+" or "") .. "] " .. title)
|
|
if not QuestLogTitleButton_Resize then
|
|
return
|
|
end
|
|
QuestLogTitleButton_Resize(button)
|
|
end
|
|
|
|
-- Update quest id button
|
|
local pfHookQuestLog_Update = QuestLog_Update
|
|
QuestLog_Update = function()
|
|
pfHookQuestLog_Update()
|
|
|
|
if pfQuest_config["questloglevel"] == "1" then
|
|
if client >= 30300 then
|
|
for i, button in pairs(QuestLogScrollFrame.buttons) do
|
|
UpdateQuestLevel(button, button:GetID())
|
|
end
|
|
else
|
|
for i = 1, QUESTS_DISPLAYED, 1 do
|
|
UpdateQuestLevel(_G["QuestLogTitle" .. i], i + FauxScrollFrame_GetOffset(QuestLogListScrollFrame))
|
|
end
|
|
end
|
|
end
|
|
|
|
if pfQuest_config["questlogbuttons"] == "1" then
|
|
-- UI refreshes must only read the mapping maintained by UpdateQuestlog.
|
|
-- Native link lookup/row selection here can trigger more quest-log events.
|
|
local selectedQlogID = GetQuestLogSelection()
|
|
local selectedTitle = compat.GetQuestLogTitle(selectedQlogID)
|
|
local questids
|
|
for id, state in pairs(pfQuest.questlog or {}) do
|
|
if type(id) == "number" and state.qlogid == selectedQlogID
|
|
and state.title == selectedTitle then
|
|
questids = { id }
|
|
break
|
|
end
|
|
end
|
|
if questids and questids[1] and tonumber(questids[1]) and pfQuest.questlog[questids[1]] then
|
|
pfQuest.buttonOnline:SetID(questids[1])
|
|
pfQuest.buttonOnline:Show()
|
|
pfQuest.buttonLanguage:Show()
|
|
-- enable buttons
|
|
if pfQuest.buttonShow then pfQuest.buttonShow:Enable() end
|
|
if pfQuest.buttonHide then pfQuest.buttonHide:Enable() end
|
|
|
|
if pfQuest_config.showids == "1" then
|
|
pfQuest.buttonOnline.txt:SetText("|cff000000[|cffaa2222id: " .. questids[1] .. "|cff000000]")
|
|
pfQuest.buttonOnline:SetWidth(pfQuest.buttonOnline.txt:GetStringWidth())
|
|
end
|
|
else
|
|
pfQuest.buttonOnline:Hide()
|
|
pfQuest.buttonLanguage:Hide()
|
|
-- disable buttons
|
|
if pfQuest.buttonShow then pfQuest.buttonShow:Disable() end
|
|
if pfQuest.buttonHide then pfQuest.buttonHide:Disable() end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- attach the new function to the scroll frame
|
|
if QuestLogScrollFrame then
|
|
QuestLogScrollFrame.update = QuestLog_Update
|
|
end
|
|
|
|
-- refresh language and url on quest selection
|
|
local pfHookQuestLogTitleButton_OnClick = QuestLogTitleButton_OnClick
|
|
QuestLogTitleButton_OnClick = function(self, button)
|
|
pfHookQuestLogTitleButton_OnClick(self, button)
|
|
QuestLog_Update()
|
|
end
|
|
|
|
if not GetQuestLink then -- Allow to send questlinks from questlog
|
|
local pfHookQuestLogTitleButton_OnClick = QuestLogTitleButton_OnClick
|
|
QuestLogTitleButton_OnClick = function(button)
|
|
if pfQuest_config.questlinks == "0" then return pfHookQuestLogTitleButton_OnClick(button) end
|
|
local scrollFrame = EQL3_QuestLogListScrollFrame or ShaguQuest_QuestLogListScrollFrame or QuestLogListScrollFrame
|
|
local questIndex = this:GetID() + FauxScrollFrame_GetOffset(scrollFrame)
|
|
local questName, questLevel = compat.GetQuestLogTitle(questIndex)
|
|
local questids = pfDatabase:GetQuestIDs(questIndex)
|
|
local questid = questids and tonumber(questids[1]) or 0
|
|
|
|
if IsShiftKeyDown() and not this.isHeader and ChatFrameEditBox:IsVisible() then
|
|
pfQuestCompat.InsertQuestLink(questid, questName)
|
|
QuestLog_SetSelection(questIndex)
|
|
QuestLog_Update()
|
|
return
|
|
end
|
|
|
|
pfHookQuestLogTitleButton_OnClick(button)
|
|
end
|
|
|
|
-- Patch ItemRef to display Questlinks
|
|
local pfQuestHookSetItemRef = SetItemRef
|
|
SetItemRef = function(link, text, button)
|
|
if pfQuest_config.questlinks == "0" then return pfQuestHookSetItemRef(link, text, button) end
|
|
local isQuest, _, id = string.find(link, "quest:(%d+):.*")
|
|
local isQuest2, _, _ = string.find(link, "quest2:.*")
|
|
|
|
if isQuest or isQuest2 then
|
|
if IsShiftKeyDown() and ChatFrameEditBox:IsVisible() then
|
|
ChatFrameEditBox:Insert(text)
|
|
return
|
|
end
|
|
|
|
if ItemRefTooltip:IsShown() and ItemRefTooltip.pfQtext == text then
|
|
HideUIPanel(ItemRefTooltip)
|
|
return
|
|
end
|
|
|
|
ShowUIPanel(ItemRefTooltip)
|
|
ItemRefTooltip:SetOwner(UIParent, "ANCHOR_PRESERVE")
|
|
|
|
local hasTitle, _, questTitle = string.find(text, ".*|h%[(.*)%]|h.*")
|
|
|
|
id = tonumber(id)
|
|
|
|
if not id or id == 0 then
|
|
for scanID, data in pairs(pfDB["quests"]["loc"]) do
|
|
if data.T == questTitle then
|
|
id = scanID
|
|
break
|
|
end
|
|
end
|
|
end
|
|
|
|
-- read and set title
|
|
if id and id > 0 and pfDB["quests"]["loc"][id] then
|
|
local questlevel = tonumber(pfDB["quests"]["data"][id]["lvl"])
|
|
local color = pfQuestCompat.GetDifficultyColor(questlevel)
|
|
ItemRefTooltip:AddLine(pfDB["quests"]["loc"][id].T, color.r, color.g, color.b)
|
|
elseif hasTitle then
|
|
ItemRefTooltip:AddLine(questTitle, 1, 1, 0)
|
|
end
|
|
|
|
-- scan for active quests
|
|
local queststate = pfQuest_history[id] and 2 or 0
|
|
queststate = pfQuest.questlog[id] and 1 or queststate
|
|
|
|
if queststate == 0 then
|
|
ItemRefTooltip:AddLine(pfQuest_Loc["You don't have this quest."] .. "\n\n", 1, 0.5, 0.5)
|
|
elseif queststate == 1 then
|
|
ItemRefTooltip:AddLine(pfQuest_Loc["You are on this quest."] .. "\n\n", 1, 1, 0.5)
|
|
elseif queststate == 2 then
|
|
ItemRefTooltip:AddLine(pfQuest_Loc["You already did this quest."] .. "\n\n", 0.5, 1, 0.5)
|
|
end
|
|
|
|
-- add database entries if existing
|
|
if pfDB["quests"]["loc"][id] then
|
|
if pfDB["quests"]["loc"][id]["O"] then
|
|
ItemRefTooltip:AddLine(pfDatabase:FormatQuestText(pfDB["quests"]["loc"][id]["O"]), 1, 1, 1, true)
|
|
end
|
|
|
|
if pfDB["quests"]["loc"][id]["O"] and pfDB["quests"]["loc"][id]["D"] then
|
|
ItemRefTooltip:AddLine(" ", 0, 0, 0)
|
|
end
|
|
|
|
if pfDB["quests"]["loc"][id]["D"] then
|
|
ItemRefTooltip:AddLine(pfDatabase:FormatQuestText(pfDB["quests"]["loc"][id]["D"]), 0.8, 0.8, 0.8, true)
|
|
end
|
|
|
|
if pfDB["quests"]["data"][id]["lvl"] or pfDB["quests"]["data"][id]["min"] then
|
|
ItemRefTooltip:AddLine(" ", 0, 0, 0)
|
|
end
|
|
|
|
if pfDB["quests"]["data"][id]["min"] then
|
|
local questlevel = tonumber(pfDB["quests"]["data"][id]["min"])
|
|
local color = pfQuestCompat.GetDifficultyColor(questlevel)
|
|
ItemRefTooltip:AddLine(
|
|
"|cffffffff" .. pfQuest_Loc["Required Level"] .. ": |r" .. questlevel,
|
|
color.r,
|
|
color.g,
|
|
color.b
|
|
)
|
|
end
|
|
|
|
if pfDB["quests"]["data"][id]["lvl"] then
|
|
local questlevel = tonumber(pfDB["quests"]["data"][id]["lvl"])
|
|
local color = pfQuestCompat.GetDifficultyColor(questlevel)
|
|
ItemRefTooltip:AddLine(
|
|
"|cffffffff" .. pfQuest_Loc["Quest Level"] .. ": |r" .. questlevel,
|
|
color.r,
|
|
color.g,
|
|
color.b
|
|
)
|
|
end
|
|
end
|
|
|
|
ItemRefTooltip:Show()
|
|
else
|
|
pfQuestHookSetItemRef(link, text, button)
|
|
end
|
|
ItemRefTooltip.pfQtext = text
|
|
end
|
|
else
|
|
-- patch itemref to show known quest levels on tbc
|
|
local pfQuestHookSetItemRef = SetItemRef
|
|
SetItemRef = function(link, text, button)
|
|
pfQuestHookSetItemRef(link, text, button)
|
|
if pfQuest_config.questlinks == "0" then return end
|
|
|
|
-- skip modifier clicks
|
|
if IsAltKeyDown() or IsControlKeyDown() or IsShiftKeyDown() then
|
|
return
|
|
end
|
|
|
|
local quest, _, id = string.find(link, "quest:(%d+):.*")
|
|
if not quest then
|
|
return
|
|
end
|
|
id = tonumber(id)
|
|
|
|
-- adjust text color to level color
|
|
if id and id > 0 and pfDB["quests"]["loc"][id] then
|
|
local questlevel = tonumber(pfDB["quests"]["data"][id]["lvl"])
|
|
local color = pfQuestCompat.GetDifficultyColor(questlevel)
|
|
ItemRefTooltipTextLeft1:SetTextColor(color.r, color.g, color.b)
|
|
end
|
|
|
|
-- add quest levels to tooltip
|
|
if pfDB["quests"]["loc"][id] then
|
|
ItemRefTooltip:AddLine(" ")
|
|
|
|
if pfDB["quests"]["data"][id]["min"] then
|
|
local questlevel = tonumber(pfDB["quests"]["data"][id]["min"])
|
|
local color = pfQuestCompat.GetDifficultyColor(questlevel)
|
|
ItemRefTooltip:AddLine(
|
|
"|cffffffff" .. pfQuest_Loc["Required Level"] .. ": |r" .. questlevel,
|
|
color.r,
|
|
color.g,
|
|
color.b
|
|
)
|
|
end
|
|
|
|
if pfDB["quests"]["data"][id]["lvl"] then
|
|
local questlevel = tonumber(pfDB["quests"]["data"][id]["lvl"])
|
|
local color = pfQuestCompat.GetDifficultyColor(questlevel)
|
|
ItemRefTooltip:AddLine(
|
|
"|cffffffff" .. pfQuest_Loc["Quest Level"] .. ": |r" .. questlevel,
|
|
color.r,
|
|
color.g,
|
|
color.b
|
|
)
|
|
end
|
|
end
|
|
|
|
ItemRefTooltip:Show()
|
|
end
|
|
end
|