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).
108 lines
5.3 KiB
JavaScript
108 lines
5.3 KiB
JavaScript
// 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,
|
||
};
|