Files
ark-stronghold-protocol/server/sim/content/kits/ops/chess_char_5_13-ghost2.js
T
sganggs 1169102d46 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.
2026-10-05 16:13:56 +08:00

105 lines
6.5 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// server/sim/content/kits/ops/chess_char_5_13-ghost2.js — 归溟幽灵鲨 (char_1023_ghost2) kit, tier 5.
// Conventions of the tier-5 kits: ../shared/tier5.js; kit contract and rules: ../README.md.
import {
AURA_IV, AURA_DUR, num, talent, traitBb, skillGrid, batPct, mods, isAbyssal, isOp, selectedId, lazySkills, whileOn,
} from '../shared/tier5.js';
export default {
// ---------------------------------------------------------------------------------------------------------------
// 归溟幽灵鲨 — dollkeeper (professions.js installDollkeeper: the 替身 form, its switch animations, 阻回). Her <替身> makes
// no normal attack (PRTS 特性备注 "<替身>不进行普通攻击") and so casts no skill (kit trait `dollNoAttack`).
// S2 生存的渴望 (15/17 s): ATK/ASPD +, HP never below 1 (PRTS 备注: 不死 — a lethal hit does not switch her meanwhile);
// when it ends she switches to the 替身 at once (PRTS 修正 "技能结束后立刻切换为<替身>", the text's 视为被击倒: no lethal
// HP loss, so no 不死 / 复活 effect takes it). T1 拥抱自我: the 替身 slows nearby enemies −40 % and deals 40 % ATK
// arts/s to them (PRTS 备注 "伤害与减速不可对空": ground enemies only) — once it fights (not during its switch
// animation). T2 阿戈尔的深邃: Abyssal Hunters in the team max HP +20 %. Module (elite): substitute ATK +15 %.
// S1 生存的技巧 (duration): swaps HP ratios with the other operator of the skill area (周围) with the lowest HP ratio,
// ATK +. S3 生存的重压 (duration): BAT +1 s, hits every blocked enemy, ATK +, max HP +; an attacked enemy whose HP ratio
// is ≥ hers takes attack@atk_scale_ex × ATK phys more, otherwise she loses attack@hp_ratio of her max HP.
// Module PUM-Y (elite): substitute max HP +20 %.
chess_char_5_13_a: (bb, chess, def) => {
const t0 = talent(chess, 0), t1 = talent(chess, 1), tb = traitBb(chess);
const sid = selectedId(chess, def);
const aroundGrid = chess?.trait?.rangeGrid ?? [[1, -1], [1, 0], [1, 1], [0, -1], [0, 0], [0, 1], [-1, -1], [-1, 0], [-1, 1]];
return {
skills: lazySkills({
skchr_ghost2_1: () => ({
kind: 'duration', mods: mods({ atkPct: num(bb.atk) }),
onStart({ battle, unit }) {
const t = battle.unitsInGrid(unit, skillGrid(chess, def) ?? aroundGrid, { side: 'ally' }).filter((a) => a !== unit && isOp(a) && a.hp > 0)
.sort((a, b) => a.hpRatio - b.hpRatio || a.id - b.id)[0];
if (!t) return;
const mine = unit.hpRatio, theirs = t.hpRatio;
unit.hp = Math.max(1, Math.min(unit.s.maxHp, unit.s.maxHp * theirs));
t.hp = Math.max(1, Math.min(t.s.maxHp, t.s.maxHp * mine));
battle.fx('hpShare', { x: t.x, y: t.y, id: unit.id, to: t.id });
},
}),
skchr_ghost2_3: () => ({
kind: 'duration',
mods: mods({ atkPct: num(bb.atk), hpPct: num(bb.max_hp), batPct: batPct(bb.base_attack_time, chess) }),
attack: {
hitAllBlocked: true,
onEachHit({ battle, unit, target, kind }) {
if (kind !== 'main' || !target || target.side !== 'enemy') return;
if (unit.mem.ghostHeavy?.get(target)) {
if (target.alive) battle.dealDamage(unit, target, { amount: unit.s.atk * num(bb['attack@atk_scale_ex']), type: 'phys', isSkill: true, tags: ['skill', 'ghost2Weight'] });
} else if (unit.alive) battle.loseHp(unit, unit.s.maxHp * num(bb['attack@hp_ratio']), { source: unit });
},
},
}),
}),
skill: {
kind: 'duration',
mods: mods({ atkPct: num(bb.atk), aspd: num(bb.attack_speed) }),
// "技能结束后立刻切换为<替身>": nothing when the skill ended with her (death) or by her switch to the 替身, or she
// already is one
onEnd({ battle, unit, reason }) {
if (reason === 'death' || reason === 'substitute' || !unit.alive || !unit.deployed || unit.trait.doll) return;
battle.emit('dollSwitch', { unit, reason: 'skill', done: false });
},
},
trait: { dollNoAttack: true },
talents: [
{ install(battle, unit) { // 拥抱自我 (the 替身 fighting: not during a switch animation)
const slow = num(t0.move_speed), scale = num(t0.atk_scale);
whileOn(battle, unit, AURA_IV, () => {
if (!unit.trait.doll || unit.trait.dollSwitching || !slow) return;
for (const e of battle.unitsInGrid(unit, aroundGrid, { side: 'enemy' })) if (!e.isFlying) battle.addBuff(e, { key: 'ghost2:embrace', duration: AURA_DUR, mods: { moveMul: Math.max(0, 1 + slow) } });
});
whileOn(battle, unit, 1, () => {
if (!unit.trait.doll || unit.trait.dollSwitching || !(scale > 0)) return;
for (const e of battle.unitsInGrid(unit, aroundGrid, { side: 'enemy' })) if (!e.isFlying) battle.dealDamage(unit, e, { amount: unit.s.atk * scale, type: 'arts', tags: ['talent', 'embrace'] });
});
} },
{ install(battle, unit) { // 阿戈尔的深邃
const hp = num(t1.max_hp);
if (!hp) return;
battle.on('battleStart', () => {
for (const a of battle.getPlayer(unit.ownerId)?.units ?? []) if (isOp(a) && isAbyssal(a)) battle.addBuff(a, { key: 'ghost2:abyss', mods: { hpPct: hp }, persist: true, allowDead: true });
}, { owner: unit });
} },
],
install(battle, unit) {
// S2: HP never below 1 while it runs
if (!sid || sid === 'skchr_ghost2_2') battle.on('fatal', (c) => { if (c.unit === unit && unit.skill?.active) c.prevented = true; }, { owner: unit, priority: 10 });
if (sid === 'skchr_ghost2_3') { // S3: 生命比例高于或等于自身 — judged when the attack starts
battle.on('beforeAttack', (c) => {
if (c.attacker !== unit || !unit.skill?.active) return;
unit.mem.ghostHeavy = new Map(c.targets.filter(Boolean).map((t) => [t, t.hpRatio >= unit.hpRatio - 1e-9]));
}, { owner: unit, priority: -100 });
}
const hpUp = num(tb.max_hp); // PUM-Y: 替身…生命值提升
if (hpUp) whileOn(battle, unit, 0.2, () => { if (unit.trait.doll) battle.addBuff(unit, { key: 'ghost2:moduleY', duration: 0.3, mods: { hpPct: hpUp } }); });
const atk = num(tb.atk);
if (atk) {
whileOn(battle, unit, 0.2, () => {
if (unit.trait.doll) battle.addBuff(unit, { key: 'ghost2:module', duration: 0.3, mods: { atkPct: atk } });
});
}
},
};
},
};