A knocked-out 灵巧 operator gives its neighbours the aura once more; a player retreat, a merchant exit or a 联防 forced exit no longer counts, and a redeploy restores it. 调和, 协防干员, 独行 and 绝技 hide their layer counts in the UI while the real layers stay for saving, settlement and support ranking. Contributed by @sunstricken.
96 lines
4.6 KiB
JavaScript
96 lines
4.6 KiB
JavaScript
import { test } from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import { DATA, makeMatch } from './harness.js';
|
|
import { GameData } from '../../server/match/gamedata.js';
|
|
import { bondSnapshot } from '../../server/match/bondsMeta.js';
|
|
import { buildBattleSpec, createBattleFromSpec, compactResult } from '../../server/sim/spec.js';
|
|
import { validateClientResult } from '../../server/match/fields.js';
|
|
import { helperStats, helperOrder } from '../../server/match/unite.js';
|
|
|
|
const IDS = ['maniShip', 'emptyShip', 'soloShip', 'suntShip'];
|
|
const gd = new GameData(DATA, 'mode_multi_normal');
|
|
const bonds = () => Object.fromEntries(IDS.map((id) => [id, { count: 2, active: true, tier: 1, layers: 20 }]));
|
|
|
|
test('hidden-layer bonds retain saved layers, prep gains, pending gains and battle input layers', () => {
|
|
const h = makeMatch({ mode: 'solo', humans: 1, fake: true }).start();
|
|
try {
|
|
h.toPrep(1);
|
|
const ps = h.ps('p_0');
|
|
for (const id of IDS) {
|
|
assert.equal(gd.bond(id).noStack, true, id);
|
|
ps.bondCountBonus[id] = gd.bond(id).thresholds[0];
|
|
ps.layers[id] = 50;
|
|
}
|
|
ps.bondCountBonus.deputShip = 2;
|
|
ps.recompute();
|
|
for (const id of IDS) {
|
|
assert.equal(ps.bonds[id].active, true);
|
|
assert.equal(ps.layers[id], 50, 'recompute must not erase saved layers');
|
|
assert.equal(ps.addLayers(id, 8, { requireActive: true }), 8);
|
|
assert.equal(ps.addLayers(id, 8, { requireActive: false }), 8);
|
|
}
|
|
h.m.dispatch(ps, 'onPrepEnd', { round: 1 });
|
|
for (const id of IDS) assert.equal(ps.layers[id], 68, '助力 prep-end traits can add hidden layers');
|
|
ps.pendingLayerGains = Object.fromEntries(IDS.map((id) => [id, 9]));
|
|
for (const id of IDS) {
|
|
assert.equal(ps.bondsView()[id].layers, 77);
|
|
assert.equal(bondSnapshot(ps.bonds)[id].layers, 68, 'battle input keeps settled layers');
|
|
}
|
|
} finally { h.m.dispose(); }
|
|
});
|
|
|
|
test('hidden-layer gains run through the simulator hooks, event stream and result', () => {
|
|
const spec = buildBattleSpec({ stageId: 'act2autochess_m01', timeLimit: 1, content: 'none',
|
|
players: [{ playerId: 'p_0', units: [], bonds: bonds() }], flags: { layerGainsEnabled: true } });
|
|
const b = createBattleFromSpec(spec, DATA, { quiet: true });
|
|
const calls = [];
|
|
b.on('layerGain', (c) => calls.push(c.bondId));
|
|
for (const id of IDS) {
|
|
assert.equal(b.getPlayer('p_0').bonds[id].layers, 20);
|
|
assert.equal(b.addLayers('p_0', id, 4), 4);
|
|
assert.equal(b.getPlayer('p_0').bonds[id].layers, 24);
|
|
}
|
|
assert.deepEqual(calls, IDS);
|
|
assert.deepEqual(b.drainEvents().filter((e) => e[0] === 'layer').map((e) => e[2]), IDS);
|
|
b.forceEnd();
|
|
assert.deepEqual(b.result().perPlayer.p_0.layerGains, Object.fromEntries(IDS.map((id) => [id, 4])));
|
|
});
|
|
|
|
test('client validation accepts legal hidden-layer gains and still rejects malformed JSON values', () => {
|
|
const spec = buildBattleSpec({ stageId: 'act2autochess_m01', timeLimit: 1, content: 'none',
|
|
players: [{ playerId: 'p_0', units: [], bonds: bonds() }], flags: { layerGainsEnabled: true } });
|
|
const b = createBattleFromSpec(spec, DATA, { quiet: true });
|
|
for (const id of IDS) b.addLayers('p_0', id, 4);
|
|
b.forceEnd();
|
|
const raw = compactResult(b.result());
|
|
assert.equal(validateClientResult(spec, raw, { gd }).ok, true);
|
|
for (const value of [-1, '4', null, {}, 1000]) {
|
|
const forged = structuredClone(b.result());
|
|
forged.perPlayer.p_0.layerGains.soloShip = value;
|
|
assert.equal(validateClientResult(spec, forged, { gd }).ok, false, JSON.stringify(value));
|
|
}
|
|
});
|
|
|
|
test('settlement retains hidden-layer gains from the server result', () => {
|
|
const h = makeMatch({ mode: 'solo', humans: 1, fake: true,
|
|
script: () => ({ layerGains: { p_0: { soloShip: 12, suntShip: 9, yanShip: 3 } } }) }).start();
|
|
try {
|
|
h.toPrep(1);
|
|
h.m.handle('p_0', { t: 'g.ready', ready: true });
|
|
h.toPrep(2);
|
|
assert.equal(h.ps('p_0').layers.soloShip, 12);
|
|
assert.equal(h.ps('p_0').layers.suntShip, 9);
|
|
assert.equal(h.ps('p_0').layers.yanShip, 3);
|
|
} finally { h.m.dispose(); }
|
|
});
|
|
|
|
test('hidden layers contribute to unite pair ordering while inactive layers do not', () => {
|
|
const player = (id, seat, layers) => ({ playerId: id, seat, deployCount: 1, board: new Map(),
|
|
layers: { soloShip: layers, suntShip: 999 }, bonds: {
|
|
soloShip: { active: true, layers }, suntShip: { active: false, layers: 999 },
|
|
} });
|
|
const a = player('p_0', 0, 5), b = player('p_1', 1, 8), m = { gd }, results = new Map();
|
|
assert.equal(helperStats(m, b, results).layers, 8);
|
|
assert.deepEqual(helperOrder(m, [a, b], results).map((p) => p.playerId), ['p_1', 'p_0']);
|
|
});
|