This commit is contained in:
Aotumuri
2026-05-16 22:01:31 +08:00
parent b8137927a6
commit 72336bcacb
13 changed files with 417 additions and 24 deletions
+10 -1
View File
@@ -27,7 +27,8 @@
"click_to_copy": "Click to copy",
"enabled": "Enabled",
"disabled": "Disabled",
"map_default": "Map default"
"map_default": "Map default",
"show_less": "Show Less"
},
"main": {
"title": "OpenFront (ALPHA)",
@@ -537,6 +538,12 @@
"error": "An error occurred. Please try again or contact support.",
"joined_waiting": "Lobby joined! Waiting for host to start...",
"version_mismatch": "This game was created with a different version. Cannot join.",
"listed_private_games": "Listed Private Games",
"no_listed_private_games": "No listed private games available.",
"show_all_listed_private_games": "Show More Lobbies (+{count})",
"show_all_settings": "Show All Settings (+{count})",
"show_fewer_settings": "Show Fewer Settings",
"join_by_lobby_id": "Join by Lobby ID",
"disabled_units": "Disabled Units",
"game_length": "Game length",
"pvp_immunity": "PVP immunity duration",
@@ -619,6 +626,8 @@
"starting_gold": "Starting Gold (Millions)",
"starting_gold_placeholder": "5",
"host_cheats": "Host Cheats",
"listed_private_game": "Listed Private Game",
"listed_private_game_desc": "Show this private lobby in the Join Lobby menu.",
"leave_confirmation": "Are you sure you want to leave the lobby?"
},
"team_colors": {
+28
View File
@@ -10,6 +10,7 @@ import {
GameMode,
UnitType,
} from "../core/game/Game";
import { hasListedPrivateGameFlare } from "../core/ListedPrivateGame";
import {
ClientInfo,
GameConfig,
@@ -19,6 +20,7 @@ import {
isValidGameID,
} from "../core/Schemas";
import { generateID } from "../core/Util";
import { getUserMe } from "./Api";
import { getPlayToken } from "./Auth";
import "./components/baseComponents/Modal";
import { BaseModal } from "./components/BaseModal";
@@ -85,6 +87,8 @@ export class HostLobbyModal extends BaseModal {
@state() private hostCheatGoldMultiplierValue: number | undefined = undefined;
@state() private hostCheatStartingGold: boolean = false;
@state() private hostCheatStartingGoldValue: number | undefined = undefined;
@state() private canListPrivateGame: boolean = false;
@state() private listedPrivateGame: boolean = false;
@state() private lobbyCreatorClientID: string = "";
@property({ attribute: false }) eventBus: EventBus | null = null;
@@ -352,6 +356,12 @@ export class HostLobbyModal extends BaseModal {
labelKey: "host_modal.host_cheats",
checked: this.hostCheatsEnabled,
},
{
labelKey: "host_modal.listed_private_game",
descriptionKey: "host_modal.listed_private_game_desc",
checked: this.listedPrivateGame,
hidden: !this.canListPrivateGame,
},
],
inputCards,
},
@@ -419,6 +429,7 @@ export class HostLobbyModal extends BaseModal {
protected onOpen(): void {
this.startLobbyUpdates();
void this.loadListedPrivateGameAccess();
this.lobbyId = generateID();
// Note: clientID will be assigned by server when we join the lobby
// lobbyCreatorClientID stays empty until then
@@ -537,6 +548,7 @@ export class HostLobbyModal extends BaseModal {
this.hostCheatGoldMultiplierValue = undefined;
this.hostCheatStartingGold = false;
this.hostCheatStartingGoldValue = undefined;
this.listedPrivateGame = false;
this.leaveLobbyOnClose = true;
}
@@ -625,6 +637,10 @@ export class HostLobbyModal extends BaseModal {
this.hostCheatsEnabled = checked;
this.putGameConfig();
break;
case "host_modal.listed_private_game":
this.listedPrivateGame = this.canListPrivateGame && checked;
this.putGameConfig();
break;
default:
break;
}
@@ -944,6 +960,8 @@ export class HostLobbyModal extends BaseModal {
instantBuild: this.instantBuild,
randomSpawn: this.randomSpawn,
gameMode: this.gameMode,
listedPrivateGame:
this.canListPrivateGame && this.listedPrivateGame,
disabledUnits: this.disabledUnits,
spawnImmunityDuration: this.spawnImmunity
? spawnImmunityTicks
@@ -1030,6 +1048,16 @@ export class HostLobbyModal extends BaseModal {
// Leave existing values unchanged so the UI stays consistent
}
}
private async loadListedPrivateGameAccess() {
const userMe = await getUserMe();
const flares = userMe === false ? [] : (userMe.player.flares ?? []);
this.canListPrivateGame = hasListedPrivateGameFlare(flares);
if (!this.canListPrivateGame && this.listedPrivateGame) {
this.listedPrivateGame = false;
this.putGameConfig();
}
}
}
async function createLobby(gameID: string): Promise<GameInfo> {
+251 -1
View File
@@ -11,6 +11,7 @@ import {
} from "../client/Utils";
import { assetUrl } from "../core/AssetUrls";
import { EventBus } from "../core/EventBus";
import { LISTED_PRIVATE_GAME_TYPE } from "../core/ListedPrivateGame";
import {
ClientInfo,
GAME_ID_REGEX,
@@ -19,6 +20,7 @@ import {
GameRecordSchema,
LobbyInfoEvent,
PublicGameInfo,
PublicGames,
} from "../core/Schemas";
import {
Difficulty,
@@ -55,6 +57,9 @@ export class JoinLobbyModal extends BaseModal {
@state() private serverTimeOffset: number = 0;
@state() private isConnecting: boolean = true;
@state() private lobbyCreatorClientID: string | null = null;
@state() private publicGames: PublicGames | null = null;
@state() private listedPrivateGamesExpanded: boolean = false;
@state() private expandedListedPrivateGameSettings: Set<string> = new Set();
private leaveLobbyOnClose = true;
private countdownTimerId: number | null = null;
@@ -77,6 +82,11 @@ export class JoinLobbyModal extends BaseModal {
});
};
private readonly handlePublicLobbiesUpdate = (event: Event) => {
const customEvent = event as CustomEvent<{ payload: PublicGames }>;
this.publicGames = customEvent.detail.payload;
};
protected renderHeaderSlot() {
if (!this.currentLobbyId) {
return modalHeader({
@@ -217,8 +227,25 @@ export class JoinLobbyModal extends BaseModal {
}
private renderJoinForm() {
const listedPrivateGames =
this.publicGames?.games?.[LISTED_PRIVATE_GAME_TYPE] ?? [];
const visibleListedPrivateGames = this.listedPrivateGamesExpanded
? listedPrivateGames
: listedPrivateGames.slice(0, 3);
const hiddenListedPrivateGameCount =
listedPrivateGames.length - visibleListedPrivateGames.length;
return html`
<form @submit=${this.joinLobbyFromInput} class="custom-scrollbar p-6 space-y-4 mr-1">
<form
@submit=${this.joinLobbyFromInput}
class="custom-scrollbar p-6 space-y-4 mr-1"
>
<div class="pt-2 border-t border-white/10">
<h3
class="mb-3 text-sm font-bold text-white uppercase tracking-wider"
>
${translateText("private_lobby.join_by_lobby_id")}
</h3>
<div class="flex flex-col gap-3">
<div class="flex gap-2">
<input
@@ -256,10 +283,219 @@ export class JoinLobbyModal extends BaseModal {
></o-button>
</div>
</div>
<div class="space-y-3 pt-2 border-t border-white/10">
<div class="flex items-center justify-between gap-3">
<h3 class="text-sm font-bold text-white uppercase tracking-wider">
${translateText("private_lobby.listed_private_games")}
</h3>
${listedPrivateGames.length > 3
? html`
<button
type="button"
class="text-xs font-bold uppercase tracking-wider text-malibu-blue hover:text-aquarius transition-colors"
@click=${this.toggleListedPrivateGamesExpanded}
>
${this.listedPrivateGamesExpanded
? translateText("common.show_less")
: translateText(
"private_lobby.show_all_listed_private_games",
{
count: String(hiddenListedPrivateGameCount),
},
)}
</button>
`
: ""}
</div>
${listedPrivateGames.length === 0
? html`
<div
class="px-4 py-3 rounded-xl border border-white/10 bg-white/5 text-sm text-white/50"
>
${translateText("private_lobby.no_listed_private_games")}
</div>
`
: html`
<div class="grid grid-cols-1 gap-2">
${visibleListedPrivateGames.map((lobby) =>
this.renderListedPrivateGame(lobby),
)}
</div>
`}
</div>
</form>
`;
}
private renderListedPrivateGame(lobby: PublicGameInfo): TemplateResult {
const mapName = getMapName(lobby.gameConfig?.gameMap);
const settings = this.listedPrivateGameSettings(lobby.gameConfig);
const settingsExpanded = this.expandedListedPrivateGameSettings.has(
lobby.gameID,
);
const visibleSettings = settingsExpanded ? settings : settings.slice(0, 3);
const hiddenSettingsCount = settings.length - visibleSettings.length;
const mode =
lobby.gameConfig?.gameMode === GameMode.Team
? translateText("game_mode.teams")
: translateText("game_mode.ffa");
return html`
<div
class="w-full min-w-0 px-3 py-3 rounded-xl border border-white/10 bg-white/5 text-left"
>
<div class="flex flex-col gap-3">
<div class="min-w-0 space-y-1">
<div class="text-sm font-bold text-white truncate">
${mapName ?? lobby.gameConfig?.gameMap ?? lobby.gameID}
</div>
<div class="text-xs text-white/55 uppercase tracking-wider">
${mode}
</div>
</div>
${settings.length > 0
? html`
<div class="grid grid-cols-1 gap-1">
${visibleSettings.map(
(setting) => html`
<div
class="w-full min-w-0 rounded bg-black/25 border border-white/10 px-2 py-1 text-[11px] font-bold uppercase tracking-wider text-white/70 truncate"
>
${setting}
</div>
`,
)}
</div>
${settings.length > 3
? html`
<button
type="button"
class="self-start text-[11px] font-bold uppercase tracking-wider text-malibu-blue hover:text-aquarius transition-colors"
@click=${() =>
this.toggleListedPrivateGameSettings(lobby.gameID)}
>
${settingsExpanded
? translateText("private_lobby.show_fewer_settings")
: translateText("private_lobby.show_all_settings", {
count: String(hiddenSettingsCount),
})}
</button>
`
: ""}
`
: ""}
<div class="flex items-center justify-between gap-2">
<div class="shrink-0 text-xs font-bold text-white/80">
${lobby.numClients}/${lobby.gameConfig?.maxPlayers ?? "-"}
</div>
<button
type="button"
class="px-3 py-2 rounded-lg bg-malibu-blue hover:bg-aquarius text-white text-xs font-bold uppercase tracking-wider transition-colors"
@click=${() => this.joinListedPrivateGame(lobby)}
>
${translateText("private_lobby.join_lobby")}
</button>
</div>
</div>
</div>
`;
}
private listedPrivateGameSettings(config: GameConfig | undefined): string[] {
if (!config) return [];
const settings: string[] = [];
const isTeam = config.gameMode === GameMode.Team;
const isCompact =
config.gameMapSize === GameMapSize.Compact ||
config.publicGameModifiers?.isCompact;
if (isCompact) settings.push(translateText("host_modal.compact_map"));
if (config.difficulty !== Difficulty.Easy) {
settings.push(
translateText(`difficulty.${config.difficulty.toLowerCase()}`),
);
}
if (config.infiniteTroops)
settings.push(translateText("host_modal.infinite_troops"));
if (config.infiniteGold)
settings.push(translateText("host_modal.infinite_gold"));
if (config.instantBuild)
settings.push(translateText("host_modal.instant_build"));
if (config.randomSpawn)
settings.push(translateText("host_modal.random_spawn"));
if (config.maxTimerValue)
settings.push(
`${translateText("private_lobby.game_length")}: ${config.maxTimerValue} min`,
);
if (config.spawnImmunityDuration) {
const seconds = Math.round(config.spawnImmunityDuration / 10);
settings.push(
`${translateText("private_lobby.pvp_immunity")}: ${renderDuration(seconds)}`,
);
}
if (config.startingGold) {
const millions = parseFloat(
(config.startingGold / 1_000_000).toPrecision(12),
);
settings.push(
`${translateText("private_lobby.starting_gold")}: ${millions}M`,
);
}
if (config.goldMultiplier) {
settings.push(
`${translateText("host_modal.gold_multiplier")}: x${config.goldMultiplier}`,
);
}
if (config.disableAlliances) {
settings.push(translateText("host_modal.disable_alliances"));
}
if (config.waterNukes) {
settings.push(translateText("host_modal.water_nukes"));
}
if ((isTeam && !config.donateGold) || (!isTeam && config.donateGold)) {
settings.push(translateText("host_modal.donate_gold"));
}
if ((isTeam && !config.donateTroops) || (!isTeam && config.donateTroops)) {
settings.push(translateText("host_modal.donate_troops"));
}
if (config.disabledUnits && config.disabledUnits.length > 0) {
settings.push(translateText("private_lobby.disabled_units"));
}
if (config.hostCheats) {
settings.push(translateText("private_lobby.host_cheats"));
}
return settings;
}
private toggleListedPrivateGameSettings(gameID: string) {
const expanded = new Set(this.expandedListedPrivateGameSettings);
if (expanded.has(gameID)) {
expanded.delete(gameID);
} else {
expanded.add(gameID);
}
this.expandedListedPrivateGameSettings = expanded;
}
private toggleListedPrivateGamesExpanded = () => {
this.listedPrivateGamesExpanded = !this.listedPrivateGamesExpanded;
};
private joinListedPrivateGame(lobby: PublicGameInfo) {
this.dispatchEvent(
new CustomEvent("join-lobby", {
detail: {
gameID: lobby.gameID,
source: "listed-private",
publicLobbyInfo: lobby,
} as JoinLobbyEvent,
bubbles: true,
composed: true,
}),
);
}
protected onOpen(args?: Record<string, unknown>): void {
const lobbyId = typeof args?.lobbyId === "string" ? args.lobbyId : "";
const lobbyInfo = args?.lobbyInfo as GameInfo | PublicGameInfo | undefined;
@@ -374,14 +610,28 @@ export class JoinLobbyModal extends BaseModal {
this.lobbyCreatorClientID = null;
this.isConnecting = true;
this.leaveLobbyOnClose = true;
this.listedPrivateGamesExpanded = false;
this.expandedListedPrivateGameSettings = new Set();
}
disconnectedCallback() {
this.clearCountdownTimer();
this.stopLobbyUpdates();
document.removeEventListener(
"public-lobbies-update",
this.handlePublicLobbiesUpdate,
);
super.disconnectedCallback();
}
connectedCallback() {
super.connectedCallback();
document.addEventListener(
"public-lobbies-update",
this.handlePublicLobbiesUpdate,
);
}
public closeAndLeave() {
this.leaveLobby();
try {
+8 -2
View File
@@ -240,7 +240,13 @@ export interface JoinLobbyEvent {
gameStartInfo?: GameStartInfo;
// GameRecord exists when replaying an archived game.
gameRecord?: GameRecord;
source?: "public" | "private" | "host" | "matchmaking" | "singleplayer";
source?:
| "public"
| "listed-private"
| "private"
| "host"
| "matchmaking"
| "singleplayer";
publicLobbyInfo?: GameInfo | PublicGameInfo;
}
@@ -824,7 +830,7 @@ class Client {
this.lobbyHandle.stop(true);
document.body.classList.remove("in-game");
}
if (lobby.source === "public") {
if (lobby.source === "public" || lobby.source === "listed-private") {
this.joinModal?.open({
lobbyId: lobby.gameID,
lobbyInfo: lobby.publicLobbyInfo,
@@ -163,6 +163,7 @@ function renderSectionHeader(
export interface ToggleOptionConfig {
labelKey: string;
descriptionKey?: string;
checked: boolean;
hidden?: boolean;
}
@@ -284,6 +285,27 @@ export class GameConfigSettings extends LitElement {
private renderOptionToggle(toggle: ToggleOptionConfig): TemplateResult {
if (toggle.hidden) return html``;
if (toggle.descriptionKey) {
return html`
<button
class="${cardClass(toggle.checked, "p-4 text-center")}"
@click=${() => this.handleOptionToggle(toggle)}
aria-pressed=${toggle.checked}
>
<span class="${CARD_LABEL_CLASS} ${stateTextClass(toggle.checked)}">
${translateText(toggle.labelKey)}
</span>
<span
class="block mt-2 text-[11px] normal-case font-medium tracking-normal leading-snug ${toggle.checked
? "text-white/80"
: "text-white/45"}"
>
${translateText(toggle.descriptionKey)}
</span>
</button>
`;
}
return renderTextCardButton(
translateText(toggle.labelKey),
toggle.checked,
+7
View File
@@ -0,0 +1,7 @@
export const LISTED_PRIVATE_GAME_FLARE = "game:*";
export const LISTED_PRIVATE_GAME_TYPE = "listed-private";
export function hasListedPrivateGameFlare(flares: readonly string[]): boolean {
return true;
// return flares.includes(LISTED_PRIVATE_GAME_FLARE);
}
+8 -1
View File
@@ -20,6 +20,7 @@ import {
Trios,
UnitType,
} from "./game/Game";
import { LISTED_PRIVATE_GAME_TYPE } from "./ListedPrivateGame";
import { PlayerStatsSchema } from "./StatsSchemas";
import { flattenedEmojiTable } from "./Util";
@@ -139,7 +140,12 @@ export type PublicGames = z.infer<typeof PublicGamesSchema>;
export type PublicGameInfo = z.infer<typeof PublicGameInfoSchema>;
export type PublicGameType = z.infer<typeof PublicGameTypeSchema>;
export const PublicGameTypeSchema = z.enum(["ffa", "team", "special"]);
export const PublicGameTypeSchema = z.enum([
"ffa",
"team",
"special",
LISTED_PRIVATE_GAME_TYPE,
] as const);
export const UsernameSchema = z
.string()
@@ -221,6 +227,7 @@ export const GameConfigSchema = z.object({
donateTroops: z.boolean(), // Configures donations to humans only
gameType: z.enum(GameType),
gameMode: z.enum(GameMode),
listedPrivateGame: z.boolean().optional(),
rankedType: z.enum(RankedType).optional(), // Only set for ranked games.
gameMapSize: z.enum(GameMapSize),
publicGameModifiers: z
+9
View File
@@ -28,6 +28,15 @@ export class GameManager {
);
}
public listedPrivateLobbies(): GameServer[] {
return Array.from(this.games.values()).filter(
(g) =>
g.phase() === GamePhase.Lobby &&
!g.isPublic() &&
g.gameConfig.listedPrivateGame === true,
);
}
joinClient(
client: Client,
gameID: GameID,
+18
View File
@@ -5,6 +5,7 @@ import { z } from "zod";
import { isAdminRole } from "../core/ApiSchemas";
import { GameEnv } from "../core/configuration/Config";
import { GameType } from "../core/game/Game";
import { hasListedPrivateGameFlare } from "../core/ListedPrivateGame";
import {
ClientID,
ClientMessageSchema,
@@ -177,6 +178,9 @@ export class GameServer {
if (gameConfig.waterNukes !== undefined) {
this.gameConfig.waterNukes = gameConfig.waterNukes ?? undefined;
}
if (gameConfig.listedPrivateGame !== undefined) {
this.gameConfig.listedPrivateGame = gameConfig.listedPrivateGame;
}
this.gameConfig.hostCheats = gameConfig.hostCheats;
}
@@ -473,6 +477,20 @@ export class GameServer {
return;
}
if (
stampedIntent.config.listedPrivateGame === true &&
!hasListedPrivateGameFlare(client.flares ?? [])
) {
this.log.warn(
`Client without required flare attempted to list private game`,
{
gameID: this.id,
clientID: client.clientID,
},
);
return;
}
this.log.info(
`Lobby creator updated game config via WebSocket`,
{
+7 -5
View File
@@ -189,14 +189,16 @@ const MUTUALLY_EXCLUSIVE_MODIFIERS: [ModifierKey, ModifierKey][] = [
["isNukesDisabled", "isWaterNukes"],
];
type ScheduledPublicGameType = Exclude<PublicGameType, "listed-private">;
export class MapPlaylist {
private playlists: Record<PublicGameType, GameMapType[]> = {
private playlists: Record<ScheduledPublicGameType, GameMapType[]> = {
ffa: [],
special: [],
team: [],
};
public async gameConfig(type: PublicGameType): Promise<GameConfig> {
public async gameConfig(type: ScheduledPublicGameType): Promise<GameConfig> {
if (type === "special") {
return this.getSpecialConfig();
}
@@ -488,7 +490,7 @@ export class MapPlaylist {
} satisfies GameConfig;
}
private getNextMap(type: PublicGameType): GameMapType {
private getNextMap(type: ScheduledPublicGameType): GameMapType {
const playlist = this.playlists[type];
if (playlist.length === 0) {
playlist.push(...this.generateNewPlaylist(type));
@@ -496,7 +498,7 @@ export class MapPlaylist {
return playlist.shift()!;
}
private generateNewPlaylist(type: PublicGameType): GameMapType[] {
private generateNewPlaylist(type: ScheduledPublicGameType): GameMapType[] {
const maps = this.buildMapsList(type);
const rand = new PseudoRandom(Date.now());
const playlist: GameMapType[] = [];
@@ -545,7 +547,7 @@ export class MapPlaylist {
return false;
}
private buildMapsList(type: PublicGameType): GameMapType[] {
private buildMapsList(type: ScheduledPublicGameType): GameMapType[] {
const maps: GameMapType[] = [];
(Object.keys(GameMapType) as GameMapName[]).forEach((key) => {
const map = GameMapType[key];
+6 -1
View File
@@ -1,5 +1,6 @@
import { Worker } from "cluster";
import winston from "winston";
import { LISTED_PRIVATE_GAME_TYPE } from "../core/ListedPrivateGame";
import { PublicGameInfo, PublicGameType } from "../core/Schemas";
import { generateID } from "../core/Util";
import {
@@ -85,9 +86,13 @@ export class MasterLobbyService {
ffa: [],
team: [],
special: [],
[LISTED_PRIVATE_GAME_TYPE]: [],
};
for (const lobby of lobbies) {
if (!result[lobby.publicGameType]) {
continue;
}
result[lobby.publicGameType].push(lobby);
}
@@ -131,7 +136,7 @@ export class MasterLobbyService {
private async maybeScheduleLobby() {
const lobbiesByType = this.getAllLobbies();
for (const type of Object.keys(lobbiesByType) as PublicGameType[]) {
for (const type of ["ffa", "team", "special"] as const) {
const lobbies = lobbiesByType[type];
// Always ensure the next lobby has a timer, even if we already have 2+
+22 -2
View File
@@ -9,6 +9,7 @@ import { WebSocket, WebSocketServer } from "ws";
import { z } from "zod";
import { GameEnv } from "../core/configuration/Config";
import { GameType } from "../core/game/Game";
import { hasListedPrivateGameFlare } from "../core/ListedPrivateGame";
import {
ClientMessageSchema,
GameID,
@@ -145,10 +146,11 @@ export async function startWorker() {
// Extract persistentID from Authorization header token
// Never accept persistentID directly from client
let creatorPersistentID: string | undefined;
let creatorToken: string | undefined;
const authHeader = req.headers.authorization;
if (authHeader?.startsWith("Bearer ")) {
const token = authHeader.substring("Bearer ".length);
const result = await verifyClientToken(token);
creatorToken = authHeader.substring("Bearer ".length);
const result = await verifyClientToken(creatorToken);
if (result.type === "success") {
creatorPersistentID = result.persistentId;
} else {
@@ -186,6 +188,24 @@ export async function startWorker() {
return res.status(401).send("Unauthorized");
}
if (gc?.listedPrivateGame === true) {
if (creatorToken === undefined) {
return res.status(401).json({ error: "Unauthorized" });
}
const userMe = await getUserMe(creatorToken);
if (userMe.type === "error") {
log.warn(`cannot verify listed private game flare: ${userMe.message}`);
return res.status(401).json({ error: "Unauthorized" });
}
const creatorFlares = userMe.response.player.flares ?? [];
if (!hasListedPrivateGameFlare(creatorFlares)) {
log.warn(
`Forbidden: player without game:* flare attempted to create listed private game`,
);
return res.status(403).json({ error: "Forbidden" });
}
}
// Double-check this worker should host this game
const expectedWorkerId = ServerEnv.workerIndex(id);
if (expectedWorkerId !== workerId) {
+17 -7
View File
@@ -1,6 +1,7 @@
import http from "http";
import { WebSocket, WebSocketServer } from "ws";
import { PublicGameInfo, PublicGames } from "../core/Schemas";
import { LISTED_PRIVATE_GAME_TYPE } from "../core/ListedPrivateGame";
import { GameInfo, PublicGameInfo, PublicGames } from "../core/Schemas";
import { GameManager } from "./GameManager";
import {
MasterMessageSchema,
@@ -83,18 +84,27 @@ export class WorkerLobbyService {
}
private sendMyLobbiesToMaster() {
const lobbies = this.gm
.publicLobbies()
.map((g) => g.gameInfo())
.map((gi) => {
const toLobbyInfo = (
gi: GameInfo,
publicGameType: PublicGameInfo["publicGameType"],
) => {
return {
gameID: gi.gameID,
numClients: gi.clients?.length ?? 0,
startsAt: gi.startsAt,
gameConfig: gi.gameConfig,
publicGameType: gi.publicGameType!,
publicGameType,
} satisfies PublicGameInfo;
});
};
const publicLobbies = this.gm
.publicLobbies()
.map((g) => g.gameInfo())
.map((gi) => toLobbyInfo(gi, gi.publicGameType!));
const listedPrivateLobbies = this.gm
.listedPrivateLobbies()
.map((g) => g.gameInfo())
.map((gi) => toLobbyInfo(gi, LISTED_PRIVATE_GAME_TYPE));
const lobbies = [...publicLobbies, ...listedPrivateLobbies];
process.send?.({ type: "lobbyList", lobbies } satisfies WorkerLobbyList);
}