Files
ark-stronghold-protocol/server/sim/content/kits/ops/standin-acnipe.js
T
sganggs f317adf003 feedback5 K2: stand-in kits for Stormeye, Pith and Raidian — every skill, talent and module at every form
Stormeye (char_611_acnipe; 空弦, 莫斯提马, 百炼嘉维尔 / 妮芙, 蕾缪安 / 异客): 风坠 rolls on every attack instance
(acnipe_t_1: ATK × atk_scale before DEF; S3 talent@prob), 风雨欲来 +0.2 SP/s after `delay` s without an attack
[ASSUMED reading], S1 ATK / ASPD / 100 DEF ignored, S2 an endless toggle (ATK +5 %, 2 targets), S3 3 targets × double
hit plus one physical ATK × 1.0 instance on a target at >= 90 % HP on the attack's first hit (acnipe_s_3[extra], no
风坠 / MAR-X on it); MAR-X stays the fastshot profile's flyScale.

Pith (char_612_accast; 烛煌, 焰影苇草 / 迷迭香 / 荒芜拉普兰德): splash from the trait blackboard, 12 RES ignored, ATK
+10 / +20 % on herself and the 【术师】 of the 4 tiles next to her, S1 ASPD + splash 1.5, S2 ASPD + ATK on every 【术师】,
S3 one ATK × atk_scale_aoe arts hit on every enemy of her range at the cast, then 2 targets, ASPD, splash 1.2 (PRTS
备注); SPC-X range (moduleRangeUp).

Raidian (char_614_acsupo; 灵知, 夕 / 安洁莉娜, 浊心斯卡蒂 / 仇白): 同调 on herself and the operators next to her (×
talent_scale in S3), 诱引 per PRTS 命中率 — one roll per enemy attack in her range, a miss cancels the whole attack
before the enemy riders (hit priority 300), S1 / S2 from the blackboards (S2 flat −0.1 / −0.2 s, 停顿 1.1 / 1.2 s), S3
y-4 + ATK + 脆弱 / 虚弱 aura with the 虚弱 stacking as 90 / 80 % (stackAs); S3 casts by the data's ACTIVE_RANGE;
DEC-X +0.2 SP/s with an enemy in range.

Triggers stay the data's (AUTO attack buffs Stormeye S2 / Pith S1 keep DEFAULT, as the W5C AUTO audit). No golden
scenario fields a stand-in: digests unchanged. Tests: test/content/standin_acnipe / _accast / _acsupo (27).
2026-10-05 18:34:46 +08:00

