From 500b5fcfde6236fa789ec09ee1f81f1563d8d4d2 Mon Sep 17 00:00:00 2001 From: evanpelle Date: Sat, 17 May 2025 17:45:10 -0700 Subject: [PATCH] Clean up and refactor the Unit class (#769) ## Description: * Merged similar fields so they can be reused (eg warshipTarget => targetUnit) * simplified isCooldown api * added "touch" method to send update to UI layer * standardized on "undefined" ## Please complete the following: - [ ] 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: evan ## Summary by CodeRabbit - **Refactor** - Unified and simplified how unit targets and cooldowns are managed across all unit types, resulting in more consistent in-game behavior for nukes, warships, trade ships, and SAM launchers. - Updated naming and logic for unit targeting and cooldowns, improving clarity in status displays and interactions. - Reorganized unit interface and streamlined cooldown handling for smoother gameplay experience. - **Bug Fixes** - Corrected visual indicators for nukes and warships to accurately reflect their targets. - **Tests** - Updated automated tests to align with the new cooldown and targeting logic. --- src/client/graphics/layers/NameLayer.ts | 2 +- src/client/graphics/layers/UnitLayer.ts | 4 +- src/core/execution/MissileSiloExecution.ts | 7 +- src/core/execution/MoveWarshipExecution.ts | 2 +- src/core/execution/NukeExecution.ts | 2 +- src/core/execution/SAMLauncherExecution.ts | 16 ++- src/core/execution/TradeShipExecution.ts | 4 +- src/core/execution/WarshipExecution.ts | 111 +++++++++++---------- src/core/game/Game.ts | 62 ++++++------ src/core/game/GameUpdates.ts | 5 +- src/core/game/GameView.ts | 20 +--- src/core/game/PlayerImpl.ts | 2 +- src/core/game/UnitImpl.ts | 102 ++++++++----------- tests/MissileSilo.test.ts | 10 +- tests/SAM.test.ts | 10 +- 15 files changed, 167 insertions(+), 192 deletions(-) diff --git a/src/client/graphics/layers/NameLayer.ts b/src/client/graphics/layers/NameLayer.ts index c73460acf..9fc9584f7 100644 --- a/src/client/graphics/layers/NameLayer.ts +++ b/src/client/graphics/layers/NameLayer.ts @@ -461,7 +461,7 @@ export class NameLayer implements Layer { ); }); const isMyPlayerTarget = nukesSentByOtherPlayer.find((unit) => { - const detonationDst = unit.detonationDst(); + const detonationDst = unit.targetTile(); if (detonationDst === undefined) return false; const targetId = this.game.owner(detonationDst).id(); return myPlayer && targetId === myPlayer.id(); diff --git a/src/client/graphics/layers/UnitLayer.ts b/src/client/graphics/layers/UnitLayer.ts index 51e05a387..05fca583f 100644 --- a/src/client/graphics/layers/UnitLayer.ts +++ b/src/client/graphics/layers/UnitLayer.ts @@ -291,7 +291,7 @@ export class UnitLayer implements Layer { } private handleWarShipEvent(unit: UnitView) { - if (unit.warshipTargetId()) { + if (unit.targetUnitId()) { this.drawSprite(unit, colord({ r: 200, b: 0, g: 0 })); } else { this.drawSprite(unit); @@ -502,7 +502,7 @@ export class UnitLayer implements Layer { if (this.alternateView) { let rel = this.relationship(unit); - const dstPortId = unit.dstPortId(); + const dstPortId = unit.targetUnitId(); if (unit.type() === UnitType.TradeShip && dstPortId !== undefined) { const target = this.game.unit(dstPortId)?.owner(); const myPlayer = this.game.myPlayer(); diff --git a/src/core/execution/MissileSiloExecution.ts b/src/core/execution/MissileSiloExecution.ts index 2b40b39ad..8732b0f3b 100644 --- a/src/core/execution/MissileSiloExecution.ts +++ b/src/core/execution/MissileSiloExecution.ts @@ -53,11 +53,8 @@ export class MissileSiloExecution implements Execution { } } - if ( - this.silo.isCooldown() && - this.silo.ticksLeftInCooldown(this.mg.config().SiloCooldown()) === 0 - ) { - this.silo.setCooldown(false); + if (this.silo.ticksLeftInCooldown() === 0) { + this.silo.touch(); } } diff --git a/src/core/execution/MoveWarshipExecution.ts b/src/core/execution/MoveWarshipExecution.ts index 415ada80a..1d3290692 100644 --- a/src/core/execution/MoveWarshipExecution.ts +++ b/src/core/execution/MoveWarshipExecution.ts @@ -24,7 +24,7 @@ export class MoveWarshipExecution implements Execution { console.log("MoveWarshipExecution: warship is already dead"); return; } - warship.setMoveTarget(this.position); + warship.setTargetTile(this.position); this.active = false; } diff --git a/src/core/execution/NukeExecution.ts b/src/core/execution/NukeExecution.ts index 0b13941a1..4910f25bd 100644 --- a/src/core/execution/NukeExecution.ts +++ b/src/core/execution/NukeExecution.ts @@ -156,7 +156,7 @@ export class NukeExecution implements Execution { .units(UnitType.MissileSilo) .find((silo) => silo.tile() === spawn); if (silo) { - silo.setCooldown(true); + silo.launch(); } return; } diff --git a/src/core/execution/SAMLauncherExecution.ts b/src/core/execution/SAMLauncherExecution.ts index f16731fb8..2439f4462 100644 --- a/src/core/execution/SAMLauncherExecution.ts +++ b/src/core/execution/SAMLauncherExecution.ts @@ -135,10 +135,10 @@ export class SAMLauncherExecution implements Execution { unit.owner() !== this.player && !this.player.isFriendly(unit.owner()), ) .filter((unit) => { - const dst = unit.detonationDst(); + const dst = unit.targetTile(); return ( this.sam !== null && - dst !== null && + dst !== undefined && this.mg.manhattanDist(dst, this.sam.tile()) < this.MIRVWarheadProtectionRadius ); @@ -149,19 +149,17 @@ export class SAMLauncherExecution implements Execution { target = this.getSingleTarget(); } - if ( - this.sam.isCooldown() && - this.sam.ticksLeftInCooldown(this.mg.config().SAMCooldown()) === 0 - ) { - this.sam.setCooldown(false); + if (this.sam.ticksLeftInCooldown() === 0) { + // Touch SAM to update sprite to show not in cooldown. + this.sam.touch(); } const isSingleTarget = target && !target.targetedBySAM(); if ( (isSingleTarget || mirvWarheadTargets.length > 0) && - !this.sam.isCooldown() + !this.sam.isInCooldown() ) { - this.sam.setCooldown(true); + this.sam.launch(); const type = mirvWarheadTargets.length > 0 ? UnitType.MIRVWarhead : target?.type(); if (type === undefined) throw new Error("Unknown unit type"); diff --git a/src/core/execution/TradeShipExecution.ts b/src/core/execution/TradeShipExecution.ts index 600250c88..5e579aca5 100644 --- a/src/core/execution/TradeShipExecution.ts +++ b/src/core/execution/TradeShipExecution.ts @@ -94,7 +94,7 @@ export class TradeShipExecution implements Execution { return; } else { this._dstPort = ports[0]; - this.tradeShip.setDstPort(this._dstPort); + this.tradeShip.setTargetUnit(this._dstPort); } } @@ -122,7 +122,7 @@ export class TradeShipExecution implements Execution { break; case PathFindResultType.Pending: // Fire unit event to rerender. - this.tradeShip.move(this.tradeShip.tile()); + this.tradeShip.touch(); break; case PathFindResultType.NextTile: this._dstPort.cachePut(this.tradeShip.tile(), result.tile); diff --git a/src/core/execution/WarshipExecution.ts b/src/core/execution/WarshipExecution.ts index ce1d3197b..3188d9476 100644 --- a/src/core/execution/WarshipExecution.ts +++ b/src/core/execution/WarshipExecution.ts @@ -19,9 +19,9 @@ export class WarshipExecution implements Execution { private _owner: Player; private active = true; private warship: Unit | null = null; - private mg: Game | null = null; + private mg: Game; - private target: Unit | null = null; + private target: Unit | undefined = undefined; private pathfinder: PathFinder | null = null; private patrolTile: TileRef | undefined; @@ -35,6 +35,7 @@ export class WarshipExecution implements Execution { ) {} init(mg: Game, ticks: number): void { + this.mg = mg; if (!mg.hasPlayer(this.playerID)) { console.log(`WarshipExecution: player ${this.playerID} not found`); this.active = false; @@ -42,7 +43,6 @@ export class WarshipExecution implements Execution { } this.pathfinder = PathFinder.Mini(mg, 5000); this._owner = mg.player(this.playerID); - this.mg = mg; this.patrolTile = this.patrolCenterTile; this.random = new PseudoRandom(mg.ticks()); } @@ -56,14 +56,14 @@ export class WarshipExecution implements Execution { const result = this.pathfinder.nextTile(this.warship.tile(), target); switch (result.type) { case PathFindResultType.Completed: - this.warship.setMoveTarget(null); - this.warship.move(this.warship.tile()); + this.warship.setTargetTile(undefined); + this.warship.touch(); return; case PathFindResultType.NextTile: this.warship.move(result.tile); break; case PathFindResultType.Pending: - this.warship.move(this.warship.tile()); + this.warship.touch(); break; case PathFindResultType.PathNotFound: consolex.log(`path not found to target`); @@ -72,7 +72,11 @@ export class WarshipExecution implements Execution { } private shoot() { - if (this.mg === null || this.warship === null || this.target === null) { + if ( + this.mg === null || + this.warship === null || + this.target === undefined + ) { throw new Error("Warship not initialized"); } const shellAttackRate = this.mg.config().warshipShellAttackRate(); @@ -89,7 +93,7 @@ export class WarshipExecution implements Execution { if (!this.target.hasHealth()) { // Don't send multiple shells to target that can be oneshotted this.alreadySentShell.add(this.target); - this.target = null; + this.target = undefined; return; } } @@ -105,8 +109,11 @@ export class WarshipExecution implements Execution { return; } } - this.warship.setWarshipTarget(this.target); - if (this.target === null || this.target.type() !== UnitType.TradeShip) { + this.warship.setTargetUnit(this.target); + if ( + this.target === undefined || + this.target.type() !== UnitType.TradeShip + ) { // Patrol unless we are hunting down a tradeship const result = this.pathfinder.nextTile( this.warship.tile(), @@ -115,13 +122,13 @@ export class WarshipExecution implements Execution { switch (result.type) { case PathFindResultType.Completed: this.patrolTile = undefined; - this.warship.move(this.warship.tile()); + this.warship.touch(); break; case PathFindResultType.NextTile: this.warship.move(result.tile); break; case PathFindResultType.Pending: - this.warship.move(this.warship.tile()); + this.warship.touch(); return; case PathFindResultType.PathNotFound: consolex.log(`path not found to patrol tile`); @@ -153,13 +160,12 @@ export class WarshipExecution implements Execution { this.active = false; return; } - if (this.target !== null && !this.target.isActive()) { - this.target = null; + if (this.target !== undefined && !this.target.isActive()) { + this.target = undefined; } const hasPort = this._owner.units(UnitType.Port).length > 0; - if (this.mg === null) throw new Error("Game not initialized"); const warship = this.warship; - if (warship === null) throw new Error("Warship not initialized"); + if (warship === undefined) throw new Error("Warship not initialized"); const ships = this.mg .nearbyUnits( this.warship.tile(), @@ -175,68 +181,67 @@ export class WarshipExecution implements Execution { (unit.type() !== UnitType.TradeShip || (hasPort && this.warship !== null && - unit.dstPort()?.owner() !== this.warship.owner() && - !unit.dstPort()?.owner().isFriendly(this.warship.owner()) && + unit.targetUnit()?.owner() !== this.warship.owner() && + !unit.targetUnit()?.owner().isFriendly(this.warship.owner()) && unit.isSafeFromPirates() !== true)), ); - this.target = - ships.sort((a, b) => { - const { unit: unitA, distSquared: distA } = a; - const { unit: unitB, distSquared: distB } = b; + this.target = ships.sort((a, b) => { + const { unit: unitA, distSquared: distA } = a; + const { unit: unitB, distSquared: distB } = b; - // Prioritize Warships - if ( - unitA.type() === UnitType.Warship && - unitB.type() !== UnitType.Warship - ) - return -1; - if ( - unitA.type() !== UnitType.Warship && - unitB.type() === UnitType.Warship - ) - return 1; + // Prioritize Warships + if ( + unitA.type() === UnitType.Warship && + unitB.type() !== UnitType.Warship + ) + return -1; + if ( + unitA.type() !== UnitType.Warship && + unitB.type() === UnitType.Warship + ) + return 1; - // Then favor Transport Ships over Trade Ships - if ( - unitA.type() === UnitType.TransportShip && - unitB.type() !== UnitType.TransportShip - ) - return -1; - if ( - unitA.type() !== UnitType.TransportShip && - unitB.type() === UnitType.TransportShip - ) - return 1; + // Then favor Transport Ships over Trade Ships + 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 both are the same type, sort by distance (lower `distSquared` means closer) + return distA - distB; + })[0]?.unit; - const moveTarget = this.warship.moveTarget(); + const moveTarget = this.warship.targetTile(); if (moveTarget) { this.goToMoveTarget(moveTarget); // If we have a "move target" then we cannot target trade ships as it // requires moving. if (this.target && this.target.type() === UnitType.TradeShip) { - this.target = null; + this.target = undefined; } } else if (!this.target || this.target.type() !== UnitType.TradeShip) { this.patrol(); } if ( - this.target === null || + this.target === undefined || !this.target.isActive() || this.target.owner() === this._owner || this.target.isSafeFromPirates() === true ) { // In case another warship captured or destroyed target, or the target escaped into safe waters - this.target = null; + this.target = undefined; return; } - this.warship.setWarshipTarget(this.target); + this.warship.setTargetUnit(this.target); // If we have a move target we do not want to go after trading ships if (!this.target) { @@ -258,7 +263,7 @@ export class WarshipExecution implements Execution { switch (result.type) { case PathFindResultType.Completed: this._owner.captureUnit(this.target); - this.target = null; + this.target = undefined; this.warship.move(this.warship.tile()); return; case PathFindResultType.NextTile: diff --git a/src/core/game/Game.ts b/src/core/game/Game.ts index 99bbc690a..61f4051d5 100644 --- a/src/core/game/Game.ts +++ b/src/core/game/Game.ts @@ -322,57 +322,55 @@ export class PlayerInfo { } export interface Unit { + // Common properties. id(): number; - - // Properties type(): UnitType; - troops(): number; owner(): Player; info(): UnitInfo; - - // Location + delete(displayerMessage?: boolean): void; tile(): TileRef; lastTile(): TileRef; move(tile: TileRef): void; - - // State isActive(): boolean; + setOwner(owner: Player): void; + touch(): void; + toUpdate(): UnitUpdate; + + // Targeting + setTargetTile(cell: TileRef | undefined): void; + targetTile(): TileRef | undefined; + setTargetUnit(unit: Unit | undefined): void; + targetUnit(): Unit | undefined; + setTargetedBySAM(targeted: boolean): void; + targetedBySAM(): boolean; + + // Health hasHealth(): boolean; health(): number; modifyHealth(delta: number): void; - setWarshipTarget(target: Unit | null): void; // warship only - warshipTarget(): Unit | null; + // Troops + setTroops(troops: number): void; + troops(): number; - setOwner(owner: Player): void; - setCooldown(triggerCooldown: boolean): void; - ticksLeftInCooldown(cooldownDuration: number): Tick; - isCooldown(): boolean; - setDstPort(dstPort: Unit): void; - dstPort(): Unit | null; // Only for trade ships + // --- UNIT SPECIFIC --- + + // SAMs & Missile Silos + launch(): void; + ticksLeftInCooldown(): Tick | undefined; + isInCooldown(): boolean; + + // Trade Ships setSafeFromPirates(): void; // Only for trade ships isSafeFromPirates(): boolean; // Only for trade ships - detonationDst(): TileRef | null; // Only for nukes - setMoveTarget(cell: TileRef | null): void; - moveTarget(): TileRef | null; - - setTargetedBySAM(targeted: boolean): void; - targetedBySAM(): boolean; - - // Mutations - setTroops(troops: number): void; - delete(displayerMessage?: boolean): void; - - // Only for Construction type + // Construction constructionType(): UnitType | null; setConstructionType(type: UnitType): void; - // Updates - toUpdate(): UnitUpdate; - - cachePut(from: TileRef, to: TileRef): void; // ports only - cacheGet(from: TileRef): TileRef | undefined; // ports only + // Ports + cachePut(from: TileRef, to: TileRef): void; + cacheGet(from: TileRef): TileRef | undefined; } export interface TerraNullius { diff --git a/src/core/game/GameUpdates.ts b/src/core/game/GameUpdates.ts index 212782eb2..94e0c01c5 100644 --- a/src/core/game/GameUpdates.ts +++ b/src/core/game/GameUpdates.ts @@ -73,9 +73,8 @@ export interface UnitUpdate { pos: TileRef; lastPos: TileRef; isActive: boolean; - dstPortId?: number; // Only for trade ships - detonationDst?: TileRef; // Only for nukes - warshipTargetId?: number; + targetUnitId?: number; // Only for trade ships + targetTile?: TileRef; // Only for nukes health?: number; constructionType?: UnitType; ticksLeftInCooldown?: Tick; diff --git a/src/core/game/GameView.ts b/src/core/game/GameView.ts index 69bd76eeb..c50c9874b 100644 --- a/src/core/game/GameView.ts +++ b/src/core/game/GameView.ts @@ -8,7 +8,6 @@ import { GameUpdates, Gold, NameViewData, - nukeTypes, Player, PlayerActions, PlayerBorderTiles, @@ -83,7 +82,7 @@ export class UnitView { return this.data.pos; } owner(): PlayerView { - return this.gameView.playerBySmallID(this.data.ownerID) as PlayerView; + return this.gameView.playerBySmallID(this.data.ownerID)! as PlayerView; } isActive(): boolean { return this.data.isActive; @@ -97,20 +96,11 @@ export class UnitView { constructionType(): UnitType | undefined { return this.data.constructionType; } - dstPortId(): number | undefined { - return this.data.dstPortId; + targetUnitId(): number | undefined { + return this.data.targetUnitId; } - detonationDst(): TileRef | undefined { - if (!nukeTypes.includes(this.type())) { - throw Error("Must be a nuke"); - } - return this.data.detonationDst; - } - warshipTargetId(): number | undefined { - if (this.type() !== UnitType.Warship) { - throw Error("Must be a warship"); - } - return this.data.warshipTargetId; + targetTile(): TileRef | undefined { + return this.data.targetTile; } ticksLeftInCooldown(): Tick | undefined { return this.data.ticksLeftInCooldown; diff --git a/src/core/game/PlayerImpl.ts b/src/core/game/PlayerImpl.ts index 622be5ff2..0178bbe02 100644 --- a/src/core/game/PlayerImpl.ts +++ b/src/core/game/PlayerImpl.ts @@ -796,7 +796,7 @@ export class PlayerImpl implements Player { // only get missilesilos that are not on cooldown const spawns = this.units(UnitType.MissileSilo) .filter((silo) => { - return !silo.isCooldown(); + return !silo.isInCooldown(); }) .sort(distSortUnit(this.mg, tile)); if (spawns.length === 0) { diff --git a/src/core/game/UnitImpl.ts b/src/core/game/UnitImpl.ts index ddb85c167..a86e6c309 100644 --- a/src/core/game/UnitImpl.ts +++ b/src/core/game/UnitImpl.ts @@ -14,20 +14,16 @@ import { PlayerImpl } from "./PlayerImpl"; export class UnitImpl implements Unit { private _active = true; + private _targetTile: TileRef | undefined; + private _targetUnit: Unit | undefined; private _health: bigint; private _lastTile: TileRef; - private _moveTarget: TileRef | null = null; private _targetedBySAM = false; - private _safeFromPiratesCooldown: number; // Only for trade ships private _lastSetSafeFromPirates: number; // Only for trade ships private _constructionType: UnitType | undefined; private _lastOwner: PlayerImpl | null = null; private _troops: number; - private _cooldownTick: Tick | null = null; - private _dstPort: Unit | undefined = undefined; // Only for trade ships - private _detonationDst: TileRef | undefined = undefined; // Only for nukes - private _warshipTarget: Unit | undefined = undefined; - private _cooldownDuration: number | undefined = undefined; + private _cooldownStartTick: Tick | null = null; private _pathCache: Map = new Map(); constructor( @@ -40,19 +36,22 @@ export class UnitImpl implements Unit { ) { this._lastTile = _tile; this._health = toInt(this.mg.unitInfo(_type).maxHealth ?? 1); - this._safeFromPiratesCooldown = this.mg - .config() - .safeFromPiratesCooldownMax(); this._troops = "troops" in params ? (params.troops ?? 0) : 0; - this._dstPort = "dstPort" in params ? params.dstPort : undefined; - this._cooldownDuration = - "cooldownDuration" in params ? params.cooldownDuration : undefined; this._lastSetSafeFromPirates = "lastSetSafeFromPirates" in params ? (params.lastSetSafeFromPirates ?? 0) : 0; } + touch(): void { + this.mg.addUpdate(this.toUpdate()); + } + setTileTarget(tile: TileRef | undefined): void { + this._targetTile = tile; + } + tileTarget(): TileRef | undefined { + return this._targetTile; + } cachePut(from: TileRef, to: TileRef): void { this._pathCache.set(from, to); @@ -66,13 +65,6 @@ export class UnitImpl implements Unit { } toUpdate(): UnitUpdate { - const warshipTarget = this.warshipTarget(); - const dstPort = this.dstPort(); - if (this._lastTile === null) throw new Error("null _lastTile"); - const ticksLeftInCooldown = - this._cooldownDuration !== undefined - ? this.ticksLeftInCooldown(this._cooldownDuration) - : undefined; return { type: GameUpdateType.Unit, unitType: this._type, @@ -85,10 +77,9 @@ export class UnitImpl implements Unit { lastPos: this._lastTile, health: this.hasHealth() ? Number(this._health) : undefined, constructionType: this._constructionType, - dstPortId: dstPort?.id() ?? undefined, - warshipTargetId: warshipTarget?.id() ?? undefined, - detonationDst: this.detonationDst() ?? undefined, - ticksLeftInCooldown, + targetUnitId: this._targetUnit?.id() ?? undefined, + targetTile: this.targetTile() ?? undefined, + ticksLeftInCooldown: this.ticksLeftInCooldown() ?? undefined, }; } @@ -97,7 +88,6 @@ export class UnitImpl implements Unit { } lastTile(): TileRef { - if (this._lastTile === null) throw new Error("null _lastTile"); return this._lastTile; } @@ -111,6 +101,7 @@ export class UnitImpl implements Unit { this.mg.addUnit(this); this.mg.addUpdate(this.toUpdate()); } + setTroops(troops: number): void { this._troops = troops; } @@ -203,52 +194,47 @@ export class UnitImpl implements Unit { return `Unit:${this._type},owner:${this.owner().name()}`; } - setWarshipTarget(target: Unit) { - this._warshipTarget = target; + launch(): void { + this._cooldownStartTick = this.mg.ticks(); + this.mg.addUpdate(this.toUpdate()); } - warshipTarget(): Unit | null { - return this._warshipTarget ?? null; - } - - detonationDst(): TileRef | null { - return this._detonationDst ?? null; - } - - dstPort(): Unit | null { - return this._dstPort ?? null; - } - - // set the cooldown to the current tick or remove it - setCooldown(triggerCooldown: boolean): void { - if (triggerCooldown) { - this._cooldownTick = this.mg.ticks(); - this.mg.addUpdate(this.toUpdate()); + ticksLeftInCooldown(): Tick | undefined { + let cooldownDuration = 0; + if (this.type() === UnitType.SAMLauncher) { + cooldownDuration = this.mg.config().SAMCooldown(); + } else if (this.type() === UnitType.MissileSilo) { + cooldownDuration = this.mg.config().SiloCooldown(); } else { - this._cooldownTick = null; - this.mg.addUpdate(this.toUpdate()); + return undefined; } + + if (!this._cooldownStartTick) { + return undefined; + } + + return cooldownDuration - (this.mg.ticks() - this._cooldownStartTick); } - ticksLeftInCooldown(cooldownDuration: number): Tick { - const cooldownTick = this._cooldownTick ?? 0; - return Math.max(0, cooldownDuration - (this.mg.ticks() - cooldownTick)); + isInCooldown(): boolean { + const ticksLeft = this.ticksLeftInCooldown(); + return ticksLeft !== undefined && ticksLeft > 0; } - isCooldown(): boolean { - return this._cooldownTick ? true : false; + setTargetTile(targetTile: TileRef | undefined) { + this._targetTile = targetTile; } - setDstPort(dstPort: Unit): void { - this._dstPort = dstPort; + targetTile(): TileRef | undefined { + return this._targetTile; } - setMoveTarget(moveTarget: TileRef) { - this._moveTarget = moveTarget; + setTargetUnit(target: Unit | undefined): void { + this._targetUnit = target; } - moveTarget(): TileRef | null { - return this._moveTarget; + targetUnit(): Unit | undefined { + return this._targetUnit; } setTargetedBySAM(targeted: boolean): void { @@ -266,7 +252,7 @@ export class UnitImpl implements Unit { isSafeFromPirates(): boolean { return ( this.mg.ticks() - this._lastSetSafeFromPirates < - this._safeFromPiratesCooldown + this.mg.config().safeFromPiratesCooldownMax() ); } } diff --git a/tests/MissileSilo.test.ts b/tests/MissileSilo.test.ts index 16162c7a3..d5930680b 100644 --- a/tests/MissileSilo.test.ts +++ b/tests/MissileSilo.test.ts @@ -73,17 +73,19 @@ describe("MissileSilo", () => { }); test("missilesilo should cooldown as long as configured", async () => { - expect(attacker.units(UnitType.MissileSilo)[0].isCooldown()).toBeFalsy(); + expect(attacker.units(UnitType.MissileSilo)[0].isInCooldown()).toBeFalsy(); // send the nuke far enough away so it doesnt destroy the silo attackerBuildsNuke(null, game.ref(50, 50)); expect(attacker.units(UnitType.AtomBomb)).toHaveLength(1); - for (let i = 0; i < game.config().SiloCooldown() - 1; i++) { + for (let i = 0; i < game.config().SiloCooldown() - 2; i++) { game.executeNextTick(); - expect(attacker.units(UnitType.MissileSilo)[0].isCooldown()).toBeTruthy(); + expect( + attacker.units(UnitType.MissileSilo)[0].isInCooldown(), + ).toBeTruthy(); } game.executeNextTick(); - expect(attacker.units(UnitType.MissileSilo)[0].isCooldown()).toBeFalsy(); + expect(attacker.units(UnitType.MissileSilo)[0].isInCooldown()).toBeFalsy(); }); }); diff --git a/tests/SAM.test.ts b/tests/SAM.test.ts index 51d267858..1da6e9114 100644 --- a/tests/SAM.test.ts +++ b/tests/SAM.test.ts @@ -78,7 +78,7 @@ describe("SAM", () => { test("sam should cooldown as long as configured", async () => { const sam = defender.buildUnit(UnitType.SAMLauncher, game.ref(1, 1), {}); game.addExecution(new SAMLauncherExecution(defender.id(), null, sam)); - expect(sam.isCooldown()).toBeFalsy(); + expect(sam.isInCooldown()).toBeFalsy(); const nuke = attacker.buildUnit(UnitType.AtomBomb, game.ref(1, 2), { detonationDst: game.ref(1, 2), }); @@ -86,14 +86,14 @@ describe("SAM", () => { executeTicks(game, 3); expect(nuke.isActive()).toBeFalsy(); - for (let i = 0; i < game.config().SAMCooldown() - 2; i++) { + for (let i = 0; i < game.config().SAMCooldown() - 3; i++) { game.executeNextTick(); - expect(sam.isCooldown()).toBeTruthy(); + expect(sam.isInCooldown()).toBeTruthy(); } executeTicks(game, 2); - expect(sam.isCooldown()).toBeFalsy(); + expect(sam.isInCooldown()).toBeFalsy(); }); test("two sams should not target twice same nuke", async () => { @@ -110,6 +110,6 @@ describe("SAM", () => { executeTicks(game, 3); expect(nuke.isActive()).toBeFalsy(); - expect([sam1, sam2].filter((s) => s.isCooldown())).toHaveLength(1); + expect([sam1, sam2].filter((s) => s.isInCooldown())).toHaveLength(1); }); });