diff --git a/src/client/ClientGameRunner.ts b/src/client/ClientGameRunner.ts index 374cc8e86..d0c4de33c 100644 --- a/src/client/ClientGameRunner.ts +++ b/src/client/ClientGameRunner.ts @@ -112,6 +112,7 @@ export async function createClientGame( const config = await getConfig( lobbyConfig.gameStartInfo.config, userSettings, + lobbyConfig.gameRecord != null, ); let gameMap: TerrainMapData | null = null; @@ -239,9 +240,11 @@ export class ClientGameRunner { this.lobby.gameStartInfo.gameID, this.lobby.clientID, ); + console.error(gu.stack); this.stop(true); return; } + this.transport.turnComplete(); gu.updates[GameUpdateType.Hash].forEach((hu: HashUpdate) => { this.eventBus.emit(new SendHashEvent(hu.tick, hu.hash)); }); diff --git a/src/client/LocalServer.ts b/src/client/LocalServer.ts index 42a777129..852d6fb64 100644 --- a/src/client/LocalServer.ts +++ b/src/client/LocalServer.ts @@ -16,42 +16,58 @@ import { LobbyConfig } from "./ClientGameRunner"; import { getPersistentIDFromCookie } from "./Main"; export class LocalServer { + // All turns from the game record on replay. + private replayTurns: Turn[] = []; + private turns: Turn[] = []; + private intents: Intent[] = []; private startedAt: number; - private endTurnIntervalID; - private paused = false; private winner: ClientSendWinnerMessage = null; private allPlayersStats: AllPlayersStats = {}; + private turnsExecuted = 0; + private lastTurnCompletedTime = 0; + + private turnCheckInterval: NodeJS.Timeout; + constructor( private lobbyConfig: LobbyConfig, private clientConnect: () => void, private clientMessage: (message: ServerMessage) => void, + private isReplay: boolean, ) {} start() { + this.turnCheckInterval = setInterval(() => { + if (this.turnsExecuted == this.turns.length) { + if ( + this.isReplay || + Date.now() > + this.lastTurnCompletedTime + + this.lobbyConfig.serverConfig.turnIntervalMs() + ) { + this.endTurn(); + } + } + }, 5); + this.startedAt = Date.now(); - if (!this.lobbyConfig.gameRecord) { - this.endTurnIntervalID = setInterval( - () => this.endTurn(), - this.lobbyConfig.serverConfig.turnIntervalMs(), - ); - } this.clientConnect(); if (this.lobbyConfig.gameRecord) { - this.turns = decompressGameRecord(this.lobbyConfig.gameRecord).turns; - console.log(`loaded turns: ${JSON.stringify(this.turns)}`); + this.replayTurns = decompressGameRecord( + this.lobbyConfig.gameRecord, + ).turns; } this.clientMessage( ServerStartGameMessageSchema.parse({ type: "start", gameID: this.lobbyConfig.gameStartInfo.gameID, gameStartInfo: this.lobbyConfig.gameStartInfo, - turns: this.turns, + turns: [], }), ); } @@ -90,7 +106,7 @@ export class LocalServer { return; } // If we are replaying a game then verify hash. - const archivedHash = this.turns[clientMsg.turnNumber].hash; + const archivedHash = this.replayTurns[clientMsg.turnNumber].hash; if (!archivedHash) { console.warn( `no archived hash found for turn ${clientMsg.turnNumber}, client hash: ${clientMsg.hash}`, @@ -121,10 +137,18 @@ export class LocalServer { } } + public turnComplete() { + this.turnsExecuted++; + this.lastTurnCompletedTime = Date.now(); + } + private endTurn() { if (this.paused) { return; } + if (this.replayTurns.length > 0) { + this.intents = this.replayTurns[this.turns.length].intents; + } const pastTurn: Turn = { turnNumber: this.turns.length, intents: this.intents, @@ -139,7 +163,7 @@ export class LocalServer { public endGame(saveFullGame: boolean = false) { consolex.log("local server ending game"); - clearInterval(this.endTurnIntervalID); + clearInterval(this.turnCheckInterval); const players: PlayerRecord[] = [ { ip: null, diff --git a/src/client/Transport.ts b/src/client/Transport.ts index 73c9fee97..6c52e4e17 100644 --- a/src/client/Transport.ts +++ b/src/client/Transport.ts @@ -263,7 +263,12 @@ export class Transport { onconnect: () => void, onmessage: (message: ServerMessage) => void, ) { - this.localServer = new LocalServer(this.lobbyConfig, onconnect, onmessage); + this.localServer = new LocalServer( + this.lobbyConfig, + onconnect, + onmessage, + this.lobbyConfig.gameRecord != null, + ); this.localServer.start(); } @@ -318,6 +323,12 @@ export class Transport { this.connect(this.onconnect, this.onmessage); } + public turnComplete() { + if (this.isLocal) { + this.localServer.turnComplete(); + } + } + private onSendLogEvent(event: SendLogEvent) { this.sendMsg( JSON.stringify({ diff --git a/src/client/graphics/layers/OptionsMenu.ts b/src/client/graphics/layers/OptionsMenu.ts index cad75e929..5d69da364 100644 --- a/src/client/graphics/layers/OptionsMenu.ts +++ b/src/client/graphics/layers/OptionsMenu.ts @@ -122,7 +122,8 @@ export class OptionsMenu extends LitElement implements Layer { init() { console.log("init called from OptionsMenu"); this.showPauseButton = - this.game.config().gameConfig().gameType == GameType.Singleplayer; + this.game.config().gameConfig().gameType == GameType.Singleplayer || + this.game.config().isReplay(); this.isVisible = true; this.requestUpdate(); } diff --git a/src/core/configuration/Config.ts b/src/core/configuration/Config.ts index 93e2f54c9..26564eaf5 100644 --- a/src/core/configuration/Config.ts +++ b/src/core/configuration/Config.ts @@ -136,6 +136,7 @@ export interface Config { defaultNukeSpeed(): number; nukeDeathFactor(humans: number, tilesOwned: number): number; structureMinDist(): number; + isReplay(): boolean; } export interface Theme { diff --git a/src/core/configuration/ConfigLoader.ts b/src/core/configuration/ConfigLoader.ts index 3954e6a4c..342b425fc 100644 --- a/src/core/configuration/ConfigLoader.ts +++ b/src/core/configuration/ConfigLoader.ts @@ -12,15 +12,16 @@ export let cachedSC: ServerConfig = null; export async function getConfig( gameConfig: GameConfig, userSettings: UserSettings | null = null, + isReplay: boolean = false, ): Promise { const sc = await getServerConfigFromClient(); switch (sc.env()) { case GameEnv.Dev: - return new DevConfig(sc, gameConfig, userSettings); + return new DevConfig(sc, gameConfig, userSettings, isReplay); case GameEnv.Preprod: case GameEnv.Prod: consolex.log("using prod config"); - return new DefaultConfig(sc, gameConfig, userSettings); + return new DefaultConfig(sc, gameConfig, userSettings, isReplay); default: throw Error(`unsupported server configuration: ${process.env.GAME_ENV}`); } diff --git a/src/core/configuration/DefaultConfig.ts b/src/core/configuration/DefaultConfig.ts index c9a4d09e0..12c1b63d8 100644 --- a/src/core/configuration/DefaultConfig.ts +++ b/src/core/configuration/DefaultConfig.ts @@ -158,7 +158,11 @@ export class DefaultConfig implements Config { private _serverConfig: ServerConfig, private _gameConfig: GameConfig, private _userSettings: UserSettings, + private _isReplay: boolean, ) {} + isReplay(): boolean { + return this._isReplay; + } samHittingChance(): number { return 0.8; diff --git a/src/core/configuration/DevConfig.ts b/src/core/configuration/DevConfig.ts index 909e3a156..0f6f0f019 100644 --- a/src/core/configuration/DevConfig.ts +++ b/src/core/configuration/DevConfig.ts @@ -41,8 +41,13 @@ export class DevServerConfig extends DefaultServerConfig { } export class DevConfig extends DefaultConfig { - constructor(sc: ServerConfig, gc: GameConfig, us: UserSettings) { - super(sc, gc, us); + constructor( + sc: ServerConfig, + gc: GameConfig, + us: UserSettings, + isReplay: boolean, + ) { + super(sc, gc, us, isReplay); } // numSpawnPhaseTurns(): number { diff --git a/src/core/execution/CityExecution.ts b/src/core/execution/CityExecution.ts index 5bf2ca7e4..289a64957 100644 --- a/src/core/execution/CityExecution.ts +++ b/src/core/execution/CityExecution.ts @@ -38,7 +38,7 @@ export class CityExecution implements Execution { this.active = false; return; } - this.city = this.player.buildUnit(UnitType.City, 0, spawnTile); + this.city = this.player.buildUnit(UnitType.City, spawnTile, {}); } if (!this.city.isActive()) { this.active = false; diff --git a/src/core/execution/ConstructionExecution.ts b/src/core/execution/ConstructionExecution.ts index c5e73f1f2..36242a4b4 100644 --- a/src/core/execution/ConstructionExecution.ts +++ b/src/core/execution/ConstructionExecution.ts @@ -60,8 +60,8 @@ export class ConstructionExecution implements Execution { } this.construction = this.player.buildUnit( UnitType.Construction, - 0, spawnTile, + {}, ); this.cost = this.mg.unitInfo(this.constructionType).cost(this.player); this.player.removeGold(this.cost); diff --git a/src/core/execution/DefensePostExecution.ts b/src/core/execution/DefensePostExecution.ts index 413cf93b4..8884f86d3 100644 --- a/src/core/execution/DefensePostExecution.ts +++ b/src/core/execution/DefensePostExecution.ts @@ -65,7 +65,7 @@ export class DefensePostExecution implements Execution { this.active = false; return; } - this.post = this.player.buildUnit(UnitType.DefensePost, 0, spawnTile); + this.post = this.player.buildUnit(UnitType.DefensePost, spawnTile, {}); } if (!this.post.isActive()) { this.active = false; diff --git a/src/core/execution/MIRVExecution.ts b/src/core/execution/MIRVExecution.ts index 17ee241c6..61b38df81 100644 --- a/src/core/execution/MIRVExecution.ts +++ b/src/core/execution/MIRVExecution.ts @@ -73,7 +73,7 @@ export class MirvExecution implements Execution { this.active = false; return; } - this.nuke = this.player.buildUnit(UnitType.MIRV, 0, spawn); + this.nuke = this.player.buildUnit(UnitType.MIRV, spawn, {}); const x = Math.floor( (this.mg.x(this.dst) + this.mg.x(this.mg.x(this.nuke.tile()))) / 2, ); diff --git a/src/core/execution/MissileSiloExecution.ts b/src/core/execution/MissileSiloExecution.ts index fd9bf2111..b13eaadef 100644 --- a/src/core/execution/MissileSiloExecution.ts +++ b/src/core/execution/MissileSiloExecution.ts @@ -41,7 +41,7 @@ export class MissileSiloExecution implements Execution { this.active = false; return; } - this.silo = this.player.buildUnit(UnitType.MissileSilo, 0, spawn, { + this.silo = this.player.buildUnit(UnitType.MissileSilo, spawn, { cooldownDuration: this.mg.config().SiloCooldown(), }); diff --git a/src/core/execution/NukeExecution.ts b/src/core/execution/NukeExecution.ts index 484da9355..4572eefc5 100644 --- a/src/core/execution/NukeExecution.ts +++ b/src/core/execution/NukeExecution.ts @@ -95,13 +95,12 @@ export class NukeExecution implements Execution { this.active = false; return; } - const maxVertex = this.type == UnitType.MIRVWarhead ? 0 : null; this.pathFinder.computeControlPoints( spawn, this.dst, this.type != UnitType.MIRVWarhead, ); - this.nuke = this.player.buildUnit(this.type, 0, spawn, { + this.nuke = this.player.buildUnit(this.type, spawn, { detonationDst: this.dst, }); if (this.mg.hasOwner(this.dst)) { diff --git a/src/core/execution/PortExecution.ts b/src/core/execution/PortExecution.ts index 0e61470ad..1f7dd52eb 100644 --- a/src/core/execution/PortExecution.ts +++ b/src/core/execution/PortExecution.ts @@ -45,7 +45,7 @@ export class PortExecution implements Execution { this.active = false; return; } - this.port = player.buildUnit(UnitType.Port, 0, spawn); + this.port = player.buildUnit(UnitType.Port, spawn, {}); } if (!this.port.isActive()) { diff --git a/src/core/execution/SAMLauncherExecution.ts b/src/core/execution/SAMLauncherExecution.ts index c3ebbf54d..511d3c5fa 100644 --- a/src/core/execution/SAMLauncherExecution.ts +++ b/src/core/execution/SAMLauncherExecution.ts @@ -99,7 +99,7 @@ export class SAMLauncherExecution implements Execution { this.active = false; return; } - this.sam = this.player.buildUnit(UnitType.SAMLauncher, 0, spawnTile, { + this.sam = this.player.buildUnit(UnitType.SAMLauncher, spawnTile, { cooldownDuration: this.mg.config().SAMCooldown(), }); } diff --git a/src/core/execution/SAMMissileExecution.ts b/src/core/execution/SAMMissileExecution.ts index 959c53330..32ce661b2 100644 --- a/src/core/execution/SAMMissileExecution.ts +++ b/src/core/execution/SAMMissileExecution.ts @@ -33,8 +33,8 @@ export class SAMMissileExecution implements Execution { if (this.SAMMissile == null) { this.SAMMissile = this._owner.buildUnit( UnitType.SAMMissile, - 0, this.spawn, + {}, ); } if (!this.SAMMissile.isActive()) { diff --git a/src/core/execution/ShellExecution.ts b/src/core/execution/ShellExecution.ts index 0fc149e11..2acc90cb3 100644 --- a/src/core/execution/ShellExecution.ts +++ b/src/core/execution/ShellExecution.ts @@ -24,7 +24,7 @@ export class ShellExecution implements Execution { tick(ticks: number): void { if (this.shell == null) { - this.shell = this._owner.buildUnit(UnitType.Shell, 0, this.spawn); + this.shell = this._owner.buildUnit(UnitType.Shell, this.spawn, {}); } if (!this.shell.isActive()) { this.active = false; diff --git a/src/core/execution/TradeShipExecution.ts b/src/core/execution/TradeShipExecution.ts index 297a0c4cf..6157e2063 100644 --- a/src/core/execution/TradeShipExecution.ts +++ b/src/core/execution/TradeShipExecution.ts @@ -45,7 +45,7 @@ export class TradeShipExecution implements Execution { this.active = false; return; } - this.tradeShip = this.origOwner.buildUnit(UnitType.TradeShip, 0, spawn, { + this.tradeShip = this.origOwner.buildUnit(UnitType.TradeShip, spawn, { dstPort: this._dstPort, lastSetSafeFromPirates: ticks, }); diff --git a/src/core/execution/TransportShipExecution.ts b/src/core/execution/TransportShipExecution.ts index 11c294b73..0194c895d 100644 --- a/src/core/execution/TransportShipExecution.ts +++ b/src/core/execution/TransportShipExecution.ts @@ -139,11 +139,9 @@ export class TransportShipExecution implements Execution { } } - this.boat = this.attacker.buildUnit( - UnitType.TransportShip, - this.troops, - this.src, - ); + this.boat = this.attacker.buildUnit(UnitType.TransportShip, this.src, { + troops: this.troops, + }); } tick(ticks: number) { diff --git a/src/core/execution/WarshipExecution.ts b/src/core/execution/WarshipExecution.ts index db467d55d..d96373b1e 100644 --- a/src/core/execution/WarshipExecution.ts +++ b/src/core/execution/WarshipExecution.ts @@ -54,11 +54,13 @@ export class WarshipExecution implements Execution { switch (result.type) { case PathFindResultType.Completed: this.warship.setMoveTarget(null); + this.warship.move(this.warship.tile()); return; case PathFindResultType.NextTile: this.warship.move(result.tile); break; case PathFindResultType.Pending: + this.warship.move(this.warship.tile()); break; case PathFindResultType.PathNotFound: consolex.log(`path not found to target`); @@ -98,11 +100,13 @@ export class WarshipExecution implements Execution { switch (result.type) { case PathFindResultType.Completed: this.patrolTile = this.randomTile(); + this.warship.move(this.warship.tile()); break; case PathFindResultType.NextTile: this.warship.move(result.tile); break; case PathFindResultType.Pending: + this.warship.move(this.warship.tile()); return; case PathFindResultType.PathNotFound: consolex.log(`path not found to patrol tile`); @@ -119,7 +123,7 @@ export class WarshipExecution implements Execution { this.active = false; return; } - this.warship = this._owner.buildUnit(UnitType.Warship, 0, spawn); + this.warship = this._owner.buildUnit(UnitType.Warship, spawn, {}); return; } if (!this.warship.isActive()) { @@ -227,11 +231,13 @@ export class WarshipExecution implements Execution { case PathFindResultType.Completed: this._owner.captureUnit(this.target); this.target = null; + this.warship.move(this.warship.tile()); return; case PathFindResultType.NextTile: this.warship.move(result.tile); break; case PathFindResultType.Pending: + this.warship.move(this.warship.tile()); break; case PathFindResultType.PathNotFound: consolex.log(`path not found to target`); diff --git a/src/core/game/Game.ts b/src/core/game/Game.ts index b00a57405..8eec8c263 100644 --- a/src/core/game/Game.ts +++ b/src/core/game/Game.ts @@ -148,6 +148,51 @@ export enum UnitType { Construction = "Construction", } +export interface UnitParamsMap { + [UnitType.TransportShip]: { + troops?: number; + destination?: TileRef; + }; + + [UnitType.Warship]: {}; + + [UnitType.Shell]: {}; + + [UnitType.SAMMissile]: {}; + + [UnitType.Port]: {}; + + [UnitType.AtomBomb]: {}; + + [UnitType.HydrogenBomb]: {}; + + [UnitType.TradeShip]: { + dstPort: Unit; + lastSetSafeFromPirates?: number; + }; + + [UnitType.MissileSilo]: { + cooldownDuration?: number; + }; + + [UnitType.DefensePost]: {}; + + [UnitType.SAMLauncher]: {}; + + [UnitType.City]: {}; + + [UnitType.MIRV]: {}; + + [UnitType.MIRVWarhead]: {}; + + [UnitType.Construction]: {}; +} + +// Type helper to get params type for a specific unit type +export type UnitParams = UnitParamsMap[T]; + +export type AllUnitParams = UnitParamsMap[keyof UnitParamsMap]; + export const nukeTypes = [ UnitType.AtomBomb, UnitType.HydrogenBomb, @@ -276,15 +321,6 @@ 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; -} - export interface Unit { id(): number; @@ -391,12 +427,12 @@ export interface Player { unitsIncludingConstruction(type: UnitType): Unit[]; buildableUnits(tile: TileRef): BuildableUnit[]; canBuild(type: UnitType, targetTile: TileRef): TileRef | false; - buildUnit( - type: UnitType, - troops: number, - tile: TileRef, - unitSpecificInfos?: UnitSpecificInfos, + buildUnit( + type: T, + spawnTile: TileRef, + params: UnitParams, ): Unit; + captureUnit(unit: Unit): void; // Relations & Diplomacy diff --git a/src/core/game/PlayerImpl.ts b/src/core/game/PlayerImpl.ts index 55972adc5..0a98ff41a 100644 --- a/src/core/game/PlayerImpl.ts +++ b/src/core/game/PlayerImpl.ts @@ -35,7 +35,7 @@ import { TerraNullius, Tick, Unit, - UnitSpecificInfos, + UnitParams, UnitType, } from "./Game"; import { GameImpl } from "./GameImpl"; @@ -703,11 +703,10 @@ export class PlayerImpl implements Player { ); } - buildUnit( - type: UnitType, - troops: number, + buildUnit( + type: T, spawnTile: TileRef, - unitSpecificInfos: UnitSpecificInfos = {}, + params: UnitParams, ): UnitImpl { if (this.mg.config().isUnitDisabled(type)) { throw new Error( @@ -720,14 +719,13 @@ export class PlayerImpl implements Player { type, this.mg, spawnTile, - troops, this.mg.nextUnitID(), this, - unitSpecificInfos, + params, ); this._units.push(b); this.removeGold(cost); - this.removeTroops(troops); + this.removeTroops("troops" in params ? params.troops : 0); this.mg.addUpdate(b.toUpdate()); this.mg.addUnit(b); diff --git a/src/core/game/UnitImpl.ts b/src/core/game/UnitImpl.ts index 247e0eced..9c1458f51 100644 --- a/src/core/game/UnitImpl.ts +++ b/src/core/game/UnitImpl.ts @@ -1,11 +1,11 @@ import { simpleHash, toInt, withinInt } from "../Util"; import { + AllUnitParams, MessageType, Player, Tick, Unit, UnitInfo, - UnitSpecificInfos, UnitType, } from "./Game"; import { GameImpl } from "./GameImpl"; @@ -24,6 +24,7 @@ export class UnitImpl implements Unit { private _lastSetSafeFromPirates: number; // Only for trade ships private _constructionType: UnitType = undefined; + private _troops: number; private _cooldownTick: Tick | null = null; private _dstPort: Unit | null = null; // Only for trade ships private _detonationDst: TileRef | null = null; // Only for nukes @@ -34,21 +35,22 @@ export class UnitImpl implements Unit { private _type: UnitType, private mg: GameImpl, private _tile: TileRef, - private _troops: number, private _id: number, public _owner: PlayerImpl, - unitsSpecificInfos: UnitSpecificInfos = {}, + params: AllUnitParams = {}, ) { - this._health = toInt(this.mg.unitInfo(_type).maxHealth ?? 1); this._lastTile = _tile; - this._dstPort = unitsSpecificInfos.dstPort; - this._detonationDst = unitsSpecificInfos.detonationDst; - this._warshipTarget = unitsSpecificInfos.warshipTarget; - this._cooldownDuration = unitsSpecificInfos.cooldownDuration; - this._lastSetSafeFromPirates = unitsSpecificInfos.lastSetSafeFromPirates; + this._health = toInt(this.mg.unitInfo(_type).maxHealth ?? 1); this._safeFromPiratesCooldown = this.mg .config() .safeFromPiratesCooldownMax(); + + this._troops = "troops" in params ? params.troops : 0; + this._dstPort = "dstPort" in params ? params.dstPort : null; + this._cooldownDuration = + "cooldownDuration" in params ? params.cooldownDuration : null; + this._lastSetSafeFromPirates = + "lastSetSafeFromPirates" in params ? params.lastSetSafeFromPirates : 0; } id() { diff --git a/tests/SAM.test.ts b/tests/SAM.test.ts index b3600310b..51d267858 100644 --- a/tests/SAM.test.ts +++ b/tests/SAM.test.ts @@ -50,9 +50,9 @@ describe("SAM", () => { }); test("one sam should take down one nuke", async () => { - const sam = defender.buildUnit(UnitType.SAMLauncher, 0, game.ref(1, 1)); + const sam = defender.buildUnit(UnitType.SAMLauncher, game.ref(1, 1), {}); game.addExecution(new SAMLauncherExecution(defender.id(), null, sam)); - attacker.buildUnit(UnitType.AtomBomb, 0, game.ref(1, 1)); + attacker.buildUnit(UnitType.AtomBomb, game.ref(1, 1), {}); executeTicks(game, 3); @@ -60,10 +60,14 @@ describe("SAM", () => { }); test("sam should only get one nuke at a time", async () => { - const sam = defender.buildUnit(UnitType.SAMLauncher, 0, game.ref(1, 1)); + const sam = defender.buildUnit(UnitType.SAMLauncher, game.ref(1, 1), {}); game.addExecution(new SAMLauncherExecution(defender.id(), null, sam)); - attacker.buildUnit(UnitType.AtomBomb, 0, game.ref(2, 1)); - attacker.buildUnit(UnitType.AtomBomb, 0, game.ref(1, 2)); + attacker.buildUnit(UnitType.AtomBomb, game.ref(2, 1), { + detonationDst: game.ref(2, 1), + }); + attacker.buildUnit(UnitType.AtomBomb, game.ref(1, 2), { + detonationDst: game.ref(1, 2), + }); expect(attacker.units(UnitType.AtomBomb)).toHaveLength(2); executeTicks(game, 3); @@ -72,10 +76,12 @@ describe("SAM", () => { }); test("sam should cooldown as long as configured", async () => { - const sam = defender.buildUnit(UnitType.SAMLauncher, 0, game.ref(1, 1)); + const sam = defender.buildUnit(UnitType.SAMLauncher, game.ref(1, 1), {}); game.addExecution(new SAMLauncherExecution(defender.id(), null, sam)); expect(sam.isCooldown()).toBeFalsy(); - const nuke = attacker.buildUnit(UnitType.AtomBomb, 0, game.ref(1, 2)); + const nuke = attacker.buildUnit(UnitType.AtomBomb, game.ref(1, 2), { + detonationDst: game.ref(1, 2), + }); executeTicks(game, 3); @@ -91,11 +97,15 @@ describe("SAM", () => { }); test("two sams should not target twice same nuke", async () => { - const sam1 = defender.buildUnit(UnitType.SAMLauncher, 0, game.ref(1, 1)); + const sam1 = defender.buildUnit(UnitType.SAMLauncher, game.ref(1, 1), { + cooldownDuration: 10, + }); game.addExecution(new SAMLauncherExecution(defender.id(), null, sam1)); - const sam2 = defender.buildUnit(UnitType.SAMLauncher, 0, game.ref(1, 2)); + const sam2 = defender.buildUnit(UnitType.SAMLauncher, game.ref(1, 2), {}); game.addExecution(new SAMLauncherExecution(defender.id(), null, sam2)); - const nuke = attacker.buildUnit(UnitType.AtomBomb, 0, game.ref(2, 2)); + const nuke = attacker.buildUnit(UnitType.AtomBomb, game.ref(2, 2), { + detonationDst: game.ref(2, 2), + }); executeTicks(game, 3); diff --git a/tests/Warship.test.ts b/tests/Warship.test.ts index c1ca61ecf..dc1905739 100644 --- a/tests/Warship.test.ts +++ b/tests/Warship.test.ts @@ -59,11 +59,11 @@ describe("Warship", () => { test("Warship heals only if player has port", async () => { const maxHealth = game.config().unitInfo(UnitType.Warship).maxHealth; - const port = player1.buildUnit(UnitType.Port, 0, game.ref(coastX, 10)); + const port = player1.buildUnit(UnitType.Port, game.ref(coastX, 10), {}); const warship = player1.buildUnit( UnitType.Warship, - 0, game.ref(coastX + 1, 10), + {}, ); game.executeNextTick(); @@ -91,8 +91,10 @@ describe("Warship", () => { // we can obviously directly add it to the player) const tradeShip = player2.buildUnit( UnitType.TradeShip, - 0, game.ref(coastX + 1, 7), + { + dstPort: null, + }, ); expect(tradeShip.owner().id()).toBe(player2.id()); @@ -113,8 +115,10 @@ describe("Warship", () => { // we can obviously directly add it to the player) const tradeShip = player2.buildUnit( UnitType.TradeShip, - 0, game.ref(coastX + 1, 11), + { + dstPort: null, + }, ); expect(tradeShip.owner().id()).toBe(player2.id()); diff --git a/tests/util/Setup.ts b/tests/util/Setup.ts index b77f74112..8168870e4 100644 --- a/tests/util/Setup.ts +++ b/tests/util/Setup.ts @@ -42,7 +42,12 @@ export async function setup( instantBuild: false, ..._gameConfig, }; - const config = new TestConfig(serverConfig, gameConfig, new UserSettings()); + const config = new TestConfig( + serverConfig, + gameConfig, + new UserSettings(), + false, + ); // Create and return the game return createGame(humans, [], gameMap, miniGameMap, config);