feat(docs): phase 16 - the RunicNPC subgroup, its API carried in full and drift-checked
All checks were successful
PR checks / checks (pull_request) Successful in 2m11s
All checks were successful
PR checks / checks (pull_request) Successful in 2m11s
PLAN.md §16 phase 16, with the org lead's D72-D74 (count of record 74). - Eight pages nested in the Rust group at /docs/rust/runicnpc/ (D72): the overview, Install RunicNPC, Commands, NPC profiles, NPC placements, Cost and caps, Profile fields (the one field reference the editor, rnpc.profile set, the data file and the API all share) and The API. - The API page carries every call, hook and data shape (D62), in the site's own words, with no decision numbers and no per-call version history (D74). Signatures print from src/data/runicnpc.mjs through Signature.astro. - checkReference.mjs gains two sections. Against docs main:runicnpc/API.md: the API version, 24 call and 10 hook signatures, and the field names of the profile, RunicNpc_List rows, placements, routes and owners (D73), plus exactly one <Signature> per call on api.mdx. Against runicnpc-rust main:plugin/RunicNPC.cs: console verbs, permissions, place options and config keys. Six planted drifts all failed before it was trusted. - checkSidebar and checkTerms read nested sidebar groups via flatLabels(). - The Rust overview, Events zones and rewards, and Canonical documents link the new pages; the canonical documents gain the four runicnpc/ docs. Both NPC admin screens were walked on the walk site first (D36); nothing was saved. npm run verify is green. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
This commit is contained in:
@@ -412,5 +412,9 @@ export const canonicalDocs = {
|
||||
'modules/rust/PLAN.md': 'The Rust module design of record',
|
||||
'modules/rust/OPERATING.md': "Running Oxide: uMod's operator pages, plus the notes on RaidableBases and Guarded Crate",
|
||||
'modules/rust/CARBON.md': 'Where Carbon differs from Oxide, and nowhere else',
|
||||
'runicnpc/API.md': "RunicNPC's API for other plugins — normative over the site's copy",
|
||||
'runicnpc/COMMANDS.md': "RunicNPC's commands and permissions",
|
||||
'runicnpc/INSTALL.md': 'Installing RunicNPC, with or without Runic Gateway',
|
||||
'runicnpc/PLAN.md': "RunicNPC's design of record, and why each behaviour is shaped as it is",
|
||||
},
|
||||
};
|
||||
|
||||
513
src/data/runicnpc.mjs
Normal file
513
src/data/runicnpc.mjs
Normal file
@@ -0,0 +1,513 @@
|
||||
/**
|
||||
* RunicNPC, as the site carries it — PLAN.md §16, phase 16 (D62, D72–D74).
|
||||
*
|
||||
* D62 made the API an exception to §1: the site carries it IN FULL rather than linking to it,
|
||||
* because its readers are plugin authors outside this project. `docs` `main:runicnpc/API.md`
|
||||
* stays normative, and `scripts/checkReference.mjs` holds everything below that API.md names
|
||||
* against it in both directions (D73): every call's and hook's signature, and the field names
|
||||
* of every data shape. A signature that differs by one parameter fails the build.
|
||||
*
|
||||
* The commands, permissions, `/rnpc place` options and config keys are names too, and are
|
||||
* checked the same way against the plugin's own source, `runicnpc-rust` `main:plugin/RunicNPC.cs`.
|
||||
*
|
||||
* Descriptions are written in the site's own words and are NOT checked (the same rule as
|
||||
* reference.mjs). D74: they carry no decision numbers and no "added in API n" history — the
|
||||
* site describes the current API only. Backticks in a description render as code
|
||||
* (`src/components/Ticks.astro`).
|
||||
*/
|
||||
|
||||
/** Who owns an NPC, which decides how long it lives. API.md "Owners". */
|
||||
export const runicnpcOwners = {
|
||||
'run:<id>': {
|
||||
who: 'An event run, through the bridge',
|
||||
lifetime: 'Never saved. Removed by its caller, or by `RunicNpc_DespawnOwner`',
|
||||
},
|
||||
'plugin:<name>': {
|
||||
who: 'Any other plugin — use your own plugin’s name',
|
||||
lifetime: 'Never saved. **Removed when that plugin unloads**',
|
||||
},
|
||||
'placement:<id>': {
|
||||
who: 'RunicNPC itself, for a placement. A caller cannot spawn with this owner',
|
||||
lifetime: 'The NPC is never saved; the placement is, and spawns a fresh one at boot',
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Every call, as `name: { params, returns }`. The signature compared is
|
||||
* `name(params) → returns`, whitespace collapsed. The prose for each is on the API page,
|
||||
* under a `<Signature call="…" />` for it; the check also requires every call here to have
|
||||
* exactly one.
|
||||
*/
|
||||
export const runicnpcCalls = {
|
||||
RunicNpc_ApiVersion: { params: '', returns: 'int' },
|
||||
RunicNpc_Spawn: { params: 'Vector3 at, string profile, string owner, JObject overrides', returns: 'BasePlayer' },
|
||||
RunicNpc_Despawn: { params: 'ulong netId', returns: 'int' },
|
||||
RunicNpc_DespawnOwner: { params: 'string owner', returns: 'int' },
|
||||
RunicNpc_List: { params: 'string owner', returns: 'List<Dictionary<string, object>>' },
|
||||
RunicNpc_Profiles: { params: '', returns: 'JObject' },
|
||||
RunicNpc_SetProfiles: { params: 'JObject all', returns: 'Dictionary<string, string>' },
|
||||
RunicNpc_Placements: { params: '', returns: 'List<Dictionary<string, object>>' },
|
||||
RunicNpc_SetPlacement: { params: 'string id, JObject placement', returns: 'string' },
|
||||
RunicNpc_RemovePlacement: { params: 'string id', returns: 'bool' },
|
||||
RunicNpc_AddPlacement: { params: 'JObject placement', returns: 'Dictionary<string, object>' },
|
||||
RunicNpc_RenamePlacement: { params: 'string from, string to', returns: 'string' },
|
||||
RunicNpc_RespawnPlacement: { params: 'string id', returns: 'int' },
|
||||
RunicNpc_Routes: { params: '', returns: 'JObject' },
|
||||
RunicNpc_SetRoute: { params: 'string name, JObject route', returns: 'string' },
|
||||
RunicNpc_RemoveRoute: { params: 'string name', returns: 'bool' },
|
||||
RunicNpc_IsRunicNpc: { params: 'BaseEntity entity', returns: 'bool' },
|
||||
RunicNpc_ProfileOf: { params: 'BaseEntity entity', returns: 'string' },
|
||||
RunicNpc_Factions: { params: '', returns: 'JArray' },
|
||||
RunicNpc_SetFactions: { params: 'JArray pairs', returns: 'string' },
|
||||
RunicNpc_Escort: { params: 'ulong netId, BaseEntity target', returns: 'string' },
|
||||
RunicNpc_Ally: { params: 'ulong netId, string kind, string id', returns: 'string' },
|
||||
RunicNpc_Tether: { params: 'ulong netId, string zone', returns: 'string' },
|
||||
RunicNpc_CostWarning: { params: 'int adding', returns: 'string' },
|
||||
};
|
||||
|
||||
/** Every hook RunicNPC raises. None answers: a return value is ignored. */
|
||||
export const runicnpcHooks = {
|
||||
OnRunicNpcSpawned: {
|
||||
params: 'BasePlayer npc, string profile, string owner',
|
||||
when: 'After an NPC spawns and is equipped.',
|
||||
},
|
||||
OnRunicNpcHealth: {
|
||||
params: 'BasePlayer npc, string profile, float threshold',
|
||||
when: 'The first time its health falls to one of its profile’s `healthThresholds`.',
|
||||
},
|
||||
OnRunicNpcDied: {
|
||||
params: 'BasePlayer npc, string profile, string owner, HitInfo info, Dictionary<ulong, float> contributors',
|
||||
when:
|
||||
'When it dies. `contributors` maps each player’s Steam id to the health that player took from it, the killing blow included, after Rust’s protection — never more than its health in all.',
|
||||
},
|
||||
OnRunicNpcDespawned: {
|
||||
params: 'BasePlayer npc, string owner',
|
||||
when: 'When it is removed without dying. Not raised while RunicNPC itself unloads.',
|
||||
},
|
||||
OnRunicNpcPlacementChanged: {
|
||||
params: 'string id, string change, string previous',
|
||||
when:
|
||||
'A placement was set, removed or renamed, through the API or in game. `change` is `set`, `removed` or `renamed`; `previous` is the old name for a rename, and null otherwise.',
|
||||
},
|
||||
OnRunicNpcEscortEnded: {
|
||||
params: 'BasePlayer npc, string profile, string owner',
|
||||
when: 'Whom it escorted died, was destroyed or left the server, and it is walking back to its spot.',
|
||||
},
|
||||
OnRunicNpcBossSpawned: {
|
||||
params: 'BasePlayer npc, string profile, string owner',
|
||||
when: 'After `OnRunicNpcSpawned`, for a profile with a `boss` block, once its spawn line has been said.',
|
||||
},
|
||||
OnRunicNpcBossPhase: {
|
||||
params: 'BasePlayer npc, string profile, string owner, int phase, float at',
|
||||
when:
|
||||
'It entered a phase. `phase` counts from 1 and `at` is the phase’s health fraction. Raised once the phase’s changes apply and its line is said; a hit that crosses two thresholds raises both, highest first.',
|
||||
},
|
||||
OnRunicNpcBossDied: {
|
||||
params: 'BasePlayer npc, string profile, string owner, HitInfo info, Dictionary<ulong, float> contributors',
|
||||
when:
|
||||
'After `OnRunicNpcDied`, for a boss, with the same contributors, once its death line has been said. RunicNPC gives no reward itself.',
|
||||
},
|
||||
OnRunicNpcUsed: {
|
||||
params: 'BasePlayer npc, string profile, BasePlayer player',
|
||||
when: 'A player pressed E on a passive NPC and was answered. At most once a second per player.',
|
||||
},
|
||||
};
|
||||
|
||||
/** The keys of each row `RunicNpc_List` returns. */
|
||||
export const runicnpcListKeys = {
|
||||
netId: { type: 'ulong', what: 'Its net id, which `RunicNpc_Despawn` and the orders take' },
|
||||
profile: { type: 'string', what: 'Its profile’s name' },
|
||||
name: { type: 'string', what: 'The name it was given from its profile’s `names`' },
|
||||
owner: { type: 'string', what: 'Its owner' },
|
||||
kit: { type: 'string', what: 'The kit picked for it' },
|
||||
placement: { type: 'string', what: 'Its placement’s id, or null' },
|
||||
position: { type: '{x, y, z}', what: 'Where it is' },
|
||||
home: { type: '{x, y, z}', what: 'Where it spawned: what it wanders around, and walks back to before it sleeps' },
|
||||
health: { type: 'float', what: 'Its health now' },
|
||||
maxHealth: { type: 'float', what: 'Its full health' },
|
||||
movement: { type: 'string', what: '`wander`, `monument` or `route:<name>`' },
|
||||
rest: { type: 'string', what: '`Awake`, `GoingHome` or `Asleep`' },
|
||||
state: { type: 'string', what: 'Rust’s own AI state: `Idle`, `Roam`, `Chase`, `Combat` and so on' },
|
||||
leashes: { type: 'int', what: 'How many times it has given up a chase at its chase range' },
|
||||
regrounded: {
|
||||
type: 'int',
|
||||
what: 'How many times the ground went from under it and it was put on the nearest navmesh',
|
||||
},
|
||||
faction: { type: 'string', what: 'Its faction, or `profile:<name>` when its profile has none' },
|
||||
target: {
|
||||
type: 'string',
|
||||
what: 'The prefab of the NPC, animal or turret it is fighting, or null. Players are not reported here',
|
||||
},
|
||||
engaged: { type: 'bool', what: 'Whether it is fighting that target now — Rust fights players first' },
|
||||
escort: { type: 'string', what: 'Whom it escorts: a player’s Steam id, or an entity’s prefab; null for none' },
|
||||
ally: { type: 'string', what: '`clan:<id>`, `team:<id>` or `player:<steam id>`, or null' },
|
||||
tether: { type: 'string', what: 'The ZoneManager zone it is held in, or null' },
|
||||
shotsMarked: {
|
||||
type: 'int',
|
||||
what: 'Shots it made at an NPC target; each marks the target hostile for that shot only',
|
||||
},
|
||||
kitUsed: {
|
||||
type: 'object',
|
||||
what: '`heals`, `throws`, `rockets` and `meleeHits`: how much of its kit’s extras it has used',
|
||||
},
|
||||
role: { type: 'string', what: 'Its profile’s role: `roamer`, `sentry`, `guard` or `passive`' },
|
||||
boss: {
|
||||
type: 'object',
|
||||
what: 'For a boss, `phase` (0 before the first, then 1, 2 …) and `damagers`, how many players have hurt it; null for any other NPC',
|
||||
},
|
||||
};
|
||||
|
||||
/** A placement's keys, as `RunicNpc_SetPlacement` and `RunicNpc_AddPlacement` take them. */
|
||||
export const runicnpcPlacementKeys = {
|
||||
profile: 'The profile its NPCs are made from',
|
||||
position: '`{x, y, z}`. Through `RunicNpc_AddPlacement`, a position without `y` is a point on the map, put on the ground there',
|
||||
yaw: 'Which way they face, in degrees',
|
||||
count: 'How many NPCs stand there. All but the first are scattered within 3 m of the spot',
|
||||
respawn: 'Seconds after a death before an NPC comes back; at least 1',
|
||||
respawnMode: '`each`: every NPC comes back `respawn` after its own death. `group`: none comes back until all are dead, then all together',
|
||||
movement: 'Optional. `{mode, radius}`, overriding the profile’s',
|
||||
tether: 'Optional. A ZoneManager zone its NPCs never leave. A zone the server lacks, or one that does not hold the spot, is refused',
|
||||
};
|
||||
|
||||
/** A route's keys. */
|
||||
export const runicnpcRouteKeys = {
|
||||
points: 'At least two `{x, y, z}` points, each on Rust’s navmesh',
|
||||
loop: 'True: the last point leads back to the first. False: the route is walked back and forth',
|
||||
};
|
||||
|
||||
/**
|
||||
* A profile's fields, by their JSON path — the same path `rnpc.profile set` takes and the
|
||||
* file and the API carry. Grouped as the site's profile editor groups them, with the label
|
||||
* it shows (`site`); null where the editor has no control for a field.
|
||||
*
|
||||
* API.md names the boss phase fields `phases[].…`, and so does this list, so the two compare
|
||||
* as written; on the page they sit under `boss.phases`.
|
||||
*/
|
||||
export const runicnpcProfileFields = {
|
||||
Looks: {
|
||||
names: { site: 'NPC names', rule: 'at least one', meaning: 'One is picked for each NPC. The first is the profile’s name on the leaderboard' },
|
||||
kits: {
|
||||
site: 'Kits',
|
||||
rule: 'at least one; each must exist in Kits',
|
||||
meaning: 'One is picked for each NPC, which is equipped with it. A kit the server lacks refuses the profile when it is saved, not when an NPC spawns',
|
||||
},
|
||||
prefab: { site: 'Prefab', rule: 'a plain Rust `scientistnpc_*` prefab', meaning: 'What it is built from' },
|
||||
role: {
|
||||
site: 'Role',
|
||||
rule: '`roamer`, `sentry`, `guard` or `passive`',
|
||||
meaning:
|
||||
'A **roamer** moves, chases and fights. A **sentry** never moves, and may stand off the navmesh. A **guard** holds its spot facing the way it was placed, chases within its chase range, and walks back. A **passive** NPC never fights — it takes no target, does not shoot back and raises no alarm — and pressing E on it does what its `use` says',
|
||||
},
|
||||
},
|
||||
Moving: {
|
||||
'movement.mode': {
|
||||
site: 'How it moves',
|
||||
rule: '`wander`, `monument`, `route:<name>`, or `stand` for a passive profile only',
|
||||
meaning:
|
||||
'Wander around its spot, follow Rust’s own paths in the monument it stands in, or walk a recorded route. A passive NPC that stands holds its spot, may stand off the navmesh, and turns to whoever presses E on it. A placement may override it',
|
||||
},
|
||||
'movement.radius': { site: 'Wander radius (m)', rule: 'above 0 for `wander`', meaning: 'How far from home it wanders' },
|
||||
sleepDistance: {
|
||||
site: 'Sleeps with nobody within (m)',
|
||||
rule: '0 or more',
|
||||
meaning: 'With no player this close it walks home, then sleeps. 0: it never sleeps',
|
||||
},
|
||||
},
|
||||
Fighting: {
|
||||
health: { site: 'Health', rule: 'above 0', meaning: 'Its health, and its maximum' },
|
||||
damageDealt: { site: 'Damage dealt ×', rule: '0 or more', meaning: 'Multiplies its weapon’s damage; 1 is the weapon’s full damage' },
|
||||
aimCone: { site: 'Aim spread', rule: '0 or more', meaning: 'How much its aim spreads. Rust’s scientists use 2' },
|
||||
visionCone: { site: 'Vision cone', rule: '−1 to 1', meaning: 'Rust’s vision cone' },
|
||||
damageTaken: {
|
||||
site: 'Damage taken × head, body, legs',
|
||||
rule: '`head`, `body` and `legs`, each 0 or more',
|
||||
meaning:
|
||||
'Multiplies the damage it takes to the head, the legs (legs and feet) and the body — everything else, including hits with no body part, like fire and explosions',
|
||||
},
|
||||
'ranges.sense': { site: 'Notices players at (m)', rule: 'above 0', meaning: 'How far it notices players' },
|
||||
'ranges.loseTarget': { site: 'Forgets them at (m)', rule: 'at least `sense`', meaning: 'How far a target must get before it is forgotten' },
|
||||
'ranges.chase': {
|
||||
site: 'Chases up to (m from home)',
|
||||
rule: '0 or more',
|
||||
meaning:
|
||||
'How far from home it will chase; 0 is no limit. At that distance it waits 5 s for the target to come back within range, then gives up and walks home',
|
||||
},
|
||||
'ranges.attack': { site: 'Shoots from (m)', rule: 'above 0', meaning: 'How far it shoots from. This replaces the weapon’s own range' },
|
||||
healthThresholds: {
|
||||
site: 'Health thresholds (%)',
|
||||
rule: 'fractions between 0 and 1',
|
||||
meaning: 'Where `OnRunicNpcHealth` is raised, once each — and so what an event’s phase can wait for',
|
||||
},
|
||||
},
|
||||
'Press E': {
|
||||
use: {
|
||||
site: 'Press E',
|
||||
rule: 'required on a passive profile, refused on any other',
|
||||
meaning:
|
||||
'What pressing E on it does, from up to 3 m. `mode` `chat` says one of `lines` (picked at random, each at most 256 characters) in that player’s chat. `mode` `window` opens a window with `title` (blank: its name), `text` (at most 2,000 characters) and a Close button. `{player}` in either is the player’s name',
|
||||
},
|
||||
},
|
||||
Boss: {
|
||||
boss: {
|
||||
site: 'It is a boss',
|
||||
rule: 'null, or the block below; not on a passive profile',
|
||||
meaning: 'Makes a roamer, sentry or guard a boss. It keeps moving and fighting as its role does',
|
||||
},
|
||||
'boss.barDistance': {
|
||||
site: 'Bar shown within (m)',
|
||||
rule: 'above 0, at most 1,000; default 100',
|
||||
meaning:
|
||||
'Players this close see its health bar at the top of the screen, and so does every player who has hurt it, wherever they are, until it dies or despawns. A player near two bosses sees the nearer',
|
||||
},
|
||||
'boss.announce': {
|
||||
site: 'Lines said in',
|
||||
rule: '`chat` or `popup`; default `chat`',
|
||||
meaning: 'Where its lines are said. `popup` uses PopupNotifications where it is loaded, and chat where it is not',
|
||||
},
|
||||
'boss.spawnLine': {
|
||||
site: 'When it spawns, to everyone',
|
||||
rule: 'at most 256 characters; blank says nothing',
|
||||
meaning: 'Said to everyone on the server. `{name}` is its name',
|
||||
},
|
||||
'boss.deathLine': {
|
||||
site: 'When it dies, to everyone',
|
||||
rule: 'at most 256 characters; blank says nothing',
|
||||
meaning: 'Said to everyone on the server. `{killer}` is the player who landed the killing blow, empty when there was none',
|
||||
},
|
||||
'boss.phases': {
|
||||
site: 'Phases',
|
||||
rule: 'a list; each `at` a fraction between 0 and 1, no two alike',
|
||||
meaning:
|
||||
'When its health falls to a phase’s `at`, that phase applies from then on. Every other part of a phase is optional, and changes only that boss’s own copy of its profile',
|
||||
},
|
||||
'phases[].damageDealt': { site: 'Damage dealt ×', rule: '0 or more', meaning: 'Replaces the profile’s' },
|
||||
'phases[].aimCone': { site: 'Aim spread', rule: '0 or more', meaning: 'Replaces the profile’s' },
|
||||
'phases[].speed': { site: 'Speed ×', rule: 'above 0, at most 5', meaning: 'A multiple of Rust’s own speed for it: 1.3 is a third faster' },
|
||||
'phases[].ranges': { site: 'Notices at, Forgets at, Chases to, Shoots from', rule: 'as the profile’s `ranges`', meaning: 'Replace the profile’s' },
|
||||
'phases[].kit': {
|
||||
site: 'Swaps to kit',
|
||||
rule: 'a Kits kit the server has',
|
||||
meaning: 'Its inventory is stripped, the kit given and its weapon equipped',
|
||||
},
|
||||
'phases[].adds': {
|
||||
site: 'Summons (profile), How many',
|
||||
rule: '`profile`: one that is not a boss; `count`: 1 to 20',
|
||||
meaning:
|
||||
'NPCs of that profile placed on a 6 m ring around it, one per server frame. They belong to its owner, so an event’s teardown removes them, and they fight on after it dies',
|
||||
},
|
||||
'phases[].line': {
|
||||
site: 'Line',
|
||||
rule: 'at most 256 characters',
|
||||
meaning: 'Said only to the players near it (within `barDistance`) and to those who have hurt it',
|
||||
},
|
||||
},
|
||||
Loot: {
|
||||
loot: {
|
||||
site: 'Its own loot',
|
||||
rule: 'null, or the block below',
|
||||
meaning: 'Null keeps Rust’s scientist loot',
|
||||
},
|
||||
'loot.start': {
|
||||
site: 'The corpse starts with',
|
||||
rule: '`rust` (default), `kit` or `none`',
|
||||
meaning:
|
||||
'What the corpse holds before the table: Rust’s scientist loot and the clothes it wore; what it carried (its kit) and the clothes; or nothing at all, clothes included',
|
||||
},
|
||||
'loot.always': {
|
||||
site: 'Always rolled',
|
||||
rule: 'up to 30 rows: `item`, `min`, `max`, `chance` (above 0, at most 1), and an optional `skin`',
|
||||
meaning: 'Each row rolls on its own. `item` is a Rust short name — `scrap`, `rifle.ak`, `explosive.timed`',
|
||||
},
|
||||
'loot.pool': {
|
||||
site: 'The pool',
|
||||
rule: '`pick` (0 to its rows), `nothing` (a weight, 0 or more), and up to 30 `rows`, each with a `weight` above 0 instead of a chance',
|
||||
meaning:
|
||||
'At most `pick` draws by weight, never the same row twice; a draw of "nothing" uses one up. A kill never pays out more than `pick` pool rows, however lucky — the fair cap on rare items',
|
||||
},
|
||||
'loot.crate': {
|
||||
site: 'The table goes into',
|
||||
rule: 'null (the corpse), `wooden`, `military`, `elite` or `locked`',
|
||||
meaning:
|
||||
'The table goes into one of Rust’s crates beside the corpse, with Rust’s own loot off, and stays until emptied; a locked crate keeps Rust’s two-hour removal while unhacked. The always-rows plus `pick` must fit its slots: 6, 6, 12 or 24 (the corpse has 24)',
|
||||
},
|
||||
'loot.hackSeconds': {
|
||||
site: 'Hack time (minutes)',
|
||||
rule: '1 to 86,400; default 900',
|
||||
meaning: 'A locked crate’s hack time, counted once a player starts the hack',
|
||||
},
|
||||
'loot.corpseSeconds': {
|
||||
site: 'The corpse stays',
|
||||
rule: 'null (Rust’s own 600 s), 0 (no corpse), or more',
|
||||
meaning: 'How long the corpse lasts',
|
||||
},
|
||||
'loot.dropTable': {
|
||||
site: null,
|
||||
rule: 'true (default) or false',
|
||||
meaning:
|
||||
'False rolls no table; the start and the corpse’s time still apply. An event’s Place NPCs step sends it as false for the NPCs it places, through a spawn’s overrides',
|
||||
},
|
||||
},
|
||||
Sides: {
|
||||
faction: {
|
||||
site: 'Faction',
|
||||
rule: 'null, or 1–40 of a-z, 0-9, _ and -; not `scientists` or `animals`',
|
||||
meaning: 'Profiles of one faction are allies. With none, its allies are its own profile’s NPCs',
|
||||
},
|
||||
relations: {
|
||||
site: 'Its own exceptions',
|
||||
rule: 'keys: a faction, `scientists`, `animals` or `profile:<name>`; values `hostile`, `neutral` or `allied`',
|
||||
meaning:
|
||||
'Its own exceptions, which win over the faction table. **A profile with no faction settings fights players only**',
|
||||
},
|
||||
alertRadius: {
|
||||
site: 'Alert radius (m)',
|
||||
rule: '0 or more; default 40',
|
||||
meaning: 'Allies this close learn whoever attacked one of them, seen or not. 0 is off',
|
||||
},
|
||||
turrets: {
|
||||
site: 'Turrets',
|
||||
rule: '`default`, `ignore` or `always`',
|
||||
meaning:
|
||||
'`default`: as Rust’s scientists — player turrets shoot it, Outpost’s and Bandit Camp’s sentries do not. `ignore`: no turret targets it. `always`: those sentries too. It shoots back at a turret that shoots it',
|
||||
},
|
||||
hurtByPlayers: {
|
||||
site: 'Players can hurt it',
|
||||
rule: 'true or false; default true, but false for a passive profile unless it is given',
|
||||
meaning:
|
||||
'Off makes it unkillable by players. RunicNPC enforces it itself, and answers TruePVE’s and NextGenPVE’s damage checks the same way. Turrets, fire and other NPCs are not affected',
|
||||
},
|
||||
hurtsPlayers: {
|
||||
site: 'It can hurt players',
|
||||
rule: 'true or false; default true',
|
||||
meaning: 'Off makes it harmless to players. It still fights NPCs and turrets',
|
||||
},
|
||||
},
|
||||
'The kit’s extras': {
|
||||
kitUse: {
|
||||
site: 'The kit’s extras',
|
||||
rule: '`heal`, `grenades`, `melee`, `rockets`, `flamethrower`, each true or false',
|
||||
meaning:
|
||||
'The extras its kit gives that it uses, each only if true and each used up from its inventory, so its loot is what is left. The main weapon — the belt’s first slot — is always used and never runs out',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* The commands, keyed by how they are typed after `/rnpc ` or `rnpc.`. The first word is the
|
||||
* verb, which is what the check compares against the plugin's `[ConsoleCommand("rnpc.…")]`
|
||||
* list. `perm` is the permission it needs; `console` marks the verbs that answer only in a
|
||||
* console.
|
||||
*/
|
||||
export const runicnpcCommands = {
|
||||
Placements: {
|
||||
'place <profile> [options]': {
|
||||
perm: 'runicnpc.place',
|
||||
does: 'Places the profile’s NPCs where you are looking, and answers with the placement’s name and the cost warning.',
|
||||
},
|
||||
'here <profile> [options]': {
|
||||
perm: 'runicnpc.place',
|
||||
does:
|
||||
'The same, a metre in front of you and facing you. If something is in the way, or there is no ground or navmesh there, it stands where you do and the reply says so.',
|
||||
},
|
||||
'remove [placement]': {
|
||||
perm: 'runicnpc.place',
|
||||
does: 'Removes a placement and its NPCs: the one you name, or the one the NPC you are looking at belongs to.',
|
||||
},
|
||||
'rename <placement> <new name>': { perm: 'runicnpc.place', does: 'Renames a placement. Its live NPCs stay where they are.' },
|
||||
'near [radius]': {
|
||||
perm: 'runicnpc.place',
|
||||
does: 'The placements and live NPCs around you, with distances and any note — waiting, or off the navmesh.',
|
||||
},
|
||||
info: {
|
||||
perm: 'runicnpc.place',
|
||||
does: 'The NPC you are looking at: name, profile, placement or owner, health, state and target.',
|
||||
},
|
||||
profiles: { perm: 'runicnpc.place', does: 'The profiles this server has, and any it refuses, with the reason.' },
|
||||
'follow <placement> <player|me|off>': {
|
||||
perm: 'runicnpc.place',
|
||||
does:
|
||||
'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. For trying an escort out — it is kept in memory only, so a respawn or a reload ends it.',
|
||||
},
|
||||
'tp <placement>': { perm: 'runicnpc.admin', does: 'Teleports you to a placement.' },
|
||||
'respawn <placement|all>': { perm: 'runicnpc.admin', does: 'Respawns a placement’s NPCs now, or every placement’s.' },
|
||||
'clear <owner>': {
|
||||
perm: 'runicnpc.admin',
|
||||
does: 'Removes every NPC an owner has: a stuck event run (`run:42`), for example, or another plugin’s (`plugin:Name`).',
|
||||
},
|
||||
},
|
||||
Routes: {
|
||||
'path record <name>': { perm: 'runicnpc.place', does: 'Starts recording a route where you walk.' },
|
||||
'path point': {
|
||||
perm: 'runicnpc.place',
|
||||
does: 'Adds the spot you stand on. A point the previous one has no walkable path to is refused.',
|
||||
},
|
||||
'path undo': { perm: 'runicnpc.place', does: 'Drops the last point.' },
|
||||
'path save [loop|back]': {
|
||||
perm: 'runicnpc.place',
|
||||
does: 'Saves the route: `loop` walks back to the first point and round again, `back` walks it back and forth.',
|
||||
},
|
||||
'path cancel': { perm: 'runicnpc.place', does: 'Abandons the recording.' },
|
||||
'path list': { perm: 'runicnpc.place', does: 'The saved routes.' },
|
||||
'path delete <name>': {
|
||||
perm: 'runicnpc.place',
|
||||
does: 'Deletes a route. A placement that walks it waits until a route of that name exists again.',
|
||||
},
|
||||
},
|
||||
'At the console': {
|
||||
'profile list': { perm: 'runicnpc.admin', console: true, does: 'The same as `/rnpc profiles`.' },
|
||||
'profile show <name>': { perm: 'runicnpc.admin', console: true, does: 'A profile in full, as JSON, and why it is refused if it is.' },
|
||||
'profile create <name> [from=<profile>]': {
|
||||
perm: 'runicnpc.admin',
|
||||
console: true,
|
||||
does: 'A new profile, empty or copied from another.',
|
||||
},
|
||||
'profile set <name> <field> <value>': {
|
||||
perm: 'runicnpc.admin',
|
||||
console: true,
|
||||
does: 'Sets one field of a profile by its path — `health 200`, `ranges.sense 40`, `kits a,b`. See [Profile fields](/docs/rust/runicnpc/profile-fields/).',
|
||||
},
|
||||
'profile delete <name>': {
|
||||
perm: 'runicnpc.admin',
|
||||
console: true,
|
||||
does: 'Deletes a profile. Placements that use it wait until a profile of that name exists again.',
|
||||
},
|
||||
'faction list': { perm: 'runicnpc.admin', console: true, does: 'The faction table: which factions fight, ignore or help each other.' },
|
||||
'faction set <a> <b> hostile|neutral|allied': { perm: 'runicnpc.admin', console: true, does: 'Sets one pair, both ways.' },
|
||||
'faction clear <a> <b>': { perm: 'runicnpc.admin', console: true, does: 'Removes a pair.' },
|
||||
status: {
|
||||
perm: 'the server console, RCON, or an admin’s F1',
|
||||
console: true,
|
||||
does: 'The version and API, the hooks that have fired, the navmesh, NPC counts by owner, profiles, placements, routes, the caps and the cost warning.',
|
||||
},
|
||||
reload: { perm: 'the server console, RCON, or an admin’s F1', console: true, does: 'Reads profiles and routes again after a hand edit of their files.' },
|
||||
help: { perm: 'anyone', console: true, does: 'The verbs you may use, and nothing else — the same list as `/rnpc` alone in chat.' },
|
||||
},
|
||||
};
|
||||
|
||||
/** RunicNPC's two permissions. */
|
||||
export const runicnpcPermissions = {
|
||||
'runicnpc.place': 'Placing, removing, renaming and inspecting NPCs, recording routes, and `follow`',
|
||||
'runicnpc.admin': 'Everything `runicnpc.place` grants, plus teleporting, respawning, clearing an owner, and the console-only verbs',
|
||||
};
|
||||
|
||||
/** The options `place` and `here` take, as `key=value`, in any order. */
|
||||
export const runicnpcPlaceOptions = {
|
||||
count: { means: 'How many NPCs stand there', default: '1' },
|
||||
respawn: { means: 'Seconds after a death before the NPC comes back', default: '300' },
|
||||
mode: { means: '`each`: every NPC on its own timer. `group`: none until all are dead, then all at once', default: '`each`' },
|
||||
move: { means: '`wander`, `monument` or `route:<name>`', default: 'the profile’s' },
|
||||
radius: { means: 'How far a wanderer strays, in metres', default: 'the profile’s' },
|
||||
tether: { means: 'A ZoneManager zone the NPCs never leave. Needs ZoneManager', default: 'none' },
|
||||
at: { means: 'Server console only, which has no position: `at=x,y,z`', default: '—' },
|
||||
yaw: { means: 'Server console only: which way they face, in degrees', default: '—' },
|
||||
};
|
||||
|
||||
/** `config/RunicNPC.json` (Carbon: `configs/RunicNPC.json`), by JSON path. */
|
||||
export const runicnpcConfig = {
|
||||
'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 each server frame placements may spend spawning; 8 by default. One NPC takes about 5 ms to spawn, so a large placement is spread over frames instead of stalling one',
|
||||
};
|
||||
Reference in New Issue
Block a user