The 「投石车」 is 自制投石机 (enemy_10162_mnctpt), the 「蒸汽地板」 the 排气格栅 smog (隐匿 to enemy ranged attacks). PRTS 天赋 「索敌不受阻挡影响」: the blocker exception (a blocked enemy attacks its blocker whatever its selectability) does not apply to it. New enemy profile field `blockFree` (spawns.js): attackTargets selects as if unblocked, canTargetAlly refuses a 隐匿 / 迷彩 blocker to it, sortAllyTargets puts no blocker first. Other enemies unchanged. Golden: standin-06 (the bounty catapult, blocked by 忍冬, now shoots by 仇恨值 — checked instrumented). Tests: test/sim/feedback5-catapult-smog.test.js (3; 2 fail on the parent). Docs: DESIGN §25.18.7, docs/SIM.md.
57 lines
3.5 KiB
JavaScript
57 lines
3.5 KiB
JavaScript
// test/sim/feedback5-catapult-smog.test.js — 自制投石机's 「索敌不受阻挡影响」 (community report of 2026-10-06, item 24, a
|
|
// player's report of the official game: 「投石车」敌人如果被蒸汽地板上的干员阻挡会不进行攻击; DESIGN §25.18).
|
|
// PRTS 自制投石机 天赋: 「普通攻击为3连击…索敌不受阻挡影响,且不会因丢失目标而结束攻击」. An operator on the 排气格栅 smog
|
|
// (战场#07 下半, tile_smog) is 隐匿 for enemy ranged attacks; any other enemy still attacks the operator that blocks it
|
|
// ("阻挡优先级最高"), but the catapult's targeting ignores its block, so that blocker is no target: alone, it is not attacked.
|
|
// Run: node --test test/sim/feedback5-catapult-smog.test.js
|
|
|
|
import { test } from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import { makeBattle, chessRec, enemyRec, flatStage, checkInvariants } from '../helpers/battleHarness.js';
|
|
|
|
const KEY = 'enemy_10162_mnctpt'; // 自制投石机 (RANGED, range 4.0)
|
|
const wall = chessRec({ id: 't_wall', profession: 'TANK', stats: { atk: 0, maxHp: 1e9, def: 0, blockCnt: 3 }, rangeGrid: [[0, 0]], skill: null });
|
|
const other = chessRec({ id: 't_other', profession: 'SNIPER', stats: { atk: 0, maxHp: 1e9, def: 0, blockCnt: 0 }, rangeGrid: [[0, 0]], skill: null });
|
|
const kits = { t_wall: () => ({ trait: { noAttack: true } }), t_other: () => ({ trait: { noAttack: true } }) };
|
|
const route = { motion: 'WALK', start: [9, 10], end: [9, 2], checkpoints: [] };
|
|
const shooter = enemyRec({ key: 'enemy_t_shooter', hp: 1e9, atk: 100, range: 3, speed: 1, bat: 2 }); // an ordinary ranged walker
|
|
|
|
function run({ smog, withOther = false, key = KEY }) {
|
|
const stage = flatStage();
|
|
if (smog) stage.rows[9] = stage.rows[9].slice(0, 6) + 'g' + stage.rows[9].slice(7); // the blocker's tile (9,6)
|
|
const units = [{ chessId: 't_wall', row: 9, col: 6 }];
|
|
if (withOther) units.push({ chessId: 't_other', row: 11, col: 6 });
|
|
const h = makeBattle({ stage, autoFinish: false, timeLimit: 60, defs: { chess: { t_wall: wall, t_other: other }, enemies: { enemy_t_shooter: shooter } }, kits, units, enemies: [{ key, route }] });
|
|
h.step();
|
|
const e = h.enemy(key);
|
|
assert.ok(h.runUntil(() => e.blockedBy, 30), 'blocked by the wall');
|
|
h.run(20);
|
|
checkInvariants(h.b);
|
|
assert.equal(h.b.errors.length, 0);
|
|
return { e, h, wall: h.unit('t_wall'), other: withOther ? h.unit('t_other') : null };
|
|
}
|
|
|
|
test('自制投石机 blocked by an operator on the 排气格栅, nobody else in range: no attack', () => {
|
|
const { e, wall: w } = run({ smog: true });
|
|
assert.ok(w.s.flags.stealth, 'the smog tile hides the blocker from ranged attacks');
|
|
assert.equal(e.blockedBy, w);
|
|
assert.equal(e.stats.attacks, 0);
|
|
assert.equal(w.stats.taken, 0);
|
|
});
|
|
|
|
test('自制投石机 blocked on the 排气格栅 shoots another operator in its range instead; off the smog it hits its blocker', () => {
|
|
const a = run({ smog: true, withOther: true });
|
|
assert.ok(a.e.stats.attacks >= 2);
|
|
assert.equal(a.wall.stats.taken, 0, 'the hidden blocker is never its target');
|
|
assert.ok(a.other.stats.taken > 0);
|
|
const b = run({ smog: false });
|
|
assert.ok(b.e.stats.attacks >= 2);
|
|
assert.ok(b.wall.stats.taken > 0, 'a visible blocker is a target as before');
|
|
});
|
|
|
|
test('any other ranged enemy still attacks the operator that blocks it on the 排气格栅 (阻挡优先级最高)', () => {
|
|
const { e, wall: w } = run({ smog: true, key: 'enemy_t_shooter' });
|
|
assert.ok(w.s.flags.stealth);
|
|
assert.ok(e.stats.attacks >= 2 && w.stats.taken > 0);
|
|
});
|