Files
pfUI/modules/buff.lua
T
Brues 907d282a68 Centralize cooldown setup and settings
Every pfUI cooldown is now made by CreateCooldown(parent, kind) in the
new api/cooldown.lua, which replaces the cooldown module. A kind names
what the cooldown times: actionbar, bags, blizzard (spellbook and
character), unitframe, indicator, nameplate, buff, tooltip, totem or
lossofcontrol. The pfCooldownType, pfCooldownStyleText,
pfCooldownStyleAnimation and pfCooldownSize fields are gone. The new
file owns everything the cooldowns share:
- the numbers' font and size
- the formatter
- each kind's numbers, sweep and sweep direction
- the GCD threshold for spell kinds
- the CooldownFrame_SetTimer hook for other addons' Cooldown frames
UpdateCooldowns applies changed settings to every cooldown at once.
GetAuraTimer moves here from the unit frames.

All settings live in one C.cooldown table, on one page: Settings ->
Cooldown. It has the shared font, size, threshold, tenths and colors,
then "Show Numbers On", "Show Sweep On" and "Reverse Sweep On" lists
with a checkbox per kind. The per-module options are removed:
- each unit frame's timer text, animation and reverse, and the
  indicators' "Show Time Left"
- the nameplate debuff timer options
- the Blizzard and foreign font sizes and toggles
- Action Bars' cooldown text size
- the reverse options of bars, bags, buffs and totems
The old appearance.cd settings are not migrated.

The player buff frame and the tooltip aura icons now show the native
countdown too, which replaces their own text and OnUpdate ticking. The
buff frame keeps its inside or below-the-icon placement. Weapon
enchants report only their time left, so they start through
SetCooldownTimeLeft: numbers, no sweep. A slim poll remains for their
charges and renewals, since no event reports those. Buff indicators
now take the aura's real duration, which gives them a correct sweep.
The spellbook and character slots now hide GCD numbers like the other
spell cooldowns.
2026-10-31 19:17:14 -05:00

374 lines
13 KiB
Lua

