Skip to content

Open custom lobbies to the public#3949

Open
sardidefcon wants to merge 5 commits into
openfrontio:mainfrom
sardidefcon:feature-match-selector
Open

Open custom lobbies to the public#3949
sardidefcon wants to merge 5 commits into
openfrontio:mainfrom
sardidefcon:feature-match-selector

Conversation

@sardidefcon
Copy link
Copy Markdown
Contributor

PUBLIC-LOBBIES

Description:

Adds the ability for hosts to open their custom lobbies to the public, allowing other players to discover and join them without needing an invite link.
This PR eliminates "private" lobbies and they are now called CUSTOM LOBBIES, since they can be either private or public.

Host side (HostLobbyModal):

  • New "Open to public" / "Close to public" toggle button in the lobby
  • Button state updates in real time and reflects the current visibility of the lobby

Player side (JoinLobbyModal):

  • New "Open lobbies" section listing all publicly open custom games
  • Each card shows: map thumbnail, map name, game mode (FFA / Teams with team count), player count, and a Join button
  • Clicking a card expands a dropdown with the full game config details before joining
  • All fields update in real time as the host changes settings

Improvements:

  • Random map: when the host selects "Random", viewers see "Random" + the random map thumbnail instead of the resolved map
  • Missing thumbnails for unknown/future maps now fall back to the random map thumbnail instead of hiding the image

OPTION TO OPEN THE GAME TO THE PUBLIC IN THE CONFIGURATOR OF A CUSTOM LOBBY

createcustomlobby

HOW THE LIST OF PUBLIC ITEMS LOOKS

joincustomlobby

HOW THE LIST OF PUBLIC ITEMS LOOKS (extended item details)

joincustomlobby_options

The text, in general, about how the game is opened to the public, what appears in the public game list, etc. I leave it up to you what you want to include; if you want, tell me on Discord and we can discuss it further.

Please complete the following:

  • I have added screenshots for all UI updates
  • I process any text displayed to the user through translateText() and I've added it to the en.json file
  • I have added relevant tests to the test directory
  • I confirm I have thoroughly tested these changes and take full responsibility for any bugs introduced

Please put your Discord username so you can be contacted if a bug or regression is found:

sardidefcon

- Hosts can open/close their custom lobby to the public from HostLobbyModal
- Open lobbies appear in a new section in JoinLobbyModal with map thumbnail, player count, game mode label, and join button
- Game mode (FFA/Teams) and map selection update in real time for viewers
- Random map selection preserved as a flag (useRandomMap) so viewers see "Random" + random thumbnail instead of the resolved map
- Fix tournament map names and thumbnails by resolving normaliseMapKey via enum key lookup
- All user-visible strings go through translateText()
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 16, 2026

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 605c2a36-3c00-4a27-a876-1bebd46a9910

📥 Commits

Reviewing files that changed from the base of the PR and between 73de312 and e34b804.

📒 Files selected for processing (1)
  • resources/lang/en.json
🚧 Files skipped from review as they are similar to previous changes (1)
  • resources/lang/en.json

Walkthrough

Hosts can open custom lobbies to public discovery; clients browse, expand details, and join open custom lobbies. Changes update schemas/intents, client host/join modals, transport routing, server lobby state, IPC propagation, utilities sync, and English localization.

Changes

Open Custom Lobbies

