diff --git a/src/core/execution/FakeHumanExecution.ts b/src/core/execution/FakeHumanExecution.ts index b43114062..4051339fc 100644 --- a/src/core/execution/FakeHumanExecution.ts +++ b/src/core/execution/FakeHumanExecution.ts @@ -1,5 +1,6 @@ import { Cell, + Difficulty, Execution, Game, Gold, @@ -46,6 +47,11 @@ export class FakeHumanExecution implements Execution { private readonly lastMIRVSent: [Tick, TileRef][] = []; private readonly embargoMalusApplied = new Set(); + // Track our transport ships we currently own + private trackedTransportShips: Set = new Set(); + // Track our trade ships we currently own + private trackedTradeShips: Set = new Set(); + /** MIRV Strategy Constants */ /** Ticks until MIRV can be attempted again */ @@ -133,6 +139,16 @@ export class FakeHumanExecution implements Execution { } tick(ticks: number) { + // Ship tracking + if ( + this.player !== null && + this.player.isAlive() && + this.mg.config().gameConfig().difficulty !== Difficulty.Easy + ) { + this.trackTransportShipsAndRetaliate(); + this.trackTradeShipsAndRetaliate(); + } + if (ticks % this.attackRate !== this.attackTick) { return; } @@ -901,6 +917,70 @@ export class FakeHumanExecution implements Execution { } } + // Send out a warship if our transport ship got captured + private trackTransportShipsAndRetaliate(): void { + if (this.player === null) return; + + // Add any currently owned transport ships to our tracking set + this.player + .units(UnitType.TransportShip) + .forEach((u) => this.trackedTransportShips.add(u)); + + // Iterate tracked transport ships; if it got destroyed by an enemy: retaliate + for (const ship of Array.from(this.trackedTransportShips)) { + if (!ship.isActive()) { + // Distinguish between arrival/retreat and enemy destruction + if (ship.wasDestroyedByEnemy()) { + this.maybeRetaliateWithWarship(ship.tile()); + } + this.trackedTransportShips.delete(ship); + } + } + } + + // Send out a warship if our trade ship got captured + private trackTradeShipsAndRetaliate(): void { + if (this.player === null) return; + + // Add any currently owned trade ships to our tracking map + this.player + .units(UnitType.TradeShip) + .forEach((u) => this.trackedTradeShips.add(u)); + + // Iterate tracked trade ships; if we no longer own it, it was captured: retaliate + for (const ship of Array.from(this.trackedTradeShips)) { + if (!ship.isActive()) { + this.trackedTradeShips.delete(ship); + continue; + } + if (ship.owner().id() !== this.player.id()) { + // Ship was ours and is now owned by someone else -> captured + this.maybeRetaliateWithWarship(ship.tile()); + this.trackedTradeShips.delete(ship); + } + } + } + + private maybeRetaliateWithWarship(tile: TileRef): void { + if (this.player === null) return; + + const { difficulty } = this.mg.config().gameConfig(); + // In Easy never retaliate. In Medium retaliate with 15% chance. Hard with 50%, Impossible with 80%. + if ( + (difficulty === Difficulty.Medium && this.random.nextInt(0, 100) < 15) || + (difficulty === Difficulty.Hard && this.random.nextInt(0, 100) < 50) || + (difficulty === Difficulty.Impossible && this.random.nextInt(0, 100) < 80) + ) { + const canBuild = this.player.canBuild(UnitType.Warship, tile); + if (canBuild === false) { + return; + } + this.mg.addExecution( + new ConstructionExecution(this.player, UnitType.Warship, tile), + ); + } + } + isActive(): boolean { return this.active; } diff --git a/src/core/game/Game.ts b/src/core/game/Game.ts index 8ce4822b3..3203b29a9 100644 --- a/src/core/game/Game.ts +++ b/src/core/game/Game.ts @@ -451,6 +451,7 @@ export interface Unit { toUpdate(): UnitUpdate; hasTrainStation(): boolean; setTrainStation(trainStation: boolean): void; + wasDestroyedByEnemy(): boolean; // Train trainType(): TrainType | undefined; diff --git a/src/core/game/UnitImpl.ts b/src/core/game/UnitImpl.ts index 917d0af67..c4f3c00e0 100644 --- a/src/core/game/UnitImpl.ts +++ b/src/core/game/UnitImpl.ts @@ -24,6 +24,7 @@ export class UnitImpl implements Unit { private _retreating: boolean = false; private _targetedBySAM = false; private _reachedTarget = false; + private _wasDestroyedByEnemy: boolean = false; private _lastSetSafeFromPirates: number; // Only for trade ships private _constructionType: UnitType | undefined; private _lastOwner: PlayerImpl | null = null; @@ -252,6 +253,10 @@ export class UnitImpl implements Unit { if (!this.isActive()) { throw new Error(`cannot delete ${this} not active`); } + + // Record whether this unit was destroyed by an enemy (vs. arrived / retreated) + this._wasDestroyedByEnemy = destroyer !== undefined; + this._owner._units = this._owner._units.filter((b) => b !== this); this._active = false; this.mg.addUpdate(this.toUpdate()); @@ -291,6 +296,10 @@ export class UnitImpl implements Unit { return this._active; } + wasDestroyedByEnemy(): boolean { + return this._wasDestroyedByEnemy; + } + retreating(): boolean { return this._retreating; }