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
+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;
|
||||
|
||||
Reference in new issue
Block a user