BulwarkFrame v0.2.0
This commit is contained in:
@@ -32,6 +32,43 @@ BF.state = {
|
||||
|
||||
-- ---- buff scan --------------------------------------------------------------------------
|
||||
|
||||
-- Walks one of the 64-slot unit aura enumerations looking for the buffer, and returns the
|
||||
-- (possibly raised) stack count plus the found flag.
|
||||
--
|
||||
-- Deliberately a file-scope function taking its inputs as arguments rather than a closure built
|
||||
-- inside ScanAuras: as a closure it allocated one object per CALL plus one more per aura SLOT (the
|
||||
-- per-slot pcall wrapper), and ScanAuras is the PLAYER_AURAS_CHANGED handler -- an event that
|
||||
-- fires on every stack change of the buffer, i.e. on every absorbed hit. Roughly two dozen
|
||||
-- throwaway closures per incoming melee swing is exactly the GC churn the rest of this addon
|
||||
-- takes care to avoid.
|
||||
--
|
||||
-- The per-slot pcall went with it. It was never per-slot error recovery either: the old code
|
||||
-- aborted the WHOLE walk as soon as one slot failed, so a single pcall around the walk (see the
|
||||
-- call sites) covers the same ground -- and UnitBuff/UnitDebuff on "player" with an integer index
|
||||
-- does not throw to begin with. The one behavioural difference is that a walk which errors part
|
||||
-- way through now contributes nothing instead of its partial count, which is the safer of the two.
|
||||
--
|
||||
-- Note the id position differs between the two calls -- an easy trap: reading return 4 from
|
||||
-- UnitBuff (or 3 from UnitDebuff) silently yields nil and the aura is never matched.
|
||||
local function scanStacks(st, fn, idPos, poolId, timeId, stacks, found)
|
||||
local i = 1
|
||||
while i <= 64 do
|
||||
local r1, r2, r3, r4 = fn("player", i)
|
||||
if r1 == nil then break end
|
||||
local id = (idPos == 4) and tonumber(r4) or tonumber(r3)
|
||||
if id == poolId or id == timeId then
|
||||
local n = tonumber(r2)
|
||||
if n and n > stacks then
|
||||
stacks = n
|
||||
found = true
|
||||
st.hasPool = true
|
||||
end
|
||||
end
|
||||
i = i + 1
|
||||
end
|
||||
return stacks, found
|
||||
end
|
||||
|
||||
-- Scans the player's buffs once and writes pool/time into BF.state.
|
||||
-- GetPlayerBuff walks HELPFUL slots; GetPlayerBuffID (SuperWoW) gives the spell id, which is the
|
||||
-- whole reason SuperWoW is a hard requirement -- matching the icon texture instead would collide
|
||||
@@ -83,28 +120,17 @@ function BF.ScanAuras()
|
||||
-- The durability aura runs as a DEBUFF, and the HELPFUL player-buff enumeration above never
|
||||
-- lists it. Reading only that API is why a full buffer reported a single stack -- and one stack
|
||||
-- renders as 61 against a maximum of 6117: a plausible number, wrong by a factor of a hundred.
|
||||
-- Note the id position differs between the two calls -- an easy trap: reading return 4 from
|
||||
-- UnitBuff (or 3 from UnitDebuff) silently yields nil and the aura is never matched.
|
||||
local scanStacks = function(fn, idPos)
|
||||
local i = 1
|
||||
while i <= 64 do
|
||||
local r1, r2, r3, r4
|
||||
local ok = pcall(function() r1, r2, r3, r4 = fn("player", i) end)
|
||||
if not ok or r1 == nil then break end
|
||||
local id = (idPos == 4) and tonumber(r4) or tonumber(r3)
|
||||
if id == poolId or id == timeId then
|
||||
local n = tonumber(r2)
|
||||
if n and n > stacks then
|
||||
stacks = n
|
||||
found = true
|
||||
st.hasPool = true
|
||||
end
|
||||
end
|
||||
i = i + 1
|
||||
end
|
||||
--
|
||||
-- The walk itself lives at file scope (see scanStacks above). pcall is handed the arguments
|
||||
-- directly instead of a wrapper closure, so this path allocates nothing at all.
|
||||
if UnitDebuff then
|
||||
local ok, s, f = pcall(scanStacks, st, UnitDebuff, 4, poolId, timeId, stacks, found)
|
||||
if ok then stacks, found = s, f end
|
||||
end
|
||||
if UnitBuff then
|
||||
local ok, s, f = pcall(scanStacks, st, UnitBuff, 3, poolId, timeId, stacks, found)
|
||||
if ok then stacks, found = s, f end
|
||||
end
|
||||
if UnitDebuff then pcall(function() scanStacks(UnitDebuff, 4) end) end
|
||||
if UnitBuff then pcall(function() scanStacks(UnitBuff, 3) end) end
|
||||
|
||||
-- A buffer aura with no applications at all still means "buffer up": 1.12 reports a
|
||||
-- single-stack aura without a count, and treating that as 0 would blank the bar while it is
|
||||
@@ -179,6 +205,15 @@ driver:RegisterEvent("PLAYER_REGEN_DISABLED")
|
||||
driver:RegisterEvent("PLAYER_REGEN_ENABLED")
|
||||
driver:RegisterEvent("UNIT_INVENTORY_CHANGED")
|
||||
|
||||
-- Max health is the base of EVERY number on screen (maxPool = maxHP/5), so it must not be read
|
||||
-- only on zone-in and gear swaps. A stamina buff landing at the raid entrance -- Fortitude, Gift
|
||||
-- of the Wild, food, a world buff -- moves max health without either of those events, and the
|
||||
-- readout would then be ~10% low for the whole encounter. UNIT_MAXHEALTH carries a unit token in
|
||||
-- arg1 and fires for every group member, so it has to be filtered; PLAYER_LEVEL_UP carries the
|
||||
-- new level in arg1 and must NOT go through that filter.
|
||||
driver:RegisterEvent("UNIT_MAXHEALTH")
|
||||
driver:RegisterEvent("PLAYER_LEVEL_UP")
|
||||
|
||||
-- SuperWoW's UNIT_CASTEVENT is the swing source: arg3 is "MAINHAND"/"OFFHAND" for auto-attacks.
|
||||
-- It is language independent and fires on misses too, unlike parsing combat-log text -- which is
|
||||
-- how the third-party swing timers get it wrong on a dodge.
|
||||
@@ -192,15 +227,32 @@ driver:SetScript("OnEvent", function()
|
||||
if arg3 == "MAINHAND" and arg1 == BF.playerGuid then
|
||||
BF.state.lastSwingAt = GetTime()
|
||||
BF.RefreshSwingInputs()
|
||||
-- Redraw explicitly, because this branch returns before the shared RequestUpdate at
|
||||
-- the end of the handler. Without it a swing landing while idle and out of combat
|
||||
-- never re-attaches the update loop and the marker simply does not move -- the one
|
||||
-- wakeup path that is neither an aura nor a combat event. The call stays INSIDE the
|
||||
-- main-hand test on purpose: UNIT_CASTEVENT fires for every cast of every unit in
|
||||
-- range, and redrawing on all of them is exactly the churn this addon avoids.
|
||||
if BF.RequestUpdate then BF.RequestUpdate() end
|
||||
end
|
||||
return
|
||||
end
|
||||
if event == "PLAYER_REGEN_DISABLED" then
|
||||
if event == "UNIT_MAXHEALTH" then
|
||||
-- Health only: no GUID re-capture, no swing re-read. This fires for party and raid
|
||||
-- members too, hence the filter.
|
||||
if arg1 == "player" then BF.state.maxHP = UnitHealthMax("player") or 0 end
|
||||
elseif event == "PLAYER_LEVEL_UP" then
|
||||
BF.state.maxHP = UnitHealthMax("player") or 0
|
||||
elseif event == "PLAYER_REGEN_DISABLED" then
|
||||
BF.state.inCombat = true
|
||||
elseif event == "PLAYER_REGEN_ENABLED" then
|
||||
BF.state.inCombat = false
|
||||
elseif event == "PLAYER_ENTERING_WORLD" or event == "UNIT_INVENTORY_CHANGED" then
|
||||
BF.state.maxHP = UnitHealthMax("player") or 0
|
||||
-- Seeded from the live API, not only from the REGEN transitions: those fire on ENTERING
|
||||
-- combat, never on login, so a /reload taken mid-fight would leave the flag false for the
|
||||
-- rest of the encounter and strand anyone running "Combat only".
|
||||
BF.state.inCombat = UnitAffectingCombat("player") and true or false
|
||||
BF.CapturePlayerGuid()
|
||||
BF.RefreshSwingInputs()
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user