mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-21 09:20:47 +00:00
Add pause button when replaying (#726)
## Description: This PR does two things: 1. Allow pausing on replay 2. As part of the refactoring, in singleplayer games, LocalServer now waits for the last turn to complete execution before sending the next turn. Previously, low end devices would sometimes fall behind getting the "playing the past" bug where commands were delayed. Now when a devices cannot keep up, the entire game slows down. ## Please complete the following: - [ ] I have added screenshots for all UI updates - [ ] I confirm I have thoroughly tested these changes and take full responsibility for any bugs introduced - [ ] 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: <DISCORD USERNAME> Co-authored-by: evan <openfrontio@gmail.com>
This commit is contained in:
@@ -112,6 +112,7 @@ export async function createClientGame(
|
||||
const config = await getConfig(
|
||||
lobbyConfig.gameStartInfo.config,
|
||||
userSettings,
|
||||
lobbyConfig.gameRecord != null,
|
||||
);
|
||||
let gameMap: TerrainMapData | null = null;
|
||||
|
||||
@@ -243,6 +244,7 @@ export class ClientGameRunner {
|
||||
this.stop(true);
|
||||
return;
|
||||
}
|
||||
this.transport.turnComplete();
|
||||
gu.updates[GameUpdateType.Hash].forEach((hu: HashUpdate) => {
|
||||
this.eventBus.emit(new SendHashEvent(hu.tick, hu.hash));
|
||||
});
|
||||
|
||||
+37
-13
@@ -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,
|
||||
|
||||
+12
-1
@@ -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({
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -136,6 +136,7 @@ export interface Config {
|
||||
defaultNukeSpeed(): number;
|
||||
nukeDeathFactor(humans: number, tilesOwned: number): number;
|
||||
structureMinDist(): number;
|
||||
isReplay(): boolean;
|
||||
}
|
||||
|
||||
export interface Theme {
|
||||
|
||||
@@ -12,15 +12,16 @@ export let cachedSC: ServerConfig = null;
|
||||
export async function getConfig(
|
||||
gameConfig: GameConfig,
|
||||
userSettings: UserSettings | null = null,
|
||||
isReplay: boolean = false,
|
||||
): Promise<Config> {
|
||||
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}`);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
+6
-1
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user