Files
OpenFrontIO/tests/Warship.test.ts
T
evanpelle d35d0f38cb refactor & update warships (#796)
## Description:
1. Refactor WarshipExecution so that it takes either attrs or a warship
unit. This makes testing much simpler as the unit test can construct a
warship and then pass it into a warship execution
2. Have MoveWarshipExecution set the patrol tile, not the move tile so
warships stay in new location instead of moving back to original
location.
3. Warships no longer target trade ships outside of its patrol range.
this prevents warships from wandering
4. Refactored & simplified WarshipExecution
5. Added more tests for warships
6. Move health modification from PlayerExecution to WarshipExecution
since Warships are the only unit that have health
7. Move fields from WarshipExecution to the Warship unit itself, this
allows other executions & components to see more data about the warship.



## Please complete the following:

- [x] I have added screenshots for all UI updates
- [x] I confirm I have thoroughly tested these changes and take full
responsibility for any bugs introduced
- [x] I understand that submitting code with bugs that could have been
caught through manual testing blocks releases and new features for all
contributors

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

<DISCORD USERNAME>
2025-05-24 13:42:56 -07:00

215 lines
5.5 KiB
TypeScript

import { MoveWarshipExecution } from "../src/core/execution/MoveWarshipExecution";
import { WarshipExecution } from "../src/core/execution/WarshipExecution";
import {
Game,
Player,
PlayerInfo,
PlayerType,
UnitType,
} from "../src/core/game/Game";
import { setup } from "./util/Setup";
import { executeTicks } from "./util/utils";
const coastX = 7;
let game: Game;
let player1: Player;
let player2: Player;
describe("Warship", () => {
beforeEach(async () => {
game = await setup(
"half_land_half_ocean",
{
infiniteGold: true,
instantBuild: true,
},
[
new PlayerInfo(
"us",
"boat dude",
PlayerType.Human,
null,
"player_1_id",
),
new PlayerInfo(
"us",
"boat dude",
PlayerType.Human,
null,
"player_2_id",
),
],
);
while (game.inSpawnPhase()) {
game.executeNextTick();
}
player1 = game.player("player_1_id");
player2 = game.player("player_2_id");
});
test("Warship heals only if player has port", async () => {
const maxHealth = game.config().unitInfo(UnitType.Warship).maxHealth;
if (typeof maxHealth !== "number") {
expect(typeof maxHealth).toBe("number");
throw new Error("unreachable");
}
const port = player1.buildUnit(UnitType.Port, game.ref(coastX, 10), {});
const warship = player1.buildUnit(
UnitType.Warship,
game.ref(coastX + 1, 10),
{
patrolTile: game.ref(coastX + 1, 10),
},
);
game.addExecution(new WarshipExecution(warship));
game.executeNextTick();
expect(warship.health()).toBe(maxHealth);
warship.modifyHealth(-10);
expect(warship.health()).toBe(maxHealth - 10);
game.executeNextTick();
expect(warship.health()).toBe(maxHealth - 9);
port.delete();
game.executeNextTick();
expect(warship.health()).toBe(maxHealth - 9);
});
test("Warship captures trade if player has port", async () => {
const portTile = game.ref(coastX, 10);
player1.buildUnit(UnitType.Port, portTile, {});
game.addExecution(
new WarshipExecution(
player1.buildUnit(UnitType.Warship, portTile, {
patrolTile: portTile,
}),
),
);
const tradeShip = player2.buildUnit(
UnitType.TradeShip,
game.ref(coastX + 1, 7),
{
targetUnit: player2.buildUnit(UnitType.Port, game.ref(coastX, 10), {}),
},
);
expect(tradeShip.owner().id()).toBe(player2.id());
// Let plenty of time for A* to execute
for (let i = 0; i < 10; i++) {
game.executeNextTick();
}
expect(tradeShip.owner()).toBe(player1);
});
test("Warship do not capture trade if player has no port", async () => {
game.addExecution(
new WarshipExecution(
player1.buildUnit(UnitType.Warship, game.ref(coastX + 1, 11), {
patrolTile: game.ref(coastX + 1, 11),
}),
),
);
const tradeShip = player2.buildUnit(
UnitType.TradeShip,
game.ref(coastX + 1, 11),
{
targetUnit: player1.buildUnit(UnitType.Port, game.ref(coastX, 11), {}),
},
);
expect(tradeShip.owner().id()).toBe(player2.id());
// Let plenty of time for warship to potentially capture trade ship
for (let i = 0; i < 10; i++) {
game.executeNextTick();
}
expect(tradeShip.owner().id()).toBe(player2.id());
});
test("Warship does not target trade ships that are safe from pirates", async () => {
// build port so warship can target trade ships
player1.buildUnit(UnitType.Port, game.ref(coastX, 10), {});
const warship = player1.buildUnit(
UnitType.Warship,
game.ref(coastX + 1, 10),
{
patrolTile: game.ref(coastX + 1, 10),
},
);
game.addExecution(new WarshipExecution(warship));
const tradeShip = player2.buildUnit(
UnitType.TradeShip,
game.ref(coastX + 1, 10),
{
targetUnit: player2.buildUnit(UnitType.Port, game.ref(coastX, 10), {}),
},
);
tradeShip.setSafeFromPirates();
executeTicks(game, 10);
expect(tradeShip.owner().id()).toBe(player2.id());
});
test("Warship moves to new patrol tile", async () => {
game.config().warshipTargettingRange = () => 1;
const warship = player1.buildUnit(
UnitType.Warship,
game.ref(coastX + 1, 10),
{
patrolTile: game.ref(coastX + 1, 10),
},
);
game.addExecution(new WarshipExecution(warship));
game.addExecution(
new MoveWarshipExecution(warship.id(), game.ref(coastX + 5, 15)),
);
executeTicks(game, 10);
expect(warship.patrolTile()).toBe(game.ref(coastX + 5, 15));
});
test("Warship does not not target trade ships outside of patrol range", async () => {
game.config().warshipTargettingRange = () => 3;
// build port so warship can target trade ships
player1.buildUnit(UnitType.Port, game.ref(coastX, 10), {});
const warship = player1.buildUnit(
UnitType.Warship,
game.ref(coastX + 1, 10),
{
patrolTile: game.ref(coastX + 1, 10),
},
);
game.addExecution(new WarshipExecution(warship));
const tradeShip = player2.buildUnit(
UnitType.TradeShip,
game.ref(coastX + 1, 15),
{
targetUnit: player2.buildUnit(UnitType.Port, game.ref(coastX, 10), {}),
},
);
executeTicks(game, 10);
// Trade ship should not be captured
expect(tradeShip.owner().id()).toBe(player2.id());
});
});