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.
150 lines
8.0 KiB
JavaScript
150 lines
8.0 KiB
JavaScript
// server/sim/content/kits/ops/chess_char_6_17-nearl2.js — 耀骑士临光 (char_1014_nearl2) kit, tier 6.
|
||
// Conventions of the tier-6 kits: ../shared/tier6.js; kit contract and rules: ../README.md.
|
||
|
||
import { absoluteRangeKeys } from '../../../targeting.js';
|
||
import { bodyInKeys } from '../../../body.js';
|
||
import {
|
||
num, tbb, live, hasBond, onDefaultSkill, selectedSkill, N4, bstate, freeTiles, bestTile,
|
||
} from '../shared/tier6.js';
|
||
|
||
/** Deploy-order tracker: last op deployed per owner (read before the update by priority-0 deploy handlers). */
|
||
function ensureDeployTracker(battle) {
|
||
const S = bstate(battle);
|
||
if (S.lastOp) return S.lastOp;
|
||
S.lastOp = new Map();
|
||
battle.on('deploy', ({ unit }) => { if (unit.kind === 'op') S.lastOp.set(unit.ownerId, unit); }, { priority: -100 });
|
||
return S.lastOp;
|
||
}
|
||
|
||
// ------------------------------------------------------------------------------------------------------------------
|
||
// 耀骑士临光 chess_char_6_17 (无畏者) — S3 耀阳颔首; 不畏苦暗; 破晓; module 耀阳锋刃
|
||
|
||
function nearl2(bb, chess, def) {
|
||
const t0 = tbb(def, 0), t1 = tbb(def, 1), tb = def?.traitBb || {};
|
||
const t0Grid = def?.talents?.[0]?.rangeGrid?.length ? def.talents[0].rangeGrid : N4;
|
||
const skillGrid = def?.skill?.rangeGrid?.length ? def.skill.rangeGrid : null;
|
||
const tokId = (chess?.tokens || []).find((t) => /nearl2_sword/.test(String(t))) || 'token_10019_nearl2_sword';
|
||
const sunBurst = (battle, unit, r, c, scale, stun) => {
|
||
const keys = new Set(absoluteRangeKeys(N4, r, c, 1, 0));
|
||
battle.fx('sunBurst', { x: c, y: r, id: unit.id });
|
||
for (const e of battle.enemies) {
|
||
if (!e.alive || e.hidden || !bodyInKeys(e, keys)) continue;
|
||
battle.dealDamage(unit, e, { amount: unit.s.atk * scale, type: 'true', isSkill: true, tags: ['skill'] });
|
||
if (stun > 0 && e.alive) battle.applyStatus(e, 'stun', { duration: stun, source: unit });
|
||
}
|
||
};
|
||
const isDef = onDefaultSkill(chess), sid = selectedSkill(chess, def);
|
||
const lastOps = { map: null };
|
||
const skills = {
|
||
// S1 灿焰长刃 (toggle, 持续时间无限): skill range, ATK / ASPD +
|
||
skchr_nearl2_1: {
|
||
kind: 'toggle',
|
||
mods: { atkPct: num(bb.atk), aspd: num(bb.attack_speed) },
|
||
...(skillGrid ? { targeting: { rangeGrid: skillGrid } } : {}),
|
||
},
|
||
// S2 逐夜烁光 (passive, ON_DEPLOY): for the skill duration after each deployment ATK +atk and `times` 护盾 layers
|
||
// (whole hits negated); then she withdraws and this redeploy time is ×respawn_time (×1 when the operator deployed
|
||
// right before her is 【卡西米尔】)
|
||
skchr_nearl2_2: {
|
||
kind: 'passive',
|
||
onStart({ battle, unit, skill }) {
|
||
const dur = num(skill.duration, num(def?.skill?.duration, 22));
|
||
if (!(dur > 0)) return;
|
||
const prev = lastOps.map?.get(unit.ownerId);
|
||
const combo = !!prev && prev !== unit && hasBond(prev, 'kazimierzShip');
|
||
battle.addBuff(unit, { key: 'nearl2:night', mods: { atkPct: num(bb.atk) }, duration: dur, visible: true });
|
||
const hits = Math.floor(num(bb.times));
|
||
if (hits > 0) battle.addBuff(unit, { key: 'nearl2:shield', shieldHits: hits, duration: dur, visible: true });
|
||
const seq = unit.deploySeq;
|
||
battle.after(dur, () => {
|
||
if (!live(unit) || unit.deploySeq !== seq) return;
|
||
battle.retreat(unit, { reason: 'retreat' });
|
||
const mul = combo ? num(bb['nearl2_s_2[withdraw][combo].respawn_time'], 1) : num(bb.respawn_time, 1);
|
||
if (Number.isFinite(unit.respawnAt) && mul !== 1) unit.respawnAt = battle.time + (unit.respawnAt - battle.time) * mul;
|
||
battle.fx('disappear', { x: unit.x, y: unit.y, id: unit.id, combo });
|
||
}, { owner: unit });
|
||
},
|
||
},
|
||
};
|
||
// module “骑士家族”: "被击倒时不撤退且回复所有生命但生命上限-60%,攻击速度+30(单次部署只触发1次)" and 不畏苦暗
|
||
// "部署时及首次被击倒时…" (the burst again)
|
||
const standVal = num(tb.value), standAs = num(tb.attack_speed), standHp = num(tb.hp_ratio, 1);
|
||
return {
|
||
skills,
|
||
install(battle, unit) {
|
||
if (sid === 'skchr_nearl2_2') lastOps.map = ensureDeployTracker(battle);
|
||
if (standVal > 0) {
|
||
battle.on('deploy', ({ unit: u }) => { if (u === unit) unit.mem.nearlStood = false; }, { owner: unit });
|
||
battle.on('fatal', (ctx) => {
|
||
if (ctx.unit !== unit || ctx.prevented || unit.mem.nearlStood) return;
|
||
ctx.prevented = true;
|
||
unit.mem.nearlStood = true;
|
||
battle.addBuff(unit, { key: 'nearl2:stand', mods: { hpMul: Math.max(0.05, 1 - standVal), aspd: standAs }, visible: true });
|
||
unit.hp = Math.max(1, unit.s.maxHp * standHp);
|
||
battle.fx('undying', { x: unit.x, y: unit.y, id: unit.id });
|
||
battle.emit('nearl2:knockdown', { unit });
|
||
}, { owner: unit, priority: -60 });
|
||
}
|
||
},
|
||
trait: num(tb.atk_scale, 1) > 1 ? { dmgMul: (b, u, t) => (t.blockedBy ? num(tb.atk_scale, 1) : 1) } : null,
|
||
skill: {
|
||
kind: 'duration',
|
||
mods: { atkPct: num(bb.atk), defPct: num(bb.def) },
|
||
...(skillGrid ? { targeting: { rangeGrid: skillGrid } } : {}),
|
||
onStart({ battle, unit, skill }) {
|
||
const tile = bestTile(battle, freeTiles(battle, unit, N4));
|
||
const sword = tile ? battle.spawnToken(unit, tokId, tile[0], tile[1], { duration: skill.timeLeft }) : null;
|
||
unit.mem.sun = sword;
|
||
if (sword) battle.fx('summon', { x: sword.x, y: sword.y, id: sword.id, src: unit.id });
|
||
// the token kit (content/tokens.js) performs the appear burst; without it (or without a free tile) do it here
|
||
if (!sword || !sword.kit?.fromTokens) {
|
||
sunBurst(battle, unit, sword ? sword.tileR : unit.tileR, sword ? sword.tileC : unit.tileC, num(bb.value), num(bb.value2));
|
||
}
|
||
},
|
||
onEnd({ battle, unit }) {
|
||
const s = unit.mem.sun;
|
||
unit.mem.sun = null;
|
||
if (s && s.alive) battle.retreat(s, { reason: 'expired', permanent: true });
|
||
},
|
||
},
|
||
talents: [
|
||
{ install(battle, unit) { // S3: attacks on units blocked by her or 耀阳 deal true damage
|
||
if (!isDef) return;
|
||
battle.on('hit', (ctx) => {
|
||
if (ctx.source !== unit || !unit.skill?.active || !ctx.dmg.isAttack) return;
|
||
const b = ctx.target.blockedBy;
|
||
if (b && (b === unit || b === unit.mem.sun)) ctx.dmg.type = 'true';
|
||
}, { owner: unit });
|
||
} },
|
||
{ install(battle, unit) { // 不畏苦暗 — the 4 tiles around her, air units too [ASSUMED: no 对空 note on PRTS, like “耀阳”]
|
||
const last = ensureDeployTracker(battle);
|
||
const sc = num(t0.atk_scale), stun = num(t0.stun);
|
||
const dawn = (times) => {
|
||
const foes = battle.unitsInGrid(unit, t0Grid, { side: 'enemy' });
|
||
if (foes.length) battle.fx('sunBurst', { x: unit.x, y: unit.y, id: unit.id });
|
||
for (const e of foes) {
|
||
for (let i = 0; i < times && e.alive; i++) battle.dealDamage(unit, e, { amount: unit.s.atk * sc, type: 'true', tags: ['talent'] });
|
||
if (stun > 0 && e.alive) battle.applyStatus(e, 'stun', { duration: stun, source: unit });
|
||
}
|
||
};
|
||
const kazTimes = () => { const prev = last.get(unit.ownerId); return prev && prev !== unit && hasBond(prev, 'kazimierzShip') ? 2 : 1; };
|
||
battle.on('deploy', ({ unit: u }) => {
|
||
if (u !== unit || !(sc > 0)) return;
|
||
unit.mem.nearlDawnTimes = kazTimes();
|
||
dawn(unit.mem.nearlDawnTimes);
|
||
}, { owner: unit });
|
||
// module “骑士家族”: "…及首次被击倒时" — the burst again when the trait keeps her standing
|
||
battle.on('nearl2:knockdown', ({ unit: u }) => { if (u === unit && sc > 0) dawn(unit.mem.nearlDawnTimes ?? 1); }, { owner: unit });
|
||
} },
|
||
{ install(battle, unit) { // 破晓
|
||
const p = num(t1.def_penetrate);
|
||
if (p) battle.addBuff(unit, { key: 'nearl2:dawn', mods: { defIgnorePct: p }, persist: true, allowDead: true });
|
||
} },
|
||
],
|
||
};
|
||
}
|
||
|
||
export default {
|
||
chess_char_6_17_a: nearl2,
|
||
};
|