Skip to content

feat: Lobby presets#3045

Open
MitchZinck wants to merge 6 commits intoopenfrontio:mainfrom
MitchZinck:2566-lobby-presets
Open

feat: Lobby presets#3045
MitchZinck wants to merge 6 commits intoopenfrontio:mainfrom
MitchZinck:2566-lobby-presets

Conversation

@MitchZinck
Copy link
Contributor

@MitchZinck MitchZinck commented Jan 27, 2026

If this PR fixes an issue, link it below. If not, delete these two lines.
Resolves #2566

Description:

  • Save lobby settings as a preset and pick from dropdown, stored in localstorage
  • Auto-load last used preset
  • Show users in lobby changes to presets, automatically load saved preset for all users when joining
  • Align server difficulty setting with client
Screenshot 2026-01-26 at 10 44 20 PM

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:

mitchfz

@CLAassistant
Copy link

CLAassistant commented Jan 27, 2026

CLA assistant check
All committers have signed the CLA.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 27, 2026

Walkthrough

Adds a lobby presets feature: client-side preset store, UI component, integration into host and single-player modals using patch-based GameConfig flows, form-state utilities, server default config factories, tests, and a test storage shim.

Changes

Cohort / File(s) Summary
Localization strings
resources/lang/en.json
Added 13 new host_modal.presets_* keys for labels, placeholders, actions, and feedback messages.
Preset persistence layer
src/client/LobbyPresets.ts
New localStorage-backed preset store with zod schemas, resilient load/save, list/upsert/delete, last-used and auto-apply flags.
Preset UI component
src/client/components/LobbyPresetControls.ts
New LitElement LobbyPresetControls with getConfigPatch, onApplyPreset, onResetPreset; handles selection, save/delete, auto-apply, and sync from store.
Host modal integration
src/client/HostLobbyModal.ts
Integrated preset controls and patch-based game-config flow; added build/apply/reset helpers; createLobby now accepts a GameConfig payload.
Single-player modal integration
src/client/SinglePlayerModal.ts
Added preset controls, patch build/apply/reset helpers, onOpen/onClose hooks, and patch-based config construction for start/join flows.
Game config form state
src/client/utilities/GameConfigFormState.ts
New FormState utilities to reset, build, and apply patches for host and single-player; conversions (minutes↔ticks) and final GameConfig builders.
Server default config usage
src/core/game/GameConfigDefaults.ts, src/server/GameManager.ts, src/server/MapPlaylist.ts
Added default GameConfig factories and updated server modules to merge defaults with provided gameConfig instead of many hard-coded defaults.
Tests & test shim
tests/client/LobbyPresets.test.ts, tests/setup.ts
Unit tests for preset upsert/delete behavior and a test helper that polyfills localStorage/sessionStorage with in-memory storage.
Other
package.json
Manifest lines updated (likely deps/dev-deps or metadata).

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant Modal as HostLobbyModal / SinglePlayerModal
    participant UI as LobbyPresetControls
    participant Store as LobbyPresets
    participant Storage as localStorage
    participant Server as GameServer

    User->>Modal: open modal
    Modal->>Store: listPresets()
    Store->>Storage: read LOBBY_PRESET_STORAGE_KEY
    Storage-->>Store: JSON
    Store-->>Modal: presets[]

    Modal->>UI: render presets selector
    User->>UI: select preset
    UI->>Store: get preset by id
    Store-->>UI: preset
    UI-->>Modal: apply preset id
    Modal->>Modal: buildGameConfigPatch()
    Modal->>Modal: applyGameConfigPatch(patch)

    User->>UI: save preset (name)
    UI->>Modal: getConfigPatch()
    Modal-->>UI: patch
    UI->>Store: upsertPreset(name, config)
    Store->>Storage: save LOBBY_PRESET_STORAGE_KEY

    User->>Modal: create/start game
    Modal->>Modal: buildFullGameConfig()
    Modal->>Server: POST /createLobby (body: GameConfig)
    Server-->>Modal: GameInfo
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🌱 Preset seeds tucked in local store,
Click to save, select, then restore.
Patches flow from UI into the form,
Defaults merge before the storm. 🎮

🚥 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 'feat: Lobby presets' directly and concisely describes the main feature added in the PR: support for saving and managing lobby presets.
Description check ✅ Passed The description clearly explains the feature: saving lobby settings as presets in localStorage, auto-loading the last used preset, showing preset changes to users, and aligning server difficulty with client. It links to the resolved issue and includes verification of UI updates and test coverage.
Linked Issues check ✅ Passed The PR addresses all requirements from #2566: adds preset UI support with dark mode, persists presets in localStorage with dropdown and name input, broadcasts preset changes, routes text through translateText() with en.json entries, includes UI screenshots, and adds test coverage.
Out of Scope Changes check ✅ Passed Changes are scoped to preset functionality: new LobbyPresets store, LobbyPresetControls component, GameConfigFormState utilities, GameConfigDefaults, and integration into HostLobbyModal and SinglePlayerModal. Updates to GameManager and MapPlaylist align server defaults with client config, directly supporting the 'align server difficulty' objective. All changes are in-scope.

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


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
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

🤖 Fix all issues with AI agents
In `@src/client/HostLobbyModal.ts`:
- Around line 1217-1229: In syncPresetsFromStore, avoid pre-selecting last-used
preset when auto-apply is off: set this.autoApplyLastUsedPreset from
store.autoApplyLastUsed first, then compute selectionId so it is
preferredSelectionId if provided, otherwise use store.lastUsedPresetId only when
store.autoApplyLastUsed is true; otherwise leave selectionId undefined. Update
assignment to this.selectedPresetId and this.presetNameInput to use that
selectionId/selectedPreset logic (references: syncPresetsFromStore,
autoApplyLastUsedPreset, preferredSelectionId, store.lastUsedPresetId,
selectedPresetId, presetNameInput).

@github-project-automation github-project-automation bot moved this from Triage to Development in OpenFront Release Management Jan 27, 2026
Copy link
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

🤖 Fix all issues with AI agents
In `@src/client/HostLobbyModal.ts`:
- Around line 1157-1165: When the preset lookup fails in HostLobbyModal
(loadLobbyPresetStore + preset lookup using this.selectedPresetId), clear the
stale UI state: set this.selectedPresetId = undefined (already done) and also
clear any "last-used" preset state and input control bound to the preset
selection (e.g., reset lastUsedPresetId or the form input controlling the
preset) before returning; keep the existing showMessage(translateText(...),
"red") behavior so the user is notified and the UI remains consistent.
🧹 Nitpick comments (3)
src/client/LobbyPresets.ts (2)

31-51: Clear invalid stored data so future loads recover cleanly.

If JSON/schema parsing fails, the bad payload stays and every load re-parses. Removing the key once avoids repeated failures and allows clean recovery.

♻️ Suggested tweak
   try {
     const parsed = JSON.parse(raw);
     const result = LobbyPresetStoreSchema.safeParse(parsed);
     if (result.success) {
       return result.data;
     }
+    localStorage.removeItem(LOBBY_PRESET_STORAGE_KEY);
   } catch {
+    localStorage.removeItem(LOBBY_PRESET_STORAGE_KEY);
     return emptyLobbyPresetStore();
   }

71-72: Return a copy to avoid accidental mutation.

Returning the internal array lets callers mutate it without saving, which can create confusing state.

♻️ Suggested tweak
 export function listPresets(): LobbyPreset[] {
-  return loadLobbyPresetStore().presets;
+  return [...loadLobbyPresetStore().presets];
 }
tests/client/LobbyPresets.test.ts (1)

1-13: Avoid hardcoding the storage key in tests.

Using the shared constant prevents drift if the key changes. This needs the constant exported from LobbyPresets.

♻️ Suggested tweak
 import {
   deletePreset,
   loadLobbyPresetStore,
   setAutoApplyLastUsed,
   setLastUsedPresetId,
   upsertPreset,
+  LOBBY_PRESET_STORAGE_KEY,
 } from "../../src/client/LobbyPresets";
@@
-    localStorage.removeItem("lobbyPresets.v1");
+    localStorage.removeItem(LOBBY_PRESET_STORAGE_KEY);

@MitchZinck MitchZinck marked this pull request as ready for review January 27, 2026 04:16
@MitchZinck MitchZinck requested a review from a team as a code owner January 27, 2026 04:16
coderabbitai[bot]
coderabbitai bot previously approved these changes Jan 27, 2026
@MitchZinck MitchZinck changed the title 2566 - Lobby presets feat: Lobby presets Jan 27, 2026
}

private async putGameConfig() {
private async handlePresetSelectionChange(e: Event) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

can the preset logic be moved into it's own lit element in client/components. Then it can be reused in singleplayer modal as well

import { renderUnitTypeOptions } from "./utilities/RenderUnitTypeOptions";
import randomMap from "/images/RandomMap.webp?url";

const DEFAULT_PRIVATE_GAME_CONFIG: GameConfig = {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Maybe all the game config storage/handling could go in its own file? One issue is that adding an new config option requires changing the code in many different places. I think if we had one registry that could make things easier.

Copy link
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

🤖 Fix all issues with AI agents
In `@src/client/components/LobbyPresetControls.ts`:
- Around line 102-121: handlePresetSaveClick currently passes a config patch
with undefined fields which JSON.stringify will drop, so disabled toggle fields
(maxTimerValue, goldMultiplier, startingGold, spawnImmunityDuration) get lost;
before calling upsertPreset replace undefined toggle values with explicit nulls
(e.g. map any toggle-off fields to null) so the preset persists the "disabled"
intent, and update the preset schema/types and the consumers
(applyCommonGameConfigPatch / applyHostLobbyGameConfigPatch) to treat null as
the disabled sentinel when applying patches. Ensure you update upsertPreset
usage and any type definitions so stored presets can contain null for those
fields and the apply* functions check for field presence and null to clear
toggles.

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.

3 participants