Drive auras from UNIT_AURA_DELTA

UNIT_AURA says nothing about what changed and stays silent when a buff
is refreshed or its duration packet lands, so a recast HoT kept its old
timer until something else rebuilt the row. UNIT_AURA_DELTA reports
added, updated and removed aura instances, refreshes included.

Unit frames register it on their own token. An add, a removal or a full
update rebuilds the auras as before; an update-only delta re-reads the
slot behind each shown icon for its stacks and timer and redraws the
buff indicators, without a relayout or a dispel scan. Frames that
follow a GUID (the target-of-X chain) stay on UNIT_AURA, since the
delta only fires for tokens. A finished aura layout announces itself
as "unitframe:auras".

buffwatch drops its 0.4s rescan: bars rebuild on the delta, on login
and on a target change, and a timer wakes the frame when the next aura
crosses the bar threshold. Bars re-anchor when the player or target
frame lays out its aura rows, and a whitelist or blacklist click
applies at once.

Nameplates, the loss of control alert and the equipment manager's
mount/stealth/dining check listen to the delta as well.
This commit is contained in:
Brues
2026-10-31 11:32:47 -05:00
parent 8b5a27f9be
commit b6f58b0e04
5 changed files with 98 additions and 17 deletions
+38 -9
View File
@@ -103,6 +103,7 @@ pfUI:RegisterModule("buffwatch", function ()
fcache[tostring(config)] = nil -- invalidate so the new entry takes effect immediately
DEFAULT_CHAT_FRAME:AddMessage("|cff33ffcc" .. skill .. "|r" .. T["is now blacklisted."])
end
this.parent.Refresh()
elseif this.parent.unit == "player" then
local aura = C_UnitAuras.GetAuraDataByIndex("player", this.id, this.type)
if aura and aura.spellId then C_Spell.CancelSpellByID(aura.spellId) end
@@ -246,7 +247,7 @@ pfUI:RegisterModule("buffwatch", function ()
table.sort(frame.buffs, frame.buffcmp)
-- create a buff bar for each below threshold
local bar = 1
local bar, crossing = 1, nil
for id, data in pairs(frame.buffs) do
if data[1] and ((data[1] ~= 0 and data[1] < frame.threshold) or frame.threshold == -1) -- timeleft checks
and data[3] and data[3] ~= "" -- buff has a name
@@ -340,6 +341,10 @@ pfUI:RegisterModule("buffwatch", function ()
frame.bars[bar]:Show()
bar = bar + 1
elseif data[1] and data[1] > 0 and data[3] and data[4] and frame.threshold ~= -1 then
-- still above the threshold: its bar shows once it drops under
local wait = data[1] - frame.threshold
if not crossing or wait < crossing then crossing = wait end
end
end
@@ -347,13 +352,11 @@ pfUI:RegisterModule("buffwatch", function ()
for i = bar, table.getn(frame.bars) do
frame.bars[i]:Hide()
end
end
-- Create a new Buff Bar
local function BuffBarFrameOnUpdate()
if ( this.tick or 1) > GetTime() then return else this.tick = GetTime() + .4 end
RefreshBuffBarFrame(this)
this:RefreshPosition()
-- no event marks an aura's time left dropping under the threshold, so
-- wake when the next one does
if frame.crossing then frame.crossing:Cancel() end
frame.crossing = crossing and C_Timer.NewTimer(crossing + .1, frame.Refresh) or nil
end
local function RefreshPosition(self)
@@ -373,6 +376,19 @@ pfUI:RegisterModule("buffwatch", function ()
end
end
-- every watch frame: one stacks on the bars of another, so all of them
-- re-anchor together
local watched = {}
local function RefreshPositions()
for _, frame in pairs(watched) do frame:RefreshPosition() end
end
-- the bars sit above their unit frame's aura rows, which move when the
-- rows lay out again
pfUI.events:RegisterCallback("unitframe:auras", function(_, unit)
if unit == pfUI.uf.player or unit == pfUI.uf.target then RefreshPositions() end
end, "buffwatch")
local function CreateBuffBarFrame(unit, type)
local framename = "pf" .. unit .. ( type == "HARMFUL" and "Debuff" or "Buff" ) .. "Bar"
local frame = _G[framename] or CreateFrame("Frame", framename, UIParent)
@@ -388,9 +404,22 @@ pfUI:RegisterModule("buffwatch", function ()
end
frame.RefreshPosition = RefreshPosition
frame.Refresh = function()
RefreshBuffBarFrame(frame)
RefreshPositions()
end
-- OnEvent (UNIT_AURA) doesn't trigger properly on buff refresh
frame:SetScript("OnUpdate", BuffBarFrameOnUpdate)
-- UNIT_AURA_DELTA reports refreshes and new durations as well, so the bars
-- follow the auras on events alone. A new target is another unit, whose
-- auras no delta describes.
frame:RegisterUnitEvent("UNIT_AURA_DELTA", frame.unit)
frame:RegisterEvent("PLAYER_ENTERING_WORLD")
if frame.unit == "target" then frame:RegisterEvent("PLAYER_TARGET_CHANGED") end
frame:SetScript("OnEvent", frame.Refresh)
table.insert(watched, frame)
-- the first read waits until the caller has configured the frame
RunNextFrame(frame.Refresh)
return frame
end
+1 -1
View File
@@ -1536,7 +1536,7 @@ pfUI:RegisterModule("equipmentmanager", function()
engineEvents:RegisterEvent("PLAYER_ENTERING_WORLD")
engineEvents:RegisterEvent("PLAYER_LEAVING_WORLD")
engineEvents:RegisterUnitEvent("UNIT_AURA", "player") -- mounted, stealth, dining
engineEvents:RegisterUnitEvent("UNIT_AURA_DELTA", "player") -- mounted, stealth, dining
engineEvents:RegisterEvent("UPDATE_SHAPESHIFT_FORM")
engineEvents:RegisterEvent("ZONE_CHANGED")
engineEvents:RegisterEvent("ZONE_CHANGED_INDOORS")
+2 -2
View File
@@ -69,10 +69,10 @@ pfUI:RegisterModule("lossofcontrol", function ()
self:Show()
end
-- a refreshed debuff (UNIT_AURA) or a second kick on the same school
-- a refreshed debuff (UNIT_AURA_DELTA) or a second kick on the same school
-- (SPELL_UPDATE_COOLDOWN) changes the timer without a loss of control event
frame:SetScript("OnShow", function()
this:RegisterUnitEvent("UNIT_AURA", "player")
this:RegisterUnitEvent("UNIT_AURA_DELTA", "player")
this:RegisterEvent("SPELL_UPDATE_COOLDOWN")
end)
frame:SetScript("OnHide", function()
+3 -3
View File
@@ -501,7 +501,7 @@ nameplates:RegisterEvent("NAME_PLATE_CREATED")
nameplates:RegisterEvent("NAME_PLATE_UNIT_ADDED")
nameplates:RegisterEvent("NAME_PLATE_UNIT_REMOVED")
nameplates:RegisterEvent("UPDATE_MOUSEOVER_UNIT")
-- UNIT_AURA / UNIT_FLAGS / UNIT_SPELLCAST_* are registered per plate, on the
-- UNIT_AURA_DELTA / UNIT_FLAGS / UNIT_SPELLCAST_* are registered per plate, on the
-- plate's own frame, against its own token -- see OnCreate and
-- NAME_PLATE_UNIT_ADDED.
nameplates:RegisterEvent("PLAYER_GUILD_UPDATE")
@@ -606,7 +606,7 @@ nameplates:RegisterUnitEvent("UNIT_SPELLCAST_NOT_INTERRUPTIBLE", "target", "focu
-- Point this plate's own subscriptions at the token it just took. On a
-- recycled frame these replace the previous unit rather than stacking:
-- RegisterUnitEvent on an already-filtered registration swaps the units.
plate.nameplate:RegisterUnitEvent("UNIT_AURA", arg1)
plate.nameplate:RegisterUnitEvent("UNIT_AURA_DELTA", arg1)
plate.nameplate:RegisterUnitEvent("UNIT_FLAGS", arg1)
plate.nameplate:RegisterUnitEvent("UNIT_TARGET", arg1)
plate.nameplate:RegisterUnitEvent("UNIT_SPELLCAST_START", arg1)
@@ -760,7 +760,7 @@ nameplates:RegisterUnitEvent("UNIT_SPELLCAST_NOT_INTERRUPTIBLE", "target", "focu
-- new token; _REMOVED drops it, which matters because freed slots are
-- reused and a stale token would feed this frame another unit's events.
nameplate:SetScript("OnEvent", function()
if event == "UNIT_AURA" then
if event == "UNIT_AURA_DELTA" then
-- a fresh C_UnitAuras read next tick rather than waiting out the 0.5s
-- throttle -- covers expiry, dispels, refreshes and stack changes
this.auraUpdate = true