# Turn-based combat (combat.turns) ## What it is for `combat.turns` runs turn-based encounters: a hero who keeps their health between fights against enemies described as data. The sides take turns, the player spends energy each turn, enemies show what they will do next (their intent) and follow move patterns, and statuses carry stacks and wear off. Effects are a small data language that other kits extend (cards, relics, tactics). Every random draw follows the game seed, so a loaded game continues exactly. Cards, tactics and dice games all fit. ## Turn it on Install the kit in a project and set its settings with the tools below; `gripforge kit api combat.turns` lists every setting, and `gripforge docs combat-turns` prints this guide offline. ```text gripforge_gamekit_install { project_id: "gkp_…", id: "combat.turns" } gripforge_gamekit_configure { project_id: "gkp_…", id: "combat.turns", config: { "heroHealth": 80, "energyPerTurn": 3 } } ``` ## First example A hero (30 health) starts a fight against a `brute` with thorns. A hit is answered by thorns, block soaks the enemy's hit and is lost when the player's turn begins, the fight can be saved and loaded in the middle, and a killing blow ends it as won. ```text combat.turns.start(["brute"], { kind: "elite" }) // true; brute: 20 hp, intent hit for 5, starts with thorns 2 combat.turns.apply([{ type: "apply_status", status: "strength", stacks: 2, target: "self" }, { type: "damage", amount: 4 }], { source: "hero", target: "e1" }) // brute 20 -> 14, thorns hit the hero 30 -> 28 combat.turns.apply([{ type: "block", amount: 3 }], { source: "hero" }) combat.turns.endTurn() // the brute hits: hero 28 -> 26, block back to 0 combat.turns.energy() // 3, turn 2 ``` ## Settings and defaults | Setting | Default | Meaning | | --- | --- | --- | | `heroHealth` | 70 | {{s_heroHealth}} | | `heroName` | `Hero` | {{s_heroName}} | | `energyPerTurn` | 3 | {{s_energyPerTurn}} | | `stage` | `true` | show the fighters in the world: the hero in front of the camera, the enemies facing them | ## Services - `combat.turns` (service): `start(enemies, { allies, driven, hero })` answers `false` for an unknown enemy, `active()`, `side()`, `turn()`, `hero()`, `combatants()`, `enemies()`, `allies()`, `energy()`, `spendEnergy`, `gainEnergy`, `endTurn()`, `heal`, `loseHp`, `addMaxHp`, `apply(effects, ctx)`, `addStatus`, `damageOf`, `blockOf`, `describe(effects)`, `movesOf(id)`, `log()`, `stats()`, `end(result)`, `registerEffect(type, fn)`, `on(hook, fn)`, `fire(hook)`, `aim(request)`, `aiming()` and `pick(target)`. - A kit that places the fighters itself (a grid, a board) starts a driven fight with fighters given as data and allies beside the hero: no intent is rolled, the caller plays every move with `act(actor, move, target)` and ends the enemy side with `endTurn()`, and `end(result)` closes a fight on a result decided outside. - The effect language: an effect is `{ type, target?, amount?, times?, status?, stacks? }` with `type` among `damage`, `block`, `apply_status`, `remove_status`, `heal`, `lose_hp`, `gain_energy`, `gain_max_hp` or a type another kit registered; `target` is `target`, `self`, `all_enemies`, `random_enemy`, `all` or `hero`; `amount` is a number, or `"x"` for the energy an X-cost action spent. - Two collections hold your content. `turn_enemies`: `id`, `name`, `kind` (`normal`, `elite`, `boss`), `hp` (a number or a `[min, max]` range rolled on appearance), `initiative`, `moves` (each with `id`, `name`, `intent`, `effects`), `pattern` (`mode` `cycle` or `random`, `opening`, `moves`, `weights`, `maxRepeat`), `start` (statuses at the start), `size`, `variant`. `turn_statuses`: `id`, `name`, `polarity` (`buff` or `debuff`), `decay` (`turn_start`, `turn_end`, `never`), `dealtAdd`, `blockAdd`, `dealtMult`, `takenMult`, `blockMult`, `reflect`, `keepBlock`, `tick`. Ready-made statuses include `vulnerable`, `weak`, `frail`, `strength`, `poison`, `thorns` and `growth`. - Variables for the HUD: `turns.active`, `turns.turn`, `turns.energy`, `turns.hp`, `turns.max_hp`. The input `turn_end` (key `KeyT`) ends the player turn. ## Events - `turns.started`: a fight began, with `enemies`, `allies`, `driven`, `kind`, `label`. - `turns.turn`: a side starts its turn (`side` `player` or `enemy`, `turn`). - `turns.acted`: a fighter played a move through `act` in a driven fight. - `turns.damaged`: a hit landed (`source`, `target`, `amount`, `blocked`, `hp`). - `turns.killed`: a fighter fell (`id`, `side`, `def`). - `turns.ended`: the fight ended (`result` `won` or `lost`, `turns`, `kind`). - `turns.hero_died`: the hero fell outside a fight (an event, a cost). ## Hooks and modding - `turns.start`: a fight against known enemies is about to start; cancelled, `start` answers `false` and nothing is created. - `turns.move`: a fighter is about to play a move (`actor`, `def`, `move`); cancelled, it skips its move this turn (a stun) and the next intent is rolled as usual. - `turns.effect`: one effect is about to run (`type`, `source`, `target`, `origin`, `amount`, `fight`); `amount` is mutable, and cancelling skips that effect only. - `turns.hit`: one hit is about to land, after strength, weak and vulnerable and before block (`source`, `target`, `amount`, `attack`); `amount` is mutable, and cancelling means no hit. - `turns.status.apply`: stacks of a status are about to be added to (or, negative, taken from) a living fighter (`target`, `status`, `stacks`); `stacks` is mutable, and cancelling or 0 changes nothing (an immunity). ```json { "id": "stun_immunity", "hook": "turns.status.apply", "match": { "status": "weak" }, "cancel": true } ``` ## Using it from content Script verbs: the action `start_turn_fight` (with `enemies`, a list of `turn_enemies` ids, and `kind`) starts a fight, and the condition `turn_fight_active` tells whether one is running. ```json { "start_turn_fight": { "enemies": ["slime", "slime"] } } ``` ## When it does not do what you expect | Symptom | Cause and fix | | --- | --- | | `start` answers `false` | An enemy id is not in `turn_enemies`, a fight already runs, or a tap of `turns.start` cancelled it. | | A status does nothing | Its id is not a ready-made one nor in `turn_statuses`; an `apply_status` effect also needs a `status` and `stacks`. | | Enemies do not play in a driven fight | That is by design: the caller plays each move with `act` and then calls `endTurn()`. | ## Limits - The kit has no cards: `cards.deck` adds them and registers their effect types on this one. - The encounter screen (enemies and their intents, the hero, energy, an End turn button) is drawn by the web display module; it steps aside for the screen of a kit that places the fighters itself. - It runs on the web player and the native Rust host; there is no Godot, Unity or Unreal adapter. ## Versions Kit `combat.turns` 1.3.0 (fighters as data and driven fights since 1.3.0, 2026-10-22). It needs `game.engine` ^1.