BulwarkFrame v0.2.0
This commit is contained in:
@@ -7,7 +7,7 @@
|
||||
-- * nothing is allocated per OnUpdate frame -- no string building, no tables. Text is only
|
||||
-- pushed when it actually changed, and the loop returns early when there is nothing to draw;
|
||||
-- * when the buffer is down and we are out of combat the OnUpdate script is detached entirely
|
||||
-- rather than left spinning on a hidden frame.
|
||||
-- rather than left spinning -- whether the frame is hidden or merely idle on screen.
|
||||
|
||||
BulwarkFrame = BulwarkFrame or {}
|
||||
local BF = BulwarkFrame
|
||||
@@ -23,6 +23,13 @@ local demo = nil -- demo mode state, nil when off
|
||||
local UPDATE_INTERVAL = 0.05
|
||||
local sinceUpdate = 0
|
||||
|
||||
-- Colour-threshold configs, allocated once and refilled in place. Built as table literals inside
|
||||
-- the update loop they were the only per-tick allocation left in this file: two tables every
|
||||
-- 50 ms, created purely to be read twice and thrown away. calc.lua reads .red/.yellow and never
|
||||
-- retains the table, so reusing one instance is safe.
|
||||
local poolCfg = { red = 0, yellow = 0 }
|
||||
local timeCfg = { red = 0, yellow = 0 }
|
||||
|
||||
-- ---- helpers ----------------------------------------------------------------------------
|
||||
|
||||
local function db() return BulwarkFrameDB end
|
||||
@@ -70,9 +77,14 @@ local function BuildFrame()
|
||||
frame = CreateFrame("Frame", "BulwarkFrameMain", UIParent)
|
||||
frame:SetWidth(d.width)
|
||||
frame:SetHeight(60)
|
||||
frame:SetPoint(d.point, UIParent, d.relPoint, d.x, d.y)
|
||||
-- The anchor is applied by ApplyLayout (called at the end of this function), so that reset and
|
||||
-- the options panel go through the SAME code path a fresh login does. Anchoring here as well
|
||||
-- would leave two placement sites and let them disagree.
|
||||
frame:SetScale(d.scale)
|
||||
frame:SetMovable(true)
|
||||
-- Without this the frame can be dragged until only a sliver is left on screen, and the
|
||||
-- documented rescue ('/bulwark reset') then has to be able to fetch it back.
|
||||
frame:SetClampedToScreen(true)
|
||||
frame:EnableMouse(not d.locked)
|
||||
frame:RegisterForDrag("LeftButton")
|
||||
frame:SetScript("OnDragStart", function() if not db().locked then this:StartMoving() end end)
|
||||
@@ -153,6 +165,12 @@ function BF.ApplyLayout()
|
||||
frame:SetWidth(w)
|
||||
frame:SetScale(d.scale)
|
||||
frame:EnableMouse(not d.locked)
|
||||
-- Re-anchor from the saved placement. Without this, '/bulwark reset' (and the panel's Reset
|
||||
-- button) put CENTER/0/-120 back into the config while the frame itself stayed wherever it was
|
||||
-- dragged -- so the documented way to rescue a frame from the screen edge did nothing until
|
||||
-- the next login.
|
||||
frame:ClearAllPoints()
|
||||
frame:SetPoint(d.point or "CENTER", UIParent, d.relPoint or "CENTER", d.x or 0, d.y or 0)
|
||||
|
||||
if d.showThreshold then
|
||||
poolBar:ClearAllPoints()
|
||||
@@ -273,7 +291,8 @@ local function updateDisplay()
|
||||
if d.showThreshold then
|
||||
local frac = C.barFraction(stacks)
|
||||
poolBar:SetValue(frac)
|
||||
local r, g, b = bucketRGB(C.poolColorName(frac, { red = d.poolRed, yellow = d.poolYellow }))
|
||||
poolCfg.red, poolCfg.yellow = d.poolRed, d.poolYellow
|
||||
local r, g, b = bucketRGB(C.poolColorName(frac, poolCfg))
|
||||
poolBar:SetStatusBarColor(r, g, b)
|
||||
if d.showThresholdText then
|
||||
local points = C.poolPoints(stacks, maxHP)
|
||||
@@ -291,7 +310,8 @@ local function updateDisplay()
|
||||
if d.showExpiry then
|
||||
local tf = C.timeFraction(timeLeft, BF.BUFFER_DURATION)
|
||||
timeBar:SetValue(tf)
|
||||
local r, g, b = bucketRGB(C.timeColorName(timeLeft, { red = d.timeRed, yellow = d.timeYellow }))
|
||||
timeCfg.red, timeCfg.yellow = d.timeRed, d.timeYellow
|
||||
local r, g, b = bucketRGB(C.timeColorName(timeLeft, timeCfg))
|
||||
-- Dimmed on purpose: same buckets, lower alpha, so the threshold bar stays the primary
|
||||
-- readout instead of two equally loud bars sitting on top of each other.
|
||||
timeBar:SetStatusBarColor(r, g, b, d.expiryAlpha or 0.65)
|
||||
@@ -339,6 +359,16 @@ function BF.RequestUpdate()
|
||||
local d = db()
|
||||
local st = BF.state
|
||||
|
||||
-- The deliberate hide is honoured FIRST, ahead of demo mode and ahead of the environment
|
||||
-- hint. Checked further down it was unreachable whenever SuperWoW was missing, so the
|
||||
-- "SuperWoW required" frame could not be dismissed at all: right-clicking the minimap button
|
||||
-- flipped the flag and announced "hidden" while the frame stayed on screen.
|
||||
if d.hidden then
|
||||
frame:Hide()
|
||||
frame:SetScript("OnUpdate", nil)
|
||||
return
|
||||
end
|
||||
|
||||
if demo then
|
||||
frame:Show()
|
||||
if not frame:GetScript("OnUpdate") then frame:SetScript("OnUpdate", BF.OnUpdate) end
|
||||
@@ -355,12 +385,34 @@ function BF.RequestUpdate()
|
||||
|
||||
local shouldShow = st.active or st.inCombat or not d.hideWhenInactive
|
||||
if d.hideOutOfCombat and not st.inCombat then shouldShow = false end
|
||||
if d.hidden then shouldShow = false end -- explicitly hidden via the minimap button
|
||||
|
||||
if shouldShow then
|
||||
hintText:Hide()
|
||||
frame:Show()
|
||||
if not frame:GetScript("OnUpdate") then frame:SetScript("OnUpdate", BF.OnUpdate) end
|
||||
-- "On screen" and "has work to do" are different questions. With hideWhenInactive off
|
||||
-- (the default) the frame stays visible while idle -- but once the buffer is down and
|
||||
-- combat has ended there is nothing left to animate, so the OnUpdate script is still
|
||||
-- detached: it was otherwise left spinning from login to logout, redrawing a saturated
|
||||
-- swing marker every rendered frame and walking the whole buff list five times a second
|
||||
-- for a readout that cannot change. The aura, combat and swing handlers in data.lua each
|
||||
-- end in a RequestUpdate call and re-attach it, so nothing has to poll for the wakeup.
|
||||
--
|
||||
-- A swing still in flight counts as work even with the buffer down and combat over:
|
||||
-- auto-attacking out of combat is ordinary play, and parking on it would freeze the
|
||||
-- marker where it stood. Once progress saturates this stops being true and the loop's
|
||||
-- own RequestUpdate call parks it.
|
||||
local swinging = d.showSwing and st.swingSpeed and st.swingSpeed > 0
|
||||
and S.hasSwung(st.lastSwingAt)
|
||||
and S.progress(st.lastSwingAt, GetTime(), st.swingSpeed, st.swingOffset) < 1
|
||||
if st.active or st.inCombat or swinging then
|
||||
if not frame:GetScript("OnUpdate") then frame:SetScript("OnUpdate", BF.OnUpdate) end
|
||||
else
|
||||
frame:SetScript("OnUpdate", nil)
|
||||
-- One last pass, unconditionally: the frame is visible and nothing will tick it, so
|
||||
-- it has to be left parked on the correct final state rather than on empty text.
|
||||
updateDisplay()
|
||||
updateSwingMarker()
|
||||
end
|
||||
else
|
||||
frame:Hide()
|
||||
frame:SetScript("OnUpdate", nil) -- idle costs nothing at all
|
||||
@@ -373,7 +425,10 @@ function BF.OnUpdate()
|
||||
if sinceUpdate < UPDATE_INTERVAL then return end
|
||||
sinceUpdate = 0
|
||||
local active = updateDisplay()
|
||||
if not demo and not active and not BF.state.inCombat and db().hideWhenInactive then
|
||||
-- Re-evaluated whatever hideWhenInactive says: with it off the frame stays on screen, but the
|
||||
-- loop still has nothing left to do, and RequestUpdate is what parks it. Gating this on the
|
||||
-- setting is why the handler used to run for the whole session in the default configuration.
|
||||
if not demo and not active and not BF.state.inCombat then
|
||||
BF.RequestUpdate()
|
||||
end
|
||||
end
|
||||
@@ -388,6 +443,13 @@ function BF.ToggleDemo(on)
|
||||
if on then
|
||||
demo = { start = GetTime(), maxHP = (UnitHealthMax("player") or 4588) }
|
||||
if demo.maxHP <= 0 then demo.maxHP = 4588 end
|
||||
-- Turning demo on is an explicit "show me the frame", so it clears the manual hide.
|
||||
-- RequestUpdate honours that hide FIRST (it has to -- otherwise the "SuperWoW required"
|
||||
-- frame cannot be dismissed), which meant '/bulwark demo' over a minimap-hidden frame
|
||||
-- reported "demo on", drew nothing, ticked nothing, and left the user silently parked in
|
||||
-- demo mode until they un-hid the frame. Clearing the flag here keeps the precedence
|
||||
-- where it belongs and still makes the command deliver what it announces.
|
||||
db().hidden = false
|
||||
-- Synthetic numbers MUST be labelled as such. Unlabelled demo data is indistinguishable
|
||||
-- from a live readout that happens to be wrong, and that is the one failure this addon
|
||||
-- exists to avoid.
|
||||
@@ -421,6 +483,11 @@ function BF.InitUI()
|
||||
local ok = BulwarkFrameEnv.check(SUPERWOW_VERSION)
|
||||
BF.envOk = ok
|
||||
BF.state.maxHP = UnitHealthMax("player") or 0
|
||||
-- Seeded here as well as in the PLAYER_ENTERING_WORLD handler: this runs off PLAYER_LOGIN and
|
||||
-- the order of the two is not guaranteed. PLAYER_REGEN_DISABLED only fires on ENTERING combat,
|
||||
-- so without a live read a /reload taken mid-fight leaves "Combat only" users with no frame
|
||||
-- for the rest of the encounter.
|
||||
BF.state.inCombat = UnitAffectingCombat("player") and true or false
|
||||
BF.CapturePlayerGuid()
|
||||
if ok then
|
||||
BF.RefreshSwingInputs()
|
||||
|
||||
Reference in New Issue
Block a user