16 Commits

Author SHA1 Message Date
Relationship ccfb78c3a6 Add files via upload 2026-07-18 18:50:56 +01:00
Relationship 9cdcc1814f Update README.md 2026-07-18 18:37:07 +01:00
Relationship 0c9af0e9d2 Update README.md 2026-07-16 17:57:27 +01:00
Relationship 8f4480bf14 Add files via upload 2026-07-16 17:56:42 +01:00
Relationship a276cca444 Add files via upload 2026-07-16 17:34:22 +01:00
Relationship 4502a1db7e Add files via upload 2026-07-16 17:03:20 +01:00
Relationship fda04907c2 Update README.md 2026-07-15 15:32:46 +01:00
Relationship e95e65fca5 Add files via upload 2026-07-12 17:16:47 +01:00
Relationship 044b6593ec Add files via upload 2026-07-12 17:06:21 +01:00
Relationship fbb3e94b93 Add files via upload 2026-07-12 17:02:18 +01:00
Relationship cca57c7adc Add files via upload 2026-07-12 16:58:48 +01:00
Relationship c1c1741b6e Add files via upload 2026-07-12 12:30:17 +01:00
Relationship 9669809096 Update RelationshipsAttune.toc 2026-07-11 11:18:43 +01:00
Relationship 04f87e5b42 Add files via upload 2026-07-11 11:12:12 +01:00
Relationship 664fc36e0d Update README.md 2026-07-10 08:08:49 +01:00
Relationship 5b9e5f6442 Update README.md 2026-07-09 17:52:48 +01:00
6 changed files with 751 additions and 428 deletions
+297 -85
View File
@@ -6,7 +6,7 @@ RelationshipsAttune = RelationshipsAttune or {}
local RA = RelationshipsAttune local RA = RelationshipsAttune
local L = RelationshipsAttune_L local L = RelationshipsAttune_L
RA.VERSION = "1.2.7" RA.VERSION = "1.4.3"
RA.COMM_PREFIX = "RATTUNE" RA.COMM_PREFIX = "RATTUNE"
RA.playerName = nil RA.playerName = nil
@@ -58,7 +58,7 @@ function RA:InitDB()
end end
local o = RelationshipsAttuneDB.options local o = RelationshipsAttuneDB.options
if o.autoShare == nil then o.autoShare = true end if o.autoShare == nil then o.autoShare = true end
if o.autoScanSeconds == nil then o.autoScanSeconds = 20 end if o.autoScanSeconds == nil then o.autoScanSeconds = 10 end
if type(o.minimapAngle) ~= "number" then o.minimapAngle = 200 end if type(o.minimapAngle) ~= "number" then o.minimapAngle = 200 end
if type(o.minimapRadius) ~= "number" then o.minimapRadius = 80 end if type(o.minimapRadius) ~= "number" then o.minimapRadius = 80 end
if o.minimapHide == nil then o.minimapHide = false end if o.minimapHide == nil then o.minimapHide = false end
@@ -67,31 +67,123 @@ function RA:InitDB()
if not RelationshipsAttuneCharDB.quests then RelationshipsAttuneCharDB.quests = {} end if not RelationshipsAttuneCharDB.quests then RelationshipsAttuneCharDB.quests = {} end
if not RelationshipsAttuneCharDB.items then RelationshipsAttuneCharDB.items = {} end if not RelationshipsAttuneCharDB.items then RelationshipsAttuneCharDB.items = {} end
if not RelationshipsAttuneCharDB.completedAttunements then RelationshipsAttuneCharDB.completedAttunements = {} end if not RelationshipsAttuneCharDB.completedAttunements then RelationshipsAttuneCharDB.completedAttunements = {} end
if not RelationshipsAttuneCharDB.announced then RelationshipsAttuneCharDB.announced = {} end
if not RelationshipsAttuneCharDB.progress then RelationshipsAttuneCharDB.progress = {} end if not RelationshipsAttuneCharDB.progress then RelationshipsAttuneCharDB.progress = {} end
if not RelationshipsAttuneCharDB.expanded then RelationshipsAttuneCharDB.expanded = {} 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 end
-- --------------------------------------------------------------- -- ---------------------------------------------------------------
-- Quest log scan (vanilla API) -- Quest log scan (vanilla API)
--
-- Note: vanilla 1.12's GetQuestLogTitle returns 6 values without a questId.
-- Turtle/Octo backported questId as the 8th return. We pcall + accept both
-- shapes so the addon degrades gracefully on stock 1.12.
-- --------------------------------------------------------------- -- ---------------------------------------------------------------
function RA:ScanQuestLog() function RA:ScanQuestLog()
if not GetNumQuestLogEntries then return end if not GetNumQuestLogEntries then return end
-- 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(prev) do
if st == "turnedin" then kept[qid] = "turnedin" end
end
RelationshipsAttuneCharDB.quests = kept
RA:BuildNameIndex()
local qNameMap = RA._questNameToId or {}
local seenThisScan = {}
local numEntries = GetNumQuestLogEntries() local numEntries = GetNumQuestLogEntries()
for i = 1, numEntries do for i = 1, numEntries do
local title, level, tag, isHeader, _, isComplete, _, questId = local ok, title, level, tag, isHeader, _, isComplete, _, questId =
GetQuestLogTitle(i) pcall(GetQuestLogTitle, i)
if (not isHeader) and questId then if ok and (not isHeader) then
-- Prefer the Turtle/Octo backported questId; fall back to matching
-- the quest title against our data so stock 1.12 (no questId return)
-- and any titles the client failed to tag still get credited.
local qid = questId
if (not qid) and title then
qid = qNameMap[string.lower(title)]
end
if qid then
seenThisScan[qid] = true
if RelationshipsAttuneCharDB.quests[qid] ~= "turnedin" then
if isComplete and isComplete == 1 then if isComplete and isComplete == 1 then
RelationshipsAttuneCharDB.quests[questId] = "complete" RelationshipsAttuneCharDB.quests[qid] = "complete"
else else
if not RelationshipsAttuneCharDB.quests[questId] then RelationshipsAttuneCharDB.quests[qid] = "inlog"
RelationshipsAttuneCharDB.quests[questId] = "inlog"
end end
end end
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 end
function RA:MarkQuestTurnedIn(questId) function RA:MarkQuestTurnedIn(questId)
if not questId then return end if not questId then return end
RelationshipsAttuneCharDB.quests[questId] = "turnedin" RelationshipsAttuneCharDB.quests[questId] = "turnedin"
@@ -112,11 +204,6 @@ local function rememberItemLink(link)
end end
-- Resolve by name so a custom Turtle/Octo item whose live client id -- Resolve by name so a custom Turtle/Octo item whose live client id
-- differs from the id declared in Data.lua still gets credited. -- differs from the id declared in Data.lua still gets credited.
-- Try GetItemInfo first (works when the item is cached), then fall back
-- to parsing the bracketed display name straight out of the link. On
-- 1.12 / Turtle / Octo, keyring items very often return a link but
-- GetItemInfo returns nil because the item is not cached client-side,
-- so the bracketed-name parse is what actually credits the item.
local resolvedName local resolvedName
if GetItemInfo then if GetItemInfo then
local ok, n = pcall(GetItemInfo, link) local ok, n = pcall(GetItemInfo, link)
@@ -148,22 +235,41 @@ end
-- for them (a known 1.12 / Turtle / Octo quirk for keyring slots). -- for them (a known 1.12 / Turtle / Octo quirk for keyring slots).
RA._nameToId = nil RA._nameToId = nil
function RA:BuildNameIndex() function RA:BuildNameIndex()
if RA._nameToId then return RA._nameToId end if RA._nameToId and RA._questNameToId then return RA._nameToId end
local map = {} local map, qmap = {}, {}
if RelationshipsAttune_Data then if RelationshipsAttune_Data then
for i = 1, table.getn(RelationshipsAttune_Data) do for i = 1, table.getn(RelationshipsAttune_Data) do
local att = RelationshipsAttune_Data[i] local att = RelationshipsAttune_Data[i]
if att and att.steps then if att and att.steps then
for j = 1, table.getn(att.steps) do for j = 1, table.getn(att.steps) do
local s = att.steps[j] local s = att.steps[j]
if s and s.type == "item" and s.name and s.id then if s and s.name and s.id then
map[string.lower(s.name)] = s.id local nlow = string.lower(s.name)
if s.type == "item" then
map[nlow] = s.id
elseif s.type == "quest" then
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
end end
end end
end end
end end
RA._nameToId = map RA._nameToId = map
RA._questNameToId = qmap
return map return map
end end
@@ -200,19 +306,10 @@ local function scanContainer(containerId)
end end
end end
-- Scan a keyring-like container. Uses every fallback we know of so that
-- keys register on vanilla 1.12, Turtle, and Octo regardless of which
-- bag id / API path actually works on that client build:
-- 1) GetContainerItemLink -> parse item id + bracketed name from link.
-- 2) SetBagItem tooltip -> read visible line as name.
-- 3) GetContainerItemInfo -> at minimum proves a slot is occupied so
-- we keep probing even when NumSlots lies.
local function scanKeyringContainer(bag) local function scanKeyringContainer(bag)
if not GetContainerNumSlots then return end if not GetContainerNumSlots then return end
local ok, slots = pcall(GetContainerNumSlots, bag) local ok, slots = pcall(GetContainerNumSlots, bag)
if not ok or not slots then slots = 0 end if not ok or not slots then slots = 0 end
-- Some 1.12 clients report 0 slots for the keyring until it has been
-- opened at least once. Probe a reasonable upper bound in that case.
local probe = slots local probe = slots
if probe < 1 then probe = 32 end if probe < 1 then probe = 32 end
@@ -224,13 +321,11 @@ local function scanKeyringContainer(bag)
local setOk = false local setOk = false
pcall(function() tip:SetBagItem(bag, slot); setOk = true end) pcall(function() tip:SetBagItem(bag, slot); setOk = true end)
-- Path 1: link
local link local link
local okL, l = pcall(GetContainerItemLink, bag, slot) local okL, l = pcall(GetContainerItemLink, bag, slot)
if okL then link = l end if okL then link = l end
if link then rememberItemLink(link) end if link then rememberItemLink(link) end
-- Path 2: tooltip visible name (works even when link is nil).
if setOk then if setOk then
local name = tipItemName() local name = tipItemName()
if name and nameMap then if name and nameMap then
@@ -239,11 +334,6 @@ local function scanKeyringContainer(bag)
end end
end end
-- Path 3: GetContainerItemInfo. We can't get a name from it, but
-- if it returns a texture the slot is definitely occupied; that
-- lets us know it's worth continuing to probe even when the
-- earlier paths returned nothing on this build.
-- (No-op for identification, kept for future diagnostics.)
if GetContainerItemInfo then if GetContainerItemInfo then
pcall(GetContainerItemInfo, bag, slot) pcall(GetContainerItemInfo, bag, slot)
end end
@@ -251,11 +341,6 @@ local function scanKeyringContainer(bag)
pcall(function() tip:Hide() end) pcall(function() tip:Hide() end)
end end
-- Warm the client-side item cache for every item id we care about. On
-- vanilla 1.12 the first GetItemInfo(id) call for an uncached item kicks
-- off a server fetch and returns nil; a later call returns real data.
-- Doing this at load time (and again after a short delay) means that by
-- the time we parse a keyring link, GetItemInfo can actually resolve it.
function RA:WarmItemCache() function RA:WarmItemCache()
if not GetItemInfo or not RelationshipsAttune_Data then return end if not GetItemInfo or not RelationshipsAttune_Data then return end
for i = 1, table.getn(RelationshipsAttune_Data) do for i = 1, table.getn(RelationshipsAttune_Data) do
@@ -278,10 +363,6 @@ function RA:ScanBags()
scanContainer(bag) scanContainer(bag)
end end
-- Keyring: try every index the various 1.12 forks use. Vanilla/Turtle/
-- Octo typically expose it as -2; a few builds use -1, -3, or define
-- KEYRING_CONTAINER to something else entirely. Scanning a bogus id
-- is harmless because scanKeyringContainer pcall-guards every call.
local seen = {} local seen = {}
local function tryBag(b) local function tryBag(b)
if b == nil or seen[b] then return end if b == nil or seen[b] then return end
@@ -301,7 +382,6 @@ function RA:ScanBags()
end end
end end
-- Back-compat shim: older code paths may still call this.
function RA:ForceKeyringResolve() function RA:ForceKeyringResolve()
scanKeyringContainer(-2) scanKeyringContainer(-2)
end end
@@ -325,43 +405,74 @@ end
-- Compute attunement progress -- Compute attunement progress
-- --------------------------------------------------------------- -- ---------------------------------------------------------------
function RA:EvaluateStep(step) function RA:EvaluateStep(step)
-- Returns two values: done (boolean), inProgress (boolean).
-- inProgress = the player is actively working on this step but has not
-- yet finished it (e.g. quest accepted but not complete).
if step.type == "level" then if step.type == "level" then
return (UnitLevel("player") or 0) >= (step.level or 0) return (UnitLevel("player") or 0) >= (step.level or 0), false
elseif step.type == "quest" then elseif step.type == "quest" then
local st = RelationshipsAttuneCharDB.quests[step.id] local st = RelationshipsAttuneCharDB.quests[step.id]
return st == "complete" or st == "turnedin" if st == "turnedin" then
return true, false
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
elseif step.type == "item" then elseif step.type == "item" then
return RelationshipsAttuneCharDB.items[step.id] == true return RelationshipsAttuneCharDB.items[step.id] == true, false
elseif step.type == "rep" then elseif step.type == "rep" then
local need = DATA_STANDING_MAP[step.standing] or step.standing or 5 local need = DATA_STANDING_MAP[step.standing] or step.standing or 5
local have = self:GetReputationStanding(step.faction) local have = self:GetReputationStanding(step.faction)
return have >= need if have >= need then return true, false end
-- Any positive rep with the faction below the required standing counts
-- as in-progress so the player sees they are working on it.
if have and have >= 4 then return false, true end
return false, false
end end
return false return false, false
end end
function RA:EvaluateAttunement(att) function RA:EvaluateAttunement(att)
if att.faction and att.faction ~= "Both" and att.faction ~= RA.playerFaction then if att.faction and att.faction ~= "Both" and att.faction ~= RA.playerFaction then
return { applicable = false, done = false, steps = {}, doneCount = 0, total = 0 } return { applicable = false, done = false, steps = {}, doneCount = 0, total = 0 }
end end
local steps, doneCount, total = {}, 0, 0 local steps, stepsInProgress, doneCount, total = {}, {}, 0, 0
local numSteps = table.getn(att.steps) local numSteps = table.getn(att.steps)
for i = 1, numSteps do for i = 1, numSteps do
local step = att.steps[i] local step = att.steps[i]
if self:StepApplies(step) then if self:StepApplies(step) then
local ok = self:EvaluateStep(step) and true or false local doneVal, ipVal = self:EvaluateStep(step)
local ok = doneVal and true or false
steps[i] = ok steps[i] = ok
stepsInProgress[i] = (not ok) and (ipVal and true or false) or false
total = total + 1 total = total + 1
if ok then doneCount = doneCount + 1 end if ok then doneCount = doneCount + 1 end
else else
steps[i] = nil steps[i] = nil
stepsInProgress[i] = nil
end end
end end
-- Short-circuit: if the player already owns the final key/item (or a step -- Apply per-step forced completions (manual overrides from the UI).
-- explicitly flagged final=true) then the entire attunement is done, even -- These are treated exactly like a real completion for tracking purposes.
-- if earlier bookkeeping steps (prereq quests, reputation) can no longer local forced = RelationshipsAttuneCharDB.forcedSteps and RelationshipsAttuneCharDB.forcedSteps[att.id]
-- be detected on this character. This handles the case where the key was if forced then
-- earned before the addon was installed. 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.
local finalIdx = nil local finalIdx = nil
for i = numSteps, 1, -1 do for i = numSteps, 1, -1 do
local step = att.steps[i] local step = att.steps[i]
@@ -371,7 +482,6 @@ function RA:EvaluateAttunement(att)
end end
end end
if not finalIdx then if not finalIdx then
-- Fall back to the last applicable item step (typically the key).
for i = numSteps, 1, -1 do for i = numSteps, 1, -1 do
local step = att.steps[i] local step = att.steps[i]
if step.type == "item" and self:StepApplies(step) then if step.type == "item" and self:StepApplies(step) then
@@ -381,8 +491,6 @@ function RA:EvaluateAttunement(att)
end end
end end
if not finalIdx then if not finalIdx then
-- If there is no key/item step, the last applicable step is the final
-- proof we can see. This covers quest-only or reputation attunements.
for i = numSteps, 1, -1 do for i = numSteps, 1, -1 do
local step = att.steps[i] local step = att.steps[i]
if self:StepApplies(step) then if self:StepApplies(step) then
@@ -391,6 +499,34 @@ function RA:EvaluateAttunement(att)
end end
end 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] local persistedDone = RelationshipsAttuneCharDB.completedAttunements and RelationshipsAttuneCharDB.completedAttunements[att.id]
if persistedDone or (finalIdx and steps[finalIdx] == true) then if persistedDone or (finalIdx and steps[finalIdx] == true) then
for i = 1, numSteps do for i = 1, numSteps do
@@ -407,15 +543,47 @@ function RA:EvaluateAttunement(att)
applicable = true, applicable = true,
done = (total > 0 and doneCount == total), done = (total > 0 and doneCount == total),
steps = steps, steps = steps,
stepsInProgress= stepsInProgress,
doneCount = doneCount, doneCount = doneCount,
total = total, total = total,
} }
end end
function RA:AnnounceCompletion(att)
if not att or not att.name then return end
if not RelationshipsAttuneCharDB.announced then
RelationshipsAttuneCharDB.announced = {}
end
if RelationshipsAttuneCharDB.announced[att.id] then return end
RelationshipsAttuneCharDB.announced[att.id] = true
local inGuild = false
if IsInGuild then
local ok, res = pcall(IsInGuild)
if ok and res then inGuild = true end
end
if not inGuild and GetGuildInfo then
local ok, gname = pcall(GetGuildInfo, "player")
if ok and gname and gname ~= "" then inGuild = true end
end
local msg = "Attunement Complete: " .. (RA.playerName or "I")
.. " has fully completed the " .. att.name .. " attunement!"
if inGuild and SendChatMessage then
pcall(SendChatMessage, msg, "GUILD")
end
RA.print(msg)
end
function RA:RecomputeAll() function RA:RecomputeAll()
for i = 1, table.getn(RelationshipsAttune_Data) do for i = 1, table.getn(RelationshipsAttune_Data) do
local att = RelationshipsAttune_Data[i] local att = RelationshipsAttune_Data[i]
RelationshipsAttuneCharDB.progress[att.id] = self:EvaluateAttunement(att) local prev = RelationshipsAttuneCharDB.progress[att.id]
local wasDone = prev and prev.done
local prog = self:EvaluateAttunement(att)
RelationshipsAttuneCharDB.progress[att.id] = prog
if prog.applicable and prog.done and not wasDone then
self:AnnounceCompletion(att)
end
end end
RelationshipsAttuneDB.players[RA.playerKey] = { RelationshipsAttuneDB.players[RA.playerKey] = {
name = RA.playerName, name = RA.playerName,
@@ -433,21 +601,28 @@ end
-- Throttled auto-scan -- Throttled auto-scan
-- --------------------------------------------------------------- -- ---------------------------------------------------------------
RA._throttle = 0 RA._throttle = 0
RA._periodic = 0
RA._dirty = false RA._dirty = false
RA._minRescan = 2.0 RA._minRescan = 1.5
function RA:MarkDirty() RA._dirty = true end function RA:MarkDirty() RA._dirty = true end
function RA:_OnUpdate(elapsed) -- Full periodic rescan (quest log + bags + recompute + UI refresh).
RA._throttle = RA._throttle + (elapsed or 0) function RA:AutoScan()
local interval = (RelationshipsAttuneDB and RelationshipsAttuneDB.options RA:ScanQuestLog()
and RelationshipsAttuneDB.options.autoScanSeconds) or 20 RA:ScanBags()
if interval < 5 then interval = 5 end RA:RecomputeAll()
if RA.UI and RA.UI.Refresh then RA.UI:Refresh() end
end
function RA:_OnUpdate(elapsed)
elapsed = elapsed or 0
RA._throttle = RA._throttle + elapsed
RA._periodic = RA._periodic + elapsed
local interval = (RelationshipsAttuneDB and RelationshipsAttuneDB.options
and RelationshipsAttuneDB.options.autoScanSeconds) or 10
if interval < 3 then interval = 3 end
-- Delayed post-login retry passes. GetItemInfo populates async on
-- 1.12, and the keyring itself may not be populated at PLAYER_LOGIN,
-- so we re-warm the cache and rescan a couple of times shortly after
-- login to catch keys that weren't resolvable on the first pass.
if RA._retryAt and GetTime then if RA._retryAt and GetTime then
local now = GetTime() local now = GetTime()
local pending = {} local pending = {}
@@ -473,22 +648,22 @@ function RA:_OnUpdate(elapsed)
end end
end end
-- Event-driven rescan: something changed, catch up quickly.
if RA._dirty and RA._throttle >= RA._minRescan then if RA._dirty and RA._throttle >= RA._minRescan then
RA._dirty = false RA._dirty = false
RA._throttle = 0 RA._throttle = 0
RA:ScanQuestLog() RA._periodic = 0
RA:ScanBags() RA:AutoScan()
RA:RecomputeAll()
if RA.UI and RA.UI.Refresh then RA.UI:Refresh() end
return return
end end
if RA._throttle >= interval then -- Unconditional periodic rescan so state stays fresh even if no events fire
-- (e.g. reputation ticks from mob kills, quest turn-ins on stock 1.12
-- without QUEST_TURNED_IN, background item pickups by other addons).
if RA._periodic >= interval then
RA._periodic = 0
RA._throttle = 0 RA._throttle = 0
RA:ScanQuestLog() RA:AutoScan()
RA:ScanBags()
RA:RecomputeAll()
if RA.UI and RA.UI.Refresh then RA.UI:Refresh() end
end end
end end
@@ -497,14 +672,12 @@ end
-- --------------------------------------------------------------- -- ---------------------------------------------------------------
local function slashHandler(msg) local function slashHandler(msg)
msg = string.lower(msg or "") msg = string.lower(msg or "")
-- trim
msg = string.gsub(msg, "^%s+", "") msg = string.gsub(msg, "^%s+", "")
msg = string.gsub(msg, "%s+$", "") msg = string.gsub(msg, "%s+$", "")
if msg == "show" then if msg == "show" then
if RA.UI then RA.UI:Show() end if RA.UI then RA.UI:Show() end
elseif msg == "hide" then elseif msg == "hide" then
-- If the window is open, /attune hide closes it; otherwise it hides the minimap button.
if RA.UI and RA.UI:IsShown() then if RA.UI and RA.UI:IsShown() then
RA.UI:Hide() RA.UI:Hide()
elseif RA.Minimap then elseif RA.Minimap then
@@ -521,6 +694,34 @@ local function slashHandler(msg)
print("Minimap button shown. Drag to reposition around the minimap.") print("Minimap button shown. Drag to reposition around the minimap.")
end end
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 elseif msg == "sync" then
if RA.Comm then RA.Comm:RequestSync() end if RA.Comm then RA.Comm:RequestSync() end
print(L.MSG_SYNC_SENT) print(L.MSG_SYNC_SENT)
@@ -563,13 +764,15 @@ local function slashHandler(msg)
end end
end end
pcall(function() tip:Hide() end) pcall(function() tip:Hide() end)
print("Scarlet Key (7146) owned: " .. tostring(RelationshipsAttuneCharDB.items[7146] == true))
elseif msg == "help" or msg == "?" then elseif msg == "help" or msg == "?" then
print("Relationships Attune - commands:") print("Relationships Attune - commands:")
print(" /attune - toggle the window") print(" /attune - toggle the window")
print(" /attune show - open the window") print(" /attune show - open the window")
print(" /attune hide - close the window (or hide the minimap button)") print(" /attune hide - close the window (or hide the minimap button)")
print(" /attune minimap - toggle 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 scan - force a rescan of quests + bags")
print(" /attune sync - request sync from your group") print(" /attune sync - request sync from your group")
print(" /attune debug - dump keyring + item detection") print(" /attune debug - dump keyring + item detection")
@@ -596,8 +799,15 @@ ef:RegisterEvent("BAG_UPDATE")
ef:RegisterEvent("PLAYER_LEVEL_UP") ef:RegisterEvent("PLAYER_LEVEL_UP")
ef:RegisterEvent("UPDATE_FACTION") ef:RegisterEvent("UPDATE_FACTION")
ef:RegisterEvent("CHAT_MSG_ADDON") ef:RegisterEvent("CHAT_MSG_ADDON")
-- These events don't exist on stock 1.12 but Turtle/Octo backports them.
-- pcall prevents a hard failure on builds that don't recognise them.
pcall(function() ef:RegisterEvent("QUEST_TURNED_IN") end) pcall(function() ef:RegisterEvent("QUEST_TURNED_IN") end)
pcall(function() ef:RegisterEvent("KEYRING_UPDATE") end) pcall(function() ef:RegisterEvent("KEYRING_UPDATE") end)
pcall(function() ef:RegisterEvent("UNIT_INVENTORY_CHANGED") end)
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)
ef:SetScript("OnEvent", function() ef:SetScript("OnEvent", function()
local e = event local e = event
@@ -610,9 +820,6 @@ ef:SetScript("OnEvent", function()
RA:BuildNameIndex() RA:BuildNameIndex()
RA:WarmItemCache() RA:WarmItemCache()
RA:ScanQuestLog(); RA:ScanBags(); RA:RecomputeAll() RA:ScanQuestLog(); RA:ScanBags(); RA:RecomputeAll()
-- Retry pass: on 1.12 GetItemInfo populates async, and the
-- keyring is not always populated at PLAYER_LOGIN. Schedule two
-- delayed rescans so keys still get credited without user action.
RA._retryAt = { GetTime() + 3, GetTime() + 10 } RA._retryAt = { GetTime() + 3, GetTime() + 10 }
if RA.Comm and RA.Comm.Init then RA.Comm:Init() end 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.UI and RA.UI.Init then RA.UI:Init() end
@@ -631,7 +838,12 @@ ef:SetScript("OnEvent", function()
or e == "BAG_UPDATE" or e == "BAG_UPDATE"
or e == "KEYRING_UPDATE" or e == "KEYRING_UPDATE"
or e == "PLAYER_LEVEL_UP" or e == "PLAYER_LEVEL_UP"
or e == "UPDATE_FACTION" then or e == "UPDATE_FACTION"
or e == "UNIT_INVENTORY_CHANGED"
or e == "ITEM_PUSH"
or e == "PLAYER_XP_UPDATE"
or e == "ZONE_CHANGED_NEW_AREA"
or e == "SKILL_LINES_CHANGED" then
if RA.initialized then RA:MarkDirty() end if RA.initialized then RA:MarkDirty() end
elseif e == "QUEST_TURNED_IN" then elseif e == "QUEST_TURNED_IN" then
+230 -250
View File
@@ -1,28 +1,31 @@
-- RelationshipsAttune: Data.lua -- RelationshipsAttune: Data.lua
-- Attunement definitions with icons + hover descriptions for the UI. -- Attunement definitions with icons + hover descriptions for the UI.
-- --
-- Sources: wowhead classic attunement guides (Onyxia, MC, BWL, Naxx, AQ40). -- Sources: wowhead classic guides + turtle-wow wiki for custom raids/keys.
-- Turtle/Octo custom raids keep placeholder ids until server confirms them. -- Turtle/Octo custom raids keep placeholder ids where the live server
-- hasn't been confirmed; the scanner also identifies items by tooltip
-- name so a name-match will still credit the step.
-- --
-- Each step: -- Each step:
-- { type = "level", level = 60, name = "...", icon = "..." } -- { type = "level", level = 60, name = "...", icon = "..." }
-- { type = "quest", id = <qid>, name = "...", icon = "...", -- { type = "quest", id = <qid>, name = "...", icon = "...",
-- faction = "Alliance"|"Horde"|nil, desc = "...", start = "..." } -- faction = "Alliance"|"Horde"|nil, desc = "...", start = "...",
-- final = true|nil }
-- { type = "item", id = <iid>, name = "...", icon = "...", desc = "...", start = "..." } -- { type = "item", id = <iid>, name = "...", icon = "...", desc = "...", start = "..." }
-- { type = "rep", faction = "Argent Dawn", standing = 5, name = "...", -- { type = "rep", faction = "Argent Dawn", standing = 5, name = "...",
-- desc = "...", start = "..." } -- desc = "...", start = "..." }
-- --
-- Standing: 1=Hostile ... 4=Neutral, 5=Honored, 6=Revered, 7=Exalted. -- Standing: 1=Hostile ... 4=Neutral, 5=Honored, 6=Revered, 7=Exalted.
-- final=true marks the "you are attuned" step so that if it's complete
-- the whole attunement is treated as done (used for quest-only chains
-- that have no key item at the end, e.g. Naxx/BWL/MC).
local ICON_QUEST = "Interface\\GossipFrame\\AvailableQuestIcon" local ICON_QUEST = "Interface\\GossipFrame\\AvailableQuestIcon"
local ICON_ACTIVE = "Interface\\GossipFrame\\ActiveQuestIcon" local ICON_ACTIVE = "Interface\\GossipFrame\\ActiveQuestIcon"
local ICON_KEY = "Interface\\Icons\\INV_Misc_Key_03" local ICON_KEY = "Interface\\Icons\\INV_Misc_Key_03"
local ICON_LEVEL = "Interface\\Icons\\Spell_Nature_EnchantArmor" local ICON_LEVEL = "Interface\\Icons\\Spell_Nature_EnchantArmor"
local ICON_REP = "Interface\\Icons\\Achievement_Reputation_01" local ICON_REP = "Interface\\Icons\\Achievement_Reputation_01"
local ICON_ITEM = "Interface\\Icons\\INV_Misc_QuestionMark" local ICON_ITEM = "Interface\\Icons\\INV_Misc_QuestionMark"
RelationshipsAttune_Data = { RelationshipsAttune_Data = {
---------------------------------------------------------------- ----------------------------------------------------------------
-- ONYXIA'S LAIR - full Drakefire Amulet chain (both factions) -- ONYXIA'S LAIR - full Drakefire Amulet chain (both factions)
---------------------------------------------------------------- ----------------------------------------------------------------
@@ -32,10 +35,8 @@ RelationshipsAttune_Data = {
steps = { steps = {
{ type = "level", level = 55, name = "Reach Level 55", icon = ICON_LEVEL, { type = "level", level = 55, name = "Reach Level 55", icon = ICON_LEVEL,
desc = "Minimum level to start either faction's Onyxia attunement chain." }, desc = "Minimum level to start either faction's Onyxia attunement chain." },
------------------------------------------------------------------ ------------------------------------------------------------------
-- ALLIANCE CHAIN (starts Burning Steppes -> Redridge -> Stormwind -- ALLIANCE CHAIN
-- -> BRD -> back to Stormwind -> Winterspring -> UBRS).
------------------------------------------------------------------ ------------------------------------------------------------------
{ type = "quest", id = 4182, faction = "Alliance", { type = "quest", id = 4182, faction = "Alliance",
name = "Dragonkin Menace", icon = ICON_QUEST, name = "Dragonkin Menace", icon = ICON_QUEST,
@@ -44,47 +45,47 @@ RelationshipsAttune_Data = {
{ type = "quest", id = 4183, faction = "Alliance", { type = "quest", id = 4183, faction = "Alliance",
name = "The True Masters (Helendis)", icon = ICON_QUEST, name = "The True Masters (Helendis)", icon = ICON_QUEST,
desc = "Deliver Helendis Riverhorn's Letter to Magistrate Solomon in Lakeshire.", desc = "Deliver Helendis Riverhorn's Letter to Magistrate Solomon in Lakeshire.",
start = "Helendis Riverhorn, Morgan's Vigil, Burning Steppes (86, 69)." }, start = "Helendis Riverhorn, Morgan's Vigil, Burning Steppes (86, 69). Solomon: Lakeshire town hall, Redridge (30, 44)." },
{ type = "quest", id = 4184, faction = "Alliance", { type = "quest", id = 4184, faction = "Alliance",
name = "The True Masters (Solomon -> Bolvar)", icon = ICON_QUEST, name = "The True Masters (Solomon -> Bolvar)", icon = ICON_QUEST,
desc = "Take Solomon's Plea to Highlord Bolvar Fordragon in Stormwind Keep.", desc = "Take Solomon's Plea to Highlord Bolvar Fordragon in Stormwind Keep.",
start = "Magistrate Solomon, Lakeshire town hall, Redridge Mountains (30, 44)." }, start = "Magistrate Solomon, Lakeshire town hall, Redridge Mountains (30, 44). Bolvar: Stormwind Keep throne room (78, 18)." },
{ type = "quest", id = 4185, faction = "Alliance", { type = "quest", id = 4185, faction = "Alliance",
name = "The True Masters (Prestor)", icon = ICON_QUEST, name = "The True Masters (Prestor)", icon = ICON_QUEST,
desc = "Speak with Lady Katrana Prestor, then return to Bolvar.", desc = "Speak with Lady Katrana Prestor, then return to Bolvar.",
start = "Highlord Bolvar Fordragon, Stormwind Keep throne room (78, 18)." }, start = "Highlord Bolvar Fordragon, Stormwind Keep throne room (78, 18). Prestor stands next to him." },
{ type = "quest", id = 4186, faction = "Alliance", { type = "quest", id = 4186, faction = "Alliance",
name = "The True Masters (Bolvar's Decree)", icon = ICON_QUEST, name = "The True Masters (Bolvar's Decree)", icon = ICON_QUEST,
desc = "Return Bolvar's Decree to Magistrate Solomon in Lakeshire.", desc = "Return Bolvar's Decree to Magistrate Solomon in Lakeshire.",
start = "Highlord Bolvar Fordragon, Stormwind Keep (78, 18)." }, start = "Highlord Bolvar Fordragon, Stormwind Keep (78, 18). Solomon: Lakeshire, Redridge (30, 44)." },
{ type = "quest", id = 4223, faction = "Alliance", { type = "quest", id = 4223, faction = "Alliance",
name = "The True Masters (Maxwell)", icon = ICON_QUEST, name = "The True Masters (Maxwell)", icon = ICON_QUEST,
desc = "Report to Marshal Maxwell at Morgan's Vigil.", desc = "Report to Marshal Maxwell at Morgan's Vigil.",
start = "Magistrate Solomon, Lakeshire, Redridge Mountains (30, 44)." }, start = "Magistrate Solomon, Lakeshire, Redridge Mountains (30, 44). Maxwell: Morgan's Vigil, Burning Steppes (85, 69)." },
{ type = "quest", id = 4224, faction = "Alliance", { type = "quest", id = 4224, faction = "Alliance",
name = "The True Masters (Ragged John)", icon = ICON_QUEST, name = "The True Masters (Ragged John)", icon = ICON_QUEST,
desc = "Find Ragged John in a cave north of Thaurissan, then return to Maxwell.", desc = "Find Ragged John in a cave north of Thaurissan, then return to Maxwell.",
start = "Marshal Maxwell, Morgan's Vigil, Burning Steppes (85, 69). Ragged John: cave at (65, 24)." }, start = "Marshal Maxwell, Morgan's Vigil, Burning Steppes (85, 69). Ragged John: cave at Burning Steppes (65, 24)." },
{ type = "quest", id = 4241, faction = "Alliance", { type = "quest", id = 4241, faction = "Alliance",
name = "Marshal Windsor", icon = ICON_QUEST, name = "Marshal Windsor", icon = ICON_QUEST,
desc = "Enter Blackrock Depths and find Marshal Windsor in his cell.", desc = "Enter Blackrock Depths and find Marshal Windsor in his cell.",
start = "Marshal Maxwell, Morgan's Vigil, Burning Steppes (85, 69). BRD: Blackrock Mountain." }, start = "Marshal Maxwell, Morgan's Vigil, Burning Steppes (85, 69). BRD entrance: Blackrock Mountain (~30, 37) in Burning Steppes." },
{ type = "quest", id = 4242, faction = "Alliance", { type = "quest", id = 4242, faction = "Alliance",
name = "Abandoned Hope", icon = ICON_QUEST, name = "Abandoned Hope", icon = ICON_QUEST,
desc = "Return the bad news from Windsor to Marshal Maxwell.", desc = "Return the bad news from Windsor to Marshal Maxwell.",
start = "Marshal Windsor, Windsor Cell, Blackrock Depths jail." }, start = "Marshal Windsor, Windsor Cell, Blackrock Depths jail (inside BRD, entrance at Blackrock Mountain ~30, 37, Burning Steppes)." },
{ type = "quest", id = 4264, faction = "Alliance", { type = "quest", id = 4264, faction = "Alliance",
name = "A Crumpled Up Note", icon = ICON_QUEST, name = "A Crumpled Up Note", icon = ICON_QUEST,
desc = "Random drop item from BRD slaves/mobs; return it to Windsor.", desc = "Random drop item from BRD slaves/mobs; return it to Windsor.",
start = "Loot A Crumpled Up Note from BRD slaves (outside instance) or trash inside." }, start = "Loot A Crumpled Up Note from BRD slaves (Shadowforge City) or trash inside Blackrock Depths (entrance Blackrock Mountain ~30, 37, Burning Steppes)." },
{ type = "quest", id = 4282, faction = "Alliance", { type = "quest", id = 4282, faction = "Alliance",
name = "A Shred of Hope", icon = ICON_QUEST, name = "A Shred of Hope", icon = ICON_QUEST,
desc = "Kill General Angerforge and Golem Lord Argelmach in BRD and loot Windsor's Lost Information from each.", desc = "Kill General Angerforge and Golem Lord Argelmach in BRD and loot Windsor's Lost Information from each.",
start = "Marshal Windsor, Windsor Cell, Blackrock Depths jail." }, start = "Marshal Windsor, Windsor Cell, Blackrock Depths jail (BRD entrance Blackrock Mountain ~30, 37, Burning Steppes)." },
{ type = "quest", id = 4322, faction = "Alliance", { type = "quest", id = 4322, faction = "Alliance",
name = "Jail Break!", icon = ICON_QUEST, name = "Jail Break!", icon = ICON_QUEST,
desc = "Escort Marshal Windsor out of BRD, then report to Marshal Maxwell.", desc = "Escort Marshal Windsor out of BRD, then report to Marshal Maxwell.",
start = "Marshal Windsor, Windsor Cell, Blackrock Depths jail." }, start = "Marshal Windsor, Windsor Cell, Blackrock Depths jail. Turn in to Maxwell, Morgan's Vigil, Burning Steppes (85, 69)." },
{ type = "quest", id = 6402, faction = "Alliance", { type = "quest", id = 6402, faction = "Alliance",
name = "Stormwind Rendezvous", icon = ICON_QUEST, name = "Stormwind Rendezvous", icon = ICON_QUEST,
desc = "Speak to Squire Rowe at the Stormwind gates to summon Reginald Windsor.", desc = "Speak to Squire Rowe at the Stormwind gates to summon Reginald Windsor.",
@@ -92,7 +93,7 @@ RelationshipsAttune_Data = {
{ type = "quest", id = 6403, faction = "Alliance", { type = "quest", id = 6403, faction = "Alliance",
name = "The Great Masquerade", icon = ICON_ACTIVE, name = "The Great Masquerade", icon = ICON_ACTIVE,
desc = "Escort Reginald Windsor to Stormwind Keep; unmask Lady Prestor as Onyxia.", desc = "Escort Reginald Windsor to Stormwind Keep; unmask Lady Prestor as Onyxia.",
start = "Reginald Windsor, Stormwind City gates (after Rendezvous)." }, start = "Reginald Windsor, Stormwind City gates (70, 86) after Rendezvous; escort ends in Stormwind Keep (78, 18)." },
{ type = "quest", id = 6501, faction = "Alliance", { type = "quest", id = 6501, faction = "Alliance",
name = "The Dragon's Eye", icon = ICON_QUEST, name = "The Dragon's Eye", icon = ICON_QUEST,
desc = "Take the Fragment of the Dragon's Eye to Haleh in Mazthoril, Winterspring.", desc = "Take the Fragment of the Dragon's Eye to Haleh in Mazthoril, Winterspring.",
@@ -100,12 +101,9 @@ RelationshipsAttune_Data = {
{ type = "quest", id = 6502, faction = "Alliance", { type = "quest", id = 6502, faction = "Alliance",
name = "Drakefire Amulet (Alliance)", icon = ICON_ACTIVE, name = "Drakefire Amulet (Alliance)", icon = ICON_ACTIVE,
desc = "Kill General Drakkisath in Upper Blackrock Spire and loot Blood of the Black Dragon Champion; return to Haleh.", desc = "Kill General Drakkisath in Upper Blackrock Spire and loot Blood of the Black Dragon Champion; return to Haleh.",
start = "Haleh, Mazthoril cave, Winterspring (55, 51)." }, start = "Haleh, Mazthoril cave, Winterspring (55, 51). UBRS entrance: Blackrock Mountain (~30, 37), Burning Steppes." },
------------------------------------------------------------------ ------------------------------------------------------------------
-- HORDE CHAIN (Kargath -> LBRS -> Orgrimmar -> UBRS -> Desolace -- HORDE CHAIN
-- -> Western Plaguelands -> Dustwallow -> Tanaris/Winterspring/
-- Swamp of Sorrows/Wetlands -> Desolace -> UBRS).
------------------------------------------------------------------ ------------------------------------------------------------------
{ type = "quest", id = 4903, faction = "Horde", { type = "quest", id = 4903, faction = "Horde",
name = "Warlord's Command", icon = ICON_QUEST, name = "Warlord's Command", icon = ICON_QUEST,
@@ -114,11 +112,11 @@ RelationshipsAttune_Data = {
{ type = "quest", id = 4941, faction = "Horde", { type = "quest", id = 4941, faction = "Horde",
name = "Eitrigg's Wisdom", icon = ICON_QUEST, name = "Eitrigg's Wisdom", icon = ICON_QUEST,
desc = "Speak to Eitrigg in Orgrimmar, then turn in to Thrall.", desc = "Speak to Eitrigg in Orgrimmar, then turn in to Thrall.",
start = "Warlord Goretooth, Kargath, Badlands (6, 48). Eitrigg/Thrall: Valley of Wisdom, Orgrimmar (32, 38)." }, start = "Warlord Goretooth, Kargath, Badlands (6, 48). Eitrigg/Thrall: Grommash Hold, Valley of Wisdom, Orgrimmar (32, 38)." },
{ type = "quest", id = 4974, faction = "Horde", { type = "quest", id = 4974, faction = "Horde",
name = "For The Horde!", icon = ICON_QUEST, name = "For The Horde!", icon = ICON_QUEST,
desc = "Kill Warchief Rend Blackhand in UBRS and return his head to Thrall (grants Warchief's Blessing).", desc = "Kill Warchief Rend Blackhand in UBRS and return his head to Thrall (grants Warchief's Blessing).",
start = "Thrall, Grommash Hold, Valley of Wisdom, Orgrimmar (32, 38)." }, start = "Thrall, Grommash Hold, Valley of Wisdom, Orgrimmar (32, 38). UBRS: Blackrock Mountain (~30, 37), Burning Steppes." },
{ type = "quest", id = 6566, faction = "Horde", { type = "quest", id = 6566, faction = "Horde",
name = "What the Wind Carries", icon = ICON_QUEST, name = "What the Wind Carries", icon = ICON_QUEST,
desc = "Listen to Thrall's tale and turn in.", desc = "Listen to Thrall's tale and turn in.",
@@ -126,15 +124,15 @@ RelationshipsAttune_Data = {
{ type = "quest", id = 6567, faction = "Horde", { type = "quest", id = 6567, faction = "Horde",
name = "The Champion of the Horde", icon = ICON_QUEST, name = "The Champion of the Horde", icon = ICON_QUEST,
desc = "Find Rexxar patrolling between Desolace, Stonetalon and Feralas.", desc = "Find Rexxar patrolling between Desolace, Stonetalon and Feralas.",
start = "Thrall, Grommash Hold, Orgrimmar (32, 38). Rexxar: Desolace patrol path." }, start = "Thrall, Grommash Hold, Orgrimmar (32, 38). Rexxar: Desolace patrol between (35, 10) and (65, 70)." },
{ type = "quest", id = 6568, faction = "Horde", { type = "quest", id = 6568, faction = "Horde",
name = "The Testament of Rexxar", icon = ICON_QUEST, name = "The Testament of Rexxar", icon = ICON_QUEST,
desc = "Deliver Rexxar's Testament to Myranda the Hag in Western Plaguelands.", desc = "Deliver Rexxar's Testament to Myranda the Hag in Western Plaguelands.",
start = "Rexxar, Desolace (roaming). Myranda: near Uther's Tomb, Western Plaguelands (51, 78)." }, start = "Rexxar, Desolace patrol (35-65, 10-70). Myranda: near Uther's Tomb, Western Plaguelands (51, 78)." },
{ type = "quest", id = 6569, faction = "Horde", { type = "quest", id = 6569, faction = "Horde",
name = "Oculus Illusions", icon = ICON_QUEST, name = "Oculus Illusions", icon = ICON_QUEST,
desc = "Collect 20 Black Dragonspawn Eyes from UBRS dragonkin for your disguise.", desc = "Collect 20 Black Dragonspawn Eyes from UBRS dragonkin for your disguise.",
start = "Myranda the Hag, Western Plaguelands (51, 78)." }, start = "Myranda the Hag, Western Plaguelands (51, 78). UBRS: Blackrock Mountain (~30, 37), Burning Steppes." },
{ type = "quest", id = 6570, faction = "Horde", { type = "quest", id = 6570, faction = "Horde",
name = "Emberstrife", icon = ICON_QUEST, name = "Emberstrife", icon = ICON_QUEST,
desc = "Use Amulet of Draconic Subversion and speak to Emberstrife in his den, Dustwallow Marsh.", desc = "Use Amulet of Draconic Subversion and speak to Emberstrife in his den, Dustwallow Marsh.",
@@ -146,7 +144,7 @@ RelationshipsAttune_Data = {
{ type = "quest", id = 6582, faction = "Horde", { type = "quest", id = 6582, faction = "Horde",
name = "The Test of Skulls, Scryer", icon = ICON_QUEST, name = "The Test of Skulls, Scryer", icon = ICON_QUEST,
desc = "Kill Scryer (Blue) inside Mazthoril cave, Winterspring.", desc = "Kill Scryer (Blue) inside Mazthoril cave, Winterspring.",
start = "Emberstrife, Emberstrife's Den, Dustwallow Marsh (57, 87). Scryer: Mazthoril (53, 56)." }, start = "Emberstrife, Emberstrife's Den, Dustwallow Marsh (57, 87). Scryer: Mazthoril, Winterspring (53, 56)." },
{ type = "quest", id = 6583, faction = "Horde", { type = "quest", id = 6583, faction = "Horde",
name = "The Test of Skulls, Somnus", icon = ICON_QUEST, name = "The Test of Skulls, Somnus", icon = ICON_QUEST,
desc = "Kill Somnus (Green) patrolling south-east Swamp of Sorrows.", desc = "Kill Somnus (Green) patrolling south-east Swamp of Sorrows.",
@@ -154,26 +152,24 @@ RelationshipsAttune_Data = {
{ type = "quest", id = 6585, faction = "Horde", { type = "quest", id = 6585, faction = "Horde",
name = "The Test of Skulls, Axtroz", icon = ICON_QUEST, name = "The Test of Skulls, Axtroz", icon = ICON_QUEST,
desc = "Unlocks after the other three Skull quests. Kill Axtroz (Red) at Grim Batol, Wetlands.", desc = "Unlocks after the other three Skull quests. Kill Axtroz (Red) at Grim Batol, Wetlands.",
start = "Emberstrife, Emberstrife's Den, Dustwallow Marsh (57, 87). Axtroz: Wetlands (85, 49)." }, start = "Emberstrife, Emberstrife's Den, Dustwallow Marsh (57, 87). Axtroz: Grim Batol entrance, Wetlands (85, 49)." },
{ type = "quest", id = 6601, faction = "Horde", { type = "quest", id = 6601, faction = "Horde",
name = "Ascension...", icon = ICON_QUEST, name = "Ascension...", icon = ICON_QUEST,
desc = "Take the Dull Drakefire Amulet to Rexxar in Desolace (not to Drakkisath).", desc = "Take the Dull Drakefire Amulet to Rexxar in Desolace (not to Drakkisath).",
start = "Emberstrife, Emberstrife's Den, Dustwallow Marsh (57, 87). Rexxar: Desolace patrol." }, start = "Emberstrife, Emberstrife's Den, Dustwallow Marsh (57, 87). Rexxar: Desolace patrol (35-65, 10-70)." },
{ type = "quest", id = 6602, faction = "Horde", { type = "quest", id = 6602, faction = "Horde",
name = "Blood of the Black Dragon Champion", icon = ICON_ACTIVE, name = "Blood of the Black Dragon Champion", icon = ICON_ACTIVE,
desc = "Kill General Drakkisath in UBRS, loot his blood and return to Rexxar for the Drakefire Amulet.", desc = "Kill General Drakkisath in UBRS, loot his blood and return to Rexxar for the Drakefire Amulet.",
start = "Rexxar, Desolace patrol path." }, start = "Rexxar, Desolace patrol (35-65, 10-70). UBRS: Blackrock Mountain (~30, 37), Burning Steppes." },
------------------------------------------------------------------ ------------------------------------------------------------------
-- Final reward (both factions). -- Final reward (both factions).
------------------------------------------------------------------ ------------------------------------------------------------------
{ type = "item", id = 16309, name = "Drakefire Amulet", { type = "item", id = 16309, name = "Drakefire Amulet",
icon = "Interface\\Icons\\INV_Jewelry_Talisman_11", icon = "Interface\\Icons\\INV_Jewelry_Talisman_11",
desc = "Must be in every raid member's bags (not bank) to enter Onyxia's Lair.", desc = "Must be in every raid member's bags (not bank) to enter Onyxia's Lair.",
start = "Awarded by Haleh (Alliance, Winterspring) or Rexxar (Horde, Desolace patrol)." }, start = "Awarded by Haleh (Alliance, Mazthoril, Winterspring 55, 51) or Rexxar (Horde, Desolace patrol 35-65, 10-70)." },
}, },
}, },
---------------------------------------------------------------- ----------------------------------------------------------------
-- MOLTEN CORE -- MOLTEN CORE
---------------------------------------------------------------- ----------------------------------------------------------------
@@ -182,19 +178,11 @@ RelationshipsAttune_Data = {
icon = "Interface\\Icons\\Spell_Fire_SelfDestruct", icon = "Interface\\Icons\\Spell_Fire_SelfDestruct",
steps = { steps = {
{ type = "level", level = 55, name = "Reach Level 55", icon = ICON_LEVEL }, { type = "level", level = 55, name = "Reach Level 55", icon = ICON_LEVEL },
{ type = "item", id = 17203, name = "Sulfuron Ingot (optional binder)", { type = "quest", id = 7487, name = "Attunement to the Core", icon = ICON_ACTIVE, final = true,
icon = "Interface\\Icons\\INV_Ingot_08", desc = "Fight through Blackrock Depths to the Molten Bridge and loot a Core Fragment from the elementals near the MC portal, then hand it to Lothos Riftwaker. Grants direct teleport into MC from the BRD entrance instance portal.",
desc = "Not required for attunement itself, but drops in MC; listed for group planning.", start = "Lothos Riftwaker, BRD instance entrance platform inside Blackrock Mountain (~30, 37), Burning Steppes, next to the Molten Core portal." },
start = "Trash drops throughout Molten Core." },
{ type = "quest", id = 7487, name = "The Molten Core (BRD group summon)", icon = ICON_QUEST,
desc = "Group prereq that starts the Attunement chain via Lothos Riftwaker.",
start = "Lothos Riftwaker, Blackrock Mountain (top of chained platform near the MC portal)." },
{ type = "quest", id = 7848, name = "Attunement to the Core", icon = ICON_ACTIVE,
desc = "Turn in a Core Fragment (from Molten Core mobs) to Lothos Riftwaker; grants direct teleport into MC from BRD.",
start = "Lothos Riftwaker, Blackrock Mountain (BRD entrance platform)." },
}, },
}, },
---------------------------------------------------------------- ----------------------------------------------------------------
-- BLACKWING LAIR -- BLACKWING LAIR
---------------------------------------------------------------- ----------------------------------------------------------------
@@ -205,13 +193,12 @@ RelationshipsAttune_Data = {
{ type = "level", level = 58, name = "Reach Level 58", icon = ICON_LEVEL }, { type = "level", level = 58, name = "Reach Level 58", icon = ICON_LEVEL },
{ type = "item", id = 12344, name = "Seal of Ascension (UBRS key prereq)", icon = ICON_KEY, { type = "item", id = 12344, name = "Seal of Ascension (UBRS key prereq)", icon = ICON_KEY,
desc = "You must be able to enter UBRS to reach Vaelan; requires the Seal of Ascension chain.", desc = "You must be able to enter UBRS to reach Vaelan; requires the Seal of Ascension chain.",
start = "Chain starts with Kibler at Flame Crest, Burning Steppes (62, 24), and Jorgen at Morgan's Vigil (85, 68)." }, start = "Jorgen at Morgan's Vigil, Burning Steppes (85, 68) points you to Vaelan in UBRS (Blackrock Mountain ~30, 37, Burning Steppes)." },
{ type = "quest", id = 7761, name = "Blackhand's Command", icon = ICON_ACTIVE, { type = "quest", id = 7761, name = "Blackhand's Command", icon = ICON_ACTIVE, final = true,
desc = "Read the scroll dropped by Vaelan behind the hidden door in UBRS, then use the orb at the top of UBRS to enter BWL.", desc = "Read the scroll dropped by Vaelan behind the hidden door in UBRS, then use the orb at the top of UBRS to enter BWL.",
start = "Vaelan, hidden alcove past the UBRS orb room, Upper Blackrock Spire." }, start = "Vaelan, hidden alcove past the UBRS orb room, Upper Blackrock Spire (Blackrock Mountain ~30, 37, Burning Steppes)." },
}, },
}, },
---------------------------------------------------------------- ----------------------------------------------------------------
-- NAXXRAMAS -- NAXXRAMAS
---------------------------------------------------------------- ----------------------------------------------------------------
@@ -224,18 +211,17 @@ RelationshipsAttune_Data = {
name = "Honored with the Argent Dawn", icon = ICON_REP, 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 reduces the gold cost (60g Honored, 40g Revered, 20g Exalted).",
start = "Turn in Scourgestones / Minion's Scourgestones to Argent Dawn quartermasters at Light's Hope Chapel, Eastern Plaguelands (76, 53)." }, 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, { 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.", desc = "Pay 60g to Angela Dosantos - available at Honored with Argent Dawn.",
start = "Archmage Angela Dosantos, Light's Hope Chapel, Eastern Plaguelands (76, 53)." }, 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, { 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.", desc = "Discounted Naxx attunement offered at Revered with Argent Dawn.",
start = "Archmage Angela Dosantos, Light's Hope Chapel, Eastern Plaguelands (76, 53)." }, 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, { 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.", desc = "Cheapest Naxx attunement, offered only at Exalted with Argent Dawn.",
start = "Archmage Angela Dosantos, Light's Hope Chapel, Eastern Plaguelands (76, 53)." }, start = "Archmage Angela Dosantos, Light's Hope Chapel, Eastern Plaguelands (76, 53)." },
}, },
}, },
---------------------------------------------------------------- ----------------------------------------------------------------
-- AQ20 - Ruins of Ahn'Qiraj -- AQ20 - Ruins of Ahn'Qiraj
---------------------------------------------------------------- ----------------------------------------------------------------
@@ -248,15 +234,13 @@ RelationshipsAttune_Data = {
name = "Neutral with Cenarion Circle", icon = ICON_REP, name = "Neutral with Cenarion Circle", icon = ICON_REP,
desc = "Automatic from Silithus mobs after the war effort opens; no separate attunement item.", desc = "Automatic from Silithus mobs after the war effort opens; no separate attunement item.",
start = "Windcaller Yessendra / Proudhorn, Cenarion Hold, Silithus (50, 33)." }, start = "Windcaller Yessendra / Proudhorn, Cenarion Hold, Silithus (50, 33)." },
{ type = "quest", id = 8743, name = "The Gates of Ahn'Qiraj (server-wide event)", icon = ICON_QUEST, { type = "quest", id = 8743, name = "The Gates of Ahn'Qiraj (server-wide event)", icon = ICON_QUEST, final = true,
desc = "The AQ gates must have been opened server-side. Once open, anyone can walk in.", desc = "The AQ gates must have been opened server-side. Once open, anyone can walk in.",
start = "Anachronos, Caverns of Time entrance, Tanaris (65, 50)." }, start = "Anachronos, Caverns of Time entrance, Tanaris (65, 50). AQ gates: southern Silithus (28, 92)." },
}, },
}, },
---------------------------------------------------------------- ----------------------------------------------------------------
-- AQ40 - Temple of Ahn'Qiraj (Scepter of the Shifting Sands) -- AQ40 - Temple of Ahn'Qiraj (Scepter of the Shifting Sands)
-- Chain sourced from CixiDelmont's Attune addon (AttuneData id=10).
---------------------------------------------------------------- ----------------------------------------------------------------
{ {
id = "aq40", name = "Temple of Ahn'Qiraj (40)", category = "Vanilla", faction = "Both", id = "aq40", name = "Temple of Ahn'Qiraj (40)", category = "Vanilla", faction = "Both",
@@ -265,17 +249,17 @@ RelationshipsAttune_Data = {
{ type = "level", level = 60, name = "Reach Level 60", icon = ICON_LEVEL }, { type = "level", level = 60, name = "Reach Level 60", icon = ICON_LEVEL },
{ type = "quest", id = 8286, name = "What Tomorrow Brings", icon = ICON_QUEST, { type = "quest", id = 8286, name = "What Tomorrow Brings", icon = ICON_QUEST,
desc = "Speak with Anachronos at the Caverns of Time to begin the Scepter chain.", desc = "Speak with Anachronos at the Caverns of Time to begin the Scepter chain.",
start = "Anachronos, Caverns of Time entrance, Tanaris (65, 50). Talk to him at the Gates of Ahn'Qiraj first (Silithus)." }, start = "Anachronos, Caverns of Time entrance, Tanaris (65, 50). Talk to him at the Gates of Ahn'Qiraj first, Silithus (28, 92)." },
{ type = "quest", id = 8288, name = "Only One May Rise", icon = ICON_QUEST, { type = "quest", id = 8288, name = "Only One May Rise", icon = ICON_QUEST,
desc = "Kill Vaelastrasz in Blackwing Lair and loot the Vessel of Rebirth.", desc = "Kill Vaelastrasz in Blackwing Lair and loot the Vessel of Rebirth.",
start = "Anachronos, Silithus / Caverns of Time." }, start = "Anachronos, Silithus AQ gates (28, 92) / Caverns of Time, Tanaris (65, 50). BWL: Blackrock Mountain (~30, 37), Burning Steppes." },
{ type = "quest", id = 8301, name = "The Path of the Righteous", icon = ICON_QUEST, { type = "quest", id = 8301, name = "The Path of the Righteous", icon = ICON_QUEST,
desc = "Continue Anachronos' story after handing in the Vessel.", desc = "Continue Anachronos' story after handing in the Vessel.",
start = "Anachronos, Caverns of Time entrance, Tanaris (65, 50)." }, start = "Anachronos, Caverns of Time entrance, Tanaris (65, 50)." },
{ type = "rep", faction = "Brood of Nozdormu", standing = 4, { type = "rep", faction = "Brood of Nozdormu", standing = 4,
name = "Neutral with Brood of Nozdormu", icon = ICON_REP, name = "Neutral with Brood of Nozdormu", icon = ICON_REP,
desc = "Kill AQ40 trash / bosses to raise Nozdormu rep from Hated to Neutral; required before Anachronos will continue.", desc = "Kill AQ40 trash / bosses to raise Nozdormu rep from Hated to Neutral; required before Anachronos will continue.",
start = "Kill Qiraji inside AQ40; automatic once AQ40 is open." }, start = "Kill Qiraji inside AQ40, southern Silithus (28, 92); automatic once AQ40 is open." },
{ type = "quest", id = 8303, name = "Chamber of the Aspects", icon = ICON_QUEST, { type = "quest", id = 8303, name = "Chamber of the Aspects", icon = ICON_QUEST,
desc = "Anachronos sends you to speak with the four dragon Aspect representatives.", desc = "Anachronos sends you to speak with the four dragon Aspect representatives.",
start = "Anachronos, Caverns of Time entrance, Tanaris (65, 50)." }, start = "Anachronos, Caverns of Time entrance, Tanaris (65, 50)." },
@@ -287,90 +271,130 @@ RelationshipsAttune_Data = {
start = "Anachronos, Caverns of Time entrance, Tanaris (65, 50)." }, start = "Anachronos, Caverns of Time entrance, Tanaris (65, 50)." },
{ type = "quest", id = 8555, name = "The Nightmare's Corruption (Red)", icon = ICON_QUEST, { type = "quest", id = 8555, name = "The Nightmare's Corruption (Red)", icon = ICON_QUEST,
desc = "Kill Vaelastrasz (BWL), loot Vaelastrasz's Tooth for Caelestrasz.", desc = "Kill Vaelastrasz (BWL), loot Vaelastrasz's Tooth for Caelestrasz.",
start = "Caelestrasz, Anachronos' platform, Caverns of Time." }, start = "Caelestrasz, Anachronos' platform, Caverns of Time, Tanaris (65, 50)." },
{ type = "quest", id = 8733, name = "Beyond the Grave (Green)", icon = ICON_QUEST, { type = "quest", id = 8733, name = "Beyond the Grave (Green)", icon = ICON_QUEST,
desc = "Investigate the pool inside The Temple of Atal'Hakkar for Merithra.", desc = "Investigate the pool inside The Temple of Atal'Hakkar for Merithra.",
start = "Merithra of the Dream, Caverns of Time." }, start = "Merithra of the Dream, Anachronos' platform, Caverns of Time, Tanaris (65, 50). Sunken Temple: Swamp of Sorrows (69, 53)." },
{ type = "quest", id = 8734, name = "Draconic for Dummies (Blue)", icon = ICON_QUEST, { type = "quest", id = 8734, name = "Draconic for Dummies (Blue)", icon = ICON_QUEST,
desc = "Arygos sends you to research Nightmare Scale fragments across Azeroth.", desc = "Arygos sends you to research Nightmare Scale fragments across Azeroth.",
start = "Arygos, Caverns of Time." }, start = "Arygos, Anachronos' platform, Caverns of Time, Tanaris (65, 50)." },
{ type = "quest", id = 8735, name = "Stones of Binding (Bronze)", icon = ICON_QUEST, { type = "quest", id = 8735, name = "Stones of Binding (Bronze)", icon = ICON_QUEST,
desc = "Retrieve five Nightmare Engravings from world dragons (Ysondre, Emeriss, Lethon, Taerar and Azuregos) for Kalecgos.", desc = "Retrieve five Nightmare Engravings from world dragons (Ysondre, Emeriss, Lethon, Taerar and Azuregos) for Kalecgos.",
start = "Kalecgos, Caverns of Time." }, start = "Kalecgos, Anachronos' platform, Caverns of Time, Tanaris (65, 50). Azuregos: Azshara (56, 42)." },
{ type = "quest", id = 8736, name = "Preparations", icon = ICON_QUEST, { type = "quest", id = 8736, name = "Preparations", icon = ICON_QUEST,
desc = "Return to Anachronos with the Nightmare Scale.", desc = "Return to Anachronos with the Nightmare Scale.",
start = "Anachronos, Caverns of Time." }, start = "Anachronos, Caverns of Time entrance, Tanaris (65, 50)." },
{ type = "quest", id = 8741, name = "The Charge of the Dragonflights", icon = ICON_QUEST, { type = "quest", id = 8741, name = "The Charge of the Dragonflights", icon = ICON_QUEST,
desc = "The Aspects reforge the scepter. Deliver the Fragment of the Nightmare's Corruption.", desc = "The Aspects reforge the scepter. Deliver the Fragment of the Nightmare's Corruption.",
start = "Anachronos, Caverns of Time." }, start = "Anachronos, Caverns of Time entrance, Tanaris (65, 50)." },
{ type = "quest", id = 8742, name = "The Hand of the Righteous", icon = ICON_QUEST, { type = "quest", id = 8742, name = "The Hand of the Righteous", icon = ICON_QUEST,
desc = "Final scepter reforging quest. Rewards Scepter of the Shifting Sands.", desc = "Final scepter reforging quest. Rewards Scepter of the Shifting Sands.",
start = "Anachronos, Caverns of Time." }, start = "Anachronos, Caverns of Time entrance, Tanaris (65, 50)." },
{ type = "item", id = 21175, name = "Scepter of the Shifting Sands", { type = "item", id = 21175, name = "Scepter of the Shifting Sands",
icon = "Interface\\Icons\\INV_Hammer_25", icon = "Interface\\Icons\\INV_Hammer_25",
desc = "Use this item at the Scarab Gong outside AQ to trigger the 10-hour war and open the gates server-wide.", desc = "Use this item at the Scarab Gong outside AQ to trigger the 10-hour war and open the gates server-wide.",
start = "Reward from 'The Hand of the Righteous'." }, start = "Reward from 'The Hand of the Righteous' (Anachronos, Caverns of Time, Tanaris 65, 50)." },
{ type = "quest", id = 8743, name = "Bang a Gong!", icon = ICON_ACTIVE, { type = "quest", id = 8743, name = "Bang a Gong!", icon = ICON_ACTIVE, final = true,
desc = "Ring the Scarab Gong at the Gates of Ahn'Qiraj to open both AQ raids server-wide. First person to ring gets Scarab Lord title + mount.", desc = "Ring the Scarab Gong at the Gates of Ahn'Qiraj to open both AQ raids server-wide. First person to ring gets Scarab Lord title + mount.",
start = "Scarab Gong, Gates of Ahn'Qiraj, Silithus." }, start = "Scarab Gong, Gates of Ahn'Qiraj, southern Silithus (28, 92)." },
{ type = "item", id = 21176, name = "Black Qiraji Resonating Crystal (Scarab Lord mount)", { type = "item", id = 21176, name = "Black Qiraji Resonating Crystal (Scarab Lord mount)",
icon = "Interface\\Icons\\INV_Misc_QirajiCrystal_04", icon = "Interface\\Icons\\INV_Misc_QirajiCrystal_04",
desc = "Awarded only to the player who rings the gong within the 10-hour war window.", desc = "Awarded only to the player who rings the gong within the 10-hour war window.",
start = "Reward for completing 'Bang a Gong!' during the war event." }, start = "Reward for completing 'Bang a Gong!' at the AQ gates, Silithus (28, 92) during the war event." },
{ type = "quest", id = 8745, name = "Ouro's Message (post-war attunement)", icon = ICON_QUEST,
desc = "Anyone can walk into AQ40 once the gates are open; no personal attunement item required after the event.",
start = "Anachronos - concludes the Scepter chain." },
}, },
}, },
---------------------------------------------------------------- ----------------------------------------------------------------
-- VANILLA KEYS & DUNGEON ACCESS -- VANILLA KEYS & DUNGEON ACCESS
---------------------------------------------------------------- ----------------------------------------------------------------
{ {
id = "ubrs", name = "Upper Blackrock Spire", category = "Keys & Access", faction = "Both", id = "ubrs", name = "Upper Blackrock Spire (Seal)", category = "Keys & Access", faction = "Both",
icon = "Interface\\Icons\\INV_Misc_Key_04", icon = "Interface\\Icons\\INV_Misc_Key_04",
steps = { steps = {
{ type = "level", level = 55, name = "Reach Level 55", icon = ICON_LEVEL }, { type = "level", level = 55, name = "Reach Level 55", icon = ICON_LEVEL },
{ type = "quest", id = 4903, name = "Kibler's Exotic Pets (chain start)", icon = ICON_QUEST, { type = "item", id = 12219, name = "Unadorned Seal of Ascension", icon = ICON_KEY,
desc = "Opens Jorgen's Seal of Ascension chain.", desc = "Opens the UBRS door at the top of LBRS. Drops from The Beast in UBRS, Overlord Wyrmthalak in LBRS, and Pyroguard Emberseer, and is provided by Vaelan for the attunement chain.",
start = "Kibler, Flame Crest, Burning Steppes (62, 24)." }, start = "Loot from LBRS/UBRS bosses, or given directly by Vaelan (hidden alcove past the orb room in UBRS, Blackrock Mountain ~30, 37, Burning Steppes)." },
{ type = "quest", id = 4743, name = "Seal of Ascension", icon = ICON_QUEST, { type = "quest", id = 4742, name = "Seal of Ascension", icon = ICON_QUEST,
desc = "Requires the Unadorned Seal of Ascension (drops in LBRS) plus three gemstones from LBRS bosses.", desc = "Collect the three Gemstones of Command (Smolderthorn from War Master Voone, Spirestone from Highlord Omokk, Bloodaxe from Overlord Wyrmthalak - all in LBRS) and return them with the Unadorned Seal of Ascension to Vaelan.",
start = "Jorgen, Morgan's Vigil, Burning Steppes (85, 68)." }, start = "Vaelan, hidden alcove past the orb room, Upper Blackrock Spire (Blackrock Mountain ~30, 37, Burning Steppes)." },
{ type = "item", id = 12344, name = "Seal of Ascension", icon = ICON_KEY, { type = "item", id = 12344, name = "Seal of Ascension", icon = ICON_KEY, final = true,
desc = "Opens the door to Upper Blackrock Spire at the top of the LBRS instance.", desc = "Uses the orb at the top of UBRS to open the instance portal. Required to enter UBRS.",
start = "Awarded by Jorgen at Morgan's Vigil after completing the Seal of Ascension chain." }, start = "Reward from Vaelan for turning in 'Seal of Ascension' inside UBRS (Blackrock Mountain ~30, 37, Burning Steppes)." },
}, },
}, },
{ {
id = "brd", name = "Blackrock Depths", category = "Keys & Access", faction = "Both", id = "brd", name = "Blackrock Depths (Shadowforge Key)", category = "Keys & Access", faction = "Both",
icon = "Interface\\Icons\\INV_Misc_Key_06", icon = "Interface\\Icons\\INV_Misc_Key_06",
steps = { steps = {
{ type = "level", level = 47, name = "Reach Level 47", icon = ICON_LEVEL }, { type = "level", level = 47, name = "Reach Level 47", icon = ICON_LEVEL },
{ type = "quest", id = 4136, name = "Dark Iron Legacy", icon = ICON_QUEST, { type = "quest", id = 3802, name = "Dark Iron Legacy", icon = ICON_QUEST,
desc = "Rewards the Shadowforge Key for opening the deeper doors in BRD.", desc = "Kill Fineous Darkvire in the Hall of Crafting inside BRD and loot Ironfel, then place Ironfel on Franclorn Forgewright's statue at the Shrine of Thaurissan. Turn-in rewards the Shadowforge Key.",
start = "Franclorn Forgewright's ghost, inside Blackrock Depths (Detention Block hallway near his statue)." }, start = "Franclorn Forgewright's ghost at the Shrine of Thaurissan inside Blackrock Depths (BRD entrance: Blackrock Mountain ~30, 37, Burning Steppes). Accept 3801 first, then 3802." },
{ type = "item", id = 11000, name = "Shadowforge Key", icon = ICON_KEY, { type = "item", id = 11000, name = "Shadowforge Key", icon = ICON_KEY,
desc = "Opens Shadowforge City / Manufactory doors deep in BRD.", desc = "Opens the Shadowforge Lock and every Dark Iron door in the deeper half of BRD.",
start = "Reward from 'Dark Iron Legacy'." }, start = "Reward from completing 'Dark Iron Legacy' (quest 3802) inside BRD (Blackrock Mountain ~30, 37, Burning Steppes)." },
}, },
}, },
{ {
id = "scholo", name = "Scholomance", category = "Keys & Access", faction = "Both", id = "brd_prison", name = "BRD Prison Cell Key", category = "Keys & Access", faction = "Both",
icon = "Interface\\Icons\\INV_Misc_Key_05",
steps = {
{ type = "level", level = 52, name = "Reach Level 52", icon = ICON_LEVEL },
{ type = "item", id = 11140, name = "Prison Cell Key", icon = ICON_KEY,
desc = "Unlocks the Detention Block cells in Blackrock Depths (frees Marshal Windsor, Dughal Stormwing, Kharan Mighthammer, etc). Rogues can also pick these locks around 250 lockpicking.",
start = "Drops from Warder Stilgiss (patrolling the Detention Block corridor with his frost hound Frostwrath, inside Blackrock Depths - entrance Blackrock Mountain ~30, 37, Burning Steppes)." },
},
},
{
id = "scholo", name = "Scholomance (Skeleton Key)", category = "Keys & Access", faction = "Both",
icon = "Interface\\Icons\\INV_Misc_Key_11", icon = "Interface\\Icons\\INV_Misc_Key_11",
steps = { steps = {
{ type = "level", level = 55, name = "Reach Level 55", icon = ICON_LEVEL }, { type = "level", level = 55, name = "Reach Level 55", icon = ICON_LEVEL },
{ type = "quest", id = 5382, name = "The Key to Scholomance", icon = ICON_QUEST, { type = "quest", id = 5537, faction = "Alliance",
desc = "Requires Blood of Innocents (Scarlet Monastery), Viewing Room Key (Scholomance) and Deed to Caer Darrow.", name = "Skeletal Fragments (Alliance)", icon = ICON_QUEST,
start = "Alliance: Alexi Barov, Hearthglen, Western Plaguelands (44, 21). Horde: Alexi Barov, Ruins of Andorhal (42, 68)." }, desc = "Collect 15 Skeletal Fragments from the reanimated skeletons around Andorhal / Sorrow Hill for the Alchemist to imbue.",
{ type = "item", id = 13704, name = "Skeleton Key", icon = ICON_KEY, start = "Alchemist Arbington, Chillwind Camp, Western Plaguelands (43, 84)." },
desc = "Opens the front door of Scholomance in Caer Darrow.", { type = "quest", id = 5538, faction = "Horde",
start = "Reward from 'The Key to Scholomance'." }, name = "Skeletal Fragments (Horde)", icon = ICON_QUEST,
desc = "Horde version of the fragment collection quest for the Skeleton Key.",
start = "Apothecary Dithers, The Bulwark, Western Plaguelands (81, 71)." },
{ type = "quest", id = 5514, faction = "Alliance",
name = "Mold Rhymes With... (Alliance)", icon = ICON_QUEST,
desc = "Bring the Imbued Skeletal Fragments and 15 gold to Krinkle Goodsteel in Gadgetzan to buy the Skeleton Key Mold.",
start = "Alchemist Arbington, Chillwind Camp, Western Plaguelands (43, 84). Krinkle: Gadgetzan, Tanaris (51, 29)." },
{ type = "quest", id = 5515, faction = "Horde",
name = "Mold Rhymes With... (Horde)", icon = ICON_QUEST,
desc = "Horde variant: bring the Imbued Skeletal Fragments and 15 gold to Krinkle Goodsteel in Gadgetzan.",
start = "Apothecary Dithers, The Bulwark, Western Plaguelands (81, 71). Krinkle: Gadgetzan, Tanaris (51, 29)." },
{ type = "quest", id = 5801, faction = "Alliance",
name = "Fire Plume Forged (Alliance)", icon = ICON_QUEST,
desc = "Use the Skeleton Key Mold plus 2 Thorium Bars at the lava lake atop Fire Plume Ridge in Un'Goro Crater to cast the Unfinished Skeleton Key.",
start = "Krinkle Goodsteel, Gadgetzan, Tanaris (51, 29). Fire Plume Ridge: Un'Goro Crater (49, 60)." },
{ type = "quest", id = 5802, faction = "Horde",
name = "Fire Plume Forged (Horde)", icon = ICON_QUEST,
desc = "Horde variant: forge the Unfinished Skeleton Key at Fire Plume Ridge in Un'Goro Crater.",
start = "Krinkle Goodsteel, Gadgetzan, Tanaris (51, 29). Fire Plume Ridge: Un'Goro Crater (49, 60)." },
{ type = "quest", id = 5803, faction = "Alliance",
name = "Araj's Scarab (Alliance)", icon = ICON_QUEST,
desc = "Slay Araj the Summoner (elite lich, center of ruined Andorhal) and loot Araj's Scarab - the head of the key. Group quest.",
start = "Alchemist Arbington, Chillwind Camp, Western Plaguelands (43, 84). Araj: center of Andorhal (42, 68)." },
{ type = "quest", id = 5804, faction = "Horde",
name = "Araj's Scarab (Horde)", icon = ICON_QUEST,
desc = "Horde variant of the Araj kill quest.",
start = "Apothecary Dithers, The Bulwark, Western Plaguelands (81, 71). Araj: center of Andorhal (42, 68)." },
{ type = "quest", id = 5505, faction = "Alliance",
name = "The Key to Scholomance (Alliance)", icon = ICON_ACTIVE,
desc = "Turn in Araj's Scarab and receive the Skeleton Key.",
start = "Alchemist Arbington, Chillwind Camp, Western Plaguelands (43, 84)." },
{ type = "quest", id = 5506, faction = "Horde",
name = "The Key to Scholomance (Horde)", icon = ICON_ACTIVE,
desc = "Horde turn-in for the Skeleton Key.",
start = "Apothecary Dithers, The Bulwark, Western Plaguelands (81, 71)." },
{ type = "item", id = 13704, name = "Skeleton Key", icon = ICON_KEY, final = true,
desc = "Opens the front door of Scholomance on Caer Darrow. Rogues can pick the door at 280 lockpicking, so the key is optional but persistent.",
start = "Reward from the final 'The Key to Scholomance' turn-in (Chillwind Camp 43, 84 Alliance / The Bulwark 81, 71 Horde, Western Plaguelands). Scholomance door: Caer Darrow, Western Plaguelands (69, 73)." },
}, },
}, },
{ {
id = "strat_live", name = "Stratholme (Service Entrance)", category = "Keys & Access", faction = "Both", id = "strat_live", name = "Stratholme (Service Entrance)", category = "Keys & Access", faction = "Both",
icon = "Interface\\Icons\\INV_Misc_Key_07", icon = "Interface\\Icons\\INV_Misc_Key_07",
@@ -378,38 +402,45 @@ RelationshipsAttune_Data = {
{ type = "level", level = 55, name = "Reach Level 55", icon = ICON_LEVEL }, { type = "level", level = 55, name = "Reach Level 55", icon = ICON_LEVEL },
{ type = "item", id = 12382, name = "Key to the City", icon = ICON_KEY, { type = "item", id = 12382, name = "Key to the City", icon = ICON_KEY,
desc = "Opens the Service Entrance shortcut into Stratholme Live.", desc = "Opens the Service Entrance shortcut into Stratholme Live.",
start = "Drops from Magistrate Barthilas, patrolling the main road of Stratholme Live." }, start = "Drops from Magistrate Barthilas, patrolling the main road of Stratholme Live (instance entrance: Eastern Plaguelands 27, 12)." },
}, },
}, },
{ {
id = "dm", name = "Dire Maul", category = "Keys & Access", faction = "Both", id = "dm", name = "Dire Maul (Crescent Key)", category = "Keys & Access", faction = "Both",
icon = "Interface\\Icons\\INV_Misc_Key_09", icon = "Interface\\Icons\\INV_Misc_Key_09",
steps = { steps = {
{ type = "level", level = 55, name = "Reach Level 55", icon = ICON_LEVEL }, { type = "level", level = 55, name = "Reach Level 55", icon = ICON_LEVEL },
{ type = "item", id = 18249, name = "Crescent Key", icon = ICON_KEY, { type = "item", id = 18249, name = "Crescent Key", icon = ICON_KEY,
desc = "Opens locked doors and cages throughout Dire Maul (all three wings).", desc = "Opens locked doors and cages throughout Dire Maul (all three wings).",
start = "Pickpocket from Kobold Geomancers inside Dire Maul West (Rogue only), lockpick at 300, or buy from the AH." }, start = "Pickpocket from Kobold Geomancers inside Dire Maul West (Rogue only, DM entrance: Feralas 61, 30), lockpick at 300, or buy from the AH." },
},
},
{
id = "dm_tribute", name = "Dire Maul North (Gordok Tribute Run)", category = "Keys & Access", faction = "Both",
icon = "Interface\\Icons\\INV_Crown_02",
steps = {
{ type = "level", level = 58, name = "Reach Level 58", icon = ICON_LEVEL },
{ type = "item", id = 18249, name = "Crescent Key", icon = ICON_KEY,
desc = "Needed to open the doors within Dire Maul North for the King run.",
start = "Same source as regular DM Crescent Key (DM entrance: Feralas 61, 30)." },
{ type = "item", id = 18746, name = "Ogre Tannin", icon = ICON_ITEM,
desc = "Bring Ogre Tannin from Knot Thimblejack's Cache in Dire Maul North (found via lockpicking or Crescent Key) to Cho'Rush the Observer AFTER King Gordok dies. Skipping this makes the tribute run impossible.",
start = "Knot Thimblejack's Cache, Dire Maul North past the Warpwood Quarter (DM entrance: Feralas 61, 30)." },
}, },
}, },
{ {
id = "gnomer", name = "Gnomeregan (Workshop Shortcut)", category = "Keys & Access", faction = "Both", id = "gnomer", name = "Gnomeregan (Workshop Shortcut)", category = "Keys & Access", faction = "Both",
icon = "Interface\\Icons\\INV_Misc_Key_08", icon = "Interface\\Icons\\INV_Misc_Key_08",
steps = { steps = {
{ type = "level", level = 24, name = "Reach Level 24", icon = ICON_LEVEL }, { type = "level", level = 24, name = "Reach Level 24", icon = ICON_LEVEL },
{ type = "quest", id = 2904, name = "Grime-Encrusted Ring", icon = ICON_QUEST, { type = "quest", id = 2945, name = "Grime-Encrusted Ring", icon = ICON_QUEST,
desc = "Drop-quest item off Gnomeregan mobs; leads into 'Return to Marvon'.", desc = "Loot a Grime-Encrusted Ring off Dark Iron mobs inside Gnomeregan and clean it in the Sparklematic 5200 (Tinker Town, Ironforge or the Gnomeregan Sparklematic). Cleaning it produces a Workshop Key.",
start = "Loot Grime-Encrusted Ring inside Gnomeregan (Dun Morogh 25, 40)." }, start = "Loot Grime-Encrusted Ring from Dark Iron dwarves inside Gnomeregan (entrance: Dun Morogh 25, 40). Clean it at any Sparklematic 5200 (Tinker Town, Ironforge)." },
{ type = "quest", id = 2905, name = "Return to Marvon", icon = ICON_QUEST, { type = "item", id = 6893, name = "Workshop Key", icon = ICON_KEY,
desc = "Rewards the Workshop Key for skipping Grubbis.", desc = "Opens the Workshop shortcut door in Gnomeregan, letting you skip most of the trash on the way to Mekgineer Thermaplugg.",
start = "Marvon Rivetseeker, Steamwheedle Port camp, Tanaris (66, 22)." }, start = "Reward for cleaning the Grime-Encrusted Ring at the Sparklematic 5200 (Tinker Town, Ironforge, or inside Gnomeregan entrance Dun Morogh 25, 40)." },
{ type = "item", id = 9240, name = "Workshop Key", icon = ICON_KEY,
desc = "Opens the shortcut door in Gnomeregan bypassing the Grubbis wing.",
start = "Reward from 'Return to Marvon'." },
}, },
}, },
{ {
id = "mara", name = "Maraudon (Portal Shortcut)", category = "Keys & Access", faction = "Both", id = "mara", name = "Maraudon (Portal Shortcut)", category = "Keys & Access", faction = "Both",
icon = "Interface\\Icons\\INV_Misc_Idol_02", icon = "Interface\\Icons\\INV_Misc_Idol_02",
@@ -417,175 +448,128 @@ RelationshipsAttune_Data = {
{ type = "level", level = 40, name = "Reach Level 40", icon = ICON_LEVEL }, { type = "level", level = 40, name = "Reach Level 40", icon = ICON_LEVEL },
{ type = "quest", id = 7064, name = "Legends of Maraudon", icon = ICON_QUEST, { type = "quest", id = 7064, name = "Legends of Maraudon", icon = ICON_QUEST,
desc = "Prereq for the Scepter of Celebras.", desc = "Prereq for the Scepter of Celebras.",
start = "Celebras the Redeemed, freed on the Orange side of Maraudon after the vine tunnel." }, start = "Celebras the Redeemed, freed on the Orange side of Maraudon after the vine tunnel (Maraudon entrance: Desolace 29, 63)." },
{ type = "quest", id = 7065, name = "The Scepter of Celebras", icon = ICON_QUEST, { type = "quest", id = 7065, name = "The Scepter of Celebras", icon = ICON_QUEST,
desc = "Rewards Scepter of Celebras (teleport into inner Maraudon).", desc = "Rewards Scepter of Celebras (teleport into inner Maraudon).",
start = "Celebras the Redeemed, Maraudon (Orange side)." }, start = "Celebras the Redeemed, Maraudon Orange side (entrance: Desolace 29, 63)." },
{ type = "item", id = 17191, name = "Scepter of Celebras", icon = ICON_KEY, { type = "item", id = 17191, name = "Scepter of Celebras", icon = ICON_KEY,
desc = "Teleports directly to the Princess wing of Maraudon.", desc = "Teleports directly to the Princess wing of Maraudon.",
start = "Reward from 'The Scepter of Celebras'." }, start = "Reward from 'The Scepter of Celebras' (Maraudon entrance: Desolace 29, 63)." },
}, },
}, },
{ {
id = "st", name = "Sunken Temple (Jammal'an)", category = "Keys & Access", faction = "Both", id = "st", name = "Sunken Temple (Atal'ai + Jammal'an)", category = "Keys & Access", faction = "Both",
icon = "Interface\\Icons\\INV_Misc_Idol_03", icon = "Interface\\Icons\\INV_Misc_Idol_03",
steps = { steps = {
{ type = "level", level = 50, name = "Reach Level 50", icon = ICON_LEVEL }, { type = "level", level = 50, name = "Reach Level 50", icon = ICON_LEVEL },
{ type = "quest", id = 3373, name = "The Ancient Egg / Jammal'an the Prophet", icon = ICON_QUEST, { type = "quest", id = 3373, name = "The Ancient Egg (Yeh'kinya intro)", icon = ICON_QUEST,
desc = "Activates the six-statue puzzle so Jammal'an can be summoned.", desc = "Long chain leading to Hakkar's summoning ritual; obtains the Ancient Egg from the pool at the bottom of Sunken Temple.",
start = "Yeh'kinya, Steamwheedle Port, Tanaris (66, 22). Sunken Temple: Swamp of Sorrows (69, 53)." }, start = "Yeh'kinya, Steamwheedle Port, Tanaris (66, 22)." },
{ type = "quest", id = 3380, name = "The Atal'ai Exile", icon = ICON_QUEST,
desc = "Prereq for the raid form; introduces you to Atal'alarion / Jammal'an's followers.",
start = "Al'tabim the All-Seeing, Marshal's Refuge, Un'Goro Crater (44, 6)." },
{ type = "quest", id = 3441, name = "Screecher Spirits", icon = ICON_QUEST,
desc = "Kills for Atal'ai Screechers around Sunken Temple; part of the Hakkar summoning chain.",
start = "Fel'zerul, Stonard, Swamp of Sorrows (46, 55)." },
{ type = "quest", id = 3446, name = "Into the Depths / Jammal'an", icon = ICON_QUEST, final = true,
desc = "Activate the six statues around the central pit in Sunken Temple to summon Jammal'an the Prophet; no key required, but the puzzle must be solved (kill six Atal'ai Defenders in the correct order).",
start = "Fel'zerul, Stonard, Swamp of Sorrows (46, 55) - Horde / Brohann Caskbelly, Hall of Explorers, Ironforge (76, 12) - Alliance. Sunken Temple entrance: Swamp of Sorrows (69, 53)." },
}, },
}, },
{ {
id = "hydraxian", name = "Hydraxian Waterlords (MC Runes)", category = "Keys & Access", faction = "Both", id = "zf", name = "Zul'Farrak (Sacred Mallet)", category = "Keys & Access", faction = "Both",
icon = "Interface\\Icons\\Spell_Frost_SummonWaterElemental", icon = "Interface\\Icons\\INV_Hammer_16",
steps = { steps = {
{ type = "level", level = 60, name = "Reach Level 60", icon = ICON_LEVEL }, { type = "level", level = 45, name = "Reach Level 45", icon = ICON_LEVEL },
{ type = "rep", faction = "Hydraxian Waterlords", standing = 5, { type = "item", id = 9241, name = "Sacred Mallet", icon = ICON_KEY,
name = "Honored with the Hydraxian Waterlords", icon = ICON_REP, desc = "No quest required. Fight your way to the top of the Altar of Zul in the Hinterlands (Vilebranch troll city Jintha'Alor) and loot the Sacred Mallet from the pedestal at the summit.",
desc = "Needed to purchase Aqual Quintessence to douse the Molten Core runes for Majordomo.", start = "Top of the Altar of Zul, Jintha'Alor, Hinterlands (~63, 74). The whole hill is heavily patrolled by level 46-50 Vilebranch trolls." },
start = "Duke Hydraxis, small island off south-east Azshara (79, 76). Turn in water elemental drops (Azshara/Silithus) and MC boss items." }, { type = "item", id = 9240, name = "Mallet of Zul'Farrak", icon = ICON_KEY, final = true,
desc = "Right-click the Sacred Mallet while standing on the Altar of the Storms (the small square platform south of the Altar of Zul) to transform it into the Mallet of Zul'Farrak. Ring the gong in the Gahz'rilla arena inside Zul'Farrak to summon the boss.",
start = "Altar of the Storms, Hinterlands (~59, 62), while carrying the Sacred Mallet. Zul'Farrak entrance: Tanaris (39, 20)." },
}, },
}, },
{ {
id = "sm_cath", name = "Scarlet Monastery (Cathedral)", category = "Keys & Access", faction = "Both", id = "sm_cath", name = "Scarlet Monastery (Cathedral)", category = "Keys & Access", faction = "Both",
icon = "Interface\\Icons\\INV_Misc_Key_02", icon = "Interface\\Icons\\INV_Misc_Key_02",
steps = { steps = {
{ type = "level", level = 35, name = "Reach Level 35", icon = ICON_LEVEL }, { type = "level", level = 35, name = "Reach Level 35", icon = ICON_LEVEL },
{ type = "quest", id = 1050, name = "Clear the Scarlet Monastery Library", icon = ICON_QUEST, { type = "item", id = 7146, name = "Scarlet Key", icon = ICON_KEY, final = true,
desc = "Clear the Library wing and loot the small chest behind the final boss to obtain the Scarlet Key.", desc = "Opens the front doors of the Scarlet Monastery Armory, Cathedral and Graveyard wings. NOT a quest reward: the key is looted from a small chest ('Ill-Fated Chest') behind Arcanist Doan at the end of the Library wing, so run Library first. Rogues can also pick these doors around 175 lockpicking.",
start = "Scarlet Monastery Library wing entrance, Tirisfal Glades (85, 32)." }, start = "Ill-Fated Chest behind Arcanist Doan, end of the Library wing, Scarlet Monastery, Tirisfal Glades (85, 32)." },
{ type = "item", id = 7146, name = "Scarlet Key", icon = ICON_KEY,
desc = "Opens the front doors to the Scarlet Monastery Cathedral wing.",
start = "Found in a small loot chest behind Arcanist Doan at the end of the Library wing (not a boss drop)." },
}, },
}, },
{ {
id = "brd_prison", name = "BRD Prison Cell Key", category = "Keys & Access", faction = "Both", id = "sgorge", name = "Key to Searing Gorge", category = "Keys & Access", faction = "Alliance",
icon = "Interface\\Icons\\INV_Misc_Key_05", icon = "Interface\\Icons\\INV_Misc_Key_14",
steps = { steps = {
{ type = "level", level = 52, name = "Reach Level 52", icon = ICON_LEVEL }, { type = "level", level = 43, name = "Reach Level 43", icon = ICON_LEVEL,
{ type = "item", id = 11266, name = "Prison Cell Key", icon = ICON_KEY, desc = "Minimum recommended level to survive the trip into south-east Searing Gorge." },
desc = "Unlocks the Detention Block cells in Blackrock Depths (frees Marshal Windsor, Dughal, Kharan, etc).", { type = "quest", id = 3181, faction = "Alliance",
start = "Drops from Warder Stilgiss in the Detention Block, Blackrock Depths." }, name = "The Horn of the Beast", icon = ICON_QUEST,
desc = "Travel through the Badlands into Searing Gorge, slay Margol the Rager (level 48 elite Stegodon in the south-east corner of Searing Gorge), and loot Margol's Gigantic Horn. Return it to Mountaineer Pebblebitty.",
start = "Mountaineer Pebblebitty, Stonewrought Pass gate, southern Loch Modan (33, 51). Margol: south-east Searing Gorge (~78, 70)." },
{ type = "quest", id = 3182, faction = "Alliance",
name = "Proof of Deed", icon = ICON_QUEST,
desc = "Pebblebitty doesn't believe the horn is real. Take Margol's Gigantic Horn to Curator Thorius in the Ironforge museum (Hall of Explorers) for authentication.",
start = "Mountaineer Pebblebitty, Stonewrought Pass gate, southern Loch Modan (33, 51). Curator Thorius: Hall of Explorers, Ironforge (73, 10)." },
{ type = "quest", id = 3201, faction = "Alliance",
name = "At Last!", icon = ICON_QUEST,
desc = "Return to Mountaineer Pebblebitty with the Proof of Deed to receive the Key to Searing Gorge.",
start = "Curator Thorius, Hall of Explorers, Ironforge (73, 10). Pebblebitty: Stonewrought Pass, southern Loch Modan (33, 51)." },
{ type = "item", id = 5396, name = "Key to Searing Gorge", icon = ICON_KEY, final = true,
desc = "Opens the Stonewrought Pass gate between Loch Modan and Searing Gorge. Horde do not need a key - they reach Searing Gorge via the Kargath / Badlands back road.",
start = "Reward from 'At Last!' handed in to Mountaineer Pebblebitty, Stonewrought Pass gate, southern Loch Modan (33, 51)." },
}, },
}, },
{
id = "sgorge", name = "Searing Gorge Access", category = "Keys & Access", faction = "Both",
icon = "Interface\\Icons\\INV_Misc_Gem_Ruby_02",
steps = {
{ type = "level", level = 45, name = "Reach Level 45", icon = ICON_LEVEL },
{ type = "quest", id = 3441, faction = "Alliance",
name = "A Bindings Blockade (Alliance)", icon = ICON_QUEST,
desc = "Opens the Thorium Brotherhood gate from Loch Modan into Searing Gorge.",
start = "Prospector Stormpike, The Military Ward, Ironforge." },
{ type = "quest", id = 3701, faction = "Horde",
name = "The Flame's Casing (Horde)", icon = ICON_QUEST,
desc = "Rewards Horde access through the Thorium Brotherhood gate into Searing Gorge.",
start = "Krakle, Kargath, Badlands (3, 45)." },
},
},
{ {
id = "uldaman", name = "Uldaman (Discs of Norgannon)", category = "Keys & Access", faction = "Both", id = "uldaman", name = "Uldaman (Discs of Norgannon)", category = "Keys & Access", faction = "Both",
icon = "Interface\\Icons\\INV_Misc_Gem_Diamond_01", icon = "Interface\\Icons\\INV_Misc_Gem_Diamond_01",
steps = { steps = {
{ type = "level", level = 40, name = "Reach Level 40", icon = ICON_LEVEL }, { type = "level", level = 40, name = "Reach Level 40", icon = ICON_LEVEL },
{ type = "quest", id = 2278, name = "The Lost Tablets of Will", icon = ICON_QUEST, { type = "quest", id = 1139, faction = "Alliance",
desc = "Prereq to activating the Discs of Norgannon so Archaedas can be reached.", name = "The Lost Tablets of Will", icon = ICON_QUEST,
start = "Alliance: Advisor Belgrum, Ironforge. Horde: Rigglefuzz, Hillsbrad Foothills." }, desc = "Alliance prereq that leads to the Discs of Norgannon and spawning Archaedas. Fetch the Tablet of Will from inside Uldaman for Advisor Belgrum. Part of the longer 'Passing Word of a Threat' -> 'An Ambassador of Evil' -> 'The Lost Tablets of Will' -> Discs chain.",
{ type = "quest", id = 2418, name = "The Platinum Discs", icon = ICON_QUEST, start = "Advisor Belgrum, Great Forge, Ironforge (55, 46). Uldaman entrance: Badlands (42, 20)." },
desc = "Interact with the Discs of Norgannon inside the Chamber of Khaz'mul to spawn Archaedas.", { type = "quest", id = 2418, faction = "Both",
start = "Uldaman, Badlands (42, 20)." }, name = "The Platinum Discs", icon = ICON_QUEST, final = true,
desc = "Interact with the Discs of Norgannon inside the Chamber of Khaz'mul (deepest room in Uldaman) to spawn Archaedas. Uldaman itself is open to everyone - no attunement is required to enter, only to finish the disc/Archaedas storyline.",
start = "Discs of Norgannon, Chamber of Khaz'mul, Uldaman, Badlands (42, 20)." },
}, },
}, },
---------------------------------------------------------------- ----------------------------------------------------------------
-- TURTLE / OCTO WOW CUSTOM CONTENT -- TURTLE / OCTO WOW CUSTOM CONTENT
-- Placeholder ids; replace once your server confirms them. -- Placeholder ids where the live client id isn't confirmed; the
-- scanner also matches by tooltip name so keys are still credited.
---------------------------------------------------------------- ----------------------------------------------------------------
{ {
id = "kara10", name = "Karazhan Crypt (10)", category = "Turtle", faction = "Both", id = "kara10", name = "Karazhan Crypt (10)", category = "Turtle", faction = "Both",
icon = "Interface\\Icons\\INV_Misc_Gem_Sapphire_02", icon = "Interface\\Icons\\INV_Misc_Gem_Sapphire_02",
steps = { steps = {
{ type = "level", level = 55, name = "Reach Level 55", icon = ICON_LEVEL }, { type = "level", level = 55, name = "Reach Level 55", icon = ICON_LEVEL },
{ type = "quest", id = 40001, name = "TODO: Crypt attunement quest", icon = ICON_QUEST, { type = "quest", id = 40001, name = "Karazhan Crypt attunement (Koren)", icon = ICON_QUEST, final = true,
desc = "Custom Turtle attunement chain for Karazhan Crypt (10-man).", desc = "Custom Turtle attunement chain for Karazhan Crypt (10-man). Follow the chain from Koren the Seeker in Deadwind Pass; involves clearing the outer crypt trash and delivering an item to the Alturus circle at Karazhan.",
start = "Koren the Seeker, Morgan's Plot camp, Deadwind Pass (40, 75)." }, start = "Koren the Seeker, Morgan's Plot camp, Deadwind Pass (40, 75). Karazhan: Deadwind Pass (47, 75)." },
{ type = "item", id = 60001, name = "TODO: Crypt Key", icon = ICON_KEY, { type = "item", id = 60001, name = "Karazhan Crypt Key", icon = ICON_KEY,
desc = "Key required to enter the crypt beneath Karazhan.", desc = "Key required to enter the crypt beneath Karazhan (10-man). Matches by tooltip name if the numeric id differs on your build.",
start = "Awarded from Koren the Seeker after completing the Karazhan Crypt attunement chain." }, start = "Awarded from Koren the Seeker (Morgan's Plot, Deadwind Pass 40, 75) after completing the Karazhan Crypt attunement chain." },
}, },
}, },
{ {
id = "kara40", name = "Lower Karazhan Halls (40)", category = "Turtle", faction = "Both", id = "kara40", name = "Lower Karazhan Halls (40)", category = "Turtle", faction = "Both",
icon = "Interface\\Icons\\INV_Misc_Gem_Sapphire_01", icon = "Interface\\Icons\\INV_Misc_Gem_Sapphire_01",
steps = { steps = {
{ type = "level", level = 60, name = "Reach Level 60", icon = ICON_LEVEL }, { type = "level", level = 60, name = "Reach Level 60", icon = ICON_LEVEL },
{ type = "quest", id = 40010, name = "TODO: LKH attunement", icon = ICON_QUEST, { type = "quest", id = 40010, name = "Lower Karazhan Halls attunement", icon = ICON_QUEST, final = true,
desc = "Custom Turtle attunement chain for Lower Karazhan Halls (40-man).", desc = "Custom Turtle attunement chain for Lower Karazhan Halls (40-man). Alturus / Kamsis at Karazhan will guide you through several steps culminating in the Master's Key.",
start = "Archmage Alturus, Morgan's Plot camp, Deadwind Pass (40, 75)." }, start = "Archmage Alturus, Morgan's Plot camp, Deadwind Pass (40, 75). Karazhan: Deadwind Pass (47, 75)." },
{ type = "item", id = 60010, name = "TODO: LKH Key", icon = ICON_KEY, { type = "item", id = 60010, name = "Master's Key (LKH)", icon = ICON_KEY,
desc = "40-man Karazhan key.", desc = "40-man Karazhan raid key. Matches by tooltip name if the numeric id differs on your build.",
start = "Awarded from Archmage Alturus at the end of the LKH attunement." }, 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 = "TODO: ES attunement", icon = ICON_QUEST,
desc = "Emerald Sanctum attunement chain (Hinterlands portal).",
start = "Keeper Remulos avatar, eastern Hinterlands near Seradane (55, 22)." },
},
},
{
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 = "TODO: Gilneas attunement", icon = ICON_QUEST,
desc = "Attunement for the Gilneas City dungeon.",
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 = "TODO: Hateforge attunement", icon = ICON_QUEST,
desc = "Attunement for the Hateforge Quarry dungeon.",
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 = "TODO: SW Vault attunement", icon = ICON_QUEST,
desc = "Attunement for the Stormwind Vault dungeon.",
start = "Warden Thelwater, Stormwind Stockade entrance, Mage Quarter, Stormwind City (50, 68)." },
},
},
---------------------------------------------------------------- ----------------------------------------------------------------
-- DRAGONMAW RETREAT (Turtle/Octo custom, Wetlands, patch 1.18) -- DRAGONMAW RETREAT (Turtle/Octo custom, Wetlands, patch 1.18)
-- Key: "Lower Reserve Key". The scanner also identifies keyring
-- items by tooltip name, so this works even if the live client
-- assigns a different numeric id than the placeholder below.
---------------------------------------------------------------- ----------------------------------------------------------------
{ {
id = "dmr", name = "Dragonmaw Retreat", category = "Turtle", faction = "Both", id = "dmr", name = "Dragonmaw Retreat", category = "Turtle", faction = "Both",
@@ -593,36 +577,32 @@ RelationshipsAttune_Data = {
steps = { steps = {
{ type = "level", level = 25, name = "Reach Level 25", icon = ICON_LEVEL, { type = "level", level = 25, name = "Reach Level 25", icon = ICON_LEVEL,
desc = "Minimum level to enter Dragonmaw Retreat." }, desc = "Minimum level to enter Dragonmaw Retreat." },
{ type = "quest", id = 41810, faction = "Alliance", { type = "quest", id = 41810, faction = "Alliance",
name = "To Crush The Dragonmaw", icon = ICON_QUEST, name = "To Crush The Dragonmaw", icon = ICON_QUEST,
desc = "Alliance intro quest for Dragonmaw Retreat.", desc = "Alliance intro quest for Dragonmaw Retreat.",
start = "Captain Stoutfist, Menethil Harbor, Wetlands." }, start = "Captain Stoutfist, Menethil Harbor, Wetlands (10, 58)." },
{ type = "quest", id = 41811, faction = "Alliance", { type = "quest", id = 41811, faction = "Alliance",
name = "Blackheart's Demise", icon = ICON_QUEST, name = "Blackheart's Demise", icon = ICON_QUEST,
desc = "Alliance follow-up to slay Overlord Blackheart inside the dungeon.", desc = "Alliance follow-up to slay Overlord Blackheart inside the dungeon.",
start = "Captain Stoutfist, Menethil Harbor, Wetlands." }, start = "Captain Stoutfist, Menethil Harbor, Wetlands (10, 58)." },
{ type = "quest", id = 41820, faction = "Horde", { type = "quest", id = 41820, faction = "Horde",
name = "Cavernweb Extract", icon = ICON_QUEST, name = "Cavernweb Extract", icon = ICON_QUEST,
desc = "Horde intro quest collect spider extract inside Dragonmaw Retreat.", desc = "Horde intro quest - collect spider extract inside Dragonmaw Retreat.",
start = "Okul, Hammerfall, Arathi Highlands." }, start = "Okul, Hammerfall, Arathi Highlands (73, 33)." },
{ type = "quest", id = 41821, faction = "Horde", { type = "quest", id = 41821, faction = "Horde",
name = "A Blaze Unending", icon = ICON_QUEST, name = "A Blaze Unending", icon = ICON_QUEST,
desc = "Horde follow-up in Dragonmaw Retreat.", desc = "Horde follow-up in Dragonmaw Retreat.",
start = "Shara Blazen, Tarren Mill, Hillsbrad Foothills." }, start = "Shara Blazen, Tarren Mill, Hillsbrad Foothills (60, 20)." },
{ type = "quest", id = 41830, name = "Gowlfang's Defeat", icon = ICON_QUEST, { type = "quest", id = 41830, name = "Gowlfang's Defeat", icon = ICON_QUEST,
desc = "Neutral quest defeat the gnoll leader Gowlfang.", desc = "Neutral quest - defeat the gnoll leader Gowlfang.",
start = "Grimbite, Green Belt gnoll camp, central Wetlands." }, start = "Grimbite, Green Belt gnoll camp, central Wetlands (~46, 50)." },
{ type = "quest", id = 41831, name = "The Dragonmaw Brood", icon = ICON_QUEST, { type = "quest", id = 41831, name = "The Dragonmaw Brood", icon = ICON_QUEST,
desc = "Neutral quest cull the drakes and whelps inside the retreat.", desc = "Neutral quest - cull the drakes and whelps inside the retreat.",
start = "Nidiszanz, Dragonmaw Gates, Wetlands." }, start = "Nidiszanz, Dragonmaw Gates, Wetlands (~70, 40)." },
{ type = "item", id = 61810, name = "Lower Reserve Key", { type = "item", id = 61810, name = "Lower Reserve Key",
icon = ICON_KEY, final = true, icon = ICON_KEY, final = true,
desc = "Opens the gates of Dragonmaw Retreat / Dragonmaw Reserve in the Wetlands.", desc = "Opens the gates of Dragonmaw Retreat / Dragonmaw Reserve in the Wetlands.",
start = "Looted from Dragonmaw guards in the Dragonmaw Reserve outpost, Wetlands." }, start = "Looted from Dragonmaw guards in the Dragonmaw Reserve outpost, Wetlands (~70, 40)." },
}, },
}, },
} }
+26 -5
View File
@@ -52,22 +52,35 @@ end
MM._updatePosition = applyPosition MM._updatePosition = applyPosition
local function onDragUpdate() local function onDragUpdate()
-- 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() local mx, my = GetCursorPosition()
local scale = UIParent:GetEffectiveScale() or 1 mx = mx / bscale
mx = mx / scale; my = my / scale my = my / bscale
local ucx, ucy = UIParent:GetCenter() local ucx, ucy = UIParent:GetCenter()
if not ucx then return end 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() local o = opts()
o.minimapX = mx - ucx o.minimapX = mx - ucx
o.minimapY = my - ucy o.minimapY = my - ucy
applyPosition(this) applyPosition(btn)
end end
function MM:Create() function MM:Create()
if getglobal(BTN_NAME) then return getglobal(BTN_NAME) end if getglobal(BTN_NAME) then return getglobal(BTN_NAME) end
-- Parent to UIParent so we're not clipped to the minimap area. -- Parent to Minimap so button-collector addons (MinimapButtonBag /
local btn = CreateFrame("Button", BTN_NAME, UIParent) -- 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:SetWidth(31); btn:SetHeight(31)
btn:SetFrameStrata("MEDIUM") btn:SetFrameStrata("MEDIUM")
btn:SetFrameLevel(8) btn:SetFrameLevel(8)
@@ -75,6 +88,12 @@ function MM:Create()
btn:RegisterForClicks("LeftButtonUp", "RightButtonUp") btn:RegisterForClicks("LeftButtonUp", "RightButtonUp")
btn:RegisterForDrag("LeftButton", "RightButton") btn:RegisterForDrag("LeftButton", "RightButton")
btn:SetMovable(true) 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) -- Round border ring (standard minimap-button look)
local overlay = btn:CreateTexture(nil, "OVERLAY") local overlay = btn:CreateTexture(nil, "OVERLAY")
@@ -89,6 +108,7 @@ function MM:Create()
icon:SetPoint("TOPLEFT", btn, "TOPLEFT", 7, -6) icon:SetPoint("TOPLEFT", btn, "TOPLEFT", 7, -6)
btn.icon = icon btn.icon = icon
btn:SetScript("OnClick", function() btn:SetScript("OnClick", function()
if RA.UI and RA.UI.Toggle then RA.UI:Toggle() end if RA.UI and RA.UI.Toggle then RA.UI:Toggle() end
end) end)
@@ -107,6 +127,7 @@ function MM:Create()
GameTooltip:AddLine("Relationships Attune", 1, 0.82, 0) GameTooltip:AddLine("Relationships Attune", 1, 0.82, 0)
GameTooltip:AddLine("Left-click: open / close window", 0.9, 0.9, 0.9) 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("Drag: move anywhere on screen", 0.9, 0.9, 0.9)
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:AddLine("/attune hide - hide this button", 0.6, 0.8, 1)
GameTooltip:Show() GameTooltip:Show()
end) end)
+96 -78
View File
@@ -1,99 +1,117 @@
# Relationships Attune <img src="https://i.postimg.cc/rwVg8mmF/v.png"/>
A lightweight raid / dungeon / misc attunement tracker for **World of Warcraft 1.12** (Vanilla and OctoWoW).
Keep track of every attunement step for yourself — quests, key items, reputation, and level requirements — all in one clean window. # RelationshipsAttune
A lightweight **World of Warcraft 1.12** attunement and dungeon access tracker built for **OctoWoW**.
RelationshipsAttune automatically scans your character's progress, tracks raid attunements, dungeon keys, reputation requirements, and important access chains, while allowing you to view the progress of other players who are using the addon.
--- ---
## ✨ What it does ## Features
Relationships Attune scans your quest log, bags, and reputations to figure out exactly how far along you are in each raid attunement. It then shares that progress with your party, raid, or guild so you can instantly see who's ready for what. * ✅ Automatic quest scanning
* ✅ Automatic bag, equipment and keyring scanning
No more asking *"do you have your Onyxia key?"* in guild chat. * ✅ Tracks raid attunements
* ✅ Tracks dungeon keys and access quests
* ✅ Reputation requirement tracking
* ✅ Progress bars for every attunement
* ✅ Detailed step-by-step guides
* ✅ Tooltips showing where every quest starts
* ✅ Supports Alliance and Horde specific quest chains
* ✅ Share progress with other addon users
* ✅ Minimap button
* ✅ Manual rescan button
* ✅ Designed for Lua 5.0 / WoW 1.12 compatibility
--- ---
## 🎯 Features # Included Raid Attunements
- **Full attunement tracking** for classic raids: ### Vanilla
- Onyxia's Lair (both Alliance & Horde chains)
- Molten Core * Onyxia's Lair
- Blackwing Lair * Molten Core
- Naxxramas * Blackwing Lair
- AQ40 * Naxxramas
- **Turtle / Octo WoW custom raids** supported * Ruins of Ahn'Qiraj (AQ20)
- **Auto-detection** of your progress by scanning: * Temple of Ahn'Qiraj (AQ40)
- Quest log (completed & in-progress quests)
- Bags (key items)
- Faction reputations
- Character level
- **Faction-aware** — only shows steps that apply to your character
- **Party / Raid / Guild sync** — see the attunement status of everyone in your group
- **Clean UI** with:
- Scrollable player list
- Color-coded progress (green = done, yellow = partial, red = not started)
- Hover tooltips explaining every step and where to start it
- Click any step to see extra details
- **Movable, hideable minimap button** — drag it anywhere on your screen
- **Throttled auto-scan** — stays light on performance
- **Per-character progress** saved between sessions
--- ---
## 📦 Installation # Included Dungeon & Key Access
* Upper Blackrock Spire (Seal of Ascension)
* Blackrock Depths (Shadowforge Key)
* Scholomance
* Stratholme Service Entrance
* Dire Maul
* Gnomeregan Workshop Shortcut
* Maraudon Portal Shortcut
* Sunken Temple
* Hydraxian Waterlords (Molten Core Rune access)
* Scarlet Monastery Cathedral
* BRD Prison Cell Key
* Searing Gorge Access
* Uldaman (Discs of Norgannon)
---
# Included OctoWoW Content
* Karazhan Crypt (10)
* Lower Karazhan Halls (40)
* Emerald Sanctum
* Gilneas City
* Hateforge Quarry
* Stormwind Vault
* Dragonmaw Retreat
---
# What is Tracked
RelationshipsAttune tracks:
* Character level requirements
* Quest progression
* Completed quest chains
* Dungeon keys
* Raid attunements
* Reputation requirements
* Required items
* Final attunement items
* Character completion status
---
# Commands
```
/attune
```
Opens the Relationships Attune window.
---
# Installation
1. Download the addon. 1. Download the addon.
2. Extract the `RelationshipsAttune` folder into your WoW addons directory: 2. Extract the folder into:
```
/Interface/AddOns/RelationshipsAttune/
```
4. Restart WoW (or reload with `/reload`).
--- ```
/Interface/AddOns/
```
## 🕹️ Usage 3. Launch the game.
4. Enable **RelationshipsAttune** on the character selection screen.
5. Type:
Open the window by clicking the minimap button, or use a slash command: ```
/attune
| Command | What it does | ```
|---|---|
| `/attune` | Toggle the main window |
| `/rattune show` | Show the window |
| `/rattune hide` | Hide the window |
| `/rattune scan` | Rescan your quest log and bags |
| `/rattune sync` | Request attunement data from your group |
| `/rattune reset` | Wipe stored data for this character |
### The main window
- **Left side:** list of players (you + anyone in your group/guild running the addon).
- **Right side:** every attunement broken down step by step.
- **Hover** a step to see what it is and where to start it.
- **Click** a step to expand extra info.
- Completed steps highlight in **green**, in-progress steps in **yellow**, missing steps in **red**.
### Minimap button
- **Left-click:** open/close the window.
- **Drag:** move the button anywhere on your screen.
- Hide it entirely via the options if you prefer a clean UI.
---
## ❓ FAQ
**Does it work on Turtle WoW / Octo WoW?**
Yes — the addon is built for 1.12 clients and is fully compatible with Turtle/Octo. Custom raid attunements are included.
**Will other players see my progress if they don't have the addon?**
No. Sync only happens between players who both have Relationships Attune installed.
**How do I move or hide the minimap button?**
Just click and drag it anywhere. To hide it, use the addon options (or set `minimapHide = true` in the saved variables).
**My progress looks wrong — what do I do?**
Run `/rattune scan` to force a rescan of your quest log, bags, and reputation. If it's still wrong, `/rattune reset` will wipe stored data for that character and rebuild it.
--- ---
+1 -1
View File
@@ -2,7 +2,7 @@
## Title: Relationships Attune ## Title: Relationships Attune
## Notes: Track Vanilla and Turtle/Octo WoW attunement progress ## Notes: Track Vanilla and Turtle/Octo WoW attunement progress
## Author: Relationship ## Author: Relationship
## Version: 1.0 ## Version: 1.8
## X-Category: Quest ## X-Category: Quest
## SavedVariables: RelationshipsAttuneDB ## SavedVariables: RelationshipsAttuneDB
## SavedVariablesPerCharacter: RelationshipsAttuneCharDB ## SavedVariablesPerCharacter: RelationshipsAttuneCharDB
+92
View File
@@ -24,6 +24,56 @@ local C_NONE = { 0.90, 0.30, 0.30 }
local C_UNKNOWN = { 0.55, 0.55, 0.55 } local C_UNKNOWN = { 0.55, 0.55, 0.55 }
local C_TURTLE = { 0.55, 0.85, 1.00 } local C_TURTLE = { 0.55, 0.85, 1.00 }
local C_KEYS = { 0.95, 0.75, 0.35 } 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 selectedPlayer = nil
local selectedRaid = nil local selectedRaid = nil
@@ -195,6 +245,29 @@ function UI:Build()
detail.category = fs(frame.rightPane, "", 10, 0.75, 0.75, 0.75) detail.category = fs(frame.rightPane, "", 10, 0.75, 0.75, 0.75)
detail.category:SetPoint("TOPLEFT", detail.name, "BOTTOMLEFT", 0, -2) 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 -- 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. -- always has a resolvable width, then size the fill from that width.
detail.barBG = solid(frame.rightPane, "BORDER", 0, 0, 0, 0.90) detail.barBG = solid(frame.rightPane, "BORDER", 0, 0, 0, 0.90)
@@ -565,6 +638,20 @@ function UI:RenderDetail()
detail.barFill:SetVertexColor(c[1], c[2], c[3], 0.95) detail.barFill:SetVertexColor(c[1], c[2], c[3], 0.95)
detail.barText:SetText(done .. " / " .. total .. " (" .. math.floor(pct * 100 + 0.5) .. "%)") 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 -- Build applicable step list
local applicable = {} local applicable = {}
for i = 1, table.getn(att.steps) do for i = 1, table.getn(att.steps) do
@@ -644,10 +731,15 @@ function UI:RenderDetail()
row.name:SetText(step.name or "?") row.name:SetText(step.name or "?")
local ok = prog and prog.steps and prog.steps[origIdx] local ok = prog and prog.steps and prog.steps[origIdx]
local inProg = prog and prog.stepsInProgress and prog.stepsInProgress[origIdx]
if ok then if ok then
row.status:SetTexture(TEX_CHECK); row.status:Show() row.status:SetTexture(TEX_CHECK); row.status:Show()
row.name:SetTextColor(C_DONE[1], C_DONE[2], C_DONE[3]) row.name:SetTextColor(C_DONE[1], C_DONE[2], C_DONE[3])
row.bg:SetVertexColor(0.20, 0.90, 0.30, 0.28) row.bg:SetVertexColor(0.20, 0.90, 0.30, 0.28)
elseif inProg then
row.status:SetTexture(TEX_WAIT); row.status:Show()
row.name:SetTextColor(C_INPROGRESS[1], C_INPROGRESS[2], C_INPROGRESS[3])
row.bg:SetVertexColor(C_INPROGRESS[1], C_INPROGRESS[2], C_INPROGRESS[3], 0.22)
elseif prog and prog.applicable then elseif prog and prog.applicable then
row.status:SetTexture(TEX_CROSS); row.status:Show() row.status:SetTexture(TEX_CROSS); row.status:Show()
row.name:SetTextColor(1, 1, 1) row.name:SetTextColor(1, 1, 1)