Mark train stations and factories as experimental (#1309)

## Description:

Train stations and factories won't be ready for v24.
Disable them by default. They can be reactivated on private lobies and
solo games, allowing us to gather feedbacks.

Changes:
- added an "experimental" attribute for units. When set, they are hidden
entirely when disabled, rather than appearing grayed out.
 - disabled train stations and factories by default.

Default values:

![image](https://github.com/user-attachments/assets/ec91ef80-c0ef-45a7-8e6c-6628a836b593)

No factories:

![image](https://github.com/user-attachments/assets/a3e35607-aed5-401c-bc38-4ea742d057c6)


## 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
- [x] I understand that submitting code with bugs that could have been
caught through manual testing blocks releases and new features for all
contributors

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

IngloriousTom
This commit is contained in:
DevelopingTom
2025-07-02 19:13:34 +02:00
committed by GitHub
parent fb38045829
commit cfabdfebc7
10 changed files with 62 additions and 20 deletions
+3 -1
View File
@@ -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",
+4 -1
View File
@@ -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
+4 -1
View File
@@ -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();
@@ -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<UnitType> {
const Units: Set<UnitType> = new Set<UnitType>();
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<UnitType> = new Set<UnitType>();
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<UnitType> = getAllEnabledUnits(
params.selected === params.myPlayer,
params.game.config(),
);
const buildElements: MenuElement[] = flattenedBuildTable
.filter((item) => unitTypes.has(item.unitType))
.map((item: BuildItemDisplay) => ({
+2 -1
View File
@@ -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"};"
@@ -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({
+2
View File
@@ -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);
+3
View File
@@ -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()) {
+1
View File
@@ -133,6 +133,7 @@ export interface UnitInfo {
constructionDuration?: number;
upgradable?: boolean;
canBuildTrainStation?: boolean;
experimental?: boolean;
}
export enum UnitType {
+10 -1
View File
@@ -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") {