diff --git a/Core.lua b/Core.lua index 8d08bc9..91f4273 100644 --- a/Core.lua +++ b/Core.lua @@ -70,6 +70,56 @@ function RA:InitDB() if not RelationshipsAttuneCharDB.announced then RelationshipsAttuneCharDB.announced = {} end if not RelationshipsAttuneCharDB.progress then RelationshipsAttuneCharDB.progress = {} end if not RelationshipsAttuneCharDB.expanded then RelationshipsAttuneCharDB.expanded = {} end + if not RelationshipsAttuneCharDB.forcedSteps then RelationshipsAttuneCharDB.forcedSteps = {} end + if not RelationshipsAttuneCharDB.forcedAtt then RelationshipsAttuneCharDB.forcedAtt = {} end +end + +-- --------------------------------------------------------------- +-- Force-complete (manual override) helpers +-- --------------------------------------------------------------- +function RA:IsStepForced(attId, i) + local t = RelationshipsAttuneCharDB.forcedSteps + return t and t[attId] and t[attId][i] == true +end + +function RA:SetStepForced(attId, i, v) + if not attId or not i then return end + local t = RelationshipsAttuneCharDB.forcedSteps + if not t then RelationshipsAttuneCharDB.forcedSteps = {}; t = RelationshipsAttuneCharDB.forcedSteps end + if not t[attId] then t[attId] = {} end + if v then t[attId][i] = true else t[attId][i] = nil end + self:RecomputeAll() + if RA.UI and RA.UI.Refresh then RA.UI:Refresh() end +end + +function RA:IsAttForced(attId) + local t = RelationshipsAttuneCharDB.forcedAtt + return t and t[attId] == true +end + +function RA:SetAttForced(attId, v) + if not attId then return end + if not RelationshipsAttuneCharDB.forcedAtt then + RelationshipsAttuneCharDB.forcedAtt = {} + end + if not RelationshipsAttuneCharDB.completedAttunements then + RelationshipsAttuneCharDB.completedAttunements = {} + end + if v then + RelationshipsAttuneCharDB.forcedAtt[attId] = true + RelationshipsAttuneCharDB.completedAttunements[attId] = true + else + RelationshipsAttuneCharDB.forcedAtt[attId] = nil + RelationshipsAttuneCharDB.completedAttunements[attId] = nil + if RelationshipsAttuneCharDB.forcedSteps then + RelationshipsAttuneCharDB.forcedSteps[attId] = nil + end + if RelationshipsAttuneCharDB.announced then + RelationshipsAttuneCharDB.announced[attId] = nil + end + end + self:RecomputeAll() + if RA.UI and RA.UI.Refresh then RA.UI:Refresh() end end -- --------------------------------------------------------------- @@ -392,6 +442,20 @@ function RA:EvaluateAttunement(att) stepsInProgress[i] = nil end end + -- Apply per-step forced completions (manual overrides from the UI). + -- These are treated exactly like a real completion for tracking purposes. + local forced = RelationshipsAttuneCharDB.forcedSteps and RelationshipsAttuneCharDB.forcedSteps[att.id] + if forced then + for i = 1, numSteps do + local step = att.steps[i] + if step and self:StepApplies(step) and forced[i] and steps[i] == false then + steps[i] = true + stepsInProgress[i] = false + doneCount = doneCount + 1 + end + end + end + -- Short-circuit: if the "final" step is complete, treat the whole -- attunement as done. Prefer an explicit final=true step; fall back to -- the last item step (key), then the last applicable step. diff --git a/Minimap.lua b/Minimap.lua index b5bab7a..facc9ea 100644 --- a/Minimap.lua +++ b/Minimap.lua @@ -52,15 +52,24 @@ end MM._updatePosition = applyPosition local function onDragUpdate() - local mx, my = GetCursorPosition() - local scale = UIParent:GetEffectiveScale() or 1 - mx = mx / scale; my = my / scale + -- Do all math in the button's own effective scale. The button is + -- parented to Minimap, whose effective scale can differ from + -- UIParent's; mixing scales makes the icon drift away from the cursor. + local btn = this + local bscale = btn:GetEffectiveScale() or 1 + local uscale = UIParent:GetEffectiveScale() or 1 + local mx, my = GetCursorPosition() + mx = mx / bscale + my = my / bscale local ucx, ucy = UIParent:GetCenter() if not ucx then return end + -- Convert UIParent center from UIParent scale into btn scale. + ucx = ucx * uscale / bscale + ucy = ucy * uscale / bscale local o = opts() o.minimapX = mx - ucx o.minimapY = my - ucy - applyPosition(this) + applyPosition(btn) end function MM:Create() diff --git a/RelationshipsAttune.toc b/RelationshipsAttune.toc index e68f263..9cdccc1 100644 --- a/RelationshipsAttune.toc +++ b/RelationshipsAttune.toc @@ -2,7 +2,7 @@ ## Title: Relationships Attune ## Notes: Track Vanilla and Turtle/Octo WoW attunement progress ## Author: Relationship -## Version: 1.6 +## Version: 1.7 ## X-Category: Quest ## SavedVariables: RelationshipsAttuneDB ## SavedVariablesPerCharacter: RelationshipsAttuneCharDB diff --git a/UI.lua b/UI.lua index 46d3a0b..151db6c 100644 --- a/UI.lua +++ b/UI.lua @@ -26,6 +26,55 @@ local C_TURTLE = { 0.55, 0.85, 1.00 } local C_KEYS = { 0.95, 0.75, 0.35 } local C_INPROGRESS = { 1.00, 0.60, 0.15 } -- orange: quest accepted / step in progress +-- --------------------------------------------------------------- +-- Force-complete confirmation dialogs (StaticPopup) +-- In vanilla 1.12, StaticPopup OnAccept is called as OnAccept(dialog.data), +-- so the data arrives as arg1 (Lua 5.0 vararg). `this` inside OnAccept is +-- NOT the dialog frame, so this.data is nil. We stash the data on the +-- dialog itself via OnShow (which does run with this = dialog) and read +-- it back from arg1 with a dialog fallback for safety. +-- --------------------------------------------------------------- +StaticPopupDialogs = StaticPopupDialogs or {} + +local function RATTUNE_StashData() + -- OnShow runs with `this` = the dialog frame. StaticPopup_Show already + -- assigns dialog.data, but we snapshot it into an upvalue table so the + -- OnAccept closures can find it even on clients that don't forward arg1. + if this then RATTUNE_LastPopupData = this.data end +end + +-- Attunement id awaiting the user's confirmation for a force / unforce. +-- StaticPopup OnAccept in vanilla 1.12 does not reliably receive dialog.data +-- as arg1, so we snapshot the id here at click time and read it back on accept. +RATTUNE_PendingForceAtt = nil + +StaticPopupDialogs["RATTUNE_FORCE_ATT"] = { + text = "Force complete the entire \"%s\" attunement?\n\nEvery step will be marked as done for tracking. You can undo this later.", + button1 = "Force Complete", + button2 = "Cancel", + OnAccept = function() + local id = RATTUNE_PendingForceAtt + RATTUNE_PendingForceAtt = nil + if id then RA:SetAttForced(id, true) end + end, + OnCancel = function() RATTUNE_PendingForceAtt = nil end, + timeout = 0, whileDead = 1, hideOnEscape = 1, showAlert = 1, +} + +StaticPopupDialogs["RATTUNE_UNFORCE_ATT"] = { + text = "Undo forced completion for \"%s\"?\n\nProgress will revert to real, scanned state.", + button1 = "Undo", + button2 = "Cancel", + OnAccept = function() + local id = RATTUNE_PendingForceAtt + RATTUNE_PendingForceAtt = nil + if id then RA:SetAttForced(id, false) end + end, + OnCancel = function() RATTUNE_PendingForceAtt = nil end, + timeout = 0, whileDead = 1, hideOnEscape = 1, showAlert = 1, +} + + local selectedPlayer = nil local selectedRaid = nil @@ -196,6 +245,29 @@ function UI:Build() detail.category = fs(frame.rightPane, "", 10, 0.75, 0.75, 0.75) detail.category:SetPoint("TOPLEFT", detail.name, "BOTTOMLEFT", 0, -2) + -- Force Complete (attunement-level) button. Sits in the top-right of the + -- right pane, hidden when viewing another player's data. + detail.forceBtn = CreateFrame("Button", nil, frame.rightPane, "UIPanelButtonTemplate") + detail.forceBtn:SetWidth(150); detail.forceBtn:SetHeight(20) + detail.forceBtn:SetPoint("TOPRIGHT", frame.rightPane, "TOPRIGHT", -4, -4) + detail.forceBtn:SetText("Force Complete") + detail.forceBtn:SetScript("OnClick", function() + local attId = selectedRaid + if not attId then return end + local att + local data = RelationshipsAttune_Data + for i = 1, table.getn(data) do + if data[i].id == attId then att = data[i]; break end + end + if not att then return end + RATTUNE_PendingForceAtt = attId + if RA:IsAttForced(attId) then + StaticPopup_Show("RATTUNE_UNFORCE_ATT", att.name) + else + StaticPopup_Show("RATTUNE_FORCE_ATT", att.name) + end + end) + -- Progress bar: give it explicit left+right anchors AND a border so it -- always has a resolvable width, then size the fill from that width. detail.barBG = solid(frame.rightPane, "BORDER", 0, 0, 0, 0.90) @@ -566,6 +638,20 @@ function UI:RenderDetail() detail.barFill:SetVertexColor(c[1], c[2], c[3], 0.95) detail.barText:SetText(done .. " / " .. total .. " (" .. math.floor(pct * 100 + 0.5) .. "%)") + -- Update attunement-level Force button (only for your own data) + if detail.forceBtn then + if selectedPlayer ~= nil then + detail.forceBtn:Hide() + else + if RA:IsAttForced(att.id) then + detail.forceBtn:SetText("Undo Force Complete") + else + detail.forceBtn:SetText("Force Complete") + end + detail.forceBtn:Show() + end + end + -- Build applicable step list local applicable = {} for i = 1, table.getn(att.steps) do