- kits/ops/op-logos.js (中坚术师; CCR-Δ, CCR-Y), op-mantra.js (本源术师; PRI-X), op-veen.js (秘术师; MSC-Y), registered in
OPERATOR_KIT_FILES one line per file; numbers from data/backups.json, mechanics from the PRTS 备注 (cited in each header),
[ASSUMED] items marked there. Data triggers kept; 维伊 S1 (an AUTO self buff) fires at full SP (the owner's rule).
- server/sim/ai.js: enemyAttack emits `palsyTrigger { enemy, buff, keep }` when a 麻痹 stack interrupts an attack (PRTS 真言
备注 "触发麻痹时指的是敌人的攻击被麻痹打断时"); `keep` leaves the stack (真言 噤声限域). Additive: no listener, no change.
docs/SIM.md §3 / §5 rows; test/sim/palsy-trigger.test.js.
- Tests: test/content/op_logos.test.js, op_mantra.test.js, op_veen.test.js (every form, skill, talent, module, range,
trigger, targeting, damage type).
- Golden: only the diy family moved — diy-003 … diy-007 now field 逻各斯 / 真言 / 维伊 in every form × module × skill (60
configurations; diy-003 … 006 held the prototypes before), the prototype picks moved to diy-008 … 011 (same packing, new
ids); diy-001 / 002 (推进之王) and roster, bonds, fields, matches, standins byte-identical.
63 lines
2.8 KiB
JavaScript
63 lines
2.8 KiB
JavaScript
// test/sim/palsy-trigger.test.js — the engine hook `palsyTrigger` (server/sim/ai.js enemyAttack): fired when a 麻痹 stack
|
|
// interrupts an enemy's normal attack (PRTS 真言 备注 "触发麻痹时指的是敌人的攻击被麻痹打断时"), once per interrupt; a
|
|
// handler may keep the stack (`keep`) or kill the enemy. 真言's 噤声限域 (kits/ops/op-mantra.js) is its user.
|
|
// Run: node --test test/sim/palsy-trigger.test.js
|
|
|
|
import { test } from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import { makeBattle, chessRec, enemyRec, checkInvariants } from '../helpers/battleHarness.js';
|
|
|
|
const guard = chessRec({ id: 't_guard', profession: 'WARRIOR', stats: { atk: 0, blockCnt: 2, maxHp: 1e6 }, skill: null });
|
|
const shooter = enemyRec({ key: 'enemy_shooter', hp: 1e6, atk: 100, bat: 1, range: 3, speed: 0, mass: 0 });
|
|
|
|
function field(setup) {
|
|
const h = makeBattle({
|
|
defs: { chess: { t_guard: guard }, enemies: { enemy_shooter: shooter } },
|
|
units: [{ chessId: 't_guard', row: 10, col: 5 }], content: 'none', autoFinish: false, setup,
|
|
});
|
|
h.step();
|
|
return h;
|
|
}
|
|
|
|
test('palsyTrigger: one event per interrupted attack, before the stack is used; no event without 麻痹', () => {
|
|
const seen = [];
|
|
const h = field((b) => b.on('palsyTrigger', (c) => seen.push({ t: b.time, stacks: c.buff.stacks, enemy: c.enemy })));
|
|
const e = h.spawn('enemy_shooter', { pos: [10, 7] });
|
|
h.run(3.05);
|
|
assert.equal(seen.length, 0, 'no 麻痹, no event');
|
|
const a0 = e.stats.attacks;
|
|
h.b.applyStatus(e, 'palsy', { value: 2 });
|
|
h.run(2.05);
|
|
assert.deepEqual(seen.map((x) => [x.enemy, x.stacks]), [[e, 2], [e, 1]], 'each interrupt, the stack count before it is used');
|
|
assert.equal(e.stats.attacks, a0, 'both attacks interrupted');
|
|
assert.equal(e.findBuff('palsy'), null);
|
|
h.run(1.1);
|
|
assert.ok(e.stats.attacks > a0, 'attacks again');
|
|
checkInvariants(h.b);
|
|
});
|
|
|
|
test('palsyTrigger: `keep` leaves the stack; a handler that kills the enemy ends its turn', () => {
|
|
let keepNext = true;
|
|
const h = field((b) => b.on('palsyTrigger', (c) => { if (keepNext) { c.keep = true; keepNext = false; } }));
|
|
const e = h.spawn('enemy_shooter', { pos: [10, 7] });
|
|
h.step();
|
|
h.b.applyStatus(e, 'palsy', { value: 1 });
|
|
const a0 = e.stats.attacks;
|
|
h.run(1.1);
|
|
assert.equal(e.findBuff('palsy')?.stacks, 1, 'kept');
|
|
assert.equal(e.stats.attacks, a0, 'still interrupted');
|
|
h.run(1.05);
|
|
assert.equal(e.findBuff('palsy'), null, 'used by the next interrupt');
|
|
assert.equal(e.stats.attacks, a0);
|
|
// a lethal handler
|
|
const h2 = field((b) => b.on('palsyTrigger', (c) => b.kill(c.enemy, null)));
|
|
const e2 = h2.spawn('enemy_shooter', { pos: [10, 7] });
|
|
h2.step();
|
|
h2.b.applyStatus(e2, 'palsy', { value: 3 });
|
|
h2.run(1.2);
|
|
assert.ok(!e2.alive);
|
|
assert.equal(h2.b.errors.length, 0, JSON.stringify(h2.b.errors[0]));
|
|
checkInvariants(h.b);
|
|
checkInvariants(h2.b);
|
|
});
|