Layer / File(s) Summary
Core protocol schemas and types
src/core/game/Game.ts, src/core/Schemas.ts, src/core/ApiSchemas.ts, src/server/IPCBridgeSchema.ts
Adds GameType.Custom, OpenToPublicIntent schema/type, GameInfo.openCustomType, PublicGames.openLobbies, GameConfig.useRandomMap, WorkerLobbyList.openLobbies, and PlayerStatsTree.Custom.
Client transport and routing
src/client/Transport.ts, src/client/Main.ts
New SendOpenToPublicIntentEvent class; Transport listens and forwards it as open_to_public intent. Main registers a document "open-to-public" listener, validates payload, and emits the transport event.
Host modal open/close UI
src/client/HostLobbyModal.ts
Host modal tracks openToPublicType, renders open-to-public controls, dispatches "open-to-public" events with publicGameType ("team" vs "ffa" or null), and includes useRandomMap in config updates.
Join modal open lobbies UI
src/client/JoinLobbyModal.ts
Adds public openLobbies property and expandedLobbies state; renders open-lobby cards (map thumbnail/name, mode, players), expand/collapse and join controls, expanded config tags and disabled units, random-map handling via useRandomMap, join validation/tracking, and reset on close.
Utility functions and synchronization
src/client/GameModeSelector.ts, src/client/Utils.ts
GameModeSelector.handleLobbiesUpdate() syncs broadcast openLobbies into join-lobby-modal. normaliseMapKey resolves map names via GameMapType before fallback normalization.
Server-side state and intent handling
src/server/GameServer.ts, src/server/GameManager.ts
GameServer stores openCustomType, syncs it when gameMode changes, authorizes and handles open_to_public intent (setting openCustomType and switching gameType to Custom when opened), and exposes openCustomLobbyType() and augmented gameInfo(). GameManager adds openCustomLobbies().
Server IPC propagation
src/server/WorkerLobbyService.ts, src/server/MasterLobbyService.ts
Workers include openLobbies in lobbyList IPC messages. Master aggregates per-worker workerOpenLobbies, clears on worker removal, and includes aggregated openLobbies in lobbiesBroadcast.
Localization
resources/lang/en.json
Replaces "Private Lobby" wording with "Custom Lobby", adds host open-to-public UI strings, adds game mode labels (team/teams/special), adds join_lobby strings, and a units block.

Sequence Diagram(s)

sequenceDiagram
  participant Host as HostLobbyModal
  participant Doc as Document
  participant Main as Client.Main
  participant Transport as Client.Transport
  participant Server as GameServer
  participant Worker as WorkerLobbyService
  participant Master as MasterLobbyService
  participant Join as JoinLobbyModal

  Host->>Doc: dispatch "open-to-public" event (publicGameType)
  Doc->>Main: document event triggers handleOpenToPublic
  Main->>Transport: emit SendOpenToPublicIntentEvent(publicGameType)
  Transport->>Server: sendIntent "open_to_public" with publicGameType
  Server->>Server: authorize owner, set openCustomType, maybe set gameType=Custom
  Server->>Worker: Worker collects openCustomLobbies()
  Worker->>Master: sendMyLobbiesToMaster (lobbies + openLobbies)
  Master->>Join: broadcast lobbiesBroadcast including openLobbies
  Join->>Join: render openLobbies list/cards
Loading

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • openfrontio/OpenFrontIO#3923: Both PRs touch JoinLobbyModal.ts and HostLobbyModal.ts; ensure open-to-public logic integrates with BaseModal changes from that refactor.

Poem

🎪 A lobby opens wide its doors,
Players browse maps and click to explore,
Custom games call out to the crowd—
Join a match, share laughs aloud!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main feature: enabling hosts to open custom lobbies to the public for discovery and joining.
Description check ✅ Passed The description comprehensively explains the feature including host-side and player-side functionality, improvements, UI screenshots, and implementation details.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/client/JoinLobbyModal.ts (1)

223-266: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Run Prettier on this file before merge.

CI reports Prettier --check warnings for this file, so formatting is currently out of sync.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/client/JoinLobbyModal.ts` around lines 223 - 266, The file formatting in
JoinLobbyModal is out of sync with project style (Prettier) causing CI --check
failures; run Prettier on this file (e.g., format src/client/JoinLobbyModal.ts)
and ensure the JSX/template block and attributes (in the JoinLobbyModal render
method around the input with id "lobbyIdInput", the o-button with
`@click`=${this.pasteFromClipboard}, the form submit handler joinLobbyFromInput,
and the renderOpenLobbies() insertion) are reformatted to match project Prettier
config, then re-run lint/CI and commit the formatted file.
🧹 Nitpick comments (1)
src/server/WorkerLobbyService.ts (1)

98-116: 💤 Low value

Consider a fallback for openCustomType to avoid potential runtime error.

There is a small timing window where a lobby could close between the openCustomLobbies() filter and calling gameInfo(). If that happens, gi.openCustomType becomes undefined and the non-null assertion on line 108 would throw.

This is unlikely in practice, but a simple guard would make it safer.

♻️ Suggested defensive filter
     const openLobbies = this.gm
       .openCustomLobbies()
       .map((g) => g.gameInfo())
+      .filter((gi) => gi.openCustomType !== undefined)
       .map((gi) => {
         return {
           gameID: gi.gameID,
           numClients: gi.clients?.length ?? 0,
           startsAt: gi.startsAt,
           gameConfig: gi.gameConfig,
-          publicGameType: gi.openCustomType!,
+          publicGameType: gi.openCustomType,
         } satisfies PublicGameInfo;
       });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/server/WorkerLobbyService.ts` around lines 98 - 116, The mapping that
