feat(alliances): custom alliance duration lobby control (#4522)

## Description:

Replaces the "Disable alliances" toggle in the host and single-player
lobbies with a "Custom alliances" control: a toggle plus a minutes input
(0 to 15, step 1).

- 0 minutes disables alliances, same behavior as the old toggle.
- 1 to 15 sets the alliance duration in minutes.

How it works:

- Adds one game-config field, `customAllianceDuration` (minutes).
- `Config.allianceDuration()` uses it when set (and falls back to the
existing 5 minute default), and `Config.disableAlliances()` returns true
when it is 0.
- The legacy `disableAlliances` boolean is still read, so older/archived
configs keep working.
- Validation reuses the existing `parseBoundedIntegerFromInput` and
`toggle-input-card` helpers, so it behaves like the other numeric lobby
options (spawn immunity, max timer).
- The join-lobby screen shows "Alliances: {x}m", or "Alliances:
Disabled" at 0.

<img width="279" height="145" alt="image"
src="https://github.com/user-attachments/assets/5a608e18-3811-4eef-a3a6-9344aaf667fe"
/>
<img width="270" height="152" alt="image"
src="https://github.com/user-attachments/assets/9d8a4d30-51e7-4e82-8ce0-121b12af1c61"
/>
<img width="270" height="144" alt="image"
src="https://github.com/user-attachments/assets/a65b20fb-0db5-4657-a964-880fad7c864e"
/>


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

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

zixer._
This commit is contained in:
Zixer1
2026-07-08 15:13:35 -04:00
committed by GitHub
parent d3ad1f51bd
commit 050c7604e8
9 changed files with 172 additions and 27 deletions
+1
View File
@@ -358,6 +358,7 @@ export const GameConfigSchema = z.object({
// OFM: allowlist of publicIds allowed to join (admin-only, see create_game).
allowedPublicIds: z.array(z.string()).max(200).optional(),
maxTimerValue: z.number().int().min(1).max(120).nullable().optional(), // In minutes
customAllianceDuration: z.number().int().min(0).max(15).nullable().optional(), // In minutes; 0 disables alliances
startDelay: z.number().int().min(0).max(600).nullable().optional(), // In seconds
spawnImmunityDuration: z.number().int().min(0).nullable().optional(), // In ticks
disabledUnits: z.enum(UnitType).array().optional(),
+10 -1
View File
@@ -219,7 +219,12 @@ export class Config {
return this._gameConfig.disableNavMesh ?? false;
}
disableAlliances(): boolean {
return this._gameConfig.disableAlliances ?? false;
// customAllianceDuration === 0 disables alliances (the "custom alliances"
// control at 0). The legacy boolean is still honored for older configs.
return (
this._gameConfig.customAllianceDuration === 0 ||
(this._gameConfig.disableAlliances ?? false)
);
}
waterNukes(): boolean {
return this._gameConfig.waterNukes ?? false;
@@ -566,6 +571,10 @@ export class Config {
return 30 * 10;
}
allianceDuration(): Tick {
// Host can set a custom alliance duration in minutes (1-15); 0 disables
// alliances (see disableAlliances). Falls back to the 5 minute default.
const m = this._gameConfig.customAllianceDuration;
if (typeof m === "number" && m > 0) return m * 60 * 10;
return 300 * 10; // 5 minutes.
}
temporaryEmbargoDuration(): Tick {