mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-21 13:20:43 +00:00
Added custom disable settings (#593)
## Description: I will write an issue later as I don't have time.  <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:
+13
-1
@@ -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
@@ -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": "基本設定",
|
||||
|
||||
@@ -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),
|
||||
},
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
@@ -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)]),
|
||||
});
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user