Files
ark-stronghold-protocol/server/sim/content/kits/ops/chess_char_6_12-rosmon.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

108 lines
5.3 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_6_12-rosmon.js — 迷迭香 (char_391_rosmon) kit, tier 6.
// Conventions of the tier-6 kits: ../shared/tier6.js; kit contract and rules: ../README.md.
import { sortEnemyTargets } from '../../../targeting.js';
import { summonToken } from '../../tokens.js';
import { num, tbb, live, opsOf, selectedSkill, batOf } from '../shared/tier6.js';
// ------------------------------------------------------------------------------------------------------------------
// 迷迭香 chess_char_6_12 (投掷手) — S2 末梢阻断; 歼灭战装备; 感知稳定
/** S2 末梢阻断 "溅射范围扩大": radius 1.5 (PRTS 溅射半径一览, 技能: 迷迭香 末梢阻断 1.5; ×1.3 [ASSUMED] until 0.1.1). */
const ROSMON_S2_SPLASH = 1.5;
function rosmon(bb, chess, def) {
const t0 = tbb(def, 0), t1 = tbb(def, 1);
const sid = selectedSkill(chess, def);
const prob = num(bb['attack@prob']), stun = num(bb['attack@stun']);
const gearId = (chess?.tokens || []).find((t) => /rosmon_shield/.test(String(t))) || 'token_10012_rosmon_shield';
const skills = {
// S1 思维膨大 (attack SP): the next attack also deals extra_atk_scale × ATK arts to every enemy it hits
skchr_rosmon_1: {
kind: 'instant',
attack: {
onEachHit({ battle, unit, target, kind }) {
if (!target || !target.alive || (kind !== 'main' && kind !== 'splash')) return;
battle.dealDamage(unit, target, { amount: unit.s.atk * num(bb.extra_atk_scale, 1), type: 'arts', isSkill: true, tags: ['skill'] });
},
},
},
// S3 “如你所愿”: base attack time ×0.5 (akdata: 乘算负数), ATK +atk, 2 targets but blocked enemies only (install);
// two 战术装备 on melee tiles of her range (the token kit: appear stun, blocked enemies DEF −160, lifetime)
skchr_rosmon_3: {
kind: 'duration',
// [ASSUMED] blocked enemies only; 白铁's unblockable 铁钳号 never: it does not open it (skills.js allyTargetsOk)
allyTargets: false,
mods: { batPct: batOf(bb.base_attack_time, def, true), atkPct: num(bb.atk) },
targeting: { maxTargets: Math.max(1, Math.floor(num(bb['attack@max_target'], 2))) },
onStart({ battle, unit }) {
let n = 0;
for (let i = 0; i < 2; i++) if (summonToken(battle, unit, gearId, 'melee')) n++;
battle.fx('summon', { x: unit.x, y: unit.y, id: unit.id, n });
},
},
};
return {
skills,
install(battle, unit) {
if (sid !== 'skchr_rosmon_3') return;
battle.on('beforeAttack', (ctx) => { // "仅选择被阻挡的敌人作为目标"
if (ctx.attacker !== unit || !unit.skill?.active) return;
// her range, plus the enemies she blocks herself — always her targets (Battle.blockedTargets, DESIGN §20.3)
const c = battle.enemiesInKeys(unit.rangeKeys, unit, ctx.profile);
for (const e of battle.blockedTargets(unit, ctx.profile)) if (!c.includes(e)) c.push(e);
for (let i = c.length - 1; i >= 0; i--) if (!c[i].blockedBy) c.splice(i, 1);
sortEnemyTargets(battle, unit, c, ctx.profile?.priority ?? null);
ctx.targets = c.slice(0, Math.max(1, Math.floor(num(ctx.profile?.maxTargets, 2))));
}, { owner: unit });
},
skill: {
kind: 'duration',
mods: { batPct: batOf(bb.base_attack_time, def), atkPct: num(bb.atk) },
onStart({ unit }) {
const p = unit.profile;
unit.mem.rosSaved = { r: p.splashRadius, n: p.shockTimes };
p.splashRadius = Math.max(num(p.splashRadius, 0.9), ROSMON_S2_SPLASH);
p.shockTimes = num(p.shockTimes, 2) + Math.floor(num(bb.add_times));
},
onEnd({ unit }) {
const s = unit.mem.rosSaved;
if (s) { unit.profile.splashRadius = s.r; unit.profile.shockTimes = s.n; }
unit.mem.rosSaved = null;
},
},
talents: [
{ install(battle, unit) { // S2: attack & aftershock stun chance
battle.on('damaged', (ctx) => {
if (ctx.source !== unit || !unit.skill?.active || !(stun > 0) || !ctx.target.alive || ctx.target.side !== 'enemy') return;
if (!ctx.dmg?.isAttack && !ctx.dmg?.tags?.includes('aftershock')) return;
if (battle.rng() < prob) battle.applyStatus(ctx.target, 'stun', { duration: stun, source: unit });
}, { owner: unit });
} },
{ install(battle, unit) { // 歼灭战装备
const v = num(t0.def_penetrate_fixed);
if (v) battle.addBuff(unit, { key: 'rosmon:pierce', mods: { defIgnoreFlat: v }, persist: true, allowDead: true });
} },
{ install(battle, unit) { // 感知稳定
const atk = num(t1.atk);
battle.on('deploy', ({ unit: u }) => {
if (u !== unit || !atk) return;
battle.after(0, () => {
if (!live(unit)) return;
const casters = opsOf(battle, unit.ownerId).filter((a) => a.def?.profession === 'CASTER');
const pick = battle.rng.pick(casters);
if (!pick) return;
battle.addBuff(unit, { key: 'rosmon:stable', mods: { atkPct: atk } });
battle.addBuff(pick, { key: 'rosmon:stable', mods: { atkPct: atk } });
battle.fx('link', { x: pick.x, y: pick.y, id: pick.id, src: unit.id });
}, { owner: unit });
}, { owner: unit });
} },
],
};
}
export default {
chess_char_6_12_a: rosmon,
};