diff --git a/src/core/configuration/Config.ts b/src/core/configuration/Config.ts index aaed33112..64ef95be0 100644 --- a/src/core/configuration/Config.ts +++ b/src/core/configuration/Config.ts @@ -98,7 +98,7 @@ export interface Config { maxPopulation(player: Player | PlayerView): number; cityPopulationIncrease(): number; boatAttackAmount(attacker: Player, defender: Player | TerraNullius): number; - warshipShellLifetime(): number; + shellLifetime(): number; boatMaxNumber(): number; allianceDuration(): Tick; allianceRequestCooldown(): Tick; @@ -111,12 +111,18 @@ export interface Config { unitInfo(type: UnitType): UnitInfo; tradeShipGold(dist: number): Gold; tradeShipSpawnRate(numberOfPorts: number): number; + safeFromPiratesCooldownMax(): number; defensePostRange(): number; SAMCooldown(): number; SiloCooldown(): number; defensePostDefenseBonus(): number; falloutDefenseModifier(percentOfFallout: number): number; difficultyModifier(difficulty: Difficulty): number; + warshipPatrolRange(): number; + warshipShellAttackRate(): number; + warshipTargettingRange(): number; + defensePostShellAttackRate(): number; + defensePostTargettingRange(): number; // 0-1 traitorDefenseDebuff(): number; traitorDuration(): number; diff --git a/src/core/configuration/DefaultConfig.ts b/src/core/configuration/DefaultConfig.ts index 03726a83b..95487a5cd 100644 --- a/src/core/configuration/DefaultConfig.ts +++ b/src/core/configuration/DefaultConfig.ts @@ -396,7 +396,7 @@ export class DefaultConfig implements Config { return 80; } boatMaxNumber(): number { - return 3; + return 9; } numSpawnPhaseTurns(): number { return this._gameConfig.gameType == GameType.Singleplayer ? 100 : 300; @@ -680,4 +680,32 @@ export class DefaultConfig implements Config { structureMinDist(): number { return 18; } + + shellLifetime(): number { + return 50; + } + + warshipPatrolRange(): number { + return 100; + } + + warshipTargettingRange(): number { + return 130; + } + + warshipShellAttackRate(): number { + return 20; + } + + defensePostShellAttackRate(): number { + return 100; + } + + safeFromPiratesCooldownMax(): number { + return 20; + } + + defensePostTargettingRange(): number { + return 75; + } } diff --git a/src/core/execution/DefensePostExecution.ts b/src/core/execution/DefensePostExecution.ts index 0da8ef983..b3524e339 100644 --- a/src/core/execution/DefensePostExecution.ts +++ b/src/core/execution/DefensePostExecution.ts @@ -8,6 +8,7 @@ import { UnitType, } from "../game/Game"; import { TileRef } from "../game/GameMap"; +import { ShellExecution } from "./ShellExecution"; export class DefensePostExecution implements Execution { private player: Player; @@ -15,6 +16,11 @@ export class DefensePostExecution implements Execution { private post: Unit; private active: boolean = true; + private target: Unit = null; + private lastShellAttack = 0; + + private alreadySentShell = new Set(); + constructor( private ownerId: PlayerID, private tile: TileRef, @@ -30,6 +36,27 @@ export class DefensePostExecution implements Execution { this.player = mg.player(this.ownerId); } + private shoot() { + const shellAttackRate = this.mg.config().defensePostShellAttackRate(); + if (this.mg.ticks() - this.lastShellAttack > shellAttackRate) { + this.lastShellAttack = this.mg.ticks(); + this.mg.addExecution( + new ShellExecution( + this.post.tile(), + this.post.owner(), + this.post, + this.target, + ), + ); + if (!this.target.hasHealth()) { + // Don't send multiple shells to target that can be oneshotted + this.alreadySentShell.add(this.target); + this.target = null; + return; + } + } + } + tick(ticks: number): void { if (this.post == null) { const spawnTile = this.player.canBuild(UnitType.DefensePost, this.tile); @@ -48,6 +75,52 @@ export class DefensePostExecution implements Execution { if (this.player != this.post.owner()) { this.player = this.post.owner(); } + + if (this.target != null && !this.target.isActive()) { + this.target = null; + } + + const ships = this.mg + .nearbyUnits( + this.post.tile(), + this.mg.config().defensePostTargettingRange(), + [UnitType.TransportShip, UnitType.Warship], + ) + .filter( + ({ unit }) => + unit.owner() !== this.post.owner() && + !unit.owner().isFriendly(this.post.owner()) && + !this.alreadySentShell.has(unit), + ); + + this.target = + ships.sort((a, b) => { + const { unit: unitA, distSquared: distA } = a; + const { unit: unitB, distSquared: distB } = b; + + // Prioritize TransportShip + if ( + unitA.type() === UnitType.TransportShip && + unitB.type() !== UnitType.TransportShip + ) + return -1; + if ( + unitA.type() !== UnitType.TransportShip && + unitB.type() === UnitType.TransportShip + ) + return 1; + + // If both are the same type, sort by distance (lower `distSquared` means closer) + return distA - distB; + })[0]?.unit ?? null; + + if (this.target == null || !this.target.isActive()) { + this.target = null; + return; + } else { + this.shoot(); + return; + } } isActive(): boolean { diff --git a/src/core/execution/ShellExecution.ts b/src/core/execution/ShellExecution.ts index 286d2bdc9..d44159ed8 100644 --- a/src/core/execution/ShellExecution.ts +++ b/src/core/execution/ShellExecution.ts @@ -42,8 +42,7 @@ export class ShellExecution implements Execution { } if (this.destroyAtTick == -1 && !this.ownerUnit.isActive()) { - this.destroyAtTick = - this.mg.ticks() + this.mg.config().warshipShellLifetime(); + this.destroyAtTick = this.mg.ticks() + this.mg.config().shellLifetime(); } for (let i = 0; i < 3; i++) { @@ -55,7 +54,7 @@ export class ShellExecution implements Execution { switch (result.type) { case PathFindResultType.Completed: this.active = false; - this.target.modifyHealth(-this.shell.info().damage); + this.target.modifyHealth(-this.effectOnTarget()); this.shell.delete(false); return; case PathFindResultType.NextTile: @@ -72,6 +71,11 @@ export class ShellExecution implements Execution { } } + private effectOnTarget(): number { + const baseDamage: number = this.mg.config().unitInfo(UnitType.Shell).damage; + return baseDamage; + } + isActive(): boolean { return this.active; } diff --git a/src/core/execution/TradeShipExecution.ts b/src/core/execution/TradeShipExecution.ts index 99663faad..297a0c4cf 100644 --- a/src/core/execution/TradeShipExecution.ts +++ b/src/core/execution/TradeShipExecution.ts @@ -47,6 +47,7 @@ export class TradeShipExecution implements Execution { } this.tradeShip = this.origOwner.buildUnit(UnitType.TradeShip, 0, spawn, { dstPort: this._dstPort, + lastSetSafeFromPirates: ticks, }); } @@ -56,11 +57,11 @@ export class TradeShipExecution implements Execution { } if (this.origOwner != this.tradeShip.owner()) { - // Store as vairable in case ship is recaptured by previous owner + // Store as variable in case ship is recaptured by previous owner this.wasCaptured = true; } - // If a player captures an other player's port while trading we should delete + // If a player captures another player's port while trading we should delete // the ship. if (this._dstPort.owner().id() == this.srcPort.owner().id()) { this.tradeShip.delete(false); @@ -107,6 +108,10 @@ export class TradeShipExecution implements Execution { this.tradeShip.move(this.tradeShip.tile()); break; case PathFindResultType.NextTile: + // Update safeFromPirates status + if (this.mg.isWater(result.tile) && this.mg.isShoreline(result.tile)) { + this.tradeShip.setSafeFromPirates(); + } this.tradeShip.move(result.tile); break; case PathFindResultType.PathNotFound: diff --git a/src/core/execution/TransportShipExecution.ts b/src/core/execution/TransportShipExecution.ts index d89515956..056a39a46 100644 --- a/src/core/execution/TransportShipExecution.ts +++ b/src/core/execution/TransportShipExecution.ts @@ -26,6 +26,7 @@ export class TransportShipExecution implements Execution { private mg: Game; private attacker: Player; private target: Player | TerraNullius; + private embarkDelay = 10; // TODO make private public path: TileRef[]; @@ -136,6 +137,10 @@ export class TransportShipExecution implements Execution { this.active = false; return; } + if (this.embarkDelay > 0) { + this.embarkDelay--; + return; + } if (ticks - this.lastMove < this.ticksPerMove) { return; } diff --git a/src/core/execution/WarshipExecution.ts b/src/core/execution/WarshipExecution.ts index 632bdc2bd..c676403e5 100644 --- a/src/core/execution/WarshipExecution.ts +++ b/src/core/execution/WarshipExecution.ts @@ -26,12 +26,7 @@ export class WarshipExecution implements Execution { private patrolTile: TileRef; - // TODO: put in config - private searchRange = 100; - - private shellAttackRate = 5; private lastShellAttack = 0; - private alreadySentShell = new Set(); constructor( @@ -72,7 +67,8 @@ export class WarshipExecution implements Execution { } private shoot() { - if (this.mg.ticks() - this.lastShellAttack > this.shellAttackRate) { + const shellAttackRate = this.mg.config().warshipShellAttackRate(); + if (this.mg.ticks() - this.lastShellAttack > shellAttackRate) { this.lastShellAttack = this.mg.ticks(); this.mg.addExecution( new ShellExecution( @@ -137,7 +133,7 @@ export class WarshipExecution implements Execution { const ships = this.mg .nearbyUnits( this.warship.tile(), - 130, // Search range + this.mg.config().warshipTargettingRange(), [UnitType.TransportShip, UnitType.Warship, UnitType.TradeShip], ) .filter( @@ -146,9 +142,11 @@ export class WarshipExecution implements Execution { unit !== this.warship && !unit.owner().isFriendly(this.warship.owner()) && !this.alreadySentShell.has(unit) && - (unit.type() !== UnitType.TradeShip || hasPort) && (unit.type() !== UnitType.TradeShip || - unit.dstPort()?.owner() !== this._owner), + (hasPort && + unit.dstPort()?.owner() !== this.warship.owner() && + !unit.dstPort()?.owner().isFriendly(this.warship.owner()) && + unit.isSafeFromPirates() !== true)), ); this.target = @@ -198,9 +196,10 @@ export class WarshipExecution implements Execution { if ( this.target == null || !this.target.isActive() || - this.target.owner() == this._owner + this.target.owner() == this._owner || + this.target.isSafeFromPirates() == true ) { - // In case another destroyer captured or destroyed target + // In case another warship captured or destroyed target, or the target escaped into safe waters this.target = null; return; } @@ -250,18 +249,29 @@ export class WarshipExecution implements Execution { } randomTile(): TileRef { - while (true) { + let warshipPatrolRange = this.mg.config().warshipPatrolRange(); + const maxAttemptBeforeExpand: number = warshipPatrolRange * 2; + let attemptCount: number = 0; + let expandCount: number = 0; + while (expandCount < 3) { const x = this.mg.x(this.patrolCenterTile) + - this.random.nextInt(-this.searchRange / 2, this.searchRange / 2); + this.random.nextInt(-warshipPatrolRange / 2, warshipPatrolRange / 2); const y = this.mg.y(this.patrolCenterTile) + - this.random.nextInt(-this.searchRange / 2, this.searchRange / 2); + this.random.nextInt(-warshipPatrolRange / 2, warshipPatrolRange / 2); if (!this.mg.isValidCoord(x, y)) { continue; } const tile = this.mg.ref(x, y); - if (!this.mg.isOcean(tile)) { + if (!this.mg.isOcean(tile) || this.mg.isShoreline(tile)) { + attemptCount++; + if (attemptCount === maxAttemptBeforeExpand) { + expandCount++; + attemptCount = 0; + warshipPatrolRange = + warshipPatrolRange + Math.floor(warshipPatrolRange / 2); + } continue; } return tile; diff --git a/src/core/game/Game.ts b/src/core/game/Game.ts index 10a10af3d..1f51e30b1 100644 --- a/src/core/game/Game.ts +++ b/src/core/game/Game.ts @@ -240,6 +240,7 @@ export class PlayerInfo { // Some units have info specific to them export interface UnitSpecificInfos { dstPort?: Unit; // Only for trade ships + lastSetSafeFromPirates?: number; // Only for trade ships detonationDst?: TileRef; // Only for nukes warshipTarget?: Unit; cooldownDuration?: number; @@ -273,6 +274,8 @@ export interface Unit { isCooldown(): boolean; setDstPort(dstPort: Unit): void; dstPort(): Unit; // Only for trade ships + setSafeFromPirates(): void; // Only for trade ships + isSafeFromPirates(): boolean; // Only for trade ships detonationDst(): TileRef; // Only for nukes setMoveTarget(cell: TileRef): void; diff --git a/src/core/game/UnitImpl.ts b/src/core/game/UnitImpl.ts index f0dd2796d..4ce55c37c 100644 --- a/src/core/game/UnitImpl.ts +++ b/src/core/game/UnitImpl.ts @@ -17,11 +17,11 @@ export class UnitImpl implements Unit { private _active = true; private _health: bigint; private _lastTile: TileRef = null; - // Currently only warship use it private _target: Unit = null; private _moveTarget: TileRef = null; private _targetedBySAM = false; - + private _safeFromPiratesCooldown: number; // Only for trade ships + private _lastSetSafeFromPirates: number; // Only for trade ships private _constructionType: UnitType = undefined; private _cooldownTick: Tick | null = null; @@ -45,6 +45,10 @@ export class UnitImpl implements Unit { this._detonationDst = unitsSpecificInfos.detonationDst; this._warshipTarget = unitsSpecificInfos.warshipTarget; this._cooldownDuration = unitsSpecificInfos.cooldownDuration; + this._lastSetSafeFromPirates = unitsSpecificInfos.lastSetSafeFromPirates; + this._safeFromPiratesCooldown = this.mg + .config() + .safeFromPiratesCooldownMax(); } id() { @@ -233,4 +237,15 @@ export class UnitImpl implements Unit { targetedBySAM(): boolean { return this._targetedBySAM; } + + setSafeFromPirates(): void { + this._lastSetSafeFromPirates = this.mg.ticks(); + } + + isSafeFromPirates(): boolean { + return ( + this.mg.ticks() - this._lastSetSafeFromPirates < + this._safeFromPiratesCooldown + ); + } }