Files
ark-stronghold-protocol/server/sim/content/kits/ops/op-pallas.js
T
sganggs c454b61075 feedback5 O9: 自选 operator kits 帕拉斯, 止颂 and 贝洛内 (with his 牵绊) — every skill, talent, trait and module (stage 1 / 3) at every form
- op-pallas.js (char_485_pallas, 教官): 英雄的诞生 精力充沛 on every 米诺斯 operator (one buff per ally, the
  highest active bonus — her S3's too), 女神的振奋 on every damage instance (her 1-1, through 禁疗; INS-Y stage 3 the
  米诺斯 extra), S1 two strikes, S2 range +1 / ATK / stun (data DEFAULT: build-data's ATTACK_RANGE_CHANGE misses
  "攻击范围向前延伸一格" — reported, not worked around), S3 three targets and the front-holder buff (low ground,
  operators only, passed on retreat / deploy). INS-Y's "可以额外部署在远程位" is a prep placement rule (owner's
  decision of 2026-10-04): unchanged, reported.
- op-lessng.js (char_4011_lessng, 无畏者): 苦痛专注 (hit-time cut while blocking, 无来源 / allies / himself
  included), 痛楚砺刃 (any damage aimed at him, dodged too), S2 the deploy window (2 strikes, ATK, talent ×2), S3
  status immunity, blocked-target scale, the cast under a 异常状态 (600 arts first, cleanse, then HP; 沉默 does
  not hold it back), DRE-X ×1.15 on a blocked target, DRE-Y once-per-deployment save and stage 3's 12 % DEF ignore.
- op-demetr.js (char_4037_demetr, 斗士): 【手段】 (one per enemy, stacks before the hit, 8 and 停顿 under S2, back
  to 5 on the 0.1 s update), the HP-ratio damage bonus, 街头直觉 from every deployment (a 【移动】 too), S1 two
  strikes, S2 3-13 / three targets, S3 the 【移动】s (x-2 around his located tile, buildable low-ground tiles,
  in-place move without a target, re-target after 1 s without damage or when the elite he hit falls), 牵绊
  (无敌 / untargetable / 孤立), the fatal-hit 不死 + end, the 【返回】 (SP emptied, a short 不死) and the forced
  retreat; FGT-Y ASPD above half HP.
- kits/index.js: the three files appended to OPERATOR_KIT_FILES.
- Tests: test/content/op_pallas.test.js (9), op_lessng.test.js (10), op_demetr.test.js (10).
- Golden: only diy moved — diy-049 … 054 now field the three operators (every form × module × skill), the
  prototype / reserve battles moved down to diy-054 … 058 (new ids, same packing); diy-001 … 048 and every other
  family unchanged.
2026-10-06 01:30:26 +08:00

169 lines
11 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-pallas.js — 帕拉斯 (char_485_pallas) 自选 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_485_pallas, 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 帕拉斯 (英勇的祝福 备注
// "持有增益的干员撤退时,增益立刻转交至帕拉斯;自身持有增益,满足条件的干员部署时,帕拉斯的增益立刻转交给对方"; 女神的振奋 备注
// "本天赋无视自身获得的禁疗,且必定进行治疗"); gamedata_const ba.strong 精力充沛 "生命值高于一定比例时获得一定属性加成(同类属性取
// 最高)"; the client's battle data — charpack char_485_pallas (英雄的诞生: an aura on every ally tagged `minos`; S3: an aura on
// her 1-1 for operators only, professionMask without TOKEN / TRAP), skchr_pallas_1 (`_additionalTimes` 1), buff_template_data
// `pallas_t_2` / `pallas_t_2[heal]`, `pallas_s_3[peak]`, `pallas_e_t_2[trigger]` and [uc]equips pallas_equip_2_x_p2.
// - Trait (教官) "可以攻击到较远敌人,攻击自身未阻挡的敌人时攻击力提升至120%": the profession profile (professions.js
// instructor: ×atk_scale on a target she does not block, her attacks only; range 2-2). Module INS-X “故乡的山岩” raises
// it to 130% (its traitOverride bb atk_scale, read by the profile through the composed record).
// - T1 英雄的诞生 "在场时,所有【米诺斯】干员生命值高于80%时获得+25%攻击力的精力充沛" (bb peak_performance.hp_ratio / .atk):
// while she is deployed, every allied operator of nation minos (her included — the aura has no self exclusion) gets ATK
// +atk while its HP ratio is above hp_ratio. INS-X stage 3: above 50 %, +30 % (the module's talent change). 精力充沛 of
// several sources is "同类属性取最高": one buff per ally carrying the highest ATK bonus whose HP condition holds (her T1
// and her S3 alike, several 帕拉斯 of a shared field too), re-evaluated every tick. 帕拉斯 is the only minos operator of
// the data (chess.json and backups.json), so in practice the talent is hers.
// - T2 女神的振奋 "每攻击一名敌人时为自身与身前一格的我方干员恢复40点生命值" (bb value): pallas_t_2 is ON_OUTPUT_DAMAGE — every
// damage instance she deals (S1's two strikes, each S3 target) heals every ally on her 1-1 (her tile and the tile in
// front, rotated with her) `value` HP, a heal that ignores 禁疗 (FixedValueHeal _ignoreHealFree; PRTS 备注) but not
// "不成为其他角色的治疗目标" (the heal's selector is HEAL-purpose). INS-Y “赫里亚之辉” stage 2+: value 60 and "并使所有【米诺斯】干员
// 额外恢复40点生命值" (talent bb pallas_e_t_2.value): pallas_e_t_2[trigger] (ON_OUTPUT_DAMAGE) uses pallas_e_heal on every
// ally its aura marks — tags minos, _selfOption INCLUDE, so herself too — for that much more (also ignoring 禁疗).
// - Module INS-Y "可以额外部署在远程位" (hidden module talent buildable_type 2) is a placement rule of the prep, not of the
// battle: by the owner's decision of 2026-10-04 (shared/highGround.js) only elite 歌蕾蒂娅 + HOK-Y may take a 高台, so a
// 帕拉斯 piece stays on the melee tiles — an open question for the owner (DESIGN draft 0.2.0.o9), nothing in this kit.
// - S1 胜利的连击 (AUTO, attack SP 3; data DEFAULT): the next attack strikes its target twice for atk_scale × ATK physical
// (skchr_pallas_1 `_additionalTimes` 1, `_triggerDelta` 0.1 s: the second strike 0.1 s after the first — both land in the
// attack here [ASSUMED: the 0.1 s is not modelled]); the instructor ×1.2 applies to each strike.
// - S2 信念的长鞭 (MANUAL, 20 / 22 s): attack range +ability_range_forward_extend tile forward (2-2 ⇒ 4 tiles: targeting
// rangeExtend, the card's live range follows), ATK +atk, every attack attack@buff_prob to stun its target attack@stun s.
// Its trigger is the data's DEFAULT: tools/build-data.mjs ATTACK_RANGE_CHANGE does not read "攻击范围向前延伸一格", so the
// owner's ACTIVE_RANGE rule (a running range that strictly contains the own one) does not reach it — reported, not
// worked around (the brief: report a trigger that looks wrong).
// - S3 英勇的祝福 (MANUAL, 30 s; data DEFAULT): ATK +atk, attacks hit attack@max_target (3) targets; the operator deployed
// on the tile in front of her, when that tile is low ground (pallas_s_3[peak] CheckHeightTypeOfRootTile LOWLAND: on any
// other tile the buff is finished on it), holds "生命值高于80%获得+35%攻击力的精力充沛、防御力+20%、阻挡数+1"
// (attack@peak_performance.hp_ratio / .atk, attack@def, attack@block_cnt) — else she holds it herself; the holder is
// re-read every tick, so it passes to her the moment the front operator leaves and back when one deploys there (PRTS
// 备注). Summons do not hold it (the aura's professionMask). The DEF / block part is per source (independentCharacterSource).
// Melee, ground-only (data canHitFly false: no anti-air); ground enemies target her normally (no 起飞 / 迷彩 / 隐匿).
import { num, talentBb, skillRec, up, alliesInGridOf } from '../shared/tier1.js';
import { frontOf } from '../../../dir.js';
const S1 = 'skchr_pallas_1';
const S2 = 'skchr_pallas_2';
const S3 = 'skchr_pallas_3';
/** The nation of 英雄的诞生 / pallas_e_heal (the client's `minos` tag = character_table nationId minos). */
const MINOS = 'minos';
/** T2's heal area: her 1-1 (pallas_t_2 `_rangeId` 1-1) — her tile and the tile in front. */
const RANGE_1_1 = Object.freeze([[0, 0], [0, 1]]);
/** The one 精力充沛 buff of an ally (ba.strong "同类属性取最高"). */
export const PEAK_KEY = 'talent:pallas:peak';
/** The S3 holder's DEF / block buff, per 帕拉斯 (independentCharacterSource). */
export const holderKey = (unit) => `skill:pallas:blessing#${unit.id}`;
const bbOf = (chess, id) => skillRec(chess, id)?.bb ?? {};
const isMinos = (a) => !!a && a.kind === 'op' && (a.def?.raw?.nationId ?? a.def?.nationId) === MINOS;
/**
* The 精力充沛 sources of a battle (a Set of `(ally) => ATK bonus`, 0 when one does not apply); the first registration
* installs the battle-wide tick that keeps, on every deployed ally, one buff with the highest bonus whose HP condition
* holds (never a sum: "同类属性取最高").
*/
const PEAKS = new WeakMap();
function peakSources(battle) {
let set = PEAKS.get(battle);
if (set) return set;
PEAKS.set(battle, (set = new Set()));
battle.on('tick', () => {
for (const a of battle.allyUnits) {
if (!a.alive || !a.deployed || a.kind === 'device') continue;
let v = 0;
for (const f of set) v = Math.max(v, f(a));
const cur = a.findBuff(PEAK_KEY);
if ((cur?.data?.v ?? 0) === v) continue;
if (v > 0) battle.addBuff(a, { key: PEAK_KEY, mods: { atkPct: v }, data: { v }, tags: ['talent'] });
else battle.removeBuff(a, PEAK_KEY);
}
});
return set;
}
/**
* The holder of 英勇的祝福's buff: the operator deployed on the tile in front of her when it stands on low ground (and her
* aura may select it), else herself.
*/
export function blessingHolder(battle, unit) {
const [fr, fc] = frontOf(unit.tileR, unit.tileC, unit.dir, 1);
const a = battle.grid.inRect(fr, fc) ? battle.unitAt(fr, fc) : null;
if (a && a !== unit && a.side === 'ally' && a.kind === 'op' && a.deployed && a.ground !== false && battle.grid.isLow(fr, fc)
&& battle.allySelectable(a, unit)) return a;
return unit;
}
export default {
char_485_pallas: (bb, chess) => {
const t0 = talentBb(chess, 0); // 英雄的诞生 (INS-X stage 3: hp_ratio 0.5, atk 0.3)
const t1 = talentBb(chess, 1); // 女神的振奋 (INS-Y stage 3: value 60, pallas_e_t_2.value 40)
const b1 = bbOf(chess, S1), b2 = bbOf(chess, S2), b3 = bbOf(chess, S3);
const blessDef = num(b3['attack@def']), blessBlock = num(b3['attack@block_cnt']);
const blessHp = num(b3['attack@peak_performance.hp_ratio'], 0.8), blessAtk = num(b3['attack@peak_performance.atk']);
/** Move 英勇的祝福 to the current holder (DEF / block here; its 精力充沛 goes through the shared tick). */
const bless = ({ battle, unit }) => {
const st = unit.mem.pallasBless ?? (unit.mem.pallasBless = { holder: null });
const h = unit.skill?.active && up(unit) ? blessingHolder(battle, unit) : null;
if (st.holder === h) return;
if (st.holder) battle.removeBuff(st.holder, holderKey(unit));
st.holder = h;
if (h) battle.addBuff(h, { key: holderKey(unit), source: unit, mods: { defPct: blessDef, blockCnt: blessBlock }, tags: ['skill'], visible: true });
};
return {
skills: {
[S1]: { kind: 'instant', attack: { atkScale: num(b1.atk_scale, 1), hits: 2 } },
[S2]: {
kind: 'duration',
mods: { atkPct: num(b2.atk) },
targeting: { rangeExtend: Math.max(0, Math.round(num(b2.ability_range_forward_extend))) },
attack: {
onHit({ battle, unit, target }) {
if (target && target.alive && target.side === 'enemy' && battle.rng.chance(num(b2['attack@buff_prob']))) {
battle.applyStatus(target, 'stun', { duration: num(b2['attack@stun']), source: unit });
}
},
},
},
[S3]: {
kind: 'duration',
mods: { atkPct: num(b3.atk) },
targeting: { maxTargets: Math.max(1, num(b3['attack@max_target'], 3)) },
onStart: bless,
onTick: bless,
onEnd: bless,
},
},
talents: [
{ install(battle, unit) { // 英雄的诞生 (and S3's 精力充沛, same 同类取最高 buff)
const hp0 = num(t0['peak_performance.hp_ratio'], 0.8), atk0 = num(t0['peak_performance.atk']);
peakSources(battle).add((a) => {
if (!up(unit) || !battle.allySelectable(a, unit)) return 0;
let v = 0;
if (atk0 > 0 && isMinos(a) && a.hpRatio > hp0) v = atk0;
if (blessAtk > 0 && unit.mem.pallasBless?.holder === a && unit.skill?.active && a.hpRatio > blessHp) v = Math.max(v, blessAtk);
return v;
});
} },
{ install(battle, unit) { // 女神的振奋 (+ INS-Y stage 2+: the minos extra heal)
const value = num(t1.value), extra = num(t1['pallas_e_t_2.value']);
if (!(value > 0) && !(extra > 0)) return;
battle.on('damaged', (c) => {
if (c.source !== unit || !c.target || c.target.side !== 'enemy' || c.type === 'element' || !up(unit)) return;
if (value > 0) {
for (const a of alliesInGridOf(battle, unit, RANGE_1_1)) battle.heal(unit, a, value, { self: a === unit, ignoreHealFree: true, tags: ['talent'] });
}
if (extra > 0) {
for (const a of battle.alliesFor(unit)) if (isMinos(a)) battle.heal(unit, a, extra, { self: a === unit, ignoreHealFree: true, tags: ['talent', 'module'] });
}
}, { owner: unit });
} },
],
};
},
};