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); + } +});