mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-21 15:20:43 +00:00
6112547273
## 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>
200 lines
7.1 KiB
TypeScript
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);
|
|
});
|
|
});
|