gamedata_const ba.protect / PRTS 术语 庇护 "受到的物理和法术伤害降低相应比例(同名效果取最高)": the strongest source holds; the
自选 kits already used battle.applyStrongest('protect'), these six multiplied private cuts with it.
- kits/shared/tier1.js: PROTECT, protectMods, PROTECT_TICK_HOLD and holdProtect (applyStrongest on the shared key).
- 宴 / 赫拉格 / 左乐 (below their HP threshold): refreshed every tick and at each hit on them; 余 (blocking) and 缪尔赛思's
melee copies at their old cadence; 赫德雷's 庇护 joins the key, his CRU-X 物理伤害 + becomes its own instance. 左乐 keeps the
PRTS S2 ordering (his own 庇护 newer than his barrier cuts behind it); 行险 drops his own held 庇护 for its 0.1 s
re-judgement [ASSUMED: another source's returns with its next refresh]. 遥's bubbles stay separate (the client's
damage_resistance[bonus] multiplies with the common damage_resistance[inf]).
Golden: no scenario moved — no unit of the corpus holds two 庇护 at once, and one source alone cuts exactly as before.
Tests: test/content/feedback5-protect.test.js (new, 2); kits_t6 (余), kits_alt_t6 (缪尔赛思), op_hodrer updated to the
shared key; kits_t1t2, op_helage, op_zuole pass. Docs: SIM.md §3, kits/README.md checklist 9.
82 lines
4.7 KiB
JavaScript
82 lines
4.7 KiB
JavaScript
// test/content/feedback5-protect.test.js — 庇护 is one effect per unit whoever grants it (gamedata_const ba.protect / PRTS 术语
|
||
// 庇护 "受到的物理和法术伤害降低相应比例(同名效果取最高)"): the older kits — 宴, 余, 缪尔赛思 (chess), 赫拉格, 左乐, 赫德雷 (自选) —
|
||
// hold it through the shared key the other 自选 kits use (kits/shared/tier1.js PROTECT / holdProtect → battle.applyStrongest),
|
||
// so the strongest value applies instead of a product of every source. 遥's bubbles keep their own key (the client's
|
||
// damage_resistance[bonus], which multiplies with the common damage_resistance[inf]).
|
||
// Run: node --test test/content/feedback5-protect.test.js
|
||
|
||
import { test } from 'node:test';
|
||
import assert from 'node:assert/strict';
|
||
import { readFileSync } from 'node:fs';
|
||
import { makeBattle, enemyRec, checkInvariants } from '../helpers/battleHarness.js';
|
||
import { PROTECT, holdProtect } from '../../server/sim/content/kits/shared/tier1.js';
|
||
import { getDefaultSource } from '../../server/sim/simdata.js';
|
||
|
||
const ds = getDefaultSource();
|
||
const approx = (a, b, msg, eps = 1e-6) => assert.ok(Math.abs(a - b) <= eps * Math.max(1, Math.abs(b)), `${msg}: ${a} vs ${b}`);
|
||
const done = (h) => { checkInvariants(h.b); assert.equal(h.b.errors.length, 0, JSON.stringify(h.b.errors[0])); };
|
||
|
||
/** 宴 (elite: 庇护 below hp_ratio) at 20 % HP with nothing to hit her. */
|
||
function utage() {
|
||
const h = makeBattle({ units: [{ chessId: 'chess_char_1_18_b', row: 9, col: 5 }], autoFinish: false, timeLimit: 60, seed: 3 });
|
||
h.step();
|
||
const u = h.unit('chess_char_1_18_b');
|
||
u.hp = u.s.maxHp * 0.2;
|
||
h.step();
|
||
return { h, u, dr: ds.rawChess('chess_char_1_18_b').talents.filter((t) => t.index !== -1)[0].bb.damage_resistance };
|
||
}
|
||
/** Physical damage she takes from a 1000-point hit (her DEF taken off). */
|
||
const physTaken = (h, u) => { const hp = u.hp; h.b.dealDamage(null, u, { amount: 1000, type: 'phys', canDodge: false }); const d = hp - u.hp; u.hp = hp; return d; };
|
||
|
||
test('宴 below her threshold holds the shared 庇护; a stronger one from another source replaces it (no product), a weaker one waits under it', () => {
|
||
const { h, u, dr } = utage();
|
||
assert.ok(dr > 0 && dr < 0.5);
|
||
const b = u.findBuff(PROTECT);
|
||
assert.ok(b && b.source === u && b.data.value === dr, 'her own 庇护, on the shared key');
|
||
const base = 1000 - u.s.def;
|
||
approx(physTaken(h, u), base * (1 - dr), 'hers alone');
|
||
// a stronger 庇护 from another source: the highest counts
|
||
holdProtect(h.b, u, 0.5, 10, { id: 'other' });
|
||
h.step();
|
||
approx(physTaken(h, u), base * (1 - 0.5), 'the stronger one only — not ×(1 − dr) as well');
|
||
assert.equal(u.buffs.filter((x) => x.key === PROTECT).length, 1, 'one 庇护 effect');
|
||
done(h);
|
||
// a weaker one: hers holds
|
||
const w = utage();
|
||
holdProtect(w.h.b, w.u, 0.1, 10, { id: 'other' });
|
||
w.h.step();
|
||
approx(physTaken(w.h, w.u), (1000 - w.u.s.def) * (1 - w.dr), 'hers — the weaker 庇护 waits');
|
||
// back above the threshold her own lapses within a tick; the weaker one resumes for its remaining time
|
||
w.u.hp = w.u.s.maxHp;
|
||
w.h.step(3);
|
||
const after = w.u.findBuff(PROTECT);
|
||
assert.ok(after && after.data.value === 0.1, 'the weaker 庇护 resumed');
|
||
done(w.h);
|
||
});
|
||
|
||
test('余 (blocking), 缪尔赛思\'s melee copies, 赫拉格 / 左乐 (SBL-X, below 50 %) and 赫德雷 (余火之氅) all hold 庇护 on the shared key', () => {
|
||
const src = (f) => readFileSync(new URL(`../../server/sim/content/kits/ops/${f}`, import.meta.url), 'utf8');
|
||
for (const f of ['chess_char_1_18-utage.js', 'chess_char_6_03-yu.js', 'chess_char_6_11-mlyss.js', 'op-helage.js', 'op-zuole.js', 'op-hodrer.js']) {
|
||
assert.match(src(f), /holdProtect\(/, `${f} uses the shared 庇护`);
|
||
assert.doesNotMatch(src(f), /physTakenMul: 1 - dr|dmg\.mul \*= 1 - (dr|protectCut|num\(t\.damage_resistance\))/, `${f}: no private 庇护 multiplier left`);
|
||
}
|
||
// 遥's bubble stays its own effect (damage_resistance[bonus])
|
||
assert.doesNotMatch(src('op-haruka.js'), /holdProtect\(/);
|
||
// 赫拉格 (SBL-X, tier 5) at 40 % HP: his 庇护 on the shared key
|
||
const h = makeBattle({
|
||
defs: { enemies: { e: enemyRec({ key: 'e', hp: 1e9, speed: 0 }) } },
|
||
units: [{ diy: { slot: 5, charId: 'char_188_helage', skillIndex: 0, uniEquipId: 'uniequip_002_helage' }, elite: true, row: 10, col: 5 }],
|
||
autoFinish: false, timeLimit: 30, seed: 3,
|
||
});
|
||
h.step();
|
||
const u = h.b.allyUnits.find((x) => x.def?.charId === 'char_188_helage');
|
||
u.hp = u.s.maxHp * 0.4;
|
||
h.step();
|
||
const b = u.findBuff(PROTECT);
|
||
assert.ok(b && b.source === u && b.data.value === 0.25, 'his SBL-X 庇护');
|
||
u.hp = u.s.maxHp;
|
||
h.step(3);
|
||
assert.equal(u.findBuff(PROTECT), null, 'gone above 50 %');
|
||
done(h);
|
||
});
|