# A computer opponent for strategy skirmishes (rts.ai) ## What it is for `rts.ai` is the computer side of an `rts.production` skirmish. It plays by the same rules as the player, through the same services, with no cheat and no extra resource. Every `think` seconds a faction puts its idle workers on the nearest nodes, trains workers up to a target, builds supply before a shortage, follows a build order, trains an army by weights, and sends attack waves on a schedule, larger each time. It can also defend its base. How it plays is data: its plans (easy, normal, hard) are documents you can edit or add to. The kit holds no rendering and no screen. ## Turn it on ```text gripforge_gamekit_install { project_id: "gkp_…", id: "rts.ai" } gripforge_gamekit_configure { project_id: "gkp_…", id: "rts.ai", config: { "difficulty": "hard" } } ``` It requires `game.engine`, `rts.production` and `units.command`. A faction is played by this kit when its `ai` field, in the `factions` of `rts.production`, names a plan. Targets: web and the Rust kit host. ## First example With the default plans, a computer faction `bot` plays `easy`. Calling the service `setPlan("bot", "hard")` answers true and `plan("bot")` now returns the `hard` plan. `setPlan("bot", "impossible")` or a non-string answers false and leaves the plan unchanged. `factions()` lists only the computer factions, with `{ faction, plan, step, wave, nextWave }`; `plan` returns null for the player's own faction or an unknown one. ## How it plays Every `think` seconds: 1. Idle workers go to the nearest node of the item furthest below its `gather` share (the rest go to the first item). 2. A supply building goes up when fewer than `supplyMargin` supply are free (`house` names it; default: the cheapest one that gives supply). 3. Workers are trained up to `workers`. 4. The next building of `build` is placed as soon as it is allowed and paid for, on a free spot around the base. 5. Each finished building trains the army type furthest below its `army` weight (two in queue at most). 6. When a wave is due, the idle army attack-moves to the nearest enemy building: at once when it has the planned size, else after 30 seconds with what it has. With `defend`, a strike on its base sends its idle army at the attacker. ## Data: the rts_ai_plans collection | Field | Meaning | |---|---| | `id`, `name` | Plan id and name (required, like every field below). | | `think` | Seconds between two decisions (0.2 to 30). | | `workers` | Workers it trains up to. | | `gather` | Share of workers per item, e.g. `{ "ore": 0.65, "wood": 0.35 }` (optional). | | `supplyMargin` | Builds supply when fewer are free. | | `house` | The supply building (optional). | | `build` | Build order, one building after the other. | | `army` | List of `{ unit, weight }`. | | `waves` | The first waves: `{ at, size }`, match seconds and units. | | `every`, `growth` | Seconds between later waves (10 to 3600) and units added to each later wave. | | `defend` | Whether the idle army answers a strike on the base. | Defaults shipped: `easy` (think 3, 7 workers, one wave at 300 s of 3 units, no defence), `normal` (think 1.5, 10 workers, first wave at 210 s of 4, defends), `hard` (think 0.75, 14 workers, first wave at 160 s of 5, defends). A project that defines `rts_ai_plans` uses its own plans. ## Settings and defaults | Setting | Type | Default | Meaning | |---|---|---|---| | `difficulty` | string | empty | A plan every AI faction plays. Empty: the `ai` of each faction in `rts.production`. | ## Service and events Service `rts.ai`: - `factions()`: `[{ faction, plan, step, wave, nextWave }]`. - `plan(faction)`: the plan document, or null. - `setPlan(faction, plan)`: true when the plan exists. | Event | Payload | |---|---| | `rts.ai.wave` | `{ faction, wave, size, planned, at, target }`: a wave left, with its units, the size the plan asked for, the match time and where it goes. | It listens to `combat.damaged`. The decision clock, the build order step and the waves sent are saved with the game, and so is the plan chosen by `setPlan`. ## Hooks and modding | Hook | Payload | Mutable | Cancelled | |---|---|---|---| | `ai.attack` | `{ faction, plan, wave, size, planned, at, x, z }` | `x`, `z` | The wave is skipped; the next comes at its time. | | `ai.build` | `{ faction, plan, type, reason, step, x, z }` | none | A build order step is skipped; a supply building is not asked again while the shortage lasts. `reason` is `order` or `supply`. | | `ai.defend` | `{ faction, plan, attacker, target, size, x, z }` | none | The army stays where it is. Asked at most every 3 seconds per faction. | ```json { "id": "truce", "hook": "ai.attack", "if": [{ "var": "story.truce", "op": "==", "value": true }], "cancel": true } ``` A mod may override `difficulty` and add, patch or remove documents of `rts_ai_plans`. ## When it does not do what you expect - A faction does nothing: its `ai` names no plan, or the match clock has not started, or the game is over. - Waves are small or late: the army is below the planned size, so the wave leaves 30 seconds after it was due with what exists. - A plan with no `waves` still attacks: the schedule then starts at 300 seconds with 3 units, spaced by `every` and `growth`. - `setPlan` answers false: the plan id is not in the plans, or is not a string. ## Limits - It uses only the services of `rts.production`, `units.command` and `resource.nodes`; it cannot do what the player cannot. - Its targets are enemy buildings; there is no scouting or tactics beyond waves and defence. ## Versions Kit 1.1.0; service `rts.ai` 1.0.0. Hooks and moddable plans since 1.1.0 (`game.engine` 1.2).