feat: API 3, placements from a website (stage 4, D249)

RunicNpc_AddPlacement creates a placement and names it as /rnpc place
does (D241, D246). A position without y is a map point: it is put on the
ground there (terrain and rock, never a building or a tree, D245), then
checked against the navmesh like an in-game placement. It answers the id,
the grounded position, whether the spot is player-built, and the cost
warning, or the in-game refusal sentence.

RunicNpc_RenamePlacement and RunicNpc_RespawnPlacement do what rnpc
rename and rnpc respawn do. OnRunicNpcPlacementChanged(id, change,
previous) is raised on every set, remove and rename, from the API or in
game, so the bridge can tell the site at once.

rnpc place and here now share CreatePlacement with the API, so both give
the same refusals. The test harness gains an api3 group; all 157 checks
pass on the Oxide and Carbon rigs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
This commit is contained in:
2026-09-30 04:01:30 -05:00
parent 9c366ab5b5
commit fe20ca41af
4 changed files with 264 additions and 44 deletions

View File

@@ -7,8 +7,9 @@ works on its own, and on a [Runic Gateway](https://gitea.whitlocktech.com/RunicG
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.
> **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
@@ -73,9 +74,10 @@ Every call is prefixed `RunicNpc_` and reached through `Call`:
int api = RunicNPC?.Call<int>("RunicNpc_ApiVersion") ?? 0;
```
The API is version 2, documented in
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, routes, the cost warning, and the hooks it raises.
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
@@ -85,7 +87,7 @@ The API version moves when a call or a raised hook changes shape, not on every r
| `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). |
| `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