Files
ark-stronghold-protocol/server/sim/content/kits/ops/op-shining.js
T
sganggs c1bf395207 feedback5 O8: 自选 operator kits 闪灵, 夜莺 (+ her 幻影) and 嵯峨 — every skill, talent, trait and module at every form
- op-shining.js 闪灵 (医师): S1 信条; S2 自动掩护 — the next heal gives its target a 屏障 of atk_scale × ATK for duration s
  (every damage type) and DEF +def while one of her barriers holds (PRTS 备注: barriers add up, own timers, older first, the
  DEF bonus does not stack); S3 教条力场 — allies in range DEF +def %; T1 黑恶魔的庇护 (allies in range DEF +60; PHY-Y stage
  3: +100, 地面 units +40 more); T2 法典 (ASPD +10; PHY-X stage 3: +15, ATK +25 % with S2, SP +0.6/s with S3); PHY-Y heals on
  地面 units ×1.15, PHY-X heals on allies below 50 % ×1.15.
- op-cgbird.js 夜莺 (群愈师): S1 治疗强化·γ型; S2 法术护盾 — every target of the next heal gets an arts-only 屏障
  (shieldType 'arts') and RES +flat; S3 圣域 — range y-4 (data ACTIVE_RANGE), allies in it RES ×(1 + v) and arts dodge;
  T1 白恶魔的庇护 (RES +15; RIN-X stage 3: healing received ×1.05); RIN-X range = its module grid, RIN-Y 4 heals; T2 幻影:
  its own talent (30 % / RIN-Y stage 3 40 % physical dodge, 3 % max HP 流失 per second), her stock (+2 / RIN-Y stage 3 +3
  per deployment, at most 3 — PRTS 备注), a fallen board 幻影 back on its tile after its redeploy time using one and its DP.
- op-saga.js 嵯峨 (尖兵): S1 冲锋号令·γ型 (SP_FULL); S2 除恶 — +4 DP, up to 6 ground enemies of the x-6 cross (重伤 ones
  included), the ones still 重伤 0.7 s later killed by her (PRTS 备注); S3 怒目 — +1 DP/s (20), +0.5 s interval, range +1,
  every blocked enemy, one more attack on a target below half HP; T1 劝善 — her damage is never lethal, an enemy at ≤ 1 HP
  becomes 重伤 (PRTS 术语释义: move −80 %, 禁用近战, 禁疗, no regeneration, a forced 无来源 death 10 s later, none while 消失),
  its killer +2 SP, she never selects a 重伤 enemy (profile skipEnemy); SOL-X stage 3 +15 % damage below half HP; T2 清明
  (once per deployment below 40 %: 70 % physical dodge, 生命回复速度 5 % / SOL-Y stage 3 6.5 % — its blackboard — for 15 / 18 s);
  SOL-X block ATK / DEF +8 %; SOL-Y's 首次部署 cost cut has no battle effect.
- kits/index.js OPERATOR_KIT_FILES: the three files, one line each.
Golden (diy only; every other family unchanged): diy-003…008 are new — 闪灵, 夜莺 and 嵯峨 in every form × module × skill at
tiers 5 and 6; the prototype battles moved from diy-003…006 to diy-009…012 (same pieces; their seed follows the index);
diy-001 / 002 (推进之王) unchanged.
Tests: test/content/op_shining.test.js (9), op_cgbird.test.js (10), op_saga.test.js (15).
2026-10-05 23:58:00 +08:00

