- buffs.js makeBuff / damage.js absorbShields: `shieldType` may be a list of damage types — the shield absorbs those only (机械师's 屏障 absorb physical and arts damage, not true: buff_template_data mcnist_t_2 / mcnist_s_2_shield BlockDamage PHYSICAL_AND_MAGICAL); a single type and an untyped shield work as before. - damage.js heal / Battle.injuredAlliesInKeys: a healer profile's `healThrough(healer, ally)` names allies its heals reach through their 禁疗 (the `noHeal` flag of a summon's 禁疗, the `healFree` flag; a profile `noHeal` — 无法被友方治疗 — still stops it), and its heal selection takes them (凯尔希 on her Mon3tr: PRTS Mon3tr(凯尔希的召唤物) "持有禁疗(可被凯尔希… 无视)"; her charpack attack `_ignoreHealFree`); every other healer still skips them. - docs/SIM.md §3 / §4 / §8, professions.js profile field list. No content uses either yet: golden unchanged. Test: test/sim/feedback5-o16-engine.test.js (2).
73 lines
4.8 KiB
JavaScript
73 lines
4.8 KiB
JavaScript
// test/sim/feedback5-o16-engine.test.js — two additive engine rules the 0.2.0 自选 kits of 机械师 and 凯尔希 need:
|
|
// - a 屏障 limited to several damage types (buff `shieldType` as a list, damage.js absorbShields): 机械师's 屏障 absorb
|
|
// physical and arts damage only (buff_template_data mcnist_t_2 / mcnist_s_2_shield: BlockDamage PHYSICAL_AND_MAGICAL);
|
|
// - a healer that heals an ally through its 禁疗 (profile `healThrough(healer, ally)`, damage.js heal and
|
|
// Battle.injuredAlliesInKeys): 凯尔希 on her Mon3tr (PRTS Mon3tr(凯尔希的召唤物) "持有禁疗(可被凯尔希…无视)") — her heal
|
|
// selection takes it and her heal reaches it; any other healer still skips it, a profile's `noHeal` (无法被友方治疗)
|
|
// still stops her.
|
|
// Run: node --test test/sim/feedback5-o16-engine.test.js
|
|
|
|
import { test } from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import { makeBattle, enemyRec, chessRec, checkInvariants } from '../helpers/battleHarness.js';
|
|
|
|
const ENEMIES = { enemy_dummy: enemyRec({ key: 'enemy_dummy', hp: 1e9, speed: 0, mass: 0 }) };
|
|
const CHESS = {
|
|
test_guard_a: chessRec({ id: 'test_guard_a', profession: 'WARRIOR', stats: { maxHp: 10000, atk: 300, def: 0, res: 0 } }),
|
|
test_medic_a: chessRec({ id: 'test_medic_a', profession: 'MEDIC', subProf: 'physician', stats: { maxHp: 2000, atk: 100, def: 0, res: 0 }, rangeGrid: [[0, 0], [0, 1], [0, 2], [1, 0], [1, 1], [-1, 0], [-1, 1]] }),
|
|
};
|
|
|
|
test('a 屏障 of several types (shieldType [\'phys\', \'arts\']) absorbs those, not true damage; older shields first as ever', () => {
|
|
const h = makeBattle({ defs: { enemies: ENEMIES, chess: CHESS }, timeLimit: 600, autoFinish: false, seed: 3, units: [{ uid: 1, chessId: 'test_guard_a', row: 10, col: 5 }] });
|
|
h.step();
|
|
const u = h.unit(1);
|
|
h.b.addBuff(u, { key: 'test:pa', shield: 100, shieldType: ['phys', 'arts'], duration: 30 });
|
|
let hp = u.hp;
|
|
for (const type of ['true', 'elemental']) {
|
|
h.b.dealDamage(null, u, { amount: 10, type, canDodge: false });
|
|
hp -= 10;
|
|
assert.equal(u.hp, hp, `${type} passes it`);
|
|
}
|
|
assert.equal(u.findBuff('test:pa').shield, 100);
|
|
h.b.dealDamage(null, u, { amount: 30, type: 'phys', canDodge: false });
|
|
h.b.dealDamage(null, u, { amount: 30, type: 'arts', canDodge: false });
|
|
assert.deepEqual([u.hp, u.findBuff('test:pa').shield], [hp, 40], 'physical and arts absorbed');
|
|
h.b.dealDamage(null, u, { amount: 50, type: 'arts', canDodge: false });
|
|
assert.deepEqual([u.hp, u.findBuff('test:pa')], [hp - 10, null], 'spent: the rest reaches HP');
|
|
// a single type still works as before
|
|
h.b.addBuff(u, { key: 'test:a', shield: 20, shieldType: 'arts', duration: 30 });
|
|
hp = u.hp;
|
|
h.b.dealDamage(null, u, { amount: 10, type: 'phys', canDodge: false });
|
|
assert.equal(u.hp, hp - 10);
|
|
checkInvariants(h.b);
|
|
});
|
|
|
|
test('profile healThrough: the healer selects and heals the ally it names through its 禁疗 (noHeal / healFree); others do not, and a profile noHeal still stops it', () => {
|
|
const marked = new Set();
|
|
const kits = { test_medic_a: () => ({ trait: { healThrough: (healer, a) => marked.has(a) } }) };
|
|
const h = makeBattle({
|
|
defs: { enemies: ENEMIES, chess: CHESS }, timeLimit: 600, autoFinish: false, seed: 3, kits, hooks: ['heal'], captureNoisy: true,
|
|
units: [{ uid: 1, chessId: 'test_medic_a', row: 10, col: 3 }, { uid: 2, chessId: 'test_guard_a', row: 10, col: 4 }, { uid: 3, chessId: 'test_guard_a', row: 11, col: 4 }],
|
|
});
|
|
h.step();
|
|
const medic = h.unit(1), a = h.unit(2), b = h.unit(3);
|
|
h.b.addBuff(a, { key: 'test:healFree', flags: { noHeal: true }, persist: true });
|
|
h.b.addBuff(b, { key: 'test:healFree', flags: { noHeal: true }, persist: true });
|
|
a.hp = 1000; b.hp = 1000;
|
|
assert.deepEqual(h.b.injuredAlliesInKeys(medic.rangeKeys, medic), [], '禁疗: no heal target');
|
|
assert.equal(h.b.heal(medic, a, 100), 0, 'no heal');
|
|
marked.add(a);
|
|
assert.deepEqual(h.b.injuredAlliesInKeys(medic.rangeKeys, medic), [a], 'the named ally is a heal target');
|
|
assert.equal(h.b.heal(medic, a, 100), 100, 'and is healed');
|
|
assert.equal(h.b.heal(medic, b, 100), 0, 'the other 禁疗 ally is not');
|
|
assert.ok(h.runUntil(() => h.hooksOf('heal').some((c) => c.source === medic && c.target === a), 5), 'its attack heals it');
|
|
assert.ok(!h.hooksOf('heal').some((c) => c.source === medic && c.target === b), 'never the other');
|
|
// another unit's heal is still stopped; healFree too passes for the named one; a profile noHeal does not
|
|
assert.equal(h.b.heal(b, a, 100), 0, 'another healer: 禁疗 holds');
|
|
h.b.addBuff(a, { key: 'test:hf2', flags: { healFree: true }, persist: true });
|
|
assert.equal(h.b.heal(medic, a, 50), 50, 'healFree passes for it');
|
|
a.profile.noHeal = true;
|
|
assert.equal(h.b.heal(medic, a, 50), 0, 'a profile noHeal (无法被友方治疗) stops it');
|
|
checkInvariants(h.b);
|
|
});
|