// ── A Place NPCs step's progress line (runicnpc stage 8, D304) ───────────── // // `rust.npc.place`'s `progress()` (MODULE_API 1.12.0). The decisions it carries: // // • a line per step: the profile's label, how many of its NPCs still stand by // the step's key, of how many it placed; a boss's adds are not counted // • a boss step is its boss's health as a percent, 0 once it is dead // • a reader who may not see the map's events layer gets nothing (the page // never says what the map would hide) // • anything that cannot be read is null, never a throw // // Stubbed at the model seams the function reads. const { test, beforeEach, afterEach } = require('node:test') const assert = require('node:assert/strict') const progress = require('../eventProgress') const eventWorld = require('../eventWorld') const mapModel = require('../model/map/map.model') const mapLive = require('../mapLive') const serversDb = require('../model/servers/servers.db') const npcs = require('../model/npcs/npcs.model') const { fakeCtx } = require('./_fakes') const saved = { access: mapModel.access, live: mapLive.live, getServer: serversDb.getServer, profileOn: npcs.profileOn, } let events let visible let profile beforeEach(() => { require('../core')._reset() require('../core').init(fakeCtx()) events = [] visible = true profile = { label: 'Bandit', boss: false } mapModel.access = async () => ({ layers: { events: { visible } } }) mapLive.live = async () => ({ ok: true, data: { events } }) serversDb.getServer = async (id) => ({ id, name: id, enabled: true }) npcs.profileOn = async () => profile }) afterEach(() => { mapModel.access = saved.access mapLive.live = saved.live serversDb.getServer = saved.getServer npcs.profileOn = saved.profileOn }) const placed = (n, serverId = 'main') => Array.from({ length: n }, (_, i) => ({ kind: 'world', ref: `${serverId}/${i}`, payload: { serverId, what: 'npc' } })) const envelope = (over = {}) => ({ runId: 41, idempotencyKey: 'k1', params: { prefab: 'profile:bandit', count: 8 }, resources: placed(8), viewer: { userId: null }, ...over, }) const npc = (over = {}) => ({ kind: 'npc', runId: '41', key: 'k1', x: 0, z: 0, ...over }) test('rust.npc.place declares progress, and the crate verb does not', () => { const byId = new Map(eventWorld.ACTIONS.map((a) => [a.id, a])) assert.equal(typeof byId.get('rust.npc.place').progress, 'function') assert.equal(byId.get('rust.crate.place').progress, undefined) }) test("a step's NPCs still standing, by its key, of how many it placed; adds and other steps are not counted", async () => { events = [ npc(), npc(), npc(), npc({ add: true }), npc({ key: 'k2' }), npc({ runId: '99' }), { kind: 'zone', runId: '41', key: 'k1', x: 0, z: 0 }, ] assert.deepStrictEqual(await progress.npcProgress(envelope()), { label: 'Bandit', left: 3, of: 8 }) }) test('all dead is 0 left, not nothing', async () => { assert.deepStrictEqual(await progress.npcProgress(envelope()), { label: 'Bandit', left: 0, of: 8 }) }) test("a boss step is its boss's health, and 0% once it is dead", async () => { profile = { label: 'The Juggernaut', boss: true } events = [npc({ boss: true, health: 0.616 }), npc({ add: true })] const env = envelope({ params: { prefab: 'profile:jugg', count: 1 }, resources: placed(1) }) assert.deepStrictEqual(await progress.npcProgress(env), { label: 'The Juggernaut', percent: 62 }) events = [] assert.deepStrictEqual(await progress.npcProgress(env), { label: 'The Juggernaut', percent: 0 }) }) test("Rust's own scientists are labelled as the step picker labels them", async () => { events = [npc()] const env = envelope({ params: { prefab: 'npc.scientist.heavy', count: 2 }, resources: placed(2) }) assert.deepStrictEqual(await progress.npcProgress(env), { label: 'Heavy scientist', left: 1, of: 2 }) }) test('a reader who may not see the events layer gets no line', async () => { events = [npc()] visible = false assert.equal(await progress.npcProgress(envelope()), null) }) test('the reader is passed to the map visibility check as the request it reads', async () => { let seen mapModel.access = async (req, serverId) => { seen = { req, serverId } return { layers: { events: { visible: true } } } } await progress.npcProgress(envelope({ viewer: { userId: 12 }, resources: placed(2, 'pve') })) assert.deepStrictEqual(seen, { req: { user: { id: 12 } }, serverId: 'pve' }) }) test('nothing placed, a game that does not answer, a server switched off, or a throw is null', async () => { assert.equal(await progress.npcProgress(envelope({ resources: [] })), null) mapLive.live = async () => ({ ok: false, status: 'http-503', data: null }) assert.equal(await progress.npcProgress(envelope()), null) mapLive.live = async () => ({ ok: true, data: { events: [] } }) serversDb.getServer = async (id) => ({ id, enabled: false }) assert.equal(await progress.npcProgress(envelope()), null) serversDb.getServer = async () => { throw new Error('pool closed') } assert.equal(await progress.npcProgress(envelope()), null) })