Files
ark-stronghold-protocol/server/sim/content/kits/ops/chess_char_5_09-cetsyr.js
T
sganggs 5c6524e1ad feedback5 WL: 生命回复速度 is not 治疗 — the 吟游者 trait, 调香师, 瑕光 S2, 铃兰 S3 and 锡人 give an hpRegen buff, so 无法被友方治疗 / 禁疗 units get it and 治疗加成 does not scale it (GitHub #96 / #137)
PRTS: 分支特性信息 吟游者 「特性为基于自身攻击力来增加受益者的“生命回复速度”属性」 (魔王, 浊心斯卡蒂); the 备注 of 调香师 熏衣草,
瑕光 S2, 铃兰 S3 and 锡人 S2 「生命恢复的提供方式为增加目标的“生命回复速度”属性…不受治疗加成和禁疗影响」 (铃兰: none in the
first second, refreshed every second); 异常效果 禁疗 「增减生命回复速度…的效果不会被识别为治疗类能力」. These were heals from
the provider, which damage.js refused on 收割者 / 不屈者 / 武者 (noHeal) and under 禁疗 — 隐德来希 gained nothing under 魔王.
This is the separation proposed in PR #135 by @SpiritedAwayCN, written anew (the PR's patch is not used):
- professions.js bardRegen: the trait is an hpRegen buff per bard refreshed every 0.25 s; 魔王's 微尘 ×1.5 goes through
  its `bardRegen` hook (both 魔王 kits); 浊心斯卡蒂's pulse and the 海嗣 token use it too.
- 调香师 (every ally), 瑕光 S2 (the skill range, removed at its end), 铃兰 S3 (from the first full second, removed at its
  end), 锡人 (per 炼金单元, its cached ATK): hpRegen buffs instead of battle.heal.
- damage.js: a `regen` tick takes no healing multiplier and a heal hook cannot change its amount (no 治疗加成 on any
  生命回复速度); 流明's 凡人之愿 and 华法琳's S1 bonus skip a regen tick of their own.
Direct heals are unchanged (still refused / scaled); 魔王 S3's redistribution and 纯烬's HoT are untouched. Other
per-second recoveries without such a note stay heals (引星棘刺, 塞雷娅 S3, 流明 / 纯烬 HoTs; 余 闲云隐市 and 天马之盔 left open).

Golden moved (every one fields 魔王 / 浊心斯卡蒂 / 调香师 / 瑕光 S2 / 铃兰 / 锡人): roster-004, roster-005, roster-006,
roster-011, roster-013, roster-015, roster-016, roster-018, roster-023, roster-025, roster-030, roster-032, roster-035,
roster-037, roster-039, roster-041, roster-042, roster-045, roster-046; bond-deputShip-high, bond-emptyShip-low,
bond-emptyShip-high, bond-indomShip-high, bond-kazimierzShip-low, bond-kazimierzShip-high, bond-sargonShip-low,
bond-sargonShip-high, bond-skillfulShip-high; boss-boss_1-pair, boss-boss_1-solo, boss-boss_2-solo, boss-boss_3-pair,
boss-boss_3-solo, boss-boss_4-solo, boss-boss_5-pair, boss-boss_5-solo, boss-boss_6-solo, boss-boss_7-pair,
boss-boss_7-solo, hidden-boss_8-solo, hidden-boss_9-pair, hidden-boss_9-solo, hidden-boss_10-solo, unite-1;
solo-NORMAL-1, solo-HARD-1, solo-ABYSS-1, coop4-ABYSS-7, coop2-NORMAL-8-serverrun, coop2-NORMAL-14-standins.
Docs: SIM.md (heal pipeline, bard row), PLAYING.md, kits/README.md (checklist item 11).
2026-10-05 21:13:15 +08:00

128 lines
7.2 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_09-cetsyr.js — 魔王 (char_4134_cetsyr) kit, tier 5 (hidden).
// Conventions of the tier-5 kits: ../shared/tier5.js; kit contract and rules: ../README.md.
import { COLS } from '../../../constants.js';
import { rotateOffset } from '../../../dir.js';
import { AURA_IV, AURA_DUR, num, talent, traitBb, skillGrid, isOp, leaderOf, whileOn } from '../shared/tier5.js';
/** 魔王 T1 微尘: collision radius of one mote (PRTS 备注 0.4); orbit radius / speed come from the talent bb. */
const MOTE_HIT_RADIUS = 0.4;
export default {
// ---------------------------------------------------------------------------------------------------------------
// 魔王 — bard (trait: 生命回复速度 +10 % ATK on the allies in range, professions.js bardRegen). S3 编织重构现世 (30 s):
// skill range, trait 65 %, motes never vanish, other allies in range get 鼓舞 = +65 % of her max HP, HP of everyone in
// range is equalised every 2 s.
// T1 过往尘埃: 3 motes orbit her; one colliding with an operator vanishes and gives it ×1.5 of her trait for 6 s, then
// respawns after 6 s.
// T2 魔王残响: allies take −10 % damage from Sarkaz enemies. Module (elite): ≥2 other ops in range → ATK +8 %.
chess_char_5_09_a: (bb, chess, def) => {
const t0 = talent(chess, 0), t1 = talent(chess, 1), tb = traitBb(chess), tm = talent(chess, -1);
const baseRatio = num(tb['attack@atk_to_hp_recovery_ratio'], 0.1);
const skillRatio = num(bb['attack@atk_to_hp_recovery_ratio'], baseRatio);
const inspire = num(bb.max_hp);
const shareIv = Math.max(0.1, num(bb['attack@cetsyr_s_3[cal_hp_ratio].interval'], 2));
const inspireAll = (battle, unit) => {
if (!(inspire > 0)) return;
const hp = unit.s.maxHp * inspire;
for (const a of battle.alliesInGrid(unit)) {
if (a !== unit) battle.addBuff(a, { key: 'cetsyr:inspire', duration: AURA_DUR, visible: true, status: 'inspire', mods: { hpFlat: hp }, data: { src: unit } });
}
};
return {
skill: {
kind: 'duration',
targeting: skillGrid(chess, def) ? { rangeGrid: skillGrid(chess, def) } : undefined,
onStart({ battle, unit }) {
if (unit.profile) unit.profile.auraRatio = skillRatio;
unit.mem.shareAcc = 0;
unit.mem.inspireAcc = 0;
inspireAll(battle, unit);
battle.fx('healAoe', { x: unit.x, y: unit.y, id: unit.id, r: 2.5 });
},
onTick({ battle, unit, dt }) {
unit.mem.inspireAcc += dt;
if (unit.mem.inspireAcc >= AURA_IV) { unit.mem.inspireAcc = 0; inspireAll(battle, unit); }
unit.mem.shareAcc += dt;
if (unit.mem.shareAcc < shareIv - 1e-9) return;
unit.mem.shareAcc -= shareIv;
const group = battle.alliesInGrid(unit).filter((a) => a.alive && a.kind !== 'device');
let hp = 0, max = 0;
for (const a of group) { hp += a.hp; max += a.s.maxHp; }
if (!(max > 0) || group.length < 2) return;
const ratio = hp / max;
for (const a of group) a.hp = Math.max(1, Math.min(a.s.maxHp, a.s.maxHp * ratio));
battle.fx('hpShare', { x: unit.x, y: unit.y, id: unit.id, ratio: Math.round(ratio * 1000) / 1000 });
},
onEnd({ battle, unit }) {
if (unit.profile) unit.profile.auraRatio = baseRatio;
for (const a of battle.allyUnits) {
const b = a.findBuff('cetsyr:inspire');
if (b && b.data.src === unit) battle.removeBuff(a, b);
}
},
},
talents: [
{ install(battle, unit) { // 过往尘埃: cnt motes orbit her (radius range_radius, dynamic_spd °/s, PRTS hit radius 0.4)
const cnt = Math.max(0, Math.floor(num(t0.cnt, 3))), orbit = num(t0.range_radius, 1.15);
const spd = (num(t0.dynamic_spd, 30) * Math.PI) / 180;
const cd = num(t0.cooldown, 6), dur = num(t0.talent_duration, 6), mul = num(t0['attack@trait_mul'], 1.5);
const motes = new Array(cnt).fill(0); // per orbit slot: the time its mote is back (vanished → respawn after cd)
const reach = orbit + MOTE_HIT_RADIUS + 0.01;
battle.on('deploy', (c) => { if (c.unit === unit) motes.fill(0); }, { owner: unit });
whileOn(battle, unit, AURA_IV, () => {
if (!cnt) return;
const keep = !!unit.skill?.active; // S3: "微尘"不再消失
// "微尘"碰撞友方干员: operators only; a mote never collides with an operator already under a mote effect
const cands = battle.alliesInRadius(unit.x, unit.y, reach, null).filter((a) => a !== unit && isOp(a) && !a.findBuff('cetsyr:mote'));
if (!cands.length) return;
const t = battle.time - num(unit.deployedAt);
for (let k = 0; k < cnt; k++) {
if (motes[k] > battle.time + 1e-9) continue;
const ang = spd * t + (2 * Math.PI * k) / cnt;
// the orbit turns with his direction: angle 0 = straight ahead, π/2 = his left hand
const [fr, fc] = unit.fwd, [lr, lc] = rotateOffset(1, 0, unit.dir);
const mx = unit.x + orbit * (fc * Math.cos(ang) + lc * Math.sin(ang)), my = unit.y + orbit * (fr * Math.cos(ang) + lr * Math.sin(ang));
let hit = null, hd = Infinity;
for (const a of cands) {
if (a.findBuff('cetsyr:mote')) continue;
const d = Math.hypot(a.x - mx, a.y - my);
if (d <= MOTE_HIT_RADIUS + 1e-9 && d < hd) { hd = d; hit = a; }
}
if (!hit) continue;
battle.addBuff(hit, { key: 'cetsyr:mote', duration: dur, visible: true, data: { src: unit, mul } });
if (!keep) motes[k] = battle.time + cd;
battle.fx('mote', { x: hit.x, y: hit.y, id: hit.id });
}
});
// "受到魔王特性效果提升至1.5倍": his trait's 生命回复速度 on that operator (professions.js bardRegen hook)
battle.on('bardRegen', (c) => {
if (c.unit !== unit) return;
const b = c.target.findBuff('cetsyr:mote');
if (b) c.value *= num(b.data.mul, 1);
}, { owner: unit });
} },
{ install(battle, unit) { // 魔王残响
const dr = num(t1.damage_resistance);
if (!(dr > 0)) return;
battle.on('hit', (c) => {
if (!c.target || c.target.side !== 'ally' || !c.source || c.source.side !== 'enemy') return;
if (!(c.source.def?.tags || []).includes('sarkaz') || leaderOf(battle, unit) !== unit) return;
c.dmg.mul *= 1 - dr;
}, { owner: unit });
} },
],
install(battle, unit) { // module: ≥ cnt other ops in the (base) range → ATK +8 %
const atk = num(tm.atk), need = num(tm.cnt, 2);
if (!chess?.isGolden || !atk) return;
whileOn(battle, unit, AURA_IV, () => {
const keys = new Set(unit.baseRangeKeys || []);
let n = 0;
for (const a of battle.allies()) if (a !== unit && isOp(a) && keys.has(a.tileR * COLS + a.tileC)) n++;
if (n >= need) battle.addBuff(unit, { key: 'cetsyr:module', duration: AURA_DUR, mods: { atkPct: atk } });
});
},
};
},
};