diff --git a/src/client/ClientGameRunner.ts b/src/client/ClientGameRunner.ts index ef977b898..97c9ab4a0 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; @@ -170,6 +171,8 @@ export class ClientGameRunner { private lastMessageTime: number = 0; private connectionCheckInterval: NodeJS.Timeout | null = null; + private turnsExecuted = 0; + constructor( private lobby: LobbyConfig, private eventBus: EventBus, @@ -218,6 +221,14 @@ export class ClientGameRunner { public start() { consolex.log("starting client game"); + setInterval(() => { + if (this.lobby.gameRecord) { + if (this.turnsExecuted == this.turnsSeen) { + this.transport.endTurn(); + } + } + }, 10); + this.isActive = true; this.lastMessageTime = Date.now(); setTimeout(() => { @@ -242,6 +253,8 @@ export class ClientGameRunner { this.stop(true); return; } + + this.turnsExecuted++; 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 8248a1603..0fdf0819b 100644 --- a/src/client/LocalServer.ts +++ b/src/client/LocalServer.ts @@ -16,7 +16,11 @@ import { LobbyConfig } from "./ClientGameRunner"; import { getPersistentIDFromCookie } from "./Main"; export class LocalServer { + // All turns from the game record on replay. + private allTurns: Turn[] = []; + private turns: Turn[] = []; + private intents: Intent[] = []; private startedAt: number; @@ -35,7 +39,7 @@ export class LocalServer { start() { this.startedAt = Date.now(); - if (!this.lobbyConfig.gameRecord) { + if (this.lobbyConfig.gameRecord == null) { this.endTurnIntervalID = setInterval( () => this.endTurn(), this.lobbyConfig.serverConfig.turnIntervalMs(), @@ -43,15 +47,15 @@ export class LocalServer { } this.clientConnect(); if (this.lobbyConfig.gameRecord) { - this.turns = decompressGameRecord(this.lobbyConfig.gameRecord).turns; - console.log(`loaded turns: ${JSON.stringify(this.turns)}`); + this.allTurns = decompressGameRecord(this.lobbyConfig.gameRecord).turns; + console.log(`loaded turns: ${JSON.stringify(this.allTurns)}`); } this.clientMessage( ServerStartGameMessageSchema.parse({ type: "start", gameID: this.lobbyConfig.gameStartInfo.gameID, gameStartInfo: this.lobbyConfig.gameStartInfo, - turns: this.turns, + turns: [], }), ); } @@ -90,7 +94,7 @@ export class LocalServer { return; } // If we are replaying a game then verify hash. - const archivedHash = this.turns[clientMsg.turnNumber].hash; + const archivedHash = this.allTurns[clientMsg.turnNumber].hash; if (!archivedHash) { console.warn( `no archived hash found for turn ${clientMsg.turnNumber}, client hash: ${clientMsg.hash}`, @@ -121,10 +125,13 @@ export class LocalServer { } } - private endTurn() { + public endTurn() { if (this.paused) { return; } + if (this.allTurns) { + this.intents = this.allTurns[this.turns.length].intents; + } const pastTurn: Turn = { turnNumber: this.turns.length, gameID: this.lobbyConfig.gameStartInfo.gameID, diff --git a/src/client/Transport.ts b/src/client/Transport.ts index a9ee93fe9..d0c19b473 100644 --- a/src/client/Transport.ts +++ b/src/client/Transport.ts @@ -313,6 +313,12 @@ export class Transport { this.connect(this.onconnect, this.onmessage); } + public endTurn() { + if (this.isLocal) { + this.localServer.endTurn(); + } + } + 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 1442dd814..805c2bf25 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 4eabc348d..7f7668b5e 100644 --- a/src/core/configuration/DefaultConfig.ts +++ b/src/core/configuration/DefaultConfig.ts @@ -151,7 +151,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 99e616cc1..62722ce8b 100644 --- a/src/core/configuration/DevConfig.ts +++ b/src/core/configuration/DevConfig.ts @@ -6,7 +6,7 @@ import { DefaultConfig, DefaultServerConfig } from "./DefaultConfig"; export class DevServerConfig extends DefaultServerConfig { r2Bucket(): string { - return "openfront-staging"; + return "openfront-prod"; } adminToken(): string { return "WARNING_DEV_ADMIN_KEY_DO_NOT_USE_IN_PRODUCTION"; @@ -44,8 +44,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/tests/util/Setup.ts b/tests/util/Setup.ts index 7f8b98a83..6ef402e23 100644 --- a/tests/util/Setup.ts +++ b/tests/util/Setup.ts @@ -32,7 +32,12 @@ export async function setup(mapName: string, _gameConfig: GameConfig = {}) { 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([], [], gameMap, miniGameMap, config);