From d143e91f8171273f60bb3ff8e1d578458a2e730e Mon Sep 17 00:00:00 2001 From: Brues <5278969+brues-code@users.noreply.github.com> Date: Mon, 2 Nov 2026 20:12:21 -0600 Subject: [PATCH] Mirror timers: bars fill from duration objects, drop the OnUpdate A breath, fatigue or feign death bar fills from a duration object that spans the bar at the timer's rate, and a text binding writes the m:ss countdown. MirrorTimer_Show and MIRROR_TIMER_PAUSE resync it from GetMirrorTimerProgress; a paused bar holds its value. --- modules/mirrortimers.lua | 80 +++++++++++++++++++++++++++++++++++----- 1 file changed, 71 insertions(+), 9 deletions(-) diff --git a/modules/mirrortimers.lua b/modules/mirrortimers.lua index 4a39aade..d6a35120 100644 --- a/modules/mirrortimers.lua +++ b/modules/mirrortimers.lua @@ -3,20 +3,53 @@ pfUI:RegisterModule("mirrortimers", function () local fontsize = tonumber(C.global.font_size) local height = fontsize * 1.3 + -- The value of a mirror timer moves by `scale` per second: breath drains at + -- -1, and refills faster than that. A running bar fills from a duration + -- object that spans the whole bar at that rate, and its text binding shows + -- the value as m:ss. A paused bar holds its value. + local function Sync(frame) + local value = GetMirrorTimerProgress(frame.timer) / 1000 + local max, scale = frame.maxvalue, frame.scale + local label = frame.label:GetText() or "" + + if frame.paused or scale == 0 then + frame.statusbar:SetMinMaxValues(0, max) + frame.statusbar:SetValue(value) + frame.binding:Disable() + frame.text:SetFormattedText("%s (%d:%02d)", label, value / 60, value % 60) + return + end + + -- the shown value is the seconds of the duration times the rate + local rate = math.abs(scale) + local property + if scale < 0 then + frame.duration:SetTimeFromEnd(GetTime() + value / rate, max / rate) + frame.statusbar:SetTimerDuration(frame.duration, Enum.StatusBarInterpolation.Immediate, Enum.StatusBarTimerDirection.RemainingTime) + property = Enum.DurationTextBindingProperty.RemainingDuration + else + frame.duration:SetTimeFromEnd(GetTime() + (max - value) / rate, max / rate) + frame.statusbar:SetTimerDuration(frame.duration, Enum.StatusBarInterpolation.Immediate, Enum.StatusBarTimerDirection.ElapsedTime) + property = Enum.DurationTextBindingProperty.ElapsedDuration + end + + local Down = Enum.NumericRuleFormatRounding.Down + frame.clock:SetBreakpoints({ + { threshold = 0, format = "%d:%02d", components = { + { div = 60 / rate, step = 1, rounding = Down }, + { div = 1 / rate, mod = 60, step = 1, rounding = Down }, + } }, + }) + frame.binding:SetTextFormat(label .. " ({})", { { property = property, formatter = frame.clock } }) + frame.binding:Enable() + end + for i = 1, MIRRORTIMER_NUMTIMERS do local frame = _G["MirrorTimer"..i] frame:GetRegions():Hide() frame:SetWidth(200) frame:SetHeight(height) - frame:SetScript("OnUpdate", function() - if this.paused or not this.timer then return end - MirrorTimerFrame_OnUpdate(this, arg1) - - local text = this.label:GetText() - if text and this.value then - this.text:SetText(format("%s (%d:%02d)", text, this.value/60, this.value - math.floor(this.value/60)*60)) - end - end) + frame:SetScript("OnUpdate", nil) frame:ClearAllPoints() frame:SetPoint("TOP", UIParent, "TOP", 0, -120-(i-1)*(height + 10)) @@ -38,5 +71,34 @@ pfUI:RegisterModule("mirrortimers", function () frame.text = frame:CreateFontString(nil, "OVERLAY") frame.text:SetFont(font, fontsize, "OUTLINE") frame.text:SetPoint("CENTER", frame.statusbar, "CENTER", 0, 0) + + frame.duration = C_DurationUtil.CreateDuration() + frame.clock = C_StringUtil.CreateNumericRuleFormatter() + frame.binding = C_DurationUtil.CreateDurationTextBinding() + frame.binding:SetFontString(frame.text) + frame.binding:SetDuration(frame.duration) + + -- the stock handler has already set the pause state + frame:HookScript("OnEvent", function() + if event == "MIRROR_TIMER_PAUSE" and this:IsShown() and arg1 == this.timer then + Sync(this) + end + end) + + frame:HookScript("OnHide", function() + this.binding:Disable() + end) end + + -- the stock function picks the bar and sets its timer, scale and pause state + hooksecurefunc("MirrorTimer_Show", function(timer, _, maxvalue) + for i = 1, MIRRORTIMER_NUMTIMERS do + local frame = _G["MirrorTimer"..i] + if frame:IsShown() and frame.timer == timer then + frame.maxvalue = maxvalue / 1000 + Sync(frame) + return + end + end + end) end)