mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-23 05:59:38 +00:00
created prestart message: add 2 second delay before starting public games to allow all players to connect
This commit is contained in:
@@ -21,7 +21,7 @@ import {
|
||||
WinUpdate,
|
||||
} from "../core/game/GameUpdates";
|
||||
import { GameView, PlayerView, UnitView } from "../core/game/GameView";
|
||||
import { loadTerrainMap } from "../core/game/TerrainMapLoader";
|
||||
import { loadTerrainMap, TerrainMapData } from "../core/game/TerrainMapLoader";
|
||||
import { UserSettings } from "../core/game/UserSettings";
|
||||
import { WorkerClient } from "../core/worker/WorkerClient";
|
||||
import { InputHandler, MouseMoveEvent, MouseUpEvent } from "./InputHandler";
|
||||
@@ -60,7 +60,8 @@ export interface LobbyConfig {
|
||||
|
||||
export function joinLobby(
|
||||
lobbyConfig: LobbyConfig,
|
||||
onjoin: () => void,
|
||||
onPrestart: () => void,
|
||||
onJoin: () => void,
|
||||
): () => void {
|
||||
const eventBus = new EventBus();
|
||||
initRemoteSender(eventBus);
|
||||
@@ -78,15 +79,28 @@ export function joinLobby(
|
||||
consolex.log(`Joined game lobby ${lobbyConfig.gameID}`);
|
||||
transport.joinGame(0);
|
||||
};
|
||||
let terrainLoad: Promise<TerrainMapData> | null = null;
|
||||
|
||||
const onmessage = (message: ServerMessage) => {
|
||||
if (message.type == "prestart") {
|
||||
consolex.log(`lobby: game prestarting: ${JSON.stringify(message)}`);
|
||||
terrainLoad = loadTerrainMap(message.gameMap);
|
||||
onPrestart();
|
||||
}
|
||||
if (message.type == "start") {
|
||||
// Trigger prestart for singleplayer games
|
||||
onPrestart();
|
||||
consolex.log(`lobby: game started: ${JSON.stringify(message)}`);
|
||||
onjoin();
|
||||
onJoin();
|
||||
// For multiplayer games, GameStartInfo is not known until game starts.
|
||||
lobbyConfig.gameStartInfo = message.gameStartInfo;
|
||||
createClientGame(lobbyConfig, eventBus, transport, userSettings).then(
|
||||
(r) => r.start(),
|
||||
);
|
||||
createClientGame(
|
||||
lobbyConfig,
|
||||
eventBus,
|
||||
transport,
|
||||
userSettings,
|
||||
terrainLoad,
|
||||
).then((r) => r.start());
|
||||
}
|
||||
};
|
||||
transport.connect(onconnect, onmessage);
|
||||
@@ -101,15 +115,19 @@ export async function createClientGame(
|
||||
eventBus: EventBus,
|
||||
transport: Transport,
|
||||
userSettings: UserSettings,
|
||||
terrainLoad: Promise<TerrainMapData> | null,
|
||||
): Promise<ClientGameRunner> {
|
||||
const config = await getConfig(
|
||||
lobbyConfig.gameStartInfo.config,
|
||||
userSettings,
|
||||
);
|
||||
let gameMap: TerrainMapData | null = null;
|
||||
|
||||
const gameMap = await loadTerrainMap(
|
||||
lobbyConfig.gameStartInfo.config.gameMap,
|
||||
);
|
||||
if (terrainLoad) {
|
||||
gameMap = await terrainLoad;
|
||||
} else {
|
||||
gameMap = await loadTerrainMap(lobbyConfig.gameStartInfo.config.gameMap);
|
||||
}
|
||||
const worker = new WorkerClient(
|
||||
lobbyConfig.gameStartInfo,
|
||||
lobbyConfig.clientID,
|
||||
|
||||
+9
-8
@@ -10,6 +10,7 @@ import "./DarkModeButton";
|
||||
import { DarkModeButton } from "./DarkModeButton";
|
||||
import "./FlagInput";
|
||||
import { FlagInput } from "./FlagInput";
|
||||
import { GameStartingModal } from "./GameStartingModal";
|
||||
import "./GoogleAdElement";
|
||||
import GoogleAdElement from "./GoogleAdElement";
|
||||
import { HelpModal } from "./HelpModal";
|
||||
@@ -25,7 +26,6 @@ import { UsernameInput } from "./UsernameInput";
|
||||
import { generateCryptoRandomUUID } from "./Utils";
|
||||
import "./components/baseComponents/Button";
|
||||
import "./components/baseComponents/Modal";
|
||||
import { GameStartingModal } from "./gameStartingModal";
|
||||
import "./styles.css";
|
||||
|
||||
export interface JoinLobbyEvent {
|
||||
@@ -177,6 +177,7 @@ class Client {
|
||||
this.gameStop();
|
||||
}
|
||||
const config = await getServerConfigFromClient();
|
||||
|
||||
this.gameStop = joinLobby(
|
||||
{
|
||||
gameID: lobby.gameID,
|
||||
@@ -192,18 +193,18 @@ class Client {
|
||||
gameRecord: lobby.gameRecord,
|
||||
},
|
||||
() => {
|
||||
this.joinModal.close();
|
||||
this.publicLobby.stop();
|
||||
document.querySelectorAll(".ad").forEach((ad) => {
|
||||
(ad as HTMLElement).style.display = "none";
|
||||
});
|
||||
|
||||
// show when the game loads
|
||||
const startingModal = document.querySelector(
|
||||
"game-starting-modal",
|
||||
) as GameStartingModal;
|
||||
startingModal instanceof GameStartingModal;
|
||||
startingModal.show();
|
||||
},
|
||||
() => {
|
||||
this.joinModal.close();
|
||||
this.publicLobby.stop();
|
||||
document.querySelectorAll(".ad").forEach((ad) => {
|
||||
(ad as HTMLElement).style.display = "none";
|
||||
});
|
||||
|
||||
if (event.detail.gameConfig?.gameType != GameType.Singleplayer) {
|
||||
window.history.pushState({}, "", `/join/${lobby.gameID}`);
|
||||
|
||||
@@ -2,8 +2,8 @@ import { consolex } from "../../core/Consolex";
|
||||
import { EventBus } from "../../core/EventBus";
|
||||
import { ClientID } from "../../core/Schemas";
|
||||
import { GameView } from "../../core/game/GameView";
|
||||
import { GameStartingModal } from "../GameStartingModal";
|
||||
import { RefreshGraphicsEvent as RedrawGraphicsEvent } from "../InputHandler";
|
||||
import { GameStartingModal } from "../gameStartingModal";
|
||||
import { TransformHandler } from "./TransformHandler";
|
||||
import { UIState } from "./UIState";
|
||||
import { BuildMenu } from "./layers/BuildMenu";
|
||||
|
||||
+10
-3
@@ -64,7 +64,8 @@ export type ServerMessage =
|
||||
| ServerSyncMessage
|
||||
| ServerStartGameMessage
|
||||
| ServerPingMessage
|
||||
| ServerDesyncMessage;
|
||||
| ServerDesyncMessage
|
||||
| ServerPrestartMessage;
|
||||
|
||||
export type ServerSyncMessage = z.infer<typeof ServerTurnMessageSchema>;
|
||||
export type ServerStartGameMessage = z.infer<
|
||||
@@ -72,7 +73,7 @@ export type ServerStartGameMessage = z.infer<
|
||||
>;
|
||||
export type ServerPingMessage = z.infer<typeof ServerPingMessageSchema>;
|
||||
export type ServerDesyncMessage = z.infer<typeof ServerDesyncSchema>;
|
||||
|
||||
export type ServerPrestartMessage = z.infer<typeof ServerPrestartMessageSchema>;
|
||||
export type ClientSendWinnerMessage = z.infer<typeof ClientSendWinnerSchema>;
|
||||
export type ClientPingMessage = z.infer<typeof ClientPingMessageSchema>;
|
||||
export type ClientIntentMessage = z.infer<typeof ClientIntentMessageSchema>;
|
||||
@@ -298,7 +299,7 @@ export const TurnSchema = z.object({
|
||||
// Server
|
||||
|
||||
const ServerBaseMessageSchema = z.object({
|
||||
type: z.enum(["turn", "ping", "start", "desync"]),
|
||||
type: z.enum(["turn", "ping", "prestart", "start", "desync"]),
|
||||
});
|
||||
|
||||
export const ServerTurnMessageSchema = ServerBaseMessageSchema.extend({
|
||||
@@ -310,6 +311,11 @@ export const ServerPingMessageSchema = ServerBaseMessageSchema.extend({
|
||||
type: z.literal("ping"),
|
||||
});
|
||||
|
||||
export const ServerPrestartMessageSchema = ServerBaseMessageSchema.extend({
|
||||
type: z.literal("prestart"),
|
||||
gameMap: z.nativeEnum(GameMapType),
|
||||
});
|
||||
|
||||
export const PlayerSchema = z.object({
|
||||
playerID: ID,
|
||||
clientID: ID,
|
||||
@@ -341,6 +347,7 @@ export const ServerDesyncSchema = ServerBaseMessageSchema.extend({
|
||||
|
||||
export const ServerMessageSchema = z.union([
|
||||
ServerTurnMessageSchema,
|
||||
ServerPrestartMessageSchema,
|
||||
ServerStartGameMessageSchema,
|
||||
ServerPingMessageSchema,
|
||||
ServerDesyncSchema,
|
||||
|
||||
@@ -3,10 +3,13 @@ import { GameMapType } from "./Game";
|
||||
import { GameMap, GameMapImpl } from "./GameMap";
|
||||
import { terrainMapFileLoader } from "./TerrainMapFileLoader";
|
||||
|
||||
const loadedMaps = new Map<
|
||||
GameMapType,
|
||||
{ nationMap: NationMap; gameMap: GameMap; miniGameMap: GameMap }
|
||||
>();
|
||||
export type TerrainMapData = {
|
||||
nationMap: NationMap;
|
||||
gameMap: GameMap;
|
||||
miniGameMap: GameMap;
|
||||
};
|
||||
|
||||
const loadedMaps = new Map<GameMapType, TerrainMapData>();
|
||||
|
||||
export interface NationMap {
|
||||
nations: Nation[];
|
||||
@@ -21,7 +24,7 @@ export interface Nation {
|
||||
|
||||
export async function loadTerrainMap(
|
||||
map: GameMapType,
|
||||
): Promise<{ nationMap: NationMap; gameMap: GameMap; miniGameMap: GameMap }> {
|
||||
): Promise<TerrainMapData> {
|
||||
if (loadedMaps.has(map)) {
|
||||
return loadedMaps.get(map);
|
||||
}
|
||||
|
||||
@@ -64,11 +64,16 @@ export class GameManager {
|
||||
const phase = game.phase();
|
||||
if (phase == GamePhase.Active) {
|
||||
if (!game.hasStarted()) {
|
||||
try {
|
||||
game.start();
|
||||
} catch (error) {
|
||||
this.log.error(`error starting game ${id}: ${error}`);
|
||||
}
|
||||
// Prestart tells clients to start loading the game.
|
||||
game.prestart();
|
||||
// Start game on delay to allow time for clients to connect.
|
||||
setTimeout(() => {
|
||||
try {
|
||||
game.start();
|
||||
} catch (error) {
|
||||
this.log.error(`error starting game ${id}: ${error}`);
|
||||
}
|
||||
}, 2000);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
Intent,
|
||||
PlayerRecord,
|
||||
ServerDesyncSchema,
|
||||
ServerPrestartMessageSchema,
|
||||
ServerStartGameMessageSchema,
|
||||
ServerTurnMessageSchema,
|
||||
Turn,
|
||||
@@ -55,6 +56,8 @@ export class GameServer {
|
||||
|
||||
private log: Logger;
|
||||
|
||||
private _hasPrestarted = false;
|
||||
|
||||
constructor(
|
||||
public readonly id: string,
|
||||
readonly log_: Logger,
|
||||
@@ -219,7 +222,38 @@ export class GameServer {
|
||||
}
|
||||
}
|
||||
|
||||
public prestart() {
|
||||
if (this.hasStarted()) {
|
||||
return;
|
||||
}
|
||||
this._hasPrestarted = true;
|
||||
|
||||
const prestartMsg = ServerPrestartMessageSchema.safeParse({
|
||||
type: "prestart",
|
||||
gameMap: this.gameConfig.gameMap,
|
||||
});
|
||||
|
||||
if (!prestartMsg.success) {
|
||||
console.error(
|
||||
`error creating prestart message for game ${this.id}, ${prestartMsg.error}`.substring(
|
||||
0,
|
||||
250,
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const msg = JSON.stringify(prestartMsg.data);
|
||||
this.activeClients.forEach((c) => {
|
||||
this.log.info(`${this.id}: sending prestart message to ${c.clientID}`);
|
||||
c.ws.send(msg);
|
||||
});
|
||||
}
|
||||
|
||||
public start() {
|
||||
if (this._hasStarted) {
|
||||
return;
|
||||
}
|
||||
this._hasStarted = true;
|
||||
this._startTime = Date.now();
|
||||
// Set last ping to start so we don't immediately stop the game
|
||||
@@ -424,7 +458,7 @@ export class GameServer {
|
||||
}
|
||||
|
||||
hasStarted(): boolean {
|
||||
return this._hasStarted;
|
||||
return this._hasStarted || this._hasPrestarted;
|
||||
}
|
||||
|
||||
public gameInfo(): GameInfo {
|
||||
@@ -446,7 +480,7 @@ export class GameServer {
|
||||
}
|
||||
|
||||
private handleSynchronization() {
|
||||
if (this.activeClients.length < 1) {
|
||||
if (this.activeClients.length <= 1) {
|
||||
return;
|
||||
}
|
||||
if (this.turns.length % 10 != 0 || this.turns.length < 10) {
|
||||
|
||||
@@ -154,6 +154,10 @@ export function startWorker() {
|
||||
log.warn(`cannot update public game ${game.id}, ip: ${clientIP}`);
|
||||
return res.status(400);
|
||||
}
|
||||
if (game.hasStarted()) {
|
||||
log.warn(`cannot update game ${game.id} after it has started`);
|
||||
return res.status(400);
|
||||
}
|
||||
game.updateGameConfig({
|
||||
gameMap: req.body.gameMap,
|
||||
difficulty: req.body.difficulty,
|
||||
|
||||
Reference in New Issue
Block a user