Fix: 深巡 / 雷蛇 S2 fire with an enemy in range, not only when hit
Both 哨戒铁卫 S2s (深巡 行动能力剥夺, 雷蛇 反击电弧) are OFFENSIVE skills but waited for a hit: the official 技能策略 row `TANK | | | 0 | TAKE_DAMAGE` is documented for skillIndex 0, and reading it as a wildcard for every index handed TAKE_DAMAGE to these two skillIndex-1 skills (issue #4). The mode's rule (docs/research/03-operators.md §1.4, quoting the in-game help) is "offensive skills activate when an enemy is in their skill range; defensive skills activate when the unit is receiving damage", so both take the basic strategy instead. - kits/tier1.js: `trigger: 'DEFAULT'` on 深巡's default S2 and on 雷蛇's. Only the rule changes — spCost / initSp / spType still come from data. The defensive S1s (深巡 侵袭破坏应对, 雷蛇 充能防御) keep theirs. - tests: the two 雷蛇 tests that pinned the hit-gated cast now assert the basic strategy, and their enemy stands two tiles away so it never touches her; 深巡's test drops its forced dealDamage hit; a new test covers all four records (normal + elite of both). node --test: 2888 tests, 2871 pass, 0 fail, 17 skipped.
This commit is contained in:
1 parent
7f134737ce
commit
60db22ed67
3 files changed
+35
-6
No files matched your search
@@ -14,7 +14,9 @@
|
||||
// talents / traits read the resolved record, so a module choice ('none' ⇒ traitBase / talentsBase, module.active
|
||||
// false) is honoured. Per-skill triggers come from data (the official 技能策略: every MANUAL 重装 skill ⇒ TAKE_DAMAGE, a
|
||||
// MANUAL skill with its own 技能范围 ⇒ SKILL_RANGE, AUTO skills keep their own rule); the few spec overrides are documented
|
||||
// at the skill (冲锋号令 AUTO ⇒ SP_FULL, 花香疗法 heal-type DEFAULT). Tests: test/content/kits_alt_t1.test.js.
|
||||
// at the skill (冲锋号令 AUTO ⇒ SP_FULL, 花香疗法 heal-type DEFAULT; the two 哨戒铁卫 S2s 深巡 行动能力剥夺 and 雷蛇 反击电弧
|
||||
// override the 重装 TAKE_DAMAGE row ⇒ DEFAULT — an offensive skill fires with an enemy in range, research 03 §1.4).
|
||||
// Tests: test/content/kits_alt_t1.test.js.
|
||||
//
|
||||
// fx kinds emitted (battle.fx(kind, {x, y, …})): aoe {radius, id, skill} · zone {radius, dur, id, skill} ·
|
||||
// counter {id} · crit {id} · dp {n, id} · heal {id} · taunt {id} · summon {id, token} · pull {id} · sonic {radius} ·
|
||||
@@ -394,6 +396,10 @@ export default {
|
||||
// ---------------------------------------------------------------------------------------------------------------
|
||||
// 1_04 深巡 行动能力剥夺: longer line range (skill grid), ATK +atk, ASPD +attack_speed, fin darts pierce
|
||||
// attack@max_target enemies on the line and cause attack@sluggish s of 停顿.
|
||||
// 技能策略 override → DEFAULT: the official class row hands a 重装 S2 TAKE_DAMAGE, which makes a 2-2 ranged 哨戒铁卫
|
||||
// wait until something hits her — in practice until she blocks (issue #4). 行动能力剥夺 is an offensive ranged skill,
|
||||
// so the basic strategy (SP ready + about to attack + an enemy inside the initial range) is the right rule. Only the
|
||||
// rule changes: spCost / initSp / spType still come from data.
|
||||
// 细胞活性抑制剂: attacks inflict `damage` arts per `interval` s for `duration` s (damage_seamonster vs 【海怪】).
|
||||
// Elite module (SPT-X): stealth of enemies inside the range is cancelled.
|
||||
// Alternate S1 侵袭破坏应对 (重装 ⇒ TAKE_DAMAGE trigger from data): ATK +atk, DEF +def.
|
||||
@@ -402,6 +408,7 @@ export default {
|
||||
const s1 = skillBbOf(chess, 'skchr_udflow_1');
|
||||
return {
|
||||
skill: {
|
||||
trigger: 'DEFAULT',
|
||||
kind: 'duration', mods: { atkPct: num(bb.atk), aspd: num(bb.attack_speed) },
|
||||
targeting: { rangeGrid: def?.skill?.rangeGrid ?? null, maxTargets: num(bb['attack@max_target'], 1) },
|
||||
attack: { onHitStatus: { key: 'sluggish', duration: num(bb['attack@sluggish'], 1) } },
|
||||
@@ -985,6 +992,10 @@ export default {
|
||||
// 1_20 雷蛇 反击电弧 (hurt SP): attack interval ×(1 + base_attack_time) (PRTS "攻击间隔增大(+70%)": a RATIO for this skill,
|
||||
// 1.2 → 2.04 s — not +0.7 s), ATK +atk, arts attacks on up to attack@max_target enemies,
|
||||
// attack@buff_prob to stun attack@stun s; afterwards 雷蛇 is stunned `stun` s.
|
||||
// 技能策略 override → DEFAULT: 反击电弧 is an offensive skill (ATK +125 %, arts hits on up to 3 enemies, stun), and the
|
||||
// mode's rule is "offensive skills activate when an enemy is in their skill range" (research 03 §1.4, the in-game
|
||||
// help). The TANK row is a TAKE_DAMAGE row documented for skillIndex 0; reading it as a wildcard made this S2 — and
|
||||
// 深巡's — wait for a hit instead (issue #4). Both 哨戒铁卫 S2s therefore override it here.
|
||||
// 战术防御: when attacked, +sp SP to herself and to one random ally in the talent grid. Elite 雷抗: RES +magic_resistance.
|
||||
// Elite module (SPT-X): stealth of enemies inside the range is cancelled.
|
||||
// Alternate S1 充能防御 (AUTO, hurt SP, "技能自动开启" — an AUTO skill takes no 技能策略: SP_FULL, so the hit that fills SP
|
||||
@@ -1018,6 +1029,7 @@ export default {
|
||||
}, { owner: unit, priority: 50 });
|
||||
},
|
||||
skill: {
|
||||
trigger: 'DEFAULT',
|
||||
kind: 'duration', mods: { atkPct: num(bb.atk), batPct: Math.max(0, num(bb.base_attack_time)) },
|
||||
targeting: { maxTargets: Math.max(1, Math.floor(num(bb['attack@max_target'], 1))) },
|
||||
attack: {
|
||||
|
||||
@@ -673,11 +673,12 @@ test('1_20 雷蛇 反击电弧: attack interval +70 % of the base (PRTS 增大(+
|
||||
for (const id of pair('20')) {
|
||||
const bb = raw(id).skill.bb;
|
||||
assert.equal(raw(id).skill.skillId, 'skchr_liskam_2');
|
||||
// 重装 S2: TAKE_DAMAGE (the enemy has to hit her)
|
||||
const h = run({ defs: { enemies: { e: dummy('e', { atk: 300, bat: 1 }) }, chess: noGarrison(id) }, units: [{ chessId: id, row: 9, col: 4, carryState: READY }], enemies: [{ key: 'e', pos: [9, 4] }] });
|
||||
// 重装 S2 反击电弧 is an OFFENSIVE skill: the basic strategy casts it with an enemy in the skill range (issue #4) —
|
||||
// the enemy here stands two tiles ahead and, being a speed-0 dummy, never touches her
|
||||
const h = run({ defs: { enemies: { e: dummy('e', { atk: 300, bat: 1 }) }, chess: noGarrison(id) }, units: [{ chessId: id, row: 9, col: 4, carryState: READY }], enemies: [{ key: 'e', pos: [9, 6] }] });
|
||||
const u = h.unit(id);
|
||||
assert.ok(h.runUntil(() => u.skill.active, 5));
|
||||
assert.equal(started(h, u)[0].reason, 'TAKE_DAMAGE');
|
||||
assert.equal(started(h, u)[0].reason, 'DEFAULT');
|
||||
approx(u.s.interval, u.base.bat * (1 + bb.base_attack_time) * 100 / u.s.aspd, `${id} interval ×${1 + bb.base_attack_time}`);
|
||||
h.runUntil(() => !u.skill.active, 30);
|
||||
approx(u.s.interval, u.base.bat * 100 / u.s.aspd, 'restored');
|
||||
|
||||
@@ -132,9 +132,11 @@ test('1_04 深巡: fin darts pierce attack@max_target enemies on the line + 1 s
|
||||
});
|
||||
const u = h.unit(id);
|
||||
h.step();
|
||||
h.b.dealDamage(h.enemies()[0], u, { amount: 10, type: 'phys' }); // 重装: TAKE_DAMAGE
|
||||
// 技能策略 (issue #4): an enemy inside her initial 2-2 range is enough — the basic strategy casts it, no hit required
|
||||
assert.equal(u.skill.rule, 'DEFAULT', '行动能力剥夺 is an offensive ranged skill, not a 重装 TAKE_DAMAGE one');
|
||||
h.run(3.5);
|
||||
assert.ok(u.skill.active);
|
||||
assert.equal(h.hooksOf('skillStart').find((c) => c.unit === u).reason, 'DEFAULT', 'cast by the basic strategy');
|
||||
approx(u.s.atk, u.base.atk * (1 + bb.atk));
|
||||
const firstAtk = h.hooksOf('attack').find((c) => c.attacker === u && c.isSkill);
|
||||
assert.equal(firstAtk.targets.length, bb['attack@max_target'], 'hits every enemy of the line');
|
||||
@@ -147,6 +149,20 @@ test('1_04 深巡: fin darts pierce attack@max_target enemies on the line + 1 s
|
||||
done(h);
|
||||
});
|
||||
|
||||
test('1_04 深巡 / 1_20 雷蛇 S2 技能策略: both 哨戒铁卫 S2s cast with an enemy in range, no hit needed (issue #4)', () => {
|
||||
// both S2s are offensive (深巡: range up + ATK/ASPD + piercing darts; 雷蛇: ATK +125 % + arts on 3 + stun), and the
|
||||
// mode's rule is "offensive skills activate when an enemy is in their skill range". The 重装 TAKE_DAMAGE row is
|
||||
// documented for skillIndex 0 only — reading it as a wildcard made both wait for a hit.
|
||||
for (const id of ['chess_char_1_04_a', 'chess_char_1_04_b', 'chess_char_1_20_a', 'chess_char_1_20_b']) {
|
||||
const h = run({ units: [{ chessId: id, row: 9, col: 4, carryState: READY }], enemies: [{ key: 'e', pos: [9, 6] }] });
|
||||
const u = h.unit(id);
|
||||
assert.equal(u.skill.rule, 'DEFAULT', `${id}: an offensive skill, not the 重装 TAKE_DAMAGE row`);
|
||||
assert.ok(h.runUntil(() => u.skill.activations >= 1, 5), `${id}: casts while an enemy is in range and untouched`);
|
||||
assert.equal(h.hooksOf('skillStart').find((c) => c.unit === u).reason, 'DEFAULT', `${id}: cast by the basic strategy`);
|
||||
done(h);
|
||||
}
|
||||
});
|
||||
|
||||
test('1_04 深巡 / 1_20 雷蛇 elite module: stealthed enemies inside the range are revealed', () => {
|
||||
for (const id of ['chess_char_1_04_b', 'chess_char_1_20_b']) {
|
||||
const h = run({ defs: { enemies: { e: dummy('e') } }, units: [{ chessId: id, row: 9, col: 4 }], enemies: [{ key: 'e', pos: [9, 6] }] });
|
||||
@@ -590,7 +606,7 @@ test('1_20 雷蛇: 反击电弧 arts on up to 3 enemies, self-stun `stun` s afte
|
||||
const u = h.unit(id);
|
||||
h.run(3);
|
||||
assert.ok(u.skill.active);
|
||||
assert.equal(h.hooksOf('skillStart').find((c) => c.unit === u).reason, 'TAKE_DAMAGE', '重装: cast by the hit');
|
||||
assert.equal(h.hooksOf('skillStart').find((c) => c.unit === u).reason, 'DEFAULT', 'an offensive skill: cast with an enemy in range');
|
||||
approx(u.s.atk, u.base.atk * (1 + bb.atk));
|
||||
approx(u.s.interval, u.base.bat * (1 + bb.base_attack_time) * 100 / u.s.aspd, 'attack interval +70 % (PRTS 增大(+70%): a ratio, not +0.7 s)');
|
||||
const a0 = h.hooksOf('attack').find((c) => c.attacker === u && c.isSkill);
|
||||
|
||||
Reference in new issue
Block a user