Files
ark-stronghold-protocol/test/content/feedback3-siracusa-rolls.test.js
T

94 lines
5.8 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// test/content/feedback3-siracusa-rolls.test.js — 叙拉古 6's true-damage proc rolls on every 普通伤害 hit of a member
// (0.1.3; community report 「叙拉古盟约真伤概率数值没有正确递增成长」, GitHub #79 「咬了一整个技能也没出」). PRTS 盟约记录
// 叙拉古: "※仅在造成普通伤害时尝试…", "每次造成普通伤害时尝试触发"; PRTS 伤害分类: 普通伤害 = attack type NORMAL, the
// default (group damage too unless it is 溅射); 溅射 / 持续 (BUFF) / 附加 (ADDITION) are other types. With the battle rng
// forced to 0 every roll hits, so the procs count the hits that rolled (the PRD rule and the amounts are unchanged).
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { makeBattle, chessRec, enemyRec, checkInvariants } from '../helpers/battleHarness.js';
const L = 20;
const SIRA6 = { siracusaShip: { count: 6, active: true, tier: 2, layers: L } };
const dummy = (key) => enemyRec({ key, hp: 1e9, speed: 0, def: 0, res: 0 });
const ENEMIES = { e_dummy: dummy('e_dummy'), e_dummy2: dummy('e_dummy2') };
function forceRng(h, v) { const orig = h.b.rng; h.b.rng = Object.assign(() => v, orig); }
const isProc = (c) => !!(c.dmg && c.dmg.tags && c.dmg.tags.includes('bond:siracusa'));
const hasTag = (c, t) => !!(c.dmg && c.dmg.tags && c.dmg.tags.includes(t));
test('叙拉古 6: which damage rolls — attacks, skill hits, drone attacks yes; 溅射, 持续, 附加, item procs, element, 无来源 no', () => {
const h = makeBattle({
defs: { chess: { r0_a: chessRec({ id: 'r0_a', bonds: ['siracusaShip'], skill: null }) }, enemies: ENEMIES },
units: [{ chessId: 'r0_a', row: 10, col: 3 }], enemies: [{ key: 'e_dummy', pos: [12, 9] }], // out of her range
bonds: SIRA6, autoFinish: false, timeLimit: 300,
});
h.step(1);
const a = h.unit('r0_a'), e = h.enemies()[0];
assert.ok(a.s.flags.stealth, '隐匿 after the deployment');
forceRng(h, 0);
let procs = 0;
h.b.on('damaged', (c) => { if (isProc(c)) procs++; });
const rolls = (d) => { const n0 = procs; h.b.dealDamage(a, e, { amount: 1, ...d }); return procs - n0; };
const cases = [
['a normal attack', { type: 'phys', isAttack: true }, 1],
['a skill hit (剑雨-like)', { type: 'arts', isSkill: true, tags: ['skill'] }, 1],
['a drone attack (neither attack nor skill)', { type: 'arts', tags: ['droneAttack'] }, 1],
['a module / talent hit with no class', { type: 'arts', tags: ['talent'] }, 1],
['溅射', { type: 'phys', isAttack: true, isSplash: true }, 0],
['持续 (the S3 pulse, 缄默德克萨斯 S1)', { type: 'arts', isSkill: true, tags: ['skill', 'drone', 'dot'] }, 0],
['持续 (periodic)', { type: 'true', canDodge: false, tags: ['dot', 'periodic'] }, 0],
['附加 (拉普兰德 module, 忍冬 追凶)', { type: 'arts', tags: ['module', 'addition'] }, 0],
['an item proc', { type: 'true', canDodge: false, tags: ['item', 'item:pegasus'] }, 0],
['another bond\'s rider', { type: 'true', canDodge: false, tags: ['bond:kazimierz'] }, 0],
['element damage', { type: 'element', element: 'burn' }, 0],
['无来源 damage', { type: 'arts', canDodge: false, sourceless: true }, 0],
];
for (const [label, d, want] of cases) assert.equal(rolls(d), want, label);
checkInvariants(h.b);
});
test('叙拉古 6: 荒芜拉普兰德 S3 — every drone attack rolls, the per-second pulse (持续法术伤害) does not', () => {
const log = [];
const h = makeBattle({
defs: { enemies: ENEMIES }, seed: 7, timeLimit: 120, autoFinish: false, bonds: SIRA6,
units: [{ chessId: 'chess_char_6_18_a', row: 10, col: 3, carryState: { sp: 69 } }],
enemies: [{ key: 'e_dummy', pos: [12, 9] }],
setup(b) { b.on('damaged', (c) => log.push(c), { priority: -999 }); },
});
const u = h.unit('chess_char_6_18_a');
assert.ok(h.runUntil(() => u.skill.active, 10), 'S3 cast');
forceRng(h, 0);
const t0 = h.b.time, from = log.length;
h.run(10);
assert.ok(u.skill.active && u.s.flags.stealth, 'still in S3 and 隐匿');
const mine = log.slice(from).filter((c) => c.source === u);
const drones = mine.filter((c) => hasTag(c, 'droneAttack')).length;
const pulses = mine.filter((c) => hasTag(c, 'drone'));
const procs = mine.filter(isProc).length;
assert.ok(drones >= 5, `drone attacks ${drones}`);
assert.ok(pulses.length >= 5 && pulses.every((c) => hasTag(c, 'dot')), `pulses ${pulses.length}, 持续伤害`);
assert.equal(mine.filter((c) => c.dmg?.isAttack).length, 0, 'no attack of her own during S3');
assert.equal(procs, drones, `one roll per drone attack, none per pulse (t ${t0.toFixed(1)}–${h.b.time.toFixed(1)})`);
checkInvariants(h.b);
});
test('叙拉古 6: 德克萨斯 S2 剑雨 — both hits on every enemy around roll', () => {
const log = [];
const h = makeBattle({
defs: { enemies: ENEMIES }, seed: 3, timeLimit: 60, autoFinish: false, bonds: SIRA6,
units: [{ chessId: 'chess_char_1_08_a', row: 10, col: 3, carryState: { sp: 40 } }],
// inside the skill's diamond, outside her attack range (her tile and the one in front)
enemies: [{ key: 'e_dummy', pos: [10, 5] }, { key: 'e_dummy2', pos: [11, 4] }],
setup(b) { b.on('damaged', (c) => log.push({ c, t: b.time }), { priority: -999 }); },
});
forceRng(h, 0);
const u = h.unit('chess_char_1_08_a');
const rainHit = (x) => x.c.source === u && x.c.dmg?.isSkill && !isProc(x.c);
assert.ok(h.runUntil(() => log.some(rainHit), 5), '剑雨 cast');
const T = log.find(rainHit).t;
const mine = log.filter((x) => x.c.source === u && x.t === T).map((x) => x.c);
assert.equal(mine.filter((c) => c.dmg?.isSkill && !isProc(c)).length, 4, 'two hits on each of the two enemies');
assert.equal(mine.filter((c) => c.dmg?.isAttack).length, 0, 'no attack hit in that tick');
assert.equal(mine.filter(isProc).length, 4, 'every 剑雨 hit rolled (rng 0: each roll hits)');
checkInvariants(h.b);
});