feat(rust): Guarded Crate — feed, map, Warden, Place a guarded crate (PLAN_REDESIGNS §14.3.5)
Stage 6 of the §14.3 integration plan, on protocol 14. Spec: docs rust-link/PROTOCOL.md §20.9. - gc.started and gc.ended public, gc.failed staff's; guardedCrate.js reads each row per viewer (the clearer's name for the presence audience, Steam id, position and key for staff, `won` for everyone). - Triggers rust.gc.started and rust.gc.ended (a crate a player cleared only). - The step rust.gc.place: an event from rust.options.gc_events and a world call's location; refused at verify for Guarded Crate absent, NpcSpawn missing (D364), console unavailable, or an unknown event. Kept (D373); the run log's success line says so (D380). Budget rust.gc.crates. - Warden (D375): guarded_won, title category guardedwon, Crates cleared column. - Map marker, server settings line, swagger, engagement manifest, bundle, README. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
This commit is contained in:
@@ -187,6 +187,15 @@ const HEADLINES = Object.freeze({
|
||||
intro: `${d.player || 'Somebody'} won the treasure${d.grid ? ` at ${d.grid}` : ''} on ${d.server}`
|
||||
+ `${d.invaders > 1 ? `, with ${d.invaders} players fighting for it` : ''}.`,
|
||||
}),
|
||||
// Guarded Crate (protocol 14, §14.3.5). A missing grid drops out.
|
||||
'rust.gc.started': (d) => ({
|
||||
title: `A guarded crate landed on ${d.server}`,
|
||||
intro: `A guarded crate landed${d.grid ? ` at ${d.grid}` : ''} on ${d.server}. Kill its guards to claim it.`,
|
||||
}),
|
||||
'rust.gc.ended': (d) => ({
|
||||
title: `The guarded crate was cleared on ${d.server}`,
|
||||
intro: `${d.player} cleared the guarded crate${d.grid ? ` at ${d.grid}` : ''} on ${d.server}.`,
|
||||
}),
|
||||
'rust.boss.killed': (d) => ({
|
||||
title: `${d.boss} was killed on ${d.server}`,
|
||||
intro: `${d.killer ? `${d.killer} landed the killing blow` : `${d.boss} fell`}`
|
||||
@@ -605,9 +614,41 @@ async function onDangerousTreasures(server, item, frame) {
|
||||
}) ? 1 : 0
|
||||
}
|
||||
|
||||
/**
|
||||
* Guarded Crate (protocol 14, §14.3.5): a crate landed, and one cleared. As a
|
||||
* treasure's are: public news, a replay past a broadcast's window tells nobody
|
||||
* (D63), the clearer by in-game name only. An ending with nobody named, and a
|
||||
* timeout, raise nothing.
|
||||
*/
|
||||
async function onGuardedCrate(server, item, frame) {
|
||||
const t = frameTime(item, frame)
|
||||
if (!stillNews(t, BROADCAST_MAX_AGE_MS)) return 0
|
||||
if (frame.kind === 'gc.ended' && !str(frame.name)) return 0
|
||||
|
||||
const meta = frame.x !== undefined ? await mapDb.getMeta(server.id).catch(() => null) : null
|
||||
const grid = meta ? worldEvents.gridOf({ worldSize: Number(meta.worldSize), gridCells: Number(meta.gridCells), gridCellSize: Number(meta.gridCellSize) }, frame.x, frame.z) : null
|
||||
const data = { ...serverVars(server), subject: `${server.id}:${frame.kind}`, ...(grid ? { grid } : {}) }
|
||||
|
||||
if (frame.kind === 'gc.started') {
|
||||
return fire(T['rust.gc.started'], {
|
||||
data: { ...data, byEvent: Boolean(str(frame.key)) },
|
||||
dedupeKey: dedupeKey('gc.started', server.id, t, frame.x, frame.z),
|
||||
occurredAt: t,
|
||||
}) ? 1 : 0
|
||||
}
|
||||
|
||||
return fire(T['rust.gc.ended'], {
|
||||
data: { ...data, player: str(frame.name) },
|
||||
dedupeKey: dedupeKey('gc.ended', server.id, t, frame.steamId, frame.x, frame.z),
|
||||
occurredAt: t,
|
||||
}) ? 1 : 0
|
||||
}
|
||||
|
||||
const HANDLERS = Object.freeze({
|
||||
'dt.started': onDangerousTreasures,
|
||||
'dt.won': onDangerousTreasures,
|
||||
'gc.started': onGuardedCrate,
|
||||
'gc.ended': onGuardedCrate,
|
||||
'world.event': onWorld,
|
||||
'npc.boss.spawned': onBoss,
|
||||
'npc.boss.phase': onBoss,
|
||||
|
||||
@@ -669,7 +669,50 @@ const DANGEROUS_TREASURES = [
|
||||
},
|
||||
]
|
||||
|
||||
const TRIGGERS = Object.freeze([RAID, ...BROADCASTS, ACCOUNT, REWARD, ...CLANS, ...MODERATION, ...NPCS, ...BOSSES, ...WORLD, ...DANGEROUS_TREASURES])
|
||||
// ── Guarded Crate (protocol 14, PLAN_REDESIGNS §14.3.5, §14.3.9) ────────────
|
||||
//
|
||||
// A guarded crate landing and being cleared are triggers, as Dangerous
|
||||
// Treasures' are: public news, the server the cooldown subject. `player` is the
|
||||
// clearer's in-game name only. A crate whose guards went with nobody killing
|
||||
// the last (NpcSpawn reloaded under it) and one that timed out raise nothing:
|
||||
// neither is news.
|
||||
|
||||
const GC_BY_EVENT = { name: 'byEvent', type: 'boolean', required: true, example: false,
|
||||
description: 'Whether one of this site\'s events placed it (a Place a guarded crate step).' }
|
||||
|
||||
const GUARDED_CRATE = [
|
||||
{
|
||||
id: 'rust.gc.started',
|
||||
label: 'A guarded crate landed',
|
||||
description: 'Guarded Crate dropped a locked crate ringed by guards on a server: on its own schedule, by an admin, or by an event.',
|
||||
kind: 'event',
|
||||
subjectKey: 'subject',
|
||||
audience: 'subscribers',
|
||||
ceiling: 'everyone',
|
||||
version: V1,
|
||||
variables: [...SERVER, ...HEADLINE, { ...DT_SUBJECT, example: 'main:gc.started' }, WORLD_GRID, GC_BY_EVENT],
|
||||
},
|
||||
{
|
||||
id: 'rust.gc.ended',
|
||||
label: 'A guarded crate was cleared',
|
||||
description: 'A player killed the last guard of a guarded crate on a server.',
|
||||
kind: 'event',
|
||||
subjectKey: 'subject',
|
||||
audience: 'subscribers',
|
||||
ceiling: 'everyone',
|
||||
version: V1,
|
||||
variables: [
|
||||
...SERVER,
|
||||
...HEADLINE,
|
||||
{ ...DT_SUBJECT, example: 'main:gc.ended' },
|
||||
WORLD_GRID,
|
||||
{ name: 'player', type: 'string', required: true, example: 'Marisol',
|
||||
description: 'Who killed the last guard, by in-game name.' },
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
const TRIGGERS = Object.freeze([RAID, ...BROADCASTS, ACCOUNT, REWARD, ...CLANS, ...MODERATION, ...NPCS, ...BOSSES, ...WORLD, ...DANGEROUS_TREASURES, ...GUARDED_CRATE])
|
||||
|
||||
const TRIGGER_IDS = Object.freeze(Object.fromEntries(TRIGGERS.map((t) => [t.id, t.id])))
|
||||
|
||||
|
||||
Reference in New Issue
Block a user