Files
ark-stronghold-protocol/server/sim/content/kits/ops/chess_char_6_09-cello.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

147 lines
8.4 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_6_09-cello.js — 塑心 (char_245_cello) kit, tier 6.
// Conventions of the tier-6 kits: ../shared/tier6.js; kit contract and rules: ../README.md.
import { bodyInKeys } from '../../../body.js';
import { hasHp } from '../../../damage.js';
import {
num, tbb, moduleBb, live, isElite, enemiesIn, selectedSkill, skillGridOf, bstate, onElementHit, elementDmg, aura,
} from '../shared/tier6.js';
// ------------------------------------------------------------------------------------------------------------------
// 塑心 chess_char_6_09 (巫役) — S1 “黄金的狂喜”; 无词哀歌; 精神逆构; module 强弱法
function cello(bb, chess, def) {
const t0 = tbb(def, 0), t1 = tbb(def, 1), tb = def?.traitBb || {}, mod = moduleBb(chess);
const sid = selectedSkill(chess, def);
const isS1 = sid === 'skchr_cello_1' || !(chess?.skills?.length); // S1 = the default (no loadout info ⇒ default)
// "第二天赋效果提升至N倍" (S3): every 精神逆构 number scaled around 1 (delta × N) while it runs
const boost = (c) => (c.mem.celloBoost > 0 ? c.mem.celloBoost : 1);
const scaled = (v, c) => 1 + (v - 1) * boost(c);
/** S2 partner: the other operator of her range with the highest ATK (re-picked every 0.5 s). */
const pickPartner = (battle, unit) => {
unit.mem.celloPick = 0.5;
const ops = battle.alliesInGrid(unit).filter((a) => a !== unit && a.kind === 'op');
unit.mem.celloPartner = ops.reduce((best, a) => (!best || a.s.atk > best.s.atk + 1e-9 ? a : best), null);
};
const skills = {
// S2 “安魂的弥撒”: ASPD +, one extra target; while it runs every damage she or the highest-ATK other operator of her
// range deals to an enemy also adds ep_damage_ratio × her ATK 凋亡损伤 (install)
skchr_cello_2: {
kind: 'duration',
mods: { aspd: num(bb.attack_speed) },
targeting: { maxTargets: 2 },
onStart({ battle, unit }) { pickPartner(battle, unit); },
onTick({ battle, unit, dt }) {
unit.mem.celloPick -= dt;
if (unit.mem.celloPick > 0 && live(unit.mem.celloPartner)) return;
pickPartner(battle, unit);
},
onEnd({ unit }) { unit.mem.celloPartner = null; },
},
// S3 “自由的探戈”: stops attacking, skill range, ATK +atk, 精神逆构 ×scale_delta_to_one; the other operators of her
// range with the highest max HP / ATK / DEF get +20 % of that stat
skchr_cello_3: {
kind: 'duration',
mods: { atkPct: num(bb.atk) },
...(skillGridOf(def) ? { targeting: { rangeGrid: skillGridOf(def) } } : {}),
attack: { noAttack: true },
onStart({ unit }) { unit.mem.celloBoost = num(bb.scale_delta_to_one, 1); unit.mem.celloAcc = Infinity; },
onTick({ battle, unit, dt }) {
unit.mem.celloAcc += dt;
if (unit.mem.celloAcc < 0.5) return;
unit.mem.celloAcc = 0;
const ops = battle.alliesInGrid(unit).filter((a) => a !== unit && a.kind === 'op');
const top = (f) => ops.reduce((best, a) => (!best || f(a) > f(best) + 1e-9 ? a : best), null);
const give = (a, key, mods) => { if (a) battle.addBuff(a, { key, mods, duration: 0.75, refresh: 'replace', source: unit, visible: true }); };
give(top((a) => a.base.maxHp), 'cello:tango:hp', { hpPct: num(bb['cello_s_3[max_hp].max_hp']) });
give(top((a) => a.base.atk), 'cello:tango:atk', { atkPct: num(bb['cello_s_3[atk].atk']) });
give(top((a) => a.base.def), 'cello:tango:def', { defPct: num(bb['cello_s_3[def].def']) });
},
onEnd({ unit }) { unit.mem.celloBoost = 1; },
},
};
return {
skills,
// S1 "技能未开启时无法普通攻击" and its 未处于损伤爆发期间 target pick belong to S1 only
trait: isS1 ? { noAttackUnlessSkill: true, priority: 'notBurst' } : null,
install(battle, unit) {
if (sid !== 'skchr_cello_2') return;
const ratio = num(bb.ep_damage_ratio);
// PRTS 塑心 S2 备注: "受影响的干员即将造成伤害时,因该效果造成的凋亡损伤生效于当次触发的伤害之前,该造成的凋亡损伤的来源
// 始终为塑心" — a late `hit` handler (after every handler that may cancel the damage), so the 凋亡 lands while the
// target is alive and may burst before the damage; 无来源 bursts (source null) never trigger it. It rides the
// damage about to be dealt, so a hit dodged or absorbed afterwards still carried it [ASSUMED].
battle.on('hit', (ctx) => {
const s = ctx.source, t = ctx.target, d = ctx.dmg;
if (!(ratio > 0) || !unit.skill?.active || !live(unit) || !s || !t || t.side !== 'enemy' || !hasHp(t) || !d || d.cancel) return;
if (d.type === 'element' || d.type === 'elemental' || !(d.amount > 0) || (s !== unit && s !== unit.mem.celloPartner)) return;
elementDmg(battle, unit, t, 'apoptosis', unit.s.atk * ratio);
}, { owner: unit, priority: -1000 });
},
skill: {
kind: 'charges',
targeting: { priority: 'notBurst' },
attack: {
atkScale: num(bb.atk_scale, 1),
onHit({ battle, unit, target }) { if (target && target.alive) elementDmg(battle, unit, target, 'apoptosis', unit.s.atk * num(bb.ep_damage_ratio)); },
},
},
talents: [
{ install(battle, unit) { // 无词哀歌
const er = num(t0.ep_damage_ratio), slug = num(t0.sluggish);
aura(battle, unit, 1, () => {
for (const e of enemiesIn(battle, unit)) {
if (er > 0) elementDmg(battle, unit, e, 'apoptosis', unit.s.atk * er, ['talent']);
if (slug > 0 && e.alive) battle.applyStatus(e, 'sluggish', { duration: slug, source: unit });
}
});
} },
{ install(battle, unit) { // 精神逆构 (+ module element fragile / field-wide + DoT) and trait module (×1.18 vs elite/leader)
const amp = num(t1.ep_damage_scale, 1), frag = num(t1.damage_scale, 1) - 1, elite = num(tb.ep_damage_scale, 1);
// module 音乐家的旅程: "在场时,全场敌人…" (field-wide) + damage_value 元素伤害 per second during the burst
const dot = num(t1.damage_value), fieldWide = t1.damage_value != null, dotIv = Math.max(0.1, num(t1.interval, 1));
unit.mem.celloAmp = amp;
unit.mem.celloWide = fieldWide;
// one battle-wide handler: every apoptosis fill on an enemy inside some 塑心's range (or anywhere for a field-wide
// one) × the strongest 精神逆构 (the same effect from two 塑心 does not stack)
const S = bstate(battle);
if (!S.cellos) {
S.cellos = new Set();
battle.on('elementHit', (ctx) => {
const t = ctx.target, d = ctx.dmg;
if (!d || d.type !== 'element' || d.element !== 'apoptosis' || !t || t.side !== 'enemy') return;
let best = 1;
for (const c of S.cellos) {
if (!live(c) || !(c.mem.celloWide || bodyInKeys(t, c.rangeKeySet))) continue;
const v = scaled(c.mem.celloAmp, c);
if (v > best) best = v;
}
if (best > 1) d.mul *= best;
});
}
if (amp > 1) S.cellos.add(unit);
// 强弱法 (elite trait): her element damage vs elite / leader enemies ×1.18
if (elite > 1) onElementHit(battle, unit, (ctx) => (ctx.source === unit && ctx.target.side === 'enemy' && isElite(ctx.target) ? elite : 1));
// "凋亡损伤爆发期间受到5%的元素脆弱" — the 元素脆弱 status (同名效果取最高)
if (frag > 0) aura(battle, unit, 0.25, () => {
for (const e of enemiesIn(battle, unit)) if (e.findBuff('apoptosisBurst')) battle.applyStatus(e, 'elemFragile', { duration: 0.4, value: frag * boost(unit), source: unit });
});
if (dot > 0) aura(battle, unit, dotIv, () => {
for (const e of battle.enemies) {
if (e.alive && !e.hidden && e.findBuff('apoptosisBurst')) battle.dealDamage(unit, e, { amount: dot * boost(unit), type: 'elemental', element: 'apoptosis', canDodge: false, tags: ['talent', 'elementDmg'] });
}
});
// module 音乐家的旅程 trait: "攻击范围内敌人受到10%的元素脆弱"
const mFrag = num(mod.damage_scale, 1) - 1;
if (mFrag > 0) aura(battle, unit, 0.25, () => {
for (const e of enemiesIn(battle, unit)) battle.applyStatus(e, 'elemFragile', { duration: 0.4, value: mFrag, source: unit });
});
} },
],
};
}
export default {
chess_char_6_09_a: cello,
};