Files
ark-stronghold-protocol/server/sim/content/kits/ops/chess_char_1_17-indigo.js
T
sganggs 8f62f3e360 feedback5 WE1: 秘术师 stores its energy at the attack check, not one interval later (GitHub #181)
PRTS 分支特性信息 秘术师: "能量储存与攻击占用相同的攻击间隔:在进行攻击判定时…若无有效目标且能量储存数未满,则改为储存一份
攻击能量(属于攻击行为);仅没有有效目标,且能量储存数已满的情况下才进入待机状态", "攻击被打断且弹道未能成功生成的情况下,储存的
能量不会被消耗"; PRTS 异常效果 缴械 "秘术师储存能量同样无法进行".

- ai.js updateAlly: a ready unit (able to act, not disarmed) whose attack check finds no valid target — no target, or
  its profile's canAttack false (a 秘术师 kit's target rule: 深靛's bound enemies, 维伊's marked ones) — calls the
  profile's storeEnergy; a stored energy restarts the attack interval, a full store idles with the attack ready. A
  DEFAULT cast at the check that leaves no target stores in the same check.
- professions.js mystic: storeEnergy (up to bb times), releaseEnergy (performAttack, after beforeAttack: a cancelled
  shot keeps them) and hitsFn(b, u, info) — the energies leave with the attack and land with its main hit; the old
  separate store timer (it started only once the attack was ready and never restarted the interval) is gone.
  [ASSUMED] a redeployment holds no energy.
- 维伊 / 黑键 kits: their own tick stores become the kit's storeEnergy on the shared check; 深靛 / 维伊 no longer write
  hadTarget themselves.

Golden: roster-003, roster-021, roster-022, bond-preciShip-high (深靛 stores at her check); matches solo-FUNNY-1,
solo-NORMAL-2, solo-HARD-2, solo-ABYSS-1, solo-ABYSS-2, coop2-NORMAL-3, coop2-HARD-4, coop4-FUNNY-6, coop4-ABYSS-7,
coop2-NORMAL-8-serverrun, solo-HARD-9-boosted, coop2-ABYSS-10-boosted, coop2-NORMAL-14-standins (each fields 深靛);
diy-032 (维伊) and diy-114 (黑键): the first store after a redeployment comes at the next tick's attack check instead of
the end of the redeploy tick (with the old tick store added back as a fallback the diy family matches the old digests).
fields, standins unchanged.

Tests: test/sim/feedback5-mystic-store.test.js (new, 4); op_veen, op_ebnhlz, kits_t1t2, kits_alt_t1 pass unchanged.
Docs: SIM.md §Professions (mystic row, profile hooks).
2026-10-06 14:53:56 +08:00

72 lines
4.1 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/chess_char_1_17-indigo.js — 深靛 (char_469_indigo) kit, tier 1.
// Conventions of the tier-1 kits: ../shared/tier1.js; kit contract and rules: ../README.md.
import { sortEnemyTargets } from '../../../targeting.js';
import { num, talentBb, skillRec, batMod } from '../shared/tier1.js';
export default {
// ---------------------------------------------------------------------------------------------------------------
// 1_17 深靛 光影迷宫: attack interval ×base_attack_time, talent chance ×talent_scale, bound enemies in range take
// indigo_s_2[damage].atk_scale × ATK arts every indigo_s_2[damage].interval s.
// 柔光缚目: attacks bind the target `duration` s with `prob`; bound enemies are never chosen as targets.
// Alternate S1 灯塔守卫者: for `duration` s the skill range (4-1), attack interval ×(1 + base_attack_time) (PRTS:
// "大幅度缩短(-80%)" ⇒ ×0.2 — a flat −0.8 s on 3 s would be less than S2's "略微缩短 ×0.7"), every attack hits for
// attack@atk_scale × ATK arts. Elite module MSC-X (store 4) is the mystic profile of the module trait (TUNE.mystic);
// the store itself is the shared 秘术师 profile (professions.js installMystic: at the attack check, PRTS 分支特性信息).
chess_char_1_17_a: (bb, chess, def) => {
const t = talentBb(chess, 0);
const dScale = num(bb['indigo_s_2[damage].atk_scale']);
const dIv = num(bb['indigo_s_2[damage].interval'], 0.5);
const bound = (e) => !!e.s.flags.bind;
const r1 = skillRec(chess, 'skchr_indigo_1');
const b1 = r1?.bb ?? {};
return {
skills: {
skchr_indigo_1: {
kind: 'duration', mods: { batPct: Math.max(-0.95, Math.min(0, num(b1.base_attack_time))) },
targeting: { rangeGrid: r1?.rangeGrid ?? null },
attack: { atkScale: num(b1['attack@atk_scale'], 1) },
},
},
trait: {
// with only bound enemies in range (or blocked by her — always her targets, Battle.blockedTargets) she has no valid
// target: she holds her fire and the mystic trait stores an energy at the attack check (professions.js
// installMystic; a bind on her only target included)
canAttack(battle, u) {
return battle.enemiesInKeys(u.rangeKeys, u, u.profile).some((e) => !bound(e)) || battle.blockedTargets(u, u.profile).some((e) => !bound(e));
},
afterHit(battle, u, target) {
if (!target || !target.alive || target.side !== 'enemy') return;
const p = num(t.prob) * (u.skill?.active ? num(bb.talent_scale, 1) : 1);
if (p > 0 && battle.rng.chance(Math.min(1, p))) battle.applyStatus(target, 'bind', { duration: num(t.duration), source: u });
},
},
skill: {
kind: 'duration', mods: { batPct: batMod(bb.base_attack_time, chess, def?.skill?.description ?? chess?.skill?.desc) },
onStart({ unit }) { unit.mem.indigoAcc = 0; },
onTick({ battle, unit, dt }) {
if (!(dScale > 0) || !(dIv > 0)) return;
unit.mem.indigoAcc = (unit.mem.indigoAcc ?? 0) + dt;
while (unit.mem.indigoAcc >= dIv - 1e-9) {
unit.mem.indigoAcc -= dIv;
for (const e of battle.enemiesInKeys(unit.rangeKeys, unit, { canHitFly: true })) {
if (bound(e)) battle.dealDamage(unit, e, { amount: unit.s.atk * dScale, type: 'arts', isSkill: true, canDodge: false, tags: ['dot'] });
}
}
},
},
talents: [{ install(battle, unit) {
battle.on('beforeAttack', (ctx) => {
if (ctx.attacker !== unit || !ctx.targets.some(bound)) return;
const prof = ctx.profile || unit.profile;
const cands = battle.enemiesInKeys(unit.rangeKeys, unit, prof);
for (const e of battle.blockedTargets(unit, prof)) if (!cands.includes(e)) cands.push(e);
for (let i = cands.length - 1; i >= 0; i--) if (bound(cands[i])) cands.splice(i, 1);
sortEnemyTargets(battle, unit, cands, prof.priority);
ctx.targets = cands.slice(0, Math.max(1, ctx.targets.length));
}, { owner: unit });
} }],
};
},
};