feedback2 WJ: tests — 蚀裂's 毒雾 and the 深池伙友卫队 / 扎罗 auras vs 隐匿 and 起飞, 死亡之眼's locked target, the blade / hammer hover attack, the 自行炮 and 深溟巢涌者 vs 迷彩, 祈祷邀约 vs 隐匿
This commit is contained in:
1 parent
852c9d5bcf
commit
dcdb2754cf
1 file changed
+149
-7
@@ -2,21 +2,26 @@
|
||||
//
|
||||
// Official: PRTS 作战机制 §隐匿 "隐匿效果使得获得该效果的单位无法被任何敌方的能力索敌选中" (a stealthed operator that blocks an
|
||||
// enemy is attacked by it whatever its selectability: "因为“阻挡优先级最高”的效果,敌人会无视一切可选性对该干员进行攻击");
|
||||
// §AOE伤害判定 "AOE的判定是对攻击范围内的每个可以被选中的敌人进行判定"; PRTS 选择器 (可选判定): a selector skips 无法选择 units
|
||||
// unless the ability "无视可选性" — as PRTS marks 萨卡兹悖谬暴虐兵长's 暴击 "(无视无法选择,无视迷彩)" and 假想敌:淤困's burst
|
||||
// spread "(中点判定,无视目标可选性,不受迷彩制约)"; gamedata_const ba.invisible 隐匿 "不阻挡时不成为敌方攻击的目标" vs
|
||||
// ba.camou 迷彩 "不阻挡时不成为敌方普通攻击的目标(无法躲避溅射类攻击)"; PRTS 异常效果 迷彩 "所有光环类能力、以及涉及中点判定/
|
||||
// 格子判定的效果均不受迷彩制约"; PRTS 选择器 "所有触发选择器通常不无视迷彩" (an area skill is cast for a target it may attack).
|
||||
// §AOE伤害判定 "AOE的判定是对攻击范围内的每个可以被选中的敌人进行判定"; §隐匿与Buff的关系 "隐匿状态下的单位一般无法被敌方的索敌
|
||||
// 机制和Buff选择器选中为目标" (寒霜's debuff being its example of one that ignores 隐匿); PRTS 异常效果 §无法选择: a unit with
|
||||
// 隐匿 / 不可选中 / 无敌 / 对地规避 is skipped by "常见的、来自不同阵营的“选择”行为" unless the ability "无视可选性" — as PRTS
|
||||
// marks 萨卡兹悖谬暴虐兵长's 暴击 "(无视无法选择,无视迷彩)" and 假想敌:淤困's burst spread "(中点判定,无视目标可选性,不受迷彩
|
||||
// 制约)", while "'直接选中'的能力…不受这些仅在选择时生效的异常效果制约"; gamedata_const ba.invisible 隐匿 "不阻挡时不成为敌方攻击
|
||||
// 的目标" vs ba.camou 迷彩 "不阻挡时不成为敌方普通攻击的目标(无法躲避溅射类攻击)"; PRTS 异常效果 迷彩 "所有光环类能力、以及涉及
|
||||
// 中点判定/格子判定的效果均不受迷彩制约"; PRTS 选择器 "所有触发选择器通常不无视迷彩" (an area skill is cast for a target it may
|
||||
// attack; a normal attack on every operator in range skips 迷彩 too).
|
||||
// Cause: the enemy side's area effects (content/enemies.js, content/bosses.js) took every ally in the area; only the
|
||||
// operator side skipped an unblocked 隐匿 enemy (Battle.foesInRadius, 0.1.1).
|
||||
// Now: targeting.js areaSelectable / enemies.js areaAllies, areaAlliesInTiles, fieldAllies — no 隐匿 ally unless it blocks
|
||||
// the enemy, no untargetable or sleeping one, no airborne 起飞 one for a ground enemy; 迷彩 is hit (DESIGN §22.12).
|
||||
// the enemy, no untargetable or sleeping one, no airborne 起飞 one for a ground enemy; 迷彩 is not checked. Buff auras:
|
||||
// auraSelectable / auraAllies (the same without 对地规避). A locked target is hit as a direct pick (targetAndArea).
|
||||
// (DESIGN §22.12.)
|
||||
|
||||
import { test } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { makeBattle, enemyRec, checkInvariants } from '../helpers/battleHarness.js';
|
||||
import { hasGeneratedData } from '../../server/sim/simdata.js';
|
||||
import { areaSelectable } from '../../server/sim/targeting.js';
|
||||
import { areaSelectable, auraSelectable } from '../../server/sim/targeting.js';
|
||||
import { areaAllies } from '../../server/sim/content/enemies.js';
|
||||
|
||||
const REAL = { skip: !hasGeneratedData() };
|
||||
@@ -70,11 +75,14 @@ test('areaSelectable: no 隐匿 ally unless it blocks the source, no untargetabl
|
||||
assert.deepEqual(areaAllies(h.b, g, g.x, g.y, 1.01).map((u) => u.def.name).sort(), [jf, hd].map((u) => u.def.name).sort(), 'areaAllies: the 隐匿 bystander is left out');
|
||||
camou(h, hd);
|
||||
assert.equal(areaSelectable(g, hd), true, '迷彩: still selected by an area effect ("无法躲避溅射类攻击")');
|
||||
assert.equal(auraSelectable(g, gm), false, 'a buff aura: 隐匿, not blocking — not taken');
|
||||
assert.equal(auraSelectable(g, jf), true, 'a buff aura: 隐匿 blocker — taken');
|
||||
h.b.addBuff(gm, { key: 'test:liftoff', persist: true, flags: { liftoff: true } });
|
||||
h.b.removeBuff(gm, 'stealth');
|
||||
assert.equal(areaSelectable(g, gm), false, '起飞 vs a ground source (对地规避)');
|
||||
assert.equal(areaSelectable(f, gm), true, '起飞 vs a flying source');
|
||||
assert.equal(areaSelectable(null, gm), true, '起飞 vs no source');
|
||||
assert.equal(auraSelectable(g, gm), true, '起飞 vs a ground enemy\'s buff aura: still taken (§21.22)');
|
||||
h.b.removeBuff(gm, 'test:liftoff');
|
||||
h.b.addBuff(gm, { key: 'test:untargetable', persist: true, flags: { untargetable: true } });
|
||||
assert.equal(areaSelectable(f, gm), false, 'untargetable (不可选中)');
|
||||
@@ -215,3 +223,137 @@ test('#32.6 ignoreSelect: 萨卡兹悖谬暴虐兵长\'s 暴击 ("无视无法
|
||||
done(h);
|
||||
}
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------------------------------------------
|
||||
// review of the first round
|
||||
|
||||
const liftoff = (h, u) => h.b.addBuff(u, { key: 'test:liftoff', persist: true, flags: { liftoff: true, blockFly: true } }); // 起飞 (蒂比's flag)
|
||||
const pool = (hp) => ({ hp, maxHp: hp, damage(pid, a) { this.hp = Math.max(0, this.hp - a); } });
|
||||
|
||||
test('#32.6 review: 蚀裂\'s 毒雾 and the buff auras of 深池伙友卫队 / 扎罗 (远古威慑) keep a 隐匿 operator out; an airborne 起飞 one is still reached', REAL, () => {
|
||||
// 假想敌:蚀裂 knocked out with no killer: its cloud (r 0.8) covers 角峰 (9,6) and 古米 (10,6) around its spot (9.5, 6)
|
||||
for (const mode of ['stealth', 'liftoff']) {
|
||||
const h = makeBattle({
|
||||
seed: 3, timeLimit: 60, autoFinish: false,
|
||||
units: [{ chessId: JF, row: 9, col: 6, dir: 'RIGHT' }, { chessId: GM, row: 10, col: 6, dir: 'RIGHT' }],
|
||||
enemies: [{ key: 'enemy_9006_actoxi', pos: [9.5, 6], time: 1, mods: { hpMul: 1e3, speedMul: 0, atkMul: 0.01 } }],
|
||||
});
|
||||
h.step(1);
|
||||
const jf = h.unit(JF), gm = h.unit(GM);
|
||||
if (mode === 'stealth') stealth(h, gm); else liftoff(h, gm);
|
||||
const ticks = new Map([[jf, 0], [gm, 0]]);
|
||||
h.b.on('damaged', (c) => { if ((c.dmg.tags || []).includes('poison') && ticks.has(c.target)) ticks.set(c.target, ticks.get(c.target) + 1); }, { priority: 1000 });
|
||||
h.run(1);
|
||||
h.b.kill(h.b.enemies.find((e) => e.defId === 'enemy_9006_actoxi'), null);
|
||||
h.run(10);
|
||||
assert.ok(ticks.get(jf) >= 8, `the cloud ticks on 角峰 (${ticks.get(jf)})`);
|
||||
if (mode === 'stealth') assert.equal(ticks.get(gm), 0, '隐匿 古米: no tick (in 0.1.1 every one)');
|
||||
else assert.equal(ticks.get(gm), ticks.get(jf), 'airborne 古米: every tick (a sourceless cloud selects no ground enemy, §21.22)');
|
||||
done(h);
|
||||
}
|
||||
// 深池伙友卫队's field (with 影刃 next to it) and 扎罗's 远古威慑 (during its 重生): 角峰 plain, 古米 隐匿, 红豆 起飞
|
||||
const aura = (enemies, buff, ko) => {
|
||||
const h = makeBattle({
|
||||
seed: 3, timeLimit: 120, autoFinish: false,
|
||||
units: [{ chessId: JF, row: 10, col: 6, dir: 'RIGHT' }, { chessId: GM, row: 9, col: 7, dir: 'RIGHT' }, { chessId: HD, row: 10, col: 8, dir: 'RIGHT' }],
|
||||
enemies: enemies.map((key, i) => ({ key, pos: [10 + i, 7], time: 1, mods: { hpMul: 1e3, speedMul: 0, atkMul: 0.001 } })),
|
||||
});
|
||||
h.step(1);
|
||||
stealth(h, h.unit(GM)); liftoff(h, h.unit(HD));
|
||||
h.run(1);
|
||||
if (ko) h.b.kill(h.b.enemies.find((e) => e.defId === ko), null);
|
||||
h.run(1);
|
||||
const got = [JF, GM, HD].map((id) => !!h.unit(id).findBuff(buff));
|
||||
done(h);
|
||||
return got;
|
||||
};
|
||||
assert.deepEqual(aura(['enemy_1174_duholy', 'enemy_1175_dushdo_2'], 'ab:forceField'), [true, false, true], '深池伙友卫队: 角峰 yes, 隐匿 古米 no, 起飞 红豆 yes');
|
||||
assert.deepEqual(aura(['enemy_1535_wlfmster'], 'ab:ancientAwe', 'enemy_1535_wlfmster'), [true, false, true], '扎罗 远古威慑: 角峰 yes, 隐匿 古米 no, 起飞 红豆 yes');
|
||||
});
|
||||
|
||||
test('#32.6 review: 死亡之眼\'s burst still hits its locked target that turned 隐匿 mid-channel (a direct pick); a 隐匿 bystander is spared', REAL, () => {
|
||||
const h = makeBattle({
|
||||
seed: 3, timeLimit: 120, autoFinish: false,
|
||||
units: [{ chessId: GM, row: 9, col: 6, dir: 'RIGHT' }, { chessId: JF, row: 10, col: 6, dir: 'RIGHT' }, { chessId: HD, row: 9, col: 5, dir: 'RIGHT' }],
|
||||
enemies: [{ key: 'enemy_1275_dwlock_2', pos: [9, 8], time: 1, mods: { hpMul: 1e3, speedMul: 0, atkMul: 0.2 } }],
|
||||
});
|
||||
h.step(1);
|
||||
const gm = h.unit(GM), jf = h.unit(JF), hd = h.unit(HD);
|
||||
stealth(h, jf);
|
||||
let locked = null;
|
||||
const fx0 = h.b.fx.bind(h.b);
|
||||
h.b.fx = (k, p) => {
|
||||
if (k === 'beam' && p && p.kind === 'deathEye' && !locked) { locked = p.to; h.b.after(1.5, () => stealth(h, gm)); } // 隐匿 mid-channel
|
||||
return fx0(k, p);
|
||||
};
|
||||
const burst = [];
|
||||
h.b.on('damaged', (c) => { if (c.source && c.source.defId === 'enemy_1275_dwlock_2' && c.dmg.element === 'apoptosis' && c.amount > 50) burst.push(c.target); }, { priority: 1000 });
|
||||
h.run(30);
|
||||
assert.equal(locked, gm.id, 'the channel locks 古米');
|
||||
assert.ok(gm.s.flags.stealth, '古米 turned 隐匿 during it');
|
||||
assert.ok(burst.includes(gm), 'the burst hits its locked target');
|
||||
assert.ok(burst.includes(hd), '…and the plain 红豆 next to it');
|
||||
assert.ok(!burst.includes(jf), '…not the 隐匿 角峰 next to it');
|
||||
done(h);
|
||||
});
|
||||
|
||||
test('#32.6 review: the 斩胄之剑 / 破胄之锤 hover attack skips 隐匿 and 迷彩; the 自行炮\'s shots and 深溟巢涌者\'s pulse hit 迷彩; 祈祷邀约 (whole field) skips 隐匿', REAL, () => {
|
||||
// hover attack (a normal attack on every operator in range): 角峰 plain, 古米 隐匿, 红豆 迷彩 — all within 1.6
|
||||
for (const key of ['enemy_9014_acstma', 'enemy_9015_acstmb']) {
|
||||
const h = makeBattle({
|
||||
kind: 'boss', sharedBoss: pool(1e6), seed: 7, autoFinish: false, timeLimit: 600,
|
||||
units: [{ chessId: JF, row: 10, col: 9, dir: 'RIGHT' }, { chessId: GM, row: 10, col: 7, dir: 'RIGHT' }, { chessId: HD, row: 10, col: 8, dir: 'RIGHT' }],
|
||||
});
|
||||
h.step();
|
||||
stealth(h, h.unit(GM)); camou(h, h.unit(HD));
|
||||
const blade = h.spawn(key, { pos: [3, 8], routeIndex: 0, mods: { speedMul: 0 }, tag: 'part' });
|
||||
const hit = [];
|
||||
h.b.on('damaged', (c) => { if (c.source === blade) hit.push(c.target); }, { priority: 1000 });
|
||||
h.run(3);
|
||||
assert.ok(hit.includes(h.unit(JF)), `${key}: hits the plain operator`);
|
||||
assert.ok(!hit.includes(h.unit(GM)) && !hit.includes(h.unit(HD)), `${key}: not the 隐匿 or the 迷彩 one (in 0.1.1 it hit all three)`);
|
||||
done(h);
|
||||
}
|
||||
// 自行炮: 红豆 (迷彩, full HP ratio) in the 9-tile zone around 角峰 (the highest max HP) is shot too
|
||||
{
|
||||
const h = makeBattle({
|
||||
seed: 3, timeLimit: 120, autoFinish: false,
|
||||
units: [{ chessId: JF, row: 9, col: 6, dir: 'RIGHT' }, { chessId: GM, row: 10, col: 6, dir: 'RIGHT' }, { chessId: HD, row: 10, col: 5, dir: 'RIGHT' }],
|
||||
enemies: [{ key: 'enemy_1273_stmgun_2', pos: [9, 9], time: 1, mods: { hpMul: 1e3, speedMul: 0 } }],
|
||||
});
|
||||
h.step(1);
|
||||
camou(h, h.unit(HD));
|
||||
const log = damageLog(h, 'enemy_1273_stmgun_2');
|
||||
h.run(40);
|
||||
assert.ok(log.some((x) => x.target === h.unit(HD) && !x.attack), '迷彩 红豆: shot inside the zone (in 0.1.1 never)');
|
||||
done(h);
|
||||
}
|
||||
// 深溟巢涌者: its pulse rides on its attack — with the plain 古米 in range the 迷彩 红豆 is hit by every pulse; alone it is never
|
||||
for (const alone of [false, true]) {
|
||||
const units = [{ chessId: HD, row: 9, col: 6, dir: 'RIGHT' }];
|
||||
if (!alone) units.push({ chessId: GM, row: 10, col: 7, dir: 'RIGHT' });
|
||||
const h = makeBattle({ seed: 3, timeLimit: 120, autoFinish: false, units, enemies: [{ key: 'enemy_1234_dsubrl', pos: [9, 7], time: 1, mods: { hpMul: 1e3, speedMul: 0 } }] });
|
||||
h.step(1);
|
||||
camou(h, h.unit(HD));
|
||||
const log = damageLog(h, 'enemy_1234_dsubrl');
|
||||
h.run(10);
|
||||
const on = (id) => log.filter((x) => x.target === h.unit(id) && x.attack).length;
|
||||
if (alone) assert.equal(on(HD), 0, '迷彩 alone in range: no attack, so no pulse [the engine attack needs a target]');
|
||||
else { assert.ok(on(GM) >= 5, `plain 古米 pulsed (${on(GM)})`); assert.equal(on(HD), on(GM), '迷彩 红豆: every pulse too (in 0.1.1 none)'); }
|
||||
done(h);
|
||||
}
|
||||
// “萨科塔昂首”'s 【祈祷邀约】 (whole field, "无视迷彩", no 无视无法选择): 角峰 slowed, the 隐匿 古米 not
|
||||
{
|
||||
const h = makeBattle({
|
||||
seed: 3, timeLimit: 120, autoFinish: false,
|
||||
units: [{ chessId: JF, row: 10, col: 6, dir: 'RIGHT' }, { chessId: GM, row: 9, col: 7, dir: 'RIGHT' }],
|
||||
enemies: [{ key: 'enemy_10085_hllevi_2', pos: [10, 9], time: 1, route: 2, mods: { hpMul: 1e3, speedMul: 0 } }],
|
||||
});
|
||||
h.step(1);
|
||||
stealth(h, h.unit(GM));
|
||||
h.run(22);
|
||||
assert.ok(h.unit(JF).findBuff('ab:roar'), '角峰: 受邀祈祷');
|
||||
assert.ok(!h.unit(GM).findBuff('ab:roar'), '隐匿 古米: not selected');
|
||||
done(h);
|
||||
}
|
||||
});
|
||||
Reference in new issue
Block a user