diff --git a/api/unitframes.lua b/api/unitframes.lua index 08a392ed..05d705d5 100644 --- a/api/unitframes.lua +++ b/api/unitframes.lua @@ -98,6 +98,25 @@ local function StartAuraTimers(icons) end end +-- Re-reads the stacks and timer of each shown icon of an aura block from the +-- slot RefreshUnit filled it from, then restarts the timers. For auras that +-- refreshed or restacked in place, which leaves every aura in its slot. +local function RetimeAuras(icons, unitstr) + if not icons then return end + + local i = 1 + while icons[i] and icons[i]:IsShown() do + local _, _, count, _, duration, expirationTime = C_UnitAuras.UnitAuraBySlot(unitstr, icons[i].slot) + if count then + icons[i].stacks:SetText(count > 1 and count or "") + icons[i].auraStart, icons[i].auraDuration = AuraTimer(duration, expirationTime) + end + i = i + 1 + end + + StartAuraTimers(icons) +end + -- ============================================================================ -- GUID-based Roster Tracking for Smart Updates -- Only updates frames where the unit actually changed, not ALL 40 frames @@ -1110,6 +1129,14 @@ function pfUI.uf.OnEvent() this.update_portrait = true elseif event == "UNIT_AURA" then this.update_aura = true + elseif event == "UNIT_AURA_DELTA" then + -- an aura that only refreshed or restacked leaves every aura in its + -- slot, so the icons need their timers and stacks, not a rebuild + if arg2.isFullUpdate or arg2.addedAuraInstanceIDs or arg2.removedAuraInstanceIDs then + this.update_aura = true + else + this.update_auratime = true + end elseif event == "UNIT_FACTION" then this.update_pvp = true elseif event == "UNIT_COMBAT" then @@ -1298,14 +1325,19 @@ function pfUI.uf.OnUpdate() this.update_full = nil this.update_base = nil this.update_aura = nil + this.update_auratime = nil this.update_portrait = nil this.update_pvp = nil else - -- process individual events + -- process individual events; a rebuild re-times the icons as well if this.update_aura then pfUI.uf:RefreshUnit(this, "aura") this.update_aura = nil + this.update_auratime = nil this.update_base = true + elseif this.update_auratime then + pfUI.uf:RefreshUnit(this, "auratime") + this.update_auratime = nil end if this.update_portrait then @@ -1350,7 +1382,6 @@ local UNIT_EVENTS = { "UNIT_FOCUS", "UNIT_PORTRAIT_UPDATE", "UNIT_MODEL_CHANGED", "UNIT_FACTION", - "UNIT_AURA", -- frame=buff, frame=debuff "INCOMING_RESURRECT_CHANGED", -- frame=ressIcon } @@ -1370,6 +1401,18 @@ function pfUI.uf:RegisterUnitEvents(unit) end end + -- auras (frame=buff, frame=debuff, indicators): UNIT_AURA_DELTA, which also + -- reports refreshes and new durations and says what changed. It fires for + -- tokens only, so a chained unit, followed by GUID, keeps UNIT_AURA. + local aura, other = "UNIT_AURA_DELTA", "UNIT_AURA" + if self.chain then aura, other = other, aura end + self:UnregisterEvent(other) + if unit then + self:RegisterUnitEvent(aura, unit) + else + self:UnregisterEvent(aura) + end + -- UNIT_COMBAT rides along only while the frame draws combat feedback text. -- UpdateConfig clears eventunit when it toggles that, so the next -- UpdateVisibility re-runs this. @@ -1830,6 +1873,7 @@ function pfUI.uf:RefreshUnit(unit, component) local name, icon, count, _, duration, expirationTime, _, _, _, spellId = C_UnitAuras.UnitAuraBySlot(unitstr, auraSlots[i]) if name then + unit.buffs[i].slot = auraSlots[i] unit.buffs[i].texture:SetTexture(icon) unit.buffs[i]:Show() @@ -1880,6 +1924,7 @@ function pfUI.uf:RefreshUnit(unit, component) unit.debuffs[i].backdrop:SetBackdropBorderColor(dispelColor:GetRGBA()) if texture then + unit.debuffs[i].slot = auraSlots[i] unit.debuffs[i]:Show() unit.debuffs[i].auraStart, unit.debuffs[i].auraDuration = AuraTimer(duration, expirationTime) @@ -1905,6 +1950,10 @@ function pfUI.uf:RefreshUnit(unit, component) LayoutAuras(unit, unitstr) StartAuraTimers(unit.buffs) StartAuraTimers(unit.debuffs) + pfUI.events:TriggerEvent("unitframe:auras", unit) + elseif component == "auratime" then + RetimeAuras(unit.buffs, unitstr) + RetimeAuras(unit.debuffs, unitstr) end -- indicators @@ -2026,7 +2075,10 @@ function pfUI.uf:RefreshUnit(unit, component) elseif unit.hp.bar.debuffindicators then unit.hp.bar.debuffindicators:Hide() end + end + -- buff indicators carry timers and stacks, so a re-time redraws them too + if component == "all" or component == "aura" or component == "auratime" then if not unit.indicators and unit.config.buff_indicator == "1" then unit.indicators = pfUI.uf:SetupBuffIndicators(unit.config) elseif not unit.indicators then diff --git a/modules/buffwatch.lua b/modules/buffwatch.lua index 9055b1d8..ca789faa 100644 --- a/modules/buffwatch.lua +++ b/modules/buffwatch.lua @@ -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 diff --git a/modules/equipmentmanager.lua b/modules/equipmentmanager.lua index 224b2754..ba442d35 100644 --- a/modules/equipmentmanager.lua +++ b/modules/equipmentmanager.lua @@ -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") diff --git a/modules/lossofcontrol.lua b/modules/lossofcontrol.lua index e54985b0..7b08d381 100644 --- a/modules/lossofcontrol.lua +++ b/modules/lossofcontrol.lua @@ -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() diff --git a/modules/nameplates.lua b/modules/nameplates.lua index 4a17b1fd..4a6ce3b7 100644 --- a/modules/nameplates.lua +++ b/modules/nameplates.lua @@ -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