diff --git a/resources/lang/en.json b/resources/lang/en.json index cd9f6aa41..101be75e7 100644 --- a/resources/lang/en.json +++ b/resources/lang/en.json @@ -241,7 +241,9 @@ "sam_launcher": "SAM Launcher", "atom_bomb": "Atom Bomb", "hydrogen_bomb": "Hydrogen Bomb", - "mirv": "MIRV" + "mirv": "MIRV", + "train": "Train", + "factory": "Factory" }, "user_setting": { "title": "User Settings", diff --git a/src/client/HostLobbyModal.ts b/src/client/HostLobbyModal.ts index 2a71ec7be..b0dbc6ae8 100644 --- a/src/client/HostLobbyModal.ts +++ b/src/client/HostLobbyModal.ts @@ -39,7 +39,10 @@ export class HostLobbyModal extends LitElement { @state() private copySuccess = false; @state() private players: string[] = []; @state() private useRandomMap: boolean = false; - @state() private disabledUnits: UnitType[] = []; + @state() private disabledUnits: UnitType[] = [ + UnitType.Factory, + UnitType.Train, + ]; private playersInterval: NodeJS.Timeout | null = null; // Add a new timer for debouncing bot changes diff --git a/src/client/SinglePlayerModal.ts b/src/client/SinglePlayerModal.ts index faef1d60b..ec7059c98 100644 --- a/src/client/SinglePlayerModal.ts +++ b/src/client/SinglePlayerModal.ts @@ -40,7 +40,10 @@ export class SinglePlayerModal extends LitElement { @state() private gameMode: GameMode = GameMode.FFA; @state() private teamCount: number | typeof Duos = 2; - @state() private disabledUnits: UnitType[] = []; + @state() private disabledUnits: UnitType[] = [ + UnitType.Factory, + UnitType.Train, + ]; private userSettings: UserSettings = new UserSettings(); diff --git a/src/client/graphics/layers/RadialMenuElements.ts b/src/client/graphics/layers/RadialMenuElements.ts index 76e7a6978..28791dcf8 100644 --- a/src/client/graphics/layers/RadialMenuElements.ts +++ b/src/client/graphics/layers/RadialMenuElements.ts @@ -1,3 +1,4 @@ +import { Config } from "../../../core/configuration/Config"; import { AllPlayers, Cell, @@ -322,6 +323,32 @@ export const infoMenuElement: MenuElement = { }, }; +function getAllEnabledUnits(myPlayer: boolean, config: Config): Set { + const Units: Set = new Set(); + + const addStructureIfEnabled = (unitType: UnitType) => { + if (!config.isUnitDisabled(unitType)) { + Units.add(unitType); + } + }; + + if (myPlayer) { + addStructureIfEnabled(UnitType.City); + addStructureIfEnabled(UnitType.DefensePost); + addStructureIfEnabled(UnitType.Port); + addStructureIfEnabled(UnitType.MissileSilo); + addStructureIfEnabled(UnitType.SAMLauncher); + addStructureIfEnabled(UnitType.Factory); + } else { + addStructureIfEnabled(UnitType.Warship); + addStructureIfEnabled(UnitType.HydrogenBomb); + addStructureIfEnabled(UnitType.MIRV); + addStructureIfEnabled(UnitType.AtomBomb); + } + + return Units; +} + export const buildMenuElement: MenuElement = { id: Slot.Build, name: "build", @@ -332,21 +359,10 @@ export const buildMenuElement: MenuElement = { subMenu: (params: MenuElementParams) => { if (params === undefined) return []; - const unitTypes: Set = new Set(); - if (params.selected === params.myPlayer) { - unitTypes.add(UnitType.City); - unitTypes.add(UnitType.DefensePost); - unitTypes.add(UnitType.Port); - unitTypes.add(UnitType.MissileSilo); - unitTypes.add(UnitType.SAMLauncher); - unitTypes.add(UnitType.Factory); - } else { - unitTypes.add(UnitType.Warship); - unitTypes.add(UnitType.HydrogenBomb); - unitTypes.add(UnitType.MIRV); - unitTypes.add(UnitType.AtomBomb); - } - + const unitTypes: Set = getAllEnabledUnits( + params.selected === params.myPlayer, + params.game.config(), + ); const buildElements: MenuElement[] = flattenedBuildTable .filter((item) => unitTypes.has(item.unitType)) .map((item: BuildItemDisplay) => ({ diff --git a/src/client/graphics/layers/UnitInfoModal.ts b/src/client/graphics/layers/UnitInfoModal.ts index a8d34be30..8163d668f 100644 --- a/src/client/graphics/layers/UnitInfoModal.ts +++ b/src/client/graphics/layers/UnitInfoModal.ts @@ -246,7 +246,8 @@ export class UnitInfoModal extends LitElement implements Layer { class="upgrade-button" title="${translateText("unit_info_modal.create_station")}" style="width: 100px; height: 32px; - display: ${this.unit.hasTrainStation() || + display: ${this.game.config().isUnitDisabled(UnitType.Train) || + this.unit.hasTrainStation() || !this.game.unitInfo(this.unit.type()).canBuildTrainStation ? "none" : "block"};" diff --git a/src/client/utilities/RenderUnitTypeOptions.ts b/src/client/utilities/RenderUnitTypeOptions.ts index c74aaf7ef..028f196fa 100644 --- a/src/client/utilities/RenderUnitTypeOptions.ts +++ b/src/client/utilities/RenderUnitTypeOptions.ts @@ -18,6 +18,8 @@ const unitOptions: { type: UnitType; translationKey: string }[] = [ { type: UnitType.AtomBomb, translationKey: "unit_type.atom_bomb" }, { type: UnitType.HydrogenBomb, translationKey: "unit_type.hydrogen_bomb" }, { type: UnitType.MIRV, translationKey: "unit_type.mirv" }, + { type: UnitType.Train, translationKey: "unit_type.train" }, + { type: UnitType.Factory, translationKey: "unit_type.factory" }, ]; export function renderUnitTypeOptions({ diff --git a/src/core/configuration/DefaultConfig.ts b/src/core/configuration/DefaultConfig.ts index 6ad3075fd..6a759a031 100644 --- a/src/core/configuration/DefaultConfig.ts +++ b/src/core/configuration/DefaultConfig.ts @@ -469,6 +469,7 @@ export class DefaultConfig implements Config { territoryBound: true, constructionDuration: this.instantBuild() ? 0 : 2 * 10, canBuildTrainStation: true, + experimental: true, }; case UnitType.Construction: return { @@ -479,6 +480,7 @@ export class DefaultConfig implements Config { return { cost: () => 0n, territoryBound: false, + experimental: true, }; default: assertNever(type); diff --git a/src/core/execution/FakeHumanExecution.ts b/src/core/execution/FakeHumanExecution.ts index ad10a8c70..48e163209 100644 --- a/src/core/execution/FakeHumanExecution.ts +++ b/src/core/execution/FakeHumanExecution.ts @@ -444,6 +444,9 @@ export class FakeHumanExecution implements Execution { } private maybeSpawnTrainStation(): boolean { + if (this.mg.config().isUnitDisabled(UnitType.Train)) { + return false; + } if (this.player === null) throw new Error("not initialized"); const citiesWithoutStations = this.player.units().filter((unit) => { switch (unit.type()) { diff --git a/src/core/game/Game.ts b/src/core/game/Game.ts index b62c62b02..e36c0f010 100644 --- a/src/core/game/Game.ts +++ b/src/core/game/Game.ts @@ -133,6 +133,7 @@ export interface UnitInfo { constructionDuration?: number; upgradable?: boolean; canBuildTrainStation?: boolean; + experimental?: boolean; } export enum UnitType { diff --git a/src/server/GameServer.ts b/src/server/GameServer.ts index eda377740..c971688e0 100644 --- a/src/server/GameServer.ts +++ b/src/server/GameServer.ts @@ -21,7 +21,7 @@ import { } from "../core/Schemas"; import { createGameRecord } from "../core/Util"; import { GameEnv, ServerConfig } from "../core/configuration/Config"; -import { GameType } from "../core/game/Game"; +import { GameType, UnitType } from "../core/game/Game"; import { archive } from "./Archive"; import { Client } from "./Client"; import { gatekeeper } from "./Gatekeeper"; @@ -222,6 +222,15 @@ export class GameServer { ); return; } + if ( + clientMsg.intent.type === "create_station" && + this.gameConfig.disabledUnits?.includes(UnitType.Train) + ) { + this.log.warn( + `create_station is disabled, client: ${client.clientID}`, + ); + return; + } this.addIntent(clientMsg.intent); } if (clientMsg.type === "ping") {