builds openLobbies uses gi.openCustomType! with a non-null assertion which can
throw if a lobby closes between openCustomLobbies() and gameInfo(); update the
transformation to defensively filter or default those entries: when mapping
results from this.gm.openCustomLobbies().map(g => g.gameInfo()), skip any gi
where gi.openCustomType is undefined (or provide a safe fallback value) before
constructing the object that satisfies PublicGameInfo, ensuring the final
openLobbies array only contains items with a defined openCustomType before
sending the WorkerLobbyList.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/client/HostLobbyModal.ts`:
- Line 89: The modal-scoped field openToPublicType (type "ffa" | "team" |
"special" | null) is not being cleared when the modal is closed, causing stale
values to flash on reopen; update the HostLobbyModal.onClose method to reset
this.openToPublicType = null (ensure it's set whether onClose is sync or after
any async cleanup) so the modal always starts with a null public-type state when
reopened.

In `@src/client/JoinLobbyModal.ts`:
- Around line 452-467: The code emits hardcoded unit suffixes ("min", "s", "m",
"M") when building the tags array in JoinLobbyModal; replace those raw suffix
strings with translateText calls and new i18n keys so units are localizable:
update the logic around c.maxTimerValue display (currently using
translateText("private_lobby.game_length")), the spawnImmunityDuration block
that builds val (replace "s", "m", "min" with translateText keys like
"units.second_short", "units.minute_short", "units.minute" and keep using
Math.floor/Math.round as-is), and the startingGold formatting (replace "M" with
translateText("units.million_short")); add corresponding entries to
resources/lang/en.json (e.g. units.second_short, units.minute_short,
units.minute, units.million_short) and use those translateText keys in the
tags.push calls so all visible units are localized.

In `@src/client/Main.ts`:
- Line 229: The custom event "open-to-public" currently force-casts its payload
string to the allowed union, letting invalid values through; add a runtime
validation step instead: define an allowedPublicGameTypes set (or an
isValidPublicGameType type-guard) that lists the permitted string values and
null, use it wherever the event payload is read (the "open-to-public"
CustomEvent handling and the code paths around the previous casts) to check the
incoming value, and only accept/narrow the type when the guard passes; for
invalid values, reject or fallback to a safe default and log an error so
malformed intents cannot be emitted.

In `@src/core/Schemas.ts`:
- Line 171: The lobby schema's openCustomType currently uses
PublicGameTypeSchema.optional() which still rejects null; update the definition
(symbol: openCustomType in Schemas.ts) to accept null by using
PublicGameTypeSchema.nullable() or PublicGameTypeSchema.nullable().optional()
(if you want both null and undefined) so the lobby_info parsing accepts the
closed-state null for custom lobbies.

---

Outside diff comments:
In `@src/client/JoinLobbyModal.ts`:
- Around line 223-266: The file formatting in JoinLobbyModal is out of sync with
project style (Prettier) causing CI --check failures; run Prettier on this file
(e.g., format src/client/JoinLobbyModal.ts) and ensure the JSX/template block
and attributes (in the JoinLobbyModal render method around the input with id
"lobbyIdInput", the o-button with `@click`=${this.pasteFromClipboard}, the form
submit handler joinLobbyFromInput, and the renderOpenLobbies() insertion) are
reformatted to match project Prettier config, then re-run lint/CI and commit the
formatted file.

---

Nitpick comments:
In `@src/server/WorkerLobbyService.ts`:
- Around line 98-116: The mapping that builds openLobbies uses
gi.openCustomType! with a non-null assertion which can throw if a lobby closes
between openCustomLobbies() and gameInfo(); update the transformation to
defensively filter or default those entries: when mapping results from
this.gm.openCustomLobbies().map(g => g.gameInfo()), skip any gi where
gi.openCustomType is undefined (or provide a safe fallback value) before
constructing the object that satisfies PublicGameInfo, ensuring the final
openLobbies array only contains items with a defined openCustomType before
sending the WorkerLobbyList.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 1edada23-6e5b-4753-a241-d0f90f6a7548

📥 Commits

Reviewing files that changed from the base of the PR and between 4460367 and a8381f9.

📒 Files selected for processing (15)
  • resources/lang/en.json
  • src/client/GameModeSelector.ts
  • src/client/HostLobbyModal.ts
  • src/client/JoinLobbyModal.ts
  • src/client/Main.ts
  • src/client/Transport.ts
  • src/client/Utils.ts
  • src/core/ApiSchemas.ts
  • src/core/Schemas.ts
  • src/core/game/Game.ts
  • src/server/GameManager.ts
  • src/server/GameServer.ts
  • src/server/IPCBridgeSchema.ts
  • src/server/MasterLobbyService.ts
  • src/server/WorkerLobbyService.ts

Comment thread src/client/HostLobbyModal.ts
Comment thread src/client/JoinLobbyModal.ts Outdated
Comment thread src/client/Main.ts
Comment thread src/core/Schemas.ts Outdated
@github-project-automation github-project-automation Bot moved this from Triage to Development in OpenFront Release Management May 16, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/client/JoinLobbyModal.ts`:
- Around line 556-567: joinOpenLobby can process stale async results; create and
use an AbortController (or a unique request token) tied to the lobby tracking so
late checkActiveLobby results are ignored. Modify startTrackingLobby to store
the current AbortController (or token) and call controller.abort() in
resetTrackingState and whenever starting a new tracking, pass controller.signal
into checkActiveLobby (or have checkActiveLobby accept the token) and before
dispatching any join logic ensure the controller.signal is not aborted (or the
token still matches the current tracked id). Update joinOpenLobby to create the
controller, pass it into checkActiveLobby, and bail out if the signal was
aborted or token mismatches before calling resetTrackingState/showMessage or
emitting any join event.
- Around line 474-479: The lobby tag rendering currently only handles c.nations
=== "disabled" and omits numeric nation caps; update the JoinLobbyModal tag
logic (near where c, isCompact and tags are used) to mirror renderGameConfig()
by adding a branch for numeric c.nations values that pushes a tag like
`${translateText("host_modal.nations")}: ${c.nations}` (or the same formatted
string used in renderGameConfig) so non-default numeric nation limits are shown
in the expanded details; keep the existing disabled branch and ensure the new
branch only runs when c.nations is not "disabled".
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 26a8d011-32ff-4b73-92ab-d87556aebc5e

