# RunicNPC Runic Gateway's own NPC plugin for [Rust](https://rust.facepunch.com/), on **Oxide and Carbon**. Other plugins drive it through an API, and admins use it directly in game through chat commands. It works on its own, and on a [Runic Gateway](https://gitea.whitlocktech.com/RunicGateway) server the website authors its NPC profiles and events use its NPCs. The plan of record, stage by stage, is [`docs/runicnpc/PLAN.md`](https://gitea.whitlocktech.com/RunicGateway/docs/src/branch/main/runicnpc/PLAN.md). > **Status: stage 4.** The NPC, its profiles, placements and routes, the API other plugins call, and > the `/rnpc` commands admins use in game. On a Runic Gateway server, the website takes over the > profiles and lists, edits and creates placements through the bridge (API 3). ## Requirements - **[Kits](https://umod.org/plugins/kits)** — required. A profile names kits, and that is how every RunicNPC NPC is equipped (D217). The plugin declares `// Requires: Kits`, so neither framework loads it without Kits. - Oxide **2.0.7726** or Carbon **2.0.259**, or newer: the builds it has been loaded on (`plugin.toml`). ## Installing it On a Runic Gateway server, the [installer](https://gitea.whitlocktech.com/RunicGateway/installer) and the Pterodactyl egg will install it from the Rust bundle, pinned and checksummed (D224, from stage 4). Until then, and on any other server: 1. Download `runicnpc-.tar.gz` and `SHA256SUMS` from this repository's [releases](https://gitea.whitlocktech.com/RunicGateway/runicnpc-rust/releases), and check the tarball against it (`sha256sum -c SHA256SUMS`). 2. Copy `runicnpc/RunicNPC.cs` into `oxide/plugins/` or `carbon/plugins/`. The framework compiles and loads it on the write. `runicnpc/manifest.json`, beside it, states the release's version, commit, API version, framework floors, required plugins, and the sha256 of every file it ships. ## Checking it ``` rnpc.status ``` Answers at the server console and over RCON: the version, the API version, and which of the plugin's hooks have fired. A hook that never fires is the first sign a Rust or framework update has renamed it, because neither framework reports a hook that matches nothing. ## In game `/rnpc` in chat, and the same verbs as `rnpc.` in a console (F1, the server console or RCON). `/rnpc` alone lists the ones you may use. Grant `runicnpc.place` to place and manage NPCs, and `runicnpc.admin` for everything (it includes `runicnpc.place`); the server console has both. ``` /rnpc place bandit count=3 respawn=300 mode=group move=route:gate > Placed bandit-1: 3 × 'bandit' (roamer, route:gate), respawn 300 s, group. /rnpc path record gate then walk, and at each point: /rnpc path point /rnpc path save loop (or back, to walk it back and forth) /rnpc near · /rnpc info · /rnpc rename bandit-1 gateguards · /rnpc remove ``` `place` uses the spot you look at, `here` the spot you stand on; the server console gives `at=x,y,z`. A roamer must stand on Rust's navmesh; only a sentry may stand off it. Every placement answers with what the server's NPCs cost. On a standalone server, profiles are made from a console with `rnpc.profile create|set|delete`. The full list is in [PLAN.md §5](https://gitea.whitlocktech.com/RunicGateway/docs/src/branch/main/runicnpc/PLAN.md). ## For other plugins Every call is prefixed `RunicNpc_` and reached through `Call`: ```csharp [PluginReference] private Plugin RunicNPC; int api = RunicNPC?.Call("RunicNpc_ApiVersion") ?? 0; ``` The API is version 3, documented in [`docs/runicnpc/API.md`](https://gitea.whitlocktech.com/RunicGateway/docs/src/branch/main/runicnpc/API.md): spawning and removing NPCs by owner, profiles, placements (created and named as in game, from a map point), routes, the cost warning, and the hooks it raises. The API version moves when a call or a raised hook changes shape, not on every release. ## Repository layout | Path | What | |---|---| | `plugin/RunicNPC.cs` | The plugin. The only file a server gets. | | `plugin.toml` | Its declarations: API version, framework floors, required plugins. The release copies them into the manifest. | | `scripts/checkPlugin.js` | The static checks run on every pull request and again before a release (see its header). | | `tools/` | Developer scaffolding for the test rigs, never shipped: the panel scripts (see `tools/rigs.example.json`); `RunicNpcHarness.cs`, the stage 1 measurement plugin; `RunicNpcTest.cs`, the stage 2, 3 and 4 test harness (`rnt.run all`, then `rnt.after` after a reload or restart); and `fieldlist/` with `managed.js`, which regenerate the swap's field list (below). | ## After a Rust update: the swap's field list Our NPC is Rust's scientist with two components swapped, and the swap copies a fixed list of fields (D232), generated from Rust's unmodified assembly. `rnpc.status` reports it as `swap fields: npc=64/64 brain=32/32 missing=- added=-`. On Carbon, where the assembly is unmodified, `added` names any field Rust has added since the list was made. To regenerate it: ```bash node tools/managed.js carbon # the Carbon rig's RustDedicated_Data/Managed dotnet run --project tools/fieldlist -- # rewrites the block in plugin/RunicNPC.cs ``` Use the Carbon rig: Oxide's patcher makes Rust's private fields public, so its assembly no longer says which fields Rust itself serialises. ## Releases Work lands on `edge` and is cut over to `main`; every releasable push to `main` tags and publishes a release (`.gitea/workflows/release.yml`). The version comes from Conventional Commits since the last tag. `v1.0.0` is stage 9's release, the one `module-rust` then requires. ## Contributing See [CONTRIBUTING.md](CONTRIBUTING.md) — including the AI-disclosure rule — and report security problems privately as [SECURITY.md](SECURITY.md) describes. ## License GPL-3.0-or-later — see [LICENSE.md](LICENSE.md).