mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-21 11:10:42 +00:00
start game via ws
This commit is contained in:
@@ -1011,21 +1011,12 @@ export class HostLobbyModal extends BaseModal {
|
||||
// If the modal closes as part of starting the game, do not leave the lobby
|
||||
this.leaveLobbyOnClose = false;
|
||||
|
||||
const config = await getRuntimeClientServerConfig();
|
||||
const response = await fetch(
|
||||
`${window.location.origin}/${config.workerPath(this.lobbyId)}/api/start_game/${this.lobbyId}`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
},
|
||||
this.dispatchEvent(
|
||||
new CustomEvent("start-game", {
|
||||
bubbles: true,
|
||||
composed: true,
|
||||
}),
|
||||
);
|
||||
|
||||
if (!response.ok) {
|
||||
this.leaveLobbyOnClose = true;
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
private kickPlayer(clientID: string) {
|
||||
|
||||
@@ -51,6 +51,7 @@ import { TerritoryPatternsModal } from "./TerritoryPatternsModal";
|
||||
import { TokenLoginModal } from "./TokenLoginModal";
|
||||
import {
|
||||
SendKickPlayerIntentEvent,
|
||||
SendStartGameEvent,
|
||||
SendUpdateGameConfigIntentEvent,
|
||||
} from "./Transport";
|
||||
import { UserSettingModal } from "./UserSettingModal";
|
||||
@@ -215,6 +216,7 @@ declare global {
|
||||
interface DocumentEventMap {
|
||||
"join-lobby": CustomEvent<JoinLobbyEvent>;
|
||||
"kick-player": CustomEvent;
|
||||
"start-game": CustomEvent;
|
||||
"join-changed": CustomEvent;
|
||||
"open-matchmaking": CustomEvent<undefined>;
|
||||
}
|
||||
@@ -311,6 +313,7 @@ class Client {
|
||||
document.addEventListener("join-lobby", this.handleJoinLobby.bind(this));
|
||||
document.addEventListener("leave-lobby", this.handleLeaveLobby.bind(this));
|
||||
document.addEventListener("kick-player", this.handleKickPlayer.bind(this));
|
||||
document.addEventListener("start-game", this.handleStartGame.bind(this));
|
||||
document.addEventListener(
|
||||
"update-game-config",
|
||||
this.handleUpdateGameConfig.bind(this),
|
||||
@@ -932,6 +935,12 @@ class Client {
|
||||
}
|
||||
}
|
||||
|
||||
private handleStartGame() {
|
||||
if (this.eventBus) {
|
||||
this.eventBus.emit(new SendStartGameEvent());
|
||||
}
|
||||
}
|
||||
|
||||
private handleUpdateGameConfig(event: CustomEvent) {
|
||||
const { config } = event.detail;
|
||||
|
||||
|
||||
@@ -173,6 +173,8 @@ export class SendUpdateGameConfigIntentEvent implements GameEvent {
|
||||
constructor(public readonly config: Partial<GameConfig>) {}
|
||||
}
|
||||
|
||||
export class SendStartGameEvent implements GameEvent {}
|
||||
|
||||
export class Transport {
|
||||
private socket: WebSocket | null = null;
|
||||
|
||||
@@ -262,6 +264,8 @@ export class Transport {
|
||||
this.eventBus.on(SendUpdateGameConfigIntentEvent, (e) =>
|
||||
this.onSendUpdateGameConfigIntent(e),
|
||||
);
|
||||
|
||||
this.eventBus.on(SendStartGameEvent, () => this.onSendStartGame());
|
||||
}
|
||||
|
||||
private startPing() {
|
||||
@@ -644,6 +648,10 @@ export class Transport {
|
||||
});
|
||||
}
|
||||
|
||||
private onSendStartGame() {
|
||||
this.sendIntent({ type: "start_game" });
|
||||
}
|
||||
|
||||
private sendIntent(intent: Intent) {
|
||||
if (this.isLocal || this.socket?.readyState === WebSocket.OPEN) {
|
||||
const msg = {
|
||||
|
||||
+8
-1
@@ -50,7 +50,8 @@ export type Intent =
|
||||
| DeleteUnitIntent
|
||||
| KickPlayerIntent
|
||||
| TogglePauseIntent
|
||||
| UpdateGameConfigIntent;
|
||||
| UpdateGameConfigIntent
|
||||
| StartGameIntent;
|
||||
|
||||
export type AttackIntent = z.infer<typeof AttackIntentSchema>;
|
||||
export type CancelAttackIntent = z.infer<typeof CancelAttackIntentSchema>;
|
||||
@@ -84,6 +85,7 @@ export type TogglePauseIntent = z.infer<typeof TogglePauseIntentSchema>;
|
||||
export type UpdateGameConfigIntent = z.infer<
|
||||
typeof UpdateGameConfigIntentSchema
|
||||
>;
|
||||
export type StartGameIntent = z.infer<typeof StartGameIntentSchema>;
|
||||
|
||||
export type Turn = z.infer<typeof TurnSchema>;
|
||||
export type GameConfig = z.infer<typeof GameConfigSchema>;
|
||||
@@ -453,6 +455,10 @@ export const UpdateGameConfigIntentSchema = z.object({
|
||||
config: GameConfigSchema.partial(),
|
||||
});
|
||||
|
||||
export const StartGameIntentSchema = z.object({
|
||||
type: z.literal("start_game"),
|
||||
});
|
||||
|
||||
const IntentSchema = z.discriminatedUnion("type", [
|
||||
AttackIntentSchema,
|
||||
CancelAttackIntentSchema,
|
||||
@@ -478,6 +484,7 @@ const IntentSchema = z.discriminatedUnion("type", [
|
||||
KickPlayerIntentSchema,
|
||||
TogglePauseIntentSchema,
|
||||
UpdateGameConfigIntentSchema,
|
||||
StartGameIntentSchema,
|
||||
]);
|
||||
|
||||
// StampedIntent = Intent with server-stamped clientID (used in turns and execution)
|
||||
|
||||
@@ -487,6 +487,35 @@ export class GameServer {
|
||||
this.updateGameConfig(stampedIntent.config);
|
||||
return;
|
||||
}
|
||||
case "start_game": {
|
||||
if (client.clientID !== this.lobbyCreatorID) {
|
||||
this.log.warn(`Only lobby creator can start game`, {
|
||||
clientID: client.clientID,
|
||||
creatorID: this.lobbyCreatorID,
|
||||
gameID: this.id,
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (this.isPublic()) {
|
||||
this.log.warn(`Cannot start public game via WebSocket`, {
|
||||
gameID: this.id,
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (this.hasStarted()) {
|
||||
this.log.warn(`Cannot start game that has already started`, {
|
||||
gameID: this.id,
|
||||
clientID: client.clientID,
|
||||
});
|
||||
return;
|
||||
}
|
||||
this.log.info(`Lobby creator starting game via WebSocket`, {
|
||||
creatorID: client.clientID,
|
||||
gameID: this.id,
|
||||
});
|
||||
this.start();
|
||||
return;
|
||||
}
|
||||
case "toggle_pause": {
|
||||
// Only lobby creator can pause/resume
|
||||
if (client.clientID !== this.lobbyCreatorID) {
|
||||
|
||||
@@ -210,25 +210,6 @@ export async function startWorker() {
|
||||
res.json(game.gameInfo());
|
||||
});
|
||||
|
||||
// Add other endpoints from your original server
|
||||
app.post("/api/start_game/:id", async (req, res) => {
|
||||
log.info(`starting private lobby with id ${req.params.id}`);
|
||||
const game = gm.game(req.params.id);
|
||||
if (!game) {
|
||||
return;
|
||||
}
|
||||
if (game.isPublic()) {
|
||||
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
|
||||
const clientIP = req.ip || req.socket.remoteAddress || "unknown";
|
||||
log.info(
|
||||
`cannot start public game ${game.id}, game is public, ip: ${ipAnonymize(clientIP)}`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
game.start();
|
||||
res.status(200).json({ success: true });
|
||||
});
|
||||
|
||||
app.get("/api/game/:id/exists", async (req, res) => {
|
||||
const lobbyId = req.params.id;
|
||||
res.json({
|
||||
|
||||
Reference in New Issue
Block a user