Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| fc4dbc4fb3 | |||
| 7b57a45a1b | |||
| a5633fd9e2 | |||
| ccfb78c3a6 | |||
| 9cdcc1814f | |||
| 0c9af0e9d2 | |||
| 8f4480bf14 | |||
| a276cca444 | |||
| 4502a1db7e | |||
| fda04907c2 | |||
| e95e65fca5 | |||
| 044b6593ec | |||
| fbb3e94b93 |
@@ -6,7 +6,7 @@ RelationshipsAttune = RelationshipsAttune or {}
|
||||
local RA = RelationshipsAttune
|
||||
local L = RelationshipsAttune_L
|
||||
|
||||
RA.VERSION = "1.4.0"
|
||||
RA.VERSION = "1.4.4"
|
||||
RA.COMM_PREFIX = "RATTUNE"
|
||||
|
||||
RA.playerName = nil
|
||||
@@ -70,6 +70,56 @@ function RA:InitDB()
|
||||
if not RelationshipsAttuneCharDB.announced then RelationshipsAttuneCharDB.announced = {} end
|
||||
if not RelationshipsAttuneCharDB.progress then RelationshipsAttuneCharDB.progress = {} end
|
||||
if not RelationshipsAttuneCharDB.expanded then RelationshipsAttuneCharDB.expanded = {} end
|
||||
if not RelationshipsAttuneCharDB.forcedSteps then RelationshipsAttuneCharDB.forcedSteps = {} end
|
||||
if not RelationshipsAttuneCharDB.forcedAtt then RelationshipsAttuneCharDB.forcedAtt = {} end
|
||||
end
|
||||
|
||||
-- ---------------------------------------------------------------
|
||||
-- Force-complete (manual override) helpers
|
||||
-- ---------------------------------------------------------------
|
||||
function RA:IsStepForced(attId, i)
|
||||
local t = RelationshipsAttuneCharDB.forcedSteps
|
||||
return t and t[attId] and t[attId][i] == true
|
||||
end
|
||||
|
||||
function RA:SetStepForced(attId, i, v)
|
||||
if not attId or not i then return end
|
||||
local t = RelationshipsAttuneCharDB.forcedSteps
|
||||
if not t then RelationshipsAttuneCharDB.forcedSteps = {}; t = RelationshipsAttuneCharDB.forcedSteps end
|
||||
if not t[attId] then t[attId] = {} end
|
||||
if v then t[attId][i] = true else t[attId][i] = nil end
|
||||
self:RecomputeAll()
|
||||
if RA.UI and RA.UI.Refresh then RA.UI:Refresh() end
|
||||
end
|
||||
|
||||
function RA:IsAttForced(attId)
|
||||
local t = RelationshipsAttuneCharDB.forcedAtt
|
||||
return t and t[attId] == true
|
||||
end
|
||||
|
||||
function RA:SetAttForced(attId, v)
|
||||
if not attId then return end
|
||||
if not RelationshipsAttuneCharDB.forcedAtt then
|
||||
RelationshipsAttuneCharDB.forcedAtt = {}
|
||||
end
|
||||
if not RelationshipsAttuneCharDB.completedAttunements then
|
||||
RelationshipsAttuneCharDB.completedAttunements = {}
|
||||
end
|
||||
if v then
|
||||
RelationshipsAttuneCharDB.forcedAtt[attId] = true
|
||||
RelationshipsAttuneCharDB.completedAttunements[attId] = true
|
||||
else
|
||||
RelationshipsAttuneCharDB.forcedAtt[attId] = nil
|
||||
RelationshipsAttuneCharDB.completedAttunements[attId] = nil
|
||||
if RelationshipsAttuneCharDB.forcedSteps then
|
||||
RelationshipsAttuneCharDB.forcedSteps[attId] = nil
|
||||
end
|
||||
if RelationshipsAttuneCharDB.announced then
|
||||
RelationshipsAttuneCharDB.announced[attId] = nil
|
||||
end
|
||||
end
|
||||
self:RecomputeAll()
|
||||
if RA.UI and RA.UI.Refresh then RA.UI:Refresh() end
|
||||
end
|
||||
|
||||
-- ---------------------------------------------------------------
|
||||
@@ -81,10 +131,16 @@ end
|
||||
-- ---------------------------------------------------------------
|
||||
function RA:ScanQuestLog()
|
||||
if not GetNumQuestLogEntries then return end
|
||||
-- Rebuild transient quest states from the live log so that abandoning a
|
||||
-- quest reverts its step colour. "turnedin" is authoritative and preserved.
|
||||
-- Snapshot previous transient states so we can detect a turn-in on clients
|
||||
-- that don't fire QUEST_TURNED_IN (stock 1.12). A quest that was
|
||||
-- "complete" (ready to hand in) and is now missing from the log is
|
||||
-- almost certainly turned in -- abandoning a quest requires it to be
|
||||
-- accepted-but-not-complete. This is what actually credits MC's
|
||||
-- "Attunement to the Core" (7487), BWL, Naxx, and any other
|
||||
-- quest-only final step.
|
||||
local prev = RelationshipsAttuneCharDB.quests or {}
|
||||
local kept = {}
|
||||
for qid, st in pairs(RelationshipsAttuneCharDB.quests) do
|
||||
for qid, st in pairs(prev) do
|
||||
if st == "turnedin" then kept[qid] = "turnedin" end
|
||||
end
|
||||
RelationshipsAttuneCharDB.quests = kept
|
||||
@@ -92,6 +148,7 @@ function RA:ScanQuestLog()
|
||||
RA:BuildNameIndex()
|
||||
local qNameMap = RA._questNameToId or {}
|
||||
|
||||
local seenThisScan = {}
|
||||
local numEntries = GetNumQuestLogEntries()
|
||||
for i = 1, numEntries do
|
||||
local ok, title, level, tag, isHeader, _, isComplete, _, questId =
|
||||
@@ -104,17 +161,98 @@ function RA:ScanQuestLog()
|
||||
if (not qid) and title then
|
||||
qid = qNameMap[string.lower(title)]
|
||||
end
|
||||
if qid and RelationshipsAttuneCharDB.quests[qid] ~= "turnedin" then
|
||||
if isComplete and isComplete == 1 then
|
||||
RelationshipsAttuneCharDB.quests[qid] = "complete"
|
||||
else
|
||||
RelationshipsAttuneCharDB.quests[qid] = "inlog"
|
||||
if qid then
|
||||
seenThisScan[qid] = true
|
||||
if RelationshipsAttuneCharDB.quests[qid] ~= "turnedin" then
|
||||
if isComplete and isComplete == 1 then
|
||||
RelationshipsAttuneCharDB.quests[qid] = "complete"
|
||||
else
|
||||
RelationshipsAttuneCharDB.quests[qid] = "inlog"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Retroactive turn-in detection: any quest we tracked as "complete" last
|
||||
-- scan that isn't in the log any more got handed in.
|
||||
for qid, st in pairs(prev) do
|
||||
if st == "complete" and not seenThisScan[qid] then
|
||||
RelationshipsAttuneCharDB.quests[qid] = "turnedin"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- ---------------------------------------------------------------
|
||||
-- Completed-quest scan (server-side completed quest cache).
|
||||
--
|
||||
-- Turtle WoW / OctoWoW backport QueryQuestsCompleted() +
|
||||
-- GetQuestsCompleted(t), which return every quest the character has
|
||||
-- ever turned in -- including quests completed BEFORE the addon was
|
||||
-- installed. Without this, the addon can only see quests that are
|
||||
-- currently in the log (or that were "complete" in a previous scan
|
||||
-- and then vanished), so a player who is already half-way through a
|
||||
-- chain shows no progress until they accept the next quest.
|
||||
--
|
||||
-- We pcall everything so stock 1.12 clients that don't expose these
|
||||
-- APIs simply skip the scan and keep the existing behaviour.
|
||||
-- ---------------------------------------------------------------
|
||||
function RA:ScanCompletedQuests()
|
||||
-- Build the set of quest ids we actually care about so we don't
|
||||
-- pollute the DB with thousands of unrelated turn-ins.
|
||||
if not RelationshipsAttune_Data then return end
|
||||
local wanted = {}
|
||||
for i = 1, table.getn(RelationshipsAttune_Data) do
|
||||
local att = RelationshipsAttune_Data[i]
|
||||
if att and att.steps then
|
||||
for j = 1, table.getn(att.steps) do
|
||||
local s = att.steps[j]
|
||||
if s and s.type == "quest" and s.id then
|
||||
wanted[s.id] = true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local completed = {}
|
||||
local haveAny = false
|
||||
|
||||
-- Preferred: bulk table fill.
|
||||
if type(GetQuestsCompleted) == "function" then
|
||||
local ok = pcall(GetQuestsCompleted, completed)
|
||||
if ok then
|
||||
for _ in pairs(completed) do haveAny = true break end
|
||||
end
|
||||
end
|
||||
|
||||
-- Fallback: per-id check (Turtle exposes IsQuestCompleted on some builds).
|
||||
if not haveAny and type(IsQuestCompleted) == "function" then
|
||||
for qid in pairs(wanted) do
|
||||
local ok, res = pcall(IsQuestCompleted, qid)
|
||||
if ok and res then
|
||||
completed[qid] = true
|
||||
haveAny = true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if not haveAny then
|
||||
-- Kick the server to populate the cache; the QUEST_QUERY_COMPLETE
|
||||
-- event handler will re-run this scan when the reply arrives.
|
||||
if type(QueryQuestsCompleted) == "function" then
|
||||
pcall(QueryQuestsCompleted)
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
for qid in pairs(wanted) do
|
||||
if completed[qid] and RelationshipsAttuneCharDB.quests[qid] ~= "turnedin" then
|
||||
RelationshipsAttuneCharDB.quests[qid] = "turnedin"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function RA:MarkQuestTurnedIn(questId)
|
||||
if not questId then return end
|
||||
RelationshipsAttuneCharDB.quests[questId] = "turnedin"
|
||||
@@ -175,10 +313,24 @@ function RA:BuildNameIndex()
|
||||
for j = 1, table.getn(att.steps) do
|
||||
local s = att.steps[j]
|
||||
if s and s.name and s.id then
|
||||
local nlow = string.lower(s.name)
|
||||
if s.type == "item" then
|
||||
map[string.lower(s.name)] = s.id
|
||||
map[nlow] = s.id
|
||||
elseif s.type == "quest" then
|
||||
qmap[string.lower(s.name)] = s.id
|
||||
qmap[nlow] = s.id
|
||||
-- Data.lua disambiguates faction-specific quests
|
||||
-- with a "(Alliance)" / "(Horde)" suffix that
|
||||
-- the in-game quest log does NOT include. Index
|
||||
-- the stripped variant so the name fallback in
|
||||
-- ScanQuestLog matches on stock 1.12 clients
|
||||
-- that don't return a questId.
|
||||
local _, _, stripped = string.find(nlow, "^(.-)%s*%(alliance%)%s*$")
|
||||
if not stripped then
|
||||
_, _, stripped = string.find(nlow, "^(.-)%s*%(horde%)%s*$")
|
||||
end
|
||||
if stripped and stripped ~= "" and not qmap[stripped] then
|
||||
qmap[stripped] = s.id
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -329,9 +481,13 @@ function RA:EvaluateStep(step)
|
||||
return (UnitLevel("player") or 0) >= (step.level or 0), false
|
||||
elseif step.type == "quest" then
|
||||
local st = RelationshipsAttuneCharDB.quests[step.id]
|
||||
if st == "complete" or st == "turnedin" then
|
||||
if st == "turnedin" then
|
||||
return true, false
|
||||
elseif st == "inlog" then
|
||||
elseif st == "complete" or st == "inlog" then
|
||||
-- Quest is in the log (either accepted or ready to turn in).
|
||||
-- Treat both as "on this step" (orange) until it is actually
|
||||
-- turned in. Retroactive completion will still mark this
|
||||
-- step green once the player advances to a later step.
|
||||
return false, true
|
||||
end
|
||||
return false, false
|
||||
@@ -369,6 +525,20 @@ function RA:EvaluateAttunement(att)
|
||||
stepsInProgress[i] = nil
|
||||
end
|
||||
end
|
||||
-- Apply per-step forced completions (manual overrides from the UI).
|
||||
-- These are treated exactly like a real completion for tracking purposes.
|
||||
local forced = RelationshipsAttuneCharDB.forcedSteps and RelationshipsAttuneCharDB.forcedSteps[att.id]
|
||||
if forced then
|
||||
for i = 1, numSteps do
|
||||
local step = att.steps[i]
|
||||
if step and self:StepApplies(step) and forced[i] and steps[i] == false then
|
||||
steps[i] = true
|
||||
stepsInProgress[i] = false
|
||||
doneCount = doneCount + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Short-circuit: if the "final" step is complete, treat the whole
|
||||
-- attunement as done. Prefer an explicit final=true step; fall back to
|
||||
-- the last item step (key), then the last applicable step.
|
||||
@@ -398,6 +568,34 @@ function RA:EvaluateAttunement(att)
|
||||
end
|
||||
end
|
||||
end
|
||||
-- Retroactive completion: if the player is on a later step (done or in
|
||||
-- progress), any earlier quest/item steps must already be done. Quests
|
||||
-- get turned in and key items get consumed, so their live state can
|
||||
-- flip back to "not done" even though the player clearly finished
|
||||
-- them. Level and reputation steps have their own live truth and are
|
||||
-- left alone.
|
||||
local laterActive = false
|
||||
for i = numSteps, 1, -1 do
|
||||
local step = att.steps[i]
|
||||
if step and self:StepApplies(step) then
|
||||
if laterActive and steps[i] == false then
|
||||
local t = step.type
|
||||
if t == "quest" or t == "item" then
|
||||
steps[i] = true
|
||||
stepsInProgress[i] = false
|
||||
doneCount = doneCount + 1
|
||||
if t == "quest" then
|
||||
RelationshipsAttuneCharDB.quests[step.id] = "turnedin"
|
||||
elseif t == "item" then
|
||||
RelationshipsAttuneCharDB.items[step.id] = true
|
||||
end
|
||||
end
|
||||
end
|
||||
if steps[i] == true or stepsInProgress[i] == true then
|
||||
laterActive = true
|
||||
end
|
||||
end
|
||||
end
|
||||
local persistedDone = RelationshipsAttuneCharDB.completedAttunements and RelationshipsAttuneCharDB.completedAttunements[att.id]
|
||||
if persistedDone or (finalIdx and steps[finalIdx] == true) then
|
||||
for i = 1, numSteps do
|
||||
@@ -481,12 +679,27 @@ function RA:MarkDirty() RA._dirty = true end
|
||||
-- Full periodic rescan (quest log + bags + recompute + UI refresh).
|
||||
function RA:AutoScan()
|
||||
RA:ScanQuestLog()
|
||||
RA:ScanCompletedQuests()
|
||||
RA:ScanBags()
|
||||
RA:RecomputeAll()
|
||||
if RA.UI and RA.UI.Refresh then RA.UI:Refresh() end
|
||||
end
|
||||
|
||||
function RA:_OnUpdate(elapsed)
|
||||
-- Elapsed can arrive as a function argument, as the global arg1, or
|
||||
-- (on some 1.12 builds) not at all. Fall back to GetTime() deltas so
|
||||
-- the periodic auto-scan always advances.
|
||||
if type(elapsed) ~= "number" then elapsed = nil end
|
||||
if not elapsed and type(arg1) == "number" then elapsed = arg1 end
|
||||
if not elapsed and GetTime then
|
||||
local now = GetTime()
|
||||
if RA._lastUpdate then
|
||||
elapsed = now - RA._lastUpdate
|
||||
else
|
||||
elapsed = 0
|
||||
end
|
||||
RA._lastUpdate = now
|
||||
end
|
||||
elapsed = elapsed or 0
|
||||
RA._throttle = RA._throttle + elapsed
|
||||
RA._periodic = RA._periodic + elapsed
|
||||
@@ -508,6 +721,7 @@ function RA:_OnUpdate(elapsed)
|
||||
end
|
||||
if fired then
|
||||
RA:WarmItemCache()
|
||||
RA:ScanCompletedQuests()
|
||||
RA:ScanBags()
|
||||
RA:RecomputeAll()
|
||||
if RA.UI and RA.UI.Refresh then RA.UI:Refresh() end
|
||||
@@ -565,17 +779,45 @@ local function slashHandler(msg)
|
||||
print("Minimap button shown. Drag to reposition around the minimap.")
|
||||
end
|
||||
end
|
||||
elseif string.find(msg, "^pos") then
|
||||
-- /attune pos <x> <y> -- set exact offset from UIParent CENTER.
|
||||
-- Values may be off-screen (e.g. -9999) to park the button out of
|
||||
-- the way while still letting MinimapButtonBag collect it.
|
||||
local _, _, sx, sy = string.find(msg, "pos%s+(%-?%d+%.?%d*)%s+(%-?%d+%.?%d*)")
|
||||
local x, y = tonumber(sx), tonumber(sy)
|
||||
if x and y and RA.Minimap then
|
||||
local o = RelationshipsAttuneDB.options
|
||||
o.minimapX, o.minimapY = x, y
|
||||
RA.Minimap:Refresh()
|
||||
print("Minimap button moved to " .. x .. ", " .. y .. ".")
|
||||
else
|
||||
print("Usage: /attune pos <x> <y> (offsets from screen center; can be off-screen)")
|
||||
end
|
||||
elseif msg == "offscreen" or msg == "park" then
|
||||
if RA.Minimap then
|
||||
local o = RelationshipsAttuneDB.options
|
||||
o.minimapX, o.minimapY = -10000, -10000
|
||||
RA.Minimap:Refresh()
|
||||
print("Minimap button parked off-screen. MinimapButtonBag (if installed) can still collect it. Use /attune resetpos to bring it back.")
|
||||
end
|
||||
elseif msg == "resetpos" then
|
||||
if RA.Minimap then
|
||||
local o = RelationshipsAttuneDB.options
|
||||
o.minimapX, o.minimapY = 0, -140
|
||||
RA.Minimap:Refresh()
|
||||
print("Minimap button position reset.")
|
||||
end
|
||||
elseif msg == "sync" then
|
||||
if RA.Comm then RA.Comm:RequestSync() end
|
||||
print(L.MSG_SYNC_SENT)
|
||||
elseif msg == "scan" then
|
||||
RA:ScanQuestLog(); RA:ScanBags(); RA:RecomputeAll()
|
||||
RA:ScanQuestLog(); RA:ScanCompletedQuests(); RA:ScanBags(); RA:RecomputeAll()
|
||||
if RA.UI and RA.UI.Refresh then RA.UI:Refresh() end
|
||||
print(L.MSG_SCANNED)
|
||||
elseif msg == "reset" then
|
||||
RelationshipsAttuneCharDB = nil
|
||||
RA:InitDB()
|
||||
RA:ScanQuestLog(); RA:ScanBags(); RA:RecomputeAll()
|
||||
RA:ScanQuestLog(); RA:ScanCompletedQuests(); RA:ScanBags(); RA:RecomputeAll()
|
||||
if RA.UI and RA.UI.Refresh then RA.UI:Refresh() end
|
||||
print(L.MSG_RESET)
|
||||
elseif msg == "debug" or string.find(msg, "^find") then
|
||||
@@ -613,6 +855,9 @@ local function slashHandler(msg)
|
||||
print(" /attune show - open the window")
|
||||
print(" /attune hide - close the window (or hide the minimap button)")
|
||||
print(" /attune minimap - toggle the minimap button")
|
||||
print(" /attune pos x y - place the minimap button at exact offset (can be off-screen)")
|
||||
print(" /attune offscreen- park the minimap button off-screen (MBB can still collect it)")
|
||||
print(" /attune resetpos - restore the minimap button to a visible default position")
|
||||
print(" /attune scan - force a rescan of quests + bags")
|
||||
print(" /attune sync - request sync from your group")
|
||||
print(" /attune debug - dump keyring + item detection")
|
||||
@@ -648,6 +893,7 @@ pcall(function() ef:RegisterEvent("ITEM_PUSH") end)
|
||||
pcall(function() ef:RegisterEvent("PLAYER_XP_UPDATE") end)
|
||||
pcall(function() ef:RegisterEvent("ZONE_CHANGED_NEW_AREA") end)
|
||||
pcall(function() ef:RegisterEvent("SKILL_LINES_CHANGED") end)
|
||||
pcall(function() ef:RegisterEvent("QUEST_QUERY_COMPLETE") end)
|
||||
|
||||
ef:SetScript("OnEvent", function()
|
||||
local e = event
|
||||
@@ -659,8 +905,15 @@ ef:SetScript("OnEvent", function()
|
||||
RA.playerFaction = UnitFactionGroup("player") or "Neutral"
|
||||
RA:BuildNameIndex()
|
||||
RA:WarmItemCache()
|
||||
RA:ScanQuestLog(); RA:ScanBags(); RA:RecomputeAll()
|
||||
RA._retryAt = { GetTime() + 3, GetTime() + 10 }
|
||||
-- ScanCompletedQuests seeds the "already turned-in" state from the
|
||||
-- server's completed-quest cache so mid-chain attunements show the
|
||||
-- player's actual current step immediately at load, instead of
|
||||
-- waiting until the next quest in the chain is accepted.
|
||||
RA:ScanQuestLog(); RA:ScanCompletedQuests(); RA:ScanBags(); RA:RecomputeAll()
|
||||
-- The completed-quest cache is often empty on first login until
|
||||
-- QueryQuestsCompleted() round-trips to the server. Schedule extra
|
||||
-- rescans so the UI catches up as soon as the cache is populated.
|
||||
RA._retryAt = { GetTime() + 2, GetTime() + 5, GetTime() + 10 }
|
||||
if RA.Comm and RA.Comm.Init then RA.Comm:Init() end
|
||||
if RA.UI and RA.UI.Init then RA.UI:Init() end
|
||||
if RA.Minimap and RA.Minimap.Init then RA.Minimap:Init() end
|
||||
@@ -689,6 +942,13 @@ ef:SetScript("OnEvent", function()
|
||||
elseif e == "QUEST_TURNED_IN" then
|
||||
if arg1 then RA:MarkQuestTurnedIn(arg1) end
|
||||
|
||||
elseif e == "QUEST_QUERY_COMPLETE" then
|
||||
if RA.initialized then
|
||||
RA:ScanCompletedQuests()
|
||||
RA:RecomputeAll()
|
||||
if RA.UI and RA.UI.Refresh then RA.UI:Refresh() end
|
||||
end
|
||||
|
||||
elseif e == "CHAT_MSG_ADDON" then
|
||||
if arg1 == RA.COMM_PREFIX and RA.Comm and RA.Comm.OnMessage then
|
||||
RA.Comm:OnMessage(arg2, arg3, arg4)
|
||||
@@ -696,7 +956,12 @@ ef:SetScript("OnEvent", function()
|
||||
end
|
||||
end)
|
||||
|
||||
ef:SetScript("OnUpdate", function()
|
||||
ef:SetScript("OnUpdate", function(_self, _elapsed)
|
||||
if not RA.initialized then return end
|
||||
RA:_OnUpdate(arg1)
|
||||
-- Vanilla 1.12 delivers elapsed via the global arg1; some private-server
|
||||
-- builds pass it as a positional parameter instead. Try both.
|
||||
local e = arg1
|
||||
if type(e) ~= "number" then e = _elapsed end
|
||||
if type(e) ~= "number" then e = _self end
|
||||
RA:_OnUpdate(e)
|
||||
end)
|
||||
|
||||
@@ -209,16 +209,16 @@ RelationshipsAttune_Data = {
|
||||
{ type = "level", level = 60, name = "Reach Level 60", icon = ICON_LEVEL },
|
||||
{ type = "rep", faction = "Argent Dawn", standing = 5,
|
||||
name = "Honored with the Argent Dawn", icon = ICON_REP,
|
||||
desc = "Required to purchase the Naxxramas attunement from Archmage Angela Dosantos. Higher rep reduces the gold cost (60g Honored, 40g Revered, 20g Exalted).",
|
||||
desc = "Required to purchase the Naxxramas attunement from Archmage Angela Dosantos. Higher rep with the Argent Dawn drastically reduces the material cost (see steps below).",
|
||||
start = "Turn in Scourgestones / Minion's Scourgestones to Argent Dawn quartermasters at Light's Hope Chapel, Eastern Plaguelands (76, 53)." },
|
||||
{ type = "quest", id = 9121, name = "The Dread Citadel - Naxxramas! (Honored, 60g)", icon = ICON_ACTIVE, final = true,
|
||||
desc = "Pay 60g to Angela Dosantos - available at Honored with Argent Dawn.",
|
||||
{ type = "quest", id = 9121, name = "The Dread Citadel - Naxxramas! (Honored)", icon = ICON_ACTIVE, final = true,
|
||||
desc = "Honored cost: 60 gold, 5 Arcane Crystals, 2 Nexus Crystals, and 1 Righteous Orb. Turn in to Archmage Angela Dosantos.",
|
||||
start = "Archmage Angela Dosantos, Light's Hope Chapel, Eastern Plaguelands (76, 53)." },
|
||||
{ type = "quest", id = 9122, name = "The Dread Citadel - Naxxramas! (Revered, 40g)", icon = ICON_ACTIVE, final = true,
|
||||
desc = "Discounted Naxx attunement offered at Revered with Argent Dawn.",
|
||||
{ type = "quest", id = 9122, name = "The Dread Citadel - Naxxramas! (Revered)", icon = ICON_ACTIVE, final = true,
|
||||
desc = "Revered cost: 30 gold, 2 Arcane Crystals, and 1 Nexus Crystal. Turn in to Archmage Angela Dosantos.",
|
||||
start = "Archmage Angela Dosantos, Light's Hope Chapel, Eastern Plaguelands (76, 53)." },
|
||||
{ type = "quest", id = 9123, name = "The Dread Citadel - Naxxramas! (Exalted, 20g)", icon = ICON_ACTIVE, final = true,
|
||||
desc = "Cheapest Naxx attunement, offered only at Exalted with Argent Dawn.",
|
||||
{ type = "quest", id = 9123, name = "The Dread Citadel - Naxxramas! (Exalted)", icon = ICON_ACTIVE, final = true,
|
||||
desc = "Exalted cost: completely free. Turn in to Archmage Angela Dosantos.",
|
||||
start = "Archmage Angela Dosantos, Light's Hope Chapel, Eastern Plaguelands (76, 53)." },
|
||||
},
|
||||
},
|
||||
@@ -307,7 +307,7 @@ RelationshipsAttune_Data = {
|
||||
-- VANILLA KEYS & DUNGEON ACCESS
|
||||
----------------------------------------------------------------
|
||||
{
|
||||
id = "ubrs", name = "Upper Blackrock Spire (Seal of Ascension)", category = "Keys & Access", faction = "Both",
|
||||
id = "ubrs", name = "Upper Blackrock Spire (Seal)", category = "Keys & Access", faction = "Both",
|
||||
icon = "Interface\\Icons\\INV_Misc_Key_04",
|
||||
steps = {
|
||||
{ type = "level", level = 55, name = "Reach Level 55", icon = ICON_LEVEL },
|
||||
@@ -537,17 +537,6 @@ RelationshipsAttune_Data = {
|
||||
start = "Discs of Norgannon, Chamber of Khaz'mul, Uldaman, Badlands (42, 20)." },
|
||||
},
|
||||
},
|
||||
{
|
||||
id = "hydraxian", name = "Hydraxian Waterlords (MC Runes)", category = "Keys & Access", faction = "Both",
|
||||
icon = "Interface\\Icons\\Spell_Frost_SummonWaterElemental",
|
||||
steps = {
|
||||
{ type = "level", level = 60, name = "Reach Level 60", icon = ICON_LEVEL },
|
||||
{ type = "rep", faction = "Hydraxian Waterlords", standing = 5,
|
||||
name = "Honored with the Hydraxian Waterlords", icon = ICON_REP,
|
||||
desc = "Needed to purchase Aqual Quintessence to douse the Molten Core runes for Majordomo.",
|
||||
start = "Duke Hydraxis, small island off south-east Azshara (79, 76). Turn in water elemental drops (Azshara/Silithus) and MC boss items." },
|
||||
},
|
||||
},
|
||||
----------------------------------------------------------------
|
||||
-- TURTLE / OCTO WOW CUSTOM CONTENT
|
||||
-- Placeholder ids where the live client id isn't confirmed; the
|
||||
@@ -579,56 +568,6 @@ RelationshipsAttune_Data = {
|
||||
start = "Awarded from Archmage Alturus (Morgan's Plot, Deadwind Pass 40, 75) at the end of the LKH attunement." },
|
||||
},
|
||||
},
|
||||
{
|
||||
id = "es", name = "Emerald Sanctum", category = "Turtle", faction = "Both",
|
||||
icon = "Interface\\Icons\\INV_Misc_Head_Dragon_Green",
|
||||
steps = {
|
||||
{ type = "level", level = 60, name = "Reach Level 60", icon = ICON_LEVEL },
|
||||
{ type = "quest", id = 40020, name = "Emerald Sanctum attunement", icon = ICON_QUEST, final = true,
|
||||
desc = "Emerald Sanctum attunement chain (Hinterlands portal). Speak with Keeper Remulos and collect the Sanctum Sigil from the four green dragons around Azeroth (Ysondre, Emeriss, Lethon, Taerar).",
|
||||
start = "Keeper Remulos avatar, eastern Hinterlands near Seradane (55, 22)." },
|
||||
},
|
||||
},
|
||||
{
|
||||
id = "cgrove", name = "Crescent Grove (Turtle 20-man)", category = "Turtle", faction = "Both",
|
||||
icon = "Interface\\Icons\\INV_Misc_Herb_02",
|
||||
steps = {
|
||||
{ type = "level", level = 55, name = "Reach Level 55", icon = ICON_LEVEL },
|
||||
{ type = "quest", id = 40100, name = "Crescent Grove attunement", icon = ICON_QUEST, final = true,
|
||||
desc = "Turtle-WoW 20-man Emerald-themed raid in Ashenvale. Speak to the druid representative at the grove entrance to start the chain (collect corrupted samples inside the outer ring).",
|
||||
start = "Ashenvale, near the Crescent Grove portal south of Warsong Lumber Camp (~40, 63)." },
|
||||
},
|
||||
},
|
||||
{
|
||||
id = "gilneas", name = "Gilneas City", category = "Turtle", faction = "Both",
|
||||
icon = "Interface\\Icons\\Ability_Mount_WhiteDireWolf",
|
||||
steps = {
|
||||
{ type = "level", level = 55, name = "Reach Level 55", icon = ICON_LEVEL },
|
||||
{ type = "quest", id = 40030, name = "Gilneas City attunement", icon = ICON_QUEST, final = true,
|
||||
desc = "Attunement for the Gilneas City dungeon. Speak with Lord Darius Crowley at the Greymane Wall and complete the intro quest chain in Silverpine.",
|
||||
start = "Lord Darius Crowley, Greymane Wall entrance, southern Silverpine Forest (43, 66)." },
|
||||
},
|
||||
},
|
||||
{
|
||||
id = "hateforge", name = "Hateforge Quarry", category = "Turtle", faction = "Both",
|
||||
icon = "Interface\\Icons\\INV_Hammer_16",
|
||||
steps = {
|
||||
{ type = "level", level = 55, name = "Reach Level 55", icon = ICON_LEVEL },
|
||||
{ type = "quest", id = 40040, name = "Hateforge Quarry attunement", icon = ICON_QUEST, final = true,
|
||||
desc = "Attunement for the Hateforge Quarry dungeon. Turtle Dark Iron chain out of Burning Steppes.",
|
||||
start = "Overseer Maltorius, Flame Crest camp, northern Burning Steppes (62, 24)." },
|
||||
},
|
||||
},
|
||||
{
|
||||
id = "stormwind_vault", name = "Stormwind Vault", category = "Turtle", faction = "Both",
|
||||
icon = "Interface\\Icons\\INV_Misc_Coin_01",
|
||||
steps = {
|
||||
{ type = "level", level = 55, name = "Reach Level 55", icon = ICON_LEVEL },
|
||||
{ type = "quest", id = 40050, name = "Stormwind Vault attunement", icon = ICON_QUEST, final = true,
|
||||
desc = "Attunement for the Stormwind Vault dungeon (Turtle). Alliance intro from the Stockade warden; Horde variant from a SI:7 defector in Booty Bay.",
|
||||
start = "Warden Thelwater, Stormwind Stockade entrance, Mage Quarter, Stormwind City (50, 68). Horde variant: Booty Bay, Stranglethorn (27, 77)." },
|
||||
},
|
||||
},
|
||||
----------------------------------------------------------------
|
||||
-- DRAGONMAW RETREAT (Turtle/Octo custom, Wetlands, patch 1.18)
|
||||
----------------------------------------------------------------
|
||||
@@ -666,4 +605,4 @@ RelationshipsAttune_Data = {
|
||||
start = "Looted from Dragonmaw guards in the Dragonmaw Reserve outpost, Wetlands (~70, 40)." },
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
+28
-7
@@ -52,22 +52,35 @@ end
|
||||
MM._updatePosition = applyPosition
|
||||
|
||||
local function onDragUpdate()
|
||||
local mx, my = GetCursorPosition()
|
||||
local scale = UIParent:GetEffectiveScale() or 1
|
||||
mx = mx / scale; my = my / scale
|
||||
-- Do all math in the button's own effective scale. The button is
|
||||
-- parented to Minimap, whose effective scale can differ from
|
||||
-- UIParent's; mixing scales makes the icon drift away from the cursor.
|
||||
local btn = this
|
||||
local bscale = btn:GetEffectiveScale() or 1
|
||||
local uscale = UIParent:GetEffectiveScale() or 1
|
||||
local mx, my = GetCursorPosition()
|
||||
mx = mx / bscale
|
||||
my = my / bscale
|
||||
local ucx, ucy = UIParent:GetCenter()
|
||||
if not ucx then return end
|
||||
-- Convert UIParent center from UIParent scale into btn scale.
|
||||
ucx = ucx * uscale / bscale
|
||||
ucy = ucy * uscale / bscale
|
||||
local o = opts()
|
||||
o.minimapX = mx - ucx
|
||||
o.minimapY = my - ucy
|
||||
applyPosition(this)
|
||||
applyPosition(btn)
|
||||
end
|
||||
|
||||
function MM:Create()
|
||||
if getglobal(BTN_NAME) then return getglobal(BTN_NAME) end
|
||||
|
||||
-- Parent to UIParent so we're not clipped to the minimap area.
|
||||
local btn = CreateFrame("Button", BTN_NAME, UIParent)
|
||||
-- Parent to Minimap so button-collector addons (MinimapButtonBag /
|
||||
-- MBB, and similar) can discover and manage this button the same way
|
||||
-- they discover Blizzard's built-in minimap buttons. SetPoint still
|
||||
-- anchors to UIParent CENTER so the button can be dragged anywhere
|
||||
-- on screen -- the anchor frame is independent of the parent frame.
|
||||
local btn = CreateFrame("Button", BTN_NAME, Minimap)
|
||||
btn:SetWidth(31); btn:SetHeight(31)
|
||||
btn:SetFrameStrata("MEDIUM")
|
||||
btn:SetFrameLevel(8)
|
||||
@@ -75,6 +88,12 @@ function MM:Create()
|
||||
btn:RegisterForClicks("LeftButtonUp", "RightButtonUp")
|
||||
btn:RegisterForDrag("LeftButton", "RightButton")
|
||||
btn:SetMovable(true)
|
||||
-- Allow the button to sit off-screen (via /attune pos or /attune offscreen)
|
||||
-- while remaining discoverable by MinimapButtonBag.
|
||||
if btn.SetClampedToScreen then btn:SetClampedToScreen(false) end
|
||||
-- MBB-friendly highlight so the button visually matches other
|
||||
-- minimap buttons once it has been swept into the bag.
|
||||
btn:SetHighlightTexture("Interface\\Minimap\\UI-Minimap-ZoomButton-Highlight")
|
||||
|
||||
-- Round border ring (standard minimap-button look)
|
||||
local overlay = btn:CreateTexture(nil, "OVERLAY")
|
||||
@@ -89,6 +108,7 @@ function MM:Create()
|
||||
icon:SetPoint("TOPLEFT", btn, "TOPLEFT", 7, -6)
|
||||
btn.icon = icon
|
||||
|
||||
|
||||
btn:SetScript("OnClick", function()
|
||||
if RA.UI and RA.UI.Toggle then RA.UI:Toggle() end
|
||||
end)
|
||||
@@ -107,7 +127,8 @@ function MM:Create()
|
||||
GameTooltip:AddLine("Relationships Attune", 1, 0.82, 0)
|
||||
GameTooltip:AddLine("Left-click: open / close window", 0.9, 0.9, 0.9)
|
||||
GameTooltip:AddLine("Drag: move anywhere on screen", 0.9, 0.9, 0.9)
|
||||
GameTooltip:AddLine("/attune hide - hide this button", 0.6, 0.8, 1)
|
||||
GameTooltip:AddLine("/attune offscreen - park off-screen (MBB safe)", 0.6, 0.8, 1)
|
||||
GameTooltip:AddLine("/attune hide - hide this button", 0.6, 0.8, 1)
|
||||
GameTooltip:Show()
|
||||
end)
|
||||
btn:SetScript("OnLeave", function() GameTooltip:Hide() end)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<img src="https://cdn.discordapp.com/attachments/1510333697277300856/1524819837270884362/kkk.gif?ex=6a5122a2&is=6a4fd122&hm=b82d19b995b2d1f8b252fcbaf44dcaa7b5f30255fb1401f50f931919c980a4ce&"/>
|
||||
<img src="https://i.postimg.cc/rwVg8mmF/v.png"/>
|
||||
|
||||
|
||||
# RelationshipsAttune
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
## Title: Relationships Attune
|
||||
## Notes: Track Vanilla and Turtle/Octo WoW attunement progress
|
||||
## Author: Relationship
|
||||
## Version: 1.4
|
||||
## Version: 1.9
|
||||
## X-Category: Quest
|
||||
## SavedVariables: RelationshipsAttuneDB
|
||||
## SavedVariablesPerCharacter: RelationshipsAttuneCharDB
|
||||
|
||||
@@ -26,6 +26,55 @@ local C_TURTLE = { 0.55, 0.85, 1.00 }
|
||||
local C_KEYS = { 0.95, 0.75, 0.35 }
|
||||
local C_INPROGRESS = { 1.00, 0.60, 0.15 } -- orange: quest accepted / step in progress
|
||||
|
||||
-- ---------------------------------------------------------------
|
||||
-- Force-complete confirmation dialogs (StaticPopup)
|
||||
-- In vanilla 1.12, StaticPopup OnAccept is called as OnAccept(dialog.data),
|
||||
-- so the data arrives as arg1 (Lua 5.0 vararg). `this` inside OnAccept is
|
||||
-- NOT the dialog frame, so this.data is nil. We stash the data on the
|
||||
-- dialog itself via OnShow (which does run with this = dialog) and read
|
||||
-- it back from arg1 with a dialog fallback for safety.
|
||||
-- ---------------------------------------------------------------
|
||||
StaticPopupDialogs = StaticPopupDialogs or {}
|
||||
|
||||
local function RATTUNE_StashData()
|
||||
-- OnShow runs with `this` = the dialog frame. StaticPopup_Show already
|
||||
-- assigns dialog.data, but we snapshot it into an upvalue table so the
|
||||
-- OnAccept closures can find it even on clients that don't forward arg1.
|
||||
if this then RATTUNE_LastPopupData = this.data end
|
||||
end
|
||||
|
||||
-- Attunement id awaiting the user's confirmation for a force / unforce.
|
||||
-- StaticPopup OnAccept in vanilla 1.12 does not reliably receive dialog.data
|
||||
-- as arg1, so we snapshot the id here at click time and read it back on accept.
|
||||
RATTUNE_PendingForceAtt = nil
|
||||
|
||||
StaticPopupDialogs["RATTUNE_FORCE_ATT"] = {
|
||||
text = "Force complete the entire \"%s\" attunement?\n\nEvery step will be marked as done for tracking. You can undo this later.",
|
||||
button1 = "Force Complete",
|
||||
button2 = "Cancel",
|
||||
OnAccept = function()
|
||||
local id = RATTUNE_PendingForceAtt
|
||||
RATTUNE_PendingForceAtt = nil
|
||||
if id then RA:SetAttForced(id, true) end
|
||||
end,
|
||||
OnCancel = function() RATTUNE_PendingForceAtt = nil end,
|
||||
timeout = 0, whileDead = 1, hideOnEscape = 1, showAlert = 1,
|
||||
}
|
||||
|
||||
StaticPopupDialogs["RATTUNE_UNFORCE_ATT"] = {
|
||||
text = "Undo forced completion for \"%s\"?\n\nProgress will revert to real, scanned state.",
|
||||
button1 = "Undo",
|
||||
button2 = "Cancel",
|
||||
OnAccept = function()
|
||||
local id = RATTUNE_PendingForceAtt
|
||||
RATTUNE_PendingForceAtt = nil
|
||||
if id then RA:SetAttForced(id, false) end
|
||||
end,
|
||||
OnCancel = function() RATTUNE_PendingForceAtt = nil end,
|
||||
timeout = 0, whileDead = 1, hideOnEscape = 1, showAlert = 1,
|
||||
}
|
||||
|
||||
|
||||
local selectedPlayer = nil
|
||||
local selectedRaid = nil
|
||||
|
||||
@@ -196,6 +245,29 @@ function UI:Build()
|
||||
detail.category = fs(frame.rightPane, "", 10, 0.75, 0.75, 0.75)
|
||||
detail.category:SetPoint("TOPLEFT", detail.name, "BOTTOMLEFT", 0, -2)
|
||||
|
||||
-- Force Complete (attunement-level) button. Sits in the top-right of the
|
||||
-- right pane, hidden when viewing another player's data.
|
||||
detail.forceBtn = CreateFrame("Button", nil, frame.rightPane, "UIPanelButtonTemplate")
|
||||
detail.forceBtn:SetWidth(150); detail.forceBtn:SetHeight(20)
|
||||
detail.forceBtn:SetPoint("TOPRIGHT", frame.rightPane, "TOPRIGHT", -4, -4)
|
||||
detail.forceBtn:SetText("Force Complete")
|
||||
detail.forceBtn:SetScript("OnClick", function()
|
||||
local attId = selectedRaid
|
||||
if not attId then return end
|
||||
local att
|
||||
local data = RelationshipsAttune_Data
|
||||
for i = 1, table.getn(data) do
|
||||
if data[i].id == attId then att = data[i]; break end
|
||||
end
|
||||
if not att then return end
|
||||
RATTUNE_PendingForceAtt = attId
|
||||
if RA:IsAttForced(attId) then
|
||||
StaticPopup_Show("RATTUNE_UNFORCE_ATT", att.name)
|
||||
else
|
||||
StaticPopup_Show("RATTUNE_FORCE_ATT", att.name)
|
||||
end
|
||||
end)
|
||||
|
||||
-- Progress bar: give it explicit left+right anchors AND a border so it
|
||||
-- always has a resolvable width, then size the fill from that width.
|
||||
detail.barBG = solid(frame.rightPane, "BORDER", 0, 0, 0, 0.90)
|
||||
@@ -566,6 +638,20 @@ function UI:RenderDetail()
|
||||
detail.barFill:SetVertexColor(c[1], c[2], c[3], 0.95)
|
||||
detail.barText:SetText(done .. " / " .. total .. " (" .. math.floor(pct * 100 + 0.5) .. "%)")
|
||||
|
||||
-- Update attunement-level Force button (only for your own data)
|
||||
if detail.forceBtn then
|
||||
if selectedPlayer ~= nil then
|
||||
detail.forceBtn:Hide()
|
||||
else
|
||||
if RA:IsAttForced(att.id) then
|
||||
detail.forceBtn:SetText("Undo Force Complete")
|
||||
else
|
||||
detail.forceBtn:SetText("Force Complete")
|
||||
end
|
||||
detail.forceBtn:Show()
|
||||
end
|
||||
end
|
||||
|
||||
-- Build applicable step list
|
||||
local applicable = {}
|
||||
for i = 1, table.getn(att.steps) do
|
||||
|
||||
Reference in New Issue
Block a user