94 lines
6.2 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/standin-acnipe.js — Stormeye (char_611_acnipe) 补位 stand-in kit: 6★ 速射手 for 空弦 (T3, S3),
// 莫斯提马 (T4, S2), 百炼嘉维尔 / 妮芙 (T5, S3 + MAR-X) and 蕾缪安 / 异客 (T6, S3 + MAR-X); every skill at every form.
// Kit contract and the stand-in rules: ../README.md ("Stand-in kits").
//
// Sources: data/backups.json (character_table / skill_table / battle_equip_table at E2 Lv1 rank 4 and E2 Lv60 rank 7)
// and the client's battle data (charpack char_611_acnipe, battle/buff_template_data.json — the templates quoted below).
// - 速射手 trait 优先攻击空中单位 (data `targetPriority` 'fly', `canHitFly`); module MAR-X "攻击空中单位时攻击力提升至110%"
// (trait atk_scale 1.1, template acnipe_e_002_trait: CheckMotionMode FLY → AtkScaleUp on every damage calculation) is
// the fastshot profile's own (professions.js fastshot `flyScale` from the trait blackboard): no kit code. The module's
// HP / ATK are in the stats; at level 3 it raises 风坠 to 190 % (the composed talents).
// - T1 风坠 "攻击时有25%的概率攻击力提升至180%" — acnipe_t_1: ON_CALCULATE_DAMAGE → Dice(prob) → AtkScaleUp(atk_scale): a roll
// on EVERY damage instance of an attack (each target, each hit of S3's double hit), ATK × atk_scale before DEF / RES.
// S3 raises `prob` to its talent@prob.
// - T2 风雨欲来 "自身未进行攻击时,技力回复速度+0.2/秒" (sp_recovery_per_sec, delay 2): +0.2 SP/s once she has made no attack
// for `delay` s — [ASSUMED] the reading of `delay` (no text or PRTS note gives it), counted from her deployment too.
// - S1 破空: ATK +atk, ASPD +attack_speed, 无视 def_penetrate_fixed DEF (defIgnoreFlat) for its 20 s.
// - S2 心手合一 (自动触发, 持续时间无限): ATK +atk and attack@max_target targets until she is knocked out (a toggle). Trigger:
// the data's DEFAULT — an AUTO attack buff waits for her next attack like 能天使 S3 / 乌尔比安 S2 (W5C's AUTO audit:
// "attack modes / attack buffs" keep waiting); being endless it changes nothing but the moment of the cast.
// - S3 旋臂: attack@max_target targets, attack@times hits each (二连击), 风坠 at talent@prob; and "攻击目标生命值高于90%时额外
// 造成1次伤害": the S3 attack carries acnipe_s_3[extra] (`_onlyFeedActiveBuffToFirstOne`: the first hit of the double hit
// only) — FilterByTargetHpRatio GE attack@hp_ratio, then AdvancedApplyDamage PHYSICAL attack@atk_scale_extra × ATK with
// `_emitSourceOnCalculateDamage` false: one extra physical instance per target per attack, which neither 风坠 nor the
// MAR-X trait touches (no isAttack: no "攻击时" effect sees it). [ASSUMED] the HP ratio is read when that first hit lands,
// before its damage (as 异客's 机理分析 kit reads it; the order of the buff and the hit is not in the data).
import { num, talentBb, skillRec, spTimeBonus } from '../shared/tier1.js';
const S1 = 'skchr_acnipe_1';
const S2 = 'skchr_acnipe_2';
const S3 = 'skchr_acnipe_3';
/** Tag of S3's extra damage instance (not an attack: 风坠 skips it). */
const EXTRA = 'acnipe:extra';
const bbOf = (chess, id) => skillRec(chess, id)?.bb ?? {};
const count = (v, d) => Math.max(1, Math.floor(num(v, d)));
const s3On = (unit) => !!(unit.skill && unit.skill.active && unit.skill.id === S3);
export default {
char_611_acnipe: (bb, chess) => {
const t0 = talentBb(chess, 0), t1 = talentBb(chess, 1);
const b1 = bbOf(chess, S1), b2 = bbOf(chess, S2), b3 = bbOf(chess, S3);
return {
skills: {
[S1]: { kind: 'duration', mods: { atkPct: num(b1.atk), aspd: num(b1.attack_speed), defIgnoreFlat: num(b1.def_penetrate_fixed) } },
[S2]: { kind: 'toggle', mods: { atkPct: num(b2.atk) }, targeting: { maxTargets: count(b2['attack@max_target'], 2) } },
[S3]: {
kind: 'duration',
targeting: { maxTargets: count(b3['attack@max_target'], 3) },
attack: {
hits: count(b3['attack@times'], 2),
// the extra instance, after the attack's hits on that target (marked by the install's first-hit check)
onEachHit({ battle, unit, target, kind }) {
const due = unit.mem.acnipeExtra;
if (kind !== 'main' || !due || !due.has(target)) return;
due.delete(target);
if (target.alive) battle.dealDamage(unit, target, { amount: unit.s.atk * num(b3['attack@atk_scale_extra'], 1), type: 'phys', isSkill: true, tags: ['skill', EXTRA] });
},
},
},
},
talents: [
{ install(battle, unit) { // 风坠: every damage instance of an attack rolls
const scale = num(t0.atk_scale, 1), base = num(t0.prob);
if (!(scale > 0) || scale === 1) return;
battle.on('hit', (ctx) => {
const d = ctx.dmg;
if (ctx.source !== unit || !ctx.target || ctx.target.side !== 'enemy' || !d.isAttack || d.type === 'element' || (d.tags || []).includes(EXTRA)) return;
const p = s3On(unit) ? num(b3['talent@prob'], base) : base;
if (p > 0 && battle.rng.chance(p)) d.amount *= scale;
}, { owner: unit });
} },
{ install(battle, unit) { // 风雨欲来: no attack for `delay` s (her deployment counts as the start) ⇒ +SP/s
const delay = num(t1.delay, 0);
spTimeBonus(battle, unit, num(t1.sp_recovery_per_sec), () => battle.time - Math.max(unit.lastAttackAt, unit.deployedAt) >= delay - 1e-9);
} },
],
install(battle, unit) {
if (unit.skill?.id !== S3) return;
// S3 [extra]: the first hit of each attack on a target checks its HP ratio before that hit's damage
const ratio = num(b3['attack@hp_ratio'], 0.9);
battle.on('hit', (ctx) => {
const t = ctx.target, d = ctx.dmg;
if (ctx.source !== unit || !t || t.side !== 'enemy' || !d.isAttack || d.isSplash || !s3On(unit)) return;
const seen = unit.mem.acnipeSeen ??= new WeakMap();
if (d.attackId && seen.get(t) === d.attackId) return;
seen.set(t, d.attackId);
if (t.hpRatio >= ratio - 1e-9) (unit.mem.acnipeExtra ??= new Set()).add(t);
}, { owner: unit });
},
};
},
};