Added custom disable settings (#593)

## Description:
I will write an issue later as I don't have time.

![スクリーンショット 2025-04-23 22 04
24](https://github.com/user-attachments/assets/77754140-eee9-46bd-a98f-a3abec35ca6a)
<img width="735" alt="スクリーンショット 2025-04-23 22 04 40"
src="https://github.com/user-attachments/assets/24bb3461-8e05-418c-8dbf-2ba1a624fe27"
/>
<img width="790" alt="スクリーンショット 2025-04-23 22 04 47"
src="https://github.com/user-attachments/assets/33387200-d0af-4394-9d60-af1f88a036e9"
/>

## Please complete the following:

- [x] I have added screenshots for all UI updates
- [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:
aotumuri
This commit is contained in:
Aotumuri
2025-05-07 22:14:54 +09:00
committed by GitHub
parent b3c59da29d
commit cddcc681dd
13 changed files with 177 additions and 71 deletions
+13 -1
View File
@@ -101,6 +101,7 @@
"infinite_gold": "Infinite gold",
"infinite_troops": "Infinite troops",
"disable_nukes": "Disable Nukes",
"enables_title": "Enable Settings",
"start": "Start Game"
},
"map": {
@@ -167,7 +168,7 @@
"instant_build": "Instant build",
"infinite_gold": "Infinite gold",
"infinite_troops": "Infinite troops",
"disable_nukes": "Disable Nukes",
"enables_title": "Enable Settings",
"player": "Player",
"players": "Players",
"waiting": "Waiting for players...",
@@ -191,6 +192,17 @@
"select_lang": {
"title": "Select Language"
},
"unit_type": {
"city": "City",
"defense_post": "Defense Post",
"port": "Port",
"warship": "Warship",
"missile_silo": "Missile Silo",
"sam_launcher": "SAM Launcher",
"atom_bomb": "Atom Bomb",
"hydrogen_bomb": "Hydrogen Bomb",
"mirv": "MIRV"
},
"user_setting": {
"title": "User Settings",
"tab_basic": "Basic Settings",
+13 -2
View File
@@ -97,7 +97,7 @@
"instant_build": "即時建設",
"infinite_gold": "資金無限",
"infinite_troops": "兵士無限",
"disable_nukes": "核兵器使用禁止",
"enables_title": "有効化設定",
"start": "ゲーム開始"
},
"map": {
@@ -158,7 +158,7 @@
"instant_build": "実在する国家を無効化",
"infinite_gold": "資金無限",
"infinite_troops": "兵士無限",
"disable_nukes": "核兵器使用禁止",
"enables_title": "有効化設定",
"player": "プレイヤー",
"players": "プレイヤー",
"waiting": "他のプレイヤーの参加を待っています...",
@@ -182,6 +182,17 @@
"select_lang": {
"title": "言語を選択"
},
"unit_type": {
"city": "都市",
"defense_post": "防衛ポスト",
"port": "港",
"warship": "戦艦",
"missile_silo": "ミサイル格納庫",
"sam_launcher": "SAMランチャー",
"atom_bomb": "原子爆弾",
"hydrogen_bomb": "水素爆弾",
"mirv": "MIRV"
},
"user_setting": {
"title": "ユーザー設定",
"tab_basic": "基本設定",
+68 -13
View File
@@ -9,6 +9,7 @@ import {
Duos,
GameMapType,
GameMode,
UnitType,
mapCategories,
} from "../core/game/Game";
import { GameConfig, GameInfo } from "../core/Schemas";
@@ -39,6 +40,7 @@ export class HostLobbyModal extends LitElement {
@state() private copySuccess = false;
@state() private players: string[] = [];
@state() private useRandomMap: boolean = false;
@state() private disabledUnits: string[] = [];
private playersInterval = null;
// Add a new timer for debouncing bot changes
@@ -302,21 +304,72 @@ export class HostLobbyModal extends LitElement {
</div>
</label>
<label
for="disable-nukes"
class="option-card ${this.disableNukes ? "selected" : ""}"
<hr style="width: 100%; border-top: 1px solid #444; margin: 16px 0;" />
<!-- Individual disables for structures/weapons -->
<div
style="margin: 8px 0 12px 0; font-weight: bold; color: #ccc; text-align: center;"
>
<div class="checkbox-icon"></div>
<input
type="checkbox"
id="disable-nukes"
@change=${this.handleDisableNukesChange}
.checked=${this.disableNukes}
/>
<div class="option-card-title">
${translateText("host_modal.disable_nukes")}
${translateText("host_modal.enables_title")}
</div>
<div
style="display: flex; flex-wrap: wrap; justify-content: center; gap: 12px;"
>
${[
[UnitType.City, "unit_type.city"],
[UnitType.DefensePost, "unit_type.defense_post"],
[UnitType.Port, "unit_type.port"],
[UnitType.Warship, "unit_type.warship"],
[UnitType.MissileSilo, "unit_type.missile_silo"],
[UnitType.SAMLauncher, "unit_type.sam_launcher"],
[UnitType.AtomBomb, "unit_type.atom_bomb"],
[UnitType.HydrogenBomb, "unit_type.hydrogen_bomb"],
[UnitType.MIRV, "unit_type.mirv"],
].map(
([unitType, translationKey]) => html`
<label
class="option-card ${this.disabledUnits.includes(
unitType,
)
? ""
: "selected"}"
style="width: 140px;"
>
<div class="checkbox-icon"></div>
<input
type="checkbox"
@change=${(e: Event) => {
const checked = (e.target as HTMLInputElement)
.checked;
const parsedUnitType =
UnitType[unitType as keyof typeof UnitType];
if (parsedUnitType) {
if (checked) {
this.disabledUnits = [
...this.disabledUnits,
parsedUnitType,
];
} else {
this.disabledUnits = this.disabledUnits.filter(
(u) => u !== parsedUnitType,
);
}
this.putGameConfig();
}
}}
.checked=${this.disabledUnits.includes(unitType)}
/>
<div
class="option-card-title"
style="text-align: center;"
>
${translateText(translationKey)}
</div>
</label>
`,
)}
</div>
</label>
</div>
</div>
</div>
</div>
@@ -490,6 +543,8 @@ export class HostLobbyModal extends LitElement {
infiniteTroops: this.infiniteTroops,
instantBuild: this.instantBuild,
gameMode: this.gameMode,
numPlayerTeams: this.teamCount,
disabledUnits: this.disabledUnits,
playerTeams: this.teamCount,
} as GameConfig),
},
+58 -15
View File
@@ -9,6 +9,7 @@ import {
GameMapType,
GameMode,
GameType,
UnitType,
mapCategories,
} from "../core/game/Game";
import { generateID } from "../core/Util";
@@ -39,6 +40,8 @@ export class SinglePlayerModal extends LitElement {
@state() private gameMode: GameMode = GameMode.FFA;
@state() private teamCount: number | typeof Duos = 2;
@state() private disabledUnits: string[] = [];
render() {
return html`
<o-modal title=${translateText("single_modal.title")}>
@@ -269,22 +272,61 @@ export class SinglePlayerModal extends LitElement {
${translateText("single_modal.infinite_troops")}
</div>
</label>
</div>
<label
for="singleplayer-modal-disable-nukes"
class="option-card ${this.disableNukes ? "selected" : ""}"
>
<div class="checkbox-icon"></div>
<input
type="checkbox"
id="singleplayer-modal-disable-nukes"
@change=${this.handleDisableNukesChange}
.checked=${this.disableNukes}
/>
<div class="option-card-title">
${translateText("single_modal.disable_nukes")}
</div>
</label>
<hr
style="width: 100%; border-top: 1px solid #444; margin: 16px 0;"
/>
<div
style="margin: 8px 0 12px 0; font-weight: bold; color: #ccc; text-align: center;"
>
${translateText("single_modal.enables_title")}
</div>
<div
style="display: flex; flex-wrap: wrap; justify-content: center; gap: 12px;"
>
${[
[UnitType.City, "unit_type.city"],
[UnitType.DefensePost, "unit_type.defense_post"],
[UnitType.Port, "unit_type.port"],
[UnitType.Warship, "unit_type.warship"],
[UnitType.MissileSilo, "unit_type.missile_silo"],
[UnitType.SAMLauncher, "unit_type.sam_launcher"],
[UnitType.AtomBomb, "unit_type.atom_bomb"],
[UnitType.HydrogenBomb, "unit_type.hydrogen_bomb"],
[UnitType.MIRV, "unit_type.mirv"],
].map(
([unitType, translationKey]) => html`
<label
class="option-card ${this.disabledUnits.includes(unitType)
? ""
: "selected"}"
style="width: 140px;"
>
<div class="checkbox-icon"></div>
<input
type="checkbox"
@change=${(e: Event) => {
const checked = (e.target as HTMLInputElement).checked;
if (checked) {
this.disabledUnits = [
...this.disabledUnits,
unitType,
];
} else {
this.disabledUnits = this.disabledUnits.filter(
(u) => u !== unitType,
);
}
}}
.checked=${this.disabledUnits.includes(unitType)}
/>
<div class="option-card-title" style="text-align: center;">
${translateText(translationKey)}
</div>
</label>
`,
)}
</div>
</div>
</div>
@@ -419,6 +461,7 @@ export class SinglePlayerModal extends LitElement {
infiniteGold: this.infiniteGold,
infiniteTroops: this.infiniteTroops,
instantBuild: this.instantBuild,
disabledUnits: this.disabledUnits,
},
},
} as JoinLobbyEvent,
+3 -15
View File
@@ -409,21 +409,9 @@ export class BuildMenu extends LitElement implements Layer {
}
private getBuildableUnits(): BuildItemDisplay[][] {
if (this.game?.config()?.disableNukes()) {
return buildTable.map((row) =>
row.filter(
(item) =>
![
UnitType.AtomBomb,
UnitType.MIRV,
UnitType.HydrogenBomb,
UnitType.MissileSilo,
UnitType.SAMLauncher,
].includes(item.unitType),
),
);
}
return buildTable;
return buildTable.map((row) =>
row.filter((item) => !this.game?.config()?.isUnitDisabled(item.unitType)),
);
}
get isVisible() {
+2 -1
View File
@@ -116,12 +116,13 @@ const GameConfigSchema = z.object({
gameType: z.nativeEnum(GameType),
gameMode: z.nativeEnum(GameMode),
disableNPCs: z.boolean(),
disableNukes: z.boolean(),
bots: z.number().int().min(0).max(400),
infiniteGold: z.boolean(),
infiniteTroops: z.boolean(),
instantBuild: z.boolean(),
maxPlayers: z.number().optional(),
numPlayerTeams: z.number().optional(),
disabledUnits: z.array(z.nativeEnum(UnitType)).optional(),
playerTeams: z.union([z.number().optional(), z.literal(Duos)]),
});
+1 -1
View File
@@ -66,7 +66,7 @@ export interface Config {
percentageTilesOwnedToWin(): number;
numBots(): number;
spawnNPCs(): boolean;
disableNukes(): boolean;
isUnitDisabled(unitType: UnitType): boolean;
bots(): number;
infiniteGold(): boolean;
infiniteTroops(): boolean;
+3 -2
View File
@@ -231,9 +231,10 @@ export class DefaultConfig implements Config {
return !this._gameConfig.disableNPCs;
}
disableNukes(): boolean {
return this._gameConfig.disableNukes;
isUnitDisabled(unitType: UnitType): boolean {
return this._gameConfig.disabledUnits?.includes(unitType) ?? false;
}
bots(): number {
return this._gameConfig.bots;
}
+1 -3
View File
@@ -421,9 +421,7 @@ export class FakeHumanExecution implements Execution {
if (this.maybeSpawnWarship()) {
return;
}
if (!this.mg.config().disableNukes()) {
this.maybeSpawnStructure(UnitType.MissileSilo, 1);
}
this.maybeSpawnStructure(UnitType.MissileSilo, 1);
}
private maybeSpawnStructure(type: UnitType, maxNum: number) {
+8 -13
View File
@@ -709,6 +709,12 @@ export class PlayerImpl implements Player {
spawnTile: TileRef,
unitSpecificInfos: UnitSpecificInfos = {},
): UnitImpl {
if (this.mg.config().isUnitDisabled(type)) {
throw new Error(
`Attempted to build disabled unit ${type} at tile ${spawnTile} by player ${this.name()}`,
);
}
const cost = this.mg.unitInfo(type).cost(this);
const b = new UnitImpl(
type,
@@ -746,19 +752,8 @@ export class PlayerImpl implements Player {
targetTile: TileRef,
validTiles: TileRef[] | null = null,
): TileRef | false {
// prevent the building of nukes and nuke related buildings
if (this.mg.config().disableNukes()) {
if (
unitType === UnitType.MissileSilo ||
unitType === UnitType.MIRV ||
unitType === UnitType.AtomBomb ||
unitType === UnitType.HydrogenBomb ||
unitType === UnitType.SAMLauncher ||
unitType === UnitType.SAMMissile ||
unitType === UnitType.MIRVWarhead
) {
return false;
}
if (this.mg.config().isUnitDisabled(unitType)) {
return false;
}
const cost = this.mg.unitInfo(unitType).cost(this);
+1 -1
View File
@@ -34,12 +34,12 @@ export class GameManager {
gameType: GameType.Private,
difficulty: Difficulty.Medium,
disableNPCs: false,
disableNukes: false,
infiniteGold: false,
infiniteTroops: false,
instantBuild: false,
gameMode: GameMode.FFA,
bots: 400,
disabledUnits: [],
...gameConfig,
});
this.games.set(id, game);
+5 -3
View File
@@ -77,9 +77,6 @@ export class GameServer {
if (gameConfig.disableNPCs != null) {
this.gameConfig.disableNPCs = gameConfig.disableNPCs;
}
if (gameConfig.disableNukes != null) {
this.gameConfig.disableNukes = gameConfig.disableNukes;
}
if (gameConfig.bots != null) {
this.gameConfig.bots = gameConfig.bots;
}
@@ -95,6 +92,11 @@ export class GameServer {
if (gameConfig.gameMode != null) {
this.gameConfig.gameMode = gameConfig.gameMode;
}
if (gameConfig.disabledUnits != null) {
this.gameConfig.disabledUnits = gameConfig.disabledUnits;
}
if (gameConfig.playerTeams != null) {
this.gameConfig.playerTeams = gameConfig.playerTeams;
}
+1 -1
View File
@@ -162,7 +162,7 @@ export function startWorker() {
instantBuild: req.body.instantBuild,
bots: req.body.bots,
disableNPCs: req.body.disableNPCs,
disableNukes: req.body.disableNukes,
disabledUnits: req.body.disabledUnits,
gameMode: req.body.gameMode,
playerTeams: req.body.playerTeams,
});