Created
May 10, 2026 04:01
-
-
Save hclivess/bbaf85e5e6da526d4b63b0e40fad5d69 to your computer and use it in GitHub Desktop.
Buildpower Bar for Beyond All Reason
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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