# Selectable units that take orders (units.command) ## What it is for `units.command` is the core of a real-time strategy or tactics game. A unit is any entity that carries the component `commandable` (faction, stats, order queue, what it carries); a building, or anything else the player may select, carries `selectable { faction, radius }`. All the state of a unit lives in its components, so a save holds it and a load carries on exactly. The local player's faction selects (click, drag box, control groups) and orders (move, attack, gather, build...). The kit draws nothing itself: `marks()` tells every renderer what to mark (a ring under each selected unit or building, a health bar over each selected or damaged one). It leaves the models, the animation and the camera to the engine, and the rules of production, economy and opponents to the kits built on top of it. ## Turn it on ```text gripforge_gamekit_install { project_id: "gkp_…", id: "units.command" } gripforge_gamekit_configure { project_id: "gkp_…", id: "units.command", config: { "faction": "blue", "spacing": 2.8 } } ``` The kit needs `game.engine`, `combat.health`, `combat.damage` and `input.actions`. Models come from the asset slot `rts_unit` (one per unit type, chosen by the `variant`). ## First example The fixture `units.command.orders` runs it: with `faction` set to `blue`, units are spawned, selected, then ordered. A spec needs a `faction` and a `type`; a missing one answers `null` instead of an id. ```json { "op": "call", "service": "units.command", "method": "spawn", "args": [{ "faction": "blue", "type": "soldier", "health": 100, "speed": 4, "attack": { "damage": 10, "range": 1.5, "cooldown": 1 } }, [0.2, 0.1]] } { "op": "call", "service": "units.command", "method": "order", "args": [["e1", "e2", "e3"], { "type": "move", "point": [14.3, 12.7] }] } ``` The three units walk to the point in formation. Adding `{ "queue": true }` as the third argument puts the order after the ones they already have. ## How it plays - **Select**: left click or tap on one of your units or buildings; Shift adds or removes. Drag a box to take every unit inside. Ctrl with a digit 1 to 5 sets a control group, the digit selects it. Z selects the whole army (workers left out). A click on an enemy shows it without ordering anything. - **Order**: right click, or tap with units selected. On an enemy: attack. On a resource node: workers gather. On one of your construction sites: workers build. On a drop-off: a loaded worker carries back. Elsewhere: move in formation. Shift queues. M, A and P arm move, attack-move and patrol for the next click (Escape disarms); S stops, H holds position. With buildings selected, a right click sets their rally point. - **Mini-map**: a left click moves the camera, a right click orders the selection there. ## Settings and defaults | Setting | Type | Default | Meaning | |---|---|---|---| | `faction` | text | empty | the faction the local player commands (empty: the team of the player entity, or what a game kit sets) | | `cellSize` | number | `1` | metres per cell of the walk grid (0.5 to 4) | | `pickRadius` | number | `1.2` | how far from a unit, beyond its own radius, a click still selects it (0 to 6) | | `spacing` | number | `2.4` | formation spacing in unit radii (1.5 to 6) | | `corpseSeconds` | number | `1.5` | a dead unit lies this long before it is removed (0 to 30) | A unit spec (`spawn`) takes `faction`, `type`, `name`, `health`, `speed`, `radius` (default 0.45, at least 0.2), `sight` (default 9), `attack { damage, range, cooldown, windup?, projectileSpeed? }`, `worker { carry, seconds }`, `passive`, `supply`, `tags`, `slot`, `variant`, `scale` and `color` (a mini-map marker). ## Services, events, variables - `units.command` (service): - units: `player()`, `setPlayer(faction)`, `spawn(spec, at, yaw?)` (answers the id), `units(faction?)`, `unit(id)`, `factionOf(id)`, `hostile(a, b)`. - selection: `selection()`, `select(ids, { add })`, `pick(point)`, `selectAt(point, { add })`, `selectBox(corners, { add })`, `selectArmy()`, `clearSelection()`, `group(n)`, `setGroup(n, ids?)`, `recallGroup(n)`, `marks()`. - orders: `order(ids, order, { queue })` with an order `{ type, point?, target? }` where `type` is `move`, `attack_move`, `attack`, `hold`, `patrol`, `gather`, `return` or `build`; `command(point, target?, { queue, ids, type })` (what a click does); `stop(ids?)`, `hold(ids?)`; `arm(mode)`, `armed()`, `onArmed(mode, handler)` (a placement kit takes the next click), `removeArmed(id)`. - walk grid: `blocked(x, z)`, `clear(from, to)`, `footprintFree(at, size, yaw?)`, `nearestOpen(at)`, `setBounds(min, max)`, `bounds()`, `stats()`. - Events: `units.spawned`, `units.selected` `{ ids, faction }`, `units.ordered` `{ ids, type, point?, target?, queued }`, `units.rally` `{ ids, point, target }`, `units.group` `{ group, ids }`, `units.idle`, `units.deposited` `{ unit, faction, item, count }`, `units.lost`. Listens to `combat.damaged`, `combat.killed` and `minimap.clicked`. - Variables: `units.selected` (how many are selected) and `units.armed` (the order waiting for a click). - Input actions: `units.select`, `units.order`, `units.queue`, `units.group_mod`, one group action per digit 1 to 5, `units.cmd_move`, `units.cmd_attack`, `units.cmd_patrol`, `units.cmd_stop`, `units.cmd_hold`, `units.select_army`, `units.cancel`; the player can remap them. ## Rules - **Walking**: the ground is a grid. Entities tagged `blocks_path` with a `size` block the cells under their footprint. A unit walks straight when the line to its goal is clear, else down a flow field computed once per goal and shared by the group. Units push each other apart, slide along walls and never enter a blocked cell. - **Fighting**: an idle unit with an attack takes on the nearest hostile it sees (fighters, then workers, then buildings) and goes home once it is too far. Attack-move and patrol fight on the way, hold only strikes what is in reach, a struck idle unit strikes back. Strikes go through `combat.attacks` when installed, else straight to `combat.damage`. Factions are hostile unless `faction.relations` says otherwise. - **Gathering**: a worker walks to a `resource.nodes` node, works for its `seconds`, carries the load to the nearest `dropoff` of its faction, banks it with `economy.stockpile` when a kit serves it, and goes back. - **Building**: a worker next to an entity with `construction { progress, seconds }` raises its progress; several build faster. The kit that placed the site finishes it. - **Death**: a unit killed through combat plays its death and is removed after `corpseSeconds`. ## Hooks and modding - Hook `unit.order`: payload `{ ids, unit, count, faction, type, point?, target?, queued }`, asked once per order given to a group (player, script or AI) before any unit takes it. `type` is mutable between `move`, `attack_move` and `patrol` when the order goes to a point, `queued` is mutable, and cancelling gives no order and no `units.ordered`. A hold asks it too; a stop does not. - Hook `unit.rally`: payload `{ ids, building, count, faction, point, target? }`, asked before the selected buildings get a rally point; cancelling leaves their rally points as they were. - A mod may change `pickRadius`, `spacing` and `corpseSeconds`. ```json { "id": "march_fights", "hook": "unit.order", "match": { "type": "move" }, "set": { "type": "attack_move" } } ``` ## Engines The kit runs on the web and in the native Rust host. The web page draws the drag box, a ring and a health bar under each selected thing, and health bars over damaged units, as a `ui.webui` fragment. ## When it does not do what you expect - A click selects nothing: the unit's `faction` is not the one in `faction` (an enemy is shown but not selected), or the click is farther than `pickRadius`. - `spawn` answers `null`: the spec has no `faction` or no `type`, or the position is not an array of numbers. - Units do not walk around a building: it lacks the tag `blocks_path` or a `size`. - An order does nothing: a hook cancelled it, or none of the ids is a living unit of the player. - A worker does not gather: no `resource.nodes` node of its item, or no `dropoff` for its faction. ## Limits Targets are the web and the native Rust host only. The kit does not train units, spend money or play an opponent: those belong to the kits that build on it. ## Versions Kit 1.3.0 (adds `marks()`). Service `units.command` 1.0.0.