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.
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 #
| Setting | Default | Meaning |
|---|---|---|
owner | player | stable id of the owner, different from the companion |
companion | companion | stable id of the companion |
maxSpeed | 6 | {{s_maxSpeed}} |
acceleration | 15 | steering acceleration and braking when approaching (0.1 to 500) |
followDistance | 2 | {{s_followDistance}} |
followOffset | [0, 0, 0] | {{s_followOffset}} |
followFrame | owner | {{s_followFrame}} |
stopRadius | 0.2 | {{s_stopRadius}} |
recoveryDistance | 30 | {{s_recoveryDistance}} |
followSlack | 0 | {{s_followSlack}} |
followDelay | 0 | {{s_followDelay}} |
walkSpeed | 0 | {{s_walkSpeed}} |
catchUpDistance | 8 | {{s_catchUpDistance}} |
Services #
companion.control(service):create(id, config),request(id, command),remove(id),ids(); the instancedefaultuses the installed settings. Commands ofrequest:command(modeoffollow,holdormove, with atargetformove),step,possess,release, plusstatus,config,preview,save,load.- With the Rust host and a
companionsdocument, the kit binds an existing NPC to its owner and moves it by itself:order(id, mode, target?)gives an order andbound()lists the bound companions. Acompanionsdocument hasid,npc(an existingnpcsdocument),ownerTag(defaultplayer; exactly one living owner must carry it),mode(followorhold),damageable(falsecancels damage to an essential follower) and an optionalconfigwith 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
opandresult; never a read or astep), companion.control.refused (a hook cancelled a command, withbyandreason). - 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 itsconfigor cancel, andcreatethen fails with the reason.companion.control.request: a decided command (everyopexcept the reads andstep) is about to apply; a tap may change thecommandor cancel, andrequestthen 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 itsmodeortarget, 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 #
| Symptom | Cause 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 move | It 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 refused | Both 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.