feat(rust): bosses and passive NPCs on the profile form, and public boss triggers (runicnpc stage 6)
All checks were successful
PR Checks / client-build (pull_request) Successful in 20s
PR Checks / server-tests (pull_request) Successful in 24s
PR Checks / frozen-manifest (pull_request) Successful in 46s

The profile form's boss box (D273-D277): the bar's distance, where its
lines are said, the spawn and death lines, and phases at health
percentages, each with optional damage, aim, speed, ranges, a kit swap,
adds of another profile and a line. The passive role (D278, D279,
D288): press E answers with a chat line or a window, it moves as its
profile says (now including `stand`), and it starts unhurtable.

The site checks both as RunicNPC does, in its words; a phase's kit is
checked on each server like the profile's own, and its adds must name a
site profile that is not a boss.

Public triggers rust.boss.spawned, rust.boss.phase and rust.boss.killed
(D277) from the bridge's new frames. In the feed, a boss appearing is
public; its death names the killer, so it sits behind the presence
setting; its phases are staff's, as the game tells them only to players
near it (D286). engagement-triggers.json 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-10-05 10:54:28 -05:00
parent 620ea63b54
commit f2ca314d10
11 changed files with 855 additions and 15 deletions

View File

@@ -472,7 +472,83 @@ const NPCS = [
},
]
const TRIGGERS = Object.freeze([RAID, ...BROADCASTS, ACCOUNT, REWARD, ...CLANS, ...MODERATION, ...NPCS])
// ── A RunicNPC boss (docs/runicnpc/PLAN.md stage 6, D277) ───────────────────
//
// Public, unlike the two above: a boss rising, changing phase and falling is
// server news, as a wipe is. `everyone` is the ceiling and `subscribers` the
// default, as for the broadcasts. The game also says these lines in chat; these
// are for the app, the in-app inbox and email. Discord hears a boss only through
// an event's own announce step (D277), so core does not change.
//
// No Steam id is declared, the killer's included: a public notice names the
// killer by their in-game name only.
const BOSS_VARS = [
{ name: 'profile', type: 'string', required: true, example: 'juggernaut',
description: 'The RunicNPC profile the boss was made from.' },
{ name: 'boss', type: 'string', required: true, example: 'Juggernaut',
description: 'The boss\'s own name, as players see it.' },
{ name: 'byEvent', type: 'boolean', required: true, example: true,
description: 'Whether an event placed it.' },
]
const BOSSES = [
{
id: 'rust.boss.spawned',
label: 'A boss appeared',
description: 'A RunicNPC boss spawned on a server: an admin\'s placement, an event, or another plugin.',
kind: 'event',
// One per kind of boss per cooldown, however often it respawns.
subjectKey: 'profile',
audience: 'subscribers',
ceiling: 'everyone',
version: V1,
variables: [...SERVER, ...HEADLINE, ...BOSS_VARS],
},
{
id: 'rust.boss.phase',
label: 'A boss changed phase',
description: 'A RunicNPC boss fell to one of its phases\' health thresholds and changed: stronger, faster, or calling for help.',
kind: 'event',
// A profile's phase: phase 2 is not swallowed by phase 1's cooldown.
subjectKey: 'phaseKey',
audience: 'subscribers',
ceiling: 'everyone',
version: V1,
variables: [
...SERVER,
...HEADLINE,
...BOSS_VARS,
{ name: 'phaseKey', type: 'string', required: true, example: 'juggernaut:2',
description: 'The profile and the phase. The cooldown subject; not meant for display.' },
{ name: 'phase', type: 'int', required: true, example: 2,
description: 'Which phase it entered, counting from 1.' },
{ name: 'percent', type: 'int', required: true, example: 25,
description: 'The health it fell to, as a percentage.' },
],
},
{
id: 'rust.boss.killed',
label: 'A boss was killed',
description: 'Players killed a RunicNPC boss on a server.',
kind: 'event',
subjectKey: 'profile',
audience: 'subscribers',
ceiling: 'everyone',
version: V1,
variables: [
...SERVER,
...HEADLINE,
...BOSS_VARS,
{ name: 'killer', type: 'string', required: false, example: 'Marisol',
description: 'The player who landed the killing blow, by in-game name. Absent when no player did.' },
{ name: 'damagers', type: 'int', required: true, example: 5,
description: 'How many players hurt it, the killer included.' },
],
},
]
const TRIGGERS = Object.freeze([RAID, ...BROADCASTS, ACCOUNT, REWARD, ...CLANS, ...MODERATION, ...NPCS, ...BOSSES])
const TRIGGER_IDS = Object.freeze(Object.fromEntries(TRIGGERS.map((t) => [t.id, t.id])))