// ── RaidableBases (protocol 14, PLAN_REDESIGNS §11.4) ─────────────────────── // // nivex's RaidableBases pastes a stocked, guarded building for players to raid. // Its bases reach this module as five kinds: `raid.started`, `raid.completed`, // `raid.purchased` and `raid.locked` (public), and `raid.ended` (staff's). A base // completed is *Warlord*'s count, from the tally's `raidsCompleted` (D202). A // run's step spawns one (`eventRaidableBases.js`). // // **Every name is the plugin's (D386).** Difficulty names and levels, profile // names and base names arrive as the installed plugin's own strings; nothing // here lists them, and an operator's own difficulty is shown as written. // // ── Who may read what ────────────────────────────────────────────────────── // // As a world event is read (`worldEvents.js`, D353, D376, D390): the base, its // difficulty, whether it is PvP, and its grid are public; the raiders' and a // buyer's names show only to the operator's presence audience, so with presence // left at staff a signed-out visitor reads "A Hard raid base was raided at G7"; // Steam ids, the position and a run's key are staff's. How // many raided it is a count, and names nobody. const { gridOf } = require('./worldEvents') const KINDS = Object.freeze(['raid.started', 'raid.completed', 'raid.ended', 'raid.purchased', 'raid.locked']) const STEAM_ID = /^\d{17}$/ function isKind(kind) { return KINDS.includes(kind) } /** * One `raid.*` frame for one viewer: `{ staff, presence, geometry }`. Passes * the envelope, `level`, `difficulty`, `base`, `pvp` and `despawnAt`, adds `grid`; a * completion adds `raiderCount`, and `names` (with the lock holder's * `ownerName`) for the presence audience; a purchase or a lock adds `name` for * the presence audience. Staff also get `x`, `z`, the run's `id`, * `runId` and `key`, and every Steam id. 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 level = Number(frame.level) if (Number.isInteger(level)) out.level = level if (typeof frame.difficulty === 'string' && frame.difficulty) out.difficulty = frame.difficulty if (typeof frame.pvp === 'boolean') out.pvp = frame.pvp if (Number.isFinite(Number(frame.despawnAt)) && Number(frame.despawnAt) > 0) out.despawnAt = Number(frame.despawnAt) // The base's file name is the operator's, as the difficulty is, and the map shows it too. if (typeof frame.base === 'string' && frame.base) out.base = frame.base const grid = gridOf(geometry, frame.x, frame.z) if (grid) out.grid = grid const named = presence || staff if (frame.kind === 'raid.completed') { const raiders = Array.isArray(frame.raiders) ? frame.raiders.filter((r) => r && typeof r === 'object') : [] const count = Number(frame.raiderCount) out.raiderCount = Number.isInteger(count) && count >= 0 ? count : raiders.length if (named) { out.names = raiders.map((r) => (typeof r.name === 'string' ? r.name : '')).filter(Boolean) if (typeof frame.ownerName === 'string' && frame.ownerName) out.ownerName = frame.ownerName } if (staff) { out.raiders = raiders .filter((r) => STEAM_ID.test(String(r.steamId || ''))) .map((r) => ({ steamId: String(r.steamId), ...(typeof r.name === 'string' ? { name: r.name } : {}) })) if (STEAM_ID.test(String(frame.ownerId || ''))) out.ownerId = String(frame.ownerId) } } if (frame.kind === 'raid.purchased' || frame.kind === 'raid.locked') { if (named && typeof frame.name === 'string' && frame.name) out.name = frame.name if (staff && STEAM_ID.test(String(frame.steamId || ''))) out.steamId = String(frame.steamId) } if (staff) { for (const key of ['x', 'z', 'id', 'runId', 'key', 'radius']) { if (frame[key] !== undefined) out[key] = frame[key] } } return out } /** A JSON column as an object, or null. */ function parse(value) { if (value === null || value === undefined) return null if (typeof value !== 'string') return value try { return JSON.parse(value) } catch { return null } } /** One profile as the helper reports it, or null when it is not one. */ function profileOf(p) { if (!p || typeof p !== 'object' || typeof p.name !== 'string' || !p.name) return null return { name: p.name, difficulty: typeof p.difficulty === 'string' ? p.difficulty : null, level: Number.isInteger(Number(p.level)) ? Number(p.level) : null, enabled: p.enabled === true, pvp: p.pvp === true, bases: Array.isArray(p.bases) ? p.bases.filter((b) => typeof b === 'string' && b) : [], seabed: p.seabed === true, float: p.float === true, } } /** * A server's last report about RaidableBases and its console, from * `servers.db.rbReportFor`: `{ raidableBases, consolePlugins }`, each null * where the plugin never said (no state yet, or a plugin before this build). * `raidableBases` is `{ loaded, version, premium, spawnCall, copyPaste, * difficulties, helper, gridReady, profiles }` — `helper.state` is `missing`, * `patched`, `unsupported` or `no-raidablebases`; `profiles` is null without a * patched helper, which is the only thing that can read them. */ function reportOf(row) { const rb = row ? parse(row.raidableBases) : null const probe = row ? parse(row.console) : null let raidableBases = null if (rb && typeof rb === 'object') { const helper = rb.helper && typeof rb.helper === 'object' ? rb.helper : {} raidableBases = { loaded: rb.loaded === true, version: typeof rb.version === 'string' ? rb.version : null, premium: rb.premium === true, spawnCall: rb.spawnCall === true, copyPaste: rb.copyPaste && typeof rb.copyPaste === 'object' ? { loaded: rb.copyPaste.loaded === true } : null, difficulties: Array.isArray(rb.difficulties) ? rb.difficulties .filter((d) => d && typeof d.name === 'string' && d.name && Number.isInteger(Number(d.level))) .map((d) => ({ name: d.name, level: Number(d.level) })) : [], helper: { state: typeof helper.state === 'string' ? helper.state : 'missing', version: typeof helper.version === 'string' ? helper.version : null, reason: typeof helper.reason === 'string' ? helper.reason : null, }, gridReady: typeof rb.gridReady === 'boolean' ? rb.gridReady : null, profiles: Array.isArray(rb.profiles) ? rb.profiles.map(profileOf).filter(Boolean) : null, } } return { raidableBases, consolePlugins: probe && typeof probe === 'object' && typeof probe.plugins === 'boolean' ? probe.plugins : null, } } /** Whether the report says the raids helper is installed and patched. */ function helperPatched(report) { const rb = report && report.raidableBases return Boolean(rb && rb.loaded && rb.helper && rb.helper.state === 'patched') } module.exports = { KINDS, isKind, shapeFor, reportOf, profileOf, helperPatched }