📥 Commits

Reviewing files that changed from the base of the PR and between a8381f9 and c92b574.

📒 Files selected for processing (3)
  • src/client/JoinLobbyModal.ts
  • src/client/Main.ts
  • src/server/GameManager.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/server/GameManager.ts
  • src/client/Main.ts

Comment thread src/client/JoinLobbyModal.ts
Comment thread src/client/JoinLobbyModal.ts
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/server/WorkerLobbyService.ts`:
- Line 102: Replace the non-strict null check in the filter callback
(.filter((gi) => gi.openCustomType != null)) with a strict null/undefined check
to satisfy eqeqeq; update the predicate to explicitly check both null and
undefined (e.g., .filter((gi) => gi.openCustomType !== null && gi.openCustomType
!== undefined)) so the filter uses strict comparisons.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 110b5a9b-42f3-41b9-8687-5a751bcc1eca

📥 Commits

Reviewing files that changed from the base of the PR and between c92b574 and b7dfe38.

📒 Files selected for processing (6)
  • resources/lang/en.json
  • src/client/HostLobbyModal.ts
  • src/client/JoinLobbyModal.ts
  • src/client/Main.ts
  • src/core/Schemas.ts
  • src/server/WorkerLobbyService.ts
🚧 Files skipped from review as they are similar to previous changes (5)
  • src/client/HostLobbyModal.ts
  • src/client/Main.ts
  • resources/lang/en.json
  • src/core/Schemas.ts
  • src/client/JoinLobbyModal.ts

Comment thread src/server/WorkerLobbyService.ts Outdated
coderabbitai[bot]
coderabbitai Bot previously approved these changes May 16, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Development

Development

Successfully merging this pull request may close these issues.

1 participant