Files
ark-stronghold-protocol/test/sim/feedback5-musha-heal.test.js
T
sganggs a7d62b5fc1 feedback5 WE1: 武者 trait heal on every damage instance, through 禁疗 (follow-up 1)
PRTS 分支特性信息 武者: "特性治疗于干员每次输出伤害时触发(不局限于攻击)", "通过自身特性/天赋/技能产生的作用于自身的治疗效果会无视
自身的禁疗" (utage_trait / helage_trait / zuole_trait: ON_OUTPUT_DAMAGE); PRTS 异常效果: an abnormal flag is one switch.

- professions.js installMushaHeal: one heal of the trait value per damage instance dealt to an enemy, at that instance
  (every hit of an attack, skill / item / bond damage); a dodged hit, a 流失, an element 损伤 or a talent / DoT damage heals
  nothing; ignoreHealFree. 收割者 keeps its rule (installReaperHeal) and ignores 禁疗 too (the same PRTS sentence).
- 赫拉格's kit: its second-hit heal patch is gone (redundant). 左乐's S1 reads his HP when it fires, before its strike's
  own heal [ASSUMED].

Golden (diy only): diy-007, diy-008, diy-009 (赫拉格: a multi-target attack heals 70 per target in separate heals instead
of one 70 × n heal — HP identical, heal hook / event counts move); diy-033, diy-034, diy-035 (左乐: heals land between
his S1 / S2 hits, so his HP-dependent T2 roll, 坚忍 and 庇护 read the healed HP from the second instance on). roster,
bonds, fields, matches, standins unchanged (宴 hits once per attack).

Tests: test/sim/feedback5-musha-heal.test.js (new, 4); op_helage, op_zuole, kits_t1t2, kits_t5, kits_alt_t5 pass.
Docs: SIM.md §Professions.
2026-10-06 15:32:30 +08:00

82 lines
5.0 KiB
JavaScript

// test/sim/feedback5-musha-heal.test.js — the 武者 trait heal on every damage instance (PRTS 分支特性信息 武者 "特性治疗于干员每次
// 输出伤害时触发(不局限于攻击)", "常态持有禁疗;通过自身特性/天赋/技能产生的作用于自身的治疗效果会无视自身的禁疗") —
// server/sim/professions.js installMushaHeal; 赫拉格's kit no longer patches his double strikes (op-helage.js), 左乐's S1 reads
// his HP when it fires (op-zuole.js).
// Run: node --test test/sim/feedback5-musha-heal.test.js
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { makeBattle, enemyRec, chessRec, checkInvariants } from '../helpers/battleHarness.js';
const VALUE = 50;
/** A 武者 (trait bb value 50) hitting `hits` times per attack; an enemy that dodges `dodge` of physical hits. */
function field({ hits = 1, dodge = 0, kit = null } = {}) {
const musha = chessRec({ id: 't_musha', subProfessionId: 'musha', stats: { maxHp: 1e6, atk: 100, bat: 1, def: 0 }, skill: null });
musha.trait = { desc: '', bb: { value: VALUE } };
const medic = chessRec({ id: 't_medic', profession: 'MEDIC', stats: { maxHp: 1000, atk: 300, bat: 1 }, skill: null, rangeGrid: [[0, 0], [0, -1], [0, -2]] });
const h = makeBattle({
defs: { chess: { t_musha: musha, t_medic: medic }, enemies: { e: { ...enemyRec({ key: 'e', hp: 1e9, speed: 0 }) } } },
kits: { t_musha: () => ({ trait: { hits, ...(kit?.trait ?? {}) }, ...(kit?.rest ?? {}) }) },
units: [{ chessId: 't_musha', row: 10, col: 5 }, { chessId: 't_medic', row: 10, col: 7 }], enemies: [{ key: 'e', pos: [10, 6] }],
content: 'generic', autoFinish: false, timeLimit: 60, hooks: ['heal', 'damaged', 'attack', 'dodge'], captureNoisy: true,
setup: (b) => { if (dodge) b.on('enemySpawn', ({ enemy }) => { b.addBuff(enemy, { key: 'test:dodge', mods: { dodgePhys: dodge } }); }); },
});
h.step();
const u = h.unit('t_musha');
u.hp = u.s.maxHp / 2;
return { h, u, e: h.enemies()[0] };
}
const traitHeals = (h, u) => h.hooksOf('heal').filter((c) => c.source === u && c.target === u && c.opts?.self && !c.opts?.tags);
const done = (h) => { checkInvariants(h.b); assert.equal(h.b.errors.length, 0, JSON.stringify(h.b.errors[0])); };
test('a double strike heals twice: one heal of the trait value per damage instance of the attack (it used to heal once per enemy struck)', () => {
const { h, u } = field({ hits: 2 });
h.run(3.05);
const atks = h.hooksOf('attack').filter((c) => c.attacker === u).length;
const hits = h.hooksOf('damaged').filter((c) => c.source === u && c.dmg.isAttack).length;
assert.ok(atks >= 3 && hits === 2 * atks, `${atks} attacks, ${hits} hits`);
assert.equal(traitHeals(h, u).length, hits, 'a heal per hit');
assert.ok(traitHeals(h, u).every((c) => c.amount === VALUE));
done(h);
});
test('every damage instance it outputs heals, not only attacks; a dodged hit, a 流失, an element 损伤 and a talent / DoT damage do not', () => {
const { h, u, e } = field({ dodge: 1 });
h.run(2.05);
assert.ok(h.hooksOf('dodge').some((c) => c.source === u), 'its hits were dodged');
assert.equal(traitHeals(h, u).length, 0, 'a dodged hit deals no damage: no heal');
const n0 = traitHeals(h, u).length;
h.b.dealDamage(u, e, { amount: 100, type: 'arts', isSkill: true, canDodge: false, tags: ['skill'] });
h.b.dealDamage(u, e, { amount: 100, type: 'true', canDodge: false, tags: ['item', 'item:test'] });
assert.equal(traitHeals(h, u).length - n0, 2, 'skill and item damage heal');
h.b.loseHp(e, 100, { source: u });
h.b.dealDamage(u, e, { type: 'element', element: 'burn', amount: 100 });
h.b.dealDamage(u, e, { amount: 100, type: 'arts', canDodge: false, tags: ['talent', 'dot'] });
assert.equal(traitHeals(h, u).length - n0, 2, 'no heal for a 流失, an element 损伤 or a DoT');
done(h);
});
test('禁疗 does not stop the trait heal ("会无视自身的禁疗"); another unit\'s heal still does not reach it', () => {
const { h, u } = field();
h.b.addBuff(u, { key: 'test:healFree', status: 'healFree', flags: { noHeal: true, healFree: true } });
const hp0 = u.hp;
h.run(1.05);
assert.ok(traitHeals(h, u).length >= 1 && u.hp > hp0, 'healed through 禁疗');
assert.equal(h.hooksOf('heal').filter((c) => c.target === u && c.source !== u).length, 0, 'the medic never heals it');
assert.equal(h.b.heal(h.unit('t_medic'), u, 500), 0);
done(h);
});
test('宴 (the chess): one heal per hit of her attacks', () => {
const h = makeBattle({
defs: { enemies: { e: enemyRec({ key: 'e', hp: 1e9, speed: 0 }) } }, units: [{ chessId: 'chess_char_1_18_a', row: 10, col: 5 }],
enemies: [{ key: 'e', pos: [10, 6] }], autoFinish: false, timeLimit: 60, hooks: ['heal', 'damaged'], captureNoisy: true, seed: 5,
});
h.run(8);
const u = h.unit('chess_char_1_18_a');
const hits = h.hooksOf('damaged').filter((c) => c.source === u && c.target.side === 'enemy' && c.type !== 'element' && !(c.dmg.tags || []).includes('dot')).length;
assert.ok(hits >= 3);
assert.equal(traitHeals(h, u).length, hits);
done(h);
});