Skip to content
4 changes: 3 additions & 1 deletion luarules/gadgets/api_missions.lua
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,9 @@ function gadget:Initialize()
--scriptPath = 'mission-api-tests/sound_test.lua'
--scriptPath = 'mission-api-tests/issue_orders_test.lua'
--scriptPath = 'mission-api-tests/unit_triggers_test.lua'
scriptPath = 'mission-api-tests/feature_triggers_test.lua'
--scriptPath = 'mission-api-tests/feature_triggers_test.lua'
--scriptPath = 'mission-api-tests/resource_test.lua'
scriptPath = 'mission-api-tests/statistics_triggers_test.lua'

if not scriptPath then
gadgetHandler:RemoveGadget()
Expand Down
41 changes: 40 additions & 1 deletion luarules/gadgets/api_missions_triggers.lua
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,21 @@ local function checkFeatureDestroyed(trigger, featureID, featureDefID, attackerA
activateTrigger(trigger)
end

-- Per-team running totals, incremented by call-ins and checked by statistics triggers:
local totalUnitsLost = {}
local totalUnitsKilled = {}
local totalUnitsBuilt = {}
local totalUnitsCaptured = {}
local function checkStatisticsTriggers(triggerType, counters, teamID)
processTriggersOfType(triggerType, function(trigger, _)
if trigger.parameters.teamID ~= teamID then return end
local count = counters[teamID] or 0
if count >= trigger.parameters.quantity then
activateTrigger(trigger)
end
end)
end


----------------------------------------------------------------
--- Call-ins:
Expand Down Expand Up @@ -405,18 +420,35 @@ function gadget:UnitCreated(unitID, unitDefID, unitTeam, builderID)
end)
end

function gadget:UnitDestroyed(unitID, unitDefID, unitTeam, _, _, _)
function gadget:UnitDestroyed(unitID, unitDefID, unitTeam, attackerID, attackerDefID, attackerTeam)
processTriggersOfType(types.UnitKilled, function(trigger, _)
checkUnitRemoved(trigger, unitID, unitDefID, unitTeam)
end)

-- The unit's team lost a unit:
totalUnitsLost[unitTeam] = (totalUnitsLost[unitTeam] or 0) + 1
checkStatisticsTriggers(types.TotalUnitsLost, totalUnitsLost, unitTeam)

-- The attacker's team kills an enemy unit:
if attackerTeam ~= nil and attackerTeam ~= unitTeam then
local unitAllyTeam = Spring.GetTeamAllyTeamID(unitTeam)
local attackerAllyTeam = Spring.GetTeamAllyTeamID(attackerTeam)
if unitAllyTeam and attackerAllyTeam and unitAllyTeam ~= attackerAllyTeam then
totalUnitsKilled[attackerTeam] = (totalUnitsKilled[attackerTeam] or 0) + 1
checkStatisticsTriggers(types.TotalUnitsKilled, totalUnitsKilled, attackerTeam)
end
end

untrackUnitID(unitID)
end

function gadget:UnitTaken(unitID, unitDefID, oldTeam, newTeam)
processTriggersOfType(types.UnitCaptured, function(trigger, _)
checkUnitCaptured(trigger, unitID, unitDefID, oldTeam, newTeam)
end)

totalUnitsCaptured[newTeam] = (totalUnitsCaptured[newTeam] or 0) + 1
checkStatisticsTriggers(types.TotalUnitsCaptured, totalUnitsCaptured, newTeam)
end

function gadget:UnitEnteredLos(unitID, unitTeam, losAllyTeamID, unitDefID)
Expand All @@ -435,6 +467,13 @@ function gadget:UnitFinished(unitID, unitDefID, unitTeam)
processTriggersOfType(types.ConstructionFinished, function(trigger, _)
checkConstructionFinished(trigger, unitID, unitDefID, unitTeam)
end)

-- Don't count units spawned by SpawnUnits action
if GG['MissionAPI'].spawningUnit then return end
-- Don't count starting commanders, initial loadout, etc.
if Spring.GetGameFrame() <= 0 then return end
totalUnitsBuilt[unitTeam] = (totalUnitsBuilt[unitTeam] or 0) + 1
checkStatisticsTriggers(types.TotalUnitsBuilt, totalUnitsBuilt, unitTeam)
end

function gadget:TeamDied(teamID)
Expand Down
3 changes: 3 additions & 0 deletions luarules/mission_api/actions.lua
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,10 @@ local function spawnUnits(unitName, unitDefName, teamID, position, quantity, fac

for _, pos in pairs(positions) do
pos.y = Spring.GetGroundHeight(pos.x, pos.z)
-- Set flag for TotalUnitsBuilt trigger not to count units not actually built by the player.
GG['MissionAPI'].spawningUnit = true
local unitID = Spring.CreateUnit(unitDefName, pos.x, pos.y, pos.z, facing or 's', teamID, construction)
GG['MissionAPI'].spawningUnit = false
trackUnit(unitName, unitID)
end
end
Expand Down
52 changes: 48 additions & 4 deletions luarules/mission_api/triggers_schema.lua
Original file line number Diff line number Diff line change
Expand Up @@ -390,10 +390,54 @@ local parameters = {
[triggerTypes.ResourceProduction] = { },

-- Statistics
[triggerTypes.TotalUnitsLost] = { },
[triggerTypes.TotalUnitsBuilt] = { },
[triggerTypes.TotalUnitsKilled] = { },
[triggerTypes.TotalUnitsCaptured] = { },
[triggerTypes.TotalUnitsLost] = {
[1] = {
name = 'teamID',
required = true,
type = Types.TeamID,
},
[2] = {
name = 'quantity',
required = true,
type = Types.Number,
},
},
[triggerTypes.TotalUnitsBuilt] = {
[1] = {
name = 'teamID',
required = true,
type = Types.TeamID,
},
[2] = {
name = 'quantity',
required = true,
type = Types.Number,
},
},
[triggerTypes.TotalUnitsKilled] = {
[1] = {
name = 'teamID',
required = true,
type = Types.TeamID,
},
[2] = {
name = 'quantity',
required = true,
type = Types.Number,
},
},
[triggerTypes.TotalUnitsCaptured] = {
[1] = {
name = 'teamID',
required = true,
type = Types.TeamID,
},
[2] = {
name = 'quantity',
required = true,
type = Types.Number,
},
},

-- Team
[triggerTypes.TeamDestroyed] = {
Expand Down
208 changes: 208 additions & 0 deletions singleplayer/mission-api-tests/statistics_triggers_test.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
---
--- Statistics triggers test mission.
---

local triggerTypes = GG['MissionAPI'].TriggerTypes
local actionTypes = GG['MissionAPI'].ActionTypes

local triggers = {

-- ── Spawns ────────────────────────────────────────────────────────────────

spawnBots = {
type = triggerTypes.TimeElapsed,
parameters = {
gameFrame = 30,
},
actions = { 'spawnFriendlyBot', 'spawnEnemyBot' },
},

killFriendlyBot = {
type = triggerTypes.TimeElapsed,
parameters = {
gameFrame = 180,
},
actions = { 'killFriendlyBot' },
},

spawnCapture = {
type = triggerTypes.TimeElapsed,
parameters = {
gameFrame = 240,
},
actions = { 'spawnCapturable', 'spawnCapturer' },
},

orderCapture = {
type = triggerTypes.TimeElapsed,
parameters = {
gameFrame = 270,
},
actions = { 'orderCapture' },
},

spawnBuilders = {
type = triggerTypes.TimeElapsed,
parameters = {
gameFrame = 510,
},
actions = { 'spawnConstructor', 'orderBuild' },
},

-- ── Statistics triggers ───────────────────────────────────────────────────

totalUnitsKilledReached = {
type = triggerTypes.TotalUnitsKilled,
parameters = {
teamID = 0,
quantity = 1,
},
actions = { 'messageTotalUnitsKilled' },
},

totalUnitsLostReached = {
type = triggerTypes.TotalUnitsLost,
parameters = {
teamID = 0,
quantity = 1,
},
actions = { 'messageTotalUnitsLost' },
},

totalUnitsCapturedReached = {
type = triggerTypes.TotalUnitsCaptured,
parameters = {
teamID = 0,
quantity = 1,
},
actions = { 'messageTotalUnitsCaptured' },
},

totalUnitsBuiltReached = {
type = triggerTypes.TotalUnitsBuilt,
parameters = {
teamID = 0,
quantity = 1,
},
actions = { 'messageTotalUnitsBuilt' },
},
}

local actions = {

-- ── Spawn ─────────────────────────────────────────────────────────────────

spawnFriendlyBot = {
type = actionTypes.SpawnUnits,
parameters = {
unitName = 'friendlyBot',
unitDefName = 'armwar',
teamID = 0,
position = { x = 1800, z = 1800 },
},
},

spawnEnemyBot = {
type = actionTypes.SpawnUnits,
parameters = {
unitDefName = 'armpw',
teamID = 1,
position = { x = 1880, z = 1800 },
},
},

spawnCapturable = {
type = actionTypes.SpawnUnits,
parameters = {
unitDefName = 'armsolar',
teamID = 1,
position = { x = 1800, z = 1950 },
},
},

spawnCapturer = {
type = actionTypes.SpawnUnits,
parameters = {
unitName = 'capturer',
unitDefName = 'armdecom',
teamID = 0,
position = { x = 1900, z = 2000 },
},
},

spawnConstructor = {
type = actionTypes.SpawnUnits,
parameters = {
unitName = 'constructor',
unitDefName = 'armck',
teamID = 0,
position = { x = 1850, z = 2100 },
quantity = 4,
},
},

-- ── Orders ────────────────────────────────────────────────────────────────

orderCapture = {
type = actionTypes.IssueOrders,
parameters = {
unitName = 'capturer',
orders = {
{ CMD.FIRE_STATE, CMD.FIRESTATE_HOLDFIRE },
{ CMD.CAPTURE, { 1800, 0, 1900, 200 } },
},
},
},

orderBuild = {
type = actionTypes.IssueOrders,
parameters = {
unitName = 'constructor',
orders = {
{ 'armwin', { 1950, 0, 2100 } },
},
},
},

killFriendlyBot = {
type = actionTypes.DespawnUnits,
parameters = {
unitName = 'friendlyBot',
},
},

-- ── Statistics trigger messages ───────────────────────────────────────────

messageTotalUnitsKilled = {
type = actionTypes.SendMessage,
parameters = {
message = "[Statistics Test] TotalUnitsKilled fired: Team 0 has killed >= 1 unit.",
},
},

messageTotalUnitsLost = {
type = actionTypes.SendMessage,
parameters = {
message = "[Statistics Test] TotalUnitsLost fired: Team 0 has lost >= 1 unit.",
},
},

messageTotalUnitsCaptured = {
type = actionTypes.SendMessage,
parameters = {
message = "[Statistics Test] TotalUnitsCaptured fired: Team 0 has captured >= 1 unit.",
},
},

messageTotalUnitsBuilt = {
type = actionTypes.SendMessage,
parameters = {
message = "[Statistics Test] TotalUnitsBuilt fired: Team 0 has built >= 1 unit.",
},
},
}

return {
Triggers = triggers,
Actions = actions,
}
Loading