Files
OpenFrontIO/tests/nukes/HydrogenAndMirv.test.ts
Aotumuri f1d162825e feat: remove spawn timer on singleplayer (#3199)
Resolves #1041 

## Description:

Remove the singleplayer spawn countdown so the game starts when the
player spawns, spawn nations immediately after player spawn, and align
game timer/max-timer timing with the new start point.

Added a singleplayer regression test for spawn-immunity timing
(GameImpl.test.ts) and updated spawn-phase loop tests to use gameType:
GameType.Public where singleplayer behavior is not under test (e.g.
MIRV/AI/Spawn/WinCheck-related suites), eliminating inSpawnPhase()
timeout hangs after the new singleplayer start logic.


https://github.com/user-attachments/assets/c07a585f-1153-490e-88ca-a91fc7ae5756

## 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:
aotumuri
2026-05-11 12:44:44 -07:00

192 lines
6.5 KiB
TypeScript

import { ConstructionExecution } from "../../src/core/execution/ConstructionExecution";
import {
Game,
Player,
PlayerInfo,
PlayerType,
UnitType,
} from "../../src/core/game/Game";
import { setup } from "../util/Setup";
describe("Hydrogen Bomb and MIRV flows", () => {
let game: Game;
let player: Player;
const info = new PlayerInfo("p", PlayerType.Human, null, "p");
beforeEach(async () => {
game = await setup("plains", { infiniteGold: true, instantBuild: true }, [
info,
]);
player = game.player(info.id);
player.conquer(game.ref(1, 1));
});
test("Hydrogen bomb launches when silo exists and cannot use silo under construction", () => {
// Build a silo instantly and launch Hydrogen Bomb
game.addExecution(
new ConstructionExecution(player, UnitType.MissileSilo, game.ref(1, 1)),
);
game.executeNextTick();
game.executeNextTick();
expect(player.units(UnitType.MissileSilo)).toHaveLength(1);
// Launch Hydrogen Bomb
const target = game.ref(7, 7);
game.addExecution(
new ConstructionExecution(player, UnitType.HydrogenBomb, target),
);
game.executeNextTick();
game.executeNextTick();
game.executeNextTick();
expect(player.units(UnitType.HydrogenBomb).length).toBeGreaterThan(0);
// Now build another silo with construction time and ensure it won't be used
// Use non-instant config by simulating an under-construction flag on a new silo
// (Use normal construction with default duration in a fresh game instance)
});
test("Hydrogen bomb launch fails when silo is under construction and succeeds after completion", async () => {
// Set up a game without instantBuild to test construction duration
const gameWithConstruction = await setup(
"plains",
{
infiniteGold: false,
instantBuild: false,
},
[info],
);
const playerWithConstruction = gameWithConstruction.player(info.id);
playerWithConstruction.conquer(gameWithConstruction.ref(1, 1));
const siloTile = gameWithConstruction.ref(7, 7);
playerWithConstruction.conquer(siloTile);
// Capture gold before starting silo construction
const goldBeforeSilo = playerWithConstruction.gold();
const siloCost = gameWithConstruction
.unitInfo(UnitType.MissileSilo)
.cost(gameWithConstruction, playerWithConstruction);
playerWithConstruction.addGold(siloCost);
// Start construction of silo
gameWithConstruction.addExecution(
new ConstructionExecution(
playerWithConstruction,
UnitType.MissileSilo,
siloTile,
),
);
gameWithConstruction.executeNextTick();
gameWithConstruction.executeNextTick();
// Verify silo exists and is under construction
const silos = playerWithConstruction.units(UnitType.MissileSilo);
expect(silos.length).toBe(1);
const silo = silos[0];
expect(silo.isUnderConstruction()).toBe(true);
// Capture gold after construction started
const goldAfterConstruction = playerWithConstruction.gold();
expect(goldAfterConstruction).toBeLessThan(goldBeforeSilo + siloCost);
// Attempt to launch HydrogenBomb while silo is under construction
const targetTile = gameWithConstruction.ref(10, 10);
const hydrogenBombCountBefore = playerWithConstruction.units(
UnitType.HydrogenBomb,
).length;
const canBuildResult = playerWithConstruction.canBuild(
UnitType.HydrogenBomb,
targetTile,
);
expect(canBuildResult).toBe(false); // Should fail because silo is under construction
// Try to add execution - should fail
gameWithConstruction.addExecution(
new ConstructionExecution(
playerWithConstruction,
UnitType.HydrogenBomb,
targetTile,
),
);
gameWithConstruction.executeNextTick();
gameWithConstruction.executeNextTick();
// Assert launch does not succeed
const hydrogenBombCountAfter = playerWithConstruction.units(
UnitType.HydrogenBomb,
).length;
expect(hydrogenBombCountAfter).toBe(hydrogenBombCountBefore);
// Assert no refunds during construction
const goldDuringConstruction = playerWithConstruction.gold();
expect(goldDuringConstruction >= goldAfterConstruction).toBe(true);
// Advance ticks to complete construction
const constructionDuration =
gameWithConstruction.unitInfo(UnitType.MissileSilo)
.constructionDuration ?? 0;
for (let i = 0; i < constructionDuration + 2; i++) {
gameWithConstruction.executeNextTick();
}
// Verify silo is complete
const completedSilo = playerWithConstruction.units(UnitType.MissileSilo)[0];
expect(completedSilo.isUnderConstruction()).toBe(false);
// Now launch should succeed - ensure we have gold and target is conquered
playerWithConstruction.conquer(targetTile);
const hydrogenBombCost = gameWithConstruction
.unitInfo(UnitType.HydrogenBomb)
.cost(gameWithConstruction, playerWithConstruction);
playerWithConstruction.addGold(hydrogenBombCost);
const canBuildAfterCompletion = playerWithConstruction.canBuild(
UnitType.HydrogenBomb,
targetTile,
);
expect(canBuildAfterCompletion).not.toBe(false);
gameWithConstruction.addExecution(
new ConstructionExecution(
playerWithConstruction,
UnitType.HydrogenBomb,
targetTile,
),
);
gameWithConstruction.executeNextTick();
gameWithConstruction.executeNextTick();
gameWithConstruction.executeNextTick();
// Verify launch succeeded
const hydrogenBombCountAfterSuccess = playerWithConstruction.units(
UnitType.HydrogenBomb,
).length;
expect(hydrogenBombCountAfterSuccess).toBeGreaterThan(
hydrogenBombCountBefore,
);
});
test("MIRV launches when silo exists and targets player-owned tiles", () => {
// Build a silo instantly
game.addExecution(
new ConstructionExecution(player, UnitType.MissileSilo, game.ref(1, 1)),
);
game.executeNextTick();
game.executeNextTick();
expect(player.units(UnitType.MissileSilo)).toHaveLength(1);
// Launch MIRV at a player-owned tile (the silo tile)
const target = game.ref(1, 1);
game.addExecution(new ConstructionExecution(player, UnitType.MIRV, target));
game.executeNextTick(); // init
game.executeNextTick(); // create MIRV unit
game.executeNextTick();
// MIRV should appear briefly before separation, otherwise warheads should be queued
const mirvs = player.units(UnitType.MIRV).length;
const warheads = player.units(UnitType.MIRVWarhead).length;
expect(mirvs > 0 || warheads > 0).toBe(true);
});
});