pfUI:RegisterModule("buff", function ()
-- Hide Blizz
BuffFrame:Hide()
BuffFrame:UnregisterAllEvents()
TemporaryEnchantFrame:Hide()
TemporaryEnchantFrame:UnregisterAllEvents()
local br, bg, bb, ba = GetStringColor(pfUI_config.appearance.border.color)
-- Player aura slot ids, enumerated once per refresh pass (ScanPlayerAuraSlots)
-- and read per button by its aura index: one GetAuraSlots walk per range
-- instead of a by-index walk per button.
local helpfulSlots, harmfulSlots = {}, {}
local function ScanPlayerAuraSlots()
ScanAuraSlots("player", "HELPFUL", helpfulSlots, 32)
ScanAuraSlots("player", "HARMFUL", harmfulSlots, 16)
end
-- weapon enchants report only their time left, and no event as their
-- charges and time run down, so the OnUpdate below refreshes their buttons
-- too: the charges, and the timer when the enchant was renewed
local function RefreshWeaponButton(buff, timeleft, charges)
timeleft = (timeleft or 0) / 1000
charges = charges or 0
buff.stacks:SetText(charges > 1 and charges or "")
local expires = GetTime() + timeleft
if timeleft <= 0 then
buff.cd:Clear()
buff.cdExpires = nil
elseif not buff.cdExpires or abs(buff.cdExpires - expires) > 1 then
SetCooldownTimeLeft(buff.cd, timeleft)
buff.cdExpires = expires
end
buff.cdStart = nil
end
local function RefreshBuffButton(buff)
if buff.btype == "HELPFUL" then
if C.buffs.separateweapons == "1" then
buff.id = buff.gid - (buff.weapon ~= nil and buff.gid or 0)
else
buff.id = buff.gid - pfUI.buff.wepbuffs.count
end
else
buff.id = buff.gid
end
if not buff.backdrop then
CreateBackdrop(buff)
CreateBackdropShadow(buff)
end
local slots = buff.btype == "HELPFUL" and helpfulSlots or harmfulSlots
local name, icon, count, dispelType, duration, expirationTime, _, _, _, spellId = C_UnitAuras.UnitAuraBySlot("player", slots[buff.id])
--detect weapon buffs
if buff.btype == "HELPFUL" and ((C.buffs.separateweapons == "0" and buff.gid <= pfUI.buff.wepbuffs.count) or (pfUI.buff.wepbuffs.count > 0 and buff.weapon ~= nil)) then
local mh, mhtime, mhcharge, oh, ohtime, ohcharge = GetWeaponEnchantInfo()
if pfUI.buff.wepbuffs.count == 2 then
if buff.gid == 1 then
buff.mode = "MAINHAND"
else
buff.mode = "OFFHAND"
end
else
if C.buffs.separateweapons == "0" then
buff.mode = mh and "MAINHAND" or oh and "OFFHAND"
else
if buff.gid == 1 then
buff.mode = mh and "MAINHAND" or oh and "OFFHAND"
else
buff:Hide()
return
end
end
end
-- Set Weapon Texture, Border and Timer
if buff.mode == "MAINHAND" then
buff.texture:SetTexture(GetInventoryItemTexture("player", 16))
buff.backdrop:SetBackdropBorderColor(GetItemQualityColor(GetInventoryItemQuality("player", 16) or 1))
RefreshWeaponButton(buff, mhtime, mhcharge)
elseif buff.mode == "OFFHAND" then
buff.texture:SetTexture(GetInventoryItemTexture("player", 17))
buff.backdrop:SetBackdropBorderColor(GetItemQualityColor(GetInventoryItemQuality("player", 17) or 1))
RefreshWeaponButton(buff, ohtime, ohcharge)
end
elseif name and (( buff.btype == "HARMFUL" and C.buffs.debuffs == "1" ) or ( buff.btype == "HELPFUL" and C.buffs.buffs == "1" )) then
-- Set Buff Texture and Border
buff.mode = buff.btype
buff.spellId = spellId
buff.texture:SetTexture(icon)
buff.stacks:SetText((count or 0) > 1 and count or "")
if buff.btype == "HARMFUL" then
local dispelColor = C_UnitAuras.GetAuraDispelTypeColor(dispelType)
buff.backdrop:SetBackdropBorderColor(dispelColor:GetRGBA())
else
buff.backdrop:SetBackdropBorderColor(br,bg,bb,ba)
end
-- start the timer from the aura's duration; permanent auras have none
local start, dur = GetAuraTimer(duration, expirationTime)
if dur <= 0 then
buff.cd:Clear()
buff.cdStart = nil
elseif buff.cdStart == nil or abs(buff.cdStart - start) > .5 or buff.cdDuration ~= dur then
CooldownFrame_SetTimer(buff.cd, start, dur, 1)
buff.cdStart = start
buff.cdDuration = dur
end
buff.cdExpires = nil
else
buff:Hide()
return
end
buff:Show()
end
local function CreateBuffButton(i, btype, weapon)
local buttonName, buttonParent
if btype == "HELPFUL" then
if weapon == 1 then
buttonName = "pfWepBuffFrame" .. i
buttonParent = pfUI.buff.wepbuffs
else
buttonName = "pfBuffFrameBuff" .. i
buttonParent = pfUI.buff.buffs
end
else
buttonName = "pfDebuffFrameBuff" .. i
buttonParent = pfUI.buff.debuffs
end
local buff = CreateFrame("Button", buttonName, buttonParent)
buff.texture = buff:CreateTexture("BuffIcon" .. i, "BACKGROUND")
buff.texture:SetTexCoord(.07,.93,.07,.93)
buff.texture:SetAllPoints(buff)
-- overlay lifts the stacks above the sweep: a child frame renders above
-- all of its parent's own regions, so they would dim under the dark
-- wedge otherwise
buff.overlay = CreateFrame("Frame", nil, buff)
buff.overlay:SetAllPoints(buff)
buff.stacks = buff.overlay:CreateFontString(nil, "OVERLAY", "GameFontNormal")
buff.stacks:SetTextColor(1,1,1,1)
buff.stacks:SetJustifyH("RIGHT")
buff.stacks:SetJustifyV("BOTTOM")
buff.stacks:SetAllPoints(buff)
-- the sweep and the duration numbers (the template covers the button and
-- starts hidden); RefreshBuffButton starts its timer
buff.cd = CreateCooldown(buff, "buff", buttonName .. "Cooldown")
buff.cd:SetFrameLevel(buff:GetFrameLevel() + 1)
buff.overlay:SetFrameLevel(buff:GetFrameLevel() + 2)
buff:RegisterForClicks("RightButtonUp")
buff.weapon = weapon
buff.btype = btype
buff.gid = i
-- PERF: OnUpdate moved to consolidated parent frame handler (see pfUI.buff:SetScript("OnUpdate"))
-- Individual buff frames no longer have their own OnUpdate
buff:SetScript("OnEnter", function()
GameTooltip:SetOwner(this, "ANCHOR_BOTTOMRIGHT")
if this.mode == this.btype then
GameTooltip:SetUnitAura("player", this.id, this.btype)
if IsShiftKeyDown() then
local aura = C_UnitAuras.GetAuraDataByIndex("player", this.id, this.btype)
local playerlist = aura and GetUnbuffedRoster(aura.name) or ""
if strlen(playerlist) > 0 then
GameTooltip:AddLine(" ")
GameTooltip:AddLine(T["Unbuffed"] .. ":", .3, 1, .8)
GameTooltip:AddLine(playerlist,1,1,1,1)
GameTooltip:Show()
end
end
elseif this.mode == "MAINHAND" then
GameTooltip:SetInventoryItem("player", 16)
elseif this.mode == "OFFHAND" then
GameTooltip:SetInventoryItem("player", 17)
end
end)
buff:SetScript("OnLeave", GameTooltip_Hide)
buff:SetScript("OnClick", function()
if this.spellId then
C_Spell.CancelSpellByID(this.spellId)
end
end)
RefreshBuffButton(buff)
return buff
end
pfUI.buff = CreateFrame("Frame", "pfGlobalBuffFrame", UIParent)
pfUI.buff:RegisterEvent("PLAYER_AURAS_CHANGED")
pfUI.buff:RegisterEvent("PLAYER_EQUIPMENT_CHANGED")
pfUI.buff:RegisterUnitEvent("UNIT_MODEL_CHANGED", "player")
pfUI.buff:RegisterEvent("BUFF_UPDATE_DURATION_SELF")
pfUI.buff:RegisterEvent("DEBUFF_UPDATE_DURATION_SELF")
pfUI.buff:SetScript("OnEvent", function()
if C.buffs.weapons == "1" then
local mh, mhtime, mhcharge, oh, ohtime, ohcharge = GetWeaponEnchantInfo()
pfUI.buff.wepbuffs.count = (mh and 1 or 0) + (oh and 1 or 0)
else
pfUI.buff.wepbuffs.count = 0
end
ScanPlayerAuraSlots()
for i=1,32 do
RefreshBuffButton(pfUI.buff.buffs.buttons[i])
end
for i=1,16 do
RefreshBuffButton(pfUI.buff.debuffs.buttons[i])
end
if C.buffs.separateweapons == "1" then
for i=1,2 do
RefreshBuffButton(pfUI.buff.wepbuffs.buttons[i])
end
end
end)
-- the buttons that can show a weapon enchant: the first two buffs, or the
-- separate weapon buttons
local weaponButtons
-- weapon enchants, see RefreshWeaponButton
pfUI.buff:SetScript("OnUpdate", function()
local now = GetTime()
if not this.nextUpdate then this.nextUpdate = now + 0.1 end
if this.nextUpdate > now then return end
this.nextUpdate = now + 0.1
local _, mhtime, mhcharge, _, ohtime, ohcharge = GetWeaponEnchantInfo()
for _, buff in ipairs(weaponButtons) do
if buff:IsShown() then
if buff.mode == "MAINHAND" then
RefreshWeaponButton(buff, mhtime, mhcharge)
elseif buff.mode == "OFFHAND" then
RefreshWeaponButton(buff, ohtime, ohcharge)
end
end
end
end)
-- CreateBuffButton refreshes each new button from the slot buffers
ScanPlayerAuraSlots()
-- Weapon Buffs
pfUI.buff.wepbuffs = CreateFrame("Frame", "pfWepBuffFrame", UIParent)
pfUI.buff.wepbuffs.count = 0
pfUI.buff.wepbuffs.buttons = {}
for i=1,2 do
pfUI.buff.wepbuffs.buttons[i] = CreateBuffButton(i, "HELPFUL", 1)
end
-- Buff Frame
pfUI.buff.buffs = CreateFrame("Frame", "pfBuffFrame", UIParent)
pfUI.buff.buffs.buttons = {}
for i=1,32 do
pfUI.buff.buffs.buttons[i] = CreateBuffButton(i, "HELPFUL")
end
-- Debuffs
pfUI.buff.debuffs = CreateFrame("Frame", "pfDebuffFrame", UIParent)
pfUI.buff.debuffs.buttons = {}
for i=1,16 do
pfUI.buff.debuffs.buttons[i] = CreateBuffButton(i, "HARMFUL")
end
weaponButtons = {
pfUI.buff.buffs.buttons[1], pfUI.buff.buffs.buttons[2],
pfUI.buff.wepbuffs.buttons[1], pfUI.buff.wepbuffs.buttons[2],
}
-- config loading
function pfUI.buff:UpdateConfigBuffButton(buff)
local fontsize = C.buffs.fontsize == "-1" and C.global.font_size or C.buffs.fontsize
local buffSize, buffSpacing = tonumber(C.buffs.size), tonumber(C.buffs.spacing)
local rowextra = C.buffs.textinside == "1" and 0 or GetCooldownFontSize(buffSize) * 1.5
local rowcount, relFrame, offsetX, offsetY
if buff.btype == "HELPFUL" then
if buff.weapon == 1 and C.buffs.separateweapons == "1" then
rowcount = floor((buff.gid-1) / tonumber(C.buffs.wepbuffrowsize))
relFrame = pfUI.buff.wepbuffs
offsetX = -(buff.gid-1-rowcount*tonumber(C.buffs.wepbuffrowsize))*(buffSize+2*buffSpacing)
offsetY = -(rowcount) * (rowextra+buffSize+2*buffSpacing)
else
rowcount = floor((buff.gid-1) / tonumber(C.buffs.buffrowsize))
relFrame = pfUI.buff.buffs
offsetX = -(buff.gid-1-rowcount*tonumber(C.buffs.buffrowsize))*(buffSize+2*buffSpacing)
offsetY = -(rowcount) * (rowextra+buffSize+2*buffSpacing)
end
else
rowcount = floor((buff.gid-1) / tonumber(C.buffs.debuffrowsize))
relFrame = pfUI.buff.debuffs
offsetX = -(buff.gid-1-rowcount*tonumber(C.buffs.debuffrowsize))*(buffSize+2*buffSpacing)
offsetY = -(rowcount) * (rowextra+buffSize+2*buffSpacing)
end
buff:SetSize(buffSize, buffSize)
buff:ClearAllPoints()
buff:SetPoint("TOPRIGHT", relFrame, "TOPRIGHT",offsetX, offsetY)
buff.stacks:SetFont(pfUI.font_default, fontsize+1, "OUTLINE")
-- the duration numbers sit inside the icon or below it
local countdown = buff.cd:GetCountdownFontString()
countdown:ClearAllPoints()
if C.buffs.textinside == "1" then
countdown:SetPoint("CENTER", buff, "CENTER", 0, 0)
else
countdown:SetPoint("TOP", buff, "BOTTOM", 0, -3)
end
end
function pfUI.buff:UpdateConfig()
local spacing = tonumber(C.buffs.spacing)
local cell = tonumber(C.buffs.size) + 2 * spacing
local rowextra = C.buffs.textinside == "1" and 0 or GetCooldownFontSize(tonumber(C.buffs.size)) * 1.5
local function SizeBuffFrame(frame, rowsize, count)
rowsize = tonumber(rowsize)
frame:SetSize(rowsize * cell, ceil(count / rowsize) * (rowextra + cell))
end
SizeBuffFrame(pfUI.buff.buffs, C.buffs.buffrowsize, 32)
pfUI.buff.buffs:SetPoint("TOPRIGHT", pfUI.minimap or UIParent, "TOPLEFT", -4 * spacing, 0)
UpdateMovable(pfUI.buff.buffs)
SizeBuffFrame(pfUI.buff.debuffs, C.buffs.debuffrowsize, 16)
pfUI.buff.debuffs:SetPoint("TOPRIGHT", pfUI.buff.buffs, "BOTTOMRIGHT", 0, 0)
UpdateMovable(pfUI.buff.debuffs)
if C.buffs.separateweapons == "1" then
pfUI.buff.wepbuffs:ClearAllPoints()
SizeBuffFrame(pfUI.buff.wepbuffs, C.buffs.wepbuffrowsize, 2)
pfUI.buff.wepbuffs:SetPoint("TOPRIGHT", pfUI.buff.debuffs, "BOTTOMRIGHT", 0, 0)
pfUI.buff.wepbuffs:Show()
UpdateMovable(pfUI.buff.wepbuffs)
else
pfUI.buff.wepbuffs:Hide()
RemoveMovable(pfUI.buff.wepbuffs)
end
for i=1,32 do
pfUI.buff:UpdateConfigBuffButton(pfUI.buff.buffs.buttons[i])
end
for i=1,16 do
pfUI.buff:UpdateConfigBuffButton(pfUI.buff.debuffs.buttons[i])
end
for i=1,2 do
pfUI.buff:UpdateConfigBuffButton(pfUI.buff.wepbuffs.buttons[i])
end
pfUI.buff:GetScript("OnEvent")()
end
pfUI.buff:UpdateConfig()
end)