Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
- [Legion changes]
- Perdition doesn't gain extra range from elevation
- Martyr damages are added to its team's damage dealt
- Harbinger mines now spawn and detonate on tall targets
- EMP damage prevents reactive armor from regenerating
- Disable Air Units now removes and refunds drone spawners
- Range rings, graphics, blueprints, and other housekeeping
Expand Down
33 changes: 29 additions & 4 deletions language/en/interface.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@
"mission": "Mission",
"stats": "Statistics",
"info": "Info",
"graphs": "Graphs",
"save": "Save"
},
"quit": {
Expand Down Expand Up @@ -392,8 +391,17 @@
"graph": {
"composition": "Composition",
"compositionDesc": "The team's unit value split by kind over the game, as shares of the whole.",
"kindHint": "Click to leave this kind of milestone off the charts, or to put it back.",
"overview": "◀ Graphs overview",
"overviewDesc": "Back to the page of charts this one was opened from.",
"timeline": "Timeline",
"timelineDesc": "A lane per team along the game clock, with every milestone it reached on it: who teched first, who lost a commander, who died. Hover a picture for what it was.",
"profile": "Team profile",
"profileDesc": "Every team's shape as it stands: damage, units, metal, energy, what it has on the field and how fast it plays, each axis measured against the best team on the chart.",
"foldHint": "Click to fold this team's players away, and again to show them.",
"all": "All",
"you": "You",
"youHint": "Your own team alone: the selection becomes yours, whatever was picked before.",
"allHint": "Clears the selection and shows hidden teams again, so every team is drawn alike.",
"allMilestones": "Every team's milestones go on the chart.",
"teams": "%{count} teams",
Expand Down Expand Up @@ -430,6 +438,8 @@
"antinuke": "First anti-nuke",
"lrpc": "First long-range cannon",
"commanderLost": "Commander lost",
"firstKill": "First kill",
"firstLoss": "First loss",
"teamDied": "Out of the game"
},
"group": {
Expand All @@ -452,10 +462,16 @@
"perMinuteDesc": "Totals divided by the minutes each team was in the game, so a short game or an early exit compares fairly.",
"bars": "Bars",
"barsDesc": "A bar behind every number, scaled to the largest in its column.",
"vsMe": "vs. me",
"vsMeDesc": "Every number as the difference from your own: green where a team is doing better than you, red where it is worse. Your own row keeps its numbers, and so do the teams' totals against your team's.",
"trend": "Trend",
"trendDesc": "A small line beside every number: how it moved over the game, every team against the same scale. A running total shows the pace it rose at, so a cell says how it is going, not only where it stands.",
"milestones": "Milestones",
"milestonesDesc": "Milestones as unit pictures on the chart, framed in their player's colour: the selected teams', every team's when none is selected. Hover one for whose it was and what.",
"selectedOnly": "Selected only",
"selectedOnlyDesc": "Only the teams selected in the bar above the chart are drawn. Off, every team is drawn and the selected ones stand out in front."
"milestonesDesc": "Milestones as unit pictures on the chart, framed in their player's colour: the selected teams', every team's when none is selected. Each chart only shows the kinds that say something about it - teching on what a team built and spent, a nuke on the damage it dealt. Hover one for whose it was and what.",
"milestoneKinds": "Milestone kinds",
"milestoneKindsDesc": "Which kinds of milestone may go on the charts. Opens a list to pick them from; the count says how many are on. A chart still only shows the kinds that say something about it.",
"perPage": "Graphs per page",
"perPageDesc": "How many graphs the page shows at once, as a grid of the picked group's stats. The wheel scrolls it when the group has more than fit, and a click opens one on its own."
},
"damage": "Damage",
"damageDealt": "Dealt",
Expand Down Expand Up @@ -498,6 +514,12 @@
"shortBuildPower": "BP",
"buildPowerUse": "Build power in use",
"shortBuildPowerUse": "In use",
"buildPowerIdle": "Build power idle",
"shortBuildPowerIdle": "Idle",
"damagePerMetal": "Damage per metal",
"shortDamagePerMetal": "Dmg/M",
"armyShare": "Army share",
"shortArmyShare": "Army %",
"value": "Unit value",
"unitValue": "Unit value",
"shortValue": "Value",
Expand Down Expand Up @@ -564,6 +586,9 @@
"conversion": "The share of the team's energy conversion capacity converting right now.",
"buildPower": "Build power of every builder, nano turret and factory the team has.",
"buildPowerUse": "The share of the team's build power at work right now: building, repairing, reclaiming or resurrecting.",
"buildPowerIdle": "Build power that stood idle, added up over the game, in build power minutes: a thousand idle build power standing still for a minute is 1k.",
"damagePerMetal": "Damage dealt for every metal the team spent, so a small army that traded well beats a big one that did not.",
"armyShare": "The share of the team's unit value that is army rather than economy, defence or builders.",
"unitValue": "What every unit the team has cost, in metal with energy at a sixtieth.",
"valueArmy": "Value of the mobile ground army: every armed ground and hover unit.",
"valueAir": "Value of every aircraft that is not a builder.",
Expand Down
23 changes: 22 additions & 1 deletion luarules/gadgets/api_teamstats.lua
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,8 @@
local BUCKETS = { "army", "air", "sea", "defense", "strategic", "factories", "builders", "economy", "utility" }

-- The moments worth remembering, and what marks them. `built` is tested on every unit
-- a team finishes and `lost` on every unit it loses; a milestone is kept once per team,
-- a team finishes and `lost` on every unit it loses, and the ones with neither are marked
-- where they happen; a milestone is kept once per team,
-- or every time when `every` is set. The unit that reached it is stored with it.
local MILESTONES = {
{
Expand Down Expand Up @@ -92,6 +93,8 @@
end,
every = true,
},
{ key = "firstKill" },
{ key = "firstLoss" },
{ key = "teamDied" },
}

Expand All @@ -114,6 +117,7 @@
"convUse",
"buildPower",
"buildPowerActive",
"buildPowerIdle",
"unitCount",
"unitValue",
"killedValue",
Expand All @@ -138,6 +142,7 @@
-- The keys the tally keeps as running counters; the rest of a sample is read live.
local TALLIED = {
"unitCount",
"buildPowerIdle",
"unitValue",
"buildPower",
"killedValue",
Expand Down Expand Up @@ -339,6 +344,12 @@
end
end

-- The milestones that are not read off a unit definition, by key.
local MILESTONE_BY_KEY = {}
for i = 1, #MILESTONES do
MILESTONE_BY_KEY[MILESTONES[i].key] = MILESTONES[i]
end

local function markMilestone(teamID, m, unitDefID, unitID)
if not m.every then
if reached[teamID][m.key] then
Expand Down Expand Up @@ -390,10 +401,18 @@

local scratch = {}

-- The minutes one sampling period is worth: idle build power is added up over them.
local SAMPLE_MINUTES = SAMPLE_PERIOD / 1800

local function sample(frame)
for teamID, h in pairs(history) do
if not dead[teamID] then
readLive(teamID, scratch)
-- What was not building over the period just gone, in build power minutes.
local t = teams[teamID]
local idle = math.max(0, (scratch.buildPower or 0) - (scratch.buildPowerActive or 0))
t.buildPowerIdle = t.buildPowerIdle + idle * SAMPLE_MINUTES

Check warning on line 414 in luarules/gadgets/api_teamstats.lua

View workflow job for this annotation

GitHub Actions / emmylua_check

need-check-nil

t may be nil

Check warning on line 414 in luarules/gadgets/api_teamstats.lua

View workflow job for this annotation

GitHub Actions / emmylua_check

need-check-nil

t may be nil
scratch.buildPowerIdle = t.buildPowerIdle

Check warning on line 415 in luarules/gadgets/api_teamstats.lua

View workflow job for this annotation

GitHub Actions / emmylua_check

need-check-nil

t may be nil
local n = #h.frames + 1
h.frames[n] = frame
local values = h.values
Expand Down Expand Up @@ -448,6 +467,7 @@
local victim = teams[unitTeam]
if victim then
victim.lostValue = victim.lostValue + value
markMilestone(unitTeam, MILESTONE_BY_KEY.firstLoss, unitDefID, unitID)
if defIsCommander[unitDefID] then
victim.comLost = victim.comLost + 1
end
Expand All @@ -468,6 +488,7 @@
return
end
killer.killedValue = killer.killedValue + value
markMilestone(attackerTeam, MILESTONE_BY_KEY.firstKill, unitDefID, unitID)
local split = killedAs[defBucket[unitDefID]]
if split then
killer[split] = killer[split] + value
Expand Down
28 changes: 0 additions & 28 deletions luarules/gadgets/game_zombies.lua
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ local ZOMBIE_REZ_FRAME_PARAM = "zombie_rez_frame"
local WAS_ZOMBIE_PARAM = "wasZombie"
local PUBLIC_RULES_PARAM_ACCESS = { public = true }
local WAS_ZOMBIE_TIMEOUT_FRAMES = Game.gameSpeed * 3
local MIN_CAPTURE_DISTANCE_BOOST = 300
local MIN_ZOMBIE_XP = 0.25
local ZOMBIE_MAX_XP = 1.5

Expand Down Expand Up @@ -363,26 +362,6 @@ local function initializeZombieAI(unitID, unitDefID)
end
end

local function applyZombieBuildRangeBonus(unitID, unitDefID)
local unitDef = unitDefs[unitDefID]
local originalBuildDistance = unitDef and unitDef.buildDistance
if not originalBuildDistance or originalBuildDistance <= 0 then
return
end
local losRadius = unitDef.losRadius or unitDef.sightDistance or 0
local boostedBuildDistance = math.max(originalBuildDistance, MIN_CAPTURE_DISTANCE_BOOST)
spring.SetUnitBuildParams(unitID, "buildDistance", boostedBuildDistance)
end

local function restoreOriginalBuildRange(unitID, unitDefID)
local unitDef = unitDefs[unitDefID]
local originalBuildDistance = unitDef and unitDef.buildDistance
if not originalBuildDistance or originalBuildDistance <= 0 then
return
end
spring.SetUnitBuildParams(unitID, "buildDistance", originalBuildDistance)
end

local function rollSpawnCount()
return random(currentZombieConfig.countMin, currentZombieConfig.countMax)
end
Expand Down Expand Up @@ -471,7 +450,6 @@ local function spawnZombies(featureID, unitDefID, healthReductionRatio, x, y, z,
spring.TransferUnit(unitID, scavTeamID)
else
initializeZombieAI(unitID, unitDefToCreate)
applyZombieBuildRangeBonus(unitID, unitDefToCreate)
end
end
end
Expand Down Expand Up @@ -509,9 +487,6 @@ local function setZombie(unitID)

spring.SetUnitRulesParam(unitID, "zombie", 1)
initializeZombieAI(unitID, unitDefID)
if spring.GetUnitTeam(unitID) == gaiaTeamID then
applyZombieBuildRangeBonus(unitID, unitDefID)
end
end

function gadget:FeatureBuildStepPost(featureID)
Expand Down Expand Up @@ -712,9 +687,6 @@ function gadget:AllowUnitCaptureStep(builderID, builderTeam, unitID, unitDefID,
end

function gadget:UnitGiven(unitID, unitDefID, newTeam, oldTeam)
if oldTeam == gaiaTeamID and newTeam ~= gaiaTeamID and isZombie(unitID) then
restoreOriginalBuildRange(unitID, unitDefID)
end
if pendingZombieCaptures[unitID] then
pendingZombieCaptures[unitID] = nil
if not isZombie(unitID) then
Expand Down
38 changes: 25 additions & 13 deletions luarules/gadgets/unit_explosion_spawner.lua
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ local expireCount = 0
local spawnList = {} -- [index] = {.spawnDef, .teamID, .x, .y, .z, .ownerID}, subtables reused
local spawnCount = 0
local spawnNames = {}
local minWaterDepth = -12 --calibrated off of the armpw's (minimum found) maxwaterdepth value
local minWaterDepth = -20 -- see movedefs.lua

for weaponDefID = 0, #WeaponDefs do
local wdcp = WeaponDefs[weaponDefID].customParams
Expand Down Expand Up @@ -127,21 +127,19 @@ local function SpawnUnit(spawnData)
else
-- Early validation checks
local x, z = spawnData.x, spawnData.z
if x <= 0 or x >= mapsizeX or z <= 0 or z >= mapsizeZ then
return -- Out of bounds
end

local validSurface = false
local unitDetonates = x <= 0 or x >= mapsizeX or z <= 0 or z >= mapsizeZ -- out of bounds?
local y = spGetGroundHeight(x, z)

if not spawnDef.surface then
validSurface = true
elseif spawnData.y < mathMax(y + 32, 32) then
else
local surface = spawnDef.surface
if stringFind(surface, "LAND", 1, true) and y > minWaterDepth then
validSurface = true
elseif stringFind(surface, "SEA", 1, true) and y <= 0 then
validSurface = true
validSurface = (surface:find("LAND", 1, true) and y >= minWaterDepth)
or (surface:find("SEA", 1, true) and y <= 0)
if validSurface and spawnData.y >= mathMax(y + 32, 32) then
unitDetonates = true
end
end

Expand Down Expand Up @@ -219,6 +217,11 @@ local function SpawnUnit(spawnData)
end
end

if unitDetonates then
spDestroyUnit(unitID, false, false) -- e.g. mines use explodeas
return
end

if spawnDef.expire then
expireCount = expireCount + 1
expireByID[unitID] = expireCount
Expand Down Expand Up @@ -270,6 +273,18 @@ function gadget:Initialize()
end
end

local function getProjectileTeam(projectileID, ownerID)
local teamID = spGetProjectileTeamID(projectileID) or (ownerID and spGetUnitTeam(ownerID))
if teamID then
return teamID
end

local allyTeamID = Spring.GetProjectileAllyTeamID(projectileID)
if allyTeamID then
return (Spring.GetTeamList(allyTeamID) or {})[1]
end
end

function gadget:Explosion(weaponDefID, x, y, z, ownerID, proID)
if noCreate then
noCreate = false
Expand All @@ -278,10 +293,7 @@ function gadget:Explosion(weaponDefID, x, y, z, ownerID, proID)

if spawnDefs[weaponDefID] then
local spawnDef = spawnDefs[weaponDefID] -- guaranteed not nil by Explosion_GetWantedWeaponDef
local teamID = proID and spGetProjectileTeamID(proID)
if not teamID and ownerID then
teamID = spGetUnitTeam(ownerID)
end
local teamID = proID and getProjectileTeam(proID, ownerID)
if not teamID then
return
end
Expand Down
Loading
Loading