From 904de7eb3bde90c47b5cc96e0f4b25c28821255a Mon Sep 17 00:00:00 2001 From: danfireman Date: Sat, 9 Mar 2024 19:06:35 +0000 Subject: [PATCH 1/6] Added shieldball healthbars widget --- LuaUI/Widgets/gui_shieldball_healthbars.lua | 283 ++++++++++++++++++++ 1 file changed, 283 insertions(+) create mode 100644 LuaUI/Widgets/gui_shieldball_healthbars.lua diff --git a/LuaUI/Widgets/gui_shieldball_healthbars.lua b/LuaUI/Widgets/gui_shieldball_healthbars.lua new file mode 100644 index 0000000000..81a67cfe77 --- /dev/null +++ b/LuaUI/Widgets/gui_shieldball_healthbars.lua @@ -0,0 +1,283 @@ +function widget:GetInfo() + return { + name = "Shieldball healthbars UI", + desc = "Give a healthbar to shieldballs. Version 1.0", + author = "dyth68", + date = "2024", + license = "PD", -- should be compatible with Spring + layer = 11, + enabled = true + } +end + +local UPDATE_FRAME=7 +local glCallList = gl.CallList +local glPushMatrix = gl.PushMatrix +local glTranslate = gl.Translate +local glRotate = gl.Rotate +local glScale = gl.Scale +local glPopMatrix = gl.PopMatrix +local glCallList = gl.CallList +local glCreateList = gl.CreateList +local glDeleteList = gl.DeleteList +local glVertex = gl.Vertex +local glPolygonMode = gl.PolygonMode +local glBeginEnd = gl.BeginEnd +local glLineWidth = gl.LineWidth +local glColor = gl.Color +local spGetUnitPosition = Spring.GetUnitPosition +local spGetUnitShieldState = Spring.GetUnitShieldState +local spGetUnitsInCylinder = Spring.GetUnitsInCylinder +local spGetUnitDefID = Spring.GetUnitDefID +local spGetUnitHealth = Spring.GetUnitHealth + +local Optics = VFS.Include("LuaRules/Gadgets/Include/Optics.lua") +--------------------------------- +local function printThing(theTable, theKey, indent) + local indent = indent or "" + local theKey = theKey or "key" + if (type(theTable) == "table") then + Spring.Echo(indent .. theKey .. ":") + for a, b in pairs(theTable) do + printThing(b, tostring(a), indent .. " ") + end + else + Spring.Echo(indent .. tostring(theKey) .. ": " .. tostring(theTable)) + end +end +--------------------------------- +local shieldUnitDefs = {} + + +for unitDefID=1, #UnitDefs do + if UnitDefs[unitDefID].shieldWeaponDef then + shieldUnitDefs[unitDefID] = true + end +end +--------------------------------- + +local BASE_FONT_SIZE = 192 +local healthBarOffsetZ = 300 +local healthBarHeight = 30 +local healthBarWidth = 200 +local healthBarMargin = 5 +local healthBarRoundiness = 5 +--------------------------------- + +-- Map from teamID to array of balls, each ball with location, curr power and total power +local shieldBalls = {} +local drawBallHealthbarList + +local font = gl.LoadFont("FreeSansBold.otf", BASE_FONT_SIZE, 0, 0) + +local function DrawHullVertices(hull) + for j = 1, #hull do + glVertex(hull[j].x, hull[j].y, hull[j].z) + end +end + +local function DrawBallHealthbar() + for teamID, teamBalls in pairs(shieldBalls) do + for _, shieldBall in pairs(teamBalls) do + glColor(0.8, 0.8, 0.8, 0.5) + glPolygonMode(GL.FRONT_AND_BACK, GL.FILL) + local healthBarHeightAboveTheZero = shieldBall.highestTopOfShield + 40 + local healthBarOffsetZ = shieldBall.zStdDev * 2 + 60 + -- The outer bar is actually an Octagon to give the impression of rounded corners + local healthBarRoundedCorners = { + {x = shieldBall.x - healthBarWidth + healthBarRoundiness, y = healthBarHeightAboveTheZero, z = shieldBall.z - (healthBarOffsetZ + healthBarHeight)}, + {x = shieldBall.x + healthBarWidth - healthBarRoundiness, y = healthBarHeightAboveTheZero, z = shieldBall.z - (healthBarOffsetZ + healthBarHeight)}, + {x = shieldBall.x + healthBarWidth, y = healthBarHeightAboveTheZero, z = shieldBall.z - (healthBarOffsetZ + healthBarHeight - healthBarRoundiness)}, + {x = shieldBall.x + healthBarWidth, y = healthBarHeightAboveTheZero, z = shieldBall.z - (healthBarOffsetZ + healthBarRoundiness)}, + {x = shieldBall.x + healthBarWidth - healthBarRoundiness, y = healthBarHeightAboveTheZero, z = shieldBall.z - (healthBarOffsetZ)}, + {x = shieldBall.x - healthBarWidth + healthBarRoundiness, y = healthBarHeightAboveTheZero, z = shieldBall.z - (healthBarOffsetZ)}, + {x = shieldBall.x - healthBarWidth, y = healthBarHeightAboveTheZero, z = shieldBall.z - (healthBarOffsetZ + healthBarRoundiness)}, + {x = shieldBall.x - healthBarWidth, y = healthBarHeightAboveTheZero, z = shieldBall.z - (healthBarOffsetZ + healthBarHeight - healthBarRoundiness)}, + } + glBeginEnd(GL.TRIANGLE_FAN, DrawHullVertices, healthBarRoundedCorners) + glColor(0.5, 0.5, 1.0, 0.5) + local innerBarWidthWhenFull = healthBarWidth - healthBarMargin + local innerBarHeight = healthBarHeight - healthBarMargin + local proportionFull = shieldBall.currShield / shieldBall.totShield + local innerBarWidth = 2 * innerBarWidthWhenFull * proportionFull + local innerHealthBarCorners = { + { x = shieldBall.x - innerBarWidthWhenFull, y = healthBarHeightAboveTheZero, z = shieldBall.z - (healthBarOffsetZ + innerBarHeight)}, + { x = shieldBall.x - innerBarWidthWhenFull + innerBarWidth, y = healthBarHeightAboveTheZero, z = shieldBall.z - (healthBarOffsetZ + innerBarHeight)}, + { x = shieldBall.x - innerBarWidthWhenFull + innerBarWidth, y = healthBarHeightAboveTheZero, z = shieldBall.z - (healthBarOffsetZ + healthBarMargin)}, + { x = shieldBall.x - innerBarWidthWhenFull, y = healthBarHeightAboveTheZero, z = shieldBall.z - (healthBarOffsetZ + healthBarMargin)}, + } + glBeginEnd(GL.TRIANGLE_FAN, DrawHullVertices, innerHealthBarCorners) + local fontSize = BASE_FONT_SIZE / 10 + --glScale(fontSize / BASE_FONT_SIZE, fontSize / BASE_FONT_SIZE, fontSize / BASE_FONT_SIZE) + glPushMatrix() + glTranslate(shieldBall.x, healthBarHeightAboveTheZero, shieldBall.z - healthBarOffsetZ - innerBarHeight/2 - healthBarMargin/2) + glRotate(-90, 1, 0, 0) + local shieldHpText = tostring(math.floor(shieldBall.currShield)) .. " / " .. tostring(shieldBall.totShield) + font:Begin() + font:SetTextColor(0.2, 0.2, 0.2, 0.6) + font:Print(shieldHpText, 0, 0, fontSize, "cv") + font:End() + glPopMatrix() + end + end +end +--------------------------------- + + -- TODO: Use link data +local function shieldsAreTouching(shield1, shield2) + if not shield2 then + return false + end + local xDiff = shield1.x - shield2.x + local zDiff = shield1.z - shield2.z + local yDiff = shield1.y - shield2.y + local sumRadius = shield1.shieldRadius + shield2.shieldRadius + return xDiff <= sumRadius and zDiff <= sumRadius and (xDiff*xDiff + yDiff*yDiff + zDiff*zDiff) < sumRadius*sumRadius +end + +--------------------------------- + +local shieldBallsIdsByTeam = {} +local allShieldUnitsByTeam = {} + +local function updateClustering() + for _, teamID in pairs(Spring.GetTeamList()) do + local allUnits = Spring.GetTeamUnits(teamID) + local allShieldUnits = {} + for _,unitID in pairs(allUnits) do + local unitDefID = spGetUnitDefID(unitID) + if unitDefID and shieldUnitDefs[unitDefID] then + local _, _, _, _, buildProgress = spGetUnitHealth(unitID) + if buildProgress == 1 then + local shieldWep = WeaponDefs[UnitDefs[unitDefID].shieldWeaponDef] + local x,y,z = spGetUnitPosition(unitID) + if x then + allShieldUnits[unitID] = { + shieldMaxCharge = shieldWep.shieldPower, + shieldRadius = shieldWep.shieldRadius, + x = x, + y = y, + z = z + } + end + --printThing(allShieldUnits[unitID], "shieldDef", "") + end + end + end + local unitLocations = {} + local unitNeighborsMatrix = {} + for unitID, shieldProps in pairs(allShieldUnits) do + local x,y,z = spGetUnitPosition(unitID) + unitLocations[#unitLocations + 1] = { + x = x, + z = z, + fID = unitID, + } + local unitsInRange = spGetUnitsInCylinder(x, z, shieldProps.shieldRadius*2) + unitNeighborsMatrix[unitID] = {} + for _, unitInRange in ipairs(unitsInRange) do + if shieldsAreTouching(shieldProps, allShieldUnits[unitInRange]) then + unitNeighborsMatrix[unitID][unitInRange] = true + if not unitNeighborsMatrix[unitInRange] then + unitNeighborsMatrix[unitInRange] = {} + end + unitNeighborsMatrix[unitInRange][unitID] = true + end + end + end + --printThing(unitLocations, "unitLocations", "") + --printThing(unitNeighborsMatrix, "unitNeighborsMatrix", "") + local opticsObject = Optics.new(unitLocations, unitNeighborsMatrix, 2, false) + opticsObject:Run() + shieldBallsIdsByTeam[teamID] = opticsObject:Clusterize(700) + allShieldUnitsByTeam[teamID] = allShieldUnits + end +end + +--------------------------------- +local function updateCurrentShieldBalls() + for _, teamID in pairs(Spring.GetTeamList()) do + local shieldBallsIds = shieldBallsIdsByTeam[teamID] or {} + local allShieldUnits = allShieldUnitsByTeam[teamID] or {} + local ballsInTeam = {} + for i = 1, #shieldBallsIds do + local thisBall = shieldBallsIds[i] + local totalCurrentShield = 0 + local totalMaxShield = 0 + local x_avg, z_avg = 0, 0 + local numUnits = 0 + local highestTopOfShield = 0 + local memberPositionsByUnitID = {} + for j = 1, #thisBall.members do + local unitID = thisBall.members[j] + local x,y,z = spGetUnitPosition(unitID) + if x then + local shieldProps = allShieldUnits[unitID] + totalMaxShield = totalMaxShield + shieldProps.shieldMaxCharge + local enabled, currPower = spGetUnitShieldState(unitID) + x_avg = x_avg + x + z_avg = z_avg + z + numUnits = numUnits + 1 + if enabled then + totalCurrentShield = totalCurrentShield + currPower + highestTopOfShield = math.max(highestTopOfShield, y + shieldProps.shieldRadius) + end + end + memberPositionsByUnitID[unitID] = {x = x, y = y, z = z} + end + if numUnits > 0 then + x_avg = x_avg / numUnits + z_avg = z_avg / numUnits + end + + local xStdDev = 0 + local zStdDev = 0 + for j = 1, #thisBall.members do + local unitID = thisBall.members[j] + local x = memberPositionsByUnitID[unitID].x + local z = memberPositionsByUnitID[unitID].z + if x then + xStdDev = xStdDev + (x - x_avg) * (x - x_avg) + zStdDev = zStdDev + (z - z_avg) * (z - z_avg) + end + end + xStdDev = math.sqrt(xStdDev / numUnits) + zStdDev = math.sqrt(zStdDev / numUnits) + + local ballData = { + currShield = totalCurrentShield, + totShield = totalMaxShield, + x = x_avg, + z = z_avg, + highestTopOfShield = highestTopOfShield, + xStdDev = xStdDev, + zStdDev = zStdDev, + numUnits = numUnits, + } + ballsInTeam[#ballsInTeam + 1] = ballData + end + + + shieldBalls[teamID] = ballsInTeam + drawBallHealthbarList = glCreateList(DrawBallHealthbar) + end +end +--------------------------------- + +function widget:GameFrame(n) + if (n%UPDATE_FRAME==0) then + updateClustering() + end + updateCurrentShieldBalls() +end + +function widget:Initialize() + updateClustering() + updateCurrentShieldBalls() +end + +function widget:DrawWorld() + if drawBallHealthbarList then + glCallList(drawBallHealthbarList) + end +end From 2a2294172e90cc4c2da06b6c54dd2e372403b180 Mon Sep 17 00:00:00 2001 From: danfireman Date: Sat, 9 Mar 2024 22:39:51 +0000 Subject: [PATCH 2/6] Adding regen and team awareness to shieldball healthbars widget --- LuaUI/Widgets/gui_shieldball_healthbars.lua | 99 ++++++++++++++++++--- 1 file changed, 88 insertions(+), 11 deletions(-) diff --git a/LuaUI/Widgets/gui_shieldball_healthbars.lua b/LuaUI/Widgets/gui_shieldball_healthbars.lua index 81a67cfe77..2b357e43a4 100644 --- a/LuaUI/Widgets/gui_shieldball_healthbars.lua +++ b/LuaUI/Widgets/gui_shieldball_healthbars.lua @@ -11,6 +11,7 @@ function widget:GetInfo() end local UPDATE_FRAME=7 + local glCallList = gl.CallList local glPushMatrix = gl.PushMatrix local glTranslate = gl.Translate @@ -25,11 +26,15 @@ local glPolygonMode = gl.PolygonMode local glBeginEnd = gl.BeginEnd local glLineWidth = gl.LineWidth local glColor = gl.Color + local spGetUnitPosition = Spring.GetUnitPosition local spGetUnitShieldState = Spring.GetUnitShieldState local spGetUnitsInCylinder = Spring.GetUnitsInCylinder local spGetUnitDefID = Spring.GetUnitDefID local spGetUnitHealth = Spring.GetUnitHealth +local spGetUnitRulesParam = Spring.GetUnitRulesParam + +local strFormat = string.format local Optics = VFS.Include("LuaRules/Gadgets/Include/Optics.lua") --------------------------------- @@ -56,6 +61,19 @@ for unitDefID=1, #UnitDefs do end --------------------------------- +local function regenStr(regen) + local sign = (regen >= 0) and "+" or "" + if regen == 0 then + return "" + end + if math.abs(math.ceil(regen) - regen) < 0.05 then + return " (" .. sign .. math.ceil(regen - 0.2) .. ")" + end + return " (" .. sign .. strFormat("%+.1f", regen) .. ")" +end + +--------------------------------- + local BASE_FONT_SIZE = 192 local healthBarOffsetZ = 300 local healthBarHeight = 30 @@ -77,10 +95,10 @@ local function DrawHullVertices(hull) end local function DrawBallHealthbar() - for teamID, teamBalls in pairs(shieldBalls) do + for allyTeamID, teamBalls in pairs(shieldBalls) do + --local isAllied = Spring.AreTeamsAllied(teamID, Spring.GetMyTeamID()) + local isAllied = (Spring.GetLocalAllyTeamID() == allyTeamID) for _, shieldBall in pairs(teamBalls) do - glColor(0.8, 0.8, 0.8, 0.5) - glPolygonMode(GL.FRONT_AND_BACK, GL.FILL) local healthBarHeightAboveTheZero = shieldBall.highestTopOfShield + 40 local healthBarOffsetZ = shieldBall.zStdDev * 2 + 60 -- The outer bar is actually an Octagon to give the impression of rounded corners @@ -94,8 +112,20 @@ local function DrawBallHealthbar() {x = shieldBall.x - healthBarWidth, y = healthBarHeightAboveTheZero, z = shieldBall.z - (healthBarOffsetZ + healthBarRoundiness)}, {x = shieldBall.x - healthBarWidth, y = healthBarHeightAboveTheZero, z = shieldBall.z - (healthBarOffsetZ + healthBarHeight - healthBarRoundiness)}, } + glPolygonMode(GL.FRONT_AND_BACK, GL.LINE) + glColor(0.5, 0.5, 0.5, 0.5) + for i = 1, 8 do + local j = (i % 8) + 1 + glBeginEnd(GL.LINE_STRIP, DrawHullVertices, {healthBarRoundedCorners[i], healthBarRoundedCorners[j]}) + end + if isAllied then + glColor(0.5, 0.8, 0.5, 0.5) + else + glColor(0.8, 0.5, 0.5, 0.5) + end + glPolygonMode(GL.FRONT_AND_BACK, GL.FILL) glBeginEnd(GL.TRIANGLE_FAN, DrawHullVertices, healthBarRoundedCorners) - glColor(0.5, 0.5, 1.0, 0.5) + local innerBarWidthWhenFull = healthBarWidth - healthBarMargin local innerBarHeight = healthBarHeight - healthBarMargin local proportionFull = shieldBall.currShield / shieldBall.totShield @@ -106,13 +136,20 @@ local function DrawBallHealthbar() { x = shieldBall.x - innerBarWidthWhenFull + innerBarWidth, y = healthBarHeightAboveTheZero, z = shieldBall.z - (healthBarOffsetZ + healthBarMargin)}, { x = shieldBall.x - innerBarWidthWhenFull, y = healthBarHeightAboveTheZero, z = shieldBall.z - (healthBarOffsetZ + healthBarMargin)}, } + if isAllied then + glColor(0.5, 0.3, 1.0, 0.5) + else + glColor(0.5, 0.3, 1.0, 0.5) + end glBeginEnd(GL.TRIANGLE_FAN, DrawHullVertices, innerHealthBarCorners) local fontSize = BASE_FONT_SIZE / 10 --glScale(fontSize / BASE_FONT_SIZE, fontSize / BASE_FONT_SIZE, fontSize / BASE_FONT_SIZE) glPushMatrix() glTranslate(shieldBall.x, healthBarHeightAboveTheZero, shieldBall.z - healthBarOffsetZ - innerBarHeight/2 - healthBarMargin/2) glRotate(-90, 1, 0, 0) - local shieldHpText = tostring(math.floor(shieldBall.currShield)) .. " / " .. tostring(shieldBall.totShield) + + local regen = regenStr(shieldBall.regen) + local shieldHpText = tostring(math.floor(shieldBall.currShield)) .. " / " .. tostring(shieldBall.totShield) .. regen font:Begin() font:SetTextColor(0.2, 0.2, 0.2, 0.6) font:Print(shieldHpText, 0, 0, fontSize, "cv") @@ -141,8 +178,14 @@ local shieldBallsIdsByTeam = {} local allShieldUnitsByTeam = {} local function updateClustering() - for _, teamID in pairs(Spring.GetTeamList()) do - local allUnits = Spring.GetTeamUnits(teamID) + for _, allyTeamID in pairs(Spring.GetAllyTeamList()) do + local allUnits = {} + for _, teamID in pairs(Spring.GetTeamList(allyTeamID)) do + local teamUnits = Spring.GetTeamUnits(teamID) + for _, unitID in pairs(teamUnits) do + allUnits[#allUnits + 1] = unitID + end + end local allShieldUnits = {} for _,unitID in pairs(allUnits) do local unitDefID = spGetUnitDefID(unitID) @@ -189,14 +232,42 @@ local function updateClustering() --printThing(unitNeighborsMatrix, "unitNeighborsMatrix", "") local opticsObject = Optics.new(unitLocations, unitNeighborsMatrix, 2, false) opticsObject:Run() - shieldBallsIdsByTeam[teamID] = opticsObject:Clusterize(700) - allShieldUnitsByTeam[teamID] = allShieldUnits + shieldBallsIdsByTeam[allyTeamID] = opticsObject:Clusterize(700) + allShieldUnitsByTeam[allyTeamID] = allShieldUnits + end +end + +--------------------------------- +local function getUnitShieldRegen(unitID, ud) + if spGetUnitRulesParam(unitID, "att_shieldDisabled") == 1 then + return 0 end + + local shieldRegen = spGetUnitRulesParam(unitID, "shieldRegenTimer") + if shieldRegen and shieldRegen > 0 then + return 0 + end + + local mult = spGetUnitRulesParam(unitID,"totalReloadSpeedChange") or 1 * (1 - (spGetUnitRulesParam(unitID, "shieldChargeDisabled") or 0)) + if mult == 0 then + return 0 + end + + -- FIXME: take energy stall into account + local wd = WeaponDefs[ud.shieldWeaponDef] + local wdc = wd.customParams + local regen = (wdc.shield_rate_charge and spGetUnitRulesParam(unitID, "shield_rate_override") and + math.floor(spGetUnitRulesParam(unitID, "shield_rate_override")*15 + 0.5)) or + tonumber(wdc.shield_rate or wd.shieldPowerRegen) + if not wd.customParams.slow_immune then + regen = mult * regen + end + return regen end --------------------------------- local function updateCurrentShieldBalls() - for _, teamID in pairs(Spring.GetTeamList()) do + for _, teamID in pairs(Spring.GetAllyTeamList()) do local shieldBallsIds = shieldBallsIdsByTeam[teamID] or {} local allShieldUnits = allShieldUnitsByTeam[teamID] or {} local ballsInTeam = {} @@ -204,6 +275,7 @@ local function updateCurrentShieldBalls() local thisBall = shieldBallsIds[i] local totalCurrentShield = 0 local totalMaxShield = 0 + local totalRegen = 0 local x_avg, z_avg = 0, 0 local numUnits = 0 local highestTopOfShield = 0 @@ -218,9 +290,12 @@ local function updateCurrentShieldBalls() x_avg = x_avg + x z_avg = z_avg + z numUnits = numUnits + 1 - if enabled then + if enabled and currPower then totalCurrentShield = totalCurrentShield + currPower highestTopOfShield = math.max(highestTopOfShield, y + shieldProps.shieldRadius) + if currPower < shieldProps.shieldMaxCharge then + totalRegen = totalRegen + getUnitShieldRegen(unitID, UnitDefs[spGetUnitDefID(unitID)]) + end end end memberPositionsByUnitID[unitID] = {x = x, y = y, z = z} @@ -244,6 +319,7 @@ local function updateCurrentShieldBalls() xStdDev = math.sqrt(xStdDev / numUnits) zStdDev = math.sqrt(zStdDev / numUnits) + local ballData = { currShield = totalCurrentShield, totShield = totalMaxShield, @@ -253,6 +329,7 @@ local function updateCurrentShieldBalls() xStdDev = xStdDev, zStdDev = zStdDev, numUnits = numUnits, + regen = totalRegen, } ballsInTeam[#ballsInTeam + 1] = ballData end From c31fcbf7e1f84e3bfc04a2cfecc739808eecda1a Mon Sep 17 00:00:00 2001 From: danfireman Date: Sat, 9 Mar 2024 22:56:52 +0000 Subject: [PATCH 3/6] Adding max shield strength to shieldball healthbar widget Also making the healthbar have an inner border rather than outer border --- LuaUI/Widgets/gui_shieldball_healthbars.lua | 27 +++++++++++++++------ 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/LuaUI/Widgets/gui_shieldball_healthbars.lua b/LuaUI/Widgets/gui_shieldball_healthbars.lua index 2b357e43a4..4213fc490f 100644 --- a/LuaUI/Widgets/gui_shieldball_healthbars.lua +++ b/LuaUI/Widgets/gui_shieldball_healthbars.lua @@ -112,12 +112,6 @@ local function DrawBallHealthbar() {x = shieldBall.x - healthBarWidth, y = healthBarHeightAboveTheZero, z = shieldBall.z - (healthBarOffsetZ + healthBarRoundiness)}, {x = shieldBall.x - healthBarWidth, y = healthBarHeightAboveTheZero, z = shieldBall.z - (healthBarOffsetZ + healthBarHeight - healthBarRoundiness)}, } - glPolygonMode(GL.FRONT_AND_BACK, GL.LINE) - glColor(0.5, 0.5, 0.5, 0.5) - for i = 1, 8 do - local j = (i % 8) + 1 - glBeginEnd(GL.LINE_STRIP, DrawHullVertices, {healthBarRoundedCorners[i], healthBarRoundedCorners[j]}) - end if isAllied then glColor(0.5, 0.8, 0.5, 0.5) else @@ -129,6 +123,19 @@ local function DrawBallHealthbar() local innerBarWidthWhenFull = healthBarWidth - healthBarMargin local innerBarHeight = healthBarHeight - healthBarMargin local proportionFull = shieldBall.currShield / shieldBall.totShield + local innerHealthBarBoundingBox = { + { x = shieldBall.x - innerBarWidthWhenFull, y = healthBarHeightAboveTheZero, z = shieldBall.z - (healthBarOffsetZ + innerBarHeight)}, + { x = shieldBall.x + innerBarWidthWhenFull, y = healthBarHeightAboveTheZero, z = shieldBall.z - (healthBarOffsetZ + innerBarHeight)}, + { x = shieldBall.x + innerBarWidthWhenFull, y = healthBarHeightAboveTheZero, z = shieldBall.z - (healthBarOffsetZ + healthBarMargin)}, + { x = shieldBall.x - innerBarWidthWhenFull, y = healthBarHeightAboveTheZero, z = shieldBall.z - (healthBarOffsetZ + healthBarMargin)}, + } + glPolygonMode(GL.FRONT_AND_BACK, GL.LINE) + glColor(0.5, 0.5, 0.5, 0.5) + for i = 1, #innerHealthBarBoundingBox do + local j = (i % #innerHealthBarBoundingBox) + 1 + glBeginEnd(GL.LINE_STRIP, DrawHullVertices, {innerHealthBarBoundingBox[i], innerHealthBarBoundingBox[j]}) + end + local innerBarWidth = 2 * innerBarWidthWhenFull * proportionFull local innerHealthBarCorners = { { x = shieldBall.x - innerBarWidthWhenFull, y = healthBarHeightAboveTheZero, z = shieldBall.z - (healthBarOffsetZ + innerBarHeight)}, @@ -136,6 +143,7 @@ local function DrawBallHealthbar() { x = shieldBall.x - innerBarWidthWhenFull + innerBarWidth, y = healthBarHeightAboveTheZero, z = shieldBall.z - (healthBarOffsetZ + healthBarMargin)}, { x = shieldBall.x - innerBarWidthWhenFull, y = healthBarHeightAboveTheZero, z = shieldBall.z - (healthBarOffsetZ + healthBarMargin)}, } + glPolygonMode(GL.FRONT_AND_BACK, GL.FILL) if isAllied then glColor(0.5, 0.3, 1.0, 0.5) else @@ -148,8 +156,8 @@ local function DrawBallHealthbar() glTranslate(shieldBall.x, healthBarHeightAboveTheZero, shieldBall.z - healthBarOffsetZ - innerBarHeight/2 - healthBarMargin/2) glRotate(-90, 1, 0, 0) - local regen = regenStr(shieldBall.regen) - local shieldHpText = tostring(math.floor(shieldBall.currShield)) .. " / " .. tostring(shieldBall.totShield) .. regen + local regen = regenStr(math.floor(shieldBall.regen)) + local shieldHpText = tostring(math.floor(shieldBall.currShield)) .. " / " .. tostring(shieldBall.totShield) .. regen .. " max " .. tostring(math.floor(shieldBall.maxCurrentPower)) font:Begin() font:SetTextColor(0.2, 0.2, 0.2, 0.6) font:Print(shieldHpText, 0, 0, fontSize, "cv") @@ -276,6 +284,7 @@ local function updateCurrentShieldBalls() local totalCurrentShield = 0 local totalMaxShield = 0 local totalRegen = 0 + local maxIndividualCurrentPower = 0 local x_avg, z_avg = 0, 0 local numUnits = 0 local highestTopOfShield = 0 @@ -296,6 +305,7 @@ local function updateCurrentShieldBalls() if currPower < shieldProps.shieldMaxCharge then totalRegen = totalRegen + getUnitShieldRegen(unitID, UnitDefs[spGetUnitDefID(unitID)]) end + maxIndividualCurrentPower = math.max(maxIndividualCurrentPower, currPower) end end memberPositionsByUnitID[unitID] = {x = x, y = y, z = z} @@ -330,6 +340,7 @@ local function updateCurrentShieldBalls() zStdDev = zStdDev, numUnits = numUnits, regen = totalRegen, + maxCurrentPower = maxIndividualCurrentPower, } ballsInTeam[#ballsInTeam + 1] = ballData end From d10d8fd18d526a26798b8c59d71dc253c6607bfd Mon Sep 17 00:00:00 2001 From: danfireman Date: Sun, 10 Mar 2024 13:56:44 +0000 Subject: [PATCH 4/6] Attempting to improve shieldball healthbars lua performance --- LuaUI/Widgets/gui_shieldball_healthbars.lua | 187 +++++++++++++------- 1 file changed, 121 insertions(+), 66 deletions(-) diff --git a/LuaUI/Widgets/gui_shieldball_healthbars.lua b/LuaUI/Widgets/gui_shieldball_healthbars.lua index 4213fc490f..e3d23c629b 100644 --- a/LuaUI/Widgets/gui_shieldball_healthbars.lua +++ b/LuaUI/Widgets/gui_shieldball_healthbars.lua @@ -18,7 +18,6 @@ local glTranslate = gl.Translate local glRotate = gl.Rotate local glScale = gl.Scale local glPopMatrix = gl.PopMatrix -local glCallList = gl.CallList local glCreateList = gl.CreateList local glDeleteList = gl.DeleteList local glVertex = gl.Vertex @@ -37,6 +36,9 @@ local spGetUnitRulesParam = Spring.GetUnitRulesParam local strFormat = string.format local Optics = VFS.Include("LuaRules/Gadgets/Include/Optics.lua") +local Benchmark = false and VFS.Include("LuaRules/Gadgets/Include/Benchmark.lua") +local benchmark = nil -- Benchmark and Benchmark.new() + --------------------------------- local function printThing(theTable, theKey, indent) local indent = indent or "" @@ -88,68 +90,64 @@ local drawBallHealthbarList local font = gl.LoadFont("FreeSansBold.otf", BASE_FONT_SIZE, 0, 0) -local function DrawHullVertices(hull) - for j = 1, #hull do - glVertex(hull[j].x, hull[j].y, hull[j].z) - end -end - local function DrawBallHealthbar() for allyTeamID, teamBalls in pairs(shieldBalls) do - --local isAllied = Spring.AreTeamsAllied(teamID, Spring.GetMyTeamID()) local isAllied = (Spring.GetLocalAllyTeamID() == allyTeamID) for _, shieldBall in pairs(teamBalls) do local healthBarHeightAboveTheZero = shieldBall.highestTopOfShield + 40 - local healthBarOffsetZ = shieldBall.zStdDev * 2 + 60 + local healthBarOffsetZ = shieldBall.zStdDev + 60 + -- The outer bar is actually an Octagon to give the impression of rounded corners - local healthBarRoundedCorners = { - {x = shieldBall.x - healthBarWidth + healthBarRoundiness, y = healthBarHeightAboveTheZero, z = shieldBall.z - (healthBarOffsetZ + healthBarHeight)}, - {x = shieldBall.x + healthBarWidth - healthBarRoundiness, y = healthBarHeightAboveTheZero, z = shieldBall.z - (healthBarOffsetZ + healthBarHeight)}, - {x = shieldBall.x + healthBarWidth, y = healthBarHeightAboveTheZero, z = shieldBall.z - (healthBarOffsetZ + healthBarHeight - healthBarRoundiness)}, - {x = shieldBall.x + healthBarWidth, y = healthBarHeightAboveTheZero, z = shieldBall.z - (healthBarOffsetZ + healthBarRoundiness)}, - {x = shieldBall.x + healthBarWidth - healthBarRoundiness, y = healthBarHeightAboveTheZero, z = shieldBall.z - (healthBarOffsetZ)}, - {x = shieldBall.x - healthBarWidth + healthBarRoundiness, y = healthBarHeightAboveTheZero, z = shieldBall.z - (healthBarOffsetZ)}, - {x = shieldBall.x - healthBarWidth, y = healthBarHeightAboveTheZero, z = shieldBall.z - (healthBarOffsetZ + healthBarRoundiness)}, - {x = shieldBall.x - healthBarWidth, y = healthBarHeightAboveTheZero, z = shieldBall.z - (healthBarOffsetZ + healthBarHeight - healthBarRoundiness)}, - } if isAllied then glColor(0.5, 0.8, 0.5, 0.5) else glColor(0.8, 0.5, 0.5, 0.5) end glPolygonMode(GL.FRONT_AND_BACK, GL.FILL) - glBeginEnd(GL.TRIANGLE_FAN, DrawHullVertices, healthBarRoundedCorners) - + glBeginEnd(GL.TRIANGLE_FAN, function() + glVertex(shieldBall.x - healthBarWidth + healthBarRoundiness, healthBarHeightAboveTheZero, shieldBall.z - (healthBarOffsetZ + healthBarHeight)) + glVertex(shieldBall.x + healthBarWidth - healthBarRoundiness, healthBarHeightAboveTheZero, shieldBall.z - (healthBarOffsetZ + healthBarHeight)) + glVertex(shieldBall.x + healthBarWidth, healthBarHeightAboveTheZero, shieldBall.z - (healthBarOffsetZ + healthBarHeight - healthBarRoundiness)) + glVertex(shieldBall.x + healthBarWidth, healthBarHeightAboveTheZero, shieldBall.z - (healthBarOffsetZ + healthBarRoundiness)) + glVertex(shieldBall.x + healthBarWidth - healthBarRoundiness, healthBarHeightAboveTheZero, shieldBall.z - (healthBarOffsetZ)) + glVertex(shieldBall.x - healthBarWidth + healthBarRoundiness, healthBarHeightAboveTheZero, shieldBall.z - (healthBarOffsetZ)) + glVertex(shieldBall.x - healthBarWidth, healthBarHeightAboveTheZero, shieldBall.z - (healthBarOffsetZ + healthBarRoundiness)) + glVertex(shieldBall.x - healthBarWidth, healthBarHeightAboveTheZero, shieldBall.z - (healthBarOffsetZ + healthBarHeight - healthBarRoundiness)) + end) + + + -- The healthbar container is four lines local innerBarWidthWhenFull = healthBarWidth - healthBarMargin local innerBarHeight = healthBarHeight - healthBarMargin local proportionFull = shieldBall.currShield / shieldBall.totShield - local innerHealthBarBoundingBox = { - { x = shieldBall.x - innerBarWidthWhenFull, y = healthBarHeightAboveTheZero, z = shieldBall.z - (healthBarOffsetZ + innerBarHeight)}, - { x = shieldBall.x + innerBarWidthWhenFull, y = healthBarHeightAboveTheZero, z = shieldBall.z - (healthBarOffsetZ + innerBarHeight)}, - { x = shieldBall.x + innerBarWidthWhenFull, y = healthBarHeightAboveTheZero, z = shieldBall.z - (healthBarOffsetZ + healthBarMargin)}, - { x = shieldBall.x - innerBarWidthWhenFull, y = healthBarHeightAboveTheZero, z = shieldBall.z - (healthBarOffsetZ + healthBarMargin)}, - } + glPolygonMode(GL.FRONT_AND_BACK, GL.LINE) glColor(0.5, 0.5, 0.5, 0.5) - for i = 1, #innerHealthBarBoundingBox do - local j = (i % #innerHealthBarBoundingBox) + 1 - glBeginEnd(GL.LINE_STRIP, DrawHullVertices, {innerHealthBarBoundingBox[i], innerHealthBarBoundingBox[j]}) - end + glBeginEnd(GL.QUADS, function() + glVertex(shieldBall.x - innerBarWidthWhenFull, healthBarHeightAboveTheZero, shieldBall.z - (healthBarOffsetZ + innerBarHeight)) + glVertex(shieldBall.x + innerBarWidthWhenFull, healthBarHeightAboveTheZero, shieldBall.z - (healthBarOffsetZ + innerBarHeight)) + glVertex(shieldBall.x + innerBarWidthWhenFull, healthBarHeightAboveTheZero, shieldBall.z - (healthBarOffsetZ + healthBarMargin)) + glVertex(shieldBall.x - innerBarWidthWhenFull, healthBarHeightAboveTheZero, shieldBall.z - (healthBarOffsetZ + healthBarMargin)) + end) + + -- The healthbar itself is a rectangle local innerBarWidth = 2 * innerBarWidthWhenFull * proportionFull - local innerHealthBarCorners = { - { x = shieldBall.x - innerBarWidthWhenFull, y = healthBarHeightAboveTheZero, z = shieldBall.z - (healthBarOffsetZ + innerBarHeight)}, - { x = shieldBall.x - innerBarWidthWhenFull + innerBarWidth, y = healthBarHeightAboveTheZero, z = shieldBall.z - (healthBarOffsetZ + innerBarHeight)}, - { x = shieldBall.x - innerBarWidthWhenFull + innerBarWidth, y = healthBarHeightAboveTheZero, z = shieldBall.z - (healthBarOffsetZ + healthBarMargin)}, - { x = shieldBall.x - innerBarWidthWhenFull, y = healthBarHeightAboveTheZero, z = shieldBall.z - (healthBarOffsetZ + healthBarMargin)}, - } glPolygonMode(GL.FRONT_AND_BACK, GL.FILL) if isAllied then glColor(0.5, 0.3, 1.0, 0.5) else glColor(0.5, 0.3, 1.0, 0.5) end - glBeginEnd(GL.TRIANGLE_FAN, DrawHullVertices, innerHealthBarCorners) + glBeginEnd(GL.TRIANGLE_FAN, function() + glVertex(shieldBall.x - innerBarWidthWhenFull, healthBarHeightAboveTheZero, shieldBall.z - (healthBarOffsetZ + innerBarHeight)) + glVertex(shieldBall.x - innerBarWidthWhenFull + innerBarWidth, healthBarHeightAboveTheZero, shieldBall.z - (healthBarOffsetZ + innerBarHeight)) + glVertex(shieldBall.x - innerBarWidthWhenFull + innerBarWidth, healthBarHeightAboveTheZero, shieldBall.z - (healthBarOffsetZ + healthBarMargin)) + glVertex(shieldBall.x - innerBarWidthWhenFull, healthBarHeightAboveTheZero, shieldBall.z - (healthBarOffsetZ + healthBarMargin)) + end) + + + -- The healthbar text local fontSize = BASE_FONT_SIZE / 10 --glScale(fontSize / BASE_FONT_SIZE, fontSize / BASE_FONT_SIZE, fontSize / BASE_FONT_SIZE) glPushMatrix() @@ -184,37 +182,49 @@ end local shieldBallsIdsByTeam = {} local allShieldUnitsByTeam = {} +local allShieldUnitIDsByTeam = {} -local function updateClustering() - for _, allyTeamID in pairs(Spring.GetAllyTeamList()) do - local allUnits = {} - for _, teamID in pairs(Spring.GetTeamList(allyTeamID)) do - local teamUnits = Spring.GetTeamUnits(teamID) - for _, unitID in pairs(teamUnits) do - allUnits[#allUnits + 1] = unitID +local function validShieldUnit(unitID) + local unitDefID = spGetUnitDefID(unitID) + if unitDefID and shieldUnitDefs[unitDefID] then + local _, _, _, _, buildProgress = spGetUnitHealth(unitID) + if buildProgress == 1 then + local x,y,z = spGetUnitPosition(unitID) + if x then + return true end end + end +end + +local function updateClustering() + if benchmark then + benchmark:Enter("ClusterizeShieldUnits") + end + for _, allyTeamID in pairs(Spring.GetAllyTeamList()) do local allShieldUnits = {} - for _,unitID in pairs(allUnits) do - local unitDefID = spGetUnitDefID(unitID) - if unitDefID and shieldUnitDefs[unitDefID] then - local _, _, _, _, buildProgress = spGetUnitHealth(unitID) - if buildProgress == 1 then - local shieldWep = WeaponDefs[UnitDefs[unitDefID].shieldWeaponDef] - local x,y,z = spGetUnitPosition(unitID) - if x then - allShieldUnits[unitID] = { - shieldMaxCharge = shieldWep.shieldPower, - shieldRadius = shieldWep.shieldRadius, - x = x, - y = y, - z = z - } + for _, teamID in pairs(Spring.GetTeamList(allyTeamID)) do + for _,unitID in ipairs(allShieldUnitIDsByTeam[teamID]) do + local unitDefID = spGetUnitDefID(unitID) + if unitDefID and shieldUnitDefs[unitDefID] then + local _, _, _, _, buildProgress = spGetUnitHealth(unitID) + if buildProgress == 1 then + local x,y,z = spGetUnitPosition(unitID) + if x then + local shieldWep = WeaponDefs[UnitDefs[unitDefID].shieldWeaponDef] + allShieldUnits[unitID] = { + shieldMaxCharge = shieldWep.shieldPower, + shieldRadius = shieldWep.shieldRadius, + x = x, + y = y, + z = z + } + end end - --printThing(allShieldUnits[unitID], "shieldDef", "") end end end + local unitLocations = {} local unitNeighborsMatrix = {} for unitID, shieldProps in pairs(allShieldUnits) do @@ -236,13 +246,15 @@ local function updateClustering() end end end - --printThing(unitLocations, "unitLocations", "") - --printThing(unitNeighborsMatrix, "unitNeighborsMatrix", "") - local opticsObject = Optics.new(unitLocations, unitNeighborsMatrix, 2, false) + local opticsObject = Optics.new(unitLocations, unitNeighborsMatrix, 2, benchmark) opticsObject:Run() shieldBallsIdsByTeam[allyTeamID] = opticsObject:Clusterize(700) + allShieldUnitsByTeam[allyTeamID] = allShieldUnits end + if benchmark then + benchmark:Leave("ClusterizeShieldUnits") + end end --------------------------------- @@ -347,19 +359,62 @@ local function updateCurrentShieldBalls() shieldBalls[teamID] = ballsInTeam - drawBallHealthbarList = glCreateList(DrawBallHealthbar) + end + drawBallHealthbarList = glCreateList(DrawBallHealthbar) +end +--------------------------------- +-- Keeping track of shield units + +function widget:UnitCreated(unitID, unitDefID, teamID) + if unitDefID and shieldUnitDefs[unitDefID] then + allShieldUnitIDsByTeam[teamID][unitID] = true + end +end + +function widget:UnitDestroyed(unitID, unitDefID, teamID) + if unitDefID and shieldUnitDefs[unitDefID] then + allShieldUnitIDsByTeam[teamID][unitID] = nil end end + +function widget:UnitGiven(unitID, unitDefID, newTeamID, oldTeamID) + widget:UnitDestroyed(unitID, unitDefID, oldTeamID) + widget:UnitCreated(unitID, unitDefID, newTeamID) +end + +function widget:UnitTaken(unitID, unitDefID, oldTeamID, newTeamID) + widget:UnitDestroyed(unitID, unitDefID, oldTeamID) + widget:UnitCreated(unitID, unitDefID, newTeamID) +end + +function widget:UnitEnteredLos(unitID, unitTeam, allyTeam, unitDefID) + widget:UnitCreated(unitID, unitDefID, unitTeam) +end + +function widget:UnitLeftLos(unitID, unitTeam, allyTeam, unitDefID) + widget:UnitDestroyed(unitID, unitDefID, unitTeam) +end + --------------------------------- function widget:GameFrame(n) - if (n%UPDATE_FRAME==0) then + if (n%UPDATE_FRAME==1) then updateClustering() end updateCurrentShieldBalls() end function widget:Initialize() + for _, teamID in pairs(Spring.GetTeamList()) do + local teamUnits = {} + local teamUnitsSpring = Spring.GetTeamUnits(teamID) + for _, unitID in pairs(teamUnitsSpring) do + if validShieldUnit(unitID) then + teamUnits[#teamUnits + 1] = unitID + end + end + allShieldUnitIDsByTeam[teamID] = teamUnits + end updateClustering() updateCurrentShieldBalls() end From 6f3107fba3602a347e6714ceea6a071fb7c87ae9 Mon Sep 17 00:00:00 2001 From: danfireman Date: Sat, 16 Mar 2024 12:39:40 +0000 Subject: [PATCH 5/6] Fixing bug causing shield healthbar flickering when unequal sized shields are close --- LuaUI/Widgets/gui_shieldball_healthbars.lua | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/LuaUI/Widgets/gui_shieldball_healthbars.lua b/LuaUI/Widgets/gui_shieldball_healthbars.lua index e3d23c629b..ebbbf4039f 100644 --- a/LuaUI/Widgets/gui_shieldball_healthbars.lua +++ b/LuaUI/Widgets/gui_shieldball_healthbars.lua @@ -235,7 +235,9 @@ local function updateClustering() fID = unitID, } local unitsInRange = spGetUnitsInCylinder(x, z, shieldProps.shieldRadius*2) - unitNeighborsMatrix[unitID] = {} + if not unitNeighborsMatrix[unitID] then + unitNeighborsMatrix[unitID] = {} + end for _, unitInRange in ipairs(unitsInRange) do if shieldsAreTouching(shieldProps, allShieldUnits[unitInRange]) then unitNeighborsMatrix[unitID][unitInRange] = true From 4aa47c112ac47a9893e5a81cea505c0fe50dd3d6 Mon Sep 17 00:00:00 2001 From: danfireman Date: Sat, 15 Jun 2024 13:46:27 +0100 Subject: [PATCH 6/6] Ensure list of shield units are actually updated in the shield healthbars widget --- LuaUI/Widgets/gui_shieldball_healthbars.lua | 31 +++++++++++++-------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/LuaUI/Widgets/gui_shieldball_healthbars.lua b/LuaUI/Widgets/gui_shieldball_healthbars.lua index ebbbf4039f..19a0bf6017 100644 --- a/LuaUI/Widgets/gui_shieldball_healthbars.lua +++ b/LuaUI/Widgets/gui_shieldball_healthbars.lua @@ -250,7 +250,7 @@ local function updateClustering() end local opticsObject = Optics.new(unitLocations, unitNeighborsMatrix, 2, benchmark) opticsObject:Run() - shieldBallsIdsByTeam[allyTeamID] = opticsObject:Clusterize(700) + shieldBallsIdsByTeam[allyTeamID] = opticsObject:Clusterize(1400) allShieldUnitsByTeam[allyTeamID] = allShieldUnits end @@ -286,6 +286,20 @@ local function getUnitShieldRegen(unitID, ud) end return regen end +--------------------------------- + +local function hardShieldListUpdate() + for _, teamID in pairs(Spring.GetTeamList()) do + local teamUnits = {} + local teamUnitsSpring = Spring.GetTeamUnits(teamID) + for _, unitID in pairs(teamUnitsSpring) do + if validShieldUnit(unitID) then + teamUnits[#teamUnits + 1] = unitID + end + end + allShieldUnitIDsByTeam[teamID] = teamUnits + end +end --------------------------------- local function updateCurrentShieldBalls() @@ -364,6 +378,7 @@ local function updateCurrentShieldBalls() end drawBallHealthbarList = glCreateList(DrawBallHealthbar) end + --------------------------------- -- Keeping track of shield units @@ -403,20 +418,14 @@ function widget:GameFrame(n) if (n%UPDATE_FRAME==1) then updateClustering() end + if (n%77==2) then + hardShieldListUpdate() + end updateCurrentShieldBalls() end function widget:Initialize() - for _, teamID in pairs(Spring.GetTeamList()) do - local teamUnits = {} - local teamUnitsSpring = Spring.GetTeamUnits(teamID) - for _, unitID in pairs(teamUnitsSpring) do - if validShieldUnit(unitID) then - teamUnits[#teamUnits + 1] = unitID - end - end - allShieldUnitIDsByTeam[teamID] = teamUnits - end + hardShieldListUpdate() updateClustering() updateCurrentShieldBalls() end