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.
79 lines
4.6 KiB
JavaScript
79 lines
4.6 KiB
JavaScript
// server/sim/content/kits/ops/chess_char_5_21-sntlla.js — 寒檀 (char_341_sntlla) kit, tier 5.
|
||
// Conventions of the tier-5 kits: ../shared/tier5.js; kit contract and rules: ../README.md.
|
||
|
||
import { COLS } from '../../../constants.js';
|
||
import { toLocal } from '../../../dir.js';
|
||
import { num, talent, skillGrid, batPct, mods, lazySkills, whileOn } from '../shared/tier5.js';
|
||
|
||
/** 寒檀 S2 icicle splash radius (PRTS 备注 "冰凌溅射半径1.5"). */
|
||
const ICICLE_RADIUS = 1.5;
|
||
/** 寒檀 S2 icicle fall time (s) between the attack and the impact. [ASSUMED] */
|
||
const ICICLE_DELAY = 0.3;
|
||
/** 寒檀 S2 icicle lines, lateral offsets in her facing-RIGHT frame: ① the line on her left, ② on her right, ③ her own, then ①. */
|
||
const ICICLE_ROWS = [1, -1, 0];
|
||
|
||
export default {
|
||
// ---------------------------------------------------------------------------------------------------------------
|
||
// 寒檀 — S2 “女巫之泪” (15 s): skill range, BAT −2.4 s; attacks become icicles on random tiles of the range (cycling left
|
||
// row → right row → own row): 65/75 % ATK arts + 1 s cold to every enemy within 1.5 of the impact. T1 生于冰寒: after 20 s on the field ATK +15 % and 抵抗 (negative
|
||
// statuses on her last half as long).
|
||
chess_char_5_21_a: (bb, chess, def) => {
|
||
const t0 = talent(chess, 0);
|
||
const scale = num(bb['attack@atk_scale'], 1), cold = num(bb['attack@cold']);
|
||
const resist = Math.min(1, Math.max(0, -num(t0.one_minus_status_resistance))); // share of the duration removed
|
||
return {
|
||
// S1 迅捷打击·γ型 (duration): ATK +, ASPD +.
|
||
skills: lazySkills({
|
||
'skcom_quickattack[3]': () => ({ kind: 'duration', mods: mods({ atkPct: num(bb.atk), aspd: num(bb.attack_speed) }) }),
|
||
}),
|
||
skill: {
|
||
kind: 'duration',
|
||
mods: mods({ batPct: batPct(bb.base_attack_time, chess) }),
|
||
targeting: skillGrid(chess, def) ? { rangeGrid: skillGrid(chess, def) } : undefined,
|
||
attack: { noAttack: true },
|
||
onStart({ unit }) { unit.mem.iceCd = 0; unit.mem.iceRow = 0; },
|
||
onTick({ battle, unit, dt }) {
|
||
unit.mem.iceCd -= dt;
|
||
if (unit.mem.iceCd > 0 || !unit.canAct || unit.s.flags.disarm) return;
|
||
if (!battle.enemiesInKeys(unit.rangeKeys, unit, { canHitFly: true }).length) return;
|
||
unit.mem.iceCd = unit.s.interval;
|
||
// PRTS 备注: icicles fall on a random tile of ① the row on her left, ② the row on her right, ③ her own row, …
|
||
let k = null;
|
||
for (let i = 0; i < ICICLE_ROWS.length && k == null; i++) {
|
||
const idx = (num(unit.mem.iceRow) + i) % ICICLE_ROWS.length;
|
||
const lat = ICICLE_ROWS[idx];
|
||
const keys = (unit.rangeKeys || []).filter((x) => toLocal(Math.floor(x / COLS) - unit.tileR, x % COLS - unit.tileC, unit.dir)[0] === lat);
|
||
if (!keys.length) continue;
|
||
k = battle.rng.pick(keys);
|
||
unit.mem.iceRow = (idx + 1) % ICICLE_ROWS.length;
|
||
}
|
||
if (k == null) return;
|
||
const r = Math.floor(k / COLS), c = k % COLS;
|
||
unit.lastAttackAt = battle.time;
|
||
unit.stats.attacks++; // "攻击变为…召唤冰凌": every icicle is one of her attacks
|
||
battle.fx('iceSpike', { x: c, y: r, id: unit.id, r: ICICLE_RADIUS });
|
||
battle.after(ICICLE_DELAY, () => {
|
||
const hit = [];
|
||
for (const e of battle.foesInRadius(c, r, ICICLE_RADIUS)) {
|
||
battle.dealDamage(unit, e, { amount: unit.s.atk * scale, type: 'arts', isAttack: true, isSkill: true, isSplash: true, tags: ['skill', 'iceSpike'] });
|
||
if (e.alive && cold > 0) battle.applyStatus(e, 'cold', { duration: cold, source: unit });
|
||
if (e.alive) hit.push(e);
|
||
}
|
||
// an icicle is an attack: "攻击时" effects (items, bonds) see it with the enemies it hit
|
||
if (hit.length && battle.hasHook('attack')) battle.emit('attack', { attacker: unit, targets: hit, isSkill: true });
|
||
}, { owner: unit });
|
||
},
|
||
},
|
||
talents: [{ install(battle, unit) { // 生于冰寒
|
||
const after = num(t0.interval, 20);
|
||
// + 抵抗 (engine `resist` status: new control statuses last half as long, never compounding with other sources)
|
||
whileOn(battle, unit, 0.5, () => {
|
||
if (battle.time - unit.deployedAt < after - 1e-9 || unit.findBuff('sntlla:born')) return;
|
||
battle.addBuff(unit, { key: 'sntlla:born', visible: true, mods: mods({ atkPct: num(t0.atk) }) });
|
||
if (resist > 0) battle.applyStatus(unit, 'resist', { value: resist, source: unit });
|
||
});
|
||
} }],
|
||
};
|
||
},
|
||
};
|