Index: binaries/data/mods/public/gui/campaigns/default_menu/CampaignMenu.js =================================================================== --- binaries/data/mods/public/gui/campaigns/default_menu/CampaignMenu.js +++ binaries/data/mods/public/gui/campaigns/default_menu/CampaignMenu.js @@ -36,11 +36,11 @@ startScenario() { - let level = this.getSelectedLevelData(); + const level = this.getSelectedLevelData(); if (!meetsRequirements(this.run, level)) return; - let settings = { + const settings = { "mapType": level.MapType, "map": "maps/" + level.Map, "settings": { @@ -52,20 +52,49 @@ "data": this.run.data } }; - let assignments = { + const assignments = { "local": { "player": 1, "name": Engine.ConfigDB_GetValue("user", "playername.singleplayer") || Engine.GetSystemUsername() } }; - let gameSettings = new GameSettings().init(); + const gameSettings = new GameSettings().init(); gameSettings.fromInitAttributes(settings); + if (level.Preview) gameSettings.mapPreview.setCustom("cropped:" + 400/512 + "," + 300/512 + ":" + level.Preview); gameSettings.mapName.set(this.getLevelName(level)); // TODO: level description should also be passed, ideally. + if (level.useGameSetup) + { + // Setup some default AI on the non-human players. + for (let i = 1; i < gameSettings.playerCount.nbPlayers; ++i) + { + const bot = { + "bot": g_Settings.PlayerDefaults[i + 1].AI, + "difficulty": +Engine.ConfigDB_GetValue("user", "gui.gamesetup.aidifficulty"), + "behavior": Engine.ConfigDB_GetValue("user", "gui.gamesetup.aibehavior"), + }; + if (typeof level.AI === "string") + bot.bot = level.AI; + gameSettings.playerAI.set(i, bot); + } + + const attributes = gameSettings.toInitAttributes(); + attributes.guiData = { + "lockSettings": { + "map": true, + }, + }; + + Engine.SwitchGuiPage("page_gamesetup.xml", { + "gameSettings": attributes, + }); + return; + } + gameSettings.launchGame(assignments); Engine.SwitchGuiPage("page_loading.xml", { "attribs": gameSettings.toInitAttributes(), Index: binaries/data/mods/public/gui/gamesetup/Controllers/GameSettingsController.js =================================================================== --- binaries/data/mods/public/gui/gamesetup/Controllers/GameSettingsController.js +++ binaries/data/mods/public/gui/gamesetup/Controllers/GameSettingsController.js @@ -23,6 +23,7 @@ this.updateLayoutHandlers = new Set(); this.settingsChangeHandlers = new Set(); this.loadingChangeHandlers = new Set(); + this.settingsLoadedHandlers = new Set(); setupWindow.registerLoadHandler(this.onLoad.bind(this)); setupWindow.registerGetHotloadDataHandler(this.onGetHotloadData.bind(this)); @@ -66,23 +67,35 @@ this.loadingChangeHandlers.add(handler); } + /** + * @param handler will be called when the initial settings have been loaded. + */ + registerSettingsLoadedHandler(handler) + { + this.settingsLoadedHandlers.add(handler); + } + onLoad(initData, hotloadData) { if (hotloadData) this.parseSettings(hotloadData.initAttributes); - else if (g_IsController && this.persistentMatchSettings.enabled) + else if (g_IsController && (initData?.gameSettings || this.persistentMatchSettings.enabled)) { - let settings = this.persistentMatchSettings.loadFile(); + // Allow opting-in to persistence when sending initial data (though default off) + if (initData?.gameSettings) + this.persistentMatchSettings.enabled = !!initData.gameSettings?.usePersistence; + const settings = initData?.gameSettings || this.persistentMatchSettings.loadFile(); if (settings) - { this.parseSettings(settings); - // If the new settings led to AI & players conflict, remove the AI. - for (let guid in g_PlayerAssignments) - if (g_PlayerAssignments[guid].player !== -1 && - g_GameSettings.playerAI.get(g_PlayerAssignments[guid].player - 1)) - g_GameSettings.playerAI.set(g_PlayerAssignments[guid].player - 1, undefined); - } } + // If the new settings led to AI & players conflict, remove the AI. + for (const guid in g_PlayerAssignments) + if (g_PlayerAssignments[guid].player !== -1 && + g_GameSettings.playerAI.get(g_PlayerAssignments[guid].player - 1)) + g_GameSettings.playerAI.set(g_PlayerAssignments[guid].player - 1, undefined); + + for (const handler of this.settingsLoadedHandlers) + handler(); this.updateLayout(); this.setNetworkInitAttributes(); Index: binaries/data/mods/public/gui/gamesetup/Controllers/GuiData.js =================================================================== --- binaries/data/mods/public/gui/gamesetup/Controllers/GuiData.js +++ binaries/data/mods/public/gui/gamesetup/Controllers/GuiData.js @@ -8,6 +8,10 @@ { this.mapFilter = new Observable(); this.mapFilter.filter = "default"; + + // Mark some settings as unmodifiable even if they normally would be. + // TODO: increase support for this feature. + this.lockSettings = {}; } /** @@ -15,14 +19,17 @@ */ Serialize() { - let ret = { - "mapFilter": this.mapFilter.filter, + const ret = { + "mapFilter": this.mapFilter.filter }; + if (Object.keys(this.lockSettings).length) + ret.lockSettings = this.lockSettings; return ret; } Deserialize(data) { this.mapFilter.filter = data.mapFilter; + this.lockSettings = data?.lockSettings || {}; } } Index: binaries/data/mods/public/gui/gamesetup/Pages/GameSetupPage/GameSettings/GameSettingControl.js =================================================================== --- binaries/data/mods/public/gui/gamesetup/Pages/GameSetupPage/GameSettings/GameSettingControl.js +++ binaries/data/mods/public/gui/gamesetup/Pages/GameSetupPage/GameSettings/GameSettingControl.js @@ -50,6 +50,9 @@ if (this.onLoad) this.setupWindow.registerLoadHandler(this.onLoad.bind(this)); + if (this.onSettingsLoaded) + this.gameSettingsController.registerSettingsLoadedHandler(this.onSettingsLoaded.bind(this)); + if (this.onPlayerAssignmentsChange) this.playerAssignmentsController.registerPlayerAssignmentsChangeHandler(this.onPlayerAssignmentsChange.bind(this)); } Index: binaries/data/mods/public/gui/gamesetup/Pages/GameSetupPage/GameSettings/Single/Buttons/MapBrowser.js =================================================================== --- binaries/data/mods/public/gui/gamesetup/Pages/GameSetupPage/GameSettings/Single/Buttons/MapBrowser.js +++ binaries/data/mods/public/gui/gamesetup/Pages/GameSetupPage/GameSettings/Single/Buttons/MapBrowser.js @@ -8,6 +8,16 @@ Engine.SetGlobalHotkey(this.HotkeyConfig, "Press", this.onPress.bind(this)); } + onSettingsLoaded() + { + if (this.gameSettingsController.guiData.lockSettings?.map) + { + this.setEnabled(false); + this.setHidden(true); + return; + } + } + setControlHidden() { this.button.hidden = false; Index: binaries/data/mods/public/gui/gamesetup/Pages/GameSetupPage/GameSettings/Single/Dropdowns/MapFilter.js =================================================================== --- binaries/data/mods/public/gui/gamesetup/Pages/GameSetupPage/GameSettings/Single/Dropdowns/MapFilter.js +++ binaries/data/mods/public/gui/gamesetup/Pages/GameSetupPage/GameSettings/Single/Dropdowns/MapFilter.js @@ -6,8 +6,19 @@ this.values = undefined; - this.gameSettingsController.guiData.mapFilter.watch(() => this.render(), ["filter"]); - g_GameSettings.map.watch(() => this.checkMapTypeChange(), ["type"]); + } + + onSettingsLoaded() + { + if (this.gameSettingsController.guiData.lockSettings?.map) + this.setEnabled(false); + else + { + this.gameSettingsController.guiData.mapFilter.watch(() => this.render(), ["filter"]); + g_GameSettings.map.watch(() => this.checkMapTypeChange(), ["type"]); + this.checkMapTypeChange(); + } + this.render(); } onHoverChange() @@ -43,6 +54,13 @@ render() { + if (!this.enabled) + { + if (!this.hidden) + this.setHidden(true); + return; + } + // Index may have changed, reset. this.setSelectedValue(this.gameSettingsController.guiData.mapFilter.filter); this.setHidden(!this.values); Index: binaries/data/mods/public/gui/gamesetup/Pages/GameSetupPage/GameSettings/Single/Dropdowns/MapSelection.js =================================================================== --- binaries/data/mods/public/gui/gamesetup/Pages/GameSetupPage/GameSettings/Single/Dropdowns/MapSelection.js +++ binaries/data/mods/public/gui/gamesetup/Pages/GameSetupPage/GameSettings/Single/Dropdowns/MapSelection.js @@ -6,11 +6,6 @@ this.values = undefined; - g_GameSettings.map.watch(() => this.render(), ["map"]); - g_GameSettings.map.watch(() => this.updateMapList(), ["type"]); - - this.gameSettingsController.guiData.mapFilter.watch(() => this.updateMapList(), ["filter"]); - this.randomItem = { "file": this.RandomMapId, "name": setStringTags(this.RandomMapCaption, this.RandomItemTags), @@ -18,6 +13,35 @@ }; } + onSettingsLoaded() + { + if (this.gameSettingsController.guiData.lockSettings?.map) + { + if (!g_GameSettings.map) + { + error("Map setting locked but no map is selected"); + throw new Error(); + } + + this.setTitle(translate("Map")); + this.setEnabled(false); + + // Watch only for map change. + g_GameSettings.map.watch(() => this.render(), ["map"]); + } + else + { + g_GameSettings.map.watch(() => this.render(), ["map"]); + g_GameSettings.map.watch(() => this.updateMapList(), ["type"]); + + this.gameSettingsController.guiData.mapFilter.watch(() => this.updateMapList(), ["filter"]); + + this.updateMapList(); + } + + this.render(); + } + onHoverChange() { this.dropdown.tooltip = this.values.description[this.dropdown.hovered] || this.Tooltip; @@ -25,12 +49,23 @@ render() { - // Can happen with bad matchsettings + if (!this.enabled) + { + const mapData = this.mapCache.getMapData(g_GameSettings.map.mapType, g_GameSettings.map.map); + this.label.caption = g_GameSettings.mapName.value || mapData.settings.Name; + return; + } + + if (!this.values) + return; + + // We can end up with incorrect map selection when dependent settings change. if (this.values.file.indexOf(g_GameSettings.map.map) === -1) { g_GameSettings.map.selectMap(this.values.file[this.values.Default]); return; } + this.setSelectedValue(g_GameSettings.map.map); } Index: binaries/data/mods/public/gui/gamesetup/Pages/GameSetupPage/GameSettings/Single/Dropdowns/MapType.js =================================================================== --- binaries/data/mods/public/gui/gamesetup/Pages/GameSetupPage/GameSettings/Single/Dropdowns/MapType.js +++ binaries/data/mods/public/gui/gamesetup/Pages/GameSetupPage/GameSettings/Single/Dropdowns/MapType.js @@ -13,18 +13,25 @@ this.dropdown.list = g_MapTypes.Title; this.dropdown.list_data = g_MapTypes.Name; - g_GameSettings.map.watch(() => this.render(), ["type"]); + } + + onSettingsLoaded() + { + if (this.gameSettingsController.guiData.lockSettings?.map) + this.setEnabled(false); + else + { + g_GameSettings.map.watch(() => this.render(), ["type"]); + + // Select a default map type if none are currently chosen. + // This in cascade will select a default filter and a default map. + if (!g_GameSettings.map.type) + g_GameSettings.map.setType(g_MapTypes.Name[g_MapTypes.Default]); + } + this.render(); } - onLoad() - { - // Select a default map type if none are currently chosen. - // This in cascade will select a default filter and a default map. - if (!g_GameSettings.map.type) - g_GameSettings.map.setType(g_MapTypes.Name[g_MapTypes.Default]); - } - onHoverChange() { this.dropdown.tooltip = g_MapTypes.Description[this.dropdown.hovered] || this.Tooltip; @@ -32,6 +39,13 @@ render() { + if (!this.enabled) + { + if (!this.hidden) + this.setHidden(true); + return; + } + this.setSelectedValue(g_GameSettings.map.type); }