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

127 lines
6.6 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_14-lumen.js — 流明 (char_4042_lumen) kit, tier 6.
// Conventions of the tier-6 kits: ../shared/tier6.js; kit contract and rules: ../README.md.
import { COLS } from '../../../constants.js';
import {
num, bv, tbb, live, onDefaultSkill, instantKind, ABNORMAL, hasAbnormal, cleanseAbnormal,
} from '../shared/tier6.js';
// ------------------------------------------------------------------------------------------------------------------
// 流明 chess_char_6_14 (疗养师) — S3 灯火不灭; 凡人之愿; 应急处理; module 纯铜单筒望远镜
function lumen(bb, chess, def) {
const t0 = tbb(def, 0), t1 = tbb(def, 1), tb = def?.traitBb || {};
const isDef = onDefaultSkill(chess);
const hs = num(bb.heal_scale, 1);
const abnormalAllies = (battle, unit) => battle.alliesInGrid(unit).filter((a) => hasAbnormal(a)).sort((a, b) => a.hpRatio - b.hpRatio || a.deploySeq - b.deploySeq);
const cleanse = (battle, t) => { cleanseAbnormal(battle, t); };
const skills = {
// S1 沐雨: the next heal also gives its target and the allies around it (8 tiles) aura.heal_scale × ATK healing
// per second for aura.projectile_life_time s
skchr_lumen_1: {
kind: 'instant',
heal: true,
attack: { healScale: 1 },
onHit({ battle, unit, target }) {
if (!target) return;
const sc = bv(bb, 'heal_scale'), life = bv(bb, 'projectile_life_time', 4), iv = Math.max(0.2, bv(bb, 'interval', 1));
if (!(sc > 0) || !(life > 0)) return;
battle.fx('healField', { x: target.x, y: target.y, id: unit.id });
for (const a of battle.alliesInRadius(target.x, target.y, 1.5, target.ownerId)) {
if (a.kind === 'device' || a.s.flags.noHeal || a.profile?.noHeal) continue; // a heal: never on 禁疗 / 孤立
battle.addBuff(a, {
key: `lumen:rain:${unit.id}`, duration: life, interval: iv, refresh: 'replace', visible: true, source: unit,
onTick: ({ unit: x }) => { if (x.hp < x.s.maxHp) battle.heal(unit, x, unit.s.atk * sc, { hot: true }); },
});
}
},
},
// S2 沛霖 (2 charges): heal up to max_target allies of her range for heal_scale × ATK; cast with full charges ⇒
// also cleanse their 异常状态 (蓄力额外效果)
skchr_lumen_2: {
kind: instantKind(def),
heal: true,
onStart({ battle, unit, skill }) {
const full = skill.maxCharges > 1 && skill.charges >= skill.maxCharges - 1; // (this cast already spent one)
const inj = battle.injuredAlliesInKeys(unit.rangeKeys, unit);
const abn = full ? abnormalAllies(battle, unit).filter((a) => !inj.includes(a)) : [];
const tgts = inj.concat(abn).slice(0, Math.max(1, Math.floor(num(bb.max_target, 2))));
for (const t of tgts) {
battle.heal(unit, t, unit.s.atk * num(bb.heal_scale, 1), { skillHeal: true });
if (full) cleanse(battle, t);
}
battle.fx('heal', { x: unit.x, y: unit.y, id: unit.id, n: tgts.length, charged: full });
},
},
};
return {
skills,
skill: {
kind: 'ammo',
heal: true,
ammo: Math.max(1, Math.floor(num(bb['attack@trigger_time'], 4))),
mods: { atkPct: num(bb.atk), aspd: num(bb.attack_speed) },
attack: { healScale: 1 },
onHit({ battle, unit, target }) { if (unit.mem.lumenAbn && target) cleanse(battle, target); },
onAttack(ctx) { // only heals on abnormal targets consume a bullet (engine: ctx.noAmmo — no bullet, no ammoUsed)
if (!ctx.unit.mem.lumenAbn) ctx.noAmmo = true;
ctx.unit.mem.lumenAbn = false;
},
},
talents: [
{ install(battle, unit) { // S3 targeting / bullet bookkeeping
if (!isDef) return;
battle.on('beforeAttack', (ctx) => {
if (ctx.attacker !== unit || !unit.skill?.active) return;
const abn = abnormalAllies(battle, unit);
unit.mem.lumenAbn = abn.length > 0;
if (abn.length) ctx.targets = [abn[0]];
if (ctx.profile && ctx.profile !== unit.profile) ctx.profile.healScale = abn.length ? hs : 1;
}, { owner: unit });
battle.on('tick', () => { // abnormal allies at full HP are healed too
const sk = unit.skill;
if (!sk?.active || !unit.canAct || unit.atkCd > 0 || unit.s.flags.disarm) return;
if (battle.injuredAlliesInKeys(unit.rangeKeys, unit).length) return;
const abn = abnormalAllies(battle, unit);
if (!abn.length) return;
battle.forceAttack(unit, [abn[0]]);
unit.atkCd = unit.s.interval;
}, { owner: unit });
} },
{ install(battle, unit) { // 凡人之愿: healed targets gain 抵抗
const base = bv(t0, 'status_resistance[limit]', 0), special = num(t0['lumen_t_1[special].status_resistance[limit]'], base);
const res = -num(t0.one_minus_status_resistance, -0.5), thr = num(t0.hp_ratio, 0.75);
battle.on('heal', (ctx) => {
const t = ctx.target;
if (ctx.source !== unit || ctx.opts?.regen || !t || !(res > 0) || !(base > 0)) return; // "治疗的目标": not her own 生命回复速度 tick
const after = Math.min(t.s.maxHp, t.hp + ctx.amount);
battle.applyStatus(t, 'resist', { duration: after / t.s.maxHp > thr ? special : base, value: Math.min(1, res), source: unit });
}, { owner: unit, priority: -10 });
} },
{ install(battle, unit) { // 应急处理
const sc = num(t1.heal_scale), cd = num(t1.duration, 12);
let ready = -Infinity;
battle.on('statusApplied', (ctx) => {
const t = ctx.target;
if (!live(unit) || !t || t.side !== 'ally' || !ABNORMAL.has(ctx.status) || battle.time < ready || !(sc > 0)) return;
if (!unit.rangeKeySet?.has(t.tileR * COLS + t.tileC)) return;
ready = battle.time + cd;
battle.heal(unit, t, unit.s.atk * sc);
battle.fx('emergency', { x: t.x, y: t.y, id: t.id, src: unit.id });
}, { owner: unit });
} },
{ install(battle, unit) { // elite module: taunt −1 and 抵抗
const taunt = num(tb.taunt_level), res = -num(tb.one_minus_status_resistance);
if (taunt) battle.addBuff(unit, { key: 'lumen:scope', mods: { taunt }, persist: true, allowDead: true });
// a permanent 抵抗 (survives death/redeploy): any buff with status 'resist' counts (engine resistOf — the
// strongest one applies, never compounding with 凡人之愿 / 灵知)
if (res > 0) battle.addBuff(unit, { key: 'lumen:scopeResist', status: 'resist', visible: true, persist: true, allowDead: true, data: { value: Math.min(1, res) } });
} },
],
};
}
export default {
chess_char_6_14_a: lumen,
};