feedback5 CB4: 自制投石机's targeting ignores its block (索敌不受阻挡影响) — blocked by an operator on the 排气格栅 with nobody else in range it does not attack, as in the official game (community report of 2026-10-06, item 24)
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.
This commit is contained in:
1 parent
d40cb4e79e
commit
fdb3ae1b46
9 files changed
+127
-52
No files matched your search
+2
-1
@@ -285,7 +285,8 @@ took off (出血, 沙狱, burning DoTs, 淤困, 【自然涌动】 — a tick se
|
||||
sourceless 毒雾 of 假想敌:蚀裂 still reach it [ASSUMED]; a ground enemy's area skill whose cast depends on allies nearby counts only the targets of its trigger
|
||||
selection — 卢西恩's 【aoe】 ("需要目标"), 锏's CircleAttack (`targetsNear`, PRTS 选择器 "所有触发选择器通常不无视迷彩");
|
||||
a stealthed ally (隐匿, 排气格栅) only for the enemy it blocks — our operators keep 隐匿 while blocking (PRTS 作战机制
|
||||
§隐匿; 索敌的概念: a blocked enemy "强行无视对方可选性" attacks its blocker); a camouflaged one (迷彩, flag `camou`: ba.camou
|
||||
§隐匿; 索敌的概念: a blocked enemy "强行无视对方可选性" attacks its blocker) — except an enemy whose 索敌不受阻挡影响
|
||||
(profile `blockFree`: 自制投石机 — it selects as if unblocked, by 仇恨值, so a 隐匿 blocker is no target; DESIGN §25.18); a camouflaged one (迷彩, flag `camou`: ba.camou
|
||||
"不阻挡时不成为敌方普通攻击的目标") likewise (PRTS 异常效果: neither anomaly is "阻挡时解除") — for every target selection
|
||||
(attacks, skill picks, cast conditions, a normal attack on every operator in range — 斩胄之剑 / 破胄之锤's hover attack,
|
||||
“灵幛”). **Enemy area effects** — splash, death and self blasts, area skills and statuses, pulses, the zones an enemy
|
||||
|
||||
+20
-2
@@ -27,7 +27,7 @@ a merged elite 拉普兰德 as a new one (GitHub #169), the leader pool per play
|
||||
model quirks (PR #211) → §25.13; the community reports of 2026-10-06 (the 孤岛风云 prisoners drawn freed, 忍冬's trigger, a
|
||||
bounty burned to death by the 活性源石, 枯朽之种 paying a bounty) → §25.14; the second batch of community reports of
|
||||
2026-10-06, stream CB4 (enemies, bosses, waves, movement: the 突袭 landing on the 临时整备区 row and the other half of the
|
||||
联防 map, a silenced 萨卡兹枯朽战士's death pollution, 萨卡兹穿刺手's speed after a stun and its first hit; the briefing types'
|
||||
联防 map, a silenced 萨卡兹枯朽战士's death pollution, 萨卡兹穿刺手's speed after a stun and its first hit, 自制投石机 blocked on the 排气格栅; the briefing types'
|
||||
7–9 waves, 新硎's 断刃 and a freed prisoner in 联防 already answered, …) → §25.18.
|
||||
|
||||
Decisions of the owner: 2026-10-04 — the server stays on plain `node:http` + `ws`, no web framework (§25.1);
|
||||
@@ -1066,7 +1066,7 @@ fixed (W5AA's commits name every golden scenario moved); two were already fixed
|
||||
is the report; on this tree only the 使徒's own leak carries it.
|
||||
- Golden: unchanged. Pinned: `test/sim/feedback5-community-pins.test.js`.
|
||||
|
||||
### 25.18 Community reports of 2026-10-06, stream CB4: enemies, bosses, waves, movement — `sim/battle/players.js onOwnBoard`, `content/bonds/addon/battle.js raidTile`, `content/enemies/archetypes.js kitPolluted`, `content/enemies/special.js kitRush`
|
||||
### 25.18 Community reports of 2026-10-06, stream CB4: enemies, bosses, waves, movement — `sim/battle/players.js onOwnBoard`, `content/bonds/addon/battle.js raidTile`, `content/enemies/archetypes.js kitPolluted`, `content/enemies/special.js kitRush`, `targeting.js canTargetAlly`
|
||||
|
||||
The owner's second collection of community reports (2026-10-06/07) was split over five streams; CB4 took the items about
|
||||
enemies, bosses, waves and movement (items 6, 10, 16.3, 24, 29, 35, 36, 38, 39, 40, 47, 52, 53, 57 of the list). Each was
|
||||
@@ -1174,3 +1174,21 @@ The real ones are fixed, one commit each with its golden scenarios named; the re
|
||||
coop2-HARD-4 (160 spawned). Tests: `test/content/enemies_bosses.test.js` (3: the layers and the cap, the resets, the
|
||||
first hit; all fail on the parent).
|
||||
|
||||
#### 25.18.7 「玩家的官方原版反馈:「投石车」敌人如果被蒸汽地板上的干员阻挡会不进行攻击」 (item 24): 自制投石机's 索敌不受阻挡影响 — `targeting.js canTargetAlly / sortAllyTargets`, `ai.js attackTargets`, `content/enemies/special.js` (enemy_10162_mnctpt)
|
||||
|
||||
- **The enemy and the terrain**: 「投石车」 = 自制投石机 (enemy_10162_mnctpt, ranged 4.0, the 悬赏·特异III bounty); the 「蒸汽
|
||||
地板」 = 战场#07 (下半)'s 排气格栅 (tile_smog: an operator on it is 隐匿 to enemy ranged attacks).
|
||||
- **Seen here**: blocked by an operator on the smog, it attacked that operator — every enemy does (the engine's rule: a
|
||||
blocked enemy attacks its blocker whatever its selectability, PRTS 索敌的概念 「…强行无视对方可选性发动攻击」).
|
||||
- **Official**: PRTS 自制投石机 天赋 「…索敌不受阻挡影响,且不会因丢失目标而结束攻击」 — the blocker exception does not apply to
|
||||
it, so a 隐匿 blocker is no target and, with nobody else in its range, it does not attack: the players' report.
|
||||
- **Now**: the catapult's profile is `blockFree` (spawns.js gives every enemy the field): `attackTargets` takes the
|
||||
allies in reach as if it were unblocked, `canTargetAlly` refuses a 隐匿 / 迷彩 blocker to it, `sortAllyTargets` puts no
|
||||
blocker first (仇恨值: taunt, then the latest deployed). Every other enemy keeps attacking a 隐匿 blocker.
|
||||
- [ASSUMED]: none for the rule. Not changed, for later: its stones' splash radius (PRTS 「目标半径1.5范围」, the data's
|
||||
`projectile_range` 0.5 × 3; the kit uses 0.5), the 0.3 s between its three stones, and 「不会因丢失目标而结束攻击」.
|
||||
- Golden: 1 battle — standin-06 (the bounty catapult, blocked by 忍冬, now shot the 斯卡蒂 / 瑕光 pieces by 仇恨值, its blocker no longer
|
||||
first). Tests: `test/sim/feedback5-catapult-smog.test.js` (3: no attack on a hidden blocker alone, another operator
|
||||
instead, every other ranged enemy still attacks its hidden blocker; the first two fail on the parent). Docs:
|
||||
docs/SIM.md.
|
||||
|
||||
+5
-2
@@ -718,10 +718,13 @@ export function attackWindup(e) {
|
||||
return a && a.dur > 0 ? clipHit(a) / clipSpeed(e, a) : 0;
|
||||
}
|
||||
|
||||
/** The allies `e` could hit now: its blocker (and, with a range, the others in reach), passing its own target rule. */
|
||||
/**
|
||||
* The allies `e` could hit now: its blocker (and, with a range, the others in reach), passing its own target rule. An
|
||||
* enemy whose 索敌不受阻挡影响 (profile `blockFree`: 自制投石机) selects as if unblocked: the allies in reach it may target.
|
||||
*/
|
||||
function attackTargets(b, e, radius, reach, own) {
|
||||
let targets = [];
|
||||
if (e.blockedBy) {
|
||||
if (e.blockedBy && !(radius > 0 && e.profile && e.profile.blockFree)) {
|
||||
const bl = e.blockedBy;
|
||||
if (radius > 0) {
|
||||
targets = b.alliesInRadius(e.x, e.y, reach, null).filter((a) => a === bl || canTargetAlly(e, a, true));
|
||||
|
||||
@@ -134,8 +134,9 @@ export class BattleSpawns {
|
||||
e.atkStandUntil = -Infinity; // standing for its attack clip (ai.js attackStand)
|
||||
e.swing = false; // a normal attack swung, its damage frame not reached yet (ai.js enemyAttack)
|
||||
// every enemy profile starts with the same fields (stable object shapes keep the hot loop's property reads fast);
|
||||
// `dmgType` null = the data's (content may arm a data-unarmed enemy: ai.js enemyAttack)
|
||||
e.profile = { noAttack: def.dmgType === 'none', maxTargets: 1, atkScale: 1, dmgType: null };
|
||||
// `dmgType` null = the data's (content may arm a data-unarmed enemy: ai.js enemyAttack); `blockFree` = its 索敌不受阻挡
|
||||
// 影响 (content: 自制投石机 — targeting.js canTargetAlly / sortAllyTargets, ai.js attackTargets)
|
||||
e.profile = { noAttack: def.dmgType === 'none', maxTargets: 1, atkScale: 1, dmgType: null, blockFree: false };
|
||||
e.route = { legs: route ? compileRoute(route, this.rect) : [], legIdx: 0, pts: null, ptIdx: 0, suffix: null, version: -1, waitLeft: null };
|
||||
if (!e.route.legs.length) {
|
||||
const end = this.grid.specialTiles('end')[0];
|
||||
|
||||
@@ -13,7 +13,8 @@
|
||||
// there: an enemy gains 1 layer — 2 at 重量 ≥ heavyWeight (the device's `value`, 3) — of ASPD aspdPerStack
|
||||
// (fractions are ×100 ASPD) and move speed × (1 + moveMulPerStack × layers), an operator 1 layer of the
|
||||
// ASPD part only; at most maxStacks layers; cleared on leaving
|
||||
// 烟雾 g operators on it cannot be targeted by enemy ranged attacks (stealth flag: blocked enemies still hit them)
|
||||
// 烟雾 g operators on it cannot be targeted by enemy ranged attacks (stealth flag: blocked enemies still hit them —
|
||||
// not 自制投石机, whose 索敌不受阻挡影响: blocked by one with nobody else in range it does not attack)
|
||||
// 深水 d ground enemies on it: sea_drown[enemy].damage dmg/s (无来源 true 持续伤害, not 环境伤害: tags dot /
|
||||
// periodic / deepsea), ASPD attack_speed (×100), move × move_speed
|
||||
// 活性源石 i a unit on it (allies and ground enemies) gets a timed effect: damage true dmg/s, ATK + atk, ASPD +
|
||||
|
||||
@@ -404,7 +404,11 @@ export const SPECIAL_KITS = Object.freeze({
|
||||
b.fx('explode', { x: e.x, y: e.y, r, kind: 'heal' });
|
||||
for (const o of b.enemiesInRadius(e.x, e.y, r)) if (o !== e) b.heal(e, o, amt);
|
||||
} }],
|
||||
enemy_10162_mnctpt: (ab) => [{ // 自制投石机 · 3-hit attacks with small splash
|
||||
enemy_10162_mnctpt: (ab) => [{ // 自制投石机 · 3-hit attacks with small splash; 索敌不受阻挡影响
|
||||
// PRTS 天赋 「索敌不受阻挡影响,且不会因丢失目标而结束攻击」: its target selection ignores its block — a 隐匿 blocker (an
|
||||
// operator on the 排气格栅) is no target, so with nobody else in range it does not attack (the official game, community
|
||||
// report of 2026-10-06, item 24); it picks by 仇恨值, not its blocker first
|
||||
spawn(b, e) { e.profile.blockFree = true; },
|
||||
dealt(c, b, e) {
|
||||
const n = T(ab, 'Attack.attack@times') ?? 1, r = T(ab, 'Attack.attack@projectile_range') ?? 0;
|
||||
const t = c.target;
|
||||
|
||||
@@ -99,13 +99,15 @@ export function canTargetEnemy(attacker, e, profile) {
|
||||
* PRTS 异常效果 gives both anomalies the note "与'阻挡时解除'没有直接关系" — for this target selection (an attack, a skill
|
||||
* pick, a cast condition, a normal attack on every ally in range: PRTS 选择器 "所有触发选择器通常不无视迷彩"); an area
|
||||
* effect selects with areaSelectable / auraSelectable, which do not check 迷彩. An airborne ally (起飞, flag `liftoff`) is
|
||||
* never a target of a ground enemy (evadesGround).
|
||||
* never a target of a ground enemy (evadesGround). An enemy whose 索敌不受阻挡影响 (profile `blockFree`: 自制投石机, PRTS 天赋)
|
||||
* has no blocker exception: a 隐匿 / 迷彩 ally that blocks it is no target either — so, blocked by an operator on the
|
||||
* 排气格栅 with nobody else in range, it does not attack (the official game, community report of 2026-10-06, item 24).
|
||||
*/
|
||||
export function canTargetAlly(e, a, ranged) {
|
||||
if (!a.alive || !a.deployed || a.hidden || a.kind === 'device') return false;
|
||||
const f = a.s.flags;
|
||||
if (f.untargetable || f.sleep) return false;
|
||||
if (ranged && (f.stealth || f.camou) && e.blockedBy !== a) return false;
|
||||
if (ranged && (f.stealth || f.camou) && (e.blockedBy !== a || (e.profile && e.profile.blockFree))) return false;
|
||||
if (f.liftoff && evadesGround(e, a)) return false;
|
||||
return true;
|
||||
}
|
||||
@@ -221,8 +223,9 @@ export function sortEnemyTargets(battle, attacker, cands, priority) {
|
||||
*/
|
||||
export function sortAllyTargets(enemy, cands) {
|
||||
if (cands.length <= 1) return cands;
|
||||
const bl = enemy.profile && enemy.profile.blockFree ? null : enemy.blockedBy; // 索敌不受阻挡影响: no blocker first
|
||||
cands.sort((a, b) => {
|
||||
const ba = enemy.blockedBy === a ? 0 : 1, bb = enemy.blockedBy === b ? 0 : 1;
|
||||
const ba = bl === a ? 0 : 1, bb = bl === b ? 0 : 1;
|
||||
return ba - bb || aggroCmp(a, b);
|
||||
});
|
||||
return cands;
|
||||
|
||||
+28
-40
@@ -306,58 +306,46 @@
|
||||
"time": 183.7,
|
||||
"ticks": 5511,
|
||||
"reason": "cleared",
|
||||
"killed": 50,
|
||||
"killed": 25,
|
||||
"total": 98,
|
||||
"unspawned": 0,
|
||||
"bossHpLeft": null,
|
||||
"rngDraws": 345,
|
||||
"rngDraws": 272,
|
||||
"errors": {"count":0,"kinds":[]},
|
||||
"players": {
|
||||
"p1": {
|
||||
"killed": 50,
|
||||
"total": 98,
|
||||
"perfect": false,
|
||||
"leaks": 48,
|
||||
"leaksUncounted": 0,
|
||||
"coins": 8,
|
||||
"dmg": 676478,
|
||||
"bossDmg": 0,
|
||||
"heal": 30426,
|
||||
"deaths": 31,
|
||||
"layerGains": {"raidShip":26,"visiShip":12}
|
||||
}
|
||||
"p1": {"killed":25,"total":98,"perfect":false,"leaks":73,"leaksUncounted":0,"coins":4,"dmg":397046,"bossDmg":0,"heal":12225,"deaths":31,"layerGains":{"raidShip":24}}
|
||||
},
|
||||
"units": [
|
||||
["p1",1,"op","chess_char_3_12_b",2,"none",1904,1,140,10,155395,5504,915,21,3],
|
||||
["p1",2,"op","chess_char_4_09_b",0,"none",0,0,6,10,119787,3510,0,39,1],
|
||||
["p1",3,"op","chess_char_3_05_b",2,"none",0,0,90,2,23950,8566,0,18,2],
|
||||
["p1",4,"op","chess_char_3_18_b",2,"none",0,0,93,3,28745,9190,2808,27,2],
|
||||
["p1",5,"op","chess_char_4_04_b",1,"none",0,0,137,0,25476,10224,0,40,9],
|
||||
["p1",6,"op","chess_char_4_07_b",1,"none",0,0,101,3,28031,10981,0,27,1],
|
||||
["p1",7,"op","chess_char_4_16_b",1,"none",0,0,139,7,42175,16104,0,70,12],
|
||||
["p1",8,"op","chess_char_3_01_b",2,"none",0,0,122,1,16716,3020,0,24,1],
|
||||
["p1",9,"op","chess_char_3_04_b",2,"none",0,0,4,0,0,15932,26703,25,2],
|
||||
["p1",10,"op","chess_char_3_21_b",2,"none",0,0,118,12,175716,7911,0,55,1],
|
||||
["p1",11,"op","chess_char_4_02_b",1,"none",0,0,124,2,41362,2966,0,27,1],
|
||||
["p1",12,"op","chess_char_4_13_b",1,"none",0,0,103,0,19124,2704,0,26,2]
|
||||
["p1",1,"op","chess_char_3_12_b",2,"none",1904,1,138,4,33527,4810,222,6,1],
|
||||
["p1",2,"op","chess_char_4_09_b",0,"none",0,0,6,2,12272,3510,0,5,1],
|
||||
["p1",3,"op","chess_char_3_05_b",2,"none",0,0,90,1,29963,11344,0,19,2],
|
||||
["p1",4,"op","chess_char_3_18_b",2,"none",0,0,12,1,32236,5744,2553,22,1],
|
||||
["p1",5,"op","chess_char_4_04_b",1,"none",0,0,137,0,21284,11506,0,43,9],
|
||||
["p1",6,"op","chess_char_4_07_b",1,"none",0,0,93,0,13096,7538,0,19,1],
|
||||
["p1",7,"op","chess_char_4_16_b",1,"none",0,0,135,2,26045,13645,0,38,12],
|
||||
["p1",8,"op","chess_char_3_01_b",2,"none",0,0,128,1,13435,3020,0,21,1],
|
||||
["p1",9,"op","chess_char_3_04_b",2,"none",0,0,4,0,0,1374,9451,11,1],
|
||||
["p1",10,"op","chess_char_3_21_b",2,"none",0,0,112,11,185272,6695,0,50,1],
|
||||
["p1",11,"op","chess_char_4_02_b",1,"none",0,0,2,0,16202,1483,0,17,1],
|
||||
["p1",12,"op","chess_char_4_13_b",1,"none",0,0,101,3,13715,3085,0,23,1]
|
||||
],
|
||||
"enemies": {
|
||||
"enemy_10162_mnctpt": [1,1,0,7378,39813],
|
||||
"enemy_1019_jshoot": [1,1,0,295,4479],
|
||||
"enemy_1019_jshoot_2": [1,1,0,837,6718],
|
||||
"enemy_1021_bslime": [1,1,0,1648,6121],
|
||||
"enemy_1023_jmage": [1,1,0,2497,5474],
|
||||
"enemy_10162_mnctpt": [1,0,1,13739,31746],
|
||||
"enemy_1019_jshoot": [1,1,0,769,4479],
|
||||
"enemy_1019_jshoot_2": [1,1,0,1779,6718],
|
||||
"enemy_1021_bslime": [1,1,0,2095,6121],
|
||||
"enemy_1023_jmage": [1,0,1,3326,1518],
|
||||
"enemy_1025_reveng": [1,1,0,0,12442],
|
||||
"enemy_1026_aghost": [1,1,0,0,9456],
|
||||
"enemy_1043_zomsbr": [1,1,0,0,6285],
|
||||
"enemy_1251_lysyta_2": [57,33,24,13447,289829],
|
||||
"enemy_1252_lysytb_2": [21,8,13,29953,226804],
|
||||
"enemy_1329_cbshld_2": [12,1,11,40556,69057]
|
||||
"enemy_1043_zomsbr": [1,0,1,0,0],
|
||||
"enemy_1251_lysyta_2": [57,15,42,6671,148451],
|
||||
"enemy_1252_lysytb_2": [21,4,17,25366,127156],
|
||||
"enemy_1329_cbshld_2": [12,1,11,20011,48959]
|
||||
},
|
||||
"hooks": [1,140,5511,596,596,875,906,94,81,139,37,37,0,7322,36,60,98,48,1,89,7,0,4],
|
||||
"events": {"atk":684,"bounty":4,"deploy":42,"die":91,"dmg":906,"engage":11,"fx":62,"heal":67,"layer":7,"leak":48,"skill":74,"spawn":110,"status":117},
|
||||
"fx": {"blink":10,"camouflage":1,"dodge":6,"dp":13,"explode":1,"garrison":7,"slash":24},
|
||||
"snaps": "be6d1821 fa43a30d 52099f77 a860ccb6 ea311a6e 44affa90 9dabf4d8 83adbc77 77dfae35 f02fd736 470fba6a a9871a60 b7e054aa ae8c4b73 55df9303 1c374baa 0d780d78 e03a2b60 b7879d42"
|
||||
"hooks": [1,138,5511,428,428,618,635,38,56,137,32,32,0,5767,24,40,98,73,1,59,3,0,6],
|
||||
"events": {"atk":493,"bounty":2,"deploy":40,"die":64,"dmg":635,"engage":11,"fx":32,"heal":36,"layer":3,"leak":73,"skill":64,"spawn":110,"status":83},
|
||||
"fx": {"blink":8,"camouflage":1,"dodge":6,"dp":5,"explode":1,"garrison":3,"slash":8},
|
||||
"snaps": "be6d1821 69cb5865 7bea5cb3 02fefb75 5eacb174 5bc1814c a70bea6d 0de1251c 172a35f2 f9001627 db7b6b38 a6083013 e2198a24 0adb9d99 8eed5e8b b756dcfe 538dc28e 876d2f95 835a73d1"
|
||||
},
|
||||
"standin-07": {
|
||||
"about": "elite band 6 as stand-ins: chess_char_4_17_b→char_610_acfend chess_char_4_18_b→char_602_cdfend chess_char_5_05_b→char_602_cdfend chess_char_4_19_b→char_608_acpion chess_char_5_01_b→char_600_cpione chess_char_4_22_b→char_609_acguad chess_char_4_23_b→char_609_acguad chess_char_5_06_b→char_609_acguad chess_char_4_20_b→char_603_csnipe chess_char_4_24_b→char_604_ccast chess_char_5_02_b→char_604_ccast chess_char_5_03_b→char_612_accast",
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
// 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);
|
||||
});
|
||||
Reference in new issue
Block a user