feedback5 R4: one file per operator kit — kits/tier1..6.js split into kits/ops/<chessId>-<codename>.js (129 kits), kits/shared/tier1..6.js (the helpers two or more kits of a tier use, and each tier's header notes) and kits/index.js (the registry, same keys in the same order); the tier files become re-export shims

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.
This commit is contained in:
sganggs committed 2026-10-05 16:13:56 +08:00
1 parent 6b489b1776
commit 1169102d46
143 files changed
+12924 -11784

No files matched your search

@@ -0,0 +1,126 @@
// server/sim/content/kits/ops/chess_char_6_14-lumen.js — 流明 (char_4042_lumen) kit, tier 6.
// Conventions of the tier-6 kits: ../shared/tier6.js; kit contract and rules: ../README.md.
import { COLS } from '../../../constants.js';
import {
num, bv, tbb, live, onDefaultSkill, instantKind, ABNORMAL, hasAbnormal, cleanseAbnormal,
} from '../shared/tier6.js';
// ------------------------------------------------------------------------------------------------------------------
// 流明 chess_char_6_14 (疗养师) — S3 灯火不灭; 凡人之愿; 应急处理; module 纯铜单筒望远镜
function lumen(bb, chess, def) {
const t0 = tbb(def, 0), t1 = tbb(def, 1), tb = def?.traitBb || {};
const isDef = onDefaultSkill(chess);
const hs = num(bb.heal_scale, 1);
const abnormalAllies = (battle, unit) => battle.alliesInGrid(unit).filter((a) => hasAbnormal(a)).sort((a, b) => a.hpRatio - b.hpRatio || a.deploySeq - b.deploySeq);
const cleanse = (battle, t) => { cleanseAbnormal(battle, t); };
const skills = {
// S1 沐雨: the next heal also gives its target and the allies around it (8 tiles) aura.heal_scale × ATK healing
// per second for aura.projectile_life_time s
skchr_lumen_1: {
kind: 'instant',
heal: true,
attack: { healScale: 1 },
onHit({ battle, unit, target }) {
if (!target) return;
const sc = bv(bb, 'heal_scale'), life = bv(bb, 'projectile_life_time', 4), iv = Math.max(0.2, bv(bb, 'interval', 1));
if (!(sc > 0) || !(life > 0)) return;
battle.fx('healField', { x: target.x, y: target.y, id: unit.id });
for (const a of battle.alliesInRadius(target.x, target.y, 1.5, target.ownerId)) {
if (a.kind === 'device' || a.s.flags.noHeal || a.profile?.noHeal) continue; // a heal: never on 禁疗 / 孤立
battle.addBuff(a, {
key: `lumen:rain:${unit.id}`, duration: life, interval: iv, refresh: 'replace', visible: true, source: unit,
onTick: ({ unit: x }) => { if (x.hp < x.s.maxHp) battle.heal(unit, x, unit.s.atk * sc, { hot: true }); },
});
}
},
},
// S2 沛霖 (2 charges): heal up to max_target allies of her range for heal_scale × ATK; cast with full charges ⇒
// also cleanse their 异常状态 (蓄力额外效果)
skchr_lumen_2: {
kind: instantKind(def),
heal: true,
onStart({ battle, unit, skill }) {
const full = skill.maxCharges > 1 && skill.charges >= skill.maxCharges - 1; // (this cast already spent one)
const inj = battle.injuredAlliesInKeys(unit.rangeKeys, unit);
const abn = full ? abnormalAllies(battle, unit).filter((a) => !inj.includes(a)) : [];
const tgts = inj.concat(abn).slice(0, Math.max(1, Math.floor(num(bb.max_target, 2))));
for (const t of tgts) {
battle.heal(unit, t, unit.s.atk * num(bb.heal_scale, 1), { skillHeal: true });
if (full) cleanse(battle, t);
}
battle.fx('heal', { x: unit.x, y: unit.y, id: unit.id, n: tgts.length, charged: full });
},
},
};
return {
skills,
skill: {
kind: 'ammo',
heal: true,
ammo: Math.max(1, Math.floor(num(bb['attack@trigger_time'], 4))),
mods: { atkPct: num(bb.atk), aspd: num(bb.attack_speed) },
attack: { healScale: 1 },
onHit({ battle, unit, target }) { if (unit.mem.lumenAbn && target) cleanse(battle, target); },
onAttack(ctx) { // only heals on abnormal targets consume a bullet (engine: ctx.noAmmo — no bullet, no ammoUsed)
if (!ctx.unit.mem.lumenAbn) ctx.noAmmo = true;
ctx.unit.mem.lumenAbn = false;
},
},
talents: [
{ install(battle, unit) { // S3 targeting / bullet bookkeeping
if (!isDef) return;
battle.on('beforeAttack', (ctx) => {
if (ctx.attacker !== unit || !unit.skill?.active) return;
const abn = abnormalAllies(battle, unit);
unit.mem.lumenAbn = abn.length > 0;
if (abn.length) ctx.targets = [abn[0]];
if (ctx.profile && ctx.profile !== unit.profile) ctx.profile.healScale = abn.length ? hs : 1;
}, { owner: unit });
battle.on('tick', () => { // abnormal allies at full HP are healed too
const sk = unit.skill;
if (!sk?.active || !unit.canAct || unit.atkCd > 0 || unit.s.flags.disarm) return;
if (battle.injuredAlliesInKeys(unit.rangeKeys, unit).length) return;
const abn = abnormalAllies(battle, unit);
if (!abn.length) return;
battle.forceAttack(unit, [abn[0]]);
unit.atkCd = unit.s.interval;
}, { owner: unit });
} },
{ install(battle, unit) { // 凡人之愿: healed targets gain 抵抗
const base = bv(t0, 'status_resistance[limit]', 0), special = num(t0['lumen_t_1[special].status_resistance[limit]'], base);
const res = -num(t0.one_minus_status_resistance, -0.5), thr = num(t0.hp_ratio, 0.75);
battle.on('heal', (ctx) => {
const t = ctx.target;
if (ctx.source !== unit || !t || !(res > 0) || !(base > 0)) return;
const after = Math.min(t.s.maxHp, t.hp + ctx.amount);
battle.applyStatus(t, 'resist', { duration: after / t.s.maxHp > thr ? special : base, value: Math.min(1, res), source: unit });
}, { owner: unit, priority: -10 });
} },
{ install(battle, unit) { // 应急处理
const sc = num(t1.heal_scale), cd = num(t1.duration, 12);
let ready = -Infinity;
battle.on('statusApplied', (ctx) => {
const t = ctx.target;
if (!live(unit) || !t || t.side !== 'ally' || !ABNORMAL.has(ctx.status) || battle.time < ready || !(sc > 0)) return;
if (!unit.rangeKeySet?.has(t.tileR * COLS + t.tileC)) return;
ready = battle.time + cd;
battle.heal(unit, t, unit.s.atk * sc);
battle.fx('emergency', { x: t.x, y: t.y, id: t.id, src: unit.id });
}, { owner: unit });
} },
{ install(battle, unit) { // elite module: taunt −1 and 抵抗
const taunt = num(tb.taunt_level), res = -num(tb.one_minus_status_resistance);
if (taunt) battle.addBuff(unit, { key: 'lumen:scope', mods: { taunt }, persist: true, allowDead: true });
// a permanent 抵抗 (survives death/redeploy): any buff with status 'resist' counts (engine resistOf — the
// strongest one applies, never compounding with 凡人之愿 / 灵知)
if (res > 0) battle.addBuff(unit, { key: 'lumen:scopeResist', status: 'resist', visible: true, persist: true, allowDead: true, data: { value: Math.min(1, res) } });
} },
],
};
}
export default {
chess_char_6_14_a: lumen,
};