feat(rust): NPC profile and placement routes, the event picker, triggers and titles (runicnpc stage 4)

Admin: /admin/rust/npcs for profiles (create, change, delete, restore a
replaced one, push now) and each server's placements (list, add from a map
point, change, remove, rename, respawn). Public: the profiles a leaderboard
ranks by, one profile's ranking counted as the profile says (D247, D250), and
one player's kills by profile (D252). Player: your own kills by profile.

The Place NPCs step offers the site's profiles first, then Rust's own
(D243). rust.npc.died and rust.npc.health are triggers a phase can wait on.
A title rule can rank a profile's kills. Swagger fragment, engagement and
route manifests regenerated.

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:34:43 -05:00
parent 564a234890
commit fa16f0ad2e
20 changed files with 3224 additions and 24 deletions

View File

@@ -1114,6 +1114,140 @@ module.exports = {
presets: { type: 'array', items: { $ref: '#/components/schemas/RustZonePreset' } },
},
},
RustNpcProfileBody: {
type: 'object',
description: 'A RunicNPC profile as RunicNPC reads it (docs/runicnpc/API.md, D238). Anything left out takes RunicNPC’s default.',
properties: {
names: { type: 'array', items: { type: 'string' }, example: ['Warden', 'Old Warden'] },
kits: { type: 'array', items: { type: 'string' }, example: ['warden_rifle'] },
prefab: { type: 'string', example: 'scientistnpc_roam' },
role: { type: 'string', enum: ['roamer', 'sentry'], example: 'roamer' },
movement: { type: 'object', example: { mode: 'wander', radius: 20 } },
health: { type: 'number', example: 250 },
damageDealt: { type: 'number', example: 1 },
damageTaken: { type: 'object', example: { head: 1, body: 1, legs: 1 } },
aimCone: { type: 'number', example: 2 },
ranges: { type: 'object', example: { sense: 30, loseTarget: 40, chase: 40, attack: 30 } },
visionCone: { type: 'number', example: -0.8 },
sleepDistance: { type: 'number', example: 160 },
healthThresholds: { type: 'array', items: { type: 'number' }, example: [0.5] },
},
},
RustNpcProfile: {
type: 'object',
description: 'A site NPC profile (docs/runicnpc/PLAN.md stage 4) for one server, several, or every server. `adoptedFrom` names the server it was read from on the site’s first push there (D244); `replaced` marks one kept aside because a site profile of its name was already on that server (D251). `killsScope` is how its kills are counted (D247).',
properties: {
id: { type: 'integer', example: 4 },
name: { type: 'string', example: 'warden' },
label: { type: 'string', example: 'Warden' },
body: { $ref: '#/components/schemas/RustNpcProfileBody' },
allServers: { type: 'boolean', example: false },
servers: { type: 'array', items: { type: 'string' }, example: ['main'] },
killsScope: { type: 'string', enum: ['server', 'name', 'profile'], example: 'server' },
adoptedFrom: { type: 'string', nullable: true, example: null },
replaced: { type: 'boolean', example: false },
updatedAt: { type: 'string', format: 'date-time', nullable: true },
},
},
RustNpcProfileInput: {
type: 'object',
required: ['name', 'body'],
description: 'A profile to save. `servers` is required unless `allServers` is true. The name is RunicNPC’s: 1 to 40 of a-z, 0-9, _ and -.',
properties: {
name: { type: 'string', example: 'warden' },
body: { $ref: '#/components/schemas/RustNpcProfileBody' },
allServers: { type: 'boolean', example: false },
servers: { type: 'array', items: { type: 'string' }, example: ['main'] },
killsScope: { type: 'string', enum: ['server', 'name', 'profile'], example: 'server' },
},
},
RustNpcs: {
type: 'object',
description: 'The NPC profiles page: each server with what it said about RunicNPC and what the last push did, every profile, the prefabs the form offers, the kill-counting scopes, and RunicNPC’s defaults.',
properties: {
servers: {
type: 'array',
items: {
type: 'object',
properties: {
id: { type: 'string', example: 'main' },
name: { type: 'string', example: 'Main · Vanilla' },
enabled: { type: 'boolean', example: true },
online: { type: 'boolean', nullable: true, example: true },
runicNpc: { type: 'object', nullable: true, example: { loaded: true, version: '0.2.0', api: 3 } },
ready: { type: 'boolean', example: true },
absence: { type: 'string', nullable: true, example: null },
sync: { type: 'object', nullable: true, example: { state: 'ok', adoptedAt: '2026-09-30T10:00:00.000Z', syncedAt: '2026-09-30T10:00:01.000Z', refused: {}, error: null } },
},
},
},
profiles: { type: 'array', items: { $ref: '#/components/schemas/RustNpcProfile' } },
prefabs: { type: 'array', items: { type: 'string' }, example: ['scientistnpc_roam'] },
killsScopes: { type: 'array', items: { type: 'string' }, example: ['server', 'name', 'profile'] },
defaults: { $ref: '#/components/schemas/RustNpcProfileBody' },
},
},
RustNpcPlacements: {
type: 'object',
description: 'One server’s placements, read live from it (D222), with the routes one may walk and the cost warning (D227).',
properties: {
placements: {
type: 'array',
items: {
type: 'object',
properties: {
id: { type: 'string', example: 'warden-2' },
placement: { type: 'object', example: { profile: 'warden', position: { x: 100, y: 12.5, z: -340 }, yaw: 90, count: 3, respawn: 300, respawnMode: 'each' } },
alive: { type: 'integer', example: 3 },
waiting: { type: 'string', nullable: true, example: null },
note: { type: 'string', nullable: true, example: null },
lastError: { type: 'string', nullable: true, example: null },
},
},
},
routes: { type: 'array', items: { type: 'string' }, example: ['gate'] },
cost: { type: 'string', nullable: true, example: 'RunicNPC: 12 NPC(s) on this server…' },
},
},
RustNpcPlacementInput: {
type: 'object',
required: ['profile'],
description: '`/rnpc place`’s options (D246). To create, `position` is the clicked point, x and z only; to change, a whole x, y, z moves it, and none keeps its spot.',
properties: {
profile: { type: 'string', example: 'warden' },
position: { type: 'object', example: { x: 100, z: -340 } },
count: { type: 'integer', example: 3 },
respawn: { type: 'number', example: 300 },
respawnMode: { type: 'string', enum: ['each', 'group'], example: 'each' },
movement: { type: 'object', nullable: true, example: { mode: 'route:gate', radius: 0 } },
},
},
RustNpcBoardProfiles: {
type: 'object',
properties: {
profiles: { type: 'array', items: { type: 'object' }, example: [{ id: 4, name: 'warden', label: 'Warden', killsScope: 'server' }] },
},
},
RustNpcLeaderboard: {
type: 'object',
properties: {
profile: { type: 'object', example: { id: 4, name: 'warden', label: 'Warden', killsScope: 'server' } },
leaderboard: { type: 'array', items: { type: 'object' }, example: [{ steamId: '76561198000000002', name: 'Marisol', kills: 12, titles: [] }] },
},
},
RustNpcPlayerKills: {
type: 'object',
properties: {
steamId: { type: 'string', example: '76561198000000002' },
kills: { type: 'array', items: { type: 'object' }, example: [{ profile: 'warden', label: 'Warden', kills: 3 }] },
},
},
RustNpcOwnKills: {
type: 'object',
properties: {
kills: { type: 'array', items: { type: 'object' }, example: [{ serverId: 'main', profile: 'warden', label: 'Warden', kills: 3 }] },
},
},
RustVisibilityUpdate: {
type: 'object',
description: 'A change to who may see who is online. Either part may be omitted; a server set to null follows the fleet default again.',