From 791640e7178b588f5c6556c9d2fdab119349a3c4 Mon Sep 17 00:00:00 2001 From: sganggs Date: Sat, 3 Oct 2026 17:47:19 +0800 Subject: [PATCH] =?UTF-8?q?feedback1=20integrate:=20=E5=8D=A2=E8=A5=BF?= =?UTF-8?q?=E6=81=A9=20and=20=E9=94=8F=20cast=20their=20area=20skills=20on?= =?UTF-8?q?ly=20with=20someone=20they=20can=20hurt=20(QA)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 卢西恩's 【aoe】 and 锏's CircleAttack counted every ally in their radius, so an airborne 蒂比 alone in range set them off and the blast skipped her. They count only allies a ground enemy does not evade (evadesGround). --- server/sim/content/bosses.js | 4 +++- server/sim/content/enemies.js | 3 ++- test/sim/feedback1f-liftoff.test.js | 29 +++++++++++++++++++++++++++++ 3 files changed, 34 insertions(+), 2 deletions(-) diff --git a/server/sim/content/bosses.js b/server/sim/content/bosses.js index 5c0f7f12..03cf8a11 100644 --- a/server/sim/content/bosses.js +++ b/server/sim/content/bosses.js @@ -922,7 +922,9 @@ function lucienCore(ab, e) { dealt(c, b, e2) { elem(b, e2, c.target, 'neural', e2.s.atk * epr); }, }, s && { - cd: s.cd, icd: s.icd, cond: (b) => b.alliesInRadius(e.x, e.y, LUCIEN_AOE_RADIUS).length > 0, + // cast only with someone the blast can hurt: an airborne (起飞) operator evades a ground leader (对地规避 — the + // blast skips her in damage.js; counting her spent the skill on nobody) + cd: s.cd, icd: s.icd, cond: (b) => b.alliesInRadius(e.x, e.y, LUCIEN_AOE_RADIUS).some((u) => !evadesGround(e, u)), fire(b, e2) { b.fx('explode', { x: e2.x, y: e2.y, r: LUCIEN_AOE_RADIUS, kind: 'crimsonAoe' }); for (const u of b.alliesInRadius(e2.x, e2.y, LUCIEN_AOE_RADIUS)) { hurt(b, e2, u, e2.s.atk * (s.bb.atk_scale ?? 0), 'phys'); elem(b, e2, u, 'neural', e2.s.atk * (s.bb.ep_damage_ratio ?? 0)); } diff --git a/server/sim/content/enemies.js b/server/sim/content/enemies.js index c83613ac..9fd75b22 100644 --- a/server/sim/content/enemies.js +++ b/server/sim/content/enemies.js @@ -2168,7 +2168,8 @@ function kitMace(ab) { b.fx('explode', { x: e.x, y: e.y, r, kind: 'blizzard', id: e.id }); for (const u of b.alliesInRadius(e.x, e.y, r)) hurt(b, e, u, e.s.atk * (s.bb.atk_scale ?? 1), 'phys'); }; - const inR = (b, e, s) => b.alliesInRadius(e.x, e.y, (s && s.bb.range_radius) || 0).length > 0; + // someone the circle can hurt: an airborne (起飞) operator evades a ground enemy (对地规避; damage.js skips her) + const inR = (b, e, s) => b.alliesInRadius(e.x, e.y, (s && s.bb.range_radius) || 0).some((u) => !evadesGround(e, u)); return [resist(), { hitOut(c, b, e) { const p = penOn(c, e); if (c.dmg.isAttack && p > 0) c.dmg.defIgnorePct = Math.min(1, (c.dmg.defIgnorePct || 0) + p); }, taken(c, b, e) { // 【瞬息杀机】 every hp_ratio of max HP lost (second form) diff --git a/test/sim/feedback1f-liftoff.test.js b/test/sim/feedback1f-liftoff.test.js index c93b0912..8d01c931 100644 --- a/test/sim/feedback1f-liftoff.test.js +++ b/test/sim/feedback1f-liftoff.test.js @@ -425,3 +425,32 @@ test('F3 review 2: a debuff a ground enemy put on 蒂比 before she took off kee done(h); } }); + +// QA after the integration: two ground enemies decided to cast an area skill by counting every ally in its radius — +// 卢西恩,“猩红血钻”'s 【aoe】 (radius 2) and 锏's CircleAttack (first form, radius 1.5) — so an airborne 蒂比 alone in +// range set it off and the blast, which skips her (对地规避), was spent on nobody (6 casts in 60 s, 0 hits). They count +// only the allies the blast can hurt now. The `liftoff` flag on 角峰 stands for 蒂比's 起飞 (targeting.js evadesGround +// reads the flag), so no skill of hers takes off or lands in the middle of the count. +test('F3 QA: 卢西恩\'s 【aoe】 and 锏\'s CircleAttack are not cast for an airborne operator alone in range; they are once it lands', REAL, () => { + for (const [key, kind, radius] of [['enemy_2016_csphtm', 'crimsonAoe', 2], ['enemy_1525_blkswb', 'blizzard', 1.5]]) { + const h = makeBattle({ + stageId: 'flat', seed: 3, autoFinish: false, timeLimit: 400, + units: [{ chessId: BAIT, row: 10, col: 5 }], + enemies: [{ key, pos: [10, 6], mods: { hpMul: 1e3, speedMul: 0, atkMul: 0.01 } }], + }); + h.step(); + const u = h.unit(BAIT); + const e = h.b.enemies.find((x) => x.alive && x.defId === key); + assert.ok(e && Math.hypot(e.x - u.x, e.y - u.y) <= radius, `${key}: 角峰 inside the radius`); + h.b.addBuff(u, { key: 'test:liftoff', persist: true, flags: { liftoff: true, blockFly: true } }); + let casts = 0; + const fx0 = h.b.fx.bind(h.b); + h.b.fx = (k, p) => { if (k === 'explode' && p && p.kind === kind) casts++; return fx0(k, p); }; + h.run(40); + assert.equal(casts, 0, `${key}: no ${kind} while the only ally in range is airborne`); + h.b.removeBuff(u, 'test:liftoff'); + h.run(2); + assert.equal(casts, 1, `${key}: cast at once when a target it can hurt is there (the skill was ready all along)`); + done(h); + } +});