Fix deselected host lobby settings persisting for joiners 🐛 (#3607)

## 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 <evanpelle@gmail.com>
This commit is contained in:
FloPinguin
2026-04-07 20:51:42 +02:00
committed by GitHub
parent 1cbee79cc7
commit 50bd075b1c
4 changed files with 23 additions and 20 deletions
+5 -8
View File
@@ -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<GameConfig>,
},
bubbles: true,
@@ -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"
: "";
+5 -5
View File
@@ -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();
+7 -5
View File
@@ -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;
}
}