Companions

Usage guide of companion.control: follow, hold and move orders for an ally bound to an owner, formation and pacing settings, recovery requests, possession, the companions collection, service, events, hooks and verbs.

Source: module-kits:kits/companion_control/usage/en.mdView Markdown source

Also read offline — CLI: grip-forge docs companion-controlMCP tool: gripforge_docs { "topic": "companion-control" }

Companions that follow and obey (companion.control) #

What it is for #

companion.control runs a travelling ally tied to an owner (the player by default). The ally follows at a distance, can wait where it stands or walk to a point, asks for help when it is lost or too far, and the player can take control of it. It decides how the companion should move; your engine (or, on the Rust host, the NPC motor with ai.navigation) does the walking, collision, camera and animation. It creates and destroys no actors.

Turn it on #

Install the kit in a project and set its settings with the tools below; gripforge kit api companion.control lists every setting, and gripforge docs companion-control prints this guide offline.

gripforge_gamekit_install   { project_id: "gkp_…", id: "companion.control" }
gripforge_gamekit_configure { project_id: "gkp_…", id: "companion.control",
                              config: { "followDistance": 2.5, "followOffset": [1.2, 0, -1], "walkSpeed": 2.8 } }

First example #

<!-- test: kits/companion_control/tests/companion_control.rs::hold_stays_and_move_walks_to_a_target_then_holds --> An explicit instance reads the observed positions of owner and companion on each step and answers a steering intent. A hold order stops it; a move order walks it to a target, and it holds again on arrival.

companion.control.request("default", { op: "command", mode: "hold" })
companion.control.request("default", { op: "step", dt: 0.1, ownerPosition: [0,0,0], position: [10,0,0], … })
  // desiredVelocity [0,0,0], state.mode "hold"
companion.control.request("default", { op: "command", mode: "move", target: [0,0,8] })
companion.control.request("default", { op: "step", dt: 0.1, position: [0,0,7.8], … })
  // arrived: true, state.mode "hold", state.target null

Settings and defaults #

SettingDefaultMeaning
ownerplayerstable id of the owner, different from the companion
companioncompanionstable id of the companion
maxSpeed6{{s_maxSpeed}}
acceleration15steering acceleration and braking when approaching (0.1 to 500)
followDistance2{{s_followDistance}}
followOffset[0, 0, 0]{{s_followOffset}}
followFrameowner{{s_followFrame}}
stopRadius0.2{{s_stopRadius}}
recoveryDistance30{{s_recoveryDistance}}
followSlack0{{s_followSlack}}
followDelay0{{s_followDelay}}
walkSpeed0{{s_walkSpeed}}
catchUpDistance8{{s_catchUpDistance}}

Services #

  • companion.control (service): create(id, config), request(id, command), remove(id), ids(); the instance default uses the installed settings. Commands of request: command (mode of follow, hold or move, with a target for move), step, possess, release, plus status, config, preview, save, load.
  • With the Rust host and a companions document, the kit binds an existing NPC to its owner and moves it by itself: order(id, mode, target?) gives an order and bound() lists the bound companions. A companions document has id, npc (an existing npcs document), ownerTag (default player; exactly one living owner must carry it), mode (follow or hold), damageable (false cancels damage to an essential follower) and an optional config with the movement settings above.

Events #

  • companion.control.created and companion.control.removed (an instance appeared or went), companion.control.changed (a decided command applied, with its op and result; never a read or a step), companion.control.refused (a hook cancelled a command, with by and reason).
  • companion.bound (a live NPC took the exclusive motor driver), companion.ordered (a follow, hold or move order was accepted, with companion, mode, target) and companion.recovery (separation exceeded the safe distance; nothing was teleported). The kit listens to no event.

Hooks and modding #

  • companion.control.create: an instance is about to be created; a tap may change its config or cancel, and create then fails with the reason.
  • companion.control.request: a decided command (every op except the reads and step) is about to apply; a tap may change the command or cancel, and request then answers { ok: false, cancelled: true, by, reason } with the instance unchanged.
  • companion.order: a bound companion is about to accept an order; a tap may change its mode or target, or cancel (Rust host only).

Using it from content #

Every setting is moddable. A companion_control_presets document { id, extends?, config } names a ready-made set of settings, and create(id, { preset: "<id>" }) takes it (own keys win). A hook document of a game or a mod looks like this:

{ "id": "no_move_orders", "hook": "companion.control.request", "match": { "op": "command" }, "cancel": true, "reason": "stay_close" }

Script verbs: the action companion_control_request sends a command to an instance (default default) through the request hook, and the condition companion_control_exists tells whether an instance exists.

The action companion_order (with companion, mode and, for move, a world-space target) orders a live managed companion. The inputs companion_hold and companion_follow are unbound by default: bind them to keys or a menu.

Driving it from your engine #

Each frame send step with dt, ownerPosition, position, velocity, ownerAlive, companionAlive and pathAvailable (optionally blocked, waypoint, attentionPosition). The answer holds desiredVelocity, arrived, applySteering, navigationTarget, needsPath, needsRecovery and the state (mode, target, possessed). With no path the companion asks for one and stays put; beyond recoveryDistance it asks for recovery and never teleports; an attentionPosition pauses motion and gives a look direction (a dialogue partner).

When it does not do what you expect #

SymptomCause and fix
A command is refused with "follow, hold or move"mode must be one of the three, and move needs a target of three numbers.
The companion does not moveIt is in hold, has arrived, is waiting out followDelay, or has no path (needsPath). Check state.mode and provide pathAvailable or a waypoint.
possess is refusedBoth owner and companion must have been observed alive by a step first.

Limits #

  • Pathfinding, collision, floor, animation, camera and input ownership stay with your engine; the kit gives intents.
  • The managed motor (binding an NPC from companions) runs on the Rust host only; the web wrapper and the C library offer the explicit driver.
  • Dialogue content, combat behaviour and models belong to your game; installing the kit does not make a companion.

Versions #

Kit companion.control 1.6.0. 1.6.0 added the dialogue attention pause, 1.5.0 the natural following settings (followSlack, followDelay, walkSpeed, catchUpDistance), 1.1.0 the hooks, presets, events and verbs. It needs npc.locomotion ^1.3 and ai.navigation ^1 for the managed motor.