PRTS: 分支特性信息 吟游者 「特性为基于自身攻击力来增加受益者的“生命回复速度”属性」 (魔王, 浊心斯卡蒂); the 备注 of 调香师 熏衣草, 瑕光 S2, 铃兰 S3 and 锡人 S2 「生命恢复的提供方式为增加目标的“生命回复速度”属性…不受治疗加成和禁疗影响」 (铃兰: none in the first second, refreshed every second); 异常效果 禁疗 「增减生命回复速度…的效果不会被识别为治疗类能力」. These were heals from the provider, which damage.js refused on 收割者 / 不屈者 / 武者 (noHeal) and under 禁疗 — 隐德来希 gained nothing under 魔王. This is the separation proposed in PR #135 by @SpiritedAwayCN, written anew (the PR's patch is not used): - professions.js bardRegen: the trait is an hpRegen buff per bard refreshed every 0.25 s; 魔王's 微尘 ×1.5 goes through its `bardRegen` hook (both 魔王 kits); 浊心斯卡蒂's pulse and the 海嗣 token use it too. - 调香师 (every ally), 瑕光 S2 (the skill range, removed at its end), 铃兰 S3 (from the first full second, removed at its end), 锡人 (per 炼金单元, its cached ATK): hpRegen buffs instead of battle.heal. - damage.js: a `regen` tick takes no healing multiplier and a heal hook cannot change its amount (no 治疗加成 on any 生命回复速度); 流明's 凡人之愿 and 华法琳's S1 bonus skip a regen tick of their own. Direct heals are unchanged (still refused / scaled); 魔王 S3's redistribution and 纯烬's HoT are untouched. Other per-second recoveries without such a note stay heals (引星棘刺, 塞雷娅 S3, 流明 / 纯烬 HoTs; 余 闲云隐市 and 天马之盔 left open). Golden moved (every one fields 魔王 / 浊心斯卡蒂 / 调香师 / 瑕光 S2 / 铃兰 / 锡人): roster-004, roster-005, roster-006, roster-011, roster-013, roster-015, roster-016, roster-018, roster-023, roster-025, roster-030, roster-032, roster-035, roster-037, roster-039, roster-041, roster-042, roster-045, roster-046; bond-deputShip-high, bond-emptyShip-low, bond-emptyShip-high, bond-indomShip-high, bond-kazimierzShip-low, bond-kazimierzShip-high, bond-sargonShip-low, bond-sargonShip-high, bond-skillfulShip-high; boss-boss_1-pair, boss-boss_1-solo, boss-boss_2-solo, boss-boss_3-pair, boss-boss_3-solo, boss-boss_4-solo, boss-boss_5-pair, boss-boss_5-solo, boss-boss_6-solo, boss-boss_7-pair, boss-boss_7-solo, hidden-boss_8-solo, hidden-boss_9-pair, hidden-boss_9-solo, hidden-boss_10-solo, unite-1; solo-NORMAL-1, solo-HARD-1, solo-ABYSS-1, coop4-ABYSS-7, coop2-NORMAL-8-serverrun, coop2-NORMAL-14-standins. Docs: SIM.md (heal pipeline, bard row), PLAYING.md, kits/README.md (checklist item 11).
93 lines
5.8 KiB
JavaScript
93 lines
5.8 KiB
JavaScript
// test/content/feedback5-regen.test.js — 生命回复速度 is not 治疗 (GitHub #96 / #137). Effects PRTS describes as raising the
|
||
// target's 「生命回复速度」 attribute — the 吟游者 trait (分支特性信息 吟游者; 魔王, 浊心斯卡蒂), 调香师's 熏衣草, 瑕光 S2, 铃兰 S3 —
|
||
// are hpRegen buffs: 无法被友方治疗 (隐德来希 收割者, 折桠 不屈者) and 禁疗 do not stop them, 治疗加成 does not scale them
|
||
// (damage.js heal `regen`); a direct heal is still refused / scaled as before.
|
||
// Run: node --test test/content/feedback5-regen.test.js
|
||
|
||
import { test } from 'node:test';
|
||
import assert from 'node:assert/strict';
|
||
import { makeBattle, chessRec, checkInvariants } from '../helpers/battleHarness.js';
|
||
|
||
const REAPER = 'chess_char_5_06_a', UNYIELD = 'chess_char_2_17_a';
|
||
const DEFS = { chess: { t_plain: chessRec({ id: 't_plain', profession: 'TANK', skill: null, stats: { maxHp: 10000, atk: 0, def: 0 } }) } };
|
||
|
||
/** Provider on (10,4) facing right with 隐德来希 on (10,5) and 折桠 on (11,4) at half HP (both "无法被友方角色治疗"). */
|
||
function field(provider, extra = []) {
|
||
const h = makeBattle({
|
||
defs: DEFS, autoFinish: false, timeLimit: 120,
|
||
units: [{ row: 10, col: 4, ...provider }, { chessId: REAPER, row: 10, col: 5 }, { chessId: UNYIELD, row: 11, col: 4 }, ...extra],
|
||
});
|
||
h.step();
|
||
const p = h.unit(provider.chessId), r = h.unit(REAPER), z = h.unit(UNYIELD);
|
||
for (const x of [r, z]) x.hp = x.s.maxHp * 0.5;
|
||
return { h, p, r, z };
|
||
}
|
||
|
||
/** HP gained over `secs` s against the unit's own 生命回复速度 (at most ~1 HP pending in the regeneration tick). */
|
||
function regenOver(h, u, secs) {
|
||
const hp0 = u.hp, rate = u.s.hpRegen;
|
||
h.run(secs);
|
||
return { gained: u.hp - hp0, expected: rate * secs, rate };
|
||
}
|
||
|
||
const PROVIDERS = [
|
||
// [label, unit entry, prepare(h, p) → buff key of the provider's 生命回复速度]
|
||
['魔王 trait (吟游者)', { chessId: 'chess_char_4_25_a' }, (h, p) => `trait:bard:${p.id}`],
|
||
['浊心斯卡蒂 trait (吟游者, S2 loadout: on, trait raised)', { chessId: 'chess_char_6_04_a', skillIndex: 1, carryState: { sp: 999 } }, (h, p) => {
|
||
assert.ok(h.runUntil(() => p.skill.active, 5));
|
||
return `trait:bard:${p.id}`;
|
||
}],
|
||
['调香师 熏衣草', { chessId: 'chess_char_2_14_a' }, (h, p) => `flower:lavender:${p.id}`],
|
||
['瑕光 S2 慑敌辉光', { chessId: 'chess_char_3_12_a', skillIndex: 1 }, (h, p) => { assert.ok(p.skill.activate('test', { free: true })); return `blemsh:regen:${p.id}`; }],
|
||
['铃兰 S3 狐火渺然', { chessId: 'chess_char_5_10_a' }, (h, p) => { assert.ok(p.skill.activate('test', { free: true })); h.run(1.05); return `lisa:fox:${p.id}`; }],
|
||
];
|
||
|
||
test('隐德来希 (收割者) and 折桠 (不屈者) regenerate under 魔王 / 浊心斯卡蒂 / 调香师 / 瑕光 S2 / 铃兰 S3 — none of it is a heal; a direct heal from the same provider is still refused', () => {
|
||
for (const [label, entry, prepare] of PROVIDERS) {
|
||
const { h, p, r, z } = field(entry);
|
||
const key = prepare(h, p);
|
||
h.run(1.2); // (the providers' own talent auras settle their ATK first)
|
||
for (const x of [r, z]) {
|
||
const b = x.findBuff(key);
|
||
assert.ok(b && b.mods.hpRegen > 0, `${label}: ${x.defId} carries the 生命回复速度 buff`);
|
||
const { gained, expected } = regenOver(h, x, 2);
|
||
assert.ok(gained > 0 && Math.abs(gained - expected) <= 1.5, `${label}: ${x.defId} +${gained.toFixed(2)} ≈ ${expected.toFixed(2)}`);
|
||
assert.equal(h.b.heal(p, x, 100), 0, `${label}: a direct heal on ${x.defId} is still refused (无法被友方角色治疗)`);
|
||
}
|
||
assert.equal(h.hooksOf('heal').filter((c) => c.source === p && (c.target === r || c.target === z)).length, 0, `${label}: no heal of the provider reached them`);
|
||
checkInvariants(h.b);
|
||
assert.equal(h.b.errors.length, 0);
|
||
}
|
||
});
|
||
|
||
test('禁疗 does not stop 生命回复速度 (异常效果 禁疗 「增减生命回复速度…的效果不会被识别为治疗类能力」); it still stops heals, the unit\'s own included', () => {
|
||
const { h, p } = field({ chessId: 'chess_char_2_14_a' }, [{ chessId: 't_plain', row: 9, col: 4 }]);
|
||
const t = h.unit('t_plain');
|
||
h.b.addBuff(t, { key: 'test:healFree', status: 'healFree', flags: { noHeal: true, healFree: true } });
|
||
t.hp = 4000;
|
||
h.run(0.6);
|
||
const { gained, expected } = regenOver(h, t, 2);
|
||
assert.ok(gained > 0 && Math.abs(gained - expected) <= 1.5, `under 禁疗: +${gained} ≈ ${expected}`);
|
||
assert.equal(h.b.heal(p, t, 100), 0, 'a heal from her: refused');
|
||
assert.equal(h.b.heal(t, t, 100, { self: true }), 0, 'its own heal: 0 under 禁疗');
|
||
checkInvariants(h.b);
|
||
});
|
||
|
||
test('治疗加成 does not scale 生命回复速度: a healing-received multiplier and 百炼嘉维尔\'s 医学背景 (a heal hook) leave the regeneration as it is; a direct heal is scaled', () => {
|
||
const { h, p } = field({ chessId: 'chess_char_2_14_a' }, [{ chessId: 't_plain', row: 9, col: 4 }, { chessId: 'chess_char_4_23_a', row: 12, col: 4 }]);
|
||
const t = h.unit('t_plain'), gv = h.unit('chess_char_4_23_a');
|
||
h.b.addBuff(t, { key: 'test:healUp', mods: { healingTakenMul: 1.5 } });
|
||
t.hp = 4000;
|
||
gv.hp = gv.s.maxHp * 0.3; // 医学背景: healing received ×1.4 below half HP
|
||
h.run(0.6);
|
||
const v = p.s.atk * p.def.talents[0].bb.atk_to_hp_recovery_ratio;
|
||
for (const x of [t, gv]) {
|
||
assert.ok(Math.abs(x.s.hpRegen - x.base.hpRecoveryPerSec - v) < 1e-6, `${x.defId}: 生命回复速度 +ATK × ratio, unscaled`);
|
||
const { gained, expected } = regenOver(h, x, 2);
|
||
assert.ok(Math.abs(gained - expected) <= 1.5, `${x.defId}: +${gained} ≈ ${expected} (no ×1.5 / ×1.4)`);
|
||
}
|
||
assert.ok(Math.abs(h.b.heal(p, t, 100) - 150) < 1e-6, 'a heal: ×1.5 healing received');
|
||
assert.ok(h.b.heal(p, gv, 100) > 100 + 1e-6, 'a heal on 百炼嘉维尔: her 医学背景 applies');
|
||
checkInvariants(h.b);
|
||
});
|