From eec8e6e698e97daf313e6349d3fd4f0cf67fa648 Mon Sep 17 00:00:00 2001 From: wtclaude Date: Tue, 6 Oct 2026 21:35:04 -0500 Subject: [PATCH] docs(runicnpc): stage 9b built; INSTALL and COMMANDS; RunicNPC required (D315, D316) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - runicnpc/PLAN.md: 9b built and measured; D315 (the performance check records today's cost, the warning says it, idle overhead is runicnpc-rust#13) and D316 (the picker keeps Rust's own; a step on a server without RunicNPC is refused on save). - runicnpc/INSTALL.md (new): requirements, the installer and egg, by hand without Runic Gateway, Kits and ZoneManager reported not installed, its files, the cost warning with stage 9's numbers, caps, and after a Rust update. - runicnpc/COMMANDS.md (new): every /rnpc verb and console command with its permission, options and defaults, and how to read rnpc.status. - rust-link/INSTALL.md: RunicNPC is required; the installer refuses a bundle without it and doctor fails. (The plan named modules/rust/OPERATING.md, which is a verbatim uMod mirror; this is the operator guide that lists Kits.) - rust-link/PROTOCOL.md: §19.16, every NPC placement needs RunicNPC; no message changes shape. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY --- runicnpc/COMMANDS.md | 109 ++++++++++++++++++++++++++++++++++ runicnpc/INSTALL.md | 132 ++++++++++++++++++++++++++++++++++++++++++ runicnpc/PLAN.md | 38 +++++++++++- rust-link/INSTALL.md | 17 +++--- rust-link/PROTOCOL.md | 21 ++++++- 5 files changed, 306 insertions(+), 11 deletions(-) create mode 100644 runicnpc/COMMANDS.md create mode 100644 runicnpc/INSTALL.md diff --git a/runicnpc/COMMANDS.md b/runicnpc/COMMANDS.md new file mode 100644 index 0000000..8af7c8d --- /dev/null +++ b/runicnpc/COMMANDS.md @@ -0,0 +1,109 @@ +# RunicNPC — command reference + +Every command RunicNPC answers, with the permission it needs. This page is the reference; how the commands came +to be is in [PLAN.md](PLAN.md) §5 and stage 3, and installing RunicNPC is in [INSTALL.md](INSTALL.md). + +## How commands are reached + +- **In chat:** `/rnpc ...`. `/rnpc` alone lists the verbs you are allowed to use. +- **In a console:** `rnpc. ...`, in a player's F1 console, the server console or RCON. The same code answers + both, with the same permissions. +- **Console-only verbs** (`profile`, `faction`) answer only in a console. In chat they tell you to open F1. +- **The server console has every permission.** It has no position, so `place`, `here` and `path point` take + `at=x,y,z` (and `yaw=`) there. In game those two options are refused. + +## Permissions + +| Permission | Grants | +|---|---| +| `runicnpc.place` | Placing, removing, renaming and inspecting NPCs, recording routes, and `follow`. | +| `runicnpc.admin` | Everything, `runicnpc.place` included: teleporting, respawning, clearing an owner, and the console-only verbs. | + +On a Runic Gateway server, both permissions show in Admin → Rust → Permissions like any plugin's, and the site +grants them there. + +## Placements + +A **placement** is a spot where a profile's NPCs stand and come back after they die. It is saved on the server +(`data/RunicNPC/placements.json`) and survives restarts. Placements made in game are named after their profile and +a number (`bandit-1`) and can be renamed. + +| Command | Does | Permission | +|---|---|---| +| `/rnpc place [options]` | Places the profile's NPCs where you are looking, and answers with the placement's name and the cost warning. | `runicnpc.place` | +| `/rnpc here [options]` | The same, where you stand. | `runicnpc.place` | +| `/rnpc remove [placement]` | Removes a placement and its NPCs: the one you name, or the one the NPC you are looking at belongs to. | `runicnpc.place` | +| `/rnpc rename ` | Renames a placement. Its live NPCs stay where they are. | `runicnpc.place` | +| `/rnpc near [radius]` | Lists the placements and live NPCs around you, with distances and any note (waiting, off the navmesh). | `runicnpc.place` | +| `/rnpc info` | The NPC you are looking at: name, profile, placement or owner, health, state and target. | `runicnpc.place` | +| `/rnpc profiles` | The profiles this server has, and any it refuses with the reason. | `runicnpc.place` | +| `/rnpc follow ` | The placement's live NPCs escort a player. They fight whoever attacks that player, and walk back to their spot if the player dies or leaves. `off` ends it. | `runicnpc.place` | +| `/rnpc tp ` | Teleports you to a placement. | `runicnpc.admin` | +| `/rnpc respawn ` | Respawns a placement's NPCs now, or every placement's. | `runicnpc.admin` | +| `/rnpc clear ` | Removes every NPC an owner has, for example a stuck event run (`run:42`) or another plugin (`plugin:Name`). | `runicnpc.admin` | + +**Options for `place` and `here`,** in any order: + +| Option | Means | Default | +|---|---|---| +| `count=` | How many NPCs stand there. | 1 | +| `respawn=` | How long after a death before the NPC comes back. | 300 | +| `mode=each\|group` | `each`: every NPC comes back on its own timer. `group`: none comes back until all are dead, then all at once. | `each` | +| `move=wander\|monument\|route:` | How the NPCs move: wander around the spot, roam the monument they stand in, or walk a recorded route. | the profile's | +| `radius=` | How far a wanderer strays. | the profile's | +| `tether=` | A ZoneManager zone the NPCs never leave. Needs ZoneManager. | none | + +**Every placement is checked against Rust's navmesh.** A roamer must stand on it. Only a sentry may stand off it. +On a player-built floor the placement is made with a warning: if the floor is destroyed, the NPCs fall back to the +nearest navmesh, and return to their spot once it is rebuilt. + +## Routes + +A route is a line of points an NPC walks. It is recorded in game where you walk, one point at a time. + +| Command | Does | Permission | +|---|---|---| +| `/rnpc path record ` | Starts recording a route. | `runicnpc.place` | +| `/rnpc path point` | Adds the spot you stand on. A point the previous one has no walkable path to is refused. | `runicnpc.place` | +| `/rnpc path undo` | Drops the last point. | `runicnpc.place` | +| `/rnpc path save [loop\|back]` | Saves the route: `loop` walks back to the first point and round again, `back` walks it back and forth. | `runicnpc.place` | +| `/rnpc path cancel` | Abandons the recording. | `runicnpc.place` | +| `/rnpc path list` | The saved routes. | `runicnpc.place` | +| `/rnpc path delete ` | Deletes a route. A placement that walks it waits until a route of that name exists again. | `runicnpc.place` | + +A recording is kept in memory only: a reload of RunicNPC discards it. + +## Console only + +| Command | Does | Permission | +|---|---|---| +| `rnpc.profile list` | The same as `/rnpc profiles`. | `runicnpc.admin` | +| `rnpc.profile show ` | A profile in full, as JSON, and why it is refused if it is. | `runicnpc.admin` | +| `rnpc.profile create [from=]` | A new profile, empty or copied from another. | `runicnpc.admin` | +| `rnpc.profile set ` | Sets one field of a profile. | `runicnpc.admin` | +| `rnpc.profile delete ` | Deletes a profile. Placements that use it wait until a profile of that name exists again. | `runicnpc.admin` | +| `rnpc.faction list` | The faction table: which factions fight, ignore or help each other. | `runicnpc.admin` | +| `rnpc.faction set hostile\|neutral\|allied` | Sets one pair, both ways. | `runicnpc.admin` | +| `rnpc.faction clear ` | Removes a pair. | `runicnpc.admin` | +| `rnpc.status` | The version and API, the hooks that have fired, the navmesh, the swap's field list, NPC counts by owner, profiles, placements, routes, what fighting costs a think, the caps, and the cost warning. | server console, RCON, or an admin's F1 | +| `rnpc.reload` | Re-reads profiles and routes after a hand edit of their files. | server console, RCON, or an admin's F1 | +| `rnpc.help` | The same list as `/rnpc` alone. | anyone; it lists only what they may use | + +**On a Runic Gateway server the website manages the profiles and the faction table** (Admin → Rust → NPC +profiles). There, `rnpc.profile create|set|delete` and `rnpc.faction set|clear` are refused with "managed by its +website", and `show` and `list` still answer. On a server without a website they are how profiles are made. + +## Reading `rnpc.status` + +``` +RunicNPC 1.0.0 api=6 hooks=12 fired=12 silent=0 +framework=oxide navmesh=ready swap fields: npc=55/55 brain=32/32 missing=- added=- +npcs=14 awake=9 goingHome=0 asleep=5 owners: placement:bandit-1=3 run:42=11 +``` + +- **`silent`** names a hook that has never fired. After a Rust or framework update, a hook that stays silent is the + first sign it was renamed: neither framework reports a hook that matches nothing. +- **`navmesh=building`** after a Rust update means Rust is still building the map's navmesh (about ten minutes on a + large map). Placements wait for it. +- **`swap fields`**: `missing` or `added` other than `-` means Rust's scientist has changed since this RunicNPC was + released. Update RunicNPC. diff --git a/runicnpc/INSTALL.md b/runicnpc/INSTALL.md new file mode 100644 index 0000000..4586b18 --- /dev/null +++ b/runicnpc/INSTALL.md @@ -0,0 +1,132 @@ +# Installing RunicNPC + +RunicNPC is Runic Gateway's own NPC plugin for Rust, on Oxide and Carbon. On a Runic Gateway server it is +**required**: every NPC an event places is RunicNPC's, Rust's own scientists included (D310). It also runs on any +Rust server on its own, driven by its `/rnpc` commands and by other plugins through its API. + +Every command is in [COMMANDS.md](COMMANDS.md), the API is in [API.md](API.md), and the design of record is +[PLAN.md](PLAN.md). + +## What it needs + +| | | +|---|---| +| **Oxide 2.0.7726 or Carbon 2.0.259**, or newer | The builds it was tested on. Its release manifest states them, and the installer checks them. | +| **[Kits](https://umod.org/plugins/kits)** | **Required.** A profile equips its NPCs with Kits kits, and RunicNPC declares `// Requires: Kits`, so neither framework loads it without Kits. | +| **[ZoneManager](https://umod.org/plugins/zone-manager)** | Optional. Only a tether (`tether=` on a placement, or an event's "keep inside the zone") uses it. | + +**Kits and ZoneManager are reported, never installed.** The installer and `doctor` say when either is missing, and +you install them from uMod. Runic Gateway does not fetch third-party plugins. + +**Do not reload Kits while an event runs.** RunicNPC requires Kits, so both frameworks reload RunicNPC with it, and +every NPC it had spawned is gone. Placements come back on their own; an event's NPCs do not (D283). + +## On a Runic Gateway server + +There is nothing to do by hand. RunicNPC is part of the Rust **bundle**, the exact set of releases CI has checked +together, beside the bridge plugin and the sidecar: + +- **The installer** (`install --game rust`, `update --game rust`) places `RunicNPC.cs` beside the bridge, checked + against the bundle's checksum. It refuses a bundle without RunicNPC. `doctor --game rust` fails when RunicNPC is + missing or was never installed, and warns when the file was edited by hand. `uninstall` removes `RunicNPC.cs` + and keeps `data/RunicNPC/`. The installer's own guide is [../rust-link/INSTALL.md](../rust-link/INSTALL.md). +- **The Pterodactyl egg** places it the same way on every install and reinstall. + +Once the server has started, **Admin → Rust → Servers** shows the server as complete. A server without RunicNPC, or +with a RunicNPC older than the bridge needs, reads "Incomplete" with the reason, and every Place NPCs step on it is +refused with "needs RunicNPC". **Admin → Rust → NPC profiles** shows each server's RunicNPC version and API. + +On such a server the website owns the NPC profiles and the faction table. It pushes them to RunicNPC, and the +console commands that would edit them are refused. Placements stay on the server: they can be made in game with +`/rnpc place` or from the site's map, and both see the same list. + +## Without Runic Gateway + +1. Download `runicnpc-.tar.gz` and `SHA256SUMS` from the + [releases](https://gitea.whitlocktech.com/RunicGateway/runicnpc-rust/releases), and check the tarball: + `sha256sum -c SHA256SUMS`. +2. Install Kits from uMod if the server does not have it. +3. Copy `runicnpc/RunicNPC.cs` from the tarball into `oxide/plugins/` or `carbon/plugins/`. The framework compiles + and loads it at once. +4. In the server console: `rnpc.status`. It should say `navmesh=ready` (a fresh map builds it for several minutes + after the first boot) and `swap fields: ... missing=- added=-`. + +On its first load RunicNPC writes four example profiles (a raider, a camp guard, a sniper and the Juggernaut boss) +and their kits into Kits' data file, so a fresh server has something to place at once. Grant `runicnpc.place` or +`runicnpc.admin` to whoever places NPCs, then `/rnpc place raider` where you look. + +**Do not create `data/RunicNPC/` yourself.** RunicNPC makes it on first load. A directory made from outside the game, +for example in a panel's file manager, is not writable by the game. + +## Its files + +| File | What it holds | Who writes it | +|---|---|---| +| `plugins/RunicNPC.cs` | The plugin. | The installer, the egg or you. | +| `config/RunicNPC.json` | Caps (below) and the spawn budget. | You. | +| `data/RunicNPC/profiles.json` | The profiles and the faction table. | The website on a Runic Gateway server (`"managed": true`); otherwise `rnpc.profile` and `rnpc.faction`. | +| `data/RunicNPC/placements.json` | The placements. | `/rnpc`, the API, and the website's map. | +| `data/RunicNPC/routes.json` | The recorded routes. | `/rnpc path`. | +| `data/RunicNPC/state.json` | Its own bookkeeping. | RunicNPC. | + +After editing `profiles.json` or `routes.json` by hand, run `rnpc.reload`. + +## The cost warning, and caps + +**RunicNPC has no caps by default** (D227). Instead, every placement and every event step answers with what the +server's RunicNPC NPCs cost, and `rnpc.status` ends with the same lines: + +``` +RunicNPC: 100 NPC(s) on this server. Measured on a test server, they add about 3 ms to every server frame while +idle with a player near enough to wake them, and about 0.5 ms once they sleep with nobody near. If all fight at +once they add about 9 ms, and Rust's shared 2 ms AI budget then lets each react only every 3 s. ... +``` + +What it means: + +- **Idle NPCs are cheap, and sleeping ones cheaper.** NPCs that stand, wander or patrol with nobody to fight add a + little to a frame. One with no player within its sleep distance (160 m by default) walks home and sleeps, and + then costs about a sixth of what it did awake. +- **Fighting NPCs hit Rust's AI budget before the frame rate.** All of Rust's human NPCs, its own scientists + included, share `aithinkmanager.framebudgetms` (2 ms a frame). With many fighting at once, each one reacts less + often. The server stays smooth, and the NPCs get slower to react. +- **The figures are measured, not guessed.** Stage 9 measured the released NPC on both frameworks, on a 6000 + map, as what 100 add to the median server frame of the same session's empty server. Beside it is Rust's own + scientist, unchanged but for RunicNPC's swap, measured the same day: fighting costs what Rust's own AI costs. + + | 100 NPCs, over an empty server | RunicNPC on Oxide | RunicNPC on Carbon | Rust's scientist (Oxide / Carbon) | + |---|---|---|---| + | Idle, a player near enough to keep them awake | +2.7 to +2.9 ms | +1.5 ms | +1.0 / +1.0 ms | + | Idle, asleep with nobody within 160 m | — | +0.5 ms | — | + | Fighting 20 players | +7.2 ms | +9.0 ms | +7.5 / +8.0 ms | + + The warning uses the worst of these, rounded up: 3 ms awake, 0.5 ms asleep and 9 ms fighting per 100. The awake + figure on Oxide is higher than it should be and is being worked on + ([runicnpc-rust#13](https://gitea.whitlocktech.com/RunicGateway/runicnpc-rust/issues/13)). Your server's own + figures depend on its CPU, its map and its players. + +To cap them anyway, set any of these in `config/RunicNPC.json` and reload RunicNPC (0 means none): + +| Key | Caps | +|---|---| +| `caps.total` | RunicNPC's NPCs on the server. | +| `caps.perOwner` | NPCs of one owner: one placement, one event run, or one other plugin. | +| `caps.perProfile` | NPCs of one profile. | +| `caps.spawnsPerSecond` | How fast NPCs are spawned. | +| `spawnBudgetMs` | Milliseconds of a frame the spawn queue may use (8 by default). One NPC takes about 5 ms to spawn, so a large placement is spread over frames instead of stalling one. | + +A step or placement over a cap is refused with the cap named. + +## After a Rust update + +Rust's monthly update can change the scientist RunicNPC is built on. After the update: + +1. **Wait for the navmesh.** `rnpc.status` says `navmesh=building` while Rust rebuilds it (about ten minutes on a + large map). Placements wait for it and spawn when it is ready. +2. **Check the swap.** `rnpc.status`'s `swap fields` line should end `missing=- added=-`. Anything else means Rust's + scientist has changed since this RunicNPC was released: update RunicNPC (on a Runic Gateway server, + `update --game rust` or an egg reinstall takes the current bundle). +3. **Check the hooks.** A hook listed as `silent` long after the server has been played on may have been renamed. + +Runic Gateway checks every new Rust staging build before it reaches the public branch (PLAN.md stage 9, the +staging drill), so a RunicNPC release that needs fixing is usually out before the update is. diff --git a/runicnpc/PLAN.md b/runicnpc/PLAN.md index b659c57..6b31978 100644 --- a/runicnpc/PLAN.md +++ b/runicnpc/PLAN.md @@ -12,7 +12,8 @@ and **its build's own questions answered 2026-10-01** (D267–D272). **Stage 5 b site 2026-10-05** on both rigs (§9); its API is [API.md](API.md) version 4. **Stage 6's design answered 2026-10-05** (D273–D282, §0), **its spike measured 2026-10-05** on both rigs, and **its two questions answered the same day** (D283, D284, §9). **Its build's own questions were answered the same day too** (D285–D289), and **stage 6 was built and walked on the site 2026-10-05** on both rigs (§9); its API is [API.md](API.md) version 5. **Stage 7's design answered 2026-10-05** (D290–D299, §0). **Stage 9's design answered 2026-10-06** -(D306–D314, §0 and §9). +(D306–D314, §0 and §9). **Phase 9b was built 2026-10-06** (§9), with its two questions answered the same day (D315, +D316). RunicNPC is Runic Gateway's own NPC plugin for Rust servers, in its own repository, [`RunicGateway/runicnpc-rust`](https://gitea.whitlocktech.com/RunicGateway/runicnpc-rust). It runs on Oxide and @@ -136,6 +137,8 @@ architectural or design decision is implemented. | **D312** | **The player walk is one checklist in one session per rig.** Every in-game check deferred so far is gathered with stage 9's own. I prepare the rigs and the walk site, the org lead plays, and I watch the logs and record the results (stage 9). | A checklist per stage; release without a player walk. | | **D313** | **v1.0.0 is one coordinated cutover.** After the walk passes, `edge` goes to `main` in all five Rust repos: RunicNPC v1.0.0 first, then the bridge, the sidecar, the installer and the bundle that pins them, and the module's requirement last (stage 9). | RunicNPC alone first, the rest later. | | **D314** | **Core's public phase label is fixed in stage 9**, in its own website PR. `eventPublic.phaseLabel` matches a phase by `key`, which specs use, and still accepts `id`; its test fixture moves to `key`. It has said "Under way" for every phase since Events Phase 14a, UO's events included (stage 9, from docs#323). | A website issue for later. | +| **D315** | **The performance check records what 100 NPCs cost today, and the cost warning says it; the idle overhead is an optimisation issue, not a release blocker.** Stage 1's own bare NPC no longer meets stage 1's bar on today's rigs (fighting +7.5 / +8.0 ms against +5.2), and RunicNPC fights at the same cost; awake idle on Oxide is about 2 ms per 100 over the bare NPC (runicnpc-rust#13). Narrows D306 (stage 9b; the org lead: "Write a warning for it, log it as an optimization bug fix issue on gitea and keep going"). | A same-session bar against the bare NPC; fixing idle before v1.0; keeping D306's absolute bar. | +| **D316** | **On a server without RunicNPC, the Place NPCs picker keeps listing Rust's own scientists, and the step is refused when saved, with the reason.** Core's option sources cannot show a module's reason (a refusal reads only "could not be read"), and Admin → Rust → Servers already says "Incomplete". Narrows 9b's "the picker shows the same reason instead of a list" (stage 9b). | A core change letting an option source answer a reason (MODULE_API minor); an empty list. | **Borrowing, not copying.** NpcSpawn states no licence at all, so its source grants us nothing and is read only as a description of *what* can be done in Rust. HumanNPC is MIT on uMod, which is GPL-compatible, but §1.2 rules out its @@ -1615,6 +1618,39 @@ is asked before the next one starts. harness of stages 1 and 5. It passes at about +1 ms and +5 ms of median frame over that session's empty baseline. The numbers go into INSTALL and the cost warning. +**9b built (2026-10-06).** Rust 25681086, Oxide 2.0.7801, Carbon 2.0.262 on both rigs. + +- **Core's phase label (D314):** website PR. `phaseLabel` finds the phase by `key`, then `id`; the fixture uses + `key`, and a unit test covers a UO-shaped spec, the fallback and key-before-id. +- **RunicNPC is required (D310):** `module-rust` asks every Place NPCs step for RunicNPC first ("Main needs + RunicNPC to place NPCs, Rust's own scientists included: RunicNPC is not loaded on it"), and its floor rises from + API 4 to the bridge's 6, so a server the site calls ready is one the bridge will not refuse. Admin → Rust → Servers + carries each server's `runicNpc` and reads "Incomplete" with the reason. The picker is unchanged (D316). The + bridge refuses one of Rust's own NPC prefabs the same way, before anything spawns, and a profile placement now + refuses an old RunicNPC up front (PROTOCOL §19.16; no message changes shape, protocol stays 13). The installer + refuses a Rust bundle without RunicNPC, `doctor` fails without it, and the bundle CI composes no Rust bundle + without a RunicNPC that answers the bridge (tested against the real releases: it refuses today's v0.1.1 bridge, + which declares no RunicNPC API, and leaves ServUO alone). **The installer had no `edge` any more**, since its + cutover deleted it; it is recreated from `main` for this, so `main`, which releases on every push, gets it at 9e. + The egg follows the bundle and is unchanged. +- **Operator documentation:** [INSTALL.md](INSTALL.md) and [COMMANDS.md](COMMANDS.md). The required note went into + [`../rust-link/INSTALL.md`](../rust-link/INSTALL.md), the Rust operator guide that lists Kits: + `modules/rust/OPERATING.md`, which this section named, is a verbatim mirror of uMod's pages. +- **The update surface:** `runicnpc-rust`'s README lists every Rust type and member the swap and the brain depend + on, where, and whether a change is caught by compiling, by the field list, or only on a running server. +- **The performance check (D306, D315):** `rnt.cost` in the test harness measures RunicNPC's own NPCs, with stage + 1's `rnh.cost` beside it in the same session as the reference. 100 NPCs, increase of the median frame: + + | | Oxide: bare NPC | Oxide: RunicNPC | Carbon: bare NPC | Carbon: RunicNPC | + |---|---|---|---|---| + | Idle, awake | +0.96 ms | +2.65 / +2.92 ms | +0.97 ms | +1.52 ms | + | Idle, asleep | | | | +0.52 ms | + | Fighting 20 stand-ins | +7.53 ms | +7.19 ms | +7.96 ms | +9.04 ms | + + The rigs are slower than in September (an empty Oxide frame 22–30 ms, against 16.4 ms then), and between two + empty windows the median moved by up to 7 ms, so every run ends with a second empty window and is judged against + the lower. The cost warning now says 3 ms per 100 awake, 0.5 ms asleep and 9 ms fighting. + **9c. The staging drill (D307–D309).** - **The rig:** a new Pterodactyl server, `rust-staging`, Oxide only, on a 2000 map at about 6 GB. It keeps a diff --git a/rust-link/INSTALL.md b/rust-link/INSTALL.md index 9299121..9dda476 100644 --- a/rust-link/INSTALL.md +++ b/rust-link/INSTALL.md @@ -7,13 +7,13 @@ the design of record is [`../modules/rust/PLAN.md`](../modules/rust/PLAN.md) §3 ## What this installs Two components per Rust server, released together as a **bundle** — an exact pair CI has checked -speaks one protocol, never "the latest of each" — and, from RunicNPC's stage 4, a third: +speaks one protocol, never "the latest of each" — and a third, RunicNPC, which is required: | Component | What it is | Released from | |---|---|---| | **The plugin** | `RunicGateway.cs`, one file that runs unchanged on Oxide and Carbon — and beside it, from protocol 13, the optional **ZoneManager helper** `RunicGatewayZones.cs` (PLAN_FIXES D181, D182), installed by default | [Rust-Plugins](https://gitea.whitlocktech.com/RunicGateway/Rust-Plugins/releases) | | **The sidecar** | `rust-link-sidecar`, which the plugin dials on loopback and the website reaches over HTTP | [Rust-Link](https://gitea.whitlocktech.com/RunicGateway/Rust-Link/releases) | -| **RunicNPC** | `RunicNPC.cs`, Runic Gateway's NPC plugin ([`../runicnpc/PLAN.md`](../runicnpc/PLAN.md)), placed beside the bridge **when the bundle carries it**: from RunicNPC's stage 4, the latest RunicNPC release that answers the API the bridge needs (D224). Optional until RunicNPC's stage 9; it needs **Kits**, like the bridge | [runicnpc-rust](https://gitea.whitlocktech.com/RunicGateway/runicnpc-rust/releases) | +| **RunicNPC** | `RunicNPC.cs`, Runic Gateway's NPC plugin ([`../runicnpc/PLAN.md`](../runicnpc/PLAN.md)), placed beside the bridge: the latest RunicNPC release that answers the API the bridge needs (D224). **Required** since RunicNPC's stage 9 (D310): without it the bridge refuses every NPC an event places, Rust's own scientists included, and Admin → Rust → Servers shows the server as incomplete. It needs **Kits**, like the bridge. Its own guide is [`../runicnpc/INSTALL.md`](../runicnpc/INSTALL.md) | [runicnpc-rust](https://gitea.whitlocktech.com/RunicGateway/runicnpc-rust/releases) | The game server opens no port for the bridge: the plugin is the client and the sidecar the listener, on `127.0.0.1`. **One sidecar serves one Rust server.** A community running six servers @@ -32,7 +32,8 @@ There are three ways to set a server up. They produce the same result: - **Oxide or Carbon is installed, and the server has started once** with it, so its directories exist. The bridge is a plugin; a vanilla server has nothing to load it. - **Kits and ZoneManager** from uMod are what the reward and zone features use. The bridge works - without them and names what is missing; nothing here installs them. + without them and names what is missing; nothing here installs them. **RunicNPC needs Kits** and + will not load without it, so without Kits no event can place NPCs. - **The website has the Rust module**, and you are an administrator on it. Servers are added at **Admin → Rust → Servers** (`/admin/rust/servers`). @@ -122,8 +123,8 @@ When you create a server from it: **The install** downloads the sidecar, its launcher and the plugin from the bundle, checks each against the bundle's checksum, and only then places them: the sidecar in `rust-link/`, the plugin in -`oxide/plugins/` or `carbon/plugins/`. When the bundle carries RunicNPC, `RunicNPC.cs` goes beside the plugin -(its data directory is left for RunicNPC to make). Any mismatch fails the install with the reason, before +`oxide/plugins/` or `carbon/plugins/`. `RunicNPC.cs` goes beside the plugin (its data directory is left for RunicNPC to make); a bundle +without RunicNPC is refused. Any mismatch fails the install with the reason, before anything is placed. **The first boot** prints, in the console, the token the sidecar generated — **once** — and a line @@ -156,8 +157,8 @@ history, and the token is unchanged. [`v2/rust/current.json`](https://gitea.whitlocktech.com/RunicGateway/installer/src/branch/bundles/v2/rust/current.json) on the installer's `bundles` branch. It names the sidecar binary for your platform and the plugin tarball, each with a `sha256`. -2. **Download and verify** both against those checksums (`sha256sum -c`, or `Get-FileHash`) — and, when - the bundle has an `npc` entry, RunicNPC's tarball too. Copy `runicnpc/RunicNPC.cs` from it into the same +2. **Download and verify** both against those checksums (`sha256sum -c`, or `Get-FileHash`) — and RunicNPC's + tarball, the bundle's `npc` entry, too. Copy `runicnpc/RunicNPC.cs` from it into the same plugins directory as the bridge. **Do not create `data/RunicNPC/` yourself**: RunicNPC makes it on first load, and one made from outside the game (a panel's file manager) is not writable by it. 3. **The plugin:** copy `runicgateway-rust-plugin/RunicGateway.cs` from the tarball into @@ -225,7 +226,7 @@ With the installer: | | | |---|---| -| `doctor --game rust [--server-id ]` | Per server: the framework; whether the plugin file is still the one deployed; each helper deployed beside it, as a **warning** when missing or edited (the bridge runs without one, and the row says what that costs); RunicNPC, when the bundle carried it, the same way (without it the site's NPC profiles and placements and events' profile NPCs are off); that the plugin's config names this server; the required uMod plugins; the service; and `/health` through to **plugin connected**. A stopped server is a warning; a running one whose plugin never connected is a failure, printed with the framework versions the plugin is known good on | +| `doctor --game rust [--server-id ]` | Per server: the framework; whether the plugin file is still the one deployed; each helper deployed beside it, as a **warning** when missing or edited (the bridge runs without one, and the row says what that costs); RunicNPC as a **failure** when it is missing or was never installed (it is required: without it every NPC an event places is refused), and a warning when edited by hand; that the plugin's config names this server; the required uMod plugins; the service; and `/health` through to **plugin connected**. A stopped server is a warning; a running one whose plugin never connected is a failure, printed with the framework versions the plugin is known good on | | `update --game rust` | Moves the sidecar and every server's plugin to the current bundle, and restarts the sidecars. Always all servers together — they share one binary | | `uninstall --game rust [--server-id ] [--purge]` | Removes the service, the plugin file, its helpers and RunicNPC. **Keeps the plugin's config** and RunicNPC's `data/RunicNPC/` (an admin's placements and routes) — it is the website's, and it names the server. `--purge` also removes the sidecar config (the token) and the database. Removing the last server removes the shared binary too | diff --git a/rust-link/PROTOCOL.md b/rust-link/PROTOCOL.md index 132f23b..7ffc898 100644 --- a/rust-link/PROTOCOL.md +++ b/rust-link/PROTOCOL.md @@ -2401,8 +2401,9 @@ in-game rows (flags on a player, the messages) wait for the later in-game walk. RunicNPC is Runic Gateway's own NPC plugin ([`../runicnpc/PLAN.md`](../runicnpc/PLAN.md), [`API.md`](../runicnpc/API.md)). **The bridge is its only link to the site**: it calls RunicNPC's API for the site's commands and turns RunicNPC's hooks into frames, and RunicNPC never talks to the sidecar, so "the sidecar is a dumb forwarder" -stays true. RunicNPC is optional until its stage 9. Without it every `npc.*` command answers `npc.error` -**`runicnpc-missing`**, and an event places Rust's own scientists only (D243). +stays true. Without it every `npc.*` command answers `npc.error` **`runicnpc-missing`**. Until its stage 9 an +event then placed Rust's own scientists only (D243); since stage 9 RunicNPC is required, and an event places no +NPCs at all without it (§19.16). The bridge needs **RunicNPC API 3** (D249), `RunicNpcApiNeeded` in the code and **`runicnpc_api = 3` in `overlay.toml`**, which the release copies into its manifest and `checkPlugin.js` holds equal to the code. @@ -2614,3 +2615,19 @@ Stage 7's wire change **joins protocol 13** while it is unreleased (D299); no me `false` spawns the NPCs with RunicNPC's override `{"loot":{"dropTable":false}}`: they keep what their profile starts the corpse with, and roll no loot table. Absent or `true`, as before. The site sends it only when an event's Place NPCs step switches it off. + +### 19.16 RunicNPC stage 9: RunicNPC is required (runicnpc PLAN.md stage 9) + +**No message changes shape**, and `PROTOCOL_VERSION` stays 13. What changes is which `world.place` the bridge +refuses (D310): **every placement of NPCs needs RunicNPC loaded and answering `RunicNpcApiNeeded` (6)**, a +`profile` placement and one of Rust's own NPC prefabs (`npc.scientist`, `npc.bandit.guard`, …) alike. Crates are +unaffected. The refusal is checked before anything is spawned, with the two reasons that already exist: + +| `reason` | Means | Retried? | +|---|---|---| +| `runicnpc-missing` | RunicNPC is not loaded, so this server places no NPCs | no | +| `runicnpc-old` | RunicNPC answers an API older than the bridge needs | no | + +The site checks the same thing first, from the hello's `integrations.runicNpc`, and refuses the step before it +is sent; the bridge's check is for a site that missed the hello. `rg.npc` says the same when RunicNPC is not +loaded.