diff --git a/src/core/execution/NationExecution.ts b/src/core/execution/NationExecution.ts index 0afe26c96..785aa41e9 100644 --- a/src/core/execution/NationExecution.ts +++ b/src/core/execution/NationExecution.ts @@ -93,6 +93,7 @@ export class NationExecution implements Execution { this.player !== null && this.player.isAlive() && this.mg.config().gameConfig().difficulty !== Difficulty.Easy && + this.player.unitsConstructed(UnitType.Port) && !this.mg.config().isUnitDisabled(UnitType.Warship) ) { this.warshipBehavior.trackShipsAndRetaliate(); diff --git a/src/core/execution/nation/NationWarshipBehavior.ts b/src/core/execution/nation/NationWarshipBehavior.ts index 16378ba5d..7b0d23b0c 100644 --- a/src/core/execution/nation/NationWarshipBehavior.ts +++ b/src/core/execution/nation/NationWarshipBehavior.ts @@ -21,6 +21,10 @@ export class NationWarshipBehavior { private trackedTransportShips: Set = new Set(); // Track our trade ships we currently own private trackedTradeShips: Set = new Set(); + // Track incoming transport ships + private trackedIncomingTransportShips: Set = new Set(); + // Track incoming transport ships we have dealt with + private dealtWithTransportShip: Set = new Set(); constructor( private random: PseudoRandom, @@ -45,7 +49,7 @@ export class NationWarshipBehavior { this.player.gold() > this.cost(UnitType.Warship) ) { const port = this.random.randElement(ports); - const targetTile = this.warshipSpawnTile(port.tile()); + const targetTile = this.warshipSpawnTile(port.tile(), 250); if (targetTile === null) { return false; } @@ -61,8 +65,7 @@ export class NationWarshipBehavior { return false; } - private warshipSpawnTile(portTile: TileRef): TileRef | null { - const radius = 250; + private warshipSpawnTile(portTile: TileRef, radius: number): TileRef | null { for (let attempts = 0; attempts < 50; attempts++) { const randX = this.random.nextInt( this.game.x(portTile) - radius, @@ -88,6 +91,7 @@ export class NationWarshipBehavior { trackShipsAndRetaliate(): void { this.trackTransportShipsAndRetaliate(); this.trackTradeShipsAndRetaliate(); + this.trackIncomingTransportsAndRetaliate(); } // Send out a warship if our transport ship got captured @@ -137,6 +141,82 @@ export class NationWarshipBehavior { } } + private trackIncomingTransportsAndRetaliate(): void { + // Add any transports which are targeting us to our tracking map + this.game + .units(UnitType.TransportShip) + .filter((p) => { + const target = p.targetTile(); + return ( + target && + p.isActive() && + !p.retreating() && + this.game.ownerID(target) === this.player?.smallID() && + p.owner().smallID() !== this.player?.smallID() + ); + }) + .forEach((p) => this.trackedIncomingTransportShips.add(p)); + + for (const transport of Array.from(this.trackedIncomingTransportShips)) { + const target = transport.targetTile(); + if ( + !transport.isActive() || + target === undefined || + transport.retreating() + ) { + this.trackedIncomingTransportShips.delete(transport); + this.dealtWithTransportShip.delete(transport); + continue; + } + // Transport has already been dealt with + if (this.dealtWithTransportShip.has(transport)) { + continue; + } + + const distanceToTarget = this.game.manhattanDist( + transport.tile(), + target, + ); + // Too close to deal with + if (distanceToTarget < 20) { + this.dealtWithTransportShip.add(transport); + continue; + } + + // Possible dock snipe counter? Too niche? + if (!transport.owner().isAlliedWith(this.player)) { + if ( + this.game.hasUnitNearby( + target, + 90, + UnitType.Warship, + this.player.id(), + true, + ) || + this.player.units(UnitType.Warship).filter((p) => { + const patrolTile = p.patrolTile(); + return ( + patrolTile !== undefined && + this.game.manhattanDist(target, patrolTile) < 90 + ); + }).length > 0 + ) { + this.dealtWithTransportShip.add(transport); + continue; + } + const oceanTiles = this.warshipSpawnTile(target, 30); + if (oceanTiles === null) continue; + this.maybeRetaliateWithWarship( + oceanTiles, + transport.owner(), + "transport", + ); + this.dealtWithTransportShip.add(transport); + break; + } + } + } + private maybeRetaliateWithWarship( tile: TileRef, enemy: Player, @@ -149,6 +229,7 @@ export class NationWarshipBehavior { // Don't send too many warships if (this.player.units(UnitType.Warship).length >= 10) { + this.maybeMoveWarship(tile); return; } @@ -161,6 +242,7 @@ export class NationWarshipBehavior { ) { const canBuild = this.player.canBuild(UnitType.Warship, tile); if (canBuild === false) { + this.maybeMoveWarship(tile); return; } this.game.addExecution( @@ -171,6 +253,32 @@ export class NationWarshipBehavior { } } + private maybeMoveWarship(tile: TileRef): void { + // Make sure we are targeting water + if (this.game.isWater(tile)) { + const warship = this.player + .units(UnitType.Warship) + .filter((p) => { + const patrolTile = p.patrolTile(); + return ( + patrolTile !== undefined && + // Dont send ships which are already traveling + this.game.manhattanDist(p.tile(), patrolTile) < 130 + ); + }) + .sort((a, b) => { + // Sort by distance (closest first) + const distA = this.game.manhattanDist(a.tile(), tile); + const distB = this.game.manhattanDist(b.tile(), tile); + return distA - distB; + })[0]; + + if (warship) { + warship.setPatrolTile(tile); + } + } + } + // Prevent warship infestations: if current player is one of the 3 richest and an enemy has too many warships, send a counter-warship. // What is a warship infestation? A player tries to dominate the entire ocean to block all trade and transport boats. counterWarshipInfestation(): void { @@ -335,6 +443,7 @@ export class NationWarshipBehavior { target.warship.tile(), ); if (canBuild === false) { + this.maybeMoveWarship(target.warship.tile()); return; }