All checks were successful
PR Checks / plugin-checks (pull_request) Successful in 7s
The chat command /rnpc and the same verbs as rnpc.<verb> in a console (PLAN.md §5), behind runicnpc.place and runicnpc.admin: - place / here with key=value options in any order (D242); placements made in game are named <profile>-<n> and renamable (D241). - remove, rename, near, info (the NPC you look at), profiles, tp, respawn, clear, and rnpc.profile create|show|set|delete for a standalone server (refused while the site manages profiles, D221). - path record / point / undo / save [loop|back] / cancel / list / delete (D240). A point an NPC cannot walk to from the last one is refused, as is a loop that cannot close: Rust's path query says so. - A roamer may stand on a player-built floor, with a warning (D239). If the floor is destroyed under a live NPC, Rust leaves it standing in the air, so the brain puts it on the nearest navmesh within 2 s; a placement whose spot is off the mesh respawns on the nearest. - The API's SetPlacement now refuses a roamer off the navmesh, as the command does; placements report a note while they fall back. - Pos.V and Profile.IsSentry no longer leak into the JSON files. The harness gains the cmd and floor groups. Both rigs: 134/134 in one rnt.run all, rnpc reload 5/5, server restart 5/5. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
119 lines
5.7 KiB
Markdown
119 lines
5.7 KiB
Markdown
# 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 3.** The NPC, its profiles, placements and routes, the API other plugins call, and
|
||
> the `/rnpc` commands admins use in game. Runic Gateway's website takes over the profiles in stage 4.
|
||
|
||
## 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-<version>.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.<verb>` 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<int>("RunicNpc_ApiVersion") ?? 0;
|
||
```
|
||
|
||
The API is version 2, 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, 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 and 3 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 <dir> # the Carbon rig's RustDedicated_Data/Managed
|
||
dotnet run --project tools/fieldlist -- <dir> # 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).
|