Index: binaries/data/mods/public/gui/common/settings.js =================================================================== --- binaries/data/mods/public/gui/common/settings.js +++ binaries/data/mods/public/gui/common/settings.js @@ -343,11 +343,6 @@ return deepfreeze(settings); } -function getGameSpeedChoices(allowFastForward) -{ - return prepareForDropdown(g_Settings.GameSpeeds.filter(speed => !speed.FastForward || allowFastForward)); -} - /** * Returns title or placeholder. * Index: binaries/data/mods/public/gui/gamesetup/gamesetup.js =================================================================== --- binaries/data/mods/public/gui/gamesetup/gamesetup.js +++ binaries/data/mods/public/gui/gamesetup/gamesetup.js @@ -1909,6 +1909,11 @@ updateGUIDropdown("triggerDifficulty"); } +function getGameSpeedChoices(allowFastForward) +{ + return prepareForDropdown(g_Settings.GameSpeeds.filter(speed => !speed.FastForward || allowFastForward)); +} + function reloadGameSpeedChoices() { g_GameSpeeds = getGameSpeedChoices(Object.keys(g_PlayerAssignments).every(guid => g_PlayerAssignments[guid].player == -1)); Index: binaries/data/mods/public/gui/session/DiplomacyColors.js =================================================================== --- binaries/data/mods/public/gui/session/DiplomacyColors.js +++ binaries/data/mods/public/gui/session/DiplomacyColors.js @@ -12,9 +12,18 @@ // The array of displayed player colors (either the diplomacy color or regular color for each player). this.displayedPlayerColors = undefined; + + this.diplomacyColorsChangeHandlers = []; + + registerPlayersInitHandler(this.onPlayersInit.bind(this)); + } + + registerDiplomacyColorsChangeHandler(handler) + { + this.diplomacyColorsChangeHandlers.push(handler); } - onPlayerInit() + onPlayersInit() { this.computeTeamColors(); } @@ -61,7 +70,8 @@ "selected": g_Selection.toList() }); - updateGUIObjects(); + for (let handler of this.diplomacyColorsChangeHandlers) + handler(this.enabled); } computeTeamColors() Index: binaries/data/mods/public/gui/session/GameSpeedControl.js =================================================================== --- /dev/null +++ binaries/data/mods/public/gui/session/GameSpeedControl.js @@ -0,0 +1,43 @@ +class GameSpeedControl +{ + constructor(playerViewControl) + { + this.gameSpeed = Engine.GetGUIObjectByName("gameSpeed"); + this.gameSpeed.onSelectionChange = this.onSelectionChange.bind(this); + + registerPlayersInitHandler(this.rebuild.bind(this)); + registerPlayersFinishedHandler(this.rebuild.bind(this)); + playerViewControl.registerViewedPlayerChangeHandler(this.rebuild.bind(this)); + } + + rebuild() + { + let player = g_Players[Engine.GetPlayerID()]; + + let gameSpeeds = prepareForDropdown(g_Settings.GameSpeeds.filter(speed => + !speed.FastForward || !player || player.state != "active")); + + this.gameSpeed.list = gameSpeeds.Title; + this.gameSpeed.list_data = gameSpeeds.Speed; + + let simRate = Engine.GetSimRate(); + + // If the gamespeed is something like 0.100001 from the gamesetup, set it to 0.1 + let gameSpeedIdx = gameSpeeds.Speed.indexOf(+simRate.toFixed(2)); + if (gameSpeedIdx == -1) + warn("Unknown gamespeed:" + simRate); + + this.gameSpeed.selected = gameSpeedIdx != -1 ? gameSpeedIdx : gameSpeeds.Default; + } + + toggle() + { + this.gameSpeed.hidden = !this.gameSpeed.hidden; + } + + onSelectionChange() + { + if (!g_IsNetworked) + Engine.SetSimRate(+this.gameSpeed.list_data[this.gameSpeed.selected]); + } +} Index: binaries/data/mods/public/gui/session/PauseControl.js =================================================================== --- /dev/null +++ binaries/data/mods/public/gui/session/PauseControl.js @@ -0,0 +1,68 @@ +class PauseControl +{ + constructor() + { + this.isPaused = false; + + // List of GUIDs of players who have currently paused the game, if the game is networked. + this.pausingClients = []; + + // Functions called when someone pauses + this.pauseHandlers = []; + } + + registerPauseHandler(handler) + { + this.pauseHandlers.push(handler); + } + + sendPauseChange() + { + for (let handler of this.pauseHandlers) + handler(); + } + + /** + * Pause or resume the game. + * + * If the current player ordered a pause manually, it is called explicit pause. + * If the player opened a dialog and is playing in singleplayer mode, it is called implicit. + */ + setPaused(pause = true, explicit = false) + { + // The NetServer only supports pausing after all clients finished loading the game. + if (g_IsNetworked && (!explicit || !g_IsNetworkedActive)) + return; + + if (explicit) + this.isPaused = pause; + + Engine.SetPaused(this.isPaused || pause, explicit); + + if (g_IsNetworked) + { + this.setClientPauseState(Engine.GetPlayerGUID(), this.isPaused); + return; + } + + this.sendPauseChange(); + } + + /** + * Called when a client pauses or resumes in a multiplayer game. + */ + setClientPauseState(guid, paused) + { + // Update the list of pausing clients. + let index = this.pausingClients.indexOf(guid); + if (paused && index == -1) + this.pausingClients.push(guid); + else if (!paused && index != -1) + this.pausingClients.splice(index, 1); + + Engine.SetPaused(!!this.pausingClients.length, false); + + this.sendPauseChange(); + } +} + Index: binaries/data/mods/public/gui/session/PauseOverlay.js =================================================================== --- /dev/null +++ binaries/data/mods/public/gui/session/PauseOverlay.js @@ -0,0 +1,40 @@ +class PauseOverlay +{ + constructor(pauseControl) + { + this.pauseControl = pauseControl; + + this.pausedByText = Engine.GetGUIObjectByName("pausedByText"); + this.pausedByText.hidden = !g_IsNetworked; + + this.pauseOverlay = Engine.GetGUIObjectByName("pauseOverlay"); + this.pauseOverlay.onPress = this.onPress.bind(this); + + this.resumeMessage = Engine.GetGUIObjectByName("resumeMessage"); + + pauseControl.registerPauseHandler(this.onPauseChange.bind(this)); + } + + onPress() + { + if (this.pauseControl.isPaused) + this.pauseControl.setPaused(false, true); + } + + onPauseChange() + { + let hidden = !this.pauseControl.isPaused && !this.pauseControl.pausingClients.length; + this.pauseOverlay.hidden = hidden; + if (hidden) + return; + + this.resumeMessage.hidden = !this.pauseControl.isPaused; + + this.pausedByText.caption = sprintf(translate(this.PausedByCaption), { + "players": this.pauseControl.pausingClients.map(guid => + colorizePlayernameByGUID(guid)).join(translateWithContext("Separator for a list of players", ", ")) + }); + } +} + +PauseOverlay.prototype.PausedByCaption = markForTranslation("Paused by %(players)s"); Index: binaries/data/mods/public/gui/session/PauseOverlay.xml =================================================================== --- /dev/null +++ binaries/data/mods/public/gui/session/PauseOverlay.xml @@ -0,0 +1,16 @@ + + - - togglePause(); - - Engine.QuickSave(getSavedGameData()); Index: binaries/data/mods/public/gui/session/menu.js =================================================================== --- binaries/data/mods/public/gui/session/menu.js +++ binaries/data/mods/public/gui/session/menu.js @@ -29,19 +29,21 @@ var g_IsMenuOpen = false; -var g_IsObjectivesOpen = false; - /** * Remember last viewed summary panel and charts. */ var g_SummarySelectedData; -function initMenu() +function initMenu(playerViewControl, pauseControl) { Engine.GetGUIObjectByName("menu").size = "100%-164 " + MENU_TOP + " 100% " + MENU_BOTTOM; + Engine.GetGUIObjectByName("lobbyButton").enabled = Engine.HasXmppClient(); + playerViewControl.registerViewedPlayerChangeHandler(updateResignButton); + updatePauseButton(); + pauseControl.registerPauseHandler(updatePauseButton); // TODO: Atlas should pass g_GameAttributes.settings - for (let button of ["menuExitButton", "summaryButton", "objectivesButton"]) + for (let button of ["menuExitButton", "summaryButton"]) Engine.GetGUIObjectByName(button).enabled = !Engine.IsAtlasRunning(); } @@ -81,6 +83,22 @@ g_IsMenuOpen = !g_IsMenuOpen; } +function updatePauseButton() +{ + let pauseButton = Engine.GetGUIObjectByName("pauseButton"); + pauseButton.caption = g_PauseControl.isPaused ? translate("Resume") : translate("Pause"); + pauseButton.enabled = !g_IsObserver || !g_IsNetworked || g_IsController; + pauseButton.onPress = () => { + closeMenu(); + g_PauseControl.setPaused(!g_PauseControl.isPaused, true); + }; +} + +function updateResignButton() +{ + Engine.GetGUIObjectByName("menuResignButton").enabled = !g_IsObserver; +} + function optionsMenuButton() { closeOpenDialogs(); @@ -104,7 +122,7 @@ function resignMenuButton() { closeOpenDialogs(); - pauseGame(); + g_PauseControl.setPaused(); messageBox( 400, 200, @@ -118,7 +136,7 @@ function exitMenuButton() { closeOpenDialogs(); - pauseGame(); + g_PauseControl.setPaused(); let messageTypes = { "host": { @@ -162,6 +180,7 @@ function openDeleteDialog(selection) { closeOpenDialogs(); + g_PauseControl.setPaused(); let deleteSelectedEntities = function(selectionArg) { @@ -184,7 +203,7 @@ function openSave() { closeOpenDialogs(); - pauseGame(); + g_PauseControl.setPaused(); Engine.PushGuiPage( "page_loadgame.xml", @@ -195,7 +214,7 @@ function openOptions() { closeOpenDialogs(); - pauseGame(); + g_PauseControl.setPaused(); Engine.PushGuiPage( "page_options.xml", @@ -216,73 +235,6 @@ !Engine.GetGUIObjectByName("tutorialText").caption; } -function updateGameSpeedControl() -{ - Engine.GetGUIObjectByName("gameSpeedButton").hidden = g_IsNetworked; - - let player = g_Players[Engine.GetPlayerID()]; - g_GameSpeeds = getGameSpeedChoices(!player || player.state != "active"); - - let gameSpeed = Engine.GetGUIObjectByName("gameSpeed"); - gameSpeed.list = g_GameSpeeds.Title; - gameSpeed.list_data = g_GameSpeeds.Speed; - - let simRate = Engine.GetSimRate(); - - let gameSpeedIdx = g_GameSpeeds.Speed.indexOf(+simRate.toFixed(2)); - if (gameSpeedIdx == -1) - warn("Unknown gamespeed:" + simRate); - - gameSpeed.selected = gameSpeedIdx != -1 ? gameSpeedIdx : g_GameSpeeds.Default; - gameSpeed.onSelectionChange = function() { - changeGameSpeed(+this.list_data[this.selected]); - }; -} - -function toggleGameSpeed() -{ - let gameSpeed = Engine.GetGUIObjectByName("gameSpeed"); - gameSpeed.hidden = !gameSpeed.hidden; -} - -function toggleObjectives() -{ - let open = g_IsObjectivesOpen; - closeOpenDialogs(); - - if (!open) - openObjectives(); -} - -function openObjectives() -{ - g_IsObjectivesOpen = true; - - let player = g_Players[Engine.GetPlayerID()]; - let playerState = player && player.state; - let isActive = !playerState || playerState == "active"; - - Engine.GetGUIObjectByName("gameDescriptionText").caption = getGameDescription(); - - let objectivesPlayerstate = Engine.GetGUIObjectByName("objectivesPlayerstate"); - objectivesPlayerstate.hidden = isActive; - objectivesPlayerstate.caption = g_PlayerStateMessages[playerState] || ""; - - let gameDescription = Engine.GetGUIObjectByName("gameDescription"); - let gameDescriptionSize = gameDescription.size; - gameDescriptionSize.top = Engine.GetGUIObjectByName( - isActive ? "objectivesTitle" : "objectivesPlayerstate").size.bottom; - gameDescription.size = gameDescriptionSize; - - Engine.GetGUIObjectByName("objectivesPanel").hidden = false; -} - -function closeObjectives() -{ - g_IsObjectivesOpen = false; - Engine.GetGUIObjectByName("objectivesPanel").hidden = true; -} - /** * Allows players to see their own summary. * If they have shared ally vision researched, they are able to see the summary of there allies too. @@ -290,7 +242,7 @@ function openGameSummary() { closeOpenDialogs(); - pauseGame(); + g_PauseControl.setPaused(); let extendedSimState = Engine.GuiInterfaceCall("GetExtendedSimulationState"); Engine.PushGuiPage( @@ -309,13 +261,22 @@ }, "selectedData": g_SummarySelectedData }, - resumeGameAndSaveSummarySelectedData); + data => + { + g_SummarySelectedData = data.summarySelectedData; + + if (data.explicitResume) + resumeGame(data.explicitResume); + else + resumeGame(); + g_PauseControl.setPaused(false, false); + }); } function openStrucTree(page) { closeOpenDialogs(); - pauseGame(); + g_PauseControl.setPaused(); Engine.PushGuiPage( page, @@ -340,103 +301,23 @@ } } -/** - * Pause or resume the game. - * - * @param explicit - true if the player explicitly wants to pause or resume. - * If this argument isn't set, a multiplayer game won't be paused and the pause overlay - * won't be shown in single player. - */ -function pauseGame(pause = true, explicit = false) -{ - // The NetServer only supports pausing after all clients finished loading the game. - if (g_IsNetworked && (!explicit || !g_IsNetworkedActive)) - return; - - if (explicit) - g_Paused = pause; - - Engine.SetPaused(g_Paused || pause, !!explicit); - - if (g_IsNetworked) - { - setClientPauseState(Engine.GetPlayerGUID(), g_Paused); - return; - } - - updatePauseOverlay(); -} - -function resumeGame(explicit = false) -{ - pauseGame(false, explicit); -} - -function resumeGameAndSaveSummarySelectedData(data) +function resumeGame() { - g_SummarySelectedData = data.summarySelectedData; - resumeGame(data.explicitResume); -} - -/** - * Called when the current player toggles a pause button. - */ -function togglePause() -{ - if (!Engine.GetGUIObjectByName("pauseButton").enabled) - return; - - closeOpenDialogs(); - - pauseGame(!g_Paused, true); -} - -/** - * Called when a client pauses or resumes in a multiplayer game. - */ -function setClientPauseState(guid, paused) -{ - // Update the list of pausing clients. - let index = g_PausingClients.indexOf(guid); - if (paused && index == -1) - g_PausingClients.push(guid); - else if (!paused && index != -1) - g_PausingClients.splice(index, 1); - - updatePauseOverlay(); - - Engine.SetPaused(!!g_PausingClients.length, false); -} - -/** - * Update the pause overlay. - */ -function updatePauseOverlay() -{ - Engine.GetGUIObjectByName("pauseButton").caption = g_Paused ? translate("Resume") : translate("Pause"); - Engine.GetGUIObjectByName("resumeMessage").hidden = !g_Paused; - - Engine.GetGUIObjectByName("pausedByText").hidden = !g_IsNetworked; - Engine.GetGUIObjectByName("pausedByText").caption = sprintf(translate("Paused by %(players)s"), - { "players": g_PausingClients.map(guid => colorizePlayernameByGUID(guid)).join(translateWithContext("Separator for a list of players", ", ")) }); - - Engine.GetGUIObjectByName("pauseOverlay").hidden = !(g_Paused || g_PausingClients.length); - Engine.GetGUIObjectByName("pauseOverlay").onPress = g_Paused ? togglePause : function() {}; + g_PauseControl.setPaused(false, false); } function openManual() { closeOpenDialogs(); - pauseGame(); + g_PauseControl.setPaused(); Engine.PushGuiPage("page_manual.xml", {}, resumeGame); } function closeOpenDialogs() { closeMenu(); - closeObjectives(); - g_Chat.closePage(); g_DiplomacyDialog.close(); + g_ObjectivesDialog.close(); g_TradeDialog.close(); } Index: binaries/data/mods/public/gui/session/menu.xml =================================================================== --- binaries/data/mods/public/gui/session/menu.xml +++ binaries/data/mods/public/gui/session/menu.xml @@ -79,13 +79,11 @@ - Pause - togglePause(); - + /> { - setClientPauseState(msg.guid, msg.pause); + g_PauseControl.setClientPauseState(msg.guid, msg.pause); }, "clients-loading": msg => { handleClientsLoadingMessage(msg.guids); @@ -579,7 +579,7 @@ function onClientLeave(guid) { - setClientPauseState(guid, false); + g_PauseControl.setClientPauseState(guid, false); for (let id in g_Players) if (g_Players[id].guid == guid) @@ -689,5 +689,5 @@ } } - pauseGame(); + g_PauseControl.setPaused(); } Index: binaries/data/mods/public/gui/session/minimap/MiniMapDiplomacyColorsButton.js =================================================================== --- binaries/data/mods/public/gui/session/minimap/MiniMapDiplomacyColorsButton.js +++ binaries/data/mods/public/gui/session/minimap/MiniMapDiplomacyColorsButton.js @@ -8,22 +8,26 @@ this.diplomacyColorsButton = Engine.GetGUIObjectByName("diplomacyColorsButton"); this.diplomacyColorsButton.onPress = diplomacyColors.toggle.bind(diplomacyColors); - this.diplomacyColors = diplomacyColors; + diplomacyColors.registerDiplomacyColorsChangeHandler(this.onDiplomacyColorsChange.bind(this)); + registerHotkeyChangeHandler(this.onHotkeyChange.bind(this)); } - update() + onHotkeyChange() { this.diplomacyColorsButton.tooltip = colorizeHotkey("%(hotkey)s" + " ", "session.diplomacycolors") + translate(this.Tooltip); + } + onDiplomacyColorsChange(enabled) + { this.diplomacyColorsButton.sprite = "stretched:" + - (this.diplomacyColors.isEnabled() ? this.SpriteEnabled : this.SpriteDisabled); + (enabled ? this.SpriteEnabled : this.SpriteDisabled); this.diplomacyColorsButton.sprite_over = "stretched:" + - (this.diplomacyColors.isEnabled() ? this.SpriteEnabledOver : this.SpriteDisabledOver); + (enabled ? this.SpriteEnabledOver : this.SpriteDisabledOver); } } Index: binaries/data/mods/public/gui/session/minimap/MiniMapIdleWorkerButton.js =================================================================== --- binaries/data/mods/public/gui/session/minimap/MiniMapIdleWorkerButton.js +++ binaries/data/mods/public/gui/session/minimap/MiniMapIdleWorkerButton.js @@ -3,19 +3,26 @@ */ class MiniMapIdleWorkerButton { - constructor(idleClasses) + constructor(playerViewControl, idleClasses) { this.idleWorkerButton = Engine.GetGUIObjectByName("idleWorkerButton"); this.idleWorkerButton.onPress = this.onPress.bind(this); this.idleClasses = idleClasses; + + registerHotkeyChangeHandler(this.onHotkeyChange.bind(this)); + registerSimulationUpdateHandler(this.rebuild.bind(this)); + playerViewControl.registerViewedPlayerChangeHandler(this.rebuild.bind(this)); } - update() + onHotkeyChange() { this.idleWorkerButton.tooltip = colorizeHotkey("%(hotkey)s" + " ", "selection.idleworker") + translate(this.Tooltip); + } + rebuild() + { this.idleWorkerButton.enabled = Engine.GuiInterfaceCall("HasIdleUnits", { "viewedPlayer": g_ViewedPlayer, "idleClasses": this.idleClasses, Index: binaries/data/mods/public/gui/session/minimap/MiniMapPanel.js =================================================================== --- binaries/data/mods/public/gui/session/minimap/MiniMapPanel.js +++ binaries/data/mods/public/gui/session/minimap/MiniMapPanel.js @@ -3,16 +3,10 @@ */ class MiniMapPanel { - constructor(diplomacyColors, idleWorkerClasses) + constructor(playerViewControl, diplomacyColors, idleWorkerClasses) { this.diplomacyColorsButton = new MiniMapDiplomacyColorsButton(diplomacyColors); - this.idleWorkerButton = new MiniMapIdleWorkerButton(idleWorkerClasses); + this.idleWorkerButton = new MiniMapIdleWorkerButton(playerViewControl, idleWorkerClasses); this.minimap = new Minimap(); } - - update() - { - this.diplomacyColorsButton.update(); - this.idleWorkerButton.update(); - } } Index: binaries/data/mods/public/gui/session/objectives/ObjectivesDialog.js =================================================================== --- /dev/null +++ binaries/data/mods/public/gui/session/objectives/ObjectivesDialog.js @@ -0,0 +1,55 @@ +class ObjectivesDialog +{ + constructor(playerViewControl) + { + this.gameDescription = Engine.GetGUIObjectByName("gameDescription"); + this.objectivesPlayerstate = Engine.GetGUIObjectByName("objectivesPlayerstate"); + this.objectivesPanel = Engine.GetGUIObjectByName("objectivesPanel"); + this.objectivesTitle = Engine.GetGUIObjectByName("objectivesTitle"); + Engine.GetGUIObjectByName("gameDescriptionText").caption = getGameDescription(); + Engine.GetGUIObjectByName("closeObjectives").onPress = this.close.bind(this); + + registerPlayersInitHandler(this.rebuild.bind(this)); + registerPlayersFinishedHandler(this.rebuild.bind(this)); + playerViewControl.registerPlayerIDChangeHandler(this.rebuild.bind(this)); + } + + open() + { + this.objectivesPanel.hidden = false; + } + + close() + { + this.objectivesPanel.hidden = true; + } + + isOpen() + { + return !this.objectivesPanel.hidden; + } + + toggle() + { + let open = this.isOpen(); + + closeOpenDialogs(); + + if (!open) + this.open(); + } + + rebuild() + { + let player = g_Players[Engine.GetPlayerID()]; + let playerState = player && player.state; + let isActive = !playerState || playerState == "active"; + + this.objectivesPlayerstate.hidden = isActive; + this.objectivesPlayerstate.caption = g_PlayerStateMessages[playerState] || ""; + + let size = this.gameDescription.size; + size.top = (isActive ? this.objectivesTitle : this.objectivesPlayerstate).size.bottom; + this.gameDescription.size = size; + } +} Index: binaries/data/mods/public/gui/session/objectives/ObjectivesDialog.xml =================================================================== --- binaries/data/mods/public/gui/session/objectives/ObjectivesDialog.xml +++ binaries/data/mods/public/gui/session/objectives/ObjectivesDialog.xml @@ -39,9 +39,8 @@ /> - + Close - closeObjectives(); Index: binaries/data/mods/public/gui/session/objectives_window.xml =================================================================== --- binaries/data/mods/public/gui/session/objectives_window.xml +++ binaries/data/mods/public/gui/session/objectives_window.xml @@ -1,48 +0,0 @@ - -