diff --git a/server/eventWorld.js b/server/eventWorld.js index 1a55c87..c598108 100644 --- a/server/eventWorld.js +++ b/server/eventWorld.js @@ -324,11 +324,19 @@ async function place(server, send, body, what) { } const placed = Array.isArray(data.placed) ? data.placed : [] + // A zone's deadline, as core's `until` (W1, PLAN_LAST_FIXES). The game erases + // the zone `holdMs` after it placed it; reckoned from the answer, on this + // clock, it lands a round trip after the game's, never before. With it core's + // reconcile files a zone gone past its deadline as expired instead of + // orphaned, which a reconnect used to show five seconds before the game's own + // `world.expired` arrived. Crates and NPCs have no deadline and send none. + const until = Number(body.holdMs) > 0 ? new Date(Date.now() + Number(body.holdMs)).toISOString() : null return { ok: true, resources: placed.map((row) => ({ kind: OWNED_KIND, ref: refOf(server.id, row.id), + ...(until ? { until } : {}), payload: { serverId: server.id, what: row.kind, diff --git a/server/test/world.test.js b/server/test/world.test.js index 5517033..84c431c 100644 --- a/server/test/world.test.js +++ b/server/test/world.test.js @@ -178,10 +178,13 @@ test('a zone crosses with its key, its duration, and the monument without the se server: 'main', body: { runId: '7', key: 'k1', monument: 'airfield_1', offsetX: 0, offsetZ: -20, radius: 40, holdMs: 5400000, name: 'Brawl' }, }) - // The ref carries the server: revert and reconcile are handed no params. - assert.deepStrictEqual(result.resources, [ - { kind: 'world', ref: 'main:rg-7-1-1', payload: { serverId: 'main', what: 'zone', name: 'Z' } }, - ]) + // The ref carries the server: revert and reconcile are handed no params. The + // zone's deadline rides as `until` (W1); zones.test.js checks its value. + const [resource] = result.resources + assert.strictEqual(typeof resource.until, 'string') + const { until, ...rest } = resource + assert.deepStrictEqual(rest, { kind: 'world', ref: 'main:rg-7-1-1', payload: { serverId: 'main', what: 'zone', name: 'Z' } }) + assert.strictEqual(result.resources.length, 1) }) test('one resource per thing placed, and a repeated key is said to be one', async (t) => { diff --git a/server/test/zones.test.js b/server/test/zones.test.js index c3e1c9c..e9d5298 100644 --- a/server/test/zones.test.js +++ b/server/test/zones.test.js @@ -232,6 +232,29 @@ test('a zone crosses with its flags, settings, messages in the chat voice, and i }) }) +test('a zone resource carries its deadline as until, a crate none (W1)', async (t) => { + // Core's reconcile reads `until`: a zone gone past it is expired, not + // orphaned. Reckoned from the answer, so it never falls before the game's own. + stubZones(t) + stubWorld(t) + const before = Date.now() + const result = await action('rust.zone.open').perform({ + runId: 7, idempotencyKey: 'k', params: { monument: 'main/a', radius: 40, minutes: 30 }, + }) + assert.strictEqual(result.ok, true, result.error) + const until = Date.parse(result.resources[0].until) + assert.ok(until >= before + 1800000 && until <= Date.now() + 1800000, result.resources[0].until) + + const saved = client.worldPlace + client.worldPlace = async () => ({ ok: true, status: 'ok', data: { kind: 'world.ok', placed: [{ id: 'rg-7-1-2', kind: 'crate' }] } }) + t.after(() => { client.worldPlace = saved }) + const crate = await action('rust.crate.place').perform({ + runId: 7, idempotencyKey: 'k2', params: { monument: 'main/a', prefab: 'crate.elite', count: 1 }, + }) + assert.strictEqual(crate.ok, true, crate.error) + assert.strictEqual('until' in crate.resources[0], false) +}) + test('a dome with its stack left blank stacks 3 spheres (D326)', async (t) => { stubZones(t, { servers: [server('main', { domes: PATCHED })] }) const calls = stubWorld(t)