From 50bd075b1c54befaccc96cdb7339d96f60d65d8b Mon Sep 17 00:00:00 2001 From: FloPinguin <25036848+FloPinguin@users.noreply.github.com> Date: Tue, 7 Apr 2026 20:51:42 +0200 Subject: [PATCH] =?UTF-8?q?Fix=20deselected=20host=20lobby=20settings=20pe?= =?UTF-8?q?rsisting=20for=20joiners=20=F0=9F=90=9B=20(#3607)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Description: ### Problem When a host toggled off certain settings (game length, PVP immunity, starting gold, gold multiplier, disable alliances) in the host lobby modal, joiners still saw the old values. The settings appeared "stuck" once enabled. ### Root Cause `putGameConfig()` sent `undefined` for disabled settings, but `JSON.stringify` strips `undefined` properties entirely. The server's `!== undefined` guard never fired, so the old value was never cleared. ### Fix - **HostLobbyModal**: Send `null` instead of `undefined` when these settings are toggled off (`null` survives JSON serialization) - **Schemas**: Add `.nullable()` to the five affected fields (`maxTimerValue`, `spawnImmunityDuration`, `goldMultiplier`, `startingGold`, `disableAlliances`) - **GameServer**: Use `?? undefined` (nullish coalescing) to convert incoming `null` back to `undefined` when storing on the config Other settings are unaffected. Booleans like `infiniteGold` always send `true`/`false`, and fields like `bots`/`gameMap` always have a concrete value.. ## Please complete the following: - [X] I have added screenshots for all UI updates - [X] I process any text displayed to the user through translateText() and I've added it to the en.json file - [X] I have added relevant tests to the test directory - [X] 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: FloPinguin --------- Co-authored-by: Evan --- src/client/HostLobbyModal.ts | 13 +++++-------- src/client/graphics/layers/GameRightSidebar.ts | 8 ++++++-- src/core/Schemas.ts | 10 +++++----- src/server/GameServer.ts | 12 +++++++----- 4 files changed, 23 insertions(+), 20 deletions(-) diff --git a/src/client/HostLobbyModal.ts b/src/client/HostLobbyModal.ts index 6436cc04d..53c1befcb 100644 --- a/src/client/HostLobbyModal.ts +++ b/src/client/HostLobbyModal.ts @@ -789,23 +789,20 @@ export class HostLobbyModal extends BaseModal { disabledUnits: this.disabledUnits, spawnImmunityDuration: this.spawnImmunity ? spawnImmunityTicks - : undefined, + : null, playerTeams: this.teamCount, nations: sliderToNationsConfig( this.nations, this.defaultNationCount, ), - maxTimerValue: - this.maxTimer === true ? this.maxTimerValue : undefined, + maxTimerValue: this.maxTimer === true ? this.maxTimerValue : null, goldMultiplier: - this.goldMultiplier === true - ? this.goldMultiplierValue - : undefined, + this.goldMultiplier === true ? this.goldMultiplierValue : null, startingGold: this.startingGold === true && this.startingGoldValue !== undefined ? Math.round(this.startingGoldValue * 1_000_000) - : undefined, - disableAlliances: this.disableAlliances || undefined, + : null, + disableAlliances: this.disableAlliances || null, } satisfies Partial, }, bubbles: true, diff --git a/src/client/graphics/layers/GameRightSidebar.ts b/src/client/graphics/layers/GameRightSidebar.ts index 67f5ed1dc..0daf3f323 100644 --- a/src/client/graphics/layers/GameRightSidebar.ts +++ b/src/client/graphics/layers/GameRightSidebar.ts @@ -99,7 +99,10 @@ export class GameRightSidebar extends LitElement implements Layer { const elapsedSeconds = Math.floor(gameTicks / 10); // 10 ticks per second if (this.game.inSpawnPhase()) { - this.timer = maxTimerValue !== undefined ? maxTimerValue * 60 : 0; + this.timer = + maxTimerValue !== null && maxTimerValue !== undefined + ? maxTimerValue * 60 + : 0; return; } @@ -107,7 +110,7 @@ export class GameRightSidebar extends LitElement implements Layer { return; } - if (maxTimerValue !== undefined) { + if (maxTimerValue !== null && maxTimerValue !== undefined) { this.timer = Math.max(0, maxTimerValue * 60 - elapsedSeconds); } else { this.timer = elapsedSeconds; @@ -179,6 +182,7 @@ export class GameRightSidebar extends LitElement implements Layer { const timerColor = this.game.config().gameConfig().maxTimerValue !== undefined && + this.game.config().gameConfig().maxTimerValue !== null && this.timer < 60 ? "text-red-400" : ""; diff --git a/src/core/Schemas.ts b/src/core/Schemas.ts index b2931dc0e..7f7b867c3 100644 --- a/src/core/Schemas.ts +++ b/src/core/Schemas.ts @@ -247,15 +247,15 @@ export const GameConfigSchema = z.object({ infiniteTroops: z.boolean(), instantBuild: z.boolean(), disableNavMesh: z.boolean().optional(), - disableAlliances: z.boolean().optional(), + disableAlliances: z.boolean().nullable().optional(), randomSpawn: z.boolean(), maxPlayers: z.number().optional(), - maxTimerValue: z.number().int().min(1).max(120).optional(), // In minutes - spawnImmunityDuration: z.number().int().min(0).optional(), // In ticks + maxTimerValue: z.number().int().min(1).max(120).nullable().optional(), // In minutes + spawnImmunityDuration: z.number().int().min(0).nullable().optional(), // In ticks disabledUnits: z.enum(UnitType).array().optional(), playerTeams: TeamCountConfigSchema.optional(), - goldMultiplier: z.number().min(0.1).max(1000).optional(), - startingGold: z.number().int().min(0).max(1000000000).optional(), + goldMultiplier: z.number().min(0.1).max(1000).nullable().optional(), + startingGold: z.number().int().min(0).max(1000000000).nullable().optional(), }); export const TeamSchema = z.string(); diff --git a/src/server/GameServer.ts b/src/server/GameServer.ts index f6ac05e76..fbb63b925 100644 --- a/src/server/GameServer.ts +++ b/src/server/GameServer.ts @@ -141,7 +141,7 @@ export class GameServer { this.gameConfig.donateTroops = gameConfig.donateTroops; } if (gameConfig.maxTimerValue !== undefined) { - this.gameConfig.maxTimerValue = gameConfig.maxTimerValue; + this.gameConfig.maxTimerValue = gameConfig.maxTimerValue ?? undefined; } if (gameConfig.instantBuild !== undefined) { this.gameConfig.instantBuild = gameConfig.instantBuild; @@ -150,7 +150,8 @@ export class GameServer { this.gameConfig.randomSpawn = gameConfig.randomSpawn; } if (gameConfig.spawnImmunityDuration !== undefined) { - this.gameConfig.spawnImmunityDuration = gameConfig.spawnImmunityDuration; + this.gameConfig.spawnImmunityDuration = + gameConfig.spawnImmunityDuration ?? undefined; } if (gameConfig.gameMode !== undefined) { this.gameConfig.gameMode = gameConfig.gameMode; @@ -162,13 +163,14 @@ export class GameServer { this.gameConfig.playerTeams = gameConfig.playerTeams; } if (gameConfig.goldMultiplier !== undefined) { - this.gameConfig.goldMultiplier = gameConfig.goldMultiplier; + this.gameConfig.goldMultiplier = gameConfig.goldMultiplier ?? undefined; } if (gameConfig.startingGold !== undefined) { - this.gameConfig.startingGold = gameConfig.startingGold; + this.gameConfig.startingGold = gameConfig.startingGold ?? undefined; } if (gameConfig.disableAlliances !== undefined) { - this.gameConfig.disableAlliances = gameConfig.disableAlliances; + this.gameConfig.disableAlliances = + gameConfig.disableAlliances ?? undefined; } }