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,133 @@
// server/sim/content/kits/ops/chess_char_6_07-siege2.js — 维娜·维多利亚 (char_1019_siege2) kit, tier 6.
// Conventions of the tier-6 kits: ../shared/tier6.js; kit contract and rules: ../README.md.
import {
num, tbb, moduleBb, live, isElite, keyOf, onDefaultSkill, selectedSkill, batOf, skillGridOf, N4, freeTiles, aura,
} from '../shared/tier6.js';
// ------------------------------------------------------------------------------------------------------------------
// 维娜·维多利亚 chess_char_6_07 (术战者) — S3 俱以我之名; 诸王的叹息; 无拘的锋芒; module 城主的冒险
function siege2(bb, chess, def) {
const t0 = tbb(def, 0), t1 = tbb(def, 1), tb = def?.traitBb || {}, mod = moduleBb(chess);
const isDef = onDefaultSkill(chess), sid = selectedSkill(chess, def);
const tGrid = def?.talents?.[0]?.rangeGrid?.length ? def.talents[0].rangeGrid : [[1, -1], [1, 0], [1, 1], [0, -1], [0, 0], [0, 1], [-1, -1], [-1, 0], [-1, 1]];
const tokId = (chess?.tokens || []).find((t) => /vlion/.test(String(t))) || 'token_10040_siege2_vlion';
const mine = (u, unit) => u === unit || (!!u && u.kind === 'token' && u.ownerUnit === unit);
const skills = {
// S1 重铸晖光: the next attack also deals atk_scale × ATK true damage to every ground enemy on the skill's 4
// surrounding tiles (+ her own)
skchr_siege2_1: {
kind: 'instant',
attack: {
onHit({ battle, unit }) {
for (const e of battle.unitsInGrid(unit, skillGridOf(def) || N4, { side: 'enemy' })) {
if (!e.isFlying && e.alive) battle.dealDamage(unit, e, { amount: unit.s.atk * num(bb.atk_scale), type: 'true', isSkill: true, tags: ['skill'] });
}
battle.fx('aoe', { x: unit.x, y: unit.y, id: unit.id });
},
},
},
// S2 进赴故土 (toggle): range +1 (skill grid), ATK +atk, one extra target; passive: ≥buff_stack_cnt other allies in
// the talent-1 area ⇒ SP +sp_recovery_per_sec (install)
skchr_siege2_2: {
kind: 'toggle',
mods: { atkPct: num(bb.atk) },
targeting: { maxTargets: Math.max(1, Math.floor(num(bb['attack@max_target'], 2))), ...(skillGridOf(def) ? { rangeGrid: skillGridOf(def) } : {}) },
},
};
return {
skills,
install(battle, unit) {
if (sid === 'skchr_siege2_2') {
const need = Math.max(1, Math.floor(num(bb.buff_stack_cnt, 2))), spr = num(bb.sp_recovery_per_sec);
if (spr > 0) aura(battle, unit, 0.25, () => {
const n = battle.unitsInGrid(unit, tGrid, { side: 'ally' }).filter((a) => a !== unit && a.kind !== 'device' && battle.allySelectable(a, unit)).length;
if (n >= need) battle.addBuff(unit, { key: 'siege2:homeland', mods: { spRecoveryFlat: spr }, duration: 0.4, refresh: 'replace' });
});
}
// module 秩序圣“球”: enemies blocked by her or her summons take 10 % 脆弱 (trait); she and her summons deal ×1.15 to
// 战栗 targets (talent)
const frag = num(tb.damage_scale, 1) - 1, tremMul = num(mod.damage_scale, 1);
if (frag > 0) aura(battle, unit, 0.25, () => {
for (const e of battle.enemies) if (e.alive && e.blockedBy && mine(e.blockedBy, unit)) battle.applyStatus(e, 'fragile', { duration: 0.4, value: frag, source: unit });
});
if (tremMul > 1) battle.on('hit', (ctx) => {
const t = ctx.target;
if (!mine(ctx.source, unit) || !t || t.side !== 'enemy' || !(t.findBuff('tremble') || t.s.flags.tremble)) return;
ctx.dmg.mul *= tremMul;
}, { owner: unit });
},
skill: {
kind: 'duration',
mods: { atkPct: num(bb.atk), batPct: batOf(bb.base_attack_time, def) },
targeting: { maxTargets: Math.max(1, Math.floor(num(bb['attack@max_target'], 1))) },
attack: { dmgType: 'true' },
// "立即在天赋一生效范围内可部署地面召唤“黄金盟誓”" (EN client "Summons Golden Vows on deployable tiles within
// Talent 1's range"): one on EVERY free tile of the talent-1 area a melee piece could be deployed on — fences
// (low, deployable, not walkable) included [ASSUMED: the EN wiki's "open low ground tiles"]; player report B3
onStart({ battle, unit, skill }) {
const lions = [];
for (const tile of freeTiles(battle, unit, tGrid, { ground: false })) {
const lion = battle.spawnToken(unit, tokId, tile[0], tile[1], { duration: skill.timeLeft });
if (!lion) continue;
lions.push(lion);
if (!lion.kit?.fromTokens && lion.profile) lion.profile.dmgType = 'true'; // "攻击造成真实伤害" without a token kit
battle.fx('summon', { x: lion.x, y: lion.y, id: lion.id, src: unit.id });
}
unit.mem.vlions = lions;
},
onEnd({ battle, unit }) {
const lions = unit.mem.vlions || [];
unit.mem.vlions = null;
for (const lion of lions) if (lion.alive) battle.retreat(lion, { reason: 'expired', permanent: true });
battle.setExtraRange(unit, null);
},
},
talents: [
{ install(battle, unit) { // S3: enemies blocked by allies inside the talent area become targetable
let sig = '';
if (!isDef) return;
battle.on('tick', () => { // (engine extra range keys: kept across range rebuilds, cleared at the skill end)
if (!live(unit) || !unit.skill?.active) { sig = ''; return; }
const keys = [];
for (const a of battle.unitsInGrid(unit, tGrid, { side: 'ally' })) for (const e of a.blocking) if (e.alive) keys.push(keyOf(e));
const s = keys.join(',');
if (s === sig) return;
sig = s;
battle.setExtraRange(unit, keys);
}, { owner: unit });
} },
{ install(battle, unit) { // 诸王的叹息
const dr = num(t0.damage_resistance), atk = num(t0.atk);
aura(battle, unit, 0.25, () => {
const allies = battle.unitsInGrid(unit, tGrid, { side: 'ally' }).filter((a) => a.kind !== 'device' && battle.allySelectable(a, unit));
if (!allies.includes(unit)) allies.push(unit);
if (dr > 0) for (const a of allies) battle.addBuff(a, { key: 'siege2:sigh', mods: { physTakenMul: 1 - dr }, duration: 0.4, refresh: 'replace' });
const n = allies.filter((a) => a !== unit).length;
if (n > 0 && atk) battle.addBuff(unit, { key: 'siege2:kings', mods: { atkPct: atk * n }, duration: 0.4, refresh: 'replace' });
});
} },
{ install(battle, unit) { // 无拘的锋芒: first damage on each enemy ⇒ 战栗 (module 秩序圣“球”: 6 s, elite / leader 12 s)
const durN = num(t1.not_combat_normal, num(t1.not_combat)), durE = num(t1.not_combat_elite, durN);
const seen = new WeakSet();
battle.on('damaged', (ctx) => {
const t = ctx.target;
if (ctx.source !== unit || !t || t.side !== 'enemy' || seen.has(t)) return;
const dur = isElite(t) ? durE : durN;
if (!(dur > 0)) return;
seen.add(t);
if (t.alive) battle.applyStatus(t, 'tremble', { duration: dur, source: unit });
}, { owner: unit });
} },
{ install(battle, unit) { // elite module: ASPD +8 while not blocking
const as = num(tb.attack_speed);
if (as) aura(battle, unit, 0.25, () => { if (!unit.blocking.length) battle.addBuff(unit, { key: 'siege2:free', mods: { aspd: as }, duration: 0.4, refresh: 'replace' }); });
} },
],
};
}
export default {
chess_char_6_07_a: siege2,
};