BulwarkFrame v0.3.0
This commit is contained in:
+1
-1
@@ -2,7 +2,7 @@
|
|||||||
## Title: BulwarkFrame
|
## Title: BulwarkFrame
|
||||||
## Notes: Compact real-time readout for the shaman Earthen Bulwark. Requires SuperWoW.
|
## Notes: Compact real-time readout for the shaman Earthen Bulwark. Requires SuperWoW.
|
||||||
## Author: ShempError
|
## Author: ShempError
|
||||||
## Version: 0.2.0
|
## Version: 0.3.0
|
||||||
## SavedVariables: BulwarkFrameDB
|
## SavedVariables: BulwarkFrameDB
|
||||||
|
|
||||||
core\env.lua
|
core\env.lua
|
||||||
|
|||||||
@@ -2,6 +2,26 @@
|
|||||||
|
|
||||||
All notable changes to BulwarkFrame are documented here.
|
All notable changes to BulwarkFrame are documented here.
|
||||||
|
|
||||||
|
## v0.3.0 — 2026-08-30
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- **Options panel controls for every setting that had none.** An audit of
|
||||||
|
`core/config.lua` found ten saved settings with no way to change them
|
||||||
|
short of hand-editing the SavedVariables file. The options panel now has:
|
||||||
|
a slider for the gap between the three bars (**Bar spacing**); a checkbox
|
||||||
|
for the attack-speed number on the expiry bar (**Attack speed text**); a
|
||||||
|
slider for the expiry bar's opacity (**Expiry bar opacity**); a colour
|
||||||
|
swatch plus opacity slider for the frame's background (**Background** /
|
||||||
|
**Background opacity**); sliders for the four colour-bucket thresholds
|
||||||
|
(**Pool red/yellow threshold**, **Expiry red/yellow threshold**); a
|
||||||
|
slider for the set-bonus percentage (**Set bonus value**); a slider for
|
||||||
|
how much of your latency shifts the swing marker (**Latency share**);
|
||||||
|
and, under a new **Aura IDs** heading, numeric edit boxes for the pool
|
||||||
|
and time buff spell ids, in case TurtleWoW renumbers the custom spell in
|
||||||
|
a future patch. Every control applies immediately, refreshes when the
|
||||||
|
panel reopens, and is covered by the Reset button, the same as every
|
||||||
|
pre-existing option.
|
||||||
|
|
||||||
## v0.2.0 — 2026-08-15
|
## v0.2.0 — 2026-08-15
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2026 ShempError
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
@@ -92,12 +92,18 @@ readout.
|
|||||||
|
|
||||||
## Options
|
## Options
|
||||||
|
|
||||||
**Frame** — lock, scale, width, per-bar heights, minimap button, hide-when-idle, combat-only.
|
**Frame** — lock, scale, width, per-bar heights, bar spacing, minimap button, hide-when-idle,
|
||||||
**Elements** — each of the three readouts and every text label individually; swing marker on the
|
combat-only.
|
||||||
expiry bar or on its own line; max hit vs. max shield.
|
**Elements** — each of the three readouts and every text label individually (including the attack
|
||||||
**Colours** — the three threshold colours and the swing marker, via the standard colour picker.
|
-speed number); swing marker on the expiry bar or on its own line; max hit vs. max shield; expiry
|
||||||
**Absorb model** — talent rank (5 / 10 / 15 %), the set-bonus reading, and whether the swing
|
bar opacity.
|
||||||
marker is latency-adjusted.
|
**Colours** — the three threshold colours, the swing marker, and the background (with its own
|
||||||
|
opacity slider), via the standard colour picker; the pool and expiry bars' red/yellow/green
|
||||||
|
threshold sliders.
|
||||||
|
**Absorb model** — talent rank (5 / 10 / 15 %), the set-bonus reading and its percentage, and
|
||||||
|
whether the swing marker is latency-adjusted, and by how much.
|
||||||
|
**Aura IDs** — the pool and time buff spell ids, editable in case TurtleWoW renumbers the custom
|
||||||
|
spell in a future patch (`/bulwark probe` finds the current one).
|
||||||
|
|
||||||
Every change applies immediately — an options panel whose effect only shows after `/reload`
|
Every change applies immediately — an options panel whose effect only shows after `/reload`
|
||||||
teaches you to distrust it.
|
teaches you to distrust it.
|
||||||
@@ -140,6 +146,7 @@ lua50 tools/luatests/test_swing.lua
|
|||||||
lua50 tools/luatests/test_env.lua
|
lua50 tools/luatests/test_env.lua
|
||||||
lua50 tools/luatests/test_config.lua
|
lua50 tools/luatests/test_config.lua
|
||||||
lua50 tools/luatests/test_slash.lua
|
lua50 tools/luatests/test_slash.lua
|
||||||
|
lua50 tools/luatests/test_options.lua
|
||||||
```
|
```
|
||||||
|
|
||||||
`test_slash.lua` is the exception to "no WoW API offline": it drives the real slash commands and
|
`test_slash.lua` is the exception to "no WoW API offline": it drives the real slash commands and
|
||||||
|
|||||||
+146
-3
@@ -14,7 +14,7 @@ local PANEL_W = 430
|
|||||||
-- Content width of one column: panel minus both outer margins minus the gutter, halved.
|
-- Content width of one column: panel minus both outer margins minus the gutter, halved.
|
||||||
local COL_W = (PANEL_W - 48 - 12) / 2
|
local COL_W = (PANEL_W - 48 - 12) / 2
|
||||||
local panel = nil
|
local panel = nil
|
||||||
local cbIndex, slIndex, swIndex = 0, 0, 0
|
local cbIndex, slIndex, swIndex, ebIndex = 0, 0, 0, 0
|
||||||
local refreshers = {}
|
local refreshers = {}
|
||||||
|
|
||||||
local function db() return BulwarkFrameDB end
|
local function db() return BulwarkFrameDB end
|
||||||
@@ -81,6 +81,8 @@ local function CreateCheckbox(parent, label, tip, getter, setter)
|
|||||||
local lbl = getglobal(name .. "Text")
|
local lbl = getglobal(name .. "Text")
|
||||||
if lbl then lbl:SetText(label); ApplyFont(lbl) end
|
if lbl then lbl:SetText(label); ApplyFont(lbl) end
|
||||||
cb.bfSet = setter
|
cb.bfSet = setter
|
||||||
|
cb.bfLabel = label -- scratch field for the offline stub; the real client's own label
|
||||||
|
-- text lives in the $parentText template region instead.
|
||||||
cb:SetScript("OnClick", function()
|
cb:SetScript("OnClick", function()
|
||||||
this.bfSet(this:GetChecked() == 1)
|
this.bfSet(this:GetChecked() == 1)
|
||||||
Applied()
|
Applied()
|
||||||
@@ -188,6 +190,56 @@ local function CreateSwatch(parent, label, key, tip)
|
|||||||
return holder
|
return holder
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- A numeric edit box: a label above a small InputBoxTemplate field. Used for the two spell ids,
|
||||||
|
-- which are not on any slider scale -- typing the exact id is the only sane input for one.
|
||||||
|
-- Applies on Enter or on losing focus (tabbing/clicking away), the same "commit, don't live-type"
|
||||||
|
-- behaviour InputBoxTemplate users get everywhere else in the default UI. An unparsable entry is
|
||||||
|
-- reverted to the current stored value rather than silently written as nil.
|
||||||
|
local function CreateEditBox(parent, label, tip, getter, setter)
|
||||||
|
ebIndex = ebIndex + 1
|
||||||
|
local name = "BulwarkFrameOptEdit" .. ebIndex
|
||||||
|
local holder = CreateFrame("Frame", name .. "Holder", parent)
|
||||||
|
holder:SetWidth(COL_W)
|
||||||
|
holder:SetHeight(34)
|
||||||
|
|
||||||
|
local lbl = holder:CreateFontString(name .. "Label", "OVERLAY", "GameFontNormalSmall")
|
||||||
|
lbl:SetPoint("TOPLEFT", holder, "TOPLEFT", 0, 0)
|
||||||
|
lbl:SetJustifyH("LEFT")
|
||||||
|
lbl:SetText(label)
|
||||||
|
ApplyFont(lbl, 11)
|
||||||
|
|
||||||
|
local eb = CreateFrame("EditBox", name, holder, "InputBoxTemplate")
|
||||||
|
eb:SetWidth(90)
|
||||||
|
eb:SetHeight(18)
|
||||||
|
eb:SetAutoFocus(false)
|
||||||
|
eb:SetMaxLetters(6)
|
||||||
|
if eb.SetNumeric then eb:SetNumeric(true) end
|
||||||
|
eb:SetPoint("TOPLEFT", holder, "TOPLEFT", 4, -16)
|
||||||
|
|
||||||
|
local function commit()
|
||||||
|
local v = tonumber(eb:GetText())
|
||||||
|
if v then
|
||||||
|
eb.bfSet(math.floor(v))
|
||||||
|
else
|
||||||
|
eb:SetText(tostring(getter()))
|
||||||
|
end
|
||||||
|
eb:ClearFocus()
|
||||||
|
Applied()
|
||||||
|
end
|
||||||
|
eb.bfSet = setter
|
||||||
|
eb:SetScript("OnEnterPressed", commit)
|
||||||
|
eb:SetScript("OnEscapePressed", function() eb:SetText(tostring(getter())); eb:ClearFocus() end)
|
||||||
|
eb:SetScript("OnEditFocusLost", commit)
|
||||||
|
|
||||||
|
-- No AddRowHover here: an overlay button covering the row would sit above the edit box and
|
||||||
|
-- eat the click meant to focus it. The tooltip is on the field and the label only.
|
||||||
|
AddTooltip(eb, tip)
|
||||||
|
holder:EnableMouse(true)
|
||||||
|
AddTooltip(holder, tip)
|
||||||
|
table.insert(refreshers, function() eb:SetText(tostring(getter())) end)
|
||||||
|
return holder
|
||||||
|
end
|
||||||
|
|
||||||
local function CreateButton(parent, label, tip, onClick)
|
local function CreateButton(parent, label, tip, onClick)
|
||||||
local b = CreateFrame("Button", nil, parent, "UIPanelButtonTemplate")
|
local b = CreateFrame("Button", nil, parent, "UIPanelButtonTemplate")
|
||||||
b:SetWidth(110)
|
b:SetWidth(110)
|
||||||
@@ -333,6 +385,12 @@ local function BuildPanel()
|
|||||||
function(v) db().swingHeight = math.floor(v) end)
|
function(v) db().swingHeight = math.floor(v) end)
|
||||||
sliderL(swingH)
|
sliderL(swingH)
|
||||||
|
|
||||||
|
local spacing = CreateSlider(panel, "Bar spacing", 0, 10, 1, "Bar spacing: %d px",
|
||||||
|
"Vertical gap between the threshold bar, the expiry bar, and the swing line.",
|
||||||
|
function() return db().spacing end,
|
||||||
|
function(v) db().spacing = math.floor(v) end)
|
||||||
|
sliderL(spacing)
|
||||||
|
|
||||||
-- right column: elements, colours, model ----------------------------------------------
|
-- right column: elements, colours, model ----------------------------------------------
|
||||||
local ry = -46
|
local ry = -46
|
||||||
headR("Elements")
|
headR("Elements")
|
||||||
@@ -383,7 +441,20 @@ local function BuildPanel()
|
|||||||
local e5 = CreateCheckbox(panel, "Expiry text", "Show the remaining seconds as a number.",
|
local e5 = CreateCheckbox(panel, "Expiry text", "Show the remaining seconds as a number.",
|
||||||
function() return db().showExpiryText end,
|
function() return db().showExpiryText end,
|
||||||
function(v) db().showExpiryText = v end)
|
function(v) db().showExpiryText = v end)
|
||||||
placeR(e5, 40)
|
placeR(e5, 28)
|
||||||
|
|
||||||
|
local e6 = CreateCheckbox(panel, "Attack speed text",
|
||||||
|
"Show the current weapon attack speed on the left of the expiry bar.",
|
||||||
|
function() return db().showSpeedText end,
|
||||||
|
function(v) db().showSpeedText = v end)
|
||||||
|
placeR(e6, 28)
|
||||||
|
|
||||||
|
local expiryAlpha = CreateSlider(panel, "Expiry bar opacity", 0, 1, 0.05, "Expiry bar opacity: %.2f",
|
||||||
|
"Alpha of the expiry bar. Kept lower than the threshold bar by default so it reads as the "
|
||||||
|
.. "secondary readout.",
|
||||||
|
function() return db().expiryAlpha end,
|
||||||
|
function(v) db().expiryAlpha = v end)
|
||||||
|
sliderR(expiryAlpha)
|
||||||
|
|
||||||
headR("Colours")
|
headR("Colours")
|
||||||
|
|
||||||
@@ -394,7 +465,47 @@ local function BuildPanel()
|
|||||||
local sw3 = CreateSwatch(panel, "High", "colorGreen", "Colour above the high threshold.")
|
local sw3 = CreateSwatch(panel, "High", "colorGreen", "Colour above the high threshold.")
|
||||||
placeR(sw3, 24)
|
placeR(sw3, 24)
|
||||||
local sw4 = CreateSwatch(panel, "Swing marker", "colorSwing", "Colour of the swing marker.")
|
local sw4 = CreateSwatch(panel, "Swing marker", "colorSwing", "Colour of the swing marker.")
|
||||||
placeR(sw4, 34)
|
placeR(sw4, 24)
|
||||||
|
|
||||||
|
-- Background colour is a 4-element {r,g,b,a} table; CreateSwatch only ever touches indices
|
||||||
|
-- 1..3, so the alpha channel gets its own slider straight onto the same table rather than a
|
||||||
|
-- second colour-helper -- there is no opacity-capable picker in this file to reuse.
|
||||||
|
local sw5 = CreateSwatch(panel, "Background", "colorBg", "Background colour of the frame.")
|
||||||
|
placeR(sw5, 24)
|
||||||
|
|
||||||
|
local bgAlpha = CreateSlider(panel, "Background opacity", 0, 1, 0.05, "Background opacity: %.2f",
|
||||||
|
"Opacity of the frame's background panel.",
|
||||||
|
function() return (db().colorBg or {})[4] or 0.8 end,
|
||||||
|
function(v) local c = db().colorBg; if c then c[4] = v end end)
|
||||||
|
sliderR(bgAlpha)
|
||||||
|
|
||||||
|
headR("Thresholds")
|
||||||
|
|
||||||
|
local poolRed = CreateSlider(panel, "Pool red threshold", 0, 100, 5, "Pool turns red below: %.0f%%",
|
||||||
|
"The threshold bar turns red at or below this fraction of the pool.",
|
||||||
|
function() return (db().poolRed or 0.30) * 100 end,
|
||||||
|
function(v) db().poolRed = v / 100 end)
|
||||||
|
sliderR(poolRed)
|
||||||
|
|
||||||
|
local poolYellow = CreateSlider(panel, "Pool yellow threshold", 0, 100, 5, "Pool turns green above: %.0f%%",
|
||||||
|
"The threshold bar turns green above this fraction of the pool (yellow in between).",
|
||||||
|
function() return (db().poolYellow or 0.70) * 100 end,
|
||||||
|
function(v) db().poolYellow = v / 100 end)
|
||||||
|
sliderR(poolYellow)
|
||||||
|
|
||||||
|
local timeRed = CreateSlider(panel, "Expiry red threshold", 0, BF.BUFFER_DURATION, 0.5,
|
||||||
|
"Expiry turns red below: %.1fs",
|
||||||
|
"The expiry bar turns red once fewer than this many seconds remain.",
|
||||||
|
function() return db().timeRed or 2.0 end,
|
||||||
|
function(v) db().timeRed = v end)
|
||||||
|
sliderR(timeRed)
|
||||||
|
|
||||||
|
local timeYellow = CreateSlider(panel, "Expiry yellow threshold", 0, BF.BUFFER_DURATION, 0.5,
|
||||||
|
"Expiry turns green above: %.1fs",
|
||||||
|
"The expiry bar turns green once more than this many seconds remain (yellow in between).",
|
||||||
|
function() return db().timeYellow or 5.0 end,
|
||||||
|
function(v) db().timeYellow = v end)
|
||||||
|
sliderR(timeYellow)
|
||||||
|
|
||||||
headR("Absorb model")
|
headR("Absorb model")
|
||||||
|
|
||||||
@@ -420,6 +531,13 @@ local function BuildPanel()
|
|||||||
placeR(setBtn, 28)
|
placeR(setBtn, 28)
|
||||||
table.insert(refreshers, function() setBtn:SetText("Set bonus: " .. (db().setBonusMode or "none")) end)
|
table.insert(refreshers, function() setBtn:SetText("Set bonus: " .. (db().setBonusMode or "none")) end)
|
||||||
|
|
||||||
|
local setVal = CreateSlider(panel, "Set bonus value", 0, 20, 1, "Set bonus value: %d%%",
|
||||||
|
"The set bonus percentage the reading above applies. Only used while the set bonus reading "
|
||||||
|
.. "is 'add' or 'mult'.",
|
||||||
|
function() return db().setBonusValue end,
|
||||||
|
function(v) db().setBonusValue = math.floor(v) end)
|
||||||
|
sliderR(setVal)
|
||||||
|
|
||||||
local latency = CreateCheckbox(panel, "Latency-adjust swing",
|
local latency = CreateCheckbox(panel, "Latency-adjust swing",
|
||||||
"Shift the swing marker by your round-trip time, so it shows when to press rather than "
|
"Shift the swing marker by your round-trip time, so it shows when to press rather than "
|
||||||
.. "when the server swung.",
|
.. "when the server swung.",
|
||||||
@@ -427,6 +545,31 @@ local function BuildPanel()
|
|||||||
function(v) db().swingUseLatency = v; BF.RefreshSwingInputs() end)
|
function(v) db().swingUseLatency = v; BF.RefreshSwingInputs() end)
|
||||||
placeR(latency, 28)
|
placeR(latency, 28)
|
||||||
|
|
||||||
|
local latencyShare = CreateSlider(panel, "Latency share", 0, 1, 0.05, "Latency share: %.2f",
|
||||||
|
"How much of the round-trip time to shift the swing marker by: 1.00 = the full round trip, "
|
||||||
|
.. "0.50 = downstream only. Only used while latency-adjust is on.",
|
||||||
|
function() return db().swingLatencyShare end,
|
||||||
|
function(v) db().swingLatencyShare = v; BF.RefreshSwingInputs() end)
|
||||||
|
sliderR(latencyShare)
|
||||||
|
|
||||||
|
headR("Aura IDs")
|
||||||
|
|
||||||
|
local poolAuraBox = CreateEditBox(panel, "Pool aura spell id",
|
||||||
|
"Spell id of the Earthen Bulwark Durability debuff, whose stack count is the pool level. "
|
||||||
|
.. "TurtleWoW can renumber a custom spell between patches -- if the readout goes blank after "
|
||||||
|
.. "an update, /bulwark probe to find the new id and enter it here.",
|
||||||
|
function() return db().poolAura end,
|
||||||
|
function(v) db().poolAura = v end)
|
||||||
|
placeR(poolAuraBox, 36)
|
||||||
|
|
||||||
|
local timeAuraBox = CreateEditBox(panel, "Time aura spell id",
|
||||||
|
"Spell id of the Earthen Bulwark buff, whose remaining duration drives the expiry bar. "
|
||||||
|
.. "TurtleWoW can renumber a custom spell between patches -- if the readout goes blank after "
|
||||||
|
.. "an update, /bulwark probe to find the new id and enter it here.",
|
||||||
|
function() return db().timeAura end,
|
||||||
|
function(v) db().timeAura = v end)
|
||||||
|
placeR(timeAuraBox, 40)
|
||||||
|
|
||||||
-- footer -------------------------------------------------------------------------------
|
-- footer -------------------------------------------------------------------------------
|
||||||
local demoBtn = CreateButton(panel, "Demo", "Run the display off a synthetic cycle so colours "
|
local demoBtn = CreateButton(panel, "Demo", "Run the display off a synthetic cycle so colours "
|
||||||
.. "and sizes can be judged without a fight.", function()
|
.. "and sizes can be judged without a fight.", function()
|
||||||
|
|||||||
Reference in New Issue
Block a user