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).
127 lines
6.6 KiB
JavaScript
127 lines
6.6 KiB
JavaScript
// 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,
|
||
};
|