# Grabs and throws (combat.grabs) ## What it is for `combat.grabs` manages the relationship between a grabber and a target: acquire, hold, escape, throw, release. A participant belongs to at most one pair, as grabber or as target. One instance handles every grab of an arena, so do not create one per fighter. The game checks range, contacts, life, immunity and permissions, then sends `grab`. The kit owns the pair and its clocks; the game owns the bodies, poses, collisions, health and what happens after a release. It is a driver you step yourself: there is no automatic tick. It works with `combat.moveset` ([guide](guide:combat-moveset)), `combat.defense` ([guide](guide:combat-defense)) and `combat.hit_reactions` ([guide](guide:combat-hit-reactions)); the game composes them. ## Turn it on ```text gripforge_gamekit_install { project_id: "gkp_…", id: "combat.grabs" } gripforge_gamekit_configure { project_id: "gkp_…", id: "combat.grabs", config: { "escapeFrames": 6, "minHoldFrames": 6, "maxHoldFrames": 60, "throwDamage": 20 } } ``` Durations are whole fixed simulation frames (normally 60 Hz); call `step` once per frame. ## First example The game confirms the three permissions, and the kit answers a `grabbed` event with a strictly increasing `sequence` and a pair in phase `holding`. The same actors cannot be grabbed twice. ```json { "service": "combat.grabs", "method": "request", "args": ["default", { "op": "grab", "attacker": "orc", "target": "elf", "inRange": true, "attackerReady": true, "targetGrabbable": true }] } ``` Later, `{ "op": "throw", "sequence": 1, "actor": "orc", "velocity": [0, 5, 8] }` (allowed from `minHoldFrames`) starts the throw (`throw_started`); `throwFrames` steps later one `thrown` event carries the velocity and damage and releases both actors. (`kits/combat_grabs/tests/combat_grabs.rs::only_the_holder_can_throw_and_only_after_the_minimum_hold`) ## Settings and defaults Fixed at creation; unknown keys are refused. Keep `escapeFrames <= minHoldFrames < maxHoldFrames`. | Setting | Default | Meaning | |---|---|---| | `maxPairs` | `32` | Simultaneous pairs (1 to 64). | | `escapeFrames` | `6` | The target may escape during frames `[0, escapeFrames)` of the hold (0 to 600). | | `minHoldFrames` | `6` | First hold frame at which a throw is allowed (0 to 600). | | `maxHoldFrames` | `60` | The hold times out and releases both (1 to 600). | | `throwFrames` | `8` | Frames between the throw request and the release (1 to 600). | | `throwDamage` | `20` | Damage offered at release (0 to 1,000,000). | | `holdImpacts` | none | Up to 32 strictly ordered `{ frame, damage }` impacts during the hold. | ## Services Service `combat.grabs` (up to 128 named instances): `create(id, config)`, `remove(id)`, `ids()` and `request(id, command)` with the commands: - `grab { attacker, target, inRange, attackerReady, targetGrabbable }`: the three booleans must be true. - `throw { sequence, actor, velocity }`: `actor` must be the grabber; `velocity` is a world vector in metres per second, Y up. - `escape { sequence, actor }`: target only, inside the escape window; releases both. - `release { sequence, actor }`: the grabber lets go, no damage. `interrupt { actor }`: ends the pair of that actor in either role. - `step { frozen?, invalidActors? }`: advances every pair once; listed invalid actors release their pair even when frozen. - `reset`, `status`, `config`, `save`, `load { save }`, `preview { command }`. Script verbs: a request verb and an exists condition named after the kit id with dots turned into underscores (`_request`, `_exists`). ## Events - Core events in the answer of a request: `grabbed`, `throw_started`, `thrown`, `released` (reason `escaped`, `voluntary`, `interrupted`, `invalid_actor` or `timeout`) and `hold_impact` (`impactIndex`, `damage`). Each names both actors and the pair `sequence`. - Bus events, named `.`: `.created`, `.removed`, `.changed` (a decided command applied) and `.refused` (a tap cancelled it). On `grabbed`, interrupt the moveset and defense of both actors. On `thrown`, give the target a `launch` reaction in `combat.hit_reactions` with a new impact sequence and apply the throw damage only if the reaction is accepted. Apply damage once per pair sequence (and impact index). ## Hooks and modding | Hook | Effect | |---|---| | `combat.grabs.create` | Before an instance is created; may change its `config` or cancel. | | `combat.grabs.request` | Before a decided command; may change the `command` or cancel. | `create(id, { preset })` reads the presets collection of the kit (its id with dots turned into underscores, then `_presets`). ## Engines The same Rust core runs on the web, the native player and as a C library and WebAssembly module for the engine wrappers; constraints between bodies are the wrapper's job. ## When it does not do what you expect - `grab` refused with a message naming a field: that permission was false; or `already held or self` when an actor is already in a pair. - `throw unavailable`: too early (before `minHoldFrames`), not the grabber, or already throwing. - Escape fails: the escape window ended or the throw started. ## Limits No physics constraint, no animation, no health. A load does not prove the saved actors still exist: pass missing ones in `invalidActors`. ## Versions Kit 1.2.0 (modding contract), 1.1.0 (`holdImpacts`). Save format `gripforge.motion/1`.