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
98 lines
3.8 KiB
JavaScript
98 lines
3.8 KiB
JavaScript
// ── Guarded Crate, with NpcSpawn (protocol 14, PLAN_REDESIGNS §14.3.5) ─────
|
|
//
|
|
// Bazz3l's Guarded Crate drops a locked crate from a cargo plane and rings it
|
|
// with NpcSpawn's scientists; whoever kills the last guard has cleared it. Its
|
|
// events reach this module as three kinds: `gc.started` (public), `gc.ended`
|
|
// (public) and `gc.failed` (staff's, the timeout). The crate cleared is
|
|
// *Warden*'s count, from the tally's `guardedWon` (D375). A run's step places
|
|
// one (`eventGuardedCrate.js`).
|
|
//
|
|
// ── Who may read what ──────────────────────────────────────────────────────
|
|
//
|
|
// As Dangerous Treasures' are (`dangerousTreasures.js`, D376): the event and its
|
|
// grid are public; the clearer's name shows only to the operator's presence
|
|
// audience, so with presence left at staff a signed-out visitor reads "The
|
|
// guarded crate was cleared · M4"; the Steam id, the position and a step's key
|
|
// are staff's. Whether a player cleared it at all is public (`won`), since an
|
|
// ending with nobody named (NpcSpawn reloaded under it) names nobody either.
|
|
|
|
const { gridOf } = require('./worldEvents')
|
|
|
|
const KINDS = Object.freeze(['gc.started', 'gc.ended', 'gc.failed'])
|
|
|
|
const STEAM_ID = /^\d{17}$/
|
|
|
|
function isKind(kind) {
|
|
return KINDS.includes(kind)
|
|
}
|
|
|
|
/**
|
|
* One `gc.*` frame for one viewer: `{ staff, presence, geometry }`. Passes the
|
|
* envelope, adds `grid`; an ending adds `won`, and `name` for the presence
|
|
* audience. Staff also get `x`, `z`, the step's `key` and the clearer's
|
|
* `steamId`. Any other frame is returned as is.
|
|
*/
|
|
function shapeFor(frame, { staff = false, presence = false, geometry = null } = {}) {
|
|
if (!frame || !isKind(frame.kind)) return frame
|
|
|
|
const out = {}
|
|
for (const key of ['kind', 'type', 't', 'serverId', 'wipeId']) {
|
|
if (frame[key] !== undefined) out[key] = frame[key]
|
|
}
|
|
|
|
const grid = gridOf(geometry, frame.x, frame.z)
|
|
if (grid) out.grid = grid
|
|
|
|
if (frame.kind === 'gc.ended') {
|
|
out.won = STEAM_ID.test(String(frame.steamId || ''))
|
|
if (out.won && (presence || staff) && typeof frame.name === 'string' && frame.name) out.name = frame.name
|
|
if (out.won && staff) out.steamId = String(frame.steamId)
|
|
}
|
|
|
|
if (staff) {
|
|
for (const key of ['x', 'z', 'key']) {
|
|
if (frame[key] !== undefined) out[key] = frame[key]
|
|
}
|
|
}
|
|
|
|
return out
|
|
}
|
|
|
|
/**
|
|
* A server's last report about Guarded Crate and its console, from
|
|
* `servers.db.gcReportFor`: `{ guardedCrate, consolePlugins }`, each null where
|
|
* the plugin never said (no state yet, or a plugin before protocol 14).
|
|
* `guardedCrate` is `{ loaded, version, npcSpawn: { loaded, version }, events }`;
|
|
* `npcSpawn` is null where it was not said, and `events` null where the
|
|
* plugin could not read Guarded Crate's data file.
|
|
*/
|
|
function reportOf(row) {
|
|
const parse = (value) => {
|
|
if (value === null || value === undefined) return null
|
|
if (typeof value !== 'string') return value
|
|
try {
|
|
return JSON.parse(value)
|
|
} catch {
|
|
return null
|
|
}
|
|
}
|
|
const integration = (body) =>
|
|
body && typeof body === 'object' ? { loaded: body.loaded === true, version: typeof body.version === 'string' ? body.version : null } : null
|
|
|
|
const gc = row ? parse(row.guardedCrate) : null
|
|
const probe = row ? parse(row.console) : null
|
|
return {
|
|
guardedCrate:
|
|
gc && typeof gc === 'object'
|
|
? {
|
|
...integration(gc),
|
|
npcSpawn: integration(gc.npcSpawn),
|
|
events: Array.isArray(gc.events) ? gc.events.filter((e) => typeof e === 'string' && e.trim()) : null,
|
|
}
|
|
: null,
|
|
consolePlugins: probe && typeof probe === 'object' && typeof probe.plugins === 'boolean' ? probe.plugins : null,
|
|
}
|
|
}
|
|
|
|
module.exports = { KINDS, isKind, shapeFor, reportOf }
|