diff --git a/mods/cnc/scripts/campaign.lua b/mods/cnc/scripts/campaign.lua index 508ccb9d2e..b5f99349bc 100644 --- a/mods/cnc/scripts/campaign.lua +++ b/mods/cnc/scripts/campaign.lua @@ -9,6 +9,8 @@ Difficulty = Map.LobbyOptionOrDefault("difficulty", "normal") +--- Prepare basic messages for a player's win, loss, or objective updates. +---@param player player InitObjectives = function(player) Trigger.OnObjectiveCompleted(player, function(p, id) Media.DisplayMessage(p.GetObjectiveDescription(id), UserInterface.GetFluentMessage("objective-completed")) @@ -29,6 +31,12 @@ InitObjectives = function(player) end) end +--- Send reinforcements carried by a naval landing craft. +---@param player player Owner of the landing craft and its passengers. +---@param units string[] Unit types to spawn and unload. +---@param transportStart cpos Cell at which the craft spawns and removes itself. +---@param transportUnload cpos Cell at which passengers unload. +---@param rallypoint? cpos Cell to which unloaded passengers will move. ReinforceWithLandingCraft = function(player, units, transportStart, transportUnload, rallypoint) local transport = Actor.Create("lst", true, { Owner = player, Facing = Angle.North, Location = transportStart }) local subcell = 1 @@ -61,6 +69,10 @@ ReinforceWithLandingCraft = function(player, units, transportStart, transportUnl transport.Destroy() end +--- Schedule repairs for this building once it takes enough damage. +---@param owner player Owner of the building. +---@param actor actor Building to be repaired. +---@param modifier number The repair threshold. Below this health percentage, repairs are started. 1 is full health, while 0.5 is half. RepairBuilding = function(owner, actor, modifier) Trigger.OnDamaged(actor, function(building) if building.Owner == owner and building.Health < building.MaxHealth * modifier then @@ -69,6 +81,9 @@ RepairBuilding = function(owner, actor, modifier) end) end +--- Schedule repairs for a player's starting buildings. +---@param owner player Owner of the buildings. +---@param modifier number The repair threshold. Below this health percentage, repairs are started. 1 is full health, while 0.5 is half. RepairNamedActors = function(owner, modifier) Utils.Do(Map.NamedActors, function(actor) if actor.Owner == owner and actor.HasProperty("StartBuildingRepairs") then @@ -77,6 +92,12 @@ RepairNamedActors = function(owner, modifier) end) end +--- Schedule production tasks for a factory or other unit producer. +---@param player player Owner of the factory. +---@param factory actor The factory itself. +---@param delay? fun():integer Function that returns a delay until production repeats. If this is absent, production will not repeat. +---@param toBuild fun():string[] Function that returns a list of unit types to be produced. +---@param after? fun(actors: actor[]) Function called by each group of built units after their production is finished. Only alive actors are included. ProduceUnits = function(player, factory, delay, toBuild, after) if factory.IsDead or factory.Owner ~= player then return @@ -93,6 +114,10 @@ ProduceUnits = function(player, factory, delay, toBuild, after) end) end +--- Check if a player currently owns one of each building type in a list. +---@param player player +---@param buildingTypes string[] +---@return boolean CheckForBase = function(player, buildingTypes) local count = 0 @@ -105,6 +130,10 @@ CheckForBase = function(player, buildingTypes) return count == #buildingTypes end +--- Schedule production of a unit's replacement after its death. +---@param unit { [1]: actor } +---@param player player +---@param factory actor RebuildUnit = function(unit, player, factory) Trigger.OnKilled(unit[1], function() ProduceUnits(player, factory, nil, function() return { unit[1].Type } end, function(actors) @@ -113,13 +142,16 @@ RebuildUnit = function(unit, player, factory) end) end -MoveAndHunt = function(actors, path) +--- Order a group to attack-move toward each waypoint in a list, then hunt. +---@param actors actor[] +---@param waypoints actor[] +MoveAndHunt = function(actors, waypoints) Utils.Do(actors, function(actor) if not actor or actor.IsDead then return end - Utils.Do(path, function(point) + Utils.Do(waypoints, function(point) actor.AttackMove(point.Location) end) @@ -127,19 +159,25 @@ MoveAndHunt = function(actors, path) end) end -MoveAndIdle = function(actors, path) +--- Order a group to move toward each waypoint in a list. +---@param actors any +---@param waypoints actor[] +MoveAndIdle = function(actors, waypoints) Utils.Do(actors, function(actor) if not actor or actor.IsDead then return end - Utils.Do(path, function(point) + Utils.Do(waypoints, function(point) actor.Move(point.Location, 0) end) end) end Searches = 0 +--- Get the position of a hostile, mobile actor that is distant from anti-air sites. +---@param player player Player to be targeted. +---@return wpos|nil target The chosen airstrike position, if any. GetAirstrikeTarget = function(player) local list = player.GetGroundAttackers() diff --git a/mods/d2k/scripts/campaign.lua b/mods/d2k/scripts/campaign.lua index 449d01ff64..02accb8b9e 100644 --- a/mods/d2k/scripts/campaign.lua +++ b/mods/d2k/scripts/campaign.lua @@ -9,6 +9,8 @@ Difficulty = Map.LobbyOptionOrDefault("difficulty", "normal") +--- Prepare basic messages for a player's win, loss, or objective updates. +---@param player player InitObjectives = function(player) Trigger.OnObjectiveCompleted(player, function(p, id) Media.DisplayMessage(p.GetObjectiveDescription(id), UserInterface.GetFluentMessage("objective-completed")) @@ -29,11 +31,22 @@ InitObjectives = function(player) end) end +---The Dune stand-in for the usual "Battlefield Control" announcer. Mentat = UserInterface.GetFluentMessage("mentat") -SendCarryallReinforcements = function(player, currentWave, totalWaves, delay, pathFunction, unitTypes, customCondition, customHuntFunction, announcementFunction) +--- Prepare waves of reinforcements to be deployed from Carryalls. +---@param player player Owner of the reinforcements and Carryalls. +---@param currentWave integer Current wave count. A typical starting value is 0. +---@param totalWaves integer Total number of waves to be reinforced. +---@param delay integer Ticks between each reinforcement. +---@param pathFunction fun():cpos[] Returns a path for each Carryall's entry flight. +---@param unitTypes table Collection of unit types that will be reinforced. Each group within this collection is keyed to a different wave number. +---@param haltCondition? fun():boolean Returns true if reinforcements should stop. If this function is absent, assume false. +---@param customHuntFunction? fun(actors: actor[]) Function called by each unit within a group upon creation. This defaults to IdleHunt. Note that reinforced units will not be in the world until unloaded. +---@param announcementFunction? fun(currentWave: integer) Function called when a new Carryall is created. +SendCarryallReinforcements = function(player, currentWave, totalWaves, delay, pathFunction, unitTypes, haltCondition, customHuntFunction, announcementFunction) Trigger.AfterDelay(delay, function() - if customCondition and customCondition() then + if haltCondition and haltCondition() then return end @@ -54,14 +67,21 @@ SendCarryallReinforcements = function(player, currentWave, totalWaves, delay, pa end Utils.Do(units, customHuntFunction) - SendCarryallReinforcements(player, currentWave, totalWaves, delay, pathFunction, unitTypes, customCondition, customHuntFunction) + SendCarryallReinforcements(player, currentWave, totalWaves, delay, pathFunction, unitTypes, haltCondition, customHuntFunction) end) end -TriggerCarryallReinforcements = function(triggeringPlayer, reinforcingPlayer, area, unitTypes, path, customCondition) +--- Create a one-time area trigger that reinforces attackers with a Carryall. +---@param triggeringPlayer player +---@param reinforcingPlayer player +---@param area cpos[] +---@param unitTypes string[] +---@param path cpos[] Returns a path for the Carryall's entry flight. +---@param pauseCondition? fun():boolean Returns true if this trigger's activation should be paused. If this function is absent, assume false. +TriggerCarryallReinforcements = function(triggeringPlayer, reinforcingPlayer, area, unitTypes, path, pauseCondition) local fired = false Trigger.OnEnteredFootprint(area, function(a, id) - if customCondition and customCondition() then + if pauseCondition and pauseCondition() then return end @@ -74,17 +94,36 @@ TriggerCarryallReinforcements = function(triggeringPlayer, reinforcingPlayer, ar end) end +--- Destroy all non-reinforcement Carryalls owned by a player. +---@param player player DestroyCarryalls = function(player) Utils.Do(player.GetActorsByType("carryall"), function(actor) actor.Kill() end) end --- Used for the AI: +--- The following tables are used by the campaign AI, +--- with each value keyed to a different AI player. +--- Collection of a bot's spare units from production and reinforcement. +--- These units are used for periodic attacks or base/harvester defense. +---@type table IdlingUnits = { } + +--- Is a bot currently using idle units to attack or defend? +---@type table Attacking = { } + +--- Is a bot's production routine on hold? +---@type table HoldProduction = { } + +--- Was a bot's last harvester eaten by a sandworm? +---@type table LastHarvesterEaten = { } +--- Gather units from a bot's idle unit pool, up to a certain group size. +---@param owner player +---@param size integer +---@return actor[] SetupAttackGroup = function(owner, size) local units = { } @@ -104,6 +143,9 @@ SetupAttackGroup = function(owner, size) return units end +--- Order an attack from this bot if one is not already started. +---@param owner player +---@param size integer SendAttack = function(owner, size) if Attacking[owner] then return @@ -120,6 +162,10 @@ SendAttack = function(owner, size) end) end +--- Prepare a unit to call for help if attacked. +---@param unit actor +---@param defendingPlayer player +---@param defenderCount integer DefendActor = function(unit, defendingPlayer, defenderCount) Trigger.OnDamaged(unit, function(self, attacker) if unit.Owner ~= defendingPlayer then @@ -154,7 +200,12 @@ DefendActor = function(unit, defendingPlayer, defenderCount) end) end +--- Prepare a harvester to call for help when attacked. +---@param unit actor +---@param owner player +---@param defenderCount integer ProtectHarvester = function(unit, owner, defenderCount) + -- Note that worm attacks will not trigger the OnDamaged event for this. DefendActor(unit, owner, defenderCount) -- Worms don't kill the actor, but dispose it instead. @@ -172,6 +223,10 @@ ProtectHarvester = function(unit, owner, defenderCount) end) end +--- Schedule repairs for this building once it takes enough damage. +---@param owner player Owner of the building. +---@param actor actor Building to be repaired. +---@param modifier number The repair threshold. Below this health percentage, repairs are started. 1 is full health, while 0.5 is half. RepairBuilding = function(owner, actor, modifier) Trigger.OnDamaged(actor, function(building) if building.Owner == owner and building.Health < building.MaxHealth * modifier then @@ -180,6 +235,11 @@ RepairBuilding = function(owner, actor, modifier) end) end +--- Prepare buildings to call for help and begin repairs if attacked. +---@param owner player Owner of the base. +---@param baseBuildings actor[] Buildings to defend and repair. +---@param modifier number The repair threshold. Below this health percentage, repairs are started. 1 is full health, while 0.5 is half. +---@param defenderCount integer Maximum number of defenders to use per counterattack. DefendAndRepairBase = function(owner, baseBuildings, modifier, defenderCount) Utils.Do(baseBuildings, function(actor) if actor.IsDead then @@ -191,6 +251,13 @@ DefendAndRepairBase = function(owner, baseBuildings, modifier, defenderCount) end) end +--- Schedule production and attacks for a factory or other unit producer. +---@param player player Owner of the factory. +---@param factory actor The factory itself. +---@param delay fun():integer Function that returns a delay until production repeats. +---@param toBuild fun():string[] Function that returns a list of unit types to be produced. +---@param attackSize integer Number of units that will form the next attack wave. +---@param attackThresholdSize integer Number of idle units that will trigger an attack wave. ProduceUnits = function(player, factory, delay, toBuild, attackSize, attackThresholdSize) if factory.IsDead or factory.Owner ~= player then return diff --git a/mods/ra/scripts/campaign.lua b/mods/ra/scripts/campaign.lua index 48c86e268a..5742220291 100644 --- a/mods/ra/scripts/campaign.lua +++ b/mods/ra/scripts/campaign.lua @@ -9,6 +9,8 @@ Difficulty = Map.LobbyOptionOrDefault("difficulty", "normal") +--- Prepare basic messages for a player's win, loss, or objective updates. +---@param player player InitObjectives = function(player) Trigger.OnObjectiveCompleted(player, function(p, id) Media.DisplayMessage(p.GetObjectiveDescription(id), UserInterface.GetFluentMessage("objective-completed")) @@ -31,6 +33,10 @@ InitObjectives = function(player) end AttackAircraftTargets = { } +--- Order an aircraft to seek and attack an enemy player's units whenever idle. +--- Each target is focused until it can no longer be attacked. +---@param aircraft actor +---@param enemyPlayer player InitializeAttackAircraft = function(aircraft, enemyPlayer) Trigger.OnIdle(aircraft, function() local actorId = tostring(aircraft) @@ -50,6 +56,10 @@ InitializeAttackAircraft = function(aircraft, enemyPlayer) end) end +--- Return a random enemy target for an actor. +---@param unit actor Actor to be given a target. +---@param enemyPlayer player Player to be targeted. +---@return actor|nil ChooseRandomTarget = function(unit, enemyPlayer) local target = nil local enemies = Utils.Where(enemyPlayer.GetActors(), function(self) @@ -61,6 +71,9 @@ ChooseRandomTarget = function(unit, enemyPlayer) return target end +--- Call a function when one of the actors in a group is damaged. The callback function will be called as func(self: actor, attacker: actor, damage: integer). +---@param actors actor[] +---@param func fun(self: actor, attacker: actor, damage: integer) OnAnyDamaged = function(actors, func) Utils.Do(actors, function(actor) Trigger.OnDamaged(actor, func)