Skip to content

Instantly share code, notes, and snippets.

@hclivess
Created May 10, 2026 04:01
Show Gist options
  • Select an option

  • Save hclivess/bbaf85e5e6da526d4b63b0e40fad5d69 to your computer and use it in GitHub Desktop.

Select an option

Save hclivess/bbaf85e5e6da526d4b63b0e40fad5d69 to your computer and use it in GitHub Desktop.
Buildpower Bar for Beyond All Reason
function widget:GetInfo()
return {
name = "BP Bar",
desc = "Shows total and in-use build power",
author = "biong",
date = "2026",
license = "GNU GPL v2",
layer = 0,
enabled = true,
}
end
local spGetMyTeamID = Spring.GetMyTeamID
local spGetTeamUnits = Spring.GetTeamUnits
local spGetUnitDefID = Spring.GetUnitDefID
local spGetUnitIsBuilding = Spring.GetUnitIsBuilding
local spGetUnitCurrentCommand = Spring.GetUnitCurrentCommand
local spGetTeamResources = Spring.GetTeamResources
local spGetViewGeometry = Spring.GetViewGeometry
local spGetUnitCurrentBuildPower = Spring.GetUnitCurrentBuildPower -- may be nil
local glColor = gl.Color
local glRect = gl.Rect
local glText = gl.Text
local glVertex = gl.Vertex
local glBeginEnd = gl.BeginEnd
local glPushMatrix = gl.PushMatrix
local glPopMatrix = gl.PopMatrix
local glTranslate = gl.Translate
local GL_QUADS = GL.QUADS
local GL_LINE_LOOP = GL.LINE_LOOP
local CMD_REPAIR = CMD.REPAIR
local CMD_RECLAIM = CMD.RECLAIM
local CMD_RESURRECT = CMD.RESURRECT
local CMD_GUARD = CMD.GUARD
local myTeamID
local builders = {} -- unitID -> buildSpeed
local totalBP, usedBP = 0, 0
local lastUpdate = 0
local UPDATE_INTERVAL = 0 -- 0 = every frame; set to 0.05 for 20Hz
-- bar geometry
local BAR_W, BAR_H = 440, 34
local BORDER = 2
local ICON_W = 30
local PAD = 6
local function isBuilder(unitDefID)
local ud = UnitDefs[unitDefID]
return ud and ud.isBuilder and ud.buildSpeed and ud.buildSpeed > 0
end
local function addUnit(unitID, unitDefID)
if isBuilder(unitDefID) then
builders[unitID] = UnitDefs[unitDefID].buildSpeed
end
end
function widget:Initialize()
myTeamID = spGetMyTeamID()
for _, uID in ipairs(spGetTeamUnits(myTeamID)) do
addUnit(uID, spGetUnitDefID(uID))
end
end
function widget:UnitFinished(unitID, unitDefID, unitTeam)
if unitTeam == myTeamID then addUnit(unitID, unitDefID) end
end
function widget:UnitDestroyed(unitID)
builders[unitID] = nil
end
function widget:UnitTaken(unitID)
builders[unitID] = nil
end
function widget:UnitGiven(unitID, unitDefID, newTeam)
if newTeam == myTeamID then addUnit(unitID, unitDefID) end
end
function widget:PlayerChanged()
myTeamID = spGetMyTeamID()
builders = {}
for _, uID in ipairs(spGetTeamUnits(myTeamID)) do
addUnit(uID, spGetUnitDefID(uID))
end
end
local function builderActivity(unitID)
if spGetUnitCurrentBuildPower then
local bp = spGetUnitCurrentBuildPower(unitID)
if bp then return bp end
end
if spGetUnitIsBuilding(unitID) then return 1 end
local cmdID, _, _, target = spGetUnitCurrentCommand(unitID)
if not cmdID then return 0 end
if cmdID < 0 then return 1 end
if cmdID == CMD_REPAIR or cmdID == CMD_RECLAIM or cmdID == CMD_RESURRECT then
return 1
end
if cmdID == CMD_GUARD and target and spGetUnitIsBuilding(target) then
return 1
end
return 0
end
local function recompute()
local t, u = 0, 0
local stallRatio = 1
if not spGetUnitCurrentBuildPower then
local mCur, _, mPull, mInc = spGetTeamResources(myTeamID, "metal")
local eCur, _, ePull, eInc = spGetTeamResources(myTeamID, "energy")
local mAvail = (mInc or 0) + (mCur or 0)
local eAvail = (eInc or 0) + (eCur or 0)
local mRatio = (mPull and mPull > 0) and math.min(1, mAvail / mPull) or 1
local eRatio = (ePull and ePull > 0) and math.min(1, eAvail / ePull) or 1
stallRatio = math.min(mRatio, eRatio)
end
for unitID, bs in pairs(builders) do
t = t + bs
local activity = builderActivity(unitID)
if activity > 0 then
u = u + bs * activity * stallRatio
end
end
totalBP, usedBP = t, u
end
function widget:Update(dt)
if UPDATE_INTERVAL <= 0 then
recompute()
else
lastUpdate = lastUpdate + dt
if lastUpdate >= UPDATE_INTERVAL then
lastUpdate = 0
recompute()
end
end
end
-- ---------- drawing helpers ----------
local function rect(x1, y1, x2, y2)
glRect(x1, y1, x2, y2)
end
-- vertical gradient quad (color1 at top, color2 at bottom)
local function gradientQuad(x1, y1, x2, y2, r1,g1,b1,a1, r2,g2,b2,a2)
glBeginEnd(GL_QUADS, function()
glColor(r2, g2, b2, a2); glVertex(x1, y1)
glColor(r2, g2, b2, a2); glVertex(x2, y1)
glColor(r1, g1, b1, a1); glVertex(x2, y2)
glColor(r1, g1, b1, a1); glVertex(x1, y2)
end)
end
-- draw a wrench-ish icon for BP using primitives
local function drawIcon(cx, cy, size)
-- outer ring
glColor(1.0, 0.85, 0.35, 1)
local s = size * 0.45
-- crossed bars forming a plus / hammer shape
glRect(cx - s, cy - s*0.25, cx + s, cy + s*0.25)
glRect(cx - s*0.25, cy - s, cx + s*0.25, cy + s)
-- center dot
glColor(0.15, 0.10, 0.0, 1)
glRect(cx - s*0.18, cy - s*0.18, cx + s*0.18, cy + s*0.18)
end
-- ---------- main draw ----------
function widget:DrawScreen()
local vsx, vsy = spGetViewGeometry()
local x = (vsx - BAR_W) * 0.5
local y = vsy - 100 -- adjust to taste
local x2 = x + BAR_W
local y2 = y + BAR_H
-- ===== outer panel: dark base + subtle vertical gradient =====
gradientQuad(x, y, x2, y2,
0.10, 0.10, 0.12, 0.92, -- top
0.04, 0.04, 0.05, 0.92) -- bottom
-- ===== outer bevel (light top/left, dark bottom/right) =====
glColor(1, 1, 1, 0.18)
rect(x, y2 - 1, x2, y2) -- top highlight
rect(x, y, x + 1, y2) -- left highlight
glColor(0, 0, 0, 0.55)
rect(x, y, x2, y + 1) -- bottom shadow
rect(x2 - 1, y, x2, y2) -- right shadow
-- ===== icon area =====
local iconCX = x + PAD + ICON_W * 0.5
local iconCY = y + BAR_H * 0.5
-- icon backplate
glColor(0, 0, 0, 0.45)
rect(x + PAD, y + PAD - 2, x + PAD + ICON_W, y2 - PAD + 2)
drawIcon(iconCX, iconCY, ICON_W)
-- ===== inner bar track =====
local trackX1 = x + PAD + ICON_W + PAD
local trackY1 = y + PAD
local trackX2 = x2 - PAD
local trackY2 = y2 - PAD
local trackW = trackX2 - trackX1
-- recessed track background
gradientQuad(trackX1, trackY1, trackX2, trackY2,
0.02, 0.02, 0.03, 1, -- top (deeper)
0.08, 0.08, 0.10, 1) -- bottom
-- inner shadow on top edge of track
glColor(0, 0, 0, 0.5)
rect(trackX1, trackY2 - 1, trackX2, trackY2)
glColor(1, 1, 1, 0.08)
rect(trackX1, trackY1, trackX2, trackY1 + 1)
-- ===== fill =====
local frac = (totalBP > 0) and (usedBP / totalBP) or 0
if frac > 1 then frac = 1 end
if frac > 0 then
local fillX2 = trackX1 + trackW * frac
-- color shifts: amber when comfortable, red-orange when saturated
local r1, g1, b1 = 1.00, 0.78, 0.25 -- top highlight
local r2, g2, b2 = 0.85, 0.50, 0.10 -- bottom body
if frac > 0.95 then
r1, g1, b1 = 1.00, 0.55, 0.20
r2, g2, b2 = 0.80, 0.25, 0.05
end
gradientQuad(trackX1, trackY1, fillX2, trackY2,
r1, g1, b1, 1,
r2, g2, b2, 1)
-- glossy top highlight on the fill
gradientQuad(trackX1, trackY1 + (trackY2 - trackY1) * 0.55,
fillX2, trackY2 - 1,
1, 1, 1, 0.18,
1, 1, 1, 0.00)
-- leading edge highlight
glColor(1, 1, 1, 0.35)
rect(fillX2 - 1, trackY1, fillX2, trackY2)
end
-- ===== track border =====
glColor(0, 0, 0, 0.85)
glBeginEnd(GL_LINE_LOOP, function()
glVertex(trackX1, trackY1)
glVertex(trackX2, trackY1)
glVertex(trackX2, trackY2)
glVertex(trackX1, trackY2)
end)
-- ===== text =====
local label = string.format("%d / %d", math.floor(usedBP + 0.5), math.floor(totalBP + 0.5))
local pct = string.format("%d%%", math.floor(frac * 100 + 0.5))
-- shadow
glColor(0, 0, 0, 0.85)
glText(label, (trackX1 + trackX2) * 0.5 + 1, trackY1 + 7 - 1, 15, "co")
glText(pct, trackX2 - PAD + 1, trackY1 + 7 - 1, 13, "ro")
-- main text
glColor(1, 1, 1, 1)
glText(label, (trackX1 + trackX2) * 0.5, trackY1 + 7, 15, "co")
glColor(1, 0.92, 0.7, 1)
glText(pct, trackX2 - PAD, trackY1 + 7, 13, "ro")
-- "BP" label above icon
glColor(0.85, 0.85, 0.85, 0.9)
glText("BP", iconCX, y2 + 2, 10, "co")
glColor(1, 1, 1, 1)
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment