Turn-based combat
Usage guide of combat.turns: a turn-based encounter engine with energy, enemy intents and move patterns, statuses with stacks, a data language of effects, driven fights, settings and defaults, collections, events, hooks and verbs.
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.
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 #
<!-- test: kits/game_engine/host/tests/combat_turns.rs::a_fight_runs_turn_by_turn --> 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.
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 })answersfalsefor 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()andpick(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 withendTurn(), andend(result)closes a fight on a result decided outside. - The effect language: an effect is
{ type, target?, amount?, times?, status?, stacks? }withtypeamongdamage,block,apply_status,remove_status,heal,lose_hp,gain_energy,gain_max_hpor a type another kit registered;targetistarget,self,all_enemies,random_enemy,allorhero;amountis 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 withid,name,intent,effects),pattern(modecycleorrandom,opening,moves,weights,maxRepeat),start(statuses at the start),size,variant.turn_statuses:id,name,polarity(buffordebuff),decay(turn_start,turn_end,never),dealtAdd,blockAdd,dealtMult,takenMult,blockMult,reflect,keepBlock,tick. Ready-made statuses includevulnerable,weak,frail,strength,poison,thornsandgrowth. - Variables for the HUD:
turns.active,turns.turn,turns.energy,turns.hp,turns.max_hp. The inputturn_end(keyKeyT) ends the player turn.
Events #
turns.started: a fight began, withenemies,allies,driven,kind,label.turns.turn: a side starts its turn (sideplayerorenemy,turn).turns.acted: a fighter played a move throughactin 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 (resultwonorlost,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,startanswersfalseand 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);amountis 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);amountis 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);stacksis mutable, and cancelling or 0 changes nothing (an immunity).
{ "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.
{ "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.deckadds 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.