Pure move: every line of a kit, of a helper and of the tier headers is copied verbatim (only the headers' first
line names the new file); a helper used by one kit moved into that kit's file. Glue only: the generated imports,
`export default { … }` around each registry entry, `export { … }` lists in shared/tier2..6.js, and tier4's
`(bb, chess, def)` default-argument wrapper as shared/tier4.js `withDefaults` (applied in each tier-4 kit file).
kits/index.js loads every kit file with a guarded dynamic import (browser-safe, no directory listing); tierN.js
re-export the tier registry (TIER_KITS) and their old named exports (tier1 helpers + tinmanKit, tier6
PITHST_ELEMENTS). The merged registry, every tier default and named export have the same keys, order, frozenness
and function sources as before. test/docs-consistency.test.js reads the moved code at its new paths.
Golden: GOLDEN_FULL=1 node --test test/golden.test.js unchanged.
63 lines
3.7 KiB
JavaScript
63 lines
3.7 KiB
JavaScript
// server/sim/content/kits/ops/chess_char_4_09-mizuki.js — 水月 (char_437_mizuki) kit, tier 4.
|
||
// Conventions of the tier-4 kits: ../shared/tier4.js; kit contract and rules: ../README.md.
|
||
|
||
import {
|
||
AURA, num, tbb, moduleBb, grid, batFlat, enemiesOnRange, whileDeployed, pulse, toggleBuff, skillActive, isSel, alt,
|
||
instantKind, withDefaults,
|
||
} from '../shared/tier4.js';
|
||
|
||
export default withDefaults({
|
||
// ===== 水月 (stalker) S2 囚徒困境 — faster, +ATK, talent 1 +1 target & bind; talents 创伤性癔症 / 反移情; module slow
|
||
// S1 唤醒 (charges: next attack 200 %/230 %, talent 1 ×2.0/×2.3); S3 镜花水月 (wider range, ATK up, talent 1 +2
|
||
// targets & stun; an attack hitting < 3 enemies costs 15 % max HP); modules: AMB-Y 65 % dodge (trait, profession)
|
||
chess_char_4_09_a: (bb, chess, def) => {
|
||
const t0 = tbb(def, 0), t1 = tbb(def, 1), mb = moduleBb(def);
|
||
const S1 = isSel(def, 'skchr_mizuki_1'), S2 = isSel(def, 'skchr_mizuki_2'), S3 = isSel(def, 'skchr_mizuki_3');
|
||
const g = grid(def.skill?.rangeGrid);
|
||
return {
|
||
skills: alt(def, {
|
||
skchr_mizuki_1: () => ({ kind: instantKind(def), attack: { atkScale: num(bb.atk_scale, 2) } }),
|
||
skchr_mizuki_3: () => ({
|
||
kind: 'duration',
|
||
mods: { atkPct: num(bb.atk) },
|
||
targeting: g ? { rangeGrid: g } : undefined,
|
||
onStart({ battle, unit }) { battle.fx('ripple', { x: unit.x, y: unit.y, id: unit.id }); },
|
||
onAttack({ battle, unit, targets }) {
|
||
if ((targets || []).filter((e) => e.side === 'enemy').length >= 3) return;
|
||
battle.loseHp(unit, unit.s.maxHp * num(bb['attack@hp_ratio'], 0.15), { tags: ['skill', 'selfLoss'] }); // (no source: not damage dealt)
|
||
},
|
||
}),
|
||
}),
|
||
skill: { kind: 'duration', mods: { batPct: batFlat(def, bb.base_attack_time), atkPct: num(bb.atk) },
|
||
onStart({ battle, unit }) { battle.fx('dilemma', { x: unit.x, y: unit.y, id: unit.id }); } },
|
||
talents: [
|
||
{ install(battle, unit) { // 创伤性癔症: +50 % ATK arts to the lowest-HP enemy hit (S2 +1 target & bind, S3 +2 & stun, S1 ×2)
|
||
battle.on('attack', (c) => {
|
||
if (c.attacker !== unit || !unit.alive) return;
|
||
const act = skillActive(unit) && (S2 || S3);
|
||
const n = Math.max(1, Math.floor(num(t0['attack@max_target'], 1))) + (act ? Math.floor(num(bb['attack@max_target'], 1)) : 0);
|
||
const mul = S1 && c.isSkill ? num(bb.talent_scale, 2) : 1;
|
||
const list = c.targets.filter((e) => e.alive && e.side === 'enemy').sort((a, b) => a.hp - b.hp || a.id - b.id).slice(0, n);
|
||
for (const e of list) {
|
||
battle.dealDamage(unit, e, { amount: unit.s.atk * num(t0['attack@mizuki_t_1.atk_scale'], 0.5) * mul, type: 'arts', tags: ['talent'] });
|
||
if (!act || !e.alive) continue;
|
||
if (S2) battle.applyStatus(e, 'bind', { duration: num(bb['attack@unmovable'], 0.8), source: unit });
|
||
else battle.applyStatus(e, 'stun', { duration: num(bb['attack@stun'], 0.7), source: unit });
|
||
}
|
||
}, { owner: unit });
|
||
} },
|
||
{ install(battle, unit) { // 反移情: ATK +10 % while an enemy in range is below 50 % HP
|
||
whileDeployed(battle, unit, 0.1, () => {
|
||
const on = enemiesOnRange(battle, unit).some((e) => e.hpRatio < num(t1.hp_ratio, 0.5));
|
||
toggleBuff(battle, unit, 'mizuki:t2', on, { atkPct: num(t1.atk, 0.1) });
|
||
});
|
||
} },
|
||
],
|
||
install(battle, unit) {
|
||
const ms = num(mb.move_speed, 0);
|
||
if (ms) whileDeployed(battle, unit, AURA, () => { for (const e of enemiesOnRange(battle, unit)) pulse(battle, e, `mizuki:slow:${unit.id}`, { moveMul: Math.max(0, 1 + ms) }); });
|
||
},
|
||
};
|
||
},
|
||
});
|