From ad42dc0ee866b6bdeee5efadf397675e14c57295 Mon Sep 17 00:00:00 2001 From: Vivacious Box Date: Fri, 27 Jun 2025 01:47:16 +0200 Subject: [PATCH] Fix sam targetting everything (#1280) ## Description: There was a regression on how sam targets nukes. This fixes it ## Please complete the following: - [x] I have added screenshots for all UI updates - [x] I process any text displayed to the user through translateText() and I've added it to the en.json file - [x] I have added relevant tests to the test directory - [x] I confirm I have thoroughly tested these changes and take full responsibility for any bugs introduced - [x] I understand that submitting code with bugs that could have been caught through manual testing blocks releases and new features for all contributors ## Please put your Discord username so you can be contacted if a bug or regression is found: Vivacious Box --- src/core/configuration/Config.ts | 2 ++ src/core/configuration/DefaultConfig.ts | 8 +++++ src/core/execution/NukeExecution.ts | 6 ++-- src/core/execution/SAMLauncherExecution.ts | 4 +-- src/core/game/GameImpl.ts | 8 ++++- tests/core/executions/NukeExecution.test.ts | 10 +++--- .../executions/SAMLauncherExecution.test.ts} | 34 +++++++++++++------ tests/util/TestConfig.ts | 8 +++++ 8 files changed, 58 insertions(+), 22 deletions(-) rename tests/{SAM.test.ts => core/executions/SAMLauncherExecution.test.ts} (86%) diff --git a/src/core/configuration/Config.ts b/src/core/configuration/Config.ts index 9b9e63b9b..e2c953bc5 100644 --- a/src/core/configuration/Config.ts +++ b/src/core/configuration/Config.ts @@ -153,6 +153,8 @@ export interface Config { traitorDuration(): number; nukeMagnitudes(unitType: UnitType): NukeMagnitude; defaultNukeSpeed(): number; + defaultNukeTargetableRange(): number; + defaultSamRange(): number; nukeDeathFactor(humans: number, tilesOwned: number): number; structureMinDist(): number; isReplay(): boolean; diff --git a/src/core/configuration/DefaultConfig.ts b/src/core/configuration/DefaultConfig.ts index aff94aacc..2e327b14c 100644 --- a/src/core/configuration/DefaultConfig.ts +++ b/src/core/configuration/DefaultConfig.ts @@ -790,6 +790,14 @@ export class DefaultConfig implements Config { return 6; } + defaultNukeTargetableRange(): number { + return 120; + } + + defaultSamRange(): number { + return 80; + } + // Humans can be population, soldiers attacking, soldiers in boat etc. nukeDeathFactor(humans: number, tilesOwned: number): number { return (5 * humans) / Math.max(1, tilesOwned); diff --git a/src/core/execution/NukeExecution.ts b/src/core/execution/NukeExecution.ts index deefc9936..651fbb1f3 100644 --- a/src/core/execution/NukeExecution.ts +++ b/src/core/execution/NukeExecution.ts @@ -13,8 +13,6 @@ import { ParabolaPathFinder } from "../pathfinding/PathFinding"; import { PseudoRandom } from "../PseudoRandom"; import { NukeType } from "../StatsSchemas"; -const NUKE_TARGETABLE_RADIUS = 120; - const SPRITE_RADIUS = 16; export class NukeExecution implements Execution { @@ -179,7 +177,9 @@ export class NukeExecution implements Execution { if (this.nuke === null || this.nuke.targetTile() === undefined) { return; } - const targetRangeSquared = NUKE_TARGETABLE_RADIUS * NUKE_TARGETABLE_RADIUS; + const targetRangeSquared = + this.mg.config().defaultNukeTargetableRange() * + this.mg.config().defaultNukeTargetableRange(); const targetTile = this.nuke.targetTile(); this.nuke.setTargetable( this.mg.euclideanDistSquared(this.nuke.tile(), targetTile!) < diff --git a/src/core/execution/SAMLauncherExecution.ts b/src/core/execution/SAMLauncherExecution.ts index 6ba46f1b8..538e759ee 100644 --- a/src/core/execution/SAMLauncherExecution.ts +++ b/src/core/execution/SAMLauncherExecution.ts @@ -14,8 +14,6 @@ export class SAMLauncherExecution implements Execution { private mg: Game; private active: boolean = true; - private searchRangeRadius = 80; - private targetRangeRadius = 120; // Nuke's target should be in this range to be focusable // As MIRV go very fast we have to detect them very early but we only // shoot the one targeting very close (MIRVWarheadProtectionRadius) private MIRVWarheadSearchRadius = 400; @@ -41,7 +39,7 @@ export class SAMLauncherExecution implements Execution { if (this.sam === null) return null; const nukes = this.mg.nearbyUnits( this.sam.tile(), - this.searchRangeRadius, + this.mg.config().defaultSamRange(), [UnitType.AtomBomb, UnitType.HydrogenBomb], ({ unit }) => unit.owner() !== this.player && diff --git a/src/core/game/GameImpl.ts b/src/core/game/GameImpl.ts index 19e89a2d8..e8cef0a77 100644 --- a/src/core/game/GameImpl.ts +++ b/src/core/game/GameImpl.ts @@ -684,8 +684,14 @@ export class GameImpl implements Game { tile: TileRef, searchRange: number, types: UnitType | UnitType[], + predicate?: (value: { unit: Unit; distSquared: number }) => boolean, ): Array<{ unit: Unit; distSquared: number }> { - return this.unitGrid.nearbyUnits(tile, searchRange, types) as Array<{ + return this.unitGrid.nearbyUnits( + tile, + searchRange, + types, + predicate, + ) as Array<{ unit: Unit; distSquared: number; }>; diff --git a/tests/core/executions/NukeExecution.test.ts b/tests/core/executions/NukeExecution.test.ts index cc19f7bc1..435d388b4 100644 --- a/tests/core/executions/NukeExecution.test.ts +++ b/tests/core/executions/NukeExecution.test.ts @@ -82,18 +82,18 @@ describe("NukeExecution", () => { game.ref(1, 1), ); game.addExecution(nukeExec); - // targetable distance is 14400 + // targetable distance is 400 - //near launch should be targetable (distance src < 14400) + //near launch should be targetable (distance src < 400) executeTicks(game, 2); expect(nukeExec.getNuke()!.isTargetable()).toBeTruthy(); - //mid air should not be targetable (distance src > 14400, distance target > 14400) + //mid air should not be targetable (distance src > 400, distance target > 400) executeTicks(game, 38); expect(nukeExec.getNuke()!.isTargetable()).toBeFalsy(); - //near target should be targetable (distance target < 14400) - executeTicks(game, 10); + //near target should be targetable (distance target < 400) + executeTicks(game, 35); expect(nukeExec.getNuke()!.isTargetable()).toBeTruthy(); }); }); diff --git a/tests/SAM.test.ts b/tests/core/executions/SAMLauncherExecution.test.ts similarity index 86% rename from tests/SAM.test.ts rename to tests/core/executions/SAMLauncherExecution.test.ts index 912befb06..7ceb08e1a 100644 --- a/tests/SAM.test.ts +++ b/tests/core/executions/SAMLauncherExecution.test.ts @@ -1,21 +1,22 @@ -import { NukeExecution } from "../src/core/execution/NukeExecution"; -import { SAMLauncherExecution } from "../src/core/execution/SAMLauncherExecution"; -import { SpawnExecution } from "../src/core/execution/SpawnExecution"; -import { UpgradeStructureExecution } from "../src/core/execution/UpgradeStructureExecution"; +import { NukeExecution } from "../../../src/core/execution/NukeExecution"; +import { SAMLauncherExecution } from "../../../src/core/execution/SAMLauncherExecution"; +import { SpawnExecution } from "../../../src/core/execution/SpawnExecution"; +import { UpgradeStructureExecution } from "../../../src/core/execution/UpgradeStructureExecution"; import { Game, Player, PlayerInfo, PlayerType, UnitType, -} from "../src/core/game/Game"; -import { setup } from "./util/Setup"; -import { constructionExecution, executeTicks } from "./util/utils"; +} from "../../../src/core/game/Game"; +import { setup } from "../../util/Setup"; +import { constructionExecution, executeTicks } from "../../util/utils"; let game: Game; let attacker: Player; let defender: Player; let far_defender: Player; +let middle_defender: Player; describe("SAM", () => { beforeEach(async () => { @@ -31,6 +32,14 @@ describe("SAM", () => { null, "defender_id", ); + const middle_defender_info = new PlayerInfo( + undefined, + "us", + "middle_defender_id", + PlayerType.Human, + null, + "middle_defender_id", + ); const far_defender_info = new PlayerInfo( undefined, "us", @@ -48,11 +57,16 @@ describe("SAM", () => { "attacker_id", ); game.addPlayer(defender_info); + game.addPlayer(middle_defender_info); game.addPlayer(far_defender_info); game.addPlayer(attacker_info); game.addExecution( new SpawnExecution(game.player(defender_info.id).info(), game.ref(1, 1)), + new SpawnExecution( + game.player(middle_defender_info.id).info(), + game.ref(50, 1), + ), new SpawnExecution( game.player(far_defender_info.id).info(), game.ref(199, 1), @@ -66,6 +80,7 @@ describe("SAM", () => { attacker = game.player("attacker_id"); defender = game.player("defender_id"); + middle_defender = game.player("middle_defender_id"); far_defender = game.player("far_defender_id"); constructionExecution(game, attacker, 7, 7, UnitType.MissileSilo); @@ -165,9 +180,9 @@ describe("SAM", () => { test("SAMs should target only nukes aimed at nearby targets if not close to launch site", async () => { const targetDistance = 199; // Middle SAM: should not intercept the nuke - const sam1 = defender.buildUnit( + const sam1 = middle_defender.buildUnit( UnitType.SAMLauncher, - game.ref(targetDistance / 2, 1), + game.ref(50, 1), {}, ); game.addExecution(new SAMLauncherExecution(defender, null, sam1)); @@ -192,7 +207,6 @@ describe("SAM", () => { targetDistance / game.config().defaultNukeSpeed(), ); executeTicks(game, ticksToExecute); - expect(nukeExecution.isActive()).toBeFalsy(); expect(sam1.isInCooldown()).toBeFalsy(); expect(sam2.isInCooldown()).toBeTruthy(); diff --git a/tests/util/TestConfig.ts b/tests/util/TestConfig.ts index 40427a90e..f0b10ac37 100644 --- a/tests/util/TestConfig.ts +++ b/tests/util/TestConfig.ts @@ -42,6 +42,14 @@ export class TestConfig extends DefaultConfig { return this._defaultNukeSpeed; } + defaultNukeTargetableRange(): number { + return 20; + } + + defaultSamRange(): number { + return 20; + } + spawnImmunityDuration(): Tick { return 0; }