Merge branch 'main' into patterned-territory

This commit is contained in:
Aotumuri
2025-05-29 06:22:10 +09:00
committed by GitHub
119 changed files with 4787 additions and 1589 deletions
+6 -6
View File
@@ -1,6 +1,7 @@
import { S3 } from "@aws-sdk/client-s3";
import { getServerConfigFromServer } from "../core/configuration/ConfigLoader";
import { AnalyticsRecord, GameID, GameRecord } from "../core/Schemas";
import { replacer } from "../core/Util";
import { logger } from "./Logger";
const config = getServerConfigFromServer();
@@ -60,7 +61,7 @@ async function archiveAnalyticsToR2(gameRecord: GameRecord) {
await r2.putObject({
Bucket: bucket,
Key: `${analyticsFolder}/${analyticsKey}`,
Body: JSON.stringify(analyticsData),
Body: JSON.stringify(analyticsData, replacer),
ContentType: "application/json",
});
@@ -78,19 +79,18 @@ async function archiveAnalyticsToR2(gameRecord: GameRecord) {
async function archiveFullGameToR2(gameRecord: GameRecord) {
// Create a deep copy to avoid modifying the original
const recordCopy = JSON.parse(JSON.stringify(gameRecord));
const recordCopy = structuredClone(gameRecord);
// Players may see this so make sure to clear PII
recordCopy.players.forEach((p) => {
p.ip = "REDACTED";
recordCopy.info.players.forEach((p) => {
p.persistentID = "REDACTED";
});
try {
await r2.putObject({
Bucket: bucket,
Key: `${gameFolder}/${recordCopy.id}`,
Body: JSON.stringify(recordCopy),
Key: `${gameFolder}/${recordCopy.info.gameID}`,
Body: JSON.stringify(recordCopy, replacer),
ContentType: "application/json",
});
} catch (error) {
+1 -4
View File
@@ -1,16 +1,13 @@
import WebSocket from "ws";
import { TokenPayload } from "../core/ApiSchemas";
import { PlayerID, Tick } from "../core/game/Game";
import { Tick } from "../core/game/Game";
import { ClientID } from "../core/Schemas";
import { generateID } from "../core/Util";
export class Client {
public lastPing: number;
public hashes: Map<Tick, number> = new Map();
public readonly playerID: PlayerID = generateID();
constructor(
public readonly clientID: ClientID,
public readonly persistentID: string,
+49 -32
View File
@@ -56,6 +56,7 @@ export class GameServer {
private _hasPrestarted = false;
private kickedClients: Set<ClientID> = new Set();
private outOfSyncClients: Set<ClientID> = new Set();
constructor(
public readonly id: string,
@@ -200,7 +201,15 @@ export class GameServer {
client.hashes.set(clientMsg.turnNumber, clientMsg.hash);
}
if (clientMsg.type === "winner") {
if (
this.outOfSyncClients.has(client.clientID) ||
this.kickedClients.has(client.clientID) ||
this.winner !== null
) {
return;
}
this.winner = clientMsg;
this.archiveGame();
}
} catch (error) {
this.log.info(
@@ -291,7 +300,6 @@ export class GameServer {
gameID: this.id,
config: this.gameConfig,
players: this.activeClients.map((c) => ({
playerID: c.playerID,
username: c.username,
clientID: c.clientID,
pattern: c.pattern,
@@ -383,40 +391,16 @@ export class GameServer {
}
this.log.info(`ending game with ${this.turns.length} turns`);
try {
if (this.allClients.size > 0) {
const playerRecords: PlayerRecord[] = Array.from(
this.allClients.values(),
).map((client) => {
const stats = this.winner?.allPlayersStats[client.clientID];
if (stats === undefined) {
this.log.warn(
`Unable to find stats for clientID ${client.clientID}`,
);
}
return {
playerID: client.playerID,
clientID: client.clientID,
username: client.username,
persistentID: client.persistentID,
stats,
} satisfies PlayerRecord;
});
archive(
createGameRecord(
this.id,
this.gameStartInfo.config,
playerRecords,
this.turns,
this._startTime ?? 0,
Date.now(),
this.winner?.winner ?? null,
this.winner?.winnerType ?? null,
),
);
} else {
if (this.allClients.size === 0) {
this.log.info("no clients joined, not archiving game", {
gameID: this.id,
});
} else if (this.winner !== null) {
this.log.info("game already archived", {
gameID: this.id,
});
} else {
this.archiveGame();
}
} catch (error) {
let errorDetails;
@@ -550,6 +534,38 @@ export class GameServer {
}
}
private archiveGame() {
this.log.info("archiving game", {
gameID: this.id,
winner: this.winner?.winner,
});
const playerRecords: PlayerRecord[] = Array.from(
this.allClients.values(),
).map((client) => {
const stats = this.winner?.allPlayersStats[client.clientID];
if (stats === undefined) {
this.log.warn(`Unable to find stats for clientID ${client.clientID}`);
}
return {
clientID: client.clientID,
username: client.username,
persistentID: client.persistentID,
stats,
} satisfies PlayerRecord;
});
archive(
createGameRecord(
this.id,
this.gameStartInfo.config,
playerRecords,
this.turns,
this._startTime ?? 0,
Date.now(),
this.winner?.winner,
),
);
}
private handleSynchronization() {
if (this.activeClients.length <= 1) {
return;
@@ -587,6 +603,7 @@ export class GameServer {
const desyncMsg = JSON.stringify(serverDesync.data);
for (const c of outOfSyncClients) {
this.outOfSyncClients.add(c.clientID);
if (this.sentDesyncMessageClients.has(c.clientID)) {
continue;
}