Files
ark-stronghold-protocol/server/sim/content/kits/ops/chess_char_3_11-snhunt.js
T
sganggs 69d791f9c6 feedback7 R22-OSIM: a skill that 白铁's 铁钳号 opens now acts on it — 蕾缪安 S3 / 艾丽妮 S3 / 陈 S2 / 引星棘刺 S2 spend on it, skills that pick enemies only stay closed; golden: unchanged
Grok's review of the 铁钳号 change (HIGH): the device opened every non-heal skill, but kits that pick their victims
with enemiesInKeys only were spent on nothing — 蕾缪安 S3 went active with 0 locks, 艾丽妮 S3 and 陈 S2 struck nothing,
引星棘刺 S2 threw at the empty far tile. Audit: every skill of every chess (loadouts) and of the 自选 pool, placed with
only the device in range; of the 351 skills the device opened, 67 never touched it.

- skills.js `allyTargetsOk`: the device counts for a skill's start only if the skill acts on it — SkillSpec
  `allyTargets` (true / false) from the kit, else [ASSUMED] a skill acting through the unit's own attacks (timed and
  still attacking, or a cast that is the next attack) on a unit that attacks. An onStart-only cast or a no-attack timed
  skill picks enemies itself and waits for one (德克萨斯 S2 剑雨, 锡人 S1/S2, 铃兰 S3 … about 50 skills).
- Spend on the device (allyTargets: true; its kit cancels the hits, its 受击回复 counts): 蕾缪安 S3 locks it and spends
  her bullets (ammoUsed, so the per-bullet layers come) and shells it; 艾丽妮 S3 strikes it; 陈 S2 draws on it after
  every enemy; 引星棘刺 S2 throws onto it and its pulses hit it. queries.js allyTargetsInRadius for their areas.
- Explicitly closed ([ASSUMED], allyTargets: false): 普罗旺斯 S2 (never shoots above 80 % HP), 卡涅利安 S1 (charged: no
  attack), 山 S2 (blocked enemies), 余 S2 / S3, 迷迭香 S3 (blocked only), 荒芜拉普兰德 S1 / S2 (drone locks), 淬羽赫默 S1–S3
  and 遥 S1–S3 (heal attacks), 机械师 S3, 珊比 S2 / S3, 陈 S3 / 艾丽妮 S2 (their guards already refused).
- 雪猎's empty-magazine cast back to enemies only (its S2 does not act on the device).
Re-audit: 281 skills open on the device alone and all act on it (蕾缪安 S3 through her locks); none of those that
acted on it before stopped. Docs: SIM.md, design/engine.md, PLAYING.md.

Tests: test/content/feedback7-allytarget-kits.test.js (5; all fail on the parent).
2026-10-08 20:53:24 +08:00

75 lines
3.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/chess_char_3_11-snhunt.js — 雪猎 (char_4211_snhunt) kit, tier 3.
// Conventions of the tier-3 kits: ../shared/tier3.js; kit contract and rules: ../README.md.
import {
num, defOf, talentBb, traitBb, selectedId, altSkills, instantKindOf, alive, fx, textNum, enemiesOn,
} from '../shared/tier3.js';
export default {
// ---- 3_11 雪猎 · 猎手 — S2 风雪连弩: instant special-bullet double shot (higher vs non-moving targets), charges;
// 裂云一击: the skill also sends 裂云兽 (ATK% phys + cold); 精锐 module HUN-X: reload from empty adds extra rounds
// S1 强力击·β型: next attack ×atk_scale (a normal shot: needs a round); 裂云兽 hits the enemy it is cast on
chess_char_3_11_a: (bb, chess, def) => {
const d = defOf(chess, def);
const t0 = talentBb(d, 0);
const tb = traitBb(d);
const sel = selectedId(chess, d);
const S2 = 'skchr_snhunt_2';
const s1 = num(bb.atk_scale_1, 1), s2 = num(bb.atk_scale_2, s1);
const charges = Math.max(1, Math.floor(num(d.skill?.maxCharges, num(bb.ct, 1))));
const shots = textNum(d.skill?.description, /(\d+|[一二两三四五])连击/, 2);
const still = (e) => !!(e.blockedBy || e.s.flags.stun || e.s.flags.noMove || !e.moving);
const extra = num(tb.extra_add, 0);
return {
skill: {
kind: charges > 1 ? 'charges' : 'instant',
onStart({ battle, unit }) {
const t = enemiesOn(battle, unit, unit.rangeKeys, 1, unit.profile)[0] ?? null;
unit.mem.snTarget = t;
if (!t) return;
battle.addProjectile({
from: unit, target: t, speed: 16, visual: 'arrow', source: unit,
onHit: ({ target }) => {
if (!target || !target.alive) return;
const sc = still(target) ? s2 : s1;
for (let i = 0; i < shots && target.alive; i++) battle.dealDamage(unit, target, { amount: unit.s.atk * sc, type: 'phys', isSkill: true, tags: ['skill', 'snhunt'] });
},
});
fx(battle, 'volley', unit, { target: t.id, skill: 'snhunt_2' });
},
},
skills: altSkills(chess, d, bb, {
skchr_snhunt_1: (s) => ({ kind: instantKindOf(s), attack: { atkScale: num(s.bb.atk_scale, 1) } }),
}),
talents: [{ install(battle, unit) {
battle.on('skillStart', (ctx) => {
if (ctx.unit !== unit) return;
// S2 aims its special bullets itself (mem.snTarget); another skill is cast on the enemy about to be shot
const t = sel === S2 || sel == null ? unit.mem.snTarget : (enemiesOn(battle, unit, unit.rangeKeys, 1, unit.profile)[0] ?? null);
if (!t || !t.alive) return;
battle.dealDamage(unit, t, { amount: unit.s.atk * num(t0.atk_scale, 1), type: 'phys', isSkill: true, tags: ['talent', 'cloudbeast'] });
if (t.alive) battle.applyStatus(t, 'cold', { duration: num(t0.cold, 3), source: unit });
fx(battle, 'strike', t, { src: unit.id, talent: 'snhunt_beast' });
}, { owner: unit });
} }],
install(battle, unit) {
// special bullets (S2 only): the skill also fires while the magazine is empty (the attack loop cannot run then)
if (sel === S2 || sel == null) {
battle.on('tick', () => {
const sk = unit.skill;
if (!alive(unit) || !unit.canAct || !sk || !sk.ready || sk.opCooling || unit.s.flags.silence || (unit.trait.ammo ?? 1) > 0) return;
if (battle.enemiesInKeys(unit.baseRangeKeys || unit.rangeKeys, unit, unit.profile).length) sk.activate('DEFAULT');
}, { owner: unit });
}
if (extra > 0) {
battle.on('tick', () => {
const a = unit.trait.ammo ?? 0;
if (unit.mem.prevAmmo === 0 && a === 1) unit.trait.ammo = Math.min(Math.max(1, unit.profile.ammoMax ?? 8), a + extra);
unit.mem.prevAmmo = unit.trait.ammo;
}, { owner: unit });
}
},
};
},
};