bugfix: SAM was only reloading when not in cooldown (#3817)

## Description:

reloadMissile() was inside the isInCooldown() block. For level-2+ SAMs,
isInCooldown() returns queue.length === level, so after firing one of
two missiles (queue.length = 1 < level = 2) the SAM is not in cooldown —
meaning expired timers were never cleaned up. Stale queue entries caused
subsequent shots to be treated as still-cooling even after the cooldown
elapsed.

SAM execution now mirrors the missile silo execution

## 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

## Please put your Discord username so you can be contacted if a bug or
regression is found:

evan
This commit is contained in:
Evan
2026-05-02 09:05:10 -06:00
committed by GitHub
parent f5a91b8aa3
commit bcdc2126c6
2 changed files with 31 additions and 14 deletions
+13 -14
View File
@@ -247,20 +247,6 @@ export class SAMLauncherExecution implements Execution {
return;
}
if (this.sam.isInCooldown()) {
const frontTime = this.sam.missileTimerQueue()[0];
if (frontTime === undefined) {
return;
}
const cooldown =
this.mg.config().SAMCooldown() - (this.mg.ticks() - frontTime);
if (cooldown <= 0) {
this.sam.reloadMissile();
}
return;
}
if (!this.sam.isActive()) {
this.active = false;
return;
@@ -270,6 +256,19 @@ export class SAMLauncherExecution implements Execution {
this.player = this.sam.owner();
}
const frontTime = this.sam.missileTimerQueue()[0];
if (frontTime !== undefined) {
const cooldown =
this.mg.config().SAMCooldown() - (this.mg.ticks() - frontTime);
if (cooldown <= 0) {
this.sam.reloadMissile();
}
}
if (this.sam.isInCooldown()) {
return;
}
this.pseudoRandom ??= new PseudoRandom(this.sam.id());
const mirvWarheadTargets = this.mg.nearbyUnits(
@@ -254,4 +254,22 @@ describe("SAM", () => {
expect(defender.units(UnitType.SAMLauncher)[0].level()).toEqual(2);
});
test("SAM should reload expired missile timers even when not in cooldown", async () => {
// Upgrading to level 2 pushes a timer for the new slot. queue.length(1) < level(2)
// so isInCooldown() is false, but the expired timer still needs to be cleaned up.
const sam = defender.buildUnit(UnitType.SAMLauncher, game.ref(1, 1), {});
sam.increaseLevel();
expect(sam.level()).toBe(2);
game.addExecution(new SAMLauncherExecution(defender, null, sam));
expect(sam.missileTimerQueue()).toHaveLength(1);
expect(sam.isInCooldown()).toBeFalsy();
// Wait for the timer to expire — reload must fire even though isInCooldown() is false
executeTicks(game, game.config().SAMCooldown() + 1);
expect(sam.missileTimerQueue()).toHaveLength(0);
});
});