Files
ark-stronghold-protocol/test/content/feedback7-yanyou-noair.test.js
T
sganggs 977215e14d feedback7 R22-OSIM: enemy effects PRTS marks 不可对空 no longer hit the flying 炎佑 (death blasts, 水遁忍者's vortex, 鼎沸, 祈祷邀约, the 胄 blades); golden: unchanged
Community report relayed by the owner: 炎祐好像吃到了不该吃到的地面伤害. 炎佑 is a flying ally (PRTS “炎佑”
行动方式 飞行). The ground terrain (活性源石), 烹泉 / 沏虹, 庞贝 and 集团军重型火炮 already skipped it, but these
不可对空 effects (PRTS, read for every enemy of data/enemies.json) went through areaAllies / fieldAllies /
targetsNear, which exclude 起飞 allies only:
- 死亡爆炸 of 高能源石虫 / 冰爆源石虫 / 卷心籽 (archetypes deathBoom: `noAir`, on by default; 寒冷 too);
- 水遁忍者 漩涡形态 "每秒对半径1.0范围内的所有我方单位造成…(无视迷彩,不可对空)";
- 鼎沸 "攻击范围内的所有我方单位每秒受到…法术持续伤害与…灼燃损伤(不可对空)";
- “萨科塔昂首” 祈祷邀约 "令全场我方单位(不可对空,无视迷彩)获得15s【受邀祈祷】攻击速度-30";
- “斩胄之剑” / “破胄之锤” "普通攻击对攻击范围内的所有我方单位造成…物理普通伤害,不可对空" (its target rule too).
A ground enemy's normal attack and the 不可对空-free effects still reach it (PRTS: enemies that hit air target it).

Tests: test/content/feedback7-yanyou-noair.test.js (8; all fail on the parent).
2026-10-08 19:36:35 +08:00

89 lines
4.4 KiB
JavaScript

// test/content/feedback7-yanyou-noair.test.js — 炎佑 is a flying ally (PRTS “炎佑” 行动方式 飞行): the enemy effects PRTS marks
// 不可对空 never reach it (community report: 炎祐好像吃到了不该吃到的地面伤害). The death blasts of 高能源石虫 / 冰爆源石虫 /
// 卷心籽 ("…无视迷彩,不可对空"), 水遁忍者's 漩涡形态 ("每秒对半径1.0范围内的所有我方单位造成…(无视迷彩,不可对空)"), “萨科塔昂首”'s
// 祈祷邀约 ("令全场我方单位(不可对空,无视迷彩)获得15s【受邀祈祷】"), 鼎沸's pulses ("攻击范围内的所有我方单位每秒受到…(不可对空)")
// and the attack of “斩胄之剑” / “破胄之锤” ("普通攻击对攻击范围内的所有我方单位造成…物理普通伤害,不可对空") took it until 0.2.1; a
// ground operator in reach still does.
// Run: node --test test/content/feedback7-yanyou-noair.test.js
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { makeBattle, checkInvariants } from '../helpers/battleHarness.js';
import { spawnYanyou } from '../../server/sim/content/tokens.js';
/** 角峰 on (10,5), the 炎佑 dragon moved onto (10,6). */
function field(seed = 4) {
const h = makeBattle({ timeLimit: 60, autoFinish: false, seed, hooks: ['damaged'], captureNoisy: true,
units: [{ uid: 1, chessId: 'chess_char_1_02_a', row: 10, col: 5 }] });
h.step();
const dragon = spawnYanyou(h.b, 'p1', { count: 1 })[0];
assert.ok(dragon && dragon.isFlying, 'a flying ally');
assert.ok(h.b.relocate(dragon, 10, 6));
return { h, dragon, op: h.unit(1) };
}
const still = (e) => { e.base.moveSpeed = 0; e.s.moveSpeed = 0; };
const tough = (e) => { e.base.maxHp = 1e9; e.s.maxHp = 1e9; e.hp = 1e9; };
/** Damage `src` dealt to `t` other than its normal attacks. */
const taken = (h, t, src) => h.hooksOf('damaged').filter((c) => c.target === t && c.source === src && !c.dmg?.isAttack).reduce((s, c) => s + c.amount, 0);
for (const [name, key, prep] of [
['高能源石虫', 'enemy_1021_bslime', null],
['冰爆源石虫', 'enemy_1067_snslime', null],
['卷心籽 (ignited)', 'enemy_10065_ftzlc', (e) => { e.mem.ab.ignited = true; }],
]) {
test(`${name}'s death blast (不可对空) spares 炎佑 and still hits a ground operator`, () => {
const { h, dragon, op } = field();
const e = h.spawn(key, { pos: [10, 6] });
still(e);
if (prep) prep(e);
h.step();
const hp0 = dragon.hp;
h.b.dealDamage(null, e, { amount: e.hp + 10, type: 'true', tags: ['test'] });
h.run(0.3);
assert.equal(e.alive, false);
assert.ok(taken(h, op, e) > 0, 'the ground operator is hit');
assert.equal(taken(h, dragon, e), 0, 'the flyer is not');
assert.equal(dragon.hp, hp0);
if (key === 'enemy_1067_snslime') {
assert.ok(op.findBuff('cold'), '寒冷 on the ground operator');
assert.equal(dragon.findBuff('cold'), null, 'no 寒冷 on the flyer');
}
checkInvariants(h.b);
});
}
test('水遁忍者\'s 漩涡形态 (不可对空) hits the ground operator beside it, never 炎佑', () => {
const { h, dragon, op } = field(6);
const e = h.spawn('enemy_10116_ymgtop', { pos: [10, 6] });
still(e);
tough(e);
assert.ok(h.runUntil(() => taken(h, op, e) > 0, 40), 'the vortex reached the operator');
h.run(3);
assert.equal(taken(h, dragon, e), 0);
checkInvariants(h.b);
});
test('“萨科塔昂首”\'s 祈祷邀约 (不可对空) slows the ground operator, not 炎佑', () => {
const { h, dragon, op } = field(7);
const e = h.spawn('enemy_10085_hllevi_2', { pos: [10, 8] });
still(e);
tough(e);
assert.ok(h.runUntil(() => !!op.findBuff('ab:roar'), 40), 'the ground operator is invited');
assert.equal(dragon.findBuff('ab:roar'), null);
checkInvariants(h.b);
});
for (const [name, key] of [['鼎沸', 'enemy_10054_cjhot'], ['“斩胄之剑”', 'enemy_9014_acstma'], ['“破胄之锤”', 'enemy_9015_acstmb']]) {
test(`${name} (不可对空) hits the ground operator beside it, never 炎佑`, () => {
const { h, dragon, op } = field(8);
const e = h.spawn(key, { pos: [10, 6] });
still(e);
tough(e);
h.run(12);
const dealt = (t) => h.hooksOf('damaged').filter((c) => c.target === t && c.source === e).reduce((s, c) => s + c.amount, 0);
assert.ok(dealt(op) > 0, 'the ground operator is hit');
assert.equal(dealt(dragon), 0, 'the flyer is not');
checkInvariants(h.b);
});
}