feedback7 R22-GHSIM: 假想敌:骨刺 targets as PRTS writes — its normal attack ignores its block (by priority, not the blocker first), never a flying unit, and its 3 hidden targets come from its normal attack's reach (PR #365 by @Sukvii, sim part, each line checked against PRTS); golden: roster-018 moves
The sim lines are the PR's (content/enemies/invisible.js kitBoneSpike: profile blockFree and canTarget, the hidden selection over rangeRadius + the ally collider), so the contributor is the author of this commit; the maintainer side checked each rule, kept the engine's single target when the hidden list is empty, and wrote the tests. Official: PRTS 假想敌:骨刺, 级别0 天赋 (on the page since 2025-11, PRTS editors): 「自身普通攻击索敌不受阻挡影响」 「不会攻击 飞行单位,隐匿(解除阻挡0秒后恢复)」 「隐匿期间可同时攻击3个目标」; enemy_database's level description 「隐匿;隐匿状态下同时攻击 3个目标。」 (the handbook line keeps 「数个目标」) — the 3 is no longer [ASSUMED]. blockFree works as for 自制投石机 (a 隐匿 / 迷彩 blocker is no target); canTarget as the engine's other 不会攻击飞行单位 enemies; the hidden 3 are picked among the allies its normal attack could hit (ai.js attackTargets: rangeRadius + ALLY_COLLIDER_RADIUS, PRTS 作战机制 碰撞体积) — they were picked within the bare 2.0. Stealth back 0 s after a block was already so. Golden: roster-018 — the 骨刺 in it lands one more attack (on 异客; dmg 2434 → 2771), the same move PR #365 reported.
This commit is contained in:
1 parent
bfef34ffa3
commit
c250ff66f4
3 files changed
+86
-10
No files matched your search
@@ -2,6 +2,7 @@
|
||||
// content/enemies.js).
|
||||
|
||||
import { canTargetAlly, enemyStealthed } from '../../targeting.js';
|
||||
import { ALLY_COLLIDER_RADIUS } from '../../constants.js';
|
||||
import { T, elem, hurt, targetsNear, byPriority, auraBuff } from './helpers.js';
|
||||
import { stealth, onHitStatus, skill, kitStealth } from './archetypes.js';
|
||||
import { hypot } from '../../detmath.js';
|
||||
@@ -9,7 +10,8 @@ import { hypot } from '../../detmath.js';
|
||||
// ---------------------------------------------------------------------------------------------------------------
|
||||
// constants (numbers that exist nowhere in the data)
|
||||
|
||||
/** "数个目标" of 假想敌:骨刺 while stealthed [ASSUMED]. */
|
||||
/** 假想敌:骨刺's targets at once while its 隐匿 holds: 3 — the level's own description 「隐匿;隐匿状态下同时攻击3个目标。」
|
||||
* (enemy_database; the handbook line says 「数个目标」) and PRTS 天赋 「隐匿期间可同时攻击3个目标」 (GitHub #365). */
|
||||
const ACBUNN_TARGETS = 3;
|
||||
|
||||
/** 重弩突袭者 直击 reach along a row/column [ASSUMED]; the skill's length and its charge before the bolt (PRTS 直击 "蓄力1.4s后
|
||||
@@ -114,11 +116,24 @@ function kitShadowBlade(ab) {
|
||||
}];
|
||||
}
|
||||
|
||||
/**
|
||||
* 假想敌:骨刺 — PRTS 天赋 (级别0): 「自身普通攻击索敌不受阻挡影响」 — profile `blockFree` (as 自制投石机): blocked, and so out of its
|
||||
* 隐匿, it still picks by priority among the allies in reach, not its blocker first, and a 隐匿 / 迷彩 blocker is no target
|
||||
* (targeting.js canTargetAlly); 「不会攻击飞行单位」 — profile `canTarget`; 「隐匿(解除阻挡0秒后恢复)」 — stealth(); 「隐匿期间
|
||||
* 可同时攻击3个目标」: while its 隐匿 holds, the first ACBUNN_TARGETS by priority among the allies its normal attack could hit
|
||||
* (ai.js attackTargets: the same reach — rangeRadius + the ally collider — and the same rules); one otherwise. Until
|
||||
* 0.2.2 (PR #365 by @Sukvii, the PRTS lines checked for this port): blocker first, flyers hit, the 3 picked within the bare
|
||||
* rangeRadius.
|
||||
*/
|
||||
function kitBoneSpike() {
|
||||
return [stealth(), {
|
||||
spawn(b, e) {
|
||||
e.profile.blockFree = true;
|
||||
e.profile.canTarget = (u) => !u.isFlying;
|
||||
},
|
||||
before(c, b, e) {
|
||||
if (!enemyStealthed(e)) return; // 隐匿状态下同时攻击数个目标 (back 0 s after a block)
|
||||
const l = byPriority(e, targetsNear(b, e, e.base.rangeRadius));
|
||||
if (!enemyStealthed(e)) return;
|
||||
const l = byPriority(e, targetsNear(b, e, e.base.rangeRadius + ALLY_COLLIDER_RADIUS).filter(e.profile.canTarget));
|
||||
if (l.length) c.targets = l.slice(0, ACBUNN_TARGETS);
|
||||
},
|
||||
}];
|
||||
|
||||
@@ -186,6 +186,67 @@ test(`${nm('enemy_9008_acbunn')}: attacks several targets at once while stealthe
|
||||
assert.ok(atk.targets.length >= 2, `targets ${atk.targets.length}`);
|
||||
});
|
||||
|
||||
// 骨刺 — PRTS 天赋 (级别0) 「自身普通攻击索敌不受阻挡影响 / 不会攻击飞行单位,隐匿(解除阻挡0秒后恢复) / 隐匿期间可同时攻击3个目标」
|
||||
// and the level description 「隐匿;隐匿状态下同时攻击3个目标。」 (PR #365, the targeting part checked against PRTS).
|
||||
function boneArena(o = {}) {
|
||||
const h = arena({
|
||||
units: ['t_wall', 't_wall2', 't_wall3', 't_wall4'].map((chessId, i) => ({ chessId, row: 9 + (i % 2), col: 6 + Math.floor(i / 2) })),
|
||||
captureNoisy: true, hooks: ['attack'], ...o,
|
||||
});
|
||||
h.step();
|
||||
for (const u of h.allies()) h.b.addBuff(u, { key: 'test:noBlock', persist: true, flags: { noBlock: true } });
|
||||
const e = put(h, 'enemy_9008_acbunn', [9, 6]);
|
||||
return { h, e };
|
||||
}
|
||||
/** The targets of `e`'s next attack. */
|
||||
function boneNext(h, e) {
|
||||
const n = h.hooksOf('attack').filter((c) => c.attacker === e).length;
|
||||
assert.ok(h.runUntil(() => h.hooksOf('attack').filter((c) => c.attacker === e).length > n, 8), 'a new attack');
|
||||
return h.hooksOf('attack').filter((c) => c.attacker === e)[n].targets;
|
||||
}
|
||||
|
||||
test(`${nm('enemy_9008_acbunn')}: 3 targets while hidden, 1 once blocked or revealed — by priority, not its blocker first; 3 again when hidden`, () => {
|
||||
for (const cause of ['block', 'reveal']) {
|
||||
const { h, e } = boneArena();
|
||||
const wall = h.unit('t_wall'), taunter = h.unit('t_wall4');
|
||||
assert.equal(new Set(boneNext(h, e)).size, 3, `${cause}: hidden — three distinct targets of the four in reach`);
|
||||
h.b.addBuff(taunter, { key: 'test:taunt', mods: { taunt: 5 } });
|
||||
if (cause === 'block') { h.b.removeBuff(wall, 'test:noBlock'); h.step(2); assert.equal(e.blockedBy, wall); }
|
||||
else h.b.addBuff(e, { key: 'test:reveal', persist: true, flags: { reveal: true } });
|
||||
assert.deepEqual(boneNext(h, e), [taunter], `${cause}: one target, the taunt-5 one (索敌不受阻挡影响)`);
|
||||
if (cause === 'block') { h.b.addBuff(wall, { key: 'test:noBlock', persist: true, flags: { noBlock: true } }); h.b.releaseBlocked(wall); }
|
||||
else h.b.removeBuff(e, 'test:reveal');
|
||||
assert.equal(boneNext(h, e).length, 3, `${cause}: hidden again (0 s after the block) — three`);
|
||||
checkInvariants(h.b);
|
||||
}
|
||||
});
|
||||
|
||||
test(`${nm('enemy_9008_acbunn')}: never targets a flying unit (炎佑, taunt 5), hidden or revealed`, () => {
|
||||
for (const revealed of [false, true]) {
|
||||
const { h, e } = boneArena({ content: 'full', extraContent: [] });
|
||||
const [air] = spawnYanyou(h.b, 'p1', { count: 1, hp: 50000 });
|
||||
assert.ok(air?.isFlying);
|
||||
air.x = 6.5; air.y = 9;
|
||||
h.b.applyStatus(air, 'stun', { duration: 30, force: true });
|
||||
h.b.addBuff(air, { key: 'test:taunt', mods: { taunt: 5 } });
|
||||
if (revealed) h.b.addBuff(e, { key: 'test:reveal', persist: true, flags: { reveal: true } });
|
||||
const t = boneNext(h, e);
|
||||
assert.equal(t.length, revealed ? 1 : 3);
|
||||
assert.ok(!t.includes(air), `${revealed ? 'revealed' : 'hidden'}: the flyer is no target`);
|
||||
}
|
||||
});
|
||||
|
||||
test(`${nm('enemy_9008_acbunn')}: its 3 hidden targets come from its normal attack's reach (2 + the 0.25 ally collider)`, () => {
|
||||
const { h, e } = boneArena();
|
||||
const edge = h.unit('t_wall3'), outside = h.unit('t_wall4');
|
||||
edge.x = e.x + 2.1; edge.y = e.y;
|
||||
outside.x = e.x + 2.3; outside.y = e.y;
|
||||
h.b.addBuff(edge, { key: 'test:taunt', mods: { taunt: 5 } });
|
||||
const t = boneNext(h, e);
|
||||
assert.ok(t.includes(edge), 'the ally 2.1 away is one of the three');
|
||||
assert.ok(!t.includes(outside), 'the one 2.3 away is out of reach');
|
||||
});
|
||||
|
||||
test(`${nm('enemy_1175_dushdo_2')}: stealth; next to 深池伙友卫队精英 its attack interval drops by traitAbility.base_attack_time`, () => {
|
||||
const h = arena();
|
||||
h.step();
|
||||
|
||||
@@ -1165,7 +1165,7 @@
|
||||
"coins": 12,
|
||||
"dmg": 1560928,
|
||||
"bossDmg": 0,
|
||||
"heal": 29179,
|
||||
"heal": 29516,
|
||||
"deaths": 1,
|
||||
"layerGains": {"lateranoShip":12,"preciShip":12,"sargonShip":26}
|
||||
}
|
||||
@@ -1174,11 +1174,11 @@
|
||||
["p1",1,"op","chess_char_6_03_b",2,"uniequip_002_yu",4558.4,1,3,0,0,0,0,0,0],
|
||||
["p1",2,"op","chess_char_6_06_b",2,"uniequip_002_pepe",3620.85,1,6,0,0,0,0,0,0],
|
||||
["p1",3,"op","chess_char_6_07_b",2,"uniequip_002_siege2",3820.43,1,9,0,0,0,0,0,0],
|
||||
["p1",4,"op","chess_char_6_20_a",2,null,1523.98,1,4,0,0,0,20204,39,2],
|
||||
["p1",4,"op","chess_char_6_20_a",2,null,1523.98,1,4,0,0,0,20541,39,2],
|
||||
["p1",5,"op","chess_char_6_01_b",2,"uniequip_002_lemuen",1811.04,1,7,12,169697,0,0,16,5],
|
||||
["p1",6,"op","chess_char_6_02_b",2,"uniequip_002_sbell2",4314.46,1,10,5,53428,1556,486,8,2],
|
||||
["p1",7,"op","chess_char_6_04_b",2,"uniequip_002_skadi2",1968.74,1,13,4,29961,8646,173,0,4],
|
||||
["p1",8,"op","chess_char_6_05_b",2,"uniequip_002_pasngr",0,0,15,16,204487,8432,2713,36,7],
|
||||
["p1",8,"op","chess_char_6_05_b",2,"uniequip_002_pasngr",0,0,15,16,204487,8769,2713,36,7],
|
||||
["p1",9,"op","chess_char_6_08_b",2,"uniequip_002_reed2",2054.98,1,5,3,63902,0,1900,8,1],
|
||||
["p1",10,"op","chess_char_6_09_b",0,"uniequip_002_cello",1628.7,1,8,0,14472,0,0,5,5],
|
||||
["p1",11,"op","chess_char_6_11_b",2,"uniequip_002_mlyss",2332.18,1,11,20,138355,1500,50,136,4],
|
||||
@@ -1212,12 +1212,12 @@
|
||||
"enemy_1367_dseed": [2,2,0,0,9953],
|
||||
"enemy_1439_dslntf": [1,1,0,0,49766],
|
||||
"enemy_2002_bearmi_2": [1,1,0,0,18248],
|
||||
"enemy_9008_acbunn": [1,1,0,2434,19907]
|
||||
"enemy_9008_acbunn": [1,1,0,2771,19907]
|
||||
},
|
||||
"hooks": [1,145,5849,507,507,1318,1468,3393,124,130,35,31,37,24779,382,5,122,0,1,124,11,6,0],
|
||||
"events": {"atk":596,"bounty":6,"deploy":21,"die":130,"dmg":1388,"engage":12,"fx":256,"heal":179,"layer":11,"skill":66,"spawn":145,"status":387},
|
||||
"hooks": [1,145,5849,507,507,1319,1469,3395,124,130,35,31,37,24779,382,5,122,0,1,124,11,6,0],
|
||||
"events": {"atk":597,"bounty":6,"deploy":21,"die":130,"dmg":1389,"engage":12,"fx":256,"heal":181,"layer":11,"skill":66,"spawn":145,"status":387},
|
||||
"fx": {"beam":4,"bombard":31,"bombardShell":31,"burst":6,"camouflage":2,"copy":1,"dp":4,"explode":1,"garrison":8,"lightning":85,"link":1,"lock":37,"lure":2,"scorchBurst":15,"snow":7,"split":4,"storm":7,"summon":2,"talent":1,"tide":4,"wanted":3},
|
||||
"snaps": "19eb7843 2593e6ad ced96303 58bf6c6a 57b56210 fb902b8c 2edac827 24d6e4e1 4fd0b828 109f10aa 67cb9d6c ce16ff8f a012eb0e 55ce4c3e 7158e61d d736ac42 c7169d95 af7956c1 aa3be1fe fc315fa7"
|
||||
"snaps": "19eb7843 2593e6ad ced96303 58bf6c6a 57b56210 fb902b8c 2edac827 dc1e40f6 4919a6eb 503315c1 d3bf6a2b 9ca37124 7959a1b9 ab8cf469 6e9a303e 47d433a9 d5193bce 9f52967e 7a198ce1 eab6362c"
|
||||
},
|
||||
"roster-019": {
|
||||
"about": "pass 0 band 7: chess_char_6_15_b chess_char_6_19_b chess_char_5_01_b chess_char_5_05_b chess_char_6_17_b chess_char_6_13_b chess_char_6_14_b chess_char_6_16_b chess_char_6_18_b chess_char_6_20_b chess_char_5_02_b chess_char_5_03_b",
|
||||
|
||||
Reference in new issue
Block a user