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.
72 lines
3.9 KiB
JavaScript
72 lines
3.9 KiB
JavaScript
// server/sim/content/kits/ops/chess_char_1_17-indigo.js — 深靛 (char_469_indigo) kit, tier 1.
|
||
// Conventions of the tier-1 kits: ../shared/tier1.js; kit contract and rules: ../README.md.
|
||
|
||
import { sortEnemyTargets } from '../../../targeting.js';
|
||
import { num, talentBb, skillRec, batMod } from '../shared/tier1.js';
|
||
|
||
export default {
|
||
// ---------------------------------------------------------------------------------------------------------------
|
||
// 1_17 深靛 光影迷宫: attack interval ×base_attack_time, talent chance ×talent_scale, bound enemies in range take
|
||
// indigo_s_2[damage].atk_scale × ATK arts every indigo_s_2[damage].interval s.
|
||
// 柔光缚目: attacks bind the target `duration` s with `prob`; bound enemies are never chosen as targets.
|
||
// Alternate S1 灯塔守卫者: for `duration` s the skill range (4-1), attack interval ×(1 + base_attack_time) (PRTS:
|
||
// "大幅度缩短(-80%)" ⇒ ×0.2 — a flat −0.8 s on 3 s would be less than S2's "略微缩短 ×0.7"), every attack hits for
|
||
// attack@atk_scale × ATK arts. Elite module MSC-X (store 4) is the mystic profile of the module trait (TUNE.mystic).
|
||
chess_char_1_17_a: (bb, chess, def) => {
|
||
const t = talentBb(chess, 0);
|
||
const dScale = num(bb['indigo_s_2[damage].atk_scale']);
|
||
const dIv = num(bb['indigo_s_2[damage].interval'], 0.5);
|
||
const bound = (e) => !!e.s.flags.bind;
|
||
const r1 = skillRec(chess, 'skchr_indigo_1');
|
||
const b1 = r1?.bb ?? {};
|
||
return {
|
||
skills: {
|
||
skchr_indigo_1: {
|
||
kind: 'duration', mods: { batPct: Math.max(-0.95, Math.min(0, num(b1.base_attack_time))) },
|
||
targeting: { rangeGrid: r1?.rangeGrid ?? null },
|
||
attack: { atkScale: num(b1['attack@atk_scale'], 1) },
|
||
},
|
||
},
|
||
trait: {
|
||
// with only bound enemies in range (or blocked by her — always her targets, Battle.blockedTargets) she holds her
|
||
// fire (the mystic trait stores the energy meanwhile)
|
||
canAttack(battle, u) {
|
||
const ok = battle.enemiesInKeys(u.rangeKeys, u, u.profile).some((e) => !bound(e)) || battle.blockedTargets(u, u.profile).some((e) => !bound(e));
|
||
if (!ok) u.trait.hadTarget = false;
|
||
return ok;
|
||
},
|
||
afterHit(battle, u, target) {
|
||
if (!target || !target.alive || target.side !== 'enemy') return;
|
||
const p = num(t.prob) * (u.skill?.active ? num(bb.talent_scale, 1) : 1);
|
||
if (p > 0 && battle.rng.chance(Math.min(1, p))) battle.applyStatus(target, 'bind', { duration: num(t.duration), source: u });
|
||
},
|
||
},
|
||
skill: {
|
||
kind: 'duration', mods: { batPct: batMod(bb.base_attack_time, chess, def?.skill?.description ?? chess?.skill?.desc) },
|
||
onStart({ unit }) { unit.mem.indigoAcc = 0; },
|
||
onTick({ battle, unit, dt }) {
|
||
if (!(dScale > 0) || !(dIv > 0)) return;
|
||
unit.mem.indigoAcc = (unit.mem.indigoAcc ?? 0) + dt;
|
||
while (unit.mem.indigoAcc >= dIv - 1e-9) {
|
||
unit.mem.indigoAcc -= dIv;
|
||
for (const e of battle.enemiesInKeys(unit.rangeKeys, unit, { canHitFly: true })) {
|
||
if (bound(e)) battle.dealDamage(unit, e, { amount: unit.s.atk * dScale, type: 'arts', isSkill: true, canDodge: false, tags: ['dot'] });
|
||
}
|
||
}
|
||
},
|
||
},
|
||
talents: [{ install(battle, unit) {
|
||
battle.on('beforeAttack', (ctx) => {
|
||
if (ctx.attacker !== unit || !ctx.targets.some(bound)) return;
|
||
const prof = ctx.profile || unit.profile;
|
||
const cands = battle.enemiesInKeys(unit.rangeKeys, unit, prof);
|
||
for (const e of battle.blockedTargets(unit, prof)) if (!cands.includes(e)) cands.push(e);
|
||
for (let i = cands.length - 1; i >= 0; i--) if (bound(cands[i])) cands.splice(i, 1);
|
||
sortEnemyTargets(battle, unit, cands, prof.priority);
|
||
ctx.targets = cands.slice(0, Math.max(1, ctx.targets.length));
|
||
}, { owner: unit });
|
||
} }],
|
||
};
|
||
},
|
||
};
|