mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-07-24 09:15:16 +00:00
feat: replay archived games, gamestate hash verification (#195)
create endpoint to load archived game. when joining game client first checks if the game is active, if not it requests the game archive from the server. the archive is sent to LocalServer to replay the game locally. Every 10 ticks a hash is stored on the archive, and during replay the LocalServer verifies this hash.
This commit is contained in:
+24
-10
@@ -14,7 +14,7 @@ import {
|
||||
ServerTurnMessageSchema,
|
||||
Turn,
|
||||
} from "../core/Schemas";
|
||||
import { CreateGameRecord } from "../core/Util";
|
||||
import { createGameRecord } from "../core/Util";
|
||||
import { ServerConfig } from "../core/configuration/Config";
|
||||
import { GameType } from "../core/game/Game";
|
||||
import { archive } from "./Archive";
|
||||
@@ -161,7 +161,7 @@ export class GameServer {
|
||||
client.lastPing = Date.now();
|
||||
}
|
||||
if (clientMsg.type == "hash") {
|
||||
client.hashes.set(clientMsg.tick, clientMsg.hash);
|
||||
client.hashes.set(clientMsg.turnNumber, clientMsg.hash);
|
||||
}
|
||||
if (clientMsg.type == "winner") {
|
||||
this.winner = clientMsg.winner;
|
||||
@@ -238,7 +238,12 @@ export class GameServer {
|
||||
),
|
||||
);
|
||||
} catch (error) {
|
||||
throw new Error(`error sending start message for game ${this.id}`);
|
||||
throw new Error(
|
||||
`error sending start message for game ${this.id}, ${error}`.substring(
|
||||
0,
|
||||
250,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -251,7 +256,7 @@ export class GameServer {
|
||||
this.turns.push(pastTurn);
|
||||
this.intents = [];
|
||||
|
||||
this.maybeSendDesync();
|
||||
this.handleSynchronization();
|
||||
|
||||
let msg = "";
|
||||
try {
|
||||
@@ -262,7 +267,12 @@ export class GameServer {
|
||||
}),
|
||||
);
|
||||
} catch (error) {
|
||||
console.log(`error sending message for game ${this.id}`);
|
||||
console.log(
|
||||
`error sending message for game ${this.id}, error ${error}`.substring(
|
||||
0,
|
||||
250,
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -294,7 +304,7 @@ export class GameServer {
|
||||
persistentID: client.persistentID,
|
||||
}));
|
||||
archive(
|
||||
CreateGameRecord(
|
||||
createGameRecord(
|
||||
this.id,
|
||||
this.gameConfig,
|
||||
playerRecords,
|
||||
@@ -405,8 +415,8 @@ export class GameServer {
|
||||
return this.gameConfig.gameType == GameType.Public;
|
||||
}
|
||||
|
||||
private maybeSendDesync() {
|
||||
if (this.activeClients.length <= 1) {
|
||||
private handleSynchronization() {
|
||||
if (this.activeClients.length < 1) {
|
||||
return;
|
||||
}
|
||||
if (this.turns.length % 10 == 0 && this.turns.length != 0) {
|
||||
@@ -415,7 +425,12 @@ export class GameServer {
|
||||
let { mostCommonHash, outOfSyncClients } =
|
||||
this.findOutOfSyncClients(lastHashTurn);
|
||||
|
||||
if (outOfSyncClients.length == 0) {
|
||||
this.turns[lastHashTurn].hash = mostCommonHash;
|
||||
}
|
||||
|
||||
if (
|
||||
outOfSyncClients.length > 0 &&
|
||||
outOfSyncClients.length >= Math.floor(this.activeClients.length / 2)
|
||||
) {
|
||||
// If half clients out of sync assume all are out of sync.
|
||||
@@ -430,8 +445,6 @@ export class GameServer {
|
||||
this.outOfSyncClients.add(oos.clientID);
|
||||
}
|
||||
}
|
||||
return;
|
||||
// TODO: renable this once desync issue fixed
|
||||
|
||||
const serverDesync = ServerDesyncSchema.safeParse({
|
||||
type: "desync",
|
||||
@@ -465,6 +478,7 @@ export class GameServer {
|
||||
for (const client of this.activeClients) {
|
||||
if (client.hashes.has(turnNumber)) {
|
||||
const clientHash = client.hashes.get(turnNumber)!;
|
||||
console.log(`clientHash: ${clientHash}`);
|
||||
counts.set(clientHash, (counts.get(clientHash) || 0) + 1);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user