From a219bd13dc80c9f5a8f2d2e3fadd3fbbef4ea109 Mon Sep 17 00:00:00 2001 From: Brues <5278969+brues-code@users.noreply.github.com> Date: Mon, 2 Nov 2026 10:56:58 -0600 Subject: [PATCH] Timer bars: buffwatch, loot roll, infobox and five-second rule drain from duration objects Each bar hands its fill to StatusBar:SetTimerDuration instead of a per-frame SetValue. Buffwatch's time text moves to a duration text binding holding the cooldown formatter, now exposed as GetTimeFormatter (replacing GetColoredTimeString, whose only caller this was). The five-second rule's overlay style becomes a color-filled StatusBar too, so both styles share one path; energytick's OnUpdate keeps only the tick spark. --- api/cooldown.lua | 11 ++++---- api/ui-widgets.lua | 13 +++------- modules/buffwatch.lua | 39 ++++++++++++++++++----------- modules/energytick.lua | 57 +++++++++++++++++++++--------------------- modules/roll.lua | 16 +++++------- 5 files changed, 68 insertions(+), 68 deletions(-) diff --git a/api/cooldown.lua b/api/cooldown.lua index 5319009f..2b0ded1a 100644 --- a/api/cooldown.lua +++ b/api/cooldown.lua @@ -196,9 +196,10 @@ function pfUI.api.GetAuraTimer(duration, expirationTime) return start, dur end --- [ GetColoredTimeString ] -- --- 'remaining' [number] the time in seconds that should be converted --- return [string] the time as cooldown numbers show it -function pfUI.api.GetColoredTimeString(remaining) - return GetFormatter():FormatNumber(remaining) +-- [ GetTimeFormatter ] -- +-- The formatter cooldown numbers use, for other timers to show time the same +-- way. Hold it rather than a copy: a settings change updates it in place. +-- return [formatter] a NumericRuleFormatter +function pfUI.api.GetTimeFormatter() + return GetFormatter() end diff --git a/api/ui-widgets.lua b/api/ui-widgets.lua index 083bf9e9..50a6aa67 100644 --- a/api/ui-widgets.lua +++ b/api/ui-widgets.lua @@ -834,10 +834,7 @@ function pfUI.api.CreateInfoBox(text, time, parent, height) end) infobox:SetScript("OnUpdate", function() - local time = infobox.lastshow + infobox.duration - GetTime() - infobox.timeout:SetValue(time) - - if GetTime() > infobox.lastshow + infobox.duration then + if infobox.duration:HasExpired() then if not infobox.fade:IsPlaying() then infobox.fade:Play() end elseif MouseIsOverHitRect(this) then this:SetAlpha(max(0.4, this:GetAlpha() - .1)) @@ -857,6 +854,8 @@ function pfUI.api.CreateInfoBox(text, time, parent, height) infobox.timeout = CreateFrame("StatusBar", nil, infobox) infobox.timeout:SetStatusBarTexture(pfUI.media["img:bar"]) infobox.timeout:SetStatusBarColor(.3,1,.8,1) + infobox.duration = C_DurationUtil.CreateDuration() + infobox.timeout:SetTimerDuration(infobox.duration, Enum.StatusBarInterpolation.Immediate, Enum.StatusBarTimerDirection.RemainingTime) infobox:ClearAllPoints() infobox.text:SetAllPoints(infobox) @@ -872,11 +871,7 @@ function pfUI.api.CreateInfoBox(text, time, parent, height) end infobox.text:SetText(text) - infobox.timeout:SetMinMaxValues(0, time) - infobox.timeout:SetValue(time) - - infobox.duration = time - infobox.lastshow = GetTime() + infobox.duration:SetTimeFromStart(GetTime(), time) infobox:SetSize(infobox.text:GetStringWidth() + 50, height) infobox:SetParent(parent) diff --git a/modules/buffwatch.lua b/modules/buffwatch.lua index ca789faa..c6de3577 100644 --- a/modules/buffwatch.lua +++ b/modules/buffwatch.lua @@ -130,15 +130,14 @@ pfUI:RegisterModule("buffwatch", function () GameTooltip:Hide() end - local function StatusBarOnUpdate() - local remaining = this.endtime - GetTime() - this.bar:SetValue(remaining > 0 and remaining or 0) - - if ( this.tick or 1) > GetTime() then return else this.tick = GetTime() + .1 end - this.time:SetText(remaining > 0 and GetColoredTimeString(remaining) or "") + -- an enabled text binding writes every frame, shown or not + local function StatusBarOnShow() + this.timer:Enable() + this.parent:RefreshPosition() end - local function StatusBarRefreshParent() + local function StatusBarOnHide() + this.timer:Disable() this.parent:RefreshPosition() end @@ -183,6 +182,16 @@ pfUI:RegisterModule("buffwatch", function () frame.time:SetTextColor(1,1,1,1) frame.time:SetJustifyH("RIGHT") + -- the bar drains and the time counts down from this, engine-side + frame.duration = C_DurationUtil.CreateDuration() + frame.bar:SetTimerDuration(frame.duration, Enum.StatusBarInterpolation.Immediate, Enum.StatusBarTimerDirection.RemainingTime) + frame.timer = C_DurationUtil.CreateDurationTextBinding() + frame.timer:SetFontString(frame.time) + frame.timer:SetFormatter(GetTimeFormatter()) + frame.timer:SetDuration(frame.duration) + frame.timer:SetExpiredText("") + frame.timer:SetZeroDurationText("") + frame.icon = frame:CreateTexture(nil, "OVERLAY") frame.icon:SetSize(height, height) frame.icon:SetPoint("LEFT", frame, "LEFT", 0, 0) @@ -195,9 +204,8 @@ pfUI:RegisterModule("buffwatch", function () frame.stacks:SetJustifyV("CENTER") frame.parent = parent - frame:SetScript("OnUpdate", StatusBarOnUpdate) - frame:SetScript("OnShow", StatusBarRefreshParent) - frame:SetScript("OnHide", StatusBarRefreshParent) + frame:SetScript("OnShow", StatusBarOnShow) + frame:SetScript("OnHide", StatusBarOnHide) frame:RegisterForClicks("LeftButtonUp", "RightButtonUp") frame:SetScript("OnClick", StatusBarOnClick) @@ -261,7 +269,6 @@ pfUI:RegisterModule("buffwatch", function () frame.bars[bar].id = data[2] frame.bars[bar].unit = frame.unit frame.bars[bar].type = frame.type - frame.bars[bar].endtime = GetTime() + ( data[1] > 0 and data[1] or -1 ) -- update max duration the cached remaining values is less than -- the real one, indicates a buff renewal @@ -313,10 +320,12 @@ pfUI:RegisterModule("buffwatch", function () frame.bars[bar].icon:SetTexture(data[4]) end - -- cache maxduration - if frame.bars[bar].cacheMaxDuration ~= frame.durations[uuid][2] then - frame.bars[bar].cacheMaxDuration = frame.durations[uuid][2] - frame.bars[bar].bar:SetMinMaxValues(0, frame.durations[uuid][2]) + -- the bar spans the longest time left seen for this aura; one without + -- a time left (threshold -1) shows an empty bar and no time + if data[1] > 0 then + frame.bars[bar].duration:SetTimeFromEnd(GetTime() + data[1], frame.durations[uuid][2]) + else + frame.bars[bar].duration:Reset() end -- set stacks diff --git a/modules/energytick.lua b/modules/energytick.lua index 9728b076..30a623f5 100644 --- a/modules/energytick.lua +++ b/modules/energytick.lua @@ -166,8 +166,8 @@ pfUI:RegisterModule("energytick", function() local cost = C_Spell.GetSpellPowerCost(arg3) cost = cost and cost[1] if cost and cost.type == Enum.PowerType.Mana and cost.cost > 0 then - this.fsrSpell, this.fsrEnd = arg3, GetTime() + FIVE_SECOND_RULE - this.fsrGain = nil + this.fsrSpell, this.fsrGain = arg3, nil + this.fsr:SetTimeFromStart(GetTime(), FIVE_SECOND_RULE) end return end @@ -175,7 +175,8 @@ pfUI:RegisterModule("energytick", function() -- Unit::Update won't expire the rule while the spending spell still channels if event == "UNIT_SPELLCAST_CHANNEL_STOP" and arg1 == "player" then if this.fsrSpell and this.fsrSpell == arg3 then - this.fsrEnd, this.fsrGain = GetTime() + FIVE_SECOND_RULE, nil + this.fsrGain = nil + this.fsr:SetTimeFromStart(GetTime(), FIVE_SECOND_RULE) end return end @@ -189,7 +190,7 @@ pfUI:RegisterModule("energytick", function() -- only a gain can be the tick; a spend never touches the phase if diff > 0 and lockTick(this) then -- a tick inside the window proves regen continues through it - if this.fsrEnd and this.fsrEnd > GetTime() then + if this.fsr:IsActive() then this.fsrGain = true end end @@ -208,25 +209,15 @@ pfUI:RegisterModule("energytick", function() end this.tick = GetTime() + 0.020 -- ~50 FPS - -- five-second rule drains to nothing. The display is cosmetic and can be - -- hidden on its own; the window itself (fsrEnd/fsrGain) keeps running - -- either way -- it still dims the sweep spark below. - local remaining = this.fsrEnd and (this.fsrEnd - GetTime()) or 0 - if this.mode == "MANA" and remaining > 0 then - if C.unitframes.player.showfsr == "1" then - if fsrOverlay then - this.fsrbar:SetWidth(getBarWidth() * remaining / FIVE_SECOND_RULE) - else - this.fsrbar:SetValue(remaining) - end - this.fsrbar:Show() - else - this.fsrbar:Hide() - end - else - this.fsrSpell, this.fsrEnd, this.fsrGain = nil, nil, nil - this.fsrbar:Hide() + -- five-second rule: fsrbar drains from this.fsr by itself. The display is + -- cosmetic and can be hidden on its own; the window itself (fsr/fsrGain) + -- keeps running either way -- it still dims the sweep spark below. + local fsrUp = this.mode == "MANA" and this.fsr:IsActive() + if not fsrUp and this.fsrSpell then + this.fsrSpell, this.fsrGain = nil, nil + this.fsr:Reset() end + this.fsrbar:SetShown(fsrUp and C.unitframes.player.showfsr == "1") if not this.start then this.spark:SetAlpha(0) @@ -249,7 +240,7 @@ pfUI:RegisterModule("energytick", function() end -- dim while the rule is up and nothing has ticked inside it yet - this.spark:SetAlpha((remaining > 0 and not this.fsrGain) and .4 or 1) + this.spark:SetAlpha((fsrUp and not this.fsrGain) and .4 or 1) if not C.unitframes.player.pheight then return @@ -259,19 +250,21 @@ pfUI:RegisterModule("energytick", function() this.spark:SetPoint("LEFT", pos - ((C.unitframes.player.pheight + 5) / 2), 0) end) + energytick.fsr = C_DurationUtil.CreateDuration() + if fsrOverlay then - -- classic style: a translucent shade on top of the power bar's own fill - energytick.fsrbar = energytick:CreateTexture(nil, "ARTWORK") - energytick.fsrbar:SetColorTexture(1, 1, 1, .15) - energytick.fsrbar:SetPoint("TOPLEFT", 0, 0) - energytick.fsrbar:SetPoint("BOTTOMLEFT", 0, 0) + -- classic style: a translucent shade on top of the power bar's own fill, + -- under the spark + energytick.fsrbar = CreateFrame("StatusBar", nil, pfUI.uf.player.power.bar) + energytick.fsrbar:SetAllPoints(energytick) + energytick.fsrbar:SetColorFill(1, 1, 1, .15) + energytick:SetFrameLevel(energytick.fsrbar:GetFrameLevel() + 1) else -- a real bar below the power bar, not an overlay on top of it -- the same -- treatment nameplate castbars get relative to the health bar, so the -- window can't be mistaken for missing mana energytick.fsrbar = CreateFrame("StatusBar", nil, pfUI.uf.player) energytick.fsrbar:SetStatusBarTexture(cbtexture) - energytick.fsrbar:SetMinMaxValues(0, FIVE_SECOND_RULE) energytick.fsrbar:SetPoint("TOPLEFT", pfUI.uf.player.power.bar, "BOTTOMLEFT", 0, -default_border * 2) energytick.fsrbar:SetPoint("TOPRIGHT", pfUI.uf.player.power.bar, "BOTTOMRIGHT", 0, -default_border * 2) energytick.fsrbar:SetHeight(tonumber(C.unitframes.player.fsrheight) or 4) @@ -279,8 +272,14 @@ pfUI:RegisterModule("energytick", function() CreateBackdrop(energytick.fsrbar, default_border) CreateBackdropShadow(energytick.fsrbar) end + energytick.fsrbar:SetTimerDuration(energytick.fsr, Enum.StatusBarInterpolation.Immediate, Enum.StatusBarTimerDirection.RemainingTime) energytick.fsrbar:Hide() + -- the bar isn't parented to energytick, and OnUpdate stops with it + energytick:SetScript("OnHide", function() + this.fsrbar:Hide() + end) + energytick.spark = energytick:CreateTexture(nil, "OVERLAY") energytick.spark:SetTexture("Interface\\CastingBar\\UI-CastingBar-Spark") energytick.spark:SetHeight(C.unitframes.player.pheight + 15) diff --git a/modules/roll.lua b/modules/roll.lua index 00e78d75..dd42dc40 100644 --- a/modules/roll.lua +++ b/modules/roll.lua @@ -238,17 +238,11 @@ pfUI:RegisterModule("roll", function () f.time.bar = CreateFrame("StatusBar", "pfLootRollFrame" .. id .. "TimeBar", f.time) f.time.bar:SetAllPoints(f.time) f.time.bar:SetStatusBarTexture(pfUI.media["img:bar"]) - f.time.bar:SetMinMaxValues(0, 100) local r, g, b, a = GetStringColor(C.appearance.border.color) f.time.bar:SetStatusBarColor(r, g, b) - f.time.bar:SetValue(20) - f.time.bar:SetScript("OnUpdate", function() - if not this:GetParent():GetParent().rollID then return end - local left = GetLootRollTimeLeft(this:GetParent():GetParent().rollID) - local min, max = this:GetMinMaxValues() - if left < min or left > max then left = min end - this:SetValue(left) - end) + -- drains by itself; UpdateLootRoll sets the roll's time span + f.time.duration = C_DurationUtil.CreateDuration() + f.time.bar:SetTimerDuration(f.time.duration, Enum.StatusBarInterpolation.Immediate, Enum.StatusBarTimerDirection.RemainingTime) return f end @@ -299,7 +293,9 @@ pfUI:RegisterModule("roll", function () pfUI.roll.frames[id].name.text:SetTextColor(color.r, color.g, color.b, 1) pfUI.roll.frames[id].icon.tex:SetTexture(texture) pfUI.roll.frames[id].backdrop:SetBackdropBorderColor(color.r, color.g, color.b) - pfUI.roll.frames[id].time.bar:SetMinMaxValues(0, pfUI.roll.frames[id].rollTime) + -- both in milliseconds + local left = GetLootRollTimeLeft(pfUI.roll.frames[id].rollID) + pfUI.roll.frames[id].time.duration:SetTimeFromEnd(GetTime() + left / 1000, pfUI.roll.frames[id].rollTime / 1000) if C.loot.raritytimer == "1" then pfUI.roll.frames[id].time.bar:SetStatusBarColor(color.r, color.g, color.b, .5)