mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-21 07:50:45 +00:00
f1d162825e
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
192 lines
6.5 KiB
TypeScript
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);
|
|
});
|
|
});
|