Files
ark-stronghold-protocol/server/sim/content/kits/ops/op-helage.js
T
sganggs cd180bc752 feedback5 WE1: 庇护 is one effect per unit — 宴, 余, 缪尔赛思, 赫拉格, 左乐 and 赫德雷 join the shared key (follow-up 18)
gamedata_const ba.protect / PRTS 术语 庇护 "受到的物理和法术伤害降低相应比例(同名效果取最高)": the strongest source holds; the
自选 kits already used battle.applyStrongest('protect'), these six multiplied private cuts with it.

- kits/shared/tier1.js: PROTECT, protectMods, PROTECT_TICK_HOLD and holdProtect (applyStrongest on the shared key).
- 宴 / 赫拉格 / 左乐 (below their HP threshold): refreshed every tick and at each hit on them; 余 (blocking) and 缪尔赛思's
  melee copies at their old cadence; 赫德雷's 庇护 joins the key, his CRU-X 物理伤害 + becomes its own instance. 左乐 keeps the
  PRTS S2 ordering (his own 庇护 newer than his barrier cuts behind it); 行险 drops his own held 庇护 for its 0.1 s
  re-judgement [ASSUMED: another source's returns with its next refresh]. 遥's bubbles stay separate (the client's
  damage_resistance[bonus] multiplies with the common damage_resistance[inf]).

Golden: no scenario moved — no unit of the corpus holds two 庇护 at once, and one source alone cuts exactly as before.

Tests: test/content/feedback5-protect.test.js (new, 2); kits_t6 (余), kits_alt_t6 (缪尔赛思), op_hodrer updated to the
shared key; kits_t1t2, op_helage, op_zuole pass. Docs: SIM.md §3, kits/README.md checklist 9.
2026-10-06 15:43:24 +08:00

115 lines
7.9 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/op-helage.js — 赫拉格 (char_188_helage) 自选 operator kit: 6★ 武者 (近卫), an owned-6★ pick of
// the tier-5 and tier-6 自选 slots; every skill, both talents, the trait and both modules at every form.
// Kit contract and the 自选 rules: ../README.md ("How to add an operator (自选)").
//
// Forms (data/backups.json units.char_188_helage, the DIY slot statuses): normal = E2 Lv1, skills at rank 4, no module;
// elite = E2 Lv60, rank 7, the picked module at stage 1 (tier 5) or 3 (tier 6). Potential 0 [ASSUMED: no account].
// Sources: character_table / skill_table / battle_equip_table (zh_CN, as built into backups.json), PRTS 赫拉格 (talents,
// skills, modules: no 备注), PRTS 分支特性信息 §武者 ("常态持有禁疗;通过自身特性/天赋/技能产生的作用于自身的治疗效果会无视自身的
// 禁疗"; "特性治疗于干员每次输出伤害时触发(不局限于攻击)"), gamedata_const ba.berserk 坚忍 ("根据已损失的生命值获得相应比例的
// 属性加成,损失一定比例时达最大加成(同类属性取最高)") and ba.protect 庇护 ("受到的物理和法术伤害降低相应比例(同名效果取最高)"), his
// battle skeleton char_188_helage.skel (Skill: two OnAttack, Skill_2: two, Skill_3_Loop: one).
// - Trait (武者) "不成为其他角色的治疗目标,每次攻击到敌人后回复自身70生命": the profession default (professions.js `musha`: no
// heal from others, `value` HP on every damage instance he deals — PRTS 分支特性信息 武者 — so the second hit of S1 / S2's
// 二连击 heals too), ground-only melee on his 1-1, block 1.
// - T1 月盈星亏 "在场时,自身获得最高+100攻击速度的坚忍(损失70%生命值时达到最大加成)": ASPD + min_attack_speed × the share of
// the HP lost up to 1 − min_hp_ratio (linear, 坚忍), refreshed every tick. SBL-X stage 3: +130 at 50 % lost (the module
// talent change).
// - T2 运筹帷幄 "未阻挡敌人时每秒回复60生命": hp_recovery_per_sec is the 生命回复速度 attribute (the stat hpRecoveryPerSec;
// SIM.md §4: an hpRegen buff, not a heal — no 治疗加成, never stopped by 禁疗), on while he blocks nobody [ASSUMED: PRTS
// has no 备注 on it; the blackboard key is the attribute's, as 角峰 S1 / 宴 S1 in their kits — the 0.2.0 生命回复速度 audit
// lists such 「每秒回复」 + hp_recovery_per_sec effects as open; a heal would reach him too, 武者's own heals ignore his
// 禁疗, and only 治疗加成 would differ]. SBL-Y stage 3
// "未阻挡敌人或生命值低于30%时每秒回复80生命": 80 / s and the hidden module talent (index 2, hp_ratio / hp_recovery_per_sec):
// the same regeneration below hp_ratio while he blocks [ASSUMED: the text's 或 — 80 / s when either holds, never both
// parts at once].
// - Module SBL-X “藏锋” "生命值低于50%时,获得25%的庇护" (a display trait part; the effect is the hidden talent part hp_ratio /
// damage_resistance, merged into talent 0's blackboard): below hp_ratio of his max HP the physical and arts damage he takes
// ×(1 − damage_resistance) (as 宴's elite module) — 庇护, "同名效果取最高": the shared effect (tier1.js holdProtect).
// - Module SBL-Y “热的雪” "被击倒时不撤退且回复30%生命(单次部署只触发1次)" (trait bb hp_ratio): the first lethal blow of a
// deployment leaves him on the field at hp_ratio of his max HP (a kit saver, priority −50 as 斯卡蒂's module).
// - S1 新月 (AUTO, attack SP, DEFAULT): the next attack at atk_scale × ATK, twice (Skill clip: two OnAttack).
// - S2 弦月 (MANUAL, time SP, DEFAULT): `duration` s — ATK +atk, every attack two hits (二连击), 物理闪避 +prob.
// - S3 满月 (MANUAL, time SP, data ACTIVE_RANGE on the running 1-1 + 1): `duration` s — ATK +atk, attack range +1 tile
// forward (ability_range_forward_extend), up to attack@max_target targets at once; back to 1-1 after.
import { num, traitBb, skillRec, toggleBuff, up, onHitOn, holdProtect, PROTECT_TICK_HOLD } from '../shared/tier1.js';
const S1 = 'skchr_helage_1';
const S2 = 'skchr_helage_2';
const S3 = 'skchr_helage_3';
const bbOf = (chess, id) => skillRec(chess, id)?.bb ?? {};
/** The blackboard of the talent with data index `i` ({} when absent; module parts with a new index included). */
const talentAt = (chess, i) => (chess?.talents ?? []).find((t) => t && t.index === i)?.bb ?? {};
export default {
char_188_helage: (bb, chess) => {
const t0 = talentAt(chess, 0), t1 = talentAt(chess, 1), t2 = talentAt(chess, 2);
const tb = traitBb(chess);
const b1 = bbOf(chess, S1), b2 = bbOf(chess, S2), b3 = bbOf(chess, S3);
return {
skills: {
[S1]: {
kind: num(skillRec(chess, S1)?.maxChargeTime, 1) > 1 ? 'charges' : 'instant',
attack: { atkScale: num(b1.atk_scale, 1), hits: 2 },
},
[S2]: {
kind: 'duration',
mods: { atkPct: num(b2.atk), dodgePhys: num(b2.prob) },
attack: { hits: 2 },
},
[S3]: {
kind: 'duration',
mods: { atkPct: num(b3.atk) },
targeting: { rangeExtend: num(b3.ability_range_forward_extend), maxTargets: Math.max(1, Math.floor(num(b3['attack@max_target'], 1))) },
},
},
talents: [
{ install(battle, unit) { // 月盈星亏: 坚忍 ASPD up to min_attack_speed, reached at min_hp_ratio HP (linear in the HP lost)
const maxAs = num(t0.min_attack_speed), minHp = num(t0.min_hp_ratio);
if (!(maxAs > 0) || !(minHp < 1)) return;
const key = 'talent:helage:berserk';
battle.on('tick', () => {
const cur = unit.findBuff(key);
if (!up(unit)) { if (cur) battle.removeBuff(unit, key); return; }
const v = maxAs * Math.max(0, Math.min(1, (1 - unit.hpRatio) / (1 - minHp)));
if (cur && Math.abs((cur.data?.v ?? 0) - v) < 0.05) return;
if (v < 0.05) { if (cur) battle.removeBuff(unit, key); return; }
battle.addBuff(unit, { key, mods: { aspd: v }, data: { v }, tags: ['talent'] });
}, { owner: unit });
} },
{ install(battle, unit) { // 运筹帷幄: 生命回复速度 while blocking nobody; SBL-Y stage 3: or below hp_ratio HP
const regen = num(t1.hp_recovery_per_sec);
if (regen > 0) toggleBuff(battle, unit, 'talent:helage:regen', () => unit.blocking.length === 0, { hpRegen: regen });
const lowHp = num(t2.hp_ratio), lowRegen = num(t2.hp_recovery_per_sec);
if (lowRegen > 0 && lowHp > 0) {
toggleBuff(battle, unit, 'talent:helage:regenLow', () => unit.blocking.length > 0 && unit.hpRatio < lowHp, { hpRegen: lowRegen });
}
} },
],
install(battle, unit) {
// SBL-X “藏锋”: 生命值低于50%时,获得25%的庇护 — the shared 庇护 (holdProtect: the strongest of every source holds),
// refreshed every tick and at each hit on him while he is below
const dr = num(t0.damage_resistance), drBelow = num(t0.hp_ratio);
if (dr > 0 && drBelow > 0) {
const keep = () => { if (up(unit) && unit.hpRatio < drBelow) holdProtect(battle, unit, dr, PROTECT_TICK_HOLD, unit); };
battle.on('tick', keep, { owner: unit });
onHitOn(battle, unit, keep);
}
// SBL-Y “热的雪”: 被击倒时不撤退且回复30%生命(单次部署只触发1次)
const rise = num(tb.hp_ratio);
if (rise > 0) {
battle.on('fatal', (ctx) => {
if (ctx.unit !== unit || ctx.prevented || unit.mem.helageRoseSeq === unit.deploySeq) return;
unit.mem.helageRoseSeq = unit.deploySeq;
ctx.prevented = true;
unit.hp = Math.max(1, unit.s.maxHp * rise);
battle.fx('undying', { x: unit.x, y: unit.y, id: unit.id });
}, { owner: unit, priority: -50 });
}
},
};
},
};