Files
OpenFrontIO/tests/nukes/HydrogenAndMirv.test.ts
Mykola 6112547273 Improve random spawn (#2503)
## Description:

This is a previously approved PR with an additional commit that fixes
case when nations change spawn & jump around, their previous territory
wasn't getting deleted.

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

nikolaj_mykola

---------

Co-authored-by: Evan <evanpelle@gmail.com>
2025-12-20 13:35:30 -08:00

200 lines
7.1 KiB
TypeScript

import { ConstructionExecution } from "../../src/core/execution/ConstructionExecution";
import { SpawnExecution } from "../../src/core/execution/SpawnExecution";
import {
Game,
Player,
PlayerInfo,
PlayerType,
UnitType,
} from "../../src/core/game/Game";
import { GameID } from "../../src/core/Schemas";
import { setup } from "../util/Setup";
describe("Hydrogen Bomb and MIRV flows", () => {
let game: Game;
let player: Player;
const gameID: GameID = "game_id";
beforeEach(async () => {
game = await setup("plains", { infiniteGold: true, instantBuild: true });
const info = new PlayerInfo("p", PlayerType.Human, null, "p");
game.addPlayer(info);
game.addExecution(new SpawnExecution(gameID, info, game.ref(1, 1)));
while (game.inSpawnPhase()) game.executeNextTick();
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,
});
const info = new PlayerInfo("p", PlayerType.Human, null, "p");
gameWithConstruction.addPlayer(info);
gameWithConstruction.addExecution(
new SpawnExecution(gameID, info, gameWithConstruction.ref(1, 1)),
);
while (gameWithConstruction.inSpawnPhase())
gameWithConstruction.executeNextTick();
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);
});
});