From 93ab07a6627bd9fb3882507c414b9b0f6989f601 Mon Sep 17 00:00:00 2001 From: Brues <5278969+brues-code@users.noreply.github.com> Date: Sat, 31 Oct 2026 18:07:59 -0500 Subject: [PATCH] Show cooldown text through ClassicAPI's native countdown The cooldown module no longer builds its own text frame with a 10 Hz OnUpdate per cooldown. The CooldownFrame_SetTimer hook now configures each Cooldown frame's own countdown instead: SetHideCountdownNumbers for the per-frame text toggles and omnicc's noCooldownCount, SetMinimumCountdownDuration for the GCD threshold, and GetCountdownFontString for the pfUI font and size. The engine ticks the numbers, gated by the countdownForCooldowns cvar. The cvar gets a checkbox in the Cooldown settings, and pfUI.SetupCVars turns it on. The text comes from one shared C_StringUtil numeric rule formatter, given to each frame with SetCountdownFormatter. Its breakpoints keep pfUI's format: the number in its unit's cooldown color, a unit (m/h/d) once the unit below passes 99, and tenths in the low color when milliseconds are on. Values now round up. GetColoredTimeString formats through the same formatter, so the buff, buffwatch and tooltip timers match. A config:changed callback rebuilds the breakpoints, and every frame picks them up on its next update. The paper-doll slots swap their indicator Models for Cooldown frames, as the spellbook and bags already do. They and the spellbook take the BLIZZARD tag when they are created, replacing the per-update hooks in the skin module. Models get no numbers. Hide Foreign Cooldown Animations clears the draw flags instead of setting alpha to 0, so the numbers stay visible. The buff module's sweep hides its numbers because buff.timer carries the text. --- api/api.lua | 102 ++++++++++------------- api/unitframes.lua | 16 ++-- modules/buff.lua | 5 +- modules/cooldown.lua | 157 ++++++++--------------------------- modules/gui.lua | 1 + modules/skin.lua | 12 --- pfUI.lua | 1 + skins/blizzard/character.lua | 9 ++ skins/blizzard/spellbook.lua | 1 + 9 files changed, 97 insertions(+), 207 deletions(-) diff --git a/api/api.lua b/api/api.lua index f0de9340..07b1e57b 100644 --- a/api/api.lua +++ b/api/api.lua @@ -1425,70 +1425,52 @@ function pfUI.api.DisableAutohide(frame) frame:SetAlpha(1) end +-- [ GetCountdownFormatter ] -- +-- The C_StringUtil formatter behind pfUI's times: the number in its unit's +-- cooldown color, rounded up, with a unit (m/h/d) once the unit below +-- passes 99. Cooldown frames share it instead of copying it, so new +-- breakpoints reach every countdown on its next update. +-- return the shared NumericRuleFormatter +local countdown_formatter + +local function ColorCode(color) + local r, g, b = pfUI.api.GetStringColor(color) + return pfUI.api.rgbhex(r, g, b) +end + +local function UpdateCountdownFormatter() + local cd = C.appearance.cd + local up = Enum.NumericRuleFormatRounding.Up + local tenths = cd.milliseconds == "1" + + countdown_formatter:SetBreakpoints({ + { threshold = 0, step = tenths and .1 or 1, rounding = up, format = ColorCode(cd.lowcolor) .. (tenths and "%.1f" or "%d") .. "|r" }, + { threshold = 5, step = 1, rounding = up, format = ColorCode(cd.normalcolor) .. "%d|r" }, + { threshold = 99, format = ColorCode(cd.minutecolor) .. "%d|rm", components = { { div = 60, step = 1, rounding = up } } }, + { threshold = 5940, format = ColorCode(cd.hourcolor) .. "%d|rh", components = { { div = 3600, step = 1, rounding = up } } }, + { threshold = 356400, format = ColorCode(cd.daycolor) .. "%d|rd", components = { { div = 86400, step = 1, rounding = up } } }, + }) +end + +function pfUI.api.GetCountdownFormatter() + if not countdown_formatter then + countdown_formatter = C_StringUtil.CreateNumericRuleFormatter() + UpdateCountdownFormatter() + end + return countdown_formatter +end + +pfUI.events:RegisterCallback("config:changed", function(_, category) + if countdown_formatter and category == C.appearance.cd then + UpdateCountdownFormatter() + end +end) + -- [ GetColoredTime ] -- -- 'remaining' the time in seconds that should be converted -- return a colored string including a time unit (m/h/d) -local color_day, color_hour, color_minute, color_low, color_normal function pfUI.api.GetColoredTimeString(remaining) - if not remaining then return "" end - - -- Show days if remaining is > 99 Hours (99 * 60 * 60) - if remaining > 356400 then - if not color_day then - local r,g,b,a = pfUI.api.GetStringColor(C.appearance.cd.daycolor) - color_day = pfUI.api.rgbhex(r,g,b) - end - - return color_day .. round(remaining / 86400) .. "|rd" - - -- Show hours if remaining is > 99 Minutes (99 * 60) - elseif remaining > 5940 then - if not color_hour then - local r,g,b,a = pfUI.api.GetStringColor(C.appearance.cd.hourcolor) - color_hour = pfUI.api.rgbhex(r,g,b) - end - - return color_hour .. round(remaining / 3600) .. "|rh" - - -- Show minutes if remaining is > 99 Seconds (99) - elseif remaining > 99 then - if not color_minute then - local r,g,b,a = pfUI.api.GetStringColor(C.appearance.cd.minutecolor) - color_minute = pfUI.api.rgbhex(r,g,b) - end - - return color_minute .. round(remaining / 60) .. "|rm" - - -- Show milliseconds on low - elseif remaining <= 5 and pfUI_config.appearance.cd.milliseconds == "1" then - if not color_low then - local r,g,b,a = pfUI.api.GetStringColor(C.appearance.cd.lowcolor) - color_low = pfUI.api.rgbhex(r,g,b) - end - - return color_low .. string.format("%.1f", round(remaining,1)) - - -- Show seconds on low - elseif remaining <= 5 then - if not color_low then - local r,g,b,a = pfUI.api.GetStringColor(C.appearance.cd.lowcolor) - color_low = pfUI.api.rgbhex(r,g,b) - end - - return color_low .. round(remaining) - - -- Show seconds on normal - elseif remaining >= 0 then - if not color_normal then - local r, g, b, a = pfUI.api.GetStringColor(C.appearance.cd.normalcolor) - color_normal = pfUI.api.rgbhex(r,g,b) - end - return color_normal .. round(remaining) - - -- Return empty - else - return "" - end + return pfUI.api.GetCountdownFormatter():FormatNumber(remaining) end -- [ GetColorGradient ] -- diff --git a/api/unitframes.lua b/api/unitframes.lua index 66ee509d..1b0f969e 100644 --- a/api/unitframes.lua +++ b/api/unitframes.lua @@ -928,12 +928,10 @@ function pfUI.uf:UpdateConfig() f.buffs[i].cd:SetDrawSwipe(cooldown_anim == 1) f.buffs[i].cd:SetDrawBling(cooldown_anim == 1) f.buffs[i].cd:SetReverse(cooldown_reverse) - + -- immediately show/hide existing cooldown text - if f.buffs[i].cd.pfCooldownText then - f.buffs[i].cd.pfCooldownText:SetShown(cooldown_text == 1) - end - + f.buffs[i].cd:SetHideCountdownNumbers(cooldown_text == 0) + f.buffs[i].id = i f.buffs[i]:Hide() @@ -990,12 +988,10 @@ function pfUI.uf:UpdateConfig() f.debuffs[i].cd:SetDrawSwipe(cooldown_anim == 1) f.debuffs[i].cd:SetDrawBling(cooldown_anim == 1) f.debuffs[i].cd:SetReverse(cooldown_reverse) - + -- immediately show/hide existing cooldown text - if f.debuffs[i].cd.pfCooldownText then - f.debuffs[i].cd.pfCooldownText:SetShown(cooldown_text == 1) - end - + f.debuffs[i].cd:SetHideCountdownNumbers(cooldown_text == 0) + f.debuffs[i].id = i f.debuffs[i]:Hide() diff --git a/modules/buff.lua b/modules/buff.lua index 79ed1c12..8349cc76 100644 --- a/modules/buff.lua +++ b/modules/buff.lua @@ -130,7 +130,7 @@ pfUI:RegisterModule("buff", function () -- overlay lifts the texts above the sweep: a child frame renders above -- all of its parent's own regions, so the timer would dim under the - -- dark wedge otherwise (same reason pfCooldownText is its own frame) + -- dark wedge otherwise buff.overlay = CreateFrame("Frame", nil, buff) buff.overlay:SetAllPoints(buff) @@ -148,8 +148,9 @@ pfUI:RegisterModule("buff", function () -- radial sweep (the template covers the button and starts hidden). -- RefreshBuffButton drives it through the frame's own methods, never -- CooldownFrame_SetTimer, so the cooldown module's hook leaves it alone; - -- text stays with buff.timer + -- text stays with buff.timer, so the frame's own numbers are hidden buff.cd = CreateFrame("Cooldown", buttonName .. "Cooldown", buff, "CooldownFrameTemplate") + buff.cd:SetHideCountdownNumbers(true) buff.cd:SetFrameLevel(buff:GetFrameLevel() + 1) buff.overlay:SetFrameLevel(buff:GetFrameLevel() + 2) diff --git a/modules/cooldown.lua b/modules/cooldown.lua index c9f7102c..1d9606ea 100644 --- a/modules/cooldown.lua +++ b/modules/cooldown.lua @@ -1,71 +1,17 @@ pfUI:RegisterModule("cooldown", function () - -- cache values - -- local lowcolor = {strsplit(",", C.appearance.cd.lowcolor)} - -- local normalcolor = {strsplit(",", C.appearance.cd.normalcolor)} - -- local minutecolor = {strsplit(",", C.appearance.cd.minutecolor)} - -- local hourcolor = {strsplit(",", C.appearance.cd.hourcolor)} - -- local daycolor = {strsplit(",", C.appearance.cd.daycolor)} - - local function pfCooldownOnUpdate() - -- Throttle FIRST. One of these runs per visible cooldown text, every frame, - -- so anything above this gate is multiplied by the frame rate and by how - -- many cooldowns are ticking. - local now = GetTime() - if (this.tick or 0) > now then return end - this.tick = now + .1 - - local parent = this:GetParent() - if not parent then this:Hide() return end - - -- avoid to set cooldowns on invalid frames. The cooldown frame is stashed - -- at creation: resolving it as _G[parent:GetName() .. "Cooldown"] built and - -- interned that string twice per call, and this is the hottest path in the - -- UI. The stashed reference is also the frame itself rather than a guess - -- from its parent's name, so it holds for cooldowns named anything else. - if this.cooldown and not this.cooldown:IsShown() then - this:Hide() - return - end - - -- fix own alpha value (should be inherited, but somehow isn't always) - if this:GetAlpha() ~= parent:GetAlpha() then - this:SetAlpha(parent:GetAlpha()) - end - - if this.start < now then - -- calculating remaining time as it should be - local remaining = this.duration - (now - this.start) - if remaining >= 0 then - this.text:SetText(GetColoredTimeString(remaining)) - else - this:Hide() - end - else - -- I have absolutely no idea, but it works: - -- https://github.com/Stanzilla/WoWUIBugs/issues/47 - local currentTime = time() - local startupTime = currentTime - now - -- just a simplification of: ((2^32) - (start * 1000)) / 1000 - local cdTime = (2 ^ 32) / 1000 - this.start - local cdStartTime = startupTime - cdTime - local cdEndTime = cdStartTime + this.duration - local remaining = cdEndTime - currentTime - - if remaining >= 0 then - this.text:SetText(GetColoredTimeString(remaining)) - else - this:Hide() - end - end - end + -- The numbers are the Cooldown frame's own countdown (ClassicAPI): the + -- engine ticks them, gated by the countdownForCooldowns cvar, and writes + -- them through pfUI's shared formatter. pfUI only configures each frame as + -- its timer is set. local function GetCooldownHeight(cooldown) return cooldown:GetParent() and cooldown:GetParent():GetHeight() or cooldown:GetHeight() or 0 end - -- sizes the text to the cooldown's current height; the dynamic size follows - -- the frame, so SetCooldown calls this again whenever that height changes - local function pfUpdateCooldownFont(cooldown) + -- sizes the numbers to the cooldown's current height; the dynamic size + -- follows the frame, so SetCooldown calls this again whenever that height + -- changes + local function UpdateCountdownFont(cooldown) local size if not cooldown.pfCooldownType then size = tonumber(C.appearance.cd.font_size_foreign) @@ -77,88 +23,53 @@ pfUI:RegisterModule("cooldown", function () size = tonumber(C.appearance.cd.font_size) end + local height = GetCooldownHeight(cooldown) + cooldown.pfCountdownHeight = height + -- enforce dynamic font size if C.appearance.cd.dynamicsize == "1" then - local height = GetCooldownHeight(cooldown) - cooldown.pfCooldownText.height = height size = math.max((height > 0 and height * .64 or 16), size) end - cooldown.pfCooldownText.text:SetFont(pfUI.media[C.appearance.cd.font], size, "OUTLINE") - end - - local textcount = 0 - local function pfCreateCoolDown(cooldown, start, duration) - textcount = textcount + 1 - local name = cooldown.GetName and cooldown:GetName() or "pfCooldown" .. textcount - - cooldown.pfCooldownText = CreateFrame("Frame", name .. "Text", cooldown:GetParent()) - cooldown.pfCooldownText.cooldown = cooldown - cooldown.pfCooldownText:SetAllPoints(cooldown) - cooldown.pfCooldownText:SetFrameLevel(cooldown:GetParent():GetFrameLevel() + 2) - cooldown.pfCooldownText.text = cooldown.pfCooldownText:CreateFontString(name .. "TextString", "OVERLAY") - - pfUpdateCooldownFont(cooldown) - cooldown.pfCooldownText.text:SetPoint("CENTER", cooldown.pfCooldownText, "CENTER", 0, 0) - cooldown.pfCooldownText:SetScript("OnUpdate", pfCooldownOnUpdate) + cooldown:GetCountdownFontString():SetFont(pfUI.media[C.appearance.cd.font], size, "OUTLINE") end -- hook - local function SetCooldown(this, start, duration, enable) - -- abort on unknown frames - if C.appearance.cd.foreign == "0" and not this.pfCooldownType then + local function SetCooldown(this) + -- 1.12's indicator Models have no countdown and keep the stock timer + if not this:IsObjectType("Cooldown") then return end + + -- no numbers on unknown frames, nor on frames with omnicc's disable flag + if (C.appearance.cd.foreign == "0" and not this.pfCooldownType) or this.noCooldownCount then + this:SetHideCountdownNumbers(true) return end - -- add support for omnicc's disable flag - if this.noCooldownCount then - return + this:SetHideCountdownNumbers(this.pfCooldownStyleText == 0) + + -- no numbers for global cooldowns, except on frames that time auras + if not this.pfCooldownType or this.pfCooldownType == "NOGCD" then + this:SetMinimumCountdownDuration(tonumber(C.appearance.cd.threshold) * 1000) + else + this:SetMinimumCountdownDuration(0) end - -- realign the indicator Models that stock buttons build; the model is - -- drawn at 36px and needs scaling to its button. A Cooldown draws its - -- sweep to its own rect. - local parent = this.GetParent and not this:IsObjectType("Cooldown") and this:GetParent() - if parent and parent:GetWidth() / 36 > 0 then - this:SetScale(parent:GetWidth() / 36) - this:SetPoint("TOPLEFT", parent, "TOPLEFT", -1, 1) - this:SetPoint("BOTTOMRIGHT", parent, "BOTTOMRIGHT", 1, -1) - end - - -- don't draw global cooldowns - if this.pfCooldownType == "NOGCD" and duration < tonumber(C.appearance.cd.threshold) then - start, duration = 0, 0 - end - - -- disable GCDs on non pfUI frames - if not this.pfCooldownType and duration < tonumber(C.appearance.cd.threshold) then - start, duration = 0, 0 - end + this:SetCountdownFormatter(GetCountdownFormatter()) -- hide foreign animations; frames that set pfCooldownStyleAnimation - -- configure their own sweep (SetDrawSwipe, SetReverse) + -- configure their own sweep. The draw flags, not alpha, so the numbers + -- stay visible if not this.pfCooldownStyleAnimation and C.appearance.cd.hideanim == "1" then - this:SetAlpha(0) + this:SetDrawSwipe(false) + this:SetDrawBling(false) end - -- print time as text on cooldown frames - if ( not this.pfCooldownStyleText or this.pfCooldownStyleText == 1) - and start > 0 and duration > 0 and (not enable or enable > 0) then - if( not this.pfCooldownText ) then - pfCreateCoolDown(this, start, duration) - elseif C.appearance.cd.dynamicsize == "1" and GetCooldownHeight(this) ~= this.pfCooldownText.height then - pfUpdateCooldownFont(this) - end - - this.pfCooldownText.start = start - this.pfCooldownText.duration = duration - this.pfCooldownText:Show() - elseif(this.pfCooldownText) then - this.pfCooldownText:Hide() + if GetCooldownHeight(this) ~= this.pfCountdownHeight then + UpdateCountdownFont(this) end end -- hook the SetTimer function that every cooldown, Cooldown frame or -- stock indicator Model, is driven through. hooksecurefunc("CooldownFrame_SetTimer", SetCooldown) -end) \ No newline at end of file +end) diff --git a/modules/gui.lua b/modules/gui.lua index 39abeaeb..a6e16143 100644 --- a/modules/gui.lua +++ b/modules/gui.lua @@ -1597,6 +1597,7 @@ pfUI:RegisterModule("gui", function () end) CreateGUIEntry(T["Settings"], T["Cooldown"], function() + CreateConfig(nil, COUNTDOWN_FOR_COOLDOWNS_TEXT, "CVAR", "countdownForCooldowns", "checkbox") CreateConfig(U["buff"], T["Show Milliseconds When Timer Runs Out"], C.appearance.cd, "milliseconds", "checkbox") CreateConfig(nil, T["Cooldown Color (Less than 3 Sec)"], C.appearance.cd, "lowcolor", "color") CreateConfig(nil, T["Cooldown Color (Seconds)"], C.appearance.cd, "normalcolor", "color") diff --git a/modules/skin.lua b/modules/skin.lua index 69081ee8..3824a044 100644 --- a/modules/skin.lua +++ b/modules/skin.lua @@ -15,18 +15,6 @@ pfUI:RegisterModule("skin", function () DurabilityFrame:SetFrameLevel(1) DurabilityFrame.SetPoint = function() return end - if C.appearance.cd.blizzard == "1" then - hooksecurefunc("PaperDollItemSlotButton_Update", function() - local cooldown = _G[this:GetName().."Cooldown"] - if cooldown then cooldown.pfCooldownType = "BLIZZARD" end - end) - - hooksecurefunc("SpellButton_UpdateButton", function() - local cooldown = _G[this:GetName().."Cooldown"] - if cooldown then cooldown.pfCooldownType = "BLIZZARD" end - end) - end - for i = 1, UIDROPDOWNMENU_MAXLEVELS do CreateBackdrop(_G["DropDownList"..i.."MenuBackdrop"], nil, true, .8) CreateBackdropShadow(_G["DropDownList"..i.."MenuBackdrop"]) diff --git a/pfUI.lua b/pfUI.lua index 0acb886a..af6c675c 100644 --- a/pfUI.lua +++ b/pfUI.lua @@ -467,6 +467,7 @@ function pfUI.SetupCVars() SetCVar("autoSelfCast", "1") SetCVar("profanityFilter", "0") + SetCVar("countdownForCooldowns", "1") MultiActionBar_ShowAllGrids() ALWAYS_SHOW_MULTIBARS = "1" diff --git a/skins/blizzard/character.lua b/skins/blizzard/character.lua index 34b179e4..04c870f5 100644 --- a/skins/blizzard/character.lua +++ b/skins/blizzard/character.lua @@ -193,6 +193,15 @@ pfUI:RegisterSkin("Character", function () HandleIcon(frame.backdrop, _G["Character"..slotName.."IconTexture"]) + -- native cooldown frame in place of the template's indicator Model, + -- which has no countdown numbers. PaperDollItemSlotButton_Update reads + -- the "