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

@@ -167,6 +167,19 @@ const HEADLINES = Object.freeze({
title: `${d.npc || d.profile} is below ${d.percent}% on ${d.server}`,
intro: `${d.npc || 'An NPC'} (${d.profile}) fell to ${d.percent}% of its health on ${d.server}.`,
}),
'rust.boss.spawned': (d) => ({
title: `${d.boss} has appeared on ${d.server}`,
intro: `${d.boss} is up on ${d.server}.`,
}),
'rust.boss.phase': (d) => ({
title: `${d.boss} is down to ${d.percent}% on ${d.server}`,
intro: `${d.boss} fell to ${d.percent}% of its health on ${d.server} and entered phase ${d.phase}.`,
}),
'rust.boss.killed': (d) => ({
title: `${d.boss} was killed on ${d.server}`,
intro: `${d.killer ? `${d.killer} landed the killing blow` : `${d.boss} fell`}`
+ `${d.damagers > 1 ? `, with ${d.damagers} players fighting it` : ''}.`,
}),
'rust.login.denied': (d) => ({
title: `A login to ${d.server} was not approved`,
intro: `${d.player || 'Someone'} (${d.steamId}) tried to join ${d.server} and was not let in within a minute.`,
@@ -417,7 +430,40 @@ async function onNpc(server, item, frame) {
}) ? 1 : 0
}
/**
* A RunicNPC boss (runicnpc stage 6, D277): spawned, a phase, killed. Public
* news, so a replay older than a broadcast's window tells nobody (D63). The
* dedupe key is the boss's net id, with the phase for a phase.
*/
async function onBoss(server, item, frame) {
const t = frameTime(item, frame)
if (!stillNews(t, BROADCAST_MAX_AGE_MS) || !str(frame.profile)) return 0
const base = { ...serverVars(server), profile: str(frame.profile), boss: str(frame.name) || str(frame.profile), byEvent: Boolean(frame.runId) }
if (frame.kind === 'npc.boss.spawned') {
return fire(T['rust.boss.spawned'], { data: base, dedupeKey: dedupeKey('boss.spawned', server.id, frame.netId, t), occurredAt: t }) ? 1 : 0
}
if (frame.kind === 'npc.boss.phase') {
const phase = Number(frame.phase) || 0
return fire(T['rust.boss.phase'], {
data: { ...base, phaseKey: `${base.profile}:${phase}`, phase, percent: Math.round((Number(frame.at) || 0) * 100) },
dedupeKey: dedupeKey('boss.phase', server.id, frame.netId, phase),
occurredAt: t,
}) ? 1 : 0
}
return fire(T['rust.boss.killed'], {
data: { ...base, killer: str(frame.killerName), damagers: Number(frame.damagers) || 0 },
dedupeKey: dedupeKey('boss.killed', server.id, frame.netId, t),
occurredAt: t,
}) ? 1 : 0
}
const HANDLERS = Object.freeze({
'npc.boss.spawned': onBoss,
'npc.boss.phase': onBoss,
'npc.boss.died': onBoss,
'npc.died': onNpc,
'npc.health': onNpc,
'entity.destroyed': onRaid,