feedback2 WH: 余 S2 厚礼上宾 casts with an enemy on its x-1 instead of when hit (the owner's deliberate deviation from the 重装 TAKE_DAMAGE row: TRIGGER_DEVIATIONS SKILL_RANGE, data regenerated, only the two 余 records change)
This commit is contained in:
1 parent
a4cf492160
commit
7362ab5439
10 files changed
+129
-42
No files matched your search
@@ -261,7 +261,7 @@ test('6_03 余 S1 今日做东: taunt +1 while carried; TAKE_DAMAGE cast, HP/DEF
|
||||
assert.equal(d.unit('chess_char_6_03_a').s.taunt, 0);
|
||||
});
|
||||
|
||||
test('6_03 余 S2 厚礼上宾: cast when hit (重装 TAKE_DAMAGE); atk_scale × ATK arts around him, reachable ground enemies teleported onto his tile; block +2, HP/ATK +, arts attacks', () => {
|
||||
test('6_03 余 S2 厚礼上宾: cast with an enemy on its x-1 (SKILL_RANGE — a deliberate deviation from the 重装 TAKE_DAMAGE, DESIGN §22.10); atk_scale × ATK arts around him, reachable ground enemies teleported onto his tile; block +2, HP/ATK +, arts attacks', () => {
|
||||
for (const id of both('chess_char_6_03')) {
|
||||
const sid = 'skchr_yu_2', bb = bbOf(id, sid);
|
||||
const h = run({
|
||||
@@ -272,6 +272,7 @@ test('6_03 余 S2 厚礼上宾: cast when hit (重装 TAKE_DAMAGE); atk_scale ×
|
||||
const u = h.unit(id);
|
||||
usesSkill(u, sid);
|
||||
assert.ok(h.runUntil(() => u.skill.active, 10));
|
||||
assert.equal(started(h, u)[0].reason, 'SKILL_RANGE');
|
||||
const t0 = started(h, u)[0].t;
|
||||
const burst = dealt(h, u, (c) => c.t === t0 && c.dmg.isSkill && !c.dmg.isAttack);
|
||||
assert.equal(burst.length, 3);
|
||||
@@ -1160,7 +1161,7 @@ test('default-skill-only hooks do not run under an alternate skill (余 / 维娜
|
||||
// 余 S2 active: 闲云隐市 is NOT given to every operator (S3 only)
|
||||
{
|
||||
const h = run({
|
||||
defs: { chess: { o1: plain('o1'), o2: plain('o2'), o3: plain('o3') }, enemies: { e: dummy('e', { atk: 100, bat: 1 }) } }, // (重装: TAKE_DAMAGE)
|
||||
defs: { chess: { o1: plain('o1'), o2: plain('o2'), o3: plain('o3') }, enemies: { e: dummy('e', { atk: 100, bat: 1 }) } }, // (S2: an enemy on x-1)
|
||||
units: [U('chess_char_6_03_a', 'skchr_yu_2', 10, 4, { carryState: READY }), { chessId: 'o1', row: 9, col: 6 }, { chessId: 'o2', row: 11, col: 6 }, { chessId: 'o3', row: 12, col: 6 }],
|
||||
enemies: [{ key: 'e', pos: [10, 4] }],
|
||||
});
|
||||
|
||||
+9
-2
@@ -488,9 +488,16 @@ test('chess: skills[] = every skill unlocked at the status, at the chess skill l
|
||||
assert.deepEqual(rules(id), ['DEFAULT', 'DEFAULT']);
|
||||
assert.deepEqual(raws(id), ['TAKE_DAMAGE', 'TAKE_DAMAGE']);
|
||||
}
|
||||
// 余 S2 厚礼上宾 (DESIGN §22.10, the owner's decision of 2026-10-04): SKILL_RANGE on its own x-1, S1 keeps the row, S3 its
|
||||
// official charId row
|
||||
for (const id of ['chess_char_6_03_a', 'chess_char_6_03_b']) {
|
||||
assert.deepEqual(rules(id), ['TAKE_DAMAGE', 'SKILL_RANGE', 'CUSTOM_RANGE']);
|
||||
assert.deepEqual(raws(id), ['TAKE_DAMAGE', 'TAKE_DAMAGE', 'CUSTOM_RANGE_SEARCH_ENEMY']);
|
||||
assert.deepEqual(chess[id].skills[1].trigger.customRangeGrid, chess[id].skills[1].rangeGrid);
|
||||
}
|
||||
const deviated = Object.values(chess).flatMap((c) => (c.skills || []).filter((s) => s.trigger.rawRule === 'TAKE_DAMAGE' && s.trigger.rule !== 'TAKE_DAMAGE').map((s) => `${c.baseId} ${s.skillId}`));
|
||||
assert.equal(deviated.length, 12, 'exactly the six skills, normal + elite');
|
||||
assert.deepEqual([...new Set(deviated)].sort(), ['chess_char_1_04_a skchr_udflow_2', 'chess_char_1_20_a skchr_liskam_2', 'chess_char_2_18_a skchr_ashlok_2', 'chess_char_2_18_a skcom_atk_up[3]', 'chess_char_5_08_a skchr_horn_2', 'chess_char_5_08_a skchr_horn_3']);
|
||||
assert.equal(deviated.length, 14, 'exactly the six skills of §21.29 and 余 S2, normal + elite');
|
||||
assert.deepEqual([...new Set(deviated)].sort(), ['chess_char_1_04_a skchr_udflow_2', 'chess_char_1_20_a skchr_liskam_2', 'chess_char_2_18_a skchr_ashlok_2', 'chess_char_2_18_a skcom_atk_up[3]', 'chess_char_5_08_a skchr_horn_2', 'chess_char_5_08_a skchr_horn_3', 'chess_char_6_03_a skchr_yu_2']);
|
||||
for (const c of Object.values(chess)) {
|
||||
for (const s of c.skills || []) {
|
||||
if (s.skillType !== 'MANUAL') assert.ok(!['TAKE_DAMAGE', 'SEARCH', 'SKILL_RANGE'].includes(s.trigger.rule), `${c.chessId} ${s.skillId}: an AUTO / PASSIVE skill takes no strategy row`);
|
||||
|
||||
@@ -4,6 +4,9 @@
|
||||
// and S3 终极防线, 灰毫 S1 攻击力强化·γ型 and S2 专注轰击. It lives in the data (tools/build-data.mjs TRIGGER_DEVIATIONS,
|
||||
// keyed per chess: 灰毫 S1 is the generic skcom_atk_up[3] other chess carry too), `rawRule` keeps the official TAKE_DAMAGE.
|
||||
// Every other MANUAL 重装 skill — 深巡 S1 侵袭破坏应对 among them — still waits for a hit.
|
||||
// One more since 2026-10-04 (the owner's decision after GitHub issue #32 item 1, DESIGN §22.10): 余 S2 厚礼上宾 casts with an
|
||||
// enemy on its own 技能范围 x-1 — SKILL_RANGE, customRangeGrid its range (his attack range is his own tile, so the basic
|
||||
// strategy could not see an enemy he can pull); `rawRule` keeps TAKE_DAMAGE.
|
||||
//
|
||||
// The battle check is real: the chess records of data/chess.json with their kits, the stage 战场#01(下半)
|
||||
// (act2autochess_m01), a real enemy (萨卡兹枯朽前锋, melee, 0.8 tiles/s) walking the lane from the gate (9,10) to the
|
||||
@@ -15,6 +18,7 @@ import fs from 'node:fs';
|
||||
import { makeBattle, checkInvariants } from '../helpers/battleHarness.js';
|
||||
import { getDefaultSource } from '../../server/sim/simdata.js';
|
||||
import { bodyInKeys } from '../../server/sim/body.js';
|
||||
import { absoluteRangeKeys } from '../../server/sim/targeting.js';
|
||||
|
||||
const CHESS = JSON.parse(fs.readFileSync(new URL('../../data/chess.json', import.meta.url), 'utf8'));
|
||||
const ds = getDefaultSource();
|
||||
@@ -28,8 +32,10 @@ const DEVIATED = [
|
||||
['chess_char_2_18_a', 'skcom_atk_up[3]'], // 灰毫 S1 攻击力强化·γ型
|
||||
['chess_char_2_18_a', 'skchr_ashlok_2'], // 灰毫 S2 专注轰击
|
||||
];
|
||||
/** 余 S2 厚礼上宾: SKILL_RANGE on its own x-1 (DESIGN §22.10). */
|
||||
const SKILL_RANGE_DEVIATED = [['chess_char_6_03_a', 'skchr_yu_2']];
|
||||
const both = (normalId) => [normalId, CHESS[normalId].goldenId];
|
||||
const isDeviated = (c, skillId) => DEVIATED.some(([id, s]) => id === c.baseId && s === skillId);
|
||||
const isDeviated = (c, skillId) => [...DEVIATED, ...SKILL_RANGE_DEVIATED].some(([id, s]) => id === c.baseId && s === skillId);
|
||||
const ENEMY = 'enemy_1422_lrsldr';
|
||||
|
||||
/**
|
||||
@@ -46,7 +52,7 @@ function firstCast(chessId, skillId, { spawnAt = 0, seconds = 40 } = {}) {
|
||||
});
|
||||
const u = h.unit(chessId);
|
||||
assert.equal(u.skill.id, skillId, `${chessId} runs ${skillId}`);
|
||||
const out = { rule: u.skill.rule, cast: null, reason: null, enemyInRange: false, blocking: false, hurt: null };
|
||||
const out = { rule: u.skill.rule, cast: null, reason: null, enemyInRange: false, enemyInSkillRange: false, blocking: false, hurt: null };
|
||||
h.b.on('damaged', (ctx) => { if (ctx.target === u && out.hurt == null) out.hurt = h.b.time; }, { priority: 1000 });
|
||||
h.b.on('skillStart', (ctx) => {
|
||||
if (ctx.unit !== u || out.cast != null) return;
|
||||
@@ -54,6 +60,8 @@ function firstCast(chessId, skillId, { spawnAt = 0, seconds = 40 } = {}) {
|
||||
out.reason = ctx.reason;
|
||||
const keys = new Set(u.baseRangeKeys || u.rangeKeys);
|
||||
out.enemyInRange = h.b.enemies.some((e) => e.alive && e.deployed && bodyInKeys(e, keys));
|
||||
const sk = u.skill.triggerGrid ? new Set(absoluteRangeKeys(u.skill.triggerGrid, u.tileR, u.tileC, u.dir, 0)) : null;
|
||||
out.enemyInSkillRange = !!sk && h.b.enemies.some((e) => e.alive && e.deployed && bodyInKeys(e, sk));
|
||||
out.blocking = u.blocking.length > 0;
|
||||
}, { priority: 1000 });
|
||||
h.runUntil(() => out.cast != null && out.hurt != null, seconds);
|
||||
@@ -62,7 +70,7 @@ function firstCast(chessId, skillId, { spawnAt = 0, seconds = 40 } = {}) {
|
||||
return out;
|
||||
}
|
||||
|
||||
test('data: the six deviated 重装 skills are DEFAULT on the normal and the elite record, rawRule the official TAKE_DAMAGE', () => {
|
||||
test('data: the six deviated 重装 skills are DEFAULT and 余 S2 is SKILL_RANGE on its x-1, on the normal and the elite record, rawRule the official TAKE_DAMAGE', () => {
|
||||
let n = 0;
|
||||
for (const [normalId, skillId] of DEVIATED) {
|
||||
for (const id of both(normalId)) {
|
||||
@@ -76,6 +84,17 @@ test('data: the six deviated 重装 skills are DEFAULT on the normal and the eli
|
||||
}
|
||||
}
|
||||
assert.equal(n, 12);
|
||||
for (const [normalId, skillId] of SKILL_RANGE_DEVIATED) {
|
||||
for (const id of both(normalId)) {
|
||||
const s = CHESS[id].skills.find((x) => x.skillId === skillId);
|
||||
assert.equal(CHESS[id].profession, 'TANK');
|
||||
assert.ok(s.rangeGrid?.length, `${id} ${skillId}: a 技能范围 of its own`);
|
||||
assert.deepEqual(s.trigger, { rule: 'SKILL_RANGE', rawRule: 'TAKE_DAMAGE', customRangeGrid: s.rangeGrid }, `${id} ${skillId}`);
|
||||
const def = ds.getChess(id, { skillIndex: s.index });
|
||||
assert.equal(def.skill.trigger.rule, 'SKILL_RANGE', `${id} ${skillId} loadout def`);
|
||||
assert.deepEqual(def.skill.trigger.grid, s.rangeGrid, `${id} ${skillId}: the trigger grid is its x-1`);
|
||||
}
|
||||
}
|
||||
// nothing else changed: every other MANUAL 重装 skill keeps the row (深巡 S1 included), and the generic
|
||||
// skcom_atk_up[3] keeps each other carrier's own rule (the deviation is keyed per chess, never per skill id)
|
||||
for (const c of Object.values(CHESS)) {
|
||||
@@ -91,9 +110,9 @@ test('data: the six deviated 重装 skills are DEFAULT on the normal and the eli
|
||||
for (const c of others) assert.deepEqual(c.skills.find((s) => s.skillId === 'skcom_atk_up[3]').trigger, { rule: 'DEFAULT', rawRule: 'DEFAULT', customRangeGrid: null }, c.chessId);
|
||||
});
|
||||
|
||||
test('PR #12\'s kit lines agree with the data: 深巡 / 雷蛇 S2 specs say DEFAULT, the other four deviated skills read the data', async () => {
|
||||
test('PR #12\'s kit lines agree with the data: 深巡 / 雷蛇 S2 specs say DEFAULT, the other deviated skills read the data', async () => {
|
||||
const { KITS } = await import('../../server/sim/content/index.js');
|
||||
for (const [normalId, skillId] of DEVIATED) {
|
||||
for (const [normalId, skillId] of [...DEVIATED, ...SKILL_RANGE_DEVIATED]) {
|
||||
for (const id of both(normalId)) {
|
||||
const rec = CHESS[id];
|
||||
const s = rec.skills.find((x) => x.skillId === skillId);
|
||||
@@ -122,6 +141,22 @@ test('real battle: each deviated skill casts with an enemy in range before anyth
|
||||
}
|
||||
});
|
||||
|
||||
test('real battle: 余 S2 casts once the walker steps onto its x-1, before anything hits him (normal + elite)', () => {
|
||||
for (const [normalId, skillId] of SKILL_RANGE_DEVIATED) {
|
||||
for (const id of both(normalId)) {
|
||||
const r = firstCast(id, skillId, { spawnAt: 3 });
|
||||
const where = `${id} ${skillId}: ${JSON.stringify(r)}`;
|
||||
assert.equal(r.rule, 'SKILL_RANGE', where);
|
||||
assert.ok(r.cast != null && r.cast >= 3, `cast once an enemy is on the field — ${where}`);
|
||||
assert.equal(r.reason, 'SKILL_RANGE', where);
|
||||
// (skillStart comes after the cast's own teleport: the walker already stands on his tile then, but he has not
|
||||
// blocked anyone yet — it was pulled in from x-1, it did not walk in)
|
||||
assert.ok(r.enemyInSkillRange && !r.blocking, `an enemy on x-1, pulled in rather than walked in — ${where}`);
|
||||
assert.ok(r.hurt == null || r.hurt > r.cast, `cast before the first hit — ${where}`);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
test('real battle: 深巡 S1 and every other MANUAL 重装 skill still wait for a hit (TAKE_DAMAGE)', () => {
|
||||
let n = 0;
|
||||
for (const c of Object.values(CHESS)) {
|
||||
|
||||
@@ -252,21 +252,51 @@ test('荒芜拉普兰德 S3 facing UP / LEFT / DOWN plays like facing RIGHT (ene
|
||||
// =====================================================================================================================
|
||||
// item 1 — 余 S2 厚礼上宾
|
||||
|
||||
/** 余 on (10,5) with S2 (`sp` carried in); parked enemies (`[key, row, col, tag?]`). */
|
||||
function yuS2(chessId, foes, sp = 999) {
|
||||
const hold = (r, c) => ({ motion: 'WALK', start: [r, c], end: [9, 2], checkpoints: [{ type: 'WAIT', time: 99 }] });
|
||||
/** 余 on (10,5) with S2 (`sp` carried in); parked enemies (`[key, row, col, tag?, motion?]`). */
|
||||
function yuS2(chessId, foes, sp = 999, defs = undefined) {
|
||||
const hold = (r, c, motion = 'WALK') => ({ motion, start: [r, c], end: [9, 2], checkpoints: [{ type: 'WAIT', time: 99 }] });
|
||||
const h = makeBattle({
|
||||
seed: 5, timeLimit: 120, autoFinish: false,
|
||||
seed: 5, timeLimit: 120, autoFinish: false, defs,
|
||||
units: [{ chessId, row: 10, col: 5, dir: 'RIGHT', skillIndex: 1, carryState: { sp } }],
|
||||
enemies: foes.map(([key, r, c, tag]) => ({ key, route: hold(r, c), time: 0, tag })),
|
||||
enemies: foes.map(([key, r, c, tag, motion]) => ({ key, route: hold(r, c, motion), time: 0, tag })),
|
||||
});
|
||||
h.step(1);
|
||||
return h;
|
||||
}
|
||||
const castOf = (h, u) => h.hooksOf('skillStart').find((c) => c.unit === u);
|
||||
|
||||
test('余 S2 (deliberate deviation, DESIGN §22.10): it fires with a ground enemy two tiles away and nobody hitting him — SKILL_RANGE on x-1 — and the enemy lands on his tile', () => {
|
||||
for (const id of ['chess_char_6_03_a', 'chess_char_6_03_b']) {
|
||||
const h = yuS2(id, [['e', 10, 7]], 999, { enemies: { e: enemyRec({ key: 'e', hp: 1e6, speed: 0, atk: 0 }) } });
|
||||
const yu = h.unit(id);
|
||||
assert.equal(yu.skill.rule, 'SKILL_RANGE', 'the data rule (rawRule TAKE_DAMAGE)');
|
||||
const e = h.b.enemies[0];
|
||||
assert.ok(yu.skill.active, 'cast in the first tick');
|
||||
assert.equal(castOf(h, yu).reason, 'SKILL_RANGE');
|
||||
assert.equal(yu.stats.taken, 0, 'nothing hit him');
|
||||
assert.deepEqual([e.y, e.x], [yu.tileR, yu.tileC], 'pulled onto his tile');
|
||||
h.step(1);
|
||||
assert.equal(e.blockedBy, yu, 'and blocked there');
|
||||
done(h);
|
||||
}
|
||||
});
|
||||
|
||||
test('余 S2: a ranged enemy hitting him from outside x-1 no longer casts it (it was the official TAKE_DAMAGE); it fires once someone stands on x-1', () => {
|
||||
// 深池暗影术师 (range 2.5) on (9,7): outside x-1 (|Δrow| + |Δcol| = 3) but in reach of him
|
||||
const h = yuS2('chess_char_6_03_a', [['enemy_1168_dumage', 9, 7]]);
|
||||
const yu = h.unit('chess_char_6_03_a');
|
||||
assert.ok(h.runUntil(() => yu.stats.taken > 0, 10), 'it hits him');
|
||||
h.run(3);
|
||||
assert.equal(yu.skill.activations, 0, 'no cast from the hit');
|
||||
h.spawn('enemy_1422_lrsldr', { pos: [10, 6] });
|
||||
h.step(2);
|
||||
assert.equal(yu.skill.activations, 1, 'an enemy steps onto x-1: cast');
|
||||
done(h);
|
||||
});
|
||||
|
||||
test('余 S2: a leader inside x-1 is teleported onto his tile like any ground enemy (PRTS 卢西恩 / 假想敌:铳 传送抗性 无); a 自缚 leader stays', () => {
|
||||
for (const id of ['chess_char_6_03_a', 'chess_char_6_03_b']) {
|
||||
// (S2 kept empty until the test's own hit: “萨米的意志”'s 冰凌 strikes 余 at once)
|
||||
// (S2 empty at first: it casts the tick its SP is filled, the three leaders already on x-1)
|
||||
const h = yuS2(id, [['enemy_2016_csphtm', 10, 7, 'boss'], ['enemy_9017_achunt', 11, 6, 'boss'], ['enemy_9033_acdeer', 9, 4, 'boss']], 0);
|
||||
const yu = h.unit(id);
|
||||
assert.ok(!yu.skill.active && !yu.skill.ready);
|
||||
@@ -276,8 +306,8 @@ test('余 S2: a leader inside x-1 is teleported onto his tile like any ground en
|
||||
const samiAt = [sami.x, sami.y];
|
||||
const fx0 = h.eventsOf('fx').length;
|
||||
yu.skill.gainSp(yu.skill.spCost, 'init', true);
|
||||
h.b.dealDamage(gun, yu, { amount: 10, type: 'phys', isAttack: true });
|
||||
assert.ok(yu.skill.active && yu.skill.id === 'skchr_yu_2', 'S2 cast on the hit (TAKE_DAMAGE)');
|
||||
h.step(1);
|
||||
assert.ok(yu.skill.active && yu.skill.id === 'skchr_yu_2' && castOf(h, yu).reason === 'SKILL_RANGE', 'S2 cast (SKILL_RANGE)');
|
||||
for (const e of [lucien, gun]) assert.deepEqual([e.y, e.x], [yu.tileR, yu.tileC], `${e.name} teleported onto 余`);
|
||||
assert.deepEqual([sami.x, sami.y], samiAt, '自缚: not a reachable target (PRTS 余 S2 备注)');
|
||||
const fx = h.eventsOf('fx').slice(fx0);
|
||||
@@ -290,15 +320,15 @@ test('余 S2: a leader inside x-1 is teleported onto his tile like any ground en
|
||||
}
|
||||
});
|
||||
|
||||
test('余 S2 with nobody to pull (the usual first hit: a ranged enemy outside x-1): damage-free cast, no teleport and no pull fx', () => {
|
||||
// 深池暗影术师 (range 2.5) on (9,7) and a 萨卡兹枯朽前锋 on (10,8): both outside x-1 (|Δrow| + |Δcol| = 3)
|
||||
const h = yuS2('chess_char_6_03_a', [['enemy_1168_dumage', 9, 7], ['enemy_1422_lrsldr', 10, 8]]);
|
||||
test('余 S2 with nobody to pull (a flyer on x-1 sets it off): the burst hits it, no teleport and no pull fx', () => {
|
||||
const h = yuS2('chess_char_6_03_a', [['fly', 10, 6, null, 'FLY']], 999, { enemies: { fly: enemyRec({ key: 'fly', hp: 1e6, speed: 0, motion: 'FLY' }) } });
|
||||
const yu = h.unit('chess_char_6_03_a');
|
||||
const fx0 = h.eventsOf('fx').length;
|
||||
assert.ok(h.runUntil(() => yu.skill.active, 10), 'the 深池暗影术师 hits him from outside x-1: S2 (TAKE_DAMAGE)');
|
||||
const fx = h.eventsOf('fx').slice(fx0).filter((e) => e[1] === 'teleport' || e[1] === 'pull');
|
||||
assert.deepEqual(fx, [], 'nobody inside x-1: no teleport, no pull fx');
|
||||
for (const e of h.b.enemies) assert.ok(!(Math.round(e.y) === yu.tileR && Math.round(e.x) === yu.tileC), `${e.name} stays`);
|
||||
const fly = h.b.enemies[0];
|
||||
assert.ok(fly.isFlying && yu.skill.active, 'cast: SKILL_RANGE counts any enemy on x-1');
|
||||
assert.ok(fly.hp < fly.s.maxHp, 'the burst hits air units [ASSUMED]');
|
||||
const fx = h.eventsOf('fx').filter((e) => e[1] === 'teleport' || e[1] === 'pull');
|
||||
assert.deepEqual(fx, [], 'a flyer is no 地面可达目标: no teleport, no pull fx');
|
||||
assert.ok(!(Math.round(fly.y) === yu.tileR && Math.round(fly.x) === yu.tileC), 'it stays');
|
||||
done(h);
|
||||
});
|
||||
|
||||
@@ -313,16 +343,18 @@ test('余 S2 on the real leader rounds: 假想敌:铳 teleported onto 余 walk
|
||||
h.step(1);
|
||||
const yu = h.unit('chess_char_6_03_a');
|
||||
const L = () => h.b.enemies.find((e) => e.defId === key && e.alive);
|
||||
const inX1 = (e) => X1.some(([dr, dc]) => Math.round(e.y) === yu.tileR + dr && Math.round(e.x) === yu.tileC + dc);
|
||||
h.b.on('tick', () => { if (!yu.skill.active && !L()?.mem.yuTest) yu.skill.sp = 0; }); // keep S2 for the test's hit
|
||||
assert.ok(h.runUntil(() => L() && inX1(L()), 90), `${key} walks into x-1`);
|
||||
const inX1At = (y, x) => X1.some(([dr, dc]) => Math.round(y) === yu.tileR + dr && Math.round(x) === yu.tileC + dc);
|
||||
// well inside x-1 (0.1 tiles of margin: the next tick's step cannot take it out before the cast)
|
||||
const deep = (e) => [[0, 0], [0.1, 0], [-0.1, 0], [0, 0.1], [0, -0.1]].every(([a, b]) => inX1At(e.y + a, e.x + b));
|
||||
h.b.on('tick', () => { if (!yu.skill.active && !L()?.mem.yuTest) yu.skill.sp = 0; }); // keep S2 for the leader
|
||||
assert.ok(h.runUntil(() => L() && deep(L()), 90), `${key} walks into x-1`);
|
||||
const e = L();
|
||||
assert.ok(e.isBoss && !e.s.flags.selfBound && e.route.legs.length > 1, 'a patrolling leader');
|
||||
e.mem.yuTest = true;
|
||||
const leg0 = e.route.legIdx;
|
||||
yu.skill.gainSp(yu.skill.spCost, 'init', true);
|
||||
h.b.dealDamage(e, yu, { amount: 10, type: 'phys', isAttack: true });
|
||||
assert.ok(yu.skill.active, 'S2');
|
||||
h.step(1);
|
||||
assert.ok(yu.skill.active, 'S2 (SKILL_RANGE)');
|
||||
assert.deepEqual([e.y, e.x], [yu.tileR, yu.tileC], 'teleported onto 余');
|
||||
assert.equal(e.route.pts, null, 'its path is re-planned from there');
|
||||
if (key === 'enemy_9017_achunt') {
|
||||
|
||||
Reference in new issue
Block a user