2661 PR 1/3 Warship Retreat Core, Blue UI Signal, and Transport-First Target Priority (#3498)

Part of #2661 (split into 3 PRs so they are not too large..)

## Description:

Part 1/3 of #2661.

This PR adds warship retreat basics, a blue retreating UI state, and
updates target priority.

Added:
- Retreat state handling
- Blue visual for retreating warships
- Target priority: transport > warship > trade 
- Tests for retreat and target priority

Example video:
https://youtu.be/2hE2qeOeY48
Ship retreating:
<img width="630" height="488" alt="image"
src="https://github.com/user-attachments/assets/56d3e6d5-08af-453d-afe5-ee21dd6f3414"
/>
Ship healing:
<img width="483" height="311" alt="image"
src="https://github.com/user-attachments/assets/aeaf2239-bb81-444f-84ef-62dbcb48fddf"
/>
Back to being deployed:
<img width="585" height="358" alt="image"
src="https://github.com/user-attachments/assets/875828a2-8a24-4593-ac76-26426bb81057"
/>

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

zixer._
This commit is contained in:
Zixer1
2026-04-24 08:26:14 -06:00
committed by GitHub
parent 66bbbc664b
commit 37079e6a05
8 changed files with 250 additions and 16 deletions
+69
View File
@@ -205,6 +205,37 @@ describe("Warship", () => {
expect(tradeShip.owner().id()).toBe(player2.id());
});
test("Warship prioritizes transport ships over warships", async () => {
game.config().warshipShellAttackRate = () => Number.MAX_SAFE_INTEGER;
const warship = player1.buildUnit(
UnitType.Warship,
game.ref(coastX + 1, 10),
{
patrolTile: game.ref(coastX + 1, 10),
},
);
player2.buildUnit(UnitType.Warship, game.ref(coastX + 2, 10), {
patrolTile: game.ref(coastX + 2, 10),
});
player2.buildUnit(UnitType.TransportShip, game.ref(coastX + 1, 11), {
targetTile: game.ref(coastX + 1, 11),
});
game.addExecution(new WarshipExecution(warship));
let selectedType: UnitType | undefined = undefined;
for (let i = 0; i < 5; i++) {
game.executeNextTick();
selectedType = warship.targetUnit()?.type();
if (selectedType === UnitType.TransportShip) {
break;
}
}
expect(selectedType).toBe(UnitType.TransportShip);
});
test("Warship does not target trade ships in different water components", async () => {
// build port so warship can target trade ships
player1.buildUnit(UnitType.Port, game.ref(coastX, 10), {});
@@ -284,4 +315,42 @@ describe("Warship", () => {
expect(exec.isActive()).toBe(false);
});
test("Warship retreats when pre-heal health is below threshold", async () => {
const maxHealth = game.config().unitInfo(UnitType.Warship).maxHealth;
if (typeof maxHealth !== "number") {
expect(typeof maxHealth).toBe("number");
throw new Error("unreachable");
}
if (maxHealth <= 599) {
expect(maxHealth).toBeGreaterThan(599);
throw new Error("unreachable");
}
game.config().warshipRetreatHealthThreshold = () => 600;
const homePort = player1.buildUnit(UnitType.Port, game.ref(coastX, 10), {});
const warship = player1.buildUnit(
UnitType.Warship,
game.ref(coastX + 1, 11),
{
patrolTile: game.ref(coastX + 1, 11),
},
);
game.addExecution(new WarshipExecution(warship));
game.executeNextTick();
warship.modifyHealth(-(maxHealth - 599));
game.executeNextTick();
expect(warship.retreating()).toBe(true);
const distanceToPort = game.euclideanDistSquared(
warship.tile(),
homePort.tile(),
);
expect(
distanceToPort <= 25 || warship.targetTile() === homePort.tile(),
).toBe(true);
});
});