diff --git a/luarules/gadgets/api_missions.lua b/luarules/gadgets/api_missions.lua index 68c5589f9a4..d233af6c980 100644 --- a/luarules/gadgets/api_missions.lua +++ b/luarules/gadgets/api_missions.lua @@ -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() diff --git a/luarules/gadgets/api_missions_triggers.lua b/luarules/gadgets/api_missions_triggers.lua index d6279e9b156..7556dc0659c 100644 --- a/luarules/gadgets/api_missions_triggers.lua +++ b/luarules/gadgets/api_missions_triggers.lua @@ -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: @@ -405,11 +420,25 @@ 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 @@ -417,6 +446,9 @@ 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) @@ -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) diff --git a/luarules/mission_api/actions.lua b/luarules/mission_api/actions.lua index b39e3d8b873..141c71b08ff 100644 --- a/luarules/mission_api/actions.lua +++ b/luarules/mission_api/actions.lua @@ -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 diff --git a/luarules/mission_api/triggers_schema.lua b/luarules/mission_api/triggers_schema.lua index e697cb5c79e..1713dd9b9e6 100644 --- a/luarules/mission_api/triggers_schema.lua +++ b/luarules/mission_api/triggers_schema.lua @@ -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] = { diff --git a/singleplayer/mission-api-tests/statistics_triggers_test.lua b/singleplayer/mission-api-tests/statistics_triggers_test.lua new file mode 100644 index 00000000000..9dac68835a7 --- /dev/null +++ b/singleplayer/mission-api-tests/statistics_triggers_test.lua @@ -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, +}