143 lines
9.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/op-shining.js — 闪灵 (char_147_shining) 自选 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_147_shining, 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) and PRTS 闪灵 (S2 备注
// "多个屏障吸收量可叠加,持续时间独立计算,优先消耗先生成的屏障,防御力提升效果不可叠加"; PRTS 术语释义 屏障 "若无特殊说明,
// 屏障可吸收全种类伤害").
// - Trait (医师) "恢复友方单位生命": the profession default (one heal on the lowest-HP% injured ally of her 3-3 range).
// Module PHY-Y 干枯剑鞘 "治疗地面单位时治疗量提升15%" (trait bb heal_scale): ×heal_scale on a heal whose target stands on
// a 地面 tile (unit.ground, as 赫默's PHY-Y); PHY-X “使徒” "治疗生命值低于50%的友方单位时治疗量提升15%" (heal_scale,
// hp_ratio): ×heal_scale when the target is below hp_ratio of its max HP before the heal (as 录武官's / 华法琳's PHY-X).
// - T1 黑恶魔的庇护 "攻击范围内的友方单位防御力+60": every ally (operators and summons: 友方单位) standing in her current
// attack range — her own tile included — DEF +def (flat). PHY-Y stage 2+ "防御力+100,地面单位防御力额外+40" (talent
// change def / def_lowland): def_lowland more on a 地面 tile. Refreshed every AURA s; two sources keep the strongest.
// - T2 法典 "攻击速度+10": ASPD +attack_speed. PHY-X stage 2+ "攻击速度+15,装备技能2时获得+25%攻击力,装备技能3时技力自然
// 回复速度+0.6/秒" (talent change attack_speed; the hidden module talent's atk / sp_recovery_per_sec): ATK +atk while 自动
// 掩护 is her picked skill, SP +sp_recovery_per_sec per second while 教条力场 is.
// - S1 信条 (MANUAL, 20 s, data DEFAULT — a heal skill: cast as she is about to heal an injured ally in range): ATK +atk,
// ASPD +attack_speed.
// - S2 自动掩护 (AUTO, 1 charge at rank 4 / 2 at rank 7, data DEFAULT): "下次治疗使目标获得一个持续N秒的屏障,屏障可以吸收相当于
// 闪灵攻击力X%的伤害,同时使目标防御力+Y%" — a "next heal" waits for her heal, as 推进之王's S2 waits for his attack (the
// owner's SP_FULL rule is for AUTO skills that act on nobody). The heal's target gets a 屏障 of atk_scale × her ATK for
// duration s (every damage type) and DEF +def while it holds: each barrier is its own buff (its own timer; older ones
// absorb first — buff order), the DEF bonus one buff per target, kept while one of her barriers lasts [ASSUMED: it goes
// with the last barrier, broken or expired — the 备注 only says it does not stack].
// - S3 教条力场 (MANUAL, 60 s, data DEFAULT): ATK +atk; every ally in her attack range DEF +def (%), refreshed every AURA s.
// Medic: heals only (dmgType heal), ranged, no anti-air question; ground enemies target her normally (no 起飞 / 迷彩 / 隐匿).
import { num, talentBb, moduleBb, traitBb, moduleOn, skillRec, statBuff, up } from '../shared/tier1.js';
const S1 = 'skchr_shining_1';
const S2 = 'skchr_shining_2';
const S3 = 'skchr_shining_3';
const PHY_Y = 'uniequip_002_shining';
const PHY_X = 'uniequip_003_shining';
/** Aura refresh period and buff life (s): an ally leaving the range loses the buff within one refresh (tier-4 AURA). */
const AURA = 0.2;
const AURA_DUR = 0.25;
const bbOf = (chess, id) => skillRec(chess, id)?.bb ?? {};
const pickedSkill = (chess) => chess?.skill?.skillId ?? null;
/**
* Keep buff `key` on every ally standing in `unit`'s current attack range (operators and summons, its own tile included;
* never a 孤立 unit — Battle.alliesInGrid) while `on()` holds, refreshed every AURA s; it lapses AURA_DUR s after the
* source stops refreshing it. Several sources of the same effect keep the strongest `value` (same-named buffs).
* Shared with 夜莺's kit (op-cgbird.js).
* @param {object} battle
* @param {object} unit the source
* @param {{ key: string, value: number, mods: object|((ally: object) => object), on?: () => boolean }} o
*/
export function rangeAura(battle, unit, { key, value, mods, on = () => true }) {
battle.every(AURA, () => {
if (!up(unit) || !on()) return;
for (const a of battle.alliesInGrid(unit)) {
const cur = a.findBuff(key);
if (cur && cur.source !== unit && cur.source?.alive && (cur.data?.v ?? 0) > value && cur.timeLeft > 0.05) continue;
battle.addBuff(a, { key, duration: AURA_DUR, mods: typeof mods === 'function' ? mods(a) : mods, source: unit, data: { v: value }, tags: ['aura'] });
}
}, { owner: unit, immediate: true });
}
/**
* A 屏障 on `target` (闪灵 S2 自动掩护, 夜莺 S2 法术护盾): `shield` HP for `duration` s — of `shieldType` damage only when
* given (damage.js absorbShields), else every type — plus the buff `statKey` (`mods`) while any barrier `key` lasts. PRTS
* 备注 of both skills: "多个屏障吸收量可叠加,持续时间独立计算,优先消耗先生成的屏障,…效果不可叠加" — each barrier is its own
* buff (refresh 'independent': its own timer; absorbShields walks the buffs oldest first), the stat bonus one buff whose
* life follows the barriers (it ends when the last one breaks or expires). Shared with 夜莺's kit (op-cgbird.js).
* @param {object} battle
* @param {object} source the operator
* @param {object} target
* @param {{ key: string, statKey: string, shield: number, duration: number, mods: object, shieldType?: string|null }} o
*/
export function grantBarrier(battle, source, target, { key, statKey, shield, duration, mods, shieldType = null }) {
if (!target || !target.alive || !(shield > 0) || !(duration > 0)) return;
const sync = () => { if (!target.buffs.some((b) => b.key === key)) battle.removeBuff(target, statKey); };
battle.addBuff(target, {
key, refresh: 'independent', maxStacks: 99, duration, shield, shieldType, source, visible: true, onRemove: sync, onExpire: sync,
});
battle.addBuff(target, { key: statKey, refresh: 'extend', duration, mods, source });
battle.fx('shield', { x: target.x, y: target.y, id: target.id, from: source.id });
}
export default {
char_147_shining: (bb, chess) => {
const b1 = bbOf(chess, S1), b2 = bbOf(chess, S2), b3 = bbOf(chess, S3);
const s2 = skillRec(chess, S2);
const t0 = talentBb(chess, 0); // 黑恶魔的庇护: def (PHY-Y stage 2+: def 100, def_lowland)
const t1 = talentBb(chess, 1); // 法典: attack_speed (PHY-X stage 2+: 15)
const hidden = moduleBb(chess); // PHY-X stage 2+: atk (with S2 picked), sp_recovery_per_sec (with S3 picked)
const tb = traitBb(chess); // PHY-Y: heal_scale; PHY-X: heal_scale, hp_ratio
const mod = moduleOn(chess) ? chess.module.id : null;
const picked = pickedSkill(chess);
return {
skills: {
[S1]: { kind: 'duration', heal: true, mods: { atkPct: num(b1.atk), aspd: num(b1.attack_speed) } },
[S2]: {
kind: num(s2?.maxChargeTime, 1) > 1 ? 'charges' : 'instant',
heal: true,
attack: { healScale: 1 }, // the next heal (ai.js doHeal calls onHit per heal target)
onHit({ battle, unit, target }) {
if (!target || target.side !== 'ally') return;
grantBarrier(battle, unit, target, {
key: 'shining:barrier', statKey: 'shining:barrierDef', shield: unit.s.atk * num(b2.atk_scale),
duration: num(b2.duration), mods: { defPct: num(b2.def) },
});
},
},
[S3]: { kind: 'duration', heal: true, mods: { atkPct: num(b3.atk) } },
},
talents: [
{ install(battle, unit) { // 黑恶魔的庇护: allies in her attack range DEF +def; PHY-Y stage 2+: 地面 units +def_lowland
const d = num(t0.def), low = num(t0.def_lowland);
if (!(d > 0) && !(low > 0)) return;
rangeAura(battle, unit, { key: 'talent:shining:def', value: d + low, mods: (a) => ({ defFlat: d + (a.ground ? low : 0) }) });
} },
{ install(battle, unit) { // 法典: ASPD +attack_speed; PHY-X stage 2+: ATK +atk with S2, SP +0.6/s with S3
statBuff(battle, unit, 'talent:shining:aspd', { aspd: num(t1.attack_speed) });
if (picked === S2) statBuff(battle, unit, 'talent:shining:s2', { atkPct: num(hidden.atk) });
if (picked === S3) statBuff(battle, unit, 'talent:shining:s3', { spRecoveryFlat: num(hidden.sp_recovery_per_sec) });
} },
],
install(battle, unit) {
// 教条力场: allies in her attack range DEF +def % while the skill runs
if (picked === S3 && num(b3.def) > 0) {
rangeAura(battle, unit, { key: 'skill:shining:def', value: num(b3.def), mods: { defPct: num(b3.def) }, on: () => !!unit.skill?.active });
}
// module traits: PHY-Y heals on 地面 units, PHY-X heals on allies below hp_ratio, ×heal_scale
const hs = num(tb.heal_scale, 1);
if (!mod || hs === 1) return;
const boosted = mod === PHY_X ? (t) => t.hpRatio < num(tb.hp_ratio) : mod === PHY_Y ? (t) => !!t.ground : null;
if (!boosted) return;
battle.on('heal', (ctx) => {
if (ctx.source !== unit || ctx.opts?.regen || !ctx.target || !boosted(ctx.target)) return;
ctx.amount *= hs;
}, { owner: unit });
},
};
},
};