Files
pfUI/modules/buff.lua
T
Brues a721f788a9 Let each cooldown frame apply its own sweep settings
The sweep options reached the frames only through the cooldown module's
CooldownFrame_SetTimer hook, which set alpha 0 for a hidden animation and
SetReverse(true) for a reversed one. With that module disabled, Show Timer
Animation and Reverse did nothing, and reverse never turned back off
without a reload. The player buff sweeps had no other path at all.

Each owner now configures its own frame. Player buffs drive their
Cooldown with SetCooldown and Clear, and only while Show Timer Animation
is on. Unit frame auras, raid aura indicators and nameplate debuffs hide
the sweep with SetDrawSwipe and SetDrawBling. Every frame with a reverse
option calls SetReverse. The hook keeps the timer text and Hide Foreign
Cooldown Animations.

Nameplate debuffs hid the sweep with alpha, which the plate's SetAlpha
copied down on every target change (ClassicAPI keeps 1.12's alpha under
default plates), so hidden sweeps came back. Their settings now come from
UpdateDebuffConfig alone.

Bag slots get a native Cooldown like bank slots, in place of the
template's indicator Model, so Reverse Cooldown Animation covers bags and
Hide Foreign Cooldown Animations no longer hides them.

The buff, unit frame, nameplate and totem options apply without a reload.

Fixes #72.
2026-10-30 10:46:00 -05:00

411 lines
15 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
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 and Border
if buff.mode == "MAINHAND" then
buff.texture:SetTexture(GetInventoryItemTexture("player", 16))
buff.backdrop:SetBackdropBorderColor(GetItemQualityColor(GetInventoryItemQuality("player", 16) or 1))
elseif buff.mode == "OFFHAND" then
buff.texture:SetTexture(GetInventoryItemTexture("player", 17))
buff.backdrop:SetBackdropBorderColor(GetItemQualityColor(GetInventoryItemQuality("player", 17) or 1))
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.expirationTime = expirationTime
buff.stackCount = count
buff.spellId = spellId
buff.texture:SetTexture(icon)
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
else
buff:Hide()
return
end
-- drive the sweep from the aura duration (weapon enchants and permanent
-- auras report no usable total, so those buttons stay text-only)
if C.buffs.cooldown_anim == "1" and buff.mode == buff.btype and duration and duration > 0 and expirationTime and expirationTime > 0 then
local now = GetTime()
local timeleft = expirationTime - now
if timeleft > 0 then
local effDuration = duration > timeleft and duration or timeleft
local newStart = now + timeleft - effDuration
if buff.cdStart == nil or abs(buff.cdStart - newStart) > .5 or buff.cdDuration ~= effDuration then
buff.cd:SetCooldown(newStart, effDuration)
buff.cdStart = newStart
buff.cdDuration = effDuration
end
else
buff.cd:Clear()
buff.cdStart = nil
end
else
buff.cd:Clear()
buff.cdStart = nil
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 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)
buff.overlay = CreateFrame("Frame", nil, buff)
buff.overlay:SetAllPoints(buff)
buff.timer = buff.overlay:CreateFontString(nil, "OVERLAY", "GameFontNormal")
buff.timer:SetTextColor(1,1,1,1)
buff.timer:SetJustifyH("CENTER")
buff.timer:SetJustifyV("CENTER")
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)
-- 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
buff.cd = CreateFrame("Cooldown", buttonName .. "Cooldown", buff, "CooldownFrameTemplate")
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)
-- PERF: Consolidated OnUpdate handler for all buff timers
-- This replaces 50 individual OnUpdate handlers with a single one
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
-- Cache weapon enchant info once per update cycle
local mh, mhtime, mhcharge, oh, ohtime, ohcharge = GetWeaponEnchantInfo()
-- Update all visible buff buttons
local buttons = pfUI.buff.buffs.buttons
for i = 1, 32 do
local buff = buttons[i]
if buff:IsShown() then
local timeleft, stacks = 0, 0
if buff.mode == buff.btype then
timeleft = buff.expirationTime > 0 and (buff.expirationTime - now) or 0
stacks = buff.stackCount or 0
elseif buff.mode == "MAINHAND" then
timeleft = mhtime and mhtime / 1000 or 0
stacks = mhcharge or 0
elseif buff.mode == "OFFHAND" then
timeleft = ohtime and ohtime / 1000 or 0
stacks = ohcharge or 0
end
buff.timer:SetText(timeleft > 0 and GetColoredTimeString(timeleft) or "")
buff.stacks:SetText(stacks > 1 and stacks or "")
end
end
-- Update all visible debuff buttons
buttons = pfUI.buff.debuffs.buttons
for i = 1, 16 do
local buff = buttons[i]
if buff:IsShown() then
local timeleft = buff.expirationTime > 0 and (buff.expirationTime - now) or 0
local stacks = buff.stackCount or 0
buff.timer:SetText(timeleft > 0 and GetColoredTimeString(timeleft) or "")
buff.stacks:SetText(stacks > 1 and stacks or "")
end
end
-- Update weapon buff buttons if separate
if C.buffs.separateweapons == "1" then
buttons = pfUI.buff.wepbuffs.buttons
for i = 1, 2 do
local buff = buttons[i]
if buff:IsShown() then
local timeleft, stacks = 0, 0
if buff.mode == "MAINHAND" then
timeleft = mhtime and mhtime / 1000 or 0
stacks = mhcharge or 0
elseif buff.mode == "OFFHAND" then
timeleft = ohtime and ohtime / 1000 or 0
stacks = ohcharge or 0
end
buff.timer:SetText(timeleft > 0 and GetColoredTimeString(timeleft) or "")
buff.stacks:SetText(stacks > 1 and stacks or "")
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
-- 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 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) * ((C.buffs.textinside == "1" and 0 or (fontsize*1.5))+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) * ((C.buffs.textinside == "1" and 0 or (fontsize*1.5))+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) * ((C.buffs.textinside == "1" and 0 or (fontsize*1.5))+buffSize+2*buffSpacing)
end
buff:SetSize(buffSize, buffSize)
buff.cd:SetReverse(C.buffs.cooldown_reverse == "1")
buff:ClearAllPoints()
buff:SetPoint("TOPRIGHT", relFrame, "TOPRIGHT",offsetX, offsetY)
buff.timer:SetFont(pfUI.font_default, fontsize, "OUTLINE")
buff.stacks:SetFont(pfUI.font_default, fontsize+1, "OUTLINE")
buff.timer:SetHeight(fontsize * 1.3)
buff.timer:ClearAllPoints()
if C.buffs.textinside == "1" then
buff.timer:SetAllPoints(buff)
else
buff.timer:SetPoint("TOP", buff, "BOTTOM", 0, -3)
end
end
function pfUI.buff:UpdateConfig()
local fontsize = C.buffs.fontsize == "-1" and C.global.font_size or C.buffs.fontsize
local spacing = tonumber(C.buffs.spacing)
local cell = tonumber(C.buffs.size) + 2 * spacing
local rowextra = C.buffs.textinside == "1" and 0 or (fontsize * 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)