Index: ps/trunk/binaries/data/mods/public/gui/gamesetup/Controls/GuiData.js =================================================================== --- ps/trunk/binaries/data/mods/public/gui/gamesetup/Controls/GuiData.js (revision 25100) +++ ps/trunk/binaries/data/mods/public/gui/gamesetup/Controls/GuiData.js (nonexistent) @@ -1,27 +0,0 @@ -/** - * This class contains GUI-specific gamesetting data. - */ -class GameSettingsGuiData -{ - constructor() - { - this.mapFilter = new Observable(); - this.mapFilter.filter = "default"; - } - - /** - * Serialize for network transmission, settings persistence or convenience in other GUI files. - */ - Serialize() - { - let ret = { - "mapFilter": this.mapFilter.filter, - }; - return ret; - } - - Deserialize(data) - { - this.mapFilter.filter = data.mapFilter; - } -} Property changes on: ps/trunk/binaries/data/mods/public/gui/gamesetup/Controls/GuiData.js ___________________________________________________________________ Deleted: svn:eol-style ## -1 +0,0 ## -native \ No newline at end of property Index: ps/trunk/binaries/data/mods/public/gui/gamesetup/Controls/ReadyControl.js =================================================================== --- ps/trunk/binaries/data/mods/public/gui/gamesetup/Controls/ReadyControl.js (revision 25100) +++ ps/trunk/binaries/data/mods/public/gui/gamesetup/Controls/ReadyControl.js (nonexistent) @@ -1,126 +0,0 @@ -/** - * Ready system: - * - * The ready mechanism protects the players from being assigned to a match with settings they didn't explicitly agree with. - * It shall be technically possible to start a networked game until all participating players formally agree with the chosen settings. - * - * Therefore assume the readystate from the user interface rather than trusting the server whether the current player is ready. - * The server may set readiness to false but not to true. - * - * The ReadyControl class stores the ready state of the current player and fires an event if the agreed settings changed. - */ -class ReadyControl -{ - constructor(netMessages, gameSettingsControl, startGameControl, playerAssignmentsControl) - { - this.startGameControl = startGameControl; - this.playerAssignmentsControl = playerAssignmentsControl; - - this.resetReadyHandlers = new Set(); - this.previousAssignments = {}; - - // This variable keeps track whether the local player is ready - // As part of cheat prevention, the server may set this to NotReady, but - // only the UI may set it to Ready or StayReady. - this.readyState = this.NotReady; - - netMessages.registerNetMessageHandler("ready", this.onReadyMessage.bind(this)); - gameSettingsControl.registerSettingsChangeHandler(this.onSettingsChange.bind(this)); - playerAssignmentsControl.registerClientJoinHandler(this.onClientJoin.bind(this)); - playerAssignmentsControl.registerClientLeaveHandler(this.onClientLeave.bind(this)); - } - - registerResetReadyHandler(handler) - { - this.resetReadyHandlers.add(handler); - } - - onClientJoin(newGUID, newAssignments) - { - if (newAssignments[newGUID].player != -1) - this.resetReady(); - } - - onClientLeave(guid) - { - if (g_PlayerAssignments[guid].player != -1) - this.resetReady(); - } - - onReadyMessage(message) - { - let playerAssignment = g_PlayerAssignments[message.guid]; - if (playerAssignment) - { - playerAssignment.status = message.status; - this.playerAssignmentsControl.updatePlayerAssignments(); - } - } - - onPlayerAssignmentsChange() - { - // Don't let the host tell you that you're ready when you're not. - let playerAssignment = g_PlayerAssignments[Engine.GetPlayerGUID()]; - if (playerAssignment && playerAssignment.status > this.readyState) - playerAssignment.status = this.readyState; - - for (let guid in g_PlayerAssignments) - if (this.previousAssignments[guid] && - this.previousAssignments[guid].player != g_PlayerAssignments[guid].player) - { - this.resetReady(); - return; - } - } - - onSettingsChange() - { - this.resetReady(); - } - - setReady(ready, sendMessage) - { - this.readyState = ready; - - if (sendMessage) - Engine.SendNetworkReady(ready); - - // Update GUI objects instantly if relevant settingchange was detected - let playerAssignment = g_PlayerAssignments[Engine.GetPlayerGUID()]; - if (playerAssignment) - { - playerAssignment.status = ready; - this.playerAssignmentsControl.updatePlayerAssignments(); - } - } - - resetReady() - { - // The gameStarted check is only necessary to allow the host to - // determine the Seed and random items after clicking start - if (!g_IsNetworked || this.startGameControl.gameStarted) - return; - - for (let handler of this.resetReadyHandlers) - handler(); - - if (g_IsController) - { - Engine.ClearAllPlayerReady(); - this.playerAssignmentsControl.updatePlayerAssignments(); - } - else if (this.readyState != this.StayReady) - this.setReady(this.NotReady, false); - } - - getLocalReadyState() - { - return this.readyState; - } -} - -ReadyControl.prototype.NotReady = 0; - -ReadyControl.prototype.Ready = 1; - -ReadyControl.prototype.StayReady = 2; Property changes on: ps/trunk/binaries/data/mods/public/gui/gamesetup/Controls/ReadyControl.js ___________________________________________________________________ Deleted: svn:eol-style ## -1 +0,0 ## -native \ No newline at end of property Index: ps/trunk/binaries/data/mods/public/gui/gamesetup/Controls/StartGameControl.js =================================================================== --- ps/trunk/binaries/data/mods/public/gui/gamesetup/Controls/StartGameControl.js (revision 25100) +++ ps/trunk/binaries/data/mods/public/gui/gamesetup/Controls/StartGameControl.js (nonexistent) @@ -1,53 +0,0 @@ -/** - * Cheat prevention: - * - * 1. Ensure that the host cannot start the game unless all clients agreed on the game settings using the ready system. - * - * TODO: - * 2. Ensure that the host cannot start the game with InitAttributes different from the agreed ones. - * This may be achieved by: - * - Determining the seed collectively. - * - passing the agreed game settings to the engine when starting the game instance - * - rejecting new game settings from the server after the game launch event - */ -class StartGameControl -{ - constructor(netMessages) - { - this.gameLaunchHandlers = new Set(); - - // This may be read from publicly - this.gameStarted = false; - - // In MP, the host launches the game and switches right away, - // clients switch when they receive the appropriate message. - netMessages.registerNetMessageHandler("start", this.switchToLoadingPage.bind(this)); - } - - registerLaunchGameHandler(handler) - { - this.gameLaunchHandlers.add(handler); - } - - launchGame() - { - this.gameStarted = true; - - for (let handler of this.gameLaunchHandlers) - handler(); - - g_GameSettings.launchGame(g_PlayerAssignments); - - // Switch to the loading page right away, - // the GUI will otherwise show the unrandomised settings. - this.switchToLoadingPage(); - } - - switchToLoadingPage() - { - Engine.SwitchGuiPage("page_loading.xml", { - "attribs": g_GameSettings.toInitAttributes(), - "playerAssignments": g_PlayerAssignments - }); - } -} Property changes on: ps/trunk/binaries/data/mods/public/gui/gamesetup/Controls/StartGameControl.js ___________________________________________________________________ Deleted: svn:eol-style ## -1 +0,0 ## -native \ No newline at end of property Index: ps/trunk/binaries/data/mods/public/gui/gamesetup/Controls/PlayerAssignmentsControl.js =================================================================== --- ps/trunk/binaries/data/mods/public/gui/gamesetup/Controls/PlayerAssignmentsControl.js (revision 25100) +++ ps/trunk/binaries/data/mods/public/gui/gamesetup/Controls/PlayerAssignmentsControl.js (nonexistent) @@ -1,214 +0,0 @@ -/** - * This class provides a property independent interface to g_PlayerAssignment events and actions. - */ -class PlayerAssignmentsControl -{ - constructor(setupWindow, netMessages, gameRegisterStanza) - { - this.clientJoinHandlers = new Set(); - this.clientLeaveHandlers = new Set(); - this.playerAssignmentsChangeHandlers = new Set(); - this.gameRegisterStanza = gameRegisterStanza; - - if (!g_IsNetworked) - { - let name = singleplayerName(); - - // Replace empty player name when entering a single-player match for the first time. - Engine.ConfigDB_CreateAndWriteValueToFile("user", this.ConfigNameSingleplayer, name, "config/user.cfg"); - - // By default, assign the player to the first slot. - g_PlayerAssignments = { - "local": { - "name": name, - "player": 1 - } - }; - } - - g_GameSettings.playerCount.watch(() => this.unassignInvalidPlayers(), ["nbPlayers"]); - - setupWindow.registerLoadHandler(this.onLoad.bind(this)); - setupWindow.registerGetHotloadDataHandler(this.onGetHotloadData.bind(this)); - netMessages.registerNetMessageHandler("players", this.onPlayerAssignmentMessage.bind(this)); - - this.registerClientJoinHandler(this.onClientJoin.bind(this)); - } - - registerPlayerAssignmentsChangeHandler(handler) - { - this.playerAssignmentsChangeHandlers.add(handler); - } - - unregisterPlayerAssignmentsChangeHandler(handler) - { - this.playerAssignmentsChangeHandlers.delete(handler); - } - - registerClientJoinHandler(handler) - { - this.clientJoinHandlers.add(handler); - } - - unregisterClientJoinHandler(handler) - { - this.clientJoinHandlers.delete(handler); - } - - registerClientLeaveHandler(handler) - { - this.clientLeaveHandlers.add(handler); - } - - unregisterClientLeaveHandler(handler) - { - this.clientLeaveHandlers.delete(handler); - } - - onLoad(initData, hotloadData) - { - if (hotloadData) - { - g_PlayerAssignments = hotloadData.playerAssignments; - this.updatePlayerAssignments(); - } - else if (!g_IsNetworked) - { - // Simulate a net message for the local player to keep a common path. - this.onPlayerAssignmentMessage({ - "newAssignments": g_PlayerAssignments - }); - } - } - - onGetHotloadData(object) - { - object.playerAssignments = g_PlayerAssignments; - } - - /** - * On client join, try to assign them to a free slot. - * (This is called before g_PlayerAssignments is updated). - */ - onClientJoin(newGUID, newAssignments) - { - if (!g_IsController || newAssignments[newGUID].player != -1) - return; - - // Assign the client (or only buddies if prefered) to a free slot - if (newGUID != Engine.GetPlayerGUID()) - { - let assignOption = Engine.ConfigDB_GetValue("user", this.ConfigAssignPlayers); - if (assignOption == "disabled" || - assignOption == "buddies" && g_Buddies.indexOf(splitRatingFromNick(newAssignments[newGUID].name).nick) == -1) - return; - } - - // Find a player slot that no other player is assigned to. - let firstFreeSlot = [...Array(g_MaxPlayers).keys()]; - firstFreeSlot = firstFreeSlot.find(i => { - for (let guid in newAssignments) - if (newAssignments[guid].player == i + 1) - return false; - return true; - }); - if (firstFreeSlot === -1) - return; - - this.assignClient(newGUID, firstFreeSlot + 1); - } - - /** - * To be called when g_PlayerAssignments is modified. - */ - updatePlayerAssignments() - { - Engine.ProfileStart("updatePlayerAssignments"); - for (let handler of this.playerAssignmentsChangeHandlers) - handler(); - Engine.ProfileStop(); - } - - /** - * Called whenever a client joins or leaves or any game setting is changed. - */ - onPlayerAssignmentMessage(message) - { - let newAssignments = message.newAssignments; - for (let guid in newAssignments) - if (!g_PlayerAssignments[guid]) - for (let handler of this.clientJoinHandlers) - handler(guid, message.newAssignments); - - for (let guid in g_PlayerAssignments) - if (!newAssignments[guid]) - for (let handler of this.clientLeaveHandlers) - handler(guid); - - g_PlayerAssignments = newAssignments; - this.updatePlayerAssignments(); - // Send at most one gameRegisterStanza after all handlers run in case a - // joining observer has been assigned to a playerslot. - this.gameRegisterStanza.sendImmediately?.(); - } - - assignClient(guid, playerIndex) - { - if (g_IsNetworked) - Engine.AssignNetworkPlayer(playerIndex, guid); - else - { - g_PlayerAssignments[guid].player = playerIndex; - this.updatePlayerAssignments(); - } - } - - /** - * If both clients are assigned players, this will swap their assignments. - */ - assignPlayer(guidToAssign, playerIndex) - { - if (g_PlayerAssignments[guidToAssign].player != -1) - { - for (let guid in g_PlayerAssignments) - if (g_PlayerAssignments[guid].player == playerIndex + 1) - { - this.assignClient(guid, g_PlayerAssignments[guidToAssign].player); - break; - } - } - this.assignClient(guidToAssign, playerIndex + 1); - } - - unassignClient(playerID) - { - if (g_IsNetworked) - Engine.AssignNetworkPlayer(playerID, ""); - else if (g_PlayerAssignments.local.player == playerID) - { - g_PlayerAssignments.local.player = -1; - this.updatePlayerAssignments(); - } - } - - unassignInvalidPlayers() - { - if (g_IsNetworked) - { - for (let guid in g_PlayerAssignments) - if (g_PlayerAssignments[guid].player > g_GameSettings.playerCount.nbPlayers) - Engine.AssignNetworkPlayer(g_PlayerAssignments[guid].player, ""); - } - else if (g_PlayerAssignments.local.player > g_GameSettings.playerCount.nbPlayers) - { - g_PlayerAssignments.local.player = -1; - this.updatePlayerAssignments(); - } - } -} - -PlayerAssignmentsControl.prototype.ConfigNameSingleplayer = - "playername.singleplayer"; - -PlayerAssignmentsControl.prototype.ConfigAssignPlayers = - "gui.gamesetup.assignplayers"; Property changes on: ps/trunk/binaries/data/mods/public/gui/gamesetup/Controls/PlayerAssignmentsControl.js ___________________________________________________________________ Deleted: svn:eol-style ## -1 +0,0 ## -native \ No newline at end of property Index: ps/trunk/binaries/data/mods/public/gui/gamesetup/Controls/GameSettingsFile.js =================================================================== --- ps/trunk/binaries/data/mods/public/gui/gamesetup/Controls/GameSettingsFile.js (revision 25100) +++ ps/trunk/binaries/data/mods/public/gui/gamesetup/Controls/GameSettingsFile.js (nonexistent) @@ -1,60 +0,0 @@ -/** - * This class provides a way to save game settings to a file and load them. - */ -class GameSettingsFile -{ - constructor(GameSettingsControl) - { - this.filename = g_IsNetworked ? - this.PersistedSettingsFileMultiplayer : - this.PersistedSettingsFileSingleplayer; - - this.gameSettingsControl = GameSettingsControl; - - this.engineInfo = Engine.GetEngineInfo(); - this.enabled = Engine.ConfigDB_GetValue("user", this.ConfigName) == "true"; - } - - loadFile() - { - Engine.ProfileStart("loadPersistMatchSettingsFile"); - - let data = - this.enabled && - g_IsController && - Engine.FileExists(this.filename) && - Engine.ReadJSONFile(this.filename); - - let persistedSettings = data?.engine_info?.engine_version == this.engineInfo.engine_version && - hasSameMods(data?.engine_info?.mods, this.engineInfo.mods) && - data.attributes || {}; - - Engine.ProfileStop(); - return persistedSettings; - } - - /** - * Delete settings if disabled, so that players are not confronted with old settings after enabling the setting again. - */ - saveFile() - { - if (!g_IsController) - return; - - Engine.ProfileStart("savePersistMatchSettingsFile"); - Engine.WriteJSONFile(this.filename, { - "attributes": this.enabled ? this.gameSettingsControl.getSettings() : {}, - "engine_info": this.engineInfo - }); - Engine.ProfileStop(); - } -} - -GameSettingsFile.prototype.ConfigName = - "persistmatchsettings"; - -GameSettingsFile.prototype.PersistedSettingsFileSingleplayer = - "config/matchsettings.json"; - -GameSettingsFile.prototype.PersistedSettingsFileMultiplayer = - "config/matchsettings.mp.json"; Property changes on: ps/trunk/binaries/data/mods/public/gui/gamesetup/Controls/GameSettingsFile.js ___________________________________________________________________ Deleted: svn:eol-style ## -1 +0,0 ## -native \ No newline at end of property Index: ps/trunk/binaries/data/mods/public/gui/gamesetup/Controls/GameSettingsControl.js =================================================================== --- ps/trunk/binaries/data/mods/public/gui/gamesetup/Controls/GameSettingsControl.js (revision 25100) +++ ps/trunk/binaries/data/mods/public/gui/gamesetup/Controls/GameSettingsControl.js (nonexistent) @@ -1,240 +0,0 @@ -/** - * 'Controller' for the GUI handling of gamesettings. - */ -class GameSettingsControl -{ - constructor(setupWindow, netMessages, startGameControl, playerAssignmentsControl, mapCache) - { - this.setupWindow = setupWindow; - this.startGameControl = startGameControl; - this.mapCache = mapCache; - this.gameSettingsFile = new GameSettingsFile(this); - - this.guiData = new GameSettingsGuiData(); - - // When joining a game, the complete set of attributes - // may not have been received yet. - this.loading = true; - - this.updateLayoutHandlers = new Set(); - this.settingsChangeHandlers = new Set(); - this.loadingChangeHandlers = new Set(); - - setupWindow.registerLoadHandler(this.onLoad.bind(this)); - setupWindow.registerGetHotloadDataHandler(this.onGetHotloadData.bind(this)); - - startGameControl.registerLaunchGameHandler(this.onLaunchGame.bind(this)); - - setupWindow.registerClosePageHandler(this.onClose.bind(this)); - - if (g_IsController && g_IsNetworked) - playerAssignmentsControl.registerClientJoinHandler(this.onClientJoin.bind(this)); - - if (g_IsNetworked) - netMessages.registerNetMessageHandler("gamesetup", this.onGamesetupMessage.bind(this)); - } - - /** - * @param handler will be called when the layout needs to be updated. - */ - registerUpdateLayoutHandler(handler) - { - this.updateLayoutHandlers.add(handler); - } - - /** - * @param handler will be called when any setting change. - * (this isn't exactly what happens but the behaviour should be similar). - */ - registerSettingsChangeHandler(handler) - { - this.settingsChangeHandlers.add(handler); - } - - /** - * @param handler will be called when the 'loading' state change. - */ - registerLoadingChangeHandler(handler) - { - this.loadingChangeHandlers.add(handler); - } - - onLoad(initData, hotloadData) - { - if (hotloadData) - this.parseSettings(hotloadData.initAttributes); - else if (g_IsController && this.gameSettingsFile.enabled) - { - let settings = this.gameSettingsFile.loadFile(); - if (settings) - this.parseSettings(settings); - } - - this.updateLayout(); - this.setNetworkInitAttributes(); - - // If we are the controller, we are done loading. - if (hotloadData || !g_IsNetworked || g_IsController) - this.setLoading(false); - } - - onClose() - { - this.gameSettingsFile.saveFile(); - } - - onClientJoin() - { - /** - * A note on network synchronization: - * The net server does not keep the current state of attributes, - * nor does it act like a message queue, so a new client - * will only receive updates after they've joined. - * In particular, new joiners start with no information, - * so the controller must first send them a complete copy of the settings. - * However, messages could be in-flight towards the controller, - * but the new client may never receive these or have already received them, - * leading to an ordering issue that might desync the new client. - * - * The simplest solution is to have the (single) controller - * act as the single source of truth. Any other message must - * first go through the controller, which will send updates. - * This enforces the ordering of the controller. - * In practical terms, if e.g. players controlling their own civ is implemented, - * the message will need to be ignored by everyone but the controller, - * and the controller will need to send an update once it rejects/accepts the changes, - * which will then update the other clients. - * Of course, the original client GUI may want to temporarily show a different state. - * Note that the final attributes are sent on game start anyways, so any - * synchronization issue that might happen at that point can be resolved. - */ - Engine.SendGameSetupMessage({ - "type": "initial-update", - "initAttribs": this.getSettings() - }); - } - - onGetHotloadData(object) - { - object.initAttributes = this.getSettings(); - } - - onGamesetupMessage(message) - { - // For now, the controller only can send updates, so no need to listen to messages. - if (!message.data || g_IsController) - return; - - if (message.data.type !== "update" && - message.data.type !== "initial-update") - { - error("Unknown message type " + message.data.type); - return; - } - - if (message.data.type === "initial-update") - { - // Ignore initial updates if we've already received settings. - if (!this.loading) - return; - this.setLoading(false); - } - - this.parseSettings(message.data.initAttribs); - - // This assumes that messages aren't sent spuriously without changes - // (which is generally fair), but technically it would be good - // to check if the new data is different from the previous data. - for (let handler of this.settingsChangeHandlers) - handler(); - } - - /** - * Returns the InitAttributes, augmented by GUI-specific data. - */ - getSettings() - { - let ret = g_GameSettings.toInitAttributes(); - ret.guiData = this.guiData.Serialize(); - return ret; - } - - /** - * Parse the following settings. - */ - parseSettings(settings) - { - if (settings.guiData) - this.guiData.Deserialize(settings.guiData); - g_GameSettings.fromInitAttributes(settings); - } - - setLoading(loading) - { - if (this.loading === loading) - return; - this.loading = loading; - for (let handler of this.loadingChangeHandlers) - handler(); - } - - /** - * This should be called whenever the GUI layout needs to be updated. - * Triggers on the next GUI tick to avoid un-necessary layout. - */ - updateLayout() - { - if (this.layoutTimer) - return; - this.layoutTimer = setTimeout(() => { - for (let handler of this.updateLayoutHandlers) - handler(); - delete this.layoutTimer; - }, 0); - } - - /** - * This function is to be called when a GUI control has initiated a value change. - * - * To avoid an infinite loop, do not call this function when a game setup message was - * received and the data had only been modified deterministically. - * - * This is run on a timer to avoid flooding the network with messages, - * e.g. when modifying a slider. - */ - setNetworkInitAttributes() - { - for (let handler of this.settingsChangeHandlers) - handler(); - - if (g_IsNetworked && this.timer === undefined) - this.timer = setTimeout(this.setNetworkInitAttributesImmediately.bind(this), this.Timeout); - } - - setNetworkInitAttributesImmediately() - { - if (this.timer) - { - clearTimeout(this.timer); - delete this.timer; - } - // See note in onClientJoin on network synchronization. - if (g_IsController) - Engine.SendGameSetupMessage({ - "type": "update", - "initAttribs": this.getSettings() - }); - } - - onLaunchGame() - { - // Save the file before random settings are resolved. - this.gameSettingsFile.saveFile(); - } -} - - -/** - * Wait (at most) this many milliseconds before sending network messages. - */ -GameSettingsControl.prototype.Timeout = 400; Property changes on: ps/trunk/binaries/data/mods/public/gui/gamesetup/Controls/GameSettingsControl.js ___________________________________________________________________ Deleted: svn:eol-style ## -1 +0,0 ## -native \ No newline at end of property Index: ps/trunk/binaries/data/mods/public/gui/gamesetup/NetMessages/GameRegisterStanza.js =================================================================== --- ps/trunk/binaries/data/mods/public/gui/gamesetup/NetMessages/GameRegisterStanza.js (revision 25100) +++ ps/trunk/binaries/data/mods/public/gui/gamesetup/NetMessages/GameRegisterStanza.js (nonexistent) @@ -1,142 +0,0 @@ -/** - * If there is an XmppClient, this class informs the XPartaMuPP lobby bot that - * this match is being setup so that others can join. - * It informs of the lobby of some setting values and the participating clients. - */ -class GameRegisterStanza -{ - constructor(initData, setupWindow, netMessages, mapCache) - { - this.mapCache = mapCache; - - this.serverName = initData.serverName; - this.hasPassword = initData.hasPassword; - - this.mods = JSON.stringify(Engine.GetEngineInfo().mods); - this.timer = undefined; - - // Only send a lobby update when its data changed - this.lastStanza = undefined; - - // Events - setupWindow.registerClosePageHandler(this.onClosePage.bind(this)); - netMessages.registerNetMessageHandler("start", this.onGameStart.bind(this)); - - g_GameSettings.map.watch(() => this.onSettingsChange(), ["map", "type"]); - g_GameSettings.mapSize.watch(() => this.onSettingsChange(), ["size"]); - g_GameSettings.victoryConditions.watch(() => this.onSettingsChange(), ["active"]); - g_GameSettings.playerCount.watch(() => this.onSettingsChange(), ["nbPlayers"]); - } - - onSettingsChange() - { - if (this.lastStanza) - this.sendDelayed(); - else - this.sendImmediately(); - } - - onGameStart() - { - if (!g_IsController || !Engine.HasXmppClient()) - return; - - this.sendImmediately(); - let clients = this.formatClientsForStanza(); - Engine.SendChangeStateGame(clients.connectedPlayers, clients.list); - } - - onClosePage() - { - if (g_IsController && Engine.HasXmppClient()) - Engine.SendUnregisterGame(); - } - - /** - * Send the relevant game settings to the lobby bot in a deferred manner. - */ - sendDelayed() - { - if (!g_IsController || !Engine.HasXmppClient()) - return; - - if (this.timer !== undefined) - clearTimeout(this.timer); - - this.timer = setTimeout(this.sendImmediately.bind(this), this.Timeout); - } - - /** - * Send the relevant game settings to the lobby bot immediately. - */ - sendImmediately() - { - if (!g_IsController || !Engine.HasXmppClient()) - return; - - Engine.ProfileStart("sendRegisterGameStanza"); - - if (this.timer !== undefined) - { - clearTimeout(this.timer); - this.timer = undefined; - } - - let clients = this.formatClientsForStanza(); - - let stanza = { - "name": this.serverName, - "hostUsername": Engine.LobbyGetNick(), - "mapName": g_GameSettings.map.map, - "niceMapName": this.mapCache.getTranslatableMapName(g_GameSettings.map.type, g_GameSettings.map.map), - "mapSize": g_GameSettings.map.type == "random" ? g_GameSettings.mapSize.size : "Default", - "mapType": g_GameSettings.map.type, - "victoryConditions": Array.from(g_GameSettings.victoryConditions.active).join(","), - "nbp": clients.connectedPlayers, - "maxnbp": g_GameSettings.playerCount.nbPlayers, - "players": clients.list, - "mods": this.mods, - "hasPassword": this.hasPassword || "" - }; - - // Only send the stanza if one of these properties changed - if (this.lastStanza && Object.keys(stanza).every(prop => this.lastStanza[prop] == stanza[prop])) - return; - - this.lastStanza = stanza; - Engine.SendRegisterGame(stanza); - Engine.ProfileStop(); - } - - /** - * Send a list of playernames and distinct between players and observers. - * Don't send teams, AIs or anything else until the game was started. - */ - formatClientsForStanza() - { - let connectedPlayers = 0; - let playerData = []; - - for (let guid in g_PlayerAssignments) - { - let pData = { "Name": g_PlayerAssignments[guid].name }; - - if (g_PlayerAssignments[guid].player <= g_GameSettings.playerCount.nbPlayers) - ++connectedPlayers; - else - pData.Team = "observer"; - - playerData.push(pData); - } - - return { - "list": playerDataToStringifiedTeamList(playerData), - "connectedPlayers": connectedPlayers - }; - } -} - -/** - * Send the current game settings to the lobby bot if the settings didn't change for this number of milliseconds. - */ -GameRegisterStanza.prototype.Timeout = 2000; Property changes on: ps/trunk/binaries/data/mods/public/gui/gamesetup/NetMessages/GameRegisterStanza.js ___________________________________________________________________ Deleted: svn:eol-style ## -1 +0,0 ## -native \ No newline at end of property Index: ps/trunk/binaries/data/mods/public/gui/gamesetup/Pages/GameSetupPage/GameSettings/Single/Checkboxes/WorldPopulation.js =================================================================== --- ps/trunk/binaries/data/mods/public/gui/gamesetup/Pages/GameSetupPage/GameSettings/Single/Checkboxes/WorldPopulation.js (revision 25100) +++ ps/trunk/binaries/data/mods/public/gui/gamesetup/Pages/GameSetupPage/GameSettings/Single/Checkboxes/WorldPopulation.js (revision 25101) @@ -1,28 +1,28 @@ GameSettingControls.WorldPopulation = class WorldPopulation extends GameSettingControlCheckbox { constructor(...args) { super(...args); g_GameSettings.population.watch(() => this.render(), ["useWorldPop"]); g_GameSettings.map.watch(() => this.render(), ["type"]); this.render(); } render() { this.setEnabled(g_GameSettings.map.type != "scenario"); this.setChecked(g_GameSettings.population.useWorldPop); } onPress(checked) { g_GameSettings.population.setPopCap(checked); - this.gameSettingsControl.setNetworkInitAttributes(); + this.gameSettingsController.setNetworkInitAttributes(); } }; GameSettingControls.WorldPopulation.prototype.TitleCaption = translate("World population"); GameSettingControls.WorldPopulation.prototype.Tooltip = translate("When checked the Population Cap will be evenly distributed over all living players."); Index: ps/trunk/binaries/data/mods/public/gui/gamesetup/Pages/GameSetupPage/GameSettings/Single/Dropdowns/Daytime.js =================================================================== --- ps/trunk/binaries/data/mods/public/gui/gamesetup/Pages/GameSetupPage/GameSettings/Single/Dropdowns/Daytime.js (revision 25100) +++ ps/trunk/binaries/data/mods/public/gui/gamesetup/Pages/GameSetupPage/GameSettings/Single/Dropdowns/Daytime.js (revision 25101) @@ -1,68 +1,68 @@ GameSettingControls.Daytime = class Daytime extends GameSettingControlDropdown { constructor(...args) { super(...args); this.values = undefined; g_GameSettings.daytime.watch(() => this.render(), ["value", "data"]); this.render(); } onHoverChange() { this.dropdown.tooltip = this.values.Description[this.dropdown.hovered] || this.Tooltip; } render() { this.setHidden(!g_GameSettings.daytime.data); if (!g_GameSettings.daytime.data) return; this.values = prepareForDropdown([ { "Id": "random", "Name": setStringTags(this.RandomTitle, this.RandomItemTags), "Description": this.RandomDescription, "Preview": g_GameSettings.map.data.settings.Preview }, ...g_GameSettings.daytime.data.map(item => ({ "Id": item.Id, "Name": translate(item.Name), "Description": translate(item.Description), "Preview": item.Preview })) ]); this.dropdown.list = this.values.Name; this.dropdown.list_data = this.values.Id; this.setSelectedValue(g_GameSettings.daytime.value); } getAutocompleteEntries() { return this.values && this.values.Name.slice(1); } onSelectionChange(itemIdx) { g_GameSettings.daytime.setValue(this.values.Id[itemIdx]); - this.gameSettingsControl.setNetworkInitAttributes(); + this.gameSettingsController.setNetworkInitAttributes(); } }; GameSettingControls.Daytime.prototype.TitleCaption = translate("Daytime"); GameSettingControls.Daytime.prototype.Tooltip = translate("Select whether the match takes place at daylight or night."); GameSettingControls.Daytime.prototype.RandomTitle = translateWithContext("daytime selection", "Random"); GameSettingControls.Daytime.prototype.RandomDescription = translateWithContext("daytime selection", "Randomly pick a time of the day."); GameSettingControls.Daytime.prototype.AutocompleteOrder = 0; Index: ps/trunk/binaries/data/mods/public/gui/gamesetup/Pages/GameSetupPage/GameSettings/Single/Dropdowns/Landscape.js =================================================================== --- ps/trunk/binaries/data/mods/public/gui/gamesetup/Pages/GameSetupPage/GameSettings/Single/Dropdowns/Landscape.js (revision 25100) +++ ps/trunk/binaries/data/mods/public/gui/gamesetup/Pages/GameSetupPage/GameSettings/Single/Dropdowns/Landscape.js (revision 25101) @@ -1,83 +1,83 @@ GameSettingControls.Landscape = class Landscape extends GameSettingControlDropdown { constructor(...args) { super(...args); this.values = undefined; g_GameSettings.landscape.watch(() => this.render(), ["value", "data"]); this.render(); } onHoverChange() { this.dropdown.tooltip = this.values.Description[this.dropdown.hovered] || this.Tooltip; } render() { this.setHidden(!g_GameSettings.landscape.data); if (!g_GameSettings.landscape.data) return; let randomItems = [{ "Id": "random", "Name": setStringTags(translateWithContext("landscape selection", "Random"), this.RandomItemTags), "Description": translateWithContext("landscape selection", "Select a random landscape.") }]; let data = g_GameSettings.landscape.data; let items = []; for (let group of data) { let itemTag = this.translateItem(group); itemTag.Name = setStringTags(itemTag.Name, this.RandomItemTags); randomItems.push(itemTag); let sort = (item1, item2) => item1.Name > item2.Name; items = items.concat(group.Items.map(this.translateItem).sort(sort)); } this.values = prepareForDropdown(randomItems.concat(items)); this.dropdown.list = this.values.Name; this.dropdown.list_data = this.values.Id; this.setSelectedValue(g_GameSettings.landscape.value); } translateItem(item) { return { "Id": item.Id, "Name": translate(item.Name), "Description": translate(item.Description), "Preview": item.Preview }; } getAutocompleteEntries() { if (!this.values) return undefined; let entries = []; for (let i = 0; i < this.values.Id.length; ++i) if (!this.values.Id[i].startsWith("random")) entries.push(this.values.Name[i]); return entries; } onSelectionChange(itemIdx) { g_GameSettings.landscape.setValue(this.values.Id[itemIdx]); - this.gameSettingsControl.setNetworkInitAttributes(); + this.gameSettingsController.setNetworkInitAttributes(); } }; GameSettingControls.Landscape.prototype.TitleCaption = translate("Landscape"); GameSettingControls.Landscape.prototype.Tooltip = translate("Select one of the landscapes of this map."); GameSettingControls.Landscape.prototype.AutocompleteOrder = 0; Index: ps/trunk/binaries/data/mods/public/gui/gamesetup/Pages/GameSetupPage/GameSettings/Single/Dropdowns/MapSelection.js =================================================================== --- ps/trunk/binaries/data/mods/public/gui/gamesetup/Pages/GameSetupPage/GameSettings/Single/Dropdowns/MapSelection.js (revision 25100) +++ ps/trunk/binaries/data/mods/public/gui/gamesetup/Pages/GameSetupPage/GameSettings/Single/Dropdowns/MapSelection.js (revision 25101) @@ -1,116 +1,116 @@ GameSettingControls.MapSelection = class MapSelection extends GameSettingControlDropdown { constructor(...args) { super(...args); this.values = undefined; g_GameSettings.map.watch(() => this.render(), ["map"]); g_GameSettings.map.watch(() => this.updateMapList(), ["type"]); - this.gameSettingsControl.guiData.mapFilter.watch(() => this.updateMapList(), ["filter"]); + this.gameSettingsController.guiData.mapFilter.watch(() => this.updateMapList(), ["filter"]); this.randomItem = { "file": this.RandomMapId, "name": setStringTags(this.RandomMapCaption, this.RandomItemTags), "description": this.RandomMapDescription }; } onHoverChange() { this.dropdown.tooltip = this.values.description[this.dropdown.hovered] || this.Tooltip; } render() { // Can happen with bad matchsettings 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); } updateMapList() { Engine.ProfileStart("updateMapSelectionList"); if (!g_GameSettings.map.type) return; { let values = this.mapFilters.getFilteredMaps( g_GameSettings.map.type, - this.gameSettingsControl.guiData.mapFilter.filter, + this.gameSettingsController.guiData.mapFilter.filter, false); values.sort(sortNameIgnoreCase); if (g_GameSettings.map.type == "random") values.unshift(this.randomItem); this.values = prepareForDropdown(values); } this.dropdown.list = this.values.name; this.dropdown.list_data = this.values.file; g_GameSettings.map.setRandomOptions(this.values.file); // Reset the selected map. if (this.values.file.indexOf(g_GameSettings.map.map) === -1) { g_GameSettings.map.selectMap(this.values.file[this.values.Default]); - this.gameSettingsControl.setNetworkInitAttributes(); + this.gameSettingsController.setNetworkInitAttributes(); } // The index may have changed: reset. this.setSelectedValue(g_GameSettings.map.map); Engine.ProfileStop(); } onSelectionChange(itemIdx) { // The triggering that happens on map change can be just slow enough // that the next event happens before we're done when scrolling, // and then the scrolling is not smooth since it can take arbitrarily long to render. // To avoid that, run the change on the next GUI tick, and only do one increment. // TODO: the problem is mostly that updating visibility can relayout the gamesetting, // which takes a few ms, but this could only be done once per frame anyways. // NB: this technically makes it possible to start the game without the change going through // but it's essentially impossible to trigger accidentally. if (this.reRenderTimeout) return; this.reRenderTimeout = setTimeout(() => { g_GameSettings.map.selectMap(this.values.file[itemIdx]); - this.gameSettingsControl.setNetworkInitAttributes(); + this.gameSettingsController.setNetworkInitAttributes(); delete this.reRenderTimeout; }, 0); } getAutocompleteEntries() { return this.values.name; } }; GameSettingControls.MapSelection.prototype.TitleCaption = translate("Select Map"); GameSettingControls.MapSelection.prototype.Tooltip = translate("Select a map to play on."); GameSettingControls.MapSelection.prototype.RandomMapId = "random"; GameSettingControls.MapSelection.prototype.RandomMapCaption = translateWithContext("map selection", "Random"); GameSettingControls.MapSelection.prototype.RandomMapDescription = translate("Pick any of the given maps at random."); GameSettingControls.MapSelection.prototype.AutocompleteOrder = 0; Index: ps/trunk/binaries/data/mods/public/gui/gamesetup/Pages/GameSetupPage/GameSettings/Single/Dropdowns/MapType.js =================================================================== --- ps/trunk/binaries/data/mods/public/gui/gamesetup/Pages/GameSetupPage/GameSettings/Single/Dropdowns/MapType.js (revision 25100) +++ ps/trunk/binaries/data/mods/public/gui/gamesetup/Pages/GameSetupPage/GameSettings/Single/Dropdowns/MapType.js (revision 25101) @@ -1,56 +1,56 @@ /** * Maptype design: * Scenario maps have fixed terrain and all settings predetermined. * Skirmish maps have fixed terrain, playercount but settings are free. * For random maps, settings are fully determined by the player and the terrain is generated based on them. */ GameSettingControls.MapType = class MapType extends GameSettingControlDropdown { constructor(...args) { super(...args); this.dropdown.list = g_MapTypes.Title; this.dropdown.list_data = g_MapTypes.Name; g_GameSettings.map.watch(() => this.render(), ["type"]); 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; } render() { this.setSelectedValue(g_GameSettings.map.type); } getAutocompleteEntries() { return g_MapTypes.Title; } onSelectionChange(itemIdx) { g_GameSettings.map.setType(g_MapTypes.Name[itemIdx]); - this.gameSettingsControl.setNetworkInitAttributes(); + this.gameSettingsController.setNetworkInitAttributes(); } }; GameSettingControls.MapType.prototype.TitleCaption = translate("Map Type"); GameSettingControls.MapType.prototype.Tooltip = translate("Select a map type."); GameSettingControls.MapType.prototype.AutocompleteOrder = 0; Index: ps/trunk/binaries/data/mods/public/gui/gamesetup/Pages/GameSetupPage/GameSettings/Single/Dropdowns/StartingResources.js =================================================================== --- ps/trunk/binaries/data/mods/public/gui/gamesetup/Pages/GameSetupPage/GameSettings/Single/Dropdowns/StartingResources.js (revision 25100) +++ ps/trunk/binaries/data/mods/public/gui/gamesetup/Pages/GameSetupPage/GameSettings/Single/Dropdowns/StartingResources.js (revision 25101) @@ -1,61 +1,61 @@ GameSettingControls.StartingResources = class StartingResources extends GameSettingControlDropdown { constructor(...args) { super(...args); this.dropdown.list = g_StartingResources.Title; this.dropdown.list_data = g_StartingResources.Resources; this.sprintfArgs = {}; g_GameSettings.startingResources.watch(() => this.render(), ["resources", "perPlayer"]); g_GameSettings.map.watch(() => this.render(), ["type"]); this.render(); } onHoverChange() { let tooltip = this.Tooltip; if (this.dropdown.hovered != -1) { this.sprintfArgs.resources = g_StartingResources.Resources[this.dropdown.hovered]; tooltip = sprintf(this.HoverTooltip, this.sprintfArgs); } this.dropdown.tooltip = tooltip; } render() { this.setEnabled(g_GameSettings.map.type != "scenario" && !g_GameSettings.startingResources.perPlayer); if (g_GameSettings.startingResources.perPlayer) this.label.caption = this.PerPlayerCaption; else this.setSelectedValue(g_GameSettings.startingResources.resources); } getAutocompleteEntries() { return g_StartingResources.Title; } onSelectionChange(itemIdx) { g_GameSettings.startingResources.setResources(g_StartingResources.Resources[itemIdx]); - this.gameSettingsControl.setNetworkInitAttributes(); + this.gameSettingsController.setNetworkInitAttributes(); } }; GameSettingControls.StartingResources.prototype.TitleCaption = translate("Starting Resources"); GameSettingControls.StartingResources.prototype.Tooltip = translate("Select the game's starting resources."); GameSettingControls.StartingResources.prototype.HoverTooltip = translate("Initial amount of each resource: %(resources)s."); GameSettingControls.StartingResources.prototype.PerPlayerCaption = translateWithContext("starting resources", "Per Player"); GameSettingControls.StartingResources.prototype.AutocompleteOrder = 0; Index: ps/trunk/binaries/data/mods/public/gui/gamesetup/Pages/GameSetupPage/GameSettings/Single/Dropdowns/WorldPopulationCap.js =================================================================== --- ps/trunk/binaries/data/mods/public/gui/gamesetup/Pages/GameSetupPage/GameSettings/Single/Dropdowns/WorldPopulationCap.js (revision 25100) +++ ps/trunk/binaries/data/mods/public/gui/gamesetup/Pages/GameSetupPage/GameSettings/Single/Dropdowns/WorldPopulationCap.js (revision 25101) @@ -1,62 +1,62 @@ GameSettingControls.WorldPopulationCap = class WorldPopulationCap extends GameSettingControlDropdown { constructor(...args) { super(...args); this.dropdown.list = g_WorldPopulationCapacities.Title; this.dropdown.list_data = g_WorldPopulationCapacities.Population; this.sprintfArgs = {}; g_GameSettings.population.watch(() => this.render(), ["useWorldPop", "cap"]); g_GameSettings.map.watch(() => this.render(), ["type"]); this.render(); } render() { this.setEnabled(g_GameSettings.map.type != "scenario"); this.setHidden(!g_GameSettings.population.useWorldPop); this.setSelectedValue(g_GameSettings.population.cap); } onHoverChange() { let tooltip = this.Tooltip; if (this.dropdown.hovered != -1) { let popCap = g_WorldPopulationCapacities.Population[this.dropdown.hovered]; if (popCap >= this.WorldPopulationCapacityRecommendation) { this.sprintfArgs.popCap = popCap; tooltip = setStringTags(sprintf(this.HoverTooltip, this.sprintfArgs), this.HoverTags); } } this.dropdown.tooltip = tooltip; } onSelectionChange(itemIdx) { g_GameSettings.population.setPopCap(true, g_WorldPopulationCapacities.Population[itemIdx]); - this.gameSettingsControl.setNetworkInitAttributes(); + this.gameSettingsController.setNetworkInitAttributes(); } }; GameSettingControls.WorldPopulationCap.prototype.TitleCaption = translate("World Population Cap"); GameSettingControls.WorldPopulationCap.prototype.Tooltip = translate("Select world population limit."); GameSettingControls.WorldPopulationCap.prototype.HoverTooltip = translate("Warning: There might be performance issues if %(popCap)s population is reached."); GameSettingControls.WorldPopulationCap.prototype.HoverTags = { "color": "orange" }; /** * Total number of units that the engine can run with smoothly. */ GameSettingControls.WorldPopulationCap.prototype.WorldPopulationCapacityRecommendation = 1200; Index: ps/trunk/binaries/data/mods/public/gui/gamesetup/Pages/GameSetupPage/GameSettings/Single/Sliders/RelicDuration.js =================================================================== --- ps/trunk/binaries/data/mods/public/gui/gamesetup/Pages/GameSetupPage/GameSettings/Single/Sliders/RelicDuration.js (revision 25100) +++ ps/trunk/binaries/data/mods/public/gui/gamesetup/Pages/GameSetupPage/GameSettings/Single/Sliders/RelicDuration.js (revision 25101) @@ -1,54 +1,54 @@ GameSettingControls.RelicDuration = class RelicDuration extends GameSettingControlSlider { constructor(...args) { super(...args); this.sprintfValue = {}; this.available = false; g_GameSettings.relic.watch(() => this.render(), ["duration", "available"]); g_GameSettings.map.watch(() => this.render(), ["type"]); this.render(); } render() { this.setHidden(!g_GameSettings.relic.available); this.setEnabled(g_GameSettings.map.type != "scenario"); if (g_GameSettings.relic.available) { let value = g_GameSettings.relic.duration; this.sprintfValue.min = value; this.setSelectedValue( g_GameSettings.relic.duration, value == 0 ? this.InstantVictory : sprintf(this.CaptionVictoryTime(value), this.sprintfValue)); } } onValueChange(value) { g_GameSettings.relic.setDuration(value); - this.gameSettingsControl.setNetworkInitAttributes(); + this.gameSettingsController.setNetworkInitAttributes(); } }; GameSettingControls.RelicDuration.prototype.TitleCaption = translate("Relic Duration"); GameSettingControls.RelicDuration.prototype.Tooltip = translate("Minutes until the player has achieved Relic Victory."); GameSettingControls.RelicDuration.prototype.NameCaptureTheRelic = "capture_the_relic"; GameSettingControls.RelicDuration.prototype.CaptionVictoryTime = min => translatePluralWithContext("victory duration", "%(min)s minute", "%(min)s minutes", min); GameSettingControls.RelicDuration.prototype.InstantVictory = translateWithContext("victory duration", "Immediate Victory."); GameSettingControls.RelicDuration.prototype.MinValue = 0; GameSettingControls.RelicDuration.prototype.MaxValue = 60; Index: ps/trunk/binaries/data/mods/public/gui/gamesetup/Pages/GameSetupPage/GameSettings/Single/Dropdowns/PlayerCount.js =================================================================== --- ps/trunk/binaries/data/mods/public/gui/gamesetup/Pages/GameSetupPage/GameSettings/Single/Dropdowns/PlayerCount.js (revision 25100) +++ ps/trunk/binaries/data/mods/public/gui/gamesetup/Pages/GameSetupPage/GameSettings/Single/Dropdowns/PlayerCount.js (revision 25101) @@ -1,36 +1,36 @@ GameSettingControls.PlayerCount = class PlayerCount extends GameSettingControlDropdown { constructor(...args) { super(...args); this.values = Array.from( new Array(g_MaxPlayers), (v, i) => i + 1); this.dropdown.list = this.values; this.dropdown.list_data = this.values; g_GameSettings.playerCount.watch(() => this.render(), ["nbPlayers"]); g_GameSettings.map.watch(() => this.render(), ["type"]); this.render(); } render() { this.setEnabled(g_GameSettings.map.type == "random"); this.setSelectedValue(g_GameSettings.playerCount.nbPlayers); } onSelectionChange(itemIdx) { g_GameSettings.playerCount.setNb(this.values[itemIdx]); - this.gameSettingsControl.setNetworkInitAttributes(); + this.gameSettingsController.setNetworkInitAttributes(); } }; GameSettingControls.PlayerCount.prototype.TitleCaption = translate("Number of Players"); GameSettingControls.PlayerCount.prototype.Tooltip = translate("Select number of players."); Index: ps/trunk/binaries/data/mods/public/gui/gamesetup/Pages/GameSetupPage/GameSettings/Single/Dropdowns/TeamPlacement.js =================================================================== --- ps/trunk/binaries/data/mods/public/gui/gamesetup/Pages/GameSetupPage/GameSettings/Single/Dropdowns/TeamPlacement.js (revision 25100) +++ ps/trunk/binaries/data/mods/public/gui/gamesetup/Pages/GameSetupPage/GameSettings/Single/Dropdowns/TeamPlacement.js (revision 25101) @@ -1,64 +1,64 @@ GameSettingControls.TeamPlacement = class TeamPlacement extends GameSettingControlDropdown { constructor(...args) { super(...args); this.values = undefined; g_GameSettings.teamPlacement.watch(() => this.render(), ["value", "available"]); this.render(); } onHoverChange() { this.dropdown.tooltip = this.values.Description[this.dropdown.hovered] || this.Tooltip; } render() { this.setHidden(!g_GameSettings.teamPlacement.value); if (!g_GameSettings.teamPlacement.value) return; let randomItem = clone(this.RandomItem); randomItem.Name = setStringTags(randomItem.Name, this.RandomItemTags); let patterns = [randomItem]; for (let pattern of g_GameSettings.teamPlacement.available) patterns.push(g_GameSettings.teamPlacement.StartingPositions .find(pObj => pObj.Id == pattern)); this.values = prepareForDropdown(patterns); this.dropdown.list = this.values.Name; this.dropdown.list_data = this.values.Id; this.setSelectedValue(g_GameSettings.teamPlacement.value); } getAutocompleteEntries() { return this.values && this.values.Name.slice(1); } onSelectionChange(itemIdx) { g_GameSettings.teamPlacement.setValue(this.values.Id[itemIdx]); - this.gameSettingsControl.setNetworkInitAttributes(); + this.gameSettingsController.setNetworkInitAttributes(); } }; GameSettingControls.TeamPlacement.prototype.TitleCaption = translate("Team Placement"); GameSettingControls.TeamPlacement.prototype.Tooltip = translate("Select one of the starting position patterns of this map."); GameSettingControls.TeamPlacement.prototype.RandomItem = { "Id": "random", "Name": translateWithContext("team placement", "Random"), "Description": translateWithContext("team placement", "Select a random team placement pattern when starting the game.") }; GameSettingControls.TeamPlacement.prototype.AutocompleteOrder = 0; Index: ps/trunk/binaries/data/mods/public/gui/gamesetup/Pages/GameSetupPage/GameSettings/Single/Sliders/Ceasefire.js =================================================================== --- ps/trunk/binaries/data/mods/public/gui/gamesetup/Pages/GameSetupPage/GameSettings/Single/Sliders/Ceasefire.js (revision 25100) +++ ps/trunk/binaries/data/mods/public/gui/gamesetup/Pages/GameSetupPage/GameSettings/Single/Sliders/Ceasefire.js (revision 25101) @@ -1,50 +1,50 @@ GameSettingControls.Ceasefire = class Ceasefire extends GameSettingControlSlider { constructor(...args) { super(...args); this.sprintfValue = {}; g_GameSettings.ceasefire.watch(() => this.render(), ["value"]); g_GameSettings.map.watch(() => this.render(), ["type"]); this.render(); } render() { this.setEnabled(g_GameSettings.map.type != "scenario"); let value = Math.round(g_GameSettings.ceasefire.value); this.sprintfValue.minutes = value; this.setSelectedValue(g_GameSettings.ceasefire.value, value == 0 ? this.NoCeasefireCaption : sprintf(this.CeasefireCaption(value), this.sprintfValue)); } onValueChange(value) { g_GameSettings.ceasefire.setValue(value); - this.gameSettingsControl.setNetworkInitAttributes(); + this.gameSettingsController.setNetworkInitAttributes(); } }; GameSettingControls.Ceasefire.prototype.TitleCaption = translate("Ceasefire"); GameSettingControls.Ceasefire.prototype.Tooltip = translate("Set time where no attacks are possible."); GameSettingControls.Ceasefire.prototype.NoCeasefireCaption = translateWithContext("ceasefire", "No ceasefire"); GameSettingControls.Ceasefire.prototype.CeasefireCaption = minutes => translatePluralWithContext("ceasefire", "%(minutes)s minute", "%(minutes)s minutes", minutes); GameSettingControls.Ceasefire.prototype.DefaultValue = 0; GameSettingControls.Ceasefire.prototype.MinValue = 0; GameSettingControls.Ceasefire.prototype.MaxValue = 45; Index: ps/trunk/binaries/data/mods/public/gui/gamesetup/Controllers/GameSettingsController.js =================================================================== --- ps/trunk/binaries/data/mods/public/gui/gamesetup/Controllers/GameSettingsController.js (nonexistent) +++ ps/trunk/binaries/data/mods/public/gui/gamesetup/Controllers/GameSettingsController.js (revision 25101) @@ -0,0 +1,284 @@ +/** + * Controller for the GUI handling of gamesettings. + */ +class GameSettingsController +{ + constructor(setupWindow, netMessages, playerAssignmentsController, mapCache) + { + this.setupWindow = setupWindow; + this.mapCache = mapCache; + this.persistentMatchSettings = new PersistentMatchSettings(this); + + this.guiData = new GameSettingsGuiData(); + + // When joining a game, the complete set of attributes + // may not have been received yet. + this.loading = true; + + // If this is true, the ready controller won't reset readiness. + // TODO: ideally the ready controller would be somewhat independent from this one, + // possibly by listening to gamesetup messages itself. + this.gameStarted = false; + + this.updateLayoutHandlers = new Set(); + this.settingsChangeHandlers = new Set(); + this.loadingChangeHandlers = new Set(); + + setupWindow.registerLoadHandler(this.onLoad.bind(this)); + setupWindow.registerGetHotloadDataHandler(this.onGetHotloadData.bind(this)); + + setupWindow.registerClosePageHandler(this.onClose.bind(this)); + + if (g_IsNetworked) + { + if (g_IsController) + playerAssignmentsController.registerClientJoinHandler(this.onClientJoin.bind(this)); + else + // In MP, the host launches the game and switches right away, + // clients switch when they receive the appropriate message. + netMessages.registerNetMessageHandler("start", this.switchToLoadingPage.bind(this)); + netMessages.registerNetMessageHandler("gamesetup", this.onGamesetupMessage.bind(this)); + } + } + + /** + * @param handler will be called when the layout needs to be updated. + */ + registerUpdateLayoutHandler(handler) + { + this.updateLayoutHandlers.add(handler); + } + + /** + * @param handler will be called when any setting change. + * (this isn't exactly what happens but the behaviour should be similar). + */ + registerSettingsChangeHandler(handler) + { + this.settingsChangeHandlers.add(handler); + } + + /** + * @param handler will be called when the 'loading' state change. + */ + registerLoadingChangeHandler(handler) + { + this.loadingChangeHandlers.add(handler); + } + + onLoad(initData, hotloadData) + { + if (hotloadData) + this.parseSettings(hotloadData.initAttributes); + else if (g_IsController && this.persistentMatchSettings.enabled) + { + let settings = this.persistentMatchSettings.loadFile(); + if (settings) + this.parseSettings(settings); + } + + this.updateLayout(); + this.setNetworkInitAttributes(); + + // If we are the controller, we are done loading. + if (hotloadData || !g_IsNetworked || g_IsController) + this.setLoading(false); + } + + onClientJoin() + { + /** + * A note on network synchronization: + * The net server does not keep the current state of attributes, + * nor does it act like a message queue, so a new client + * will only receive updates after they've joined. + * In particular, new joiners start with no information, + * so the controller must first send them a complete copy of the settings. + * However, messages could be in-flight towards the controller, + * but the new client may never receive these or have already received them, + * leading to an ordering issue that might desync the new client. + * + * The simplest solution is to have the (single) controller + * act as the single source of truth. Any other message must + * first go through the controller, which will send updates. + * This enforces the ordering of the controller. + * In practical terms, if e.g. players controlling their own civ is implemented, + * the message will need to be ignored by everyone but the controller, + * and the controller will need to send an update once it rejects/accepts the changes, + * which will then update the other clients. + * Of course, the original client GUI may want to temporarily show a different state. + * Note that the final attributes are sent on game start anyways, so any + * synchronization issue that might happen at that point can be resolved. + */ + Engine.SendGameSetupMessage({ + "type": "initial-update", + "initAttribs": this.getSettings() + }); + } + + onGetHotloadData(object) + { + object.initAttributes = this.getSettings(); + } + + onGamesetupMessage(message) + { + // For now, the controller only can send updates, so no need to listen to messages. + if (!message.data || g_IsController) + return; + + if (message.data.type !== "update" && + message.data.type !== "initial-update") + { + error("Unknown message type " + message.data.type); + return; + } + + if (message.data.type === "initial-update") + { + // Ignore initial updates if we've already received settings. + if (!this.loading) + return; + this.setLoading(false); + } + + this.parseSettings(message.data.initAttribs); + + // This assumes that messages aren't sent spuriously without changes + // (which is generally fair), but technically it would be good + // to check if the new data is different from the previous data. + for (let handler of this.settingsChangeHandlers) + handler(); + } + + /** + * Returns the InitAttributes, augmented by GUI-specific data. + */ + getSettings() + { + let ret = g_GameSettings.toInitAttributes(); + ret.guiData = this.guiData.Serialize(); + return ret; + } + + /** + * Parse the following settings. + */ + parseSettings(settings) + { + if (settings.guiData) + this.guiData.Deserialize(settings.guiData); + g_GameSettings.fromInitAttributes(settings); + } + + setLoading(loading) + { + if (this.loading === loading) + return; + this.loading = loading; + for (let handler of this.loadingChangeHandlers) + handler(); + } + + /** + * This should be called whenever the GUI layout needs to be updated. + * Triggers on the next GUI tick to avoid un-necessary layout. + */ + updateLayout() + { + if (this.layoutTimer) + return; + this.layoutTimer = setTimeout(() => { + for (let handler of this.updateLayoutHandlers) + handler(); + delete this.layoutTimer; + }, 0); + } + + /** + * This function is to be called when a GUI control has initiated a value change. + * + * To avoid an infinite loop, do not call this function when a game setup message was + * received and the data had only been modified deterministically. + * + * This is run on a timer to avoid flooding the network with messages, + * e.g. when modifying a slider. + */ + setNetworkInitAttributes() + { + for (let handler of this.settingsChangeHandlers) + handler(); + + if (g_IsNetworked && this.timer === undefined) + this.timer = setTimeout(this.setNetworkInitAttributesImmediately.bind(this), this.Timeout); + } + + setNetworkInitAttributesImmediately() + { + if (this.timer) + { + clearTimeout(this.timer); + delete this.timer; + } + // See note in onClientJoin on network synchronization. + if (g_IsController) + Engine.SendGameSetupMessage({ + "type": "update", + "initAttribs": this.getSettings() + }); + } + + /** + * Cheat prevention: + * + * 1. Ensure that the host cannot start the game unless all clients agreed on the game settings using the ready system. + * + * TODO: + * 2. Ensure that the host cannot start the game with InitAttributes different from the agreed ones. + * This may be achieved by: + * - Determining the seed collectively. + * - passing the agreed game settings to the engine when starting the game instance + * - rejecting new game settings from the server after the game launch event + */ + launchGame() + { + // Save the file before random settings are resolved. + this.savePersistentMatchSettings(); + + // Mark the game as started so the readyController won't reset state. + this.gameStarted = true; + + // This will resolve random settings & send game start messages. + // TODO: this will trigger observers, which is somewhat wasteful. + g_GameSettings.launchGame(g_PlayerAssignments); + + // Switch to the loading page right away, + // the GUI will otherwise show the unrandomised settings. + this.switchToLoadingPage(); + } + + switchToLoadingPage() + { + Engine.SwitchGuiPage("page_loading.xml", { + "attribs": g_GameSettings.toInitAttributes(), + "playerAssignments": g_PlayerAssignments + }); + } + + onClose() + { + this.savePersistentMatchSettings(); + } + + savePersistentMatchSettings() + { + // TODO: ought to only save a subset of settings. + this.persistentMatchSettings.saveFile(this.getSettings()); + } +} + + +/** + * Wait (at most) this many milliseconds before sending network messages. + */ +GameSettingsController.prototype.Timeout = 400; Property changes on: ps/trunk/binaries/data/mods/public/gui/gamesetup/Controllers/GameSettingsController.js ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Index: ps/trunk/binaries/data/mods/public/gui/gamesetup/Controllers/GuiData.js =================================================================== --- ps/trunk/binaries/data/mods/public/gui/gamesetup/Controllers/GuiData.js (nonexistent) +++ ps/trunk/binaries/data/mods/public/gui/gamesetup/Controllers/GuiData.js (revision 25101) @@ -0,0 +1,28 @@ +/** + * This class contains network-synchronized data specific to GameSettingsController. + * It's split from GameSettingsController for convenience. + */ +class GameSettingsGuiData +{ + constructor() + { + this.mapFilter = new Observable(); + this.mapFilter.filter = "default"; + } + + /** + * Serialize for network transmission & settings persistence. + */ + Serialize() + { + let ret = { + "mapFilter": this.mapFilter.filter, + }; + return ret; + } + + Deserialize(data) + { + this.mapFilter.filter = data.mapFilter; + } +} Property changes on: ps/trunk/binaries/data/mods/public/gui/gamesetup/Controllers/GuiData.js ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Index: ps/trunk/binaries/data/mods/public/gui/gamesetup/Controllers/LobbyGameRegistration.js =================================================================== --- ps/trunk/binaries/data/mods/public/gui/gamesetup/Controllers/LobbyGameRegistration.js (nonexistent) +++ ps/trunk/binaries/data/mods/public/gui/gamesetup/Controllers/LobbyGameRegistration.js (revision 25101) @@ -0,0 +1,142 @@ +/** + * If there is an XmppClient, this class informs the XPartaMuPP lobby bot that + * this match is being setup so that others can join. + * It informs of the lobby of some setting values and the participating clients. + */ +class LobbyGameRegistrationController +{ + constructor(initData, setupWindow, netMessages, mapCache, playerAssignmentsController) + { + this.mapCache = mapCache; + + this.serverName = initData.serverName; + this.hasPassword = initData.hasPassword; + + this.mods = JSON.stringify(Engine.GetEngineInfo().mods); + this.timer = undefined; + + // Only send a lobby update when its data changed + this.lastStanza = undefined; + + // Events + setupWindow.registerClosePageHandler(this.onClosePage.bind(this)); + netMessages.registerNetMessageHandler("start", this.onGameStart.bind(this)); + playerAssignmentsController.registerPlayerAssignmentsChangeHandler(this.sendImmediately.bind(this)); + + g_GameSettings.map.watch(() => this.onSettingsChange(), ["map", "type"]); + g_GameSettings.mapSize.watch(() => this.onSettingsChange(), ["size"]); + g_GameSettings.victoryConditions.watch(() => this.onSettingsChange(), ["active"]); + g_GameSettings.playerCount.watch(() => this.onSettingsChange(), ["nbPlayers"]); + } + + onSettingsChange() + { + if (this.lastStanza) + this.sendDelayed(); + else + this.sendImmediately(); + } + + onGameStart() + { + this.sendImmediately(); + let clients = this.formatClientsForStanza(); + Engine.SendChangeStateGame(clients.connectedPlayers, clients.list); + } + + onClosePage() + { + if (g_IsController && Engine.HasXmppClient()) + Engine.SendUnregisterGame(); + } + + /** + * Send the relevant game settings to the lobby bot in a deferred manner. + */ + sendDelayed() + { + if (!g_IsController || !Engine.HasXmppClient()) + return; + + // Already sending an update - do nothing. + if (this.timer !== undefined) + return; + + this.timer = setTimeout(this.sendImmediately.bind(this), this.Timeout); + } + + /** + * Send the relevant game settings to the lobby bot immediately. + */ + sendImmediately() + { + if (!g_IsController || !Engine.HasXmppClient()) + return; + + Engine.ProfileStart("sendRegisterGameStanza"); + + if (this.timer !== undefined) + { + clearTimeout(this.timer); + this.timer = undefined; + } + + let clients = this.formatClientsForStanza(); + + let stanza = { + "name": this.serverName, + "hostUsername": Engine.LobbyGetNick(), + "mapName": g_GameSettings.map.map, + // TODO: if the map name was always up-to-date we wouldn't need the mapcache here. + "niceMapName": this.mapCache.getTranslatableMapName(g_GameSettings.map.type, g_GameSettings.map.map), + "mapSize": g_GameSettings.map.type == "random" ? g_GameSettings.mapSize.size : "Default", + "mapType": g_GameSettings.map.type, + "victoryConditions": Array.from(g_GameSettings.victoryConditions.active).join(","), + "nbp": clients.connectedPlayers, + "maxnbp": g_GameSettings.playerCount.nbPlayers, + "players": clients.list, + "mods": this.mods, + "hasPassword": this.hasPassword || "" + }; + + // Only send the stanza if one of these properties changed + if (this.lastStanza && Object.keys(stanza).every(prop => this.lastStanza[prop] == stanza[prop])) + return; + + this.lastStanza = stanza; + Engine.SendRegisterGame(stanza); + Engine.ProfileStop(); + } + + /** + * Send a list of playernames and distinct between players and observers. + * Don't send teams, AIs or anything else until the game was started. + */ + formatClientsForStanza() + { + let connectedPlayers = 0; + let playerData = []; + + for (let guid in g_PlayerAssignments) + { + let pData = { "Name": g_PlayerAssignments[guid].name }; + + if (g_PlayerAssignments[guid].player <= g_GameSettings.playerCount.nbPlayers) + ++connectedPlayers; + else + pData.Team = "observer"; + + playerData.push(pData); + } + + return { + "list": playerDataToStringifiedTeamList(playerData), + "connectedPlayers": connectedPlayers + }; + } +} + +/** + * Send the current game settings to the lobby bot if the settings didn't change for this number of milliseconds. + */ +LobbyGameRegistrationController.prototype.Timeout = 2000; Property changes on: ps/trunk/binaries/data/mods/public/gui/gamesetup/Controllers/LobbyGameRegistration.js ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Index: ps/trunk/binaries/data/mods/public/gui/gamesetup/Controllers/PlayerAssignmentsController.js =================================================================== --- ps/trunk/binaries/data/mods/public/gui/gamesetup/Controllers/PlayerAssignmentsController.js (nonexistent) +++ ps/trunk/binaries/data/mods/public/gui/gamesetup/Controllers/PlayerAssignmentsController.js (revision 25101) @@ -0,0 +1,210 @@ +/** + * This class provides a property independent interface to g_PlayerAssignment events and actions. + */ +class PlayerAssignmentsController +{ + constructor(setupWindow, netMessages) + { + this.clientJoinHandlers = new Set(); + this.clientLeaveHandlers = new Set(); + this.playerAssignmentsChangeHandlers = new Set(); + + if (!g_IsNetworked) + { + let name = singleplayerName(); + + // Replace empty player name when entering a single-player match for the first time. + Engine.ConfigDB_CreateAndWriteValueToFile("user", this.ConfigNameSingleplayer, name, "config/user.cfg"); + + // By default, assign the player to the first slot. + g_PlayerAssignments = { + "local": { + "name": name, + "player": 1 + } + }; + } + + g_GameSettings.playerCount.watch(() => this.unassignInvalidPlayers(), ["nbPlayers"]); + + setupWindow.registerLoadHandler(this.onLoad.bind(this)); + setupWindow.registerGetHotloadDataHandler(this.onGetHotloadData.bind(this)); + netMessages.registerNetMessageHandler("players", this.onPlayerAssignmentMessage.bind(this)); + + this.registerClientJoinHandler(this.onClientJoin.bind(this)); + } + + registerPlayerAssignmentsChangeHandler(handler) + { + this.playerAssignmentsChangeHandlers.add(handler); + } + + unregisterPlayerAssignmentsChangeHandler(handler) + { + this.playerAssignmentsChangeHandlers.delete(handler); + } + + registerClientJoinHandler(handler) + { + this.clientJoinHandlers.add(handler); + } + + unregisterClientJoinHandler(handler) + { + this.clientJoinHandlers.delete(handler); + } + + registerClientLeaveHandler(handler) + { + this.clientLeaveHandlers.add(handler); + } + + unregisterClientLeaveHandler(handler) + { + this.clientLeaveHandlers.delete(handler); + } + + onLoad(initData, hotloadData) + { + if (hotloadData) + { + g_PlayerAssignments = hotloadData.playerAssignments; + this.updatePlayerAssignments(); + } + else if (!g_IsNetworked) + { + // Simulate a net message for the local player to keep a common path. + this.onPlayerAssignmentMessage({ + "newAssignments": g_PlayerAssignments + }); + } + } + + onGetHotloadData(object) + { + object.playerAssignments = g_PlayerAssignments; + } + + /** + * On client join, try to assign them to a free slot. + * (This is called before g_PlayerAssignments is updated). + */ + onClientJoin(newGUID, newAssignments) + { + if (!g_IsController || newAssignments[newGUID].player != -1) + return; + + // Assign the client (or only buddies if prefered) to a free slot + if (newGUID != Engine.GetPlayerGUID()) + { + let assignOption = Engine.ConfigDB_GetValue("user", this.ConfigAssignPlayers); + if (assignOption == "disabled" || + assignOption == "buddies" && g_Buddies.indexOf(splitRatingFromNick(newAssignments[newGUID].name).nick) == -1) + return; + } + + // Find a player slot that no other player is assigned to. + let firstFreeSlot = [...Array(g_MaxPlayers).keys()]; + firstFreeSlot = firstFreeSlot.find(i => { + for (let guid in newAssignments) + if (newAssignments[guid].player == i + 1) + return false; + return true; + }); + if (firstFreeSlot === -1) + return; + + this.assignClient(newGUID, firstFreeSlot + 1); + } + + /** + * To be called when g_PlayerAssignments is modified. + */ + updatePlayerAssignments() + { + Engine.ProfileStart("updatePlayerAssignments"); + for (let handler of this.playerAssignmentsChangeHandlers) + handler(); + Engine.ProfileStop(); + } + + /** + * Called whenever a client joins or leaves or any game setting is changed. + */ + onPlayerAssignmentMessage(message) + { + let newAssignments = message.newAssignments; + for (let guid in newAssignments) + if (!g_PlayerAssignments[guid]) + for (let handler of this.clientJoinHandlers) + handler(guid, message.newAssignments); + + for (let guid in g_PlayerAssignments) + if (!newAssignments[guid]) + for (let handler of this.clientLeaveHandlers) + handler(guid); + + g_PlayerAssignments = newAssignments; + this.updatePlayerAssignments(); + } + + assignClient(guid, playerIndex) + { + if (g_IsNetworked) + Engine.AssignNetworkPlayer(playerIndex, guid); + else + { + g_PlayerAssignments[guid].player = playerIndex; + this.updatePlayerAssignments(); + } + } + + /** + * If both clients are assigned players, this will swap their assignments. + */ + assignPlayer(guidToAssign, playerIndex) + { + if (g_PlayerAssignments[guidToAssign].player != -1) + { + for (let guid in g_PlayerAssignments) + if (g_PlayerAssignments[guid].player == playerIndex + 1) + { + this.assignClient(guid, g_PlayerAssignments[guidToAssign].player); + break; + } + } + this.assignClient(guidToAssign, playerIndex + 1); + } + + unassignClient(playerID) + { + if (g_IsNetworked) + Engine.AssignNetworkPlayer(playerID, ""); + else if (g_PlayerAssignments.local.player == playerID) + { + g_PlayerAssignments.local.player = -1; + this.updatePlayerAssignments(); + } + } + + unassignInvalidPlayers() + { + if (g_IsNetworked) + { + for (let guid in g_PlayerAssignments) + if (g_PlayerAssignments[guid].player > g_GameSettings.playerCount.nbPlayers) + Engine.AssignNetworkPlayer(g_PlayerAssignments[guid].player, ""); + } + else if (g_PlayerAssignments.local.player > g_GameSettings.playerCount.nbPlayers) + { + g_PlayerAssignments.local.player = -1; + this.updatePlayerAssignments(); + } + } +} + +PlayerAssignmentsController.prototype.ConfigNameSingleplayer = + "playername.singleplayer"; + +PlayerAssignmentsController.prototype.ConfigAssignPlayers = + "gui.gamesetup.assignplayers"; Property changes on: ps/trunk/binaries/data/mods/public/gui/gamesetup/Controllers/PlayerAssignmentsController.js ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Index: ps/trunk/binaries/data/mods/public/gui/gamesetup/Controllers/ReadyController.js =================================================================== --- ps/trunk/binaries/data/mods/public/gui/gamesetup/Controllers/ReadyController.js (nonexistent) +++ ps/trunk/binaries/data/mods/public/gui/gamesetup/Controllers/ReadyController.js (revision 25101) @@ -0,0 +1,126 @@ +/** + * Ready system: + * + * The ready mechanism protects the players from being assigned to a match with settings they didn't explicitly agree with. + * It shall be technically possible to start a networked game until all participating players formally agree with the chosen settings. + * + * Therefore assume the readystate from the user interface rather than trusting the server whether the current player is ready. + * The server may set readiness to false but not to true. + * + * The ReadyController class stores the ready state of the current player and fires an event if the agreed settings changed. + */ +class ReadyController +{ + constructor(netMessages, gameSettingsController, playerAssignmentsController) + { + this.playerAssignmentsController = playerAssignmentsController; + this.gameSettingsController = gameSettingsController; + + this.resetReadyHandlers = new Set(); + this.previousAssignments = {}; + + // This variable keeps track whether the local player is ready + // As part of cheat prevention, the server may set this to NotReady, but + // only the UI may set it to Ready or StayReady. + this.readyState = this.NotReady; + + netMessages.registerNetMessageHandler("ready", this.onReadyMessage.bind(this)); + gameSettingsController.registerSettingsChangeHandler(this.onSettingsChange.bind(this)); + playerAssignmentsController.registerClientJoinHandler(this.onClientJoin.bind(this)); + playerAssignmentsController.registerClientLeaveHandler(this.onClientLeave.bind(this)); + } + + registerResetReadyHandler(handler) + { + this.resetReadyHandlers.add(handler); + } + + onClientJoin(newGUID, newAssignments) + { + if (newAssignments[newGUID].player != -1) + this.resetReady(); + } + + onClientLeave(guid) + { + if (g_PlayerAssignments[guid].player != -1) + this.resetReady(); + } + + onReadyMessage(message) + { + let playerAssignment = g_PlayerAssignments[message.guid]; + if (playerAssignment) + { + playerAssignment.status = message.status; + this.playerAssignmentsController.updatePlayerAssignments(); + } + } + + onPlayerAssignmentsChange() + { + // Don't let the host tell you that you're ready when you're not. + let playerAssignment = g_PlayerAssignments[Engine.GetPlayerGUID()]; + if (playerAssignment && playerAssignment.status > this.readyState) + playerAssignment.status = this.readyState; + + for (let guid in g_PlayerAssignments) + if (this.previousAssignments[guid] && + this.previousAssignments[guid].player != g_PlayerAssignments[guid].player) + { + this.resetReady(); + return; + } + } + + onSettingsChange() + { + this.resetReady(); + } + + setReady(ready, sendMessage) + { + this.readyState = ready; + + if (sendMessage) + Engine.SendNetworkReady(ready); + + // Update GUI objects instantly if relevant settingchange was detected + let playerAssignment = g_PlayerAssignments[Engine.GetPlayerGUID()]; + if (playerAssignment) + { + playerAssignment.status = ready; + this.playerAssignmentsController.updatePlayerAssignments(); + } + } + + resetReady() + { + // The gameStarted check is only necessary to allow the host to + // determine random items after clicking start. + if (!g_IsNetworked || this.gameSettingsController.gameStarted) + return; + + for (let handler of this.resetReadyHandlers) + handler(); + + if (g_IsController) + { + Engine.ClearAllPlayerReady(); + this.playerAssignmentsController.updatePlayerAssignments(); + } + else if (this.readyState != this.StayReady) + this.setReady(this.NotReady, false); + } + + getLocalReadyState() + { + return this.readyState; + } +} + +ReadyController.prototype.NotReady = 0; + +ReadyController.prototype.Ready = 1; + +ReadyController.prototype.StayReady = 2; Property changes on: ps/trunk/binaries/data/mods/public/gui/gamesetup/Controllers/ReadyController.js ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Index: ps/trunk/binaries/data/mods/public/gui/gamesetup/NetMessages/NetMessages.js =================================================================== --- ps/trunk/binaries/data/mods/public/gui/gamesetup/NetMessages/NetMessages.js (revision 25100) +++ ps/trunk/binaries/data/mods/public/gui/gamesetup/NetMessages/NetMessages.js (revision 25101) @@ -1,71 +1,61 @@ /** - * This class enables other classes to subscribe to specific CNetMessage types (see NetMessage.h, NetMessages.h) sent by the CNetServer. + * Convenience wrapper to poll messages from the C++ NetClient. */ class NetMessages { constructor(setupWindow) { this.netMessageHandlers = {}; for (let messageType of this.MessageTypes) this.netMessageHandlers[messageType] = new Set(); - - this.registerNetMessageHandler("netwarn", addNetworkWarning); - - Engine.GetGUIObjectByName("netMessages").onTick = this.onTick.bind(this); - setupWindow.registerClosePageHandler(this.onClosePage.bind(this)); } registerNetMessageHandler(messageType, handler) { if (this.netMessageHandlers[messageType]) this.netMessageHandlers[messageType].add(handler); else error("Unknown net message type: " + uneval(messageType)); } unregisterNetMessageHandler(messageType, handler) { if (this.netMessageHandlers[messageType]) this.netMessageHandlers[messageType].delete(handler); else error("Unknown net message type: " + uneval(messageType)); } - onTick() + pollPendingMessages() { while (true) { let message = Engine.PollNetworkClient(); if (!message) break; log("Net message: " + uneval(message)); if (this.netMessageHandlers[message.type]) for (let handler of this.netMessageHandlers[message.type]) handler(message); else error("Unrecognized net message type " + message.type); } } - - onClosePage() - { - Engine.DisconnectNetworkGame(); - } } /** - * Messages types are present here if and only if they are sent by NetClient.cpp. + * List of message types sent by C++ (keep this in sync with NetClient.cpp). */ NetMessages.prototype.MessageTypes = [ "chat", "ready", "gamesetup", "kicked", "netstatus", "netwarn", "players", "start" ]; Index: ps/trunk/binaries/data/mods/public/gui/gamesetup/Pages/AIConfigPage/AIConfigPage.js =================================================================== --- ps/trunk/binaries/data/mods/public/gui/gamesetup/Pages/AIConfigPage/AIConfigPage.js (revision 25100) +++ ps/trunk/binaries/data/mods/public/gui/gamesetup/Pages/AIConfigPage/AIConfigPage.js (revision 25101) @@ -1,67 +1,67 @@ /** * This class contains all controls modifying the AI settings of a player. */ class AIGameSettingControls { } SetupWindowPages.AIConfigPage = class { constructor(setupWindow) { - this.gameSettingsControl = setupWindow.controls.gameSettingsControl; + this.gameSettingsController = setupWindow.controls.gameSettingsController; this.playerIndex = undefined; this.row = 0; this.openPageHandlers = new Set(); this.AIGameSettingControls = {}; for (let name of this.AIGameSettingControlOrder) this.AIGameSettingControls[name] = new AIGameSettingControls[name](this, undefined, undefined, setupWindow); this.aiDescription = new AIDescription(this, setupWindow); this.aiConfigPage = Engine.GetGUIObjectByName("aiConfigPage"); Engine.GetGUIObjectByName("aiConfigOkButton").onPress = this.closePage.bind(this); g_GameSettings.playerAI.watch(() => this.maybeClose(), ["values"]); } registerOpenPageHandler(handler) { this.openPageHandlers.add(handler); } getRow() { return this.row++; } openPage(playerIndex) { this.playerIndex = playerIndex; for (let handler of this.openPageHandlers) handler(playerIndex); this.aiConfigPage.hidden = false; } maybeClose() { if (!g_GameSettings.playerAI.get(this.playerIndex)) this.closePage(); } closePage() { this.aiConfigPage.hidden = true; } }; SetupWindowPages.AIConfigPage.prototype.AIGameSettingControlOrder = [ "AISelection", "AIDifficulty", "AIBehavior" ]; Index: ps/trunk/binaries/data/mods/public/gui/gamesetup/Pages/AIConfigPage/Controls/AIBehavior.js =================================================================== --- ps/trunk/binaries/data/mods/public/gui/gamesetup/Pages/AIConfigPage/Controls/AIBehavior.js (revision 25100) +++ ps/trunk/binaries/data/mods/public/gui/gamesetup/Pages/AIConfigPage/Controls/AIBehavior.js (revision 25101) @@ -1,29 +1,29 @@ AIGameSettingControls.AIBehavior = class extends AIGameSettingControlDropdown { constructor(...args) { super(...args); g_GameSettings.playerAI.watch(() => this.render(), ["values"]); } render() { this.dropdown.list = g_Settings.AIBehaviors.map(AIBehavior => AIBehavior.Title); this.dropdown.list_data = g_Settings.AIBehaviors.map(AIBehavior => AIBehavior.Name); let ai = g_GameSettings.playerAI.get(this.playerIndex); this.setHidden(!ai); if (!!ai) this.setSelectedValue(ai.behavior); } onSelectionChange(itemIdx) { g_GameSettings.playerAI.setBehavior(this.playerIndex, this.dropdown.list_data[itemIdx]); - this.gameSettingsControl.setNetworkInitAttributes(); + this.gameSettingsController.setNetworkInitAttributes(); } }; AIGameSettingControls.AIBehavior.prototype.TitleCaption = translate("AI Behavior"); Index: ps/trunk/binaries/data/mods/public/gui/gamesetup/Pages/AIConfigPage/Controls/AIDifficulty.js =================================================================== --- ps/trunk/binaries/data/mods/public/gui/gamesetup/Pages/AIConfigPage/Controls/AIDifficulty.js (revision 25100) +++ ps/trunk/binaries/data/mods/public/gui/gamesetup/Pages/AIConfigPage/Controls/AIDifficulty.js (revision 25101) @@ -1,32 +1,32 @@ AIGameSettingControls.AIDifficulty = class extends AIGameSettingControlDropdown { constructor(...args) { super(...args); g_GameSettings.playerAI.watch(() => this.render(), ["values"]); } render() { this.dropdown.list = g_Settings.AIDifficulties.map(AI => AI.Title); this.dropdown.list_data = g_Settings.AIDifficulties.map((AI, i) => i); let ai = g_GameSettings.playerAI.get(this.playerIndex); this.setHidden(!ai); if (!!ai) this.setSelectedValue(ai.difficulty); } onSelectionChange(itemIdx) { g_GameSettings.playerAI.setDifficulty(this.playerIndex, this.dropdown.list_data[itemIdx]); - this.gameSettingsControl.setNetworkInitAttributes(); + this.gameSettingsController.setNetworkInitAttributes(); } }; AIGameSettingControls.AIDifficulty.prototype.ConfigDifficulty = "gui.gamesetup.aidifficulty"; AIGameSettingControls.AIDifficulty.prototype.TitleCaption = translate("AI Difficulty"); Index: ps/trunk/binaries/data/mods/public/gui/gamesetup/Pages/AIConfigPage/Controls/AISelection.js =================================================================== --- ps/trunk/binaries/data/mods/public/gui/gamesetup/Pages/AIConfigPage/Controls/AISelection.js (revision 25100) +++ ps/trunk/binaries/data/mods/public/gui/gamesetup/Pages/AIConfigPage/Controls/AISelection.js (revision 25101) @@ -1,44 +1,44 @@ AIGameSettingControls.AISelection = class extends AIGameSettingControlDropdown { constructor(...args) { super(...args); g_GameSettings.playerAI.watch(() => this.render(), ["values"]); this.values = prepareForDropdown([ this.NoAI, ...g_Settings.AIDescriptions.map(AI => ({ "Title": AI.data.name, "Id": AI.id })) ]); this.dropdown.list = this.values.Title; this.dropdown.list_data = this.values.Id; } render() { let ai = g_GameSettings.playerAI.get(this.playerIndex); this.setHidden(!ai); if (!!ai) this.setSelectedValue(ai.bot); else this.setSelectedValue(undefined); } onSelectionChange(itemIdx) { g_GameSettings.playerAI.setAI(this.playerIndex, this.dropdown.list_data[itemIdx]); - this.gameSettingsControl.setNetworkInitAttributes(); + this.gameSettingsController.setNetworkInitAttributes(); } }; AIGameSettingControls.AISelection.prototype.NoAI = { "Title": translateWithContext("ai", "None"), "Id": undefined }; AIGameSettingControls.AISelection.prototype.TitleCaption = translate("AI Player"); Index: ps/trunk/binaries/data/mods/public/gui/gamesetup/Pages/GameSetupPage/GameSettings/GameSettingControl.js =================================================================== --- ps/trunk/binaries/data/mods/public/gui/gamesetup/Pages/GameSetupPage/GameSettings/GameSettingControl.js (revision 25100) +++ ps/trunk/binaries/data/mods/public/gui/gamesetup/Pages/GameSetupPage/GameSettings/GameSettingControl.js (revision 25101) @@ -1,145 +1,145 @@ /** * The GameSettingControl is an abstract class that is inherited by game-setting control classes specific to a GUI-object type, * such as the GameSettingControlCheckbox or GameSettingControlDropdown. * * The purpose of these classes is to control one logical game setting. * The base classes allow implementing that while avoiding duplication. * * GameSettingControl classes watch for g_GameSettings property changes, * and re-render accordingly. They also trigger changes in g_GameSettings. * * The GameSettingControl classes are responsible for triggering network synchronisation, * and for updating the whole gamesetup layout when necessary. */ class GameSettingControl /* extends Profilable /* Uncomment to profile controls without hassle. */ { constructor(gameSettingControlManager, category, playerIndex, setupWindow) { // Store arguments { this.category = category; this.playerIndex = playerIndex; this.setupWindow = setupWindow; - this.gameSettingsControl = setupWindow.controls.gameSettingsControl; + this.gameSettingsController = setupWindow.controls.gameSettingsController; this.mapCache = setupWindow.controls.mapCache; this.mapFilters = setupWindow.controls.mapFilters; this.netMessages = setupWindow.controls.netMessages; - this.playerAssignmentsControl = setupWindow.controls.playerAssignmentsControl; + this.playerAssignmentsController = setupWindow.controls.playerAssignmentsController; } // enabled and hidden should only be modified through their setters or // by calling updateVisibility after modification. this.enabled = true; this.hidden = false; if (this.setControl) this.setControl(gameSettingControlManager); // This variable also used for autocompleting chat. this.autocompleteTitle = undefined; if (this.title && this.TitleCaption) this.setTitle(this.TitleCaption); if (this.Tooltip) this.setTooltip(this.Tooltip); this.setHidden(false); if (this.onLoad) this.setupWindow.registerLoadHandler(this.onLoad.bind(this)); if (this.onPlayerAssignmentsChange) - this.playerAssignmentsControl.registerPlayerAssignmentsChangeHandler(this.onPlayerAssignmentsChange.bind(this)); + this.playerAssignmentsController.registerPlayerAssignmentsChangeHandler(this.onPlayerAssignmentsChange.bind(this)); } setTitle(titleCaption) { this.autocompleteTitle = titleCaption; this.title.caption = sprintf(this.TitleCaptionFormat, { "setting": titleCaption }); } setTooltip(tooltip) { if (this.title) this.title.tooltip = tooltip; if (this.label) this.label.tooltip = tooltip; if (this.setControlTooltip) this.setControlTooltip(tooltip); } setEnabled(enabled) { this.enabled = enabled; this.updateVisibility(); } setHidden(hidden) { this.hidden = hidden; // Trigger a layout update to reposition items. - this.gameSettingsControl.updateLayout(); + this.gameSettingsController.updateLayout(); } updateVisibility() { let hidden = this.hidden || this.playerIndex === undefined && this.category != g_TabCategorySelected || this.playerIndex !== undefined && this.playerIndex >= g_GameSettings.playerCount.nbPlayers; if (this.frame) this.frame.hidden = hidden; if (hidden) return; let enabled = g_IsController && this.enabled; if (this.setControlHidden) this.setControlHidden(!enabled); if (this.label) this.label.hidden = !!enabled; } /** * Returns whether the control specifies an order but didn't implement the function. */ addAutocompleteEntries(name, autocomplete) { if (this.autocompleteTitle) autocomplete[0].push(this.autocompleteTitle); if (!Number.isInteger(this.AutocompleteOrder)) return; if (!this.getAutocompleteEntries) { error(name + " specifies AutocompleteOrder but didn't implement getAutocompleteEntries"); return; } let newEntries = this.getAutocompleteEntries(); if (newEntries) autocomplete[this.AutocompleteOrder] = (autocomplete[this.AutocompleteOrder] || []).concat(newEntries); } } GameSettingControl.prototype.TitleCaptionFormat = translateWithContext("Title for specific setting", "%(setting)s:"); /** * Derived classes can set this to a number to enable chat autocompleting of setting values. * Higher numbers are autocompleted first. */ GameSettingControl.prototype.AutocompleteOrder = undefined; Index: ps/trunk/binaries/data/mods/public/gui/gamesetup/Pages/GameSetupPage/GameSettings/PerPlayer/Dropdowns/PlayerAssignment.js =================================================================== --- ps/trunk/binaries/data/mods/public/gui/gamesetup/Pages/GameSetupPage/GameSettings/PerPlayer/Dropdowns/PlayerAssignment.js (revision 25100) +++ ps/trunk/binaries/data/mods/public/gui/gamesetup/Pages/GameSetupPage/GameSettings/PerPlayer/Dropdowns/PlayerAssignment.js (revision 25101) @@ -1,260 +1,260 @@ // Declare this first to avoid redundant lint warnings. class PlayerAssignmentItem { } /** * Warning: this class handles more than most other GUI controls. * Indeed, the logic of how to handle player assignments is here, * as that is not really a GUI-agnostic concern * (campaigns and other autostarting scripts should handle that themselves). */ PlayerSettingControls.PlayerAssignment = class PlayerAssignment extends GameSettingControlDropdown { constructor(...args) { super(...args); this.clientItemFactory = new PlayerAssignmentItem.Client(); this.aiItemFactory = new PlayerAssignmentItem.AI(); this.unassignedItem = new PlayerAssignmentItem.Unassigned().createItem(); this.aiItems = g_Settings.AIDescriptions.filter(ai => !ai.data.hidden).map( this.aiItemFactory.createItem.bind(this.aiItemFactory)); this.values = undefined; this.assignedGUID = undefined; this.fixedAI = undefined; // Build the initial list of values with undefined & AI clients. this.rebuildList(); g_GameSettings.playerAI.watch(() => this.render(), ["values"]); g_GameSettings.playerCount.watch((_, oldNb) => this.OnPlayerNbChange(oldNb), ["nbPlayers"]); } setControl() { this.dropdown = Engine.GetGUIObjectByName("playerAssignment[" + this.playerIndex + "]"); this.label = Engine.GetGUIObjectByName("playerAssignmentText[" + this.playerIndex + "]"); } OnPlayerNbChange(oldNb) { let isPlayerSlot = Object.values(g_PlayerAssignments).some(x => x.player === this.playerIndex + 1); if (!isPlayerSlot && !g_GameSettings.playerAI.get(this.playerIndex) && this.playerIndex >= oldNb && this.playerIndex < g_GameSettings.playerCount.nbPlayers) { // Add AIs to unused slots by default. // TODO: we could save the settings in case the player lowers, then re-raises the # of players. g_GameSettings.playerAI.set(this.playerIndex, { "bot": g_Settings.PlayerDefaults[this.playerIndex + 1].AI, "difficulty": +Engine.ConfigDB_GetValue("user", "gui.gamesetup.aidifficulty"), "behavior": Engine.ConfigDB_GetValue("user", "gui.gamesetup.aibehavior"), }); } } onPlayerAssignmentsChange() { // Rebuild the list to account for new/removed players. this.rebuildList(); let newGUID; for (let guid in g_PlayerAssignments) if (g_PlayerAssignments[guid].player == this.playerIndex + 1) { newGUID = guid; break; } if (this.assignedGUID === newGUID) return; this.assignedGUID = newGUID; if (this.assignedGUID && g_GameSettings.playerAI.get(this.playerIndex)) { g_GameSettings.playerAI.setAI(this.playerIndex, undefined); - this.gameSettingsControl.setNetworkInitAttributes(); + this.gameSettingsController.setNetworkInitAttributes(); } this.render(); } render() { this.setEnabled(true); if (this.assignedGUID) { this.setSelectedValue(this.assignedGUID); return; } let ai = g_GameSettings.playerAI.get(this.playerIndex); if (ai) { this.setSelectedValue(ai.bot); return; } this.setSelectedValue(undefined); } rebuildList() { Engine.ProfileStart("updatePlayerAssignmentsList"); // TODO: this particular bit is done for each row, which is unnecessarily inefficient. this.playerItems = sortGUIDsByPlayerID().map( this.clientItemFactory.createItem.bind(this.clientItemFactory)); this.values = prepareForDropdown([ ...this.playerItems, ...this.aiItems, this.unassignedItem ]); let selected = this.dropdown.list_data?.[this.dropdown.selected]; this.dropdown.list = this.values.Caption; this.dropdown.list_data = this.values.Value; this.setSelectedValue(selected); Engine.ProfileStop(); } onSelectionChange(itemIdx) { this.values.Handler[itemIdx].onSelectionChange( - this.gameSettingsControl, - this.playerAssignmentsControl, + this.gameSettingsController, + this.playerAssignmentsController, this.playerIndex, this.values.Value[itemIdx]); } getAutocompleteEntries() { return this.values.Autocomplete; } }; PlayerSettingControls.PlayerAssignment.prototype.Tooltip = translate("Select player."); PlayerSettingControls.PlayerAssignment.prototype.AutocompleteOrder = 100; { PlayerAssignmentItem.Client = class { createItem(guid) { return { "Handler": this, "Value": guid, "Autocomplete": g_PlayerAssignments[guid].name, "Caption": setStringTags( g_PlayerAssignments[guid].name, g_PlayerAssignments[guid].player == -1 ? this.ObserverTags : this.PlayerTags) }; } - onSelectionChange(gameSettingsControl, playerAssignmentsControl, playerIndex, guidToAssign) + onSelectionChange(gameSettingsController, playerAssignmentsController, playerIndex, guidToAssign) { let sourcePlayer = g_PlayerAssignments[guidToAssign].player - 1; if (sourcePlayer >= 0) { let ai = g_GameSettings.playerAI.get(playerIndex); // If the target was an AI, swap so AI settings are kept. if (ai) g_GameSettings.playerAI.swap(sourcePlayer, playerIndex); // Swap color + civ as well - this allows easy reorganizing of player order. if (g_GameSettings.map.type !== "scenario") { g_GameSettings.playerCiv.swap(sourcePlayer, playerIndex); g_GameSettings.playerColor.swap(sourcePlayer, playerIndex); } } - playerAssignmentsControl.assignPlayer(guidToAssign, playerIndex); - gameSettingsControl.setNetworkInitAttributes(); + playerAssignmentsController.assignPlayer(guidToAssign, playerIndex); + gameSettingsController.setNetworkInitAttributes(); } isSelected(pData, guid, value) { return guid !== undefined && guid == value; } }; PlayerAssignmentItem.Client.prototype.PlayerTags = { "color": "white" }; PlayerAssignmentItem.Client.prototype.ObserverTags = { "color": "170 170 250" }; } { PlayerAssignmentItem.AI = class { createItem(ai) { let aiName = translate(ai.data.name); return { "Handler": this, "Value": ai.id, "Autocomplete": aiName, "Caption": setStringTags(sprintf(this.Label, { "ai": aiName }), this.Tags) }; } - onSelectionChange(gameSettingsControl, playerAssignmentsControl, playerIndex, value) + onSelectionChange(gameSettingsController, playerAssignmentsController, playerIndex, value) { - playerAssignmentsControl.unassignClient(playerIndex + 1); + playerAssignmentsController.unassignClient(playerIndex + 1); g_GameSettings.playerAI.set(playerIndex, { "bot": value, "difficulty": +Engine.ConfigDB_GetValue("user", "gui.gamesetup.aidifficulty"), "behavior": Engine.ConfigDB_GetValue("user", "gui.gamesetup.aibehavior"), }); - gameSettingsControl.setNetworkInitAttributes(); + gameSettingsController.setNetworkInitAttributes(); } isSelected(pData, guid, value) { return !guid && pData.AI && pData.AI == value; } }; PlayerAssignmentItem.AI.prototype.Label = translate("AI: %(ai)s"); PlayerAssignmentItem.AI.prototype.Tags = { "color": "70 150 70" }; } { PlayerAssignmentItem.Unassigned = class { createItem() { return { "Handler": this, "Value": undefined, "Autocomplete": this.Label, "Caption": setStringTags(this.Label, this.Tags) }; } - onSelectionChange(gameSettingsControl, playerAssignmentsControl, playerIndex) + onSelectionChange(gameSettingsController, playerAssignmentsController, playerIndex) { - playerAssignmentsControl.unassignClient(playerIndex + 1); + playerAssignmentsController.unassignClient(playerIndex + 1); g_GameSettings.playerAI.setAI(playerIndex, undefined); - gameSettingsControl.setNetworkInitAttributes(); + gameSettingsController.setNetworkInitAttributes(); } isSelected(pData, guid, value) { return !guid && !pData.AI; } }; PlayerAssignmentItem.Unassigned.prototype.Label = translate("Unassigned"); PlayerAssignmentItem.Unassigned.prototype.Tags = { "color": "140 140 140" }; } Index: ps/trunk/binaries/data/mods/public/gui/gamesetup/Pages/GameSetupPage/GameSettings/PerPlayer/Dropdowns/PlayerCiv.js =================================================================== --- ps/trunk/binaries/data/mods/public/gui/gamesetup/Pages/GameSetupPage/GameSettings/PerPlayer/Dropdowns/PlayerCiv.js (revision 25100) +++ ps/trunk/binaries/data/mods/public/gui/gamesetup/Pages/GameSetupPage/GameSettings/PerPlayer/Dropdowns/PlayerCiv.js (revision 25101) @@ -1,82 +1,82 @@ PlayerSettingControls.PlayerCiv = class PlayerCiv extends GameSettingControlDropdown { constructor(...args) { super(...args); this.values = prepareForDropdown(this.getItems()); this.dropdown.list = this.values.name; this.dropdown.list_data = this.values.civ; g_GameSettings.playerCiv.watch(() => this.render(), ["values", "locked"]); this.render(); } setControl() { this.label = Engine.GetGUIObjectByName("playerCivText[" + this.playerIndex + "]"); this.dropdown = Engine.GetGUIObjectByName("playerCiv[" + this.playerIndex + "]"); } onHoverChange() { this.dropdown.tooltip = this.values && this.values.tooltip[this.dropdown.hovered] || this.Tooltip; } render() { this.setEnabled(!g_GameSettings.playerCiv.locked[this.playerIndex]); this.setSelectedValue(g_GameSettings.playerCiv.values[this.playerIndex]); } getItems() { let values = []; for (let civ in g_CivData) if (g_CivData[civ].SelectableInGameSetup) values.push({ "name": g_CivData[civ].Name, "autocomplete": g_CivData[civ].Name, "tooltip": g_CivData[civ].History, "civ": civ }); values.sort(sortNameIgnoreCase); values.unshift({ "name": setStringTags(this.RandomCivCaption, this.RandomItemTags), "autocomplete": this.RandomCivCaption, "tooltip": this.RandomCivTooltip, "civ": this.RandomCivId }); return values; } getAutocompleteEntries() { return this.values.autocomplete; } onSelectionChange(itemIdx) { g_GameSettings.playerCiv.setValue(this.playerIndex, this.values.civ[itemIdx]); - this.gameSettingsControl.setNetworkInitAttributes(); + this.gameSettingsController.setNetworkInitAttributes(); } }; PlayerSettingControls.PlayerCiv.prototype.Tooltip = translate("Choose the civilization for this player."); PlayerSettingControls.PlayerCiv.prototype.RandomCivCaption = translateWithContext("civilization", "Random"); PlayerSettingControls.PlayerCiv.prototype.RandomCivId = "random"; PlayerSettingControls.PlayerCiv.prototype.RandomCivTooltip = translate("Picks one civilization at random when the game starts."); PlayerSettingControls.PlayerCiv.prototype.AutocompleteOrder = 90; Index: ps/trunk/binaries/data/mods/public/gui/gamesetup/Pages/GameSetupPage/GameSettings/PerPlayer/Dropdowns/PlayerColor.js =================================================================== --- ps/trunk/binaries/data/mods/public/gui/gamesetup/Pages/GameSetupPage/GameSettings/PerPlayer/Dropdowns/PlayerColor.js (revision 25100) +++ ps/trunk/binaries/data/mods/public/gui/gamesetup/Pages/GameSetupPage/GameSettings/PerPlayer/Dropdowns/PlayerColor.js (revision 25101) @@ -1,47 +1,47 @@ PlayerSettingControls.PlayerColor = class PlayerColor extends GameSettingControlDropdown { constructor(...args) { super(...args); g_GameSettings.playerColor.watch(() => this.render(), ["values", "locked"]); this.render(); } setControl() { this.dropdown = Engine.GetGUIObjectByName("playerColor[" + this.playerIndex + "]"); this.playerBackgroundColor = Engine.GetGUIObjectByName("playerBackgroundColor[" + this.playerIndex + "]"); this.playerColorHeading = Engine.GetGUIObjectByName("playerColorHeading"); } render() { if (g_GameSettings.playerCount.nbPlayers < this.playerIndex + 1) return; let hidden = !g_IsController || g_GameSettings.map.type == "scenario"; this.dropdown.hidden = hidden; this.playerColorHeading.hidden = hidden; let value = g_GameSettings.playerColor.get(this.playerIndex); this.setSelectedValue(value); this.playerBackgroundColor.sprite = "color:" + rgbToGuiColor(value, 100); this.values = g_GameSettings.playerColor.available; this.dropdown.list = this.values.map(color => coloredText(this.ColorIcon, rgbToGuiColor(color))); this.dropdown.list_data = this.values.map((color, i) => i); } onSelectionChange(itemIdx) { g_GameSettings.playerColor.setColor(this.playerIndex, this.values[itemIdx]); - this.gameSettingsControl.setNetworkInitAttributes(); + this.gameSettingsController.setNetworkInitAttributes(); } }; PlayerSettingControls.PlayerColor.prototype.Tooltip = translate("Pick a color."); PlayerSettingControls.PlayerColor.prototype.ColorIcon = "â– "; Index: ps/trunk/binaries/data/mods/public/gui/gamesetup/Pages/GameSetupPage/GameSettings/PerPlayer/Dropdowns/PlayerTeam.js =================================================================== --- ps/trunk/binaries/data/mods/public/gui/gamesetup/Pages/GameSetupPage/GameSettings/PerPlayer/Dropdowns/PlayerTeam.js (revision 25100) +++ ps/trunk/binaries/data/mods/public/gui/gamesetup/Pages/GameSetupPage/GameSettings/PerPlayer/Dropdowns/PlayerTeam.js (revision 25101) @@ -1,52 +1,52 @@ PlayerSettingControls.PlayerTeam = class PlayerTeam extends GameSettingControlDropdown { constructor(...args) { super(...args); this.values = prepareForDropdown([ { "label": this.NoTeam, "id": this.NoTeamId }, ...Array.from( new Array(g_MaxTeams), (v, i) => ({ "label": i + 1, "id": i })) ]); this.dropdown.list = this.values.label; this.dropdown.list_data = this.values.id; g_GameSettings.playerTeam.watch(() => this.render(), ["values", "locked"]); this.render(); } setControl() { this.label = Engine.GetGUIObjectByName("playerTeamText[" + this.playerIndex + "]"); this.dropdown = Engine.GetGUIObjectByName("playerTeam[" + this.playerIndex + "]"); } render() { this.setEnabled(g_GameSettings.map.type != "scenario"); this.setSelectedValue(g_GameSettings.playerTeam.values[this.playerIndex]); } onSelectionChange(itemIdx) { g_GameSettings.playerTeam.setValue(this.playerIndex, itemIdx - 1); - this.gameSettingsControl.setNetworkInitAttributes(); + this.gameSettingsController.setNetworkInitAttributes(); } }; PlayerSettingControls.PlayerTeam.prototype.Tooltip = translate("Select player's team."); PlayerSettingControls.PlayerTeam.prototype.NoTeam = translateWithContext("team", "None"); PlayerSettingControls.PlayerTeam.prototype.NoTeamId = -1; Index: ps/trunk/binaries/data/mods/public/gui/gamesetup/Pages/GameSetupPage/GameSettings/Single/Checkboxes/Cheats.js =================================================================== --- ps/trunk/binaries/data/mods/public/gui/gamesetup/Pages/GameSetupPage/GameSettings/Single/Checkboxes/Cheats.js (revision 25100) +++ ps/trunk/binaries/data/mods/public/gui/gamesetup/Pages/GameSetupPage/GameSettings/Single/Checkboxes/Cheats.js (revision 25101) @@ -1,37 +1,37 @@ /** * Cheats are always enabled in singleplayer mode, since they are the choice of that one player. */ GameSettingControls.Cheats = class Cheats extends GameSettingControlCheckbox { constructor(...args) { super(...args); g_GameSettings.cheats.watch(() => this.render(), ["enabled"]); g_GameSettings.rating.watch(() => this.render(), ["enabled"]); } onLoad() { g_GameSettings.cheats.setEnabled(!g_IsNetworked); this.render(); } render() { this.setChecked(g_GameSettings.cheats.enabled); this.setEnabled(g_IsNetworked && !g_GameSettings.rating.enabled); } onPress(checked) { g_GameSettings.cheats.setEnabled(!g_IsNetworked || checked); - this.gameSettingsControl.setNetworkInitAttributes(); + this.gameSettingsController.setNetworkInitAttributes(); } }; GameSettingControls.Cheats.prototype.TitleCaption = translate("Cheats"); GameSettingControls.Cheats.prototype.Tooltip = translate("Toggle the usability of cheats."); Index: ps/trunk/binaries/data/mods/public/gui/gamesetup/Pages/GameSetupPage/GameSettings/Single/Checkboxes/ExploredMap.js =================================================================== --- ps/trunk/binaries/data/mods/public/gui/gamesetup/Pages/GameSetupPage/GameSettings/Single/Checkboxes/ExploredMap.js (revision 25100) +++ ps/trunk/binaries/data/mods/public/gui/gamesetup/Pages/GameSetupPage/GameSettings/Single/Checkboxes/ExploredMap.js (revision 25101) @@ -1,30 +1,30 @@ GameSettingControls.ExploredMap = class ExploredMap extends GameSettingControlCheckbox { constructor(...args) { super(...args); g_GameSettings.mapExploration.watch(() => this.render(), ["explored"]); g_GameSettings.map.watch(() => this.render(), ["type"]); this.render(); } render() { this.setEnabled(g_GameSettings.map.type != "scenario"); this.setChecked(g_GameSettings.mapExploration.explored); } onPress(checked) { g_GameSettings.mapExploration.setExplored(checked); - this.gameSettingsControl.setNetworkInitAttributes(); + this.gameSettingsController.setNetworkInitAttributes(); } }; GameSettingControls.ExploredMap.prototype.TitleCaption = // Translation: Make sure to differentiate between the revealed map and explored map settings! translate("Explored Map"); GameSettingControls.ExploredMap.prototype.Tooltip = // Translation: Make sure to differentiate between the revealed map and explored map settings! translate("Toggle explored map (see initial map)."); Index: ps/trunk/binaries/data/mods/public/gui/gamesetup/Pages/GameSetupPage/GameSettings/Single/Checkboxes/LastManStanding.js =================================================================== --- ps/trunk/binaries/data/mods/public/gui/gamesetup/Pages/GameSetupPage/GameSettings/Single/Checkboxes/LastManStanding.js (revision 25100) +++ ps/trunk/binaries/data/mods/public/gui/gamesetup/Pages/GameSetupPage/GameSettings/Single/Checkboxes/LastManStanding.js (revision 25101) @@ -1,29 +1,29 @@ GameSettingControls.LastManStanding = class LastManStanding extends GameSettingControlCheckbox { constructor(...args) { super(...args); g_GameSettings.lastManStanding.watch(() => this.render(), ["enabled", "available"]); g_GameSettings.map.watch(() => this.render(), ["type"]); } render() { // Always display this, so that players are aware that there is this gamemode this.setChecked(g_GameSettings.lastManStanding.enabled); this.setEnabled(g_GameSettings.map.type != "scenario" && g_GameSettings.lastManStanding.available); } onPress(checked) { g_GameSettings.lastManStanding.setEnabled(checked); - this.gameSettingsControl.setNetworkInitAttributes(); + this.gameSettingsController.setNetworkInitAttributes(); } }; GameSettingControls.LastManStanding.prototype.TitleCaption = translate("Last Man Standing"); GameSettingControls.LastManStanding.prototype.Tooltip = translate("Toggle whether the last remaining player or the last remaining set of allies wins."); Index: ps/trunk/binaries/data/mods/public/gui/gamesetup/Pages/GameSetupPage/GameSettings/Single/Checkboxes/LockedTeams.js =================================================================== --- ps/trunk/binaries/data/mods/public/gui/gamesetup/Pages/GameSetupPage/GameSettings/Single/Checkboxes/LockedTeams.js (revision 25100) +++ ps/trunk/binaries/data/mods/public/gui/gamesetup/Pages/GameSetupPage/GameSettings/Single/Checkboxes/LockedTeams.js (revision 25101) @@ -1,40 +1,40 @@ GameSettingControls.LockedTeams = class LockedTeams extends GameSettingControlCheckbox { constructor(...args) { super(...args); g_GameSettings.map.watch(() => this.render(), ["type"]); g_GameSettings.lockedTeams.watch(() => this.render(), ["available", "enabled"]); this.render(); } onLoad() { g_GameSettings.lockedTeams.setEnabled(this.DefaultValue); } render() { this.setEnabled(g_GameSettings.map.type != "scenario" && g_GameSettings.lockedTeams.available); this.setChecked(g_GameSettings.lockedTeams.enabled); } onPress(checked) { g_GameSettings.lockedTeams.setEnabled(checked); - this.gameSettingsControl.setNetworkInitAttributes(); + this.gameSettingsController.setNetworkInitAttributes(); } }; GameSettingControls.LockedTeams.prototype.TitleCaption = translate("Teams Locked"); GameSettingControls.LockedTeams.prototype.Tooltip = translate("Toggle locked teams."); /** * In multiplayer mode, players negotiate teams before starting the match and * expect to play the match with these teams unless explicitly stated otherwise during the match settings. * For singleplayermode, preserve the historic default of open diplomacies. */ GameSettingControls.LockedTeams.prototype.DefaultValue = Engine.HasNetClient(); Index: ps/trunk/binaries/data/mods/public/gui/gamesetup/Pages/GameSetupPage/GameSettings/Single/Checkboxes/Nomad.js =================================================================== --- ps/trunk/binaries/data/mods/public/gui/gamesetup/Pages/GameSetupPage/GameSettings/Single/Checkboxes/Nomad.js (revision 25100) +++ ps/trunk/binaries/data/mods/public/gui/gamesetup/Pages/GameSetupPage/GameSettings/Single/Checkboxes/Nomad.js (revision 25101) @@ -1,28 +1,28 @@ GameSettingControls.Nomad = class Nomad extends GameSettingControlCheckbox { constructor(...args) { super(...args); g_GameSettings.nomad.watch(() => this.render(), ["enabled"]); g_GameSettings.map.watch(() => this.render(), ["type"]); this.render(); } render() { this.setHidden(g_GameSettings.map.type != "random"); this.setChecked(g_GameSettings.nomad.enabled); } onPress(checked) { g_GameSettings.nomad.setEnabled(checked); - this.gameSettingsControl.setNetworkInitAttributes(); + this.gameSettingsController.setNetworkInitAttributes(); } }; GameSettingControls.Nomad.prototype.TitleCaption = translate("Nomad"); GameSettingControls.Nomad.prototype.Tooltip = translate("In Nomad mode, players start with only few units and have to find a suitable place to build their city. Ceasefire is recommended."); Index: ps/trunk/binaries/data/mods/public/gui/gamesetup/Pages/GameSetupPage/GameSettings/Single/Checkboxes/Rating.js =================================================================== --- ps/trunk/binaries/data/mods/public/gui/gamesetup/Pages/GameSetupPage/GameSettings/Single/Checkboxes/Rating.js (revision 25100) +++ ps/trunk/binaries/data/mods/public/gui/gamesetup/Pages/GameSetupPage/GameSettings/Single/Checkboxes/Rating.js (revision 25101) @@ -1,30 +1,30 @@ GameSettingControls.Rating = class Rating extends GameSettingControlCheckbox { constructor(...args) { super(...args); // The availability of rated games is not a GUI concern, unlike most other // potentially available settings. g_GameSettings.rating.watch(() => this.render(), ["enabled", "available"]); this.render(); } render() { this.setHidden(!g_GameSettings.rating.available); this.setChecked(g_GameSettings.rating.enabled); } onPress(checked) { g_GameSettings.rating.setEnabled(checked); - this.gameSettingsControl.setNetworkInitAttributes(); + this.gameSettingsController.setNetworkInitAttributes(); } }; GameSettingControls.Rating.prototype.TitleCaption = translate("Rated Game"); GameSettingControls.Rating.prototype.Tooltip = translate("Toggle if this game will be rated for the leaderboard."); Index: ps/trunk/binaries/data/mods/public/gui/gamesetup/Pages/GameSetupPage/GameSettings/Single/Checkboxes/RegicideGarrison.js =================================================================== --- ps/trunk/binaries/data/mods/public/gui/gamesetup/Pages/GameSetupPage/GameSettings/Single/Checkboxes/RegicideGarrison.js (revision 25100) +++ ps/trunk/binaries/data/mods/public/gui/gamesetup/Pages/GameSetupPage/GameSettings/Single/Checkboxes/RegicideGarrison.js (revision 25101) @@ -1,30 +1,30 @@ GameSettingControls.RegicideGarrison = class RegicideGarrison extends GameSettingControlCheckbox { constructor(...args) { super(...args); g_GameSettings.regicideGarrison.watch(() => this.render(), ["enabled", "available"]); g_GameSettings.map.watch(() => this.render(), ["type"]); this.render(); } render() { this.setHidden(g_GameSettings.map.type == "scenario" || !g_GameSettings.regicideGarrison.available); this.setChecked(g_GameSettings.regicideGarrison.enabled); } onPress(checked) { g_GameSettings.regicideGarrison.setEnabled(checked); - this.gameSettingsControl.setNetworkInitAttributes(); + this.gameSettingsController.setNetworkInitAttributes(); } }; GameSettingControls.RegicideGarrison.prototype.TitleCaption = translate("Hero Garrison"); GameSettingControls.RegicideGarrison.prototype.Tooltip = translate("Toggle whether heroes can be garrisoned."); Index: ps/trunk/binaries/data/mods/public/gui/gamesetup/Pages/GameSetupPage/GameSettings/Single/Checkboxes/RevealedMap.js =================================================================== --- ps/trunk/binaries/data/mods/public/gui/gamesetup/Pages/GameSetupPage/GameSettings/Single/Checkboxes/RevealedMap.js (revision 25100) +++ ps/trunk/binaries/data/mods/public/gui/gamesetup/Pages/GameSetupPage/GameSettings/Single/Checkboxes/RevealedMap.js (revision 25101) @@ -1,30 +1,30 @@ GameSettingControls.RevealedMap = class RevealedMap extends GameSettingControlCheckbox { constructor(...args) { super(...args); g_GameSettings.mapExploration.watch(() => this.render(), ["revealed"]); g_GameSettings.map.watch(() => this.render(), ["type"]); this.render(); } render() { this.setEnabled(g_GameSettings.map.type != "scenario"); this.setChecked(g_GameSettings.mapExploration.revealed); } onPress(checked) { g_GameSettings.mapExploration.setRevealed(checked); - this.gameSettingsControl.setNetworkInitAttributes(); + this.gameSettingsController.setNetworkInitAttributes(); } }; GameSettingControls.RevealedMap.prototype.TitleCaption = // Translation: Make sure to differentiate between the revealed map and explored map settings! translate("Revealed Map"); GameSettingControls.RevealedMap.prototype.Tooltip = // Translation: Make sure to differentiate between the revealed map and explored map settings! translate("Toggle revealed map (see everything)."); Index: ps/trunk/binaries/data/mods/public/gui/gamesetup/Pages/GameSetupPage/GameSettings/Single/Checkboxes/Spies.js =================================================================== --- ps/trunk/binaries/data/mods/public/gui/gamesetup/Pages/GameSetupPage/GameSettings/Single/Checkboxes/Spies.js (revision 25100) +++ ps/trunk/binaries/data/mods/public/gui/gamesetup/Pages/GameSetupPage/GameSettings/Single/Checkboxes/Spies.js (revision 25101) @@ -1,29 +1,29 @@ GameSettingControls.Spies = class Spies extends GameSettingControlCheckbox { constructor(...args) { super(...args); g_GameSettings.disableSpies.watch(() => this.render(), ["enabled"]); g_GameSettings.map.watch(() => this.render(), ["type"]); this.render(); } render() { this.setEnabled(g_GameSettings.map.type != "scenario"); this.setChecked(g_GameSettings.disableSpies.enabled); } onPress(checked) { g_GameSettings.disableSpies.setEnabled(checked); - this.gameSettingsControl.setNetworkInitAttributes(); + this.gameSettingsController.setNetworkInitAttributes(); } }; GameSettingControls.Spies.prototype.TitleCaption = translate("Disable Spies"); GameSettingControls.Spies.prototype.Tooltip = translate("Disable spies during the game."); Index: ps/trunk/binaries/data/mods/public/gui/gamesetup/Pages/GameSetupPage/GameSettings/Single/Checkboxes/Treasures.js =================================================================== --- ps/trunk/binaries/data/mods/public/gui/gamesetup/Pages/GameSetupPage/GameSettings/Single/Checkboxes/Treasures.js (revision 25100) +++ ps/trunk/binaries/data/mods/public/gui/gamesetup/Pages/GameSetupPage/GameSettings/Single/Checkboxes/Treasures.js (revision 25101) @@ -1,28 +1,28 @@ GameSettingControls.Treasures = class Treasures extends GameSettingControlCheckbox { constructor(...args) { super(...args); g_GameSettings.disableTreasures.watch(() => this.render(), ["enabled"]); g_GameSettings.map.watch(() => this.render(), ["type"]); this.render(); } render() { this.setEnabled(g_GameSettings.map.type != "scenario"); this.setChecked(g_GameSettings.disableTreasures.enabled); } onPress(checked) { g_GameSettings.disableTreasures.setEnabled(checked); - this.gameSettingsControl.setNetworkInitAttributes(); + this.gameSettingsController.setNetworkInitAttributes(); } }; GameSettingControls.Treasures.prototype.TitleCaption = translate("Disable Treasures"); GameSettingControls.Treasures.prototype.Tooltip = translate("Do not add treasures to the map."); Index: ps/trunk/binaries/data/mods/public/gui/gamesetup/Pages/GameSetupPage/GameSettings/Single/Dropdowns/Biome.js =================================================================== --- ps/trunk/binaries/data/mods/public/gui/gamesetup/Pages/GameSetupPage/GameSettings/Single/Dropdowns/Biome.js (revision 25100) +++ ps/trunk/binaries/data/mods/public/gui/gamesetup/Pages/GameSetupPage/GameSettings/Single/Dropdowns/Biome.js (revision 25101) @@ -1,68 +1,68 @@ GameSettingControls.Biome = class Biome extends GameSettingControlDropdown { constructor(...args) { super(...args); g_GameSettings.biome.watch(() => this.render(), ["biome", "available"]); this.render(); } onHoverChange() { if (!this.dropdown.list_data[this.dropdown.hovered]) this.dropdown.tooltip = ""; else if (this.dropdown.list_data[this.dropdown.hovered] == "random") this.dropdown.tooltip = this.RandomDescription; else this.dropdown.tooltip = g_GameSettings.biome.biomeData[ this.dropdown.list_data[this.dropdown.hovered] ].Description; } render() { this.setHidden(!g_GameSettings.biome.available.size); let values = prepareForDropdown([ { "Title": setStringTags(this.RandomBiome, this.RandomItemTags), "Id": "random" }, ...g_GameSettings.biome.getAvailableBiomeData() ]); this.dropdown.list = values.Title; this.dropdown.list_data = values.Id; this.setSelectedValue(g_GameSettings.biome.biome); } getAutocompleteEntries() { return g_GameSettings.biome.biomes; } onSelectionChange(itemIdx) { g_GameSettings.biome.setBiome(this.dropdown.list_data[itemIdx]); - this.gameSettingsControl.setNetworkInitAttributes(); + this.gameSettingsController.setNetworkInitAttributes(); } }; GameSettingControls.Biome.prototype.TitleCaption = translate("Biome"); GameSettingControls.Biome.prototype.RandomBiomeId = "random"; GameSettingControls.Biome.prototype.Tooltip = translate("Select the flora and fauna."); GameSettingControls.Biome.prototype.RandomBiome = translateWithContext("biome", "Random"); GameSettingControls.Biome.prototype.RandomDescription = translate("Pick a biome at random."); GameSettingControls.Biome.prototype.AutocompleteOrder = 0; Index: ps/trunk/binaries/data/mods/public/gui/gamesetup/Pages/GameSetupPage/GameSettings/Single/Dropdowns/GameSpeed.js =================================================================== --- ps/trunk/binaries/data/mods/public/gui/gamesetup/Pages/GameSetupPage/GameSettings/Single/Dropdowns/GameSpeed.js (revision 25100) +++ ps/trunk/binaries/data/mods/public/gui/gamesetup/Pages/GameSetupPage/GameSettings/Single/Dropdowns/GameSpeed.js (revision 25101) @@ -1,52 +1,52 @@ GameSettingControls.GameSpeed = class GameSpeed extends GameSettingControlDropdown { constructor(...args) { super(...args); this.previousAllowFastForward = undefined; g_GameSettings.gameSpeed.watch(() => this.render(), ["gameSpeed"]); this.render(); } onPlayerAssignmentsChange() { this.render(); } render() { let allowFastForward = true; for (let guid in g_PlayerAssignments) if (g_PlayerAssignments[guid].player != -1) { allowFastForward = false; break; } if (this.previousAllowFastForward !== allowFastForward) { this.previousAllowFastForward = allowFastForward; let values = prepareForDropdown( g_Settings.GameSpeeds.filter(speed => !speed.FastForward || allowFastForward)); this.dropdown.list = values.Title; this.dropdown.list_data = values.Speed; } this.setSelectedValue(g_GameSettings.gameSpeed.gameSpeed); } onSelectionChange(itemIdx) { g_GameSettings.gameSpeed.setSpeed(this.dropdown.list_data[itemIdx]); - this.gameSettingsControl.setNetworkInitAttributes(); + this.gameSettingsController.setNetworkInitAttributes(); } }; GameSettingControls.GameSpeed.prototype.TitleCaption = translate("Game Speed"); GameSettingControls.GameSpeed.prototype.Tooltip = translate("Select game speed."); Index: ps/trunk/binaries/data/mods/public/gui/gamesetup/Pages/GameSetupPage/GameSettings/Single/Dropdowns/MapFilter.js =================================================================== --- ps/trunk/binaries/data/mods/public/gui/gamesetup/Pages/GameSetupPage/GameSettings/Single/Dropdowns/MapFilter.js (revision 25100) +++ ps/trunk/binaries/data/mods/public/gui/gamesetup/Pages/GameSetupPage/GameSettings/Single/Dropdowns/MapFilter.js (revision 25101) @@ -1,68 +1,68 @@ GameSettingControls.MapFilter = class MapFilter extends GameSettingControlDropdown { constructor(...args) { super(...args); this.values = undefined; - this.gameSettingsControl.guiData.mapFilter.watch(() => this.render(), ["filter"]); + this.gameSettingsController.guiData.mapFilter.watch(() => this.render(), ["filter"]); g_GameSettings.map.watch(() => this.checkMapTypeChange(), ["type"]); } onHoverChange() { this.dropdown.tooltip = this.values.Description[this.dropdown.hovered] || this.Tooltip; } checkMapTypeChange() { if (!g_GameSettings.map.type) return; let values = prepareForDropdown( this.mapFilters.getAvailableMapFilters( g_GameSettings.map.type)); if (values.Name.length) { this.dropdown.list = values.Title; this.dropdown.list_data = values.Name; this.values = values; } else this.values = undefined; - if (this.values && this.values.Name.indexOf(this.gameSettingsControl.guiData.mapFilter.filter) === -1) + if (this.values && this.values.Name.indexOf(this.gameSettingsController.guiData.mapFilter.filter) === -1) { - this.gameSettingsControl.guiData.mapFilter.filter = this.values.Name[this.values.Default]; - this.gameSettingsControl.setNetworkInitAttributes(); + this.gameSettingsController.guiData.mapFilter.filter = this.values.Name[this.values.Default]; + this.gameSettingsController.setNetworkInitAttributes(); } this.render(); } render() { // Index may have changed, reset. - this.setSelectedValue(this.gameSettingsControl.guiData.mapFilter.filter); + this.setSelectedValue(this.gameSettingsController.guiData.mapFilter.filter); this.setHidden(!this.values); } getAutocompleteEntries() { return this.values && this.values.Title; } onSelectionChange(itemIdx) { - this.gameSettingsControl.guiData.mapFilter.filter = this.values.Name[itemIdx]; + this.gameSettingsController.guiData.mapFilter.filter = this.values.Name[itemIdx]; } }; GameSettingControls.MapFilter.prototype.TitleCaption = translate("Map Filter"); GameSettingControls.MapFilter.prototype.Tooltip = translate("Select a map filter."); GameSettingControls.MapFilter.prototype.AutocompleteOrder = 0; Index: ps/trunk/binaries/data/mods/public/gui/gamesetup/Pages/GameSetupPage/GameSettings/Single/Dropdowns/MapSize.js =================================================================== --- ps/trunk/binaries/data/mods/public/gui/gamesetup/Pages/GameSetupPage/GameSettings/Single/Dropdowns/MapSize.js (revision 25100) +++ ps/trunk/binaries/data/mods/public/gui/gamesetup/Pages/GameSetupPage/GameSettings/Single/Dropdowns/MapSize.js (revision 25101) @@ -1,44 +1,44 @@ GameSettingControls.MapSize = class MapSize extends GameSettingControlDropdown { constructor(...args) { super(...args); this.dropdown.list = g_MapSizes.Name; this.dropdown.list_data = g_MapSizes.Tiles; g_GameSettings.mapSize.watch(() => this.render(), ["size", "available"]); this.render(); } onHoverChange() { this.dropdown.tooltip = g_MapSizes.Tooltip[this.dropdown.hovered] || this.Tooltip; } render() { this.setHidden(!g_GameSettings.mapSize.available); this.setSelectedValue(g_GameSettings.mapSize.size); // TODO: select first entry. } getAutocompleteEntries() { return g_MapSizes.Name; } onSelectionChange(itemIdx) { g_GameSettings.mapSize.setSize(g_MapSizes.Tiles[itemIdx]); - this.gameSettingsControl.setNetworkInitAttributes(); + this.gameSettingsController.setNetworkInitAttributes(); } }; GameSettingControls.MapSize.prototype.TitleCaption = translate("Map Size"); GameSettingControls.MapSize.prototype.Tooltip = translate("Select map size. (Larger sizes may reduce performance.)"); GameSettingControls.MapSize.prototype.AutocompleteOrder = 0; Index: ps/trunk/binaries/data/mods/public/gui/gamesetup/Pages/GameSetupPage/GameSettings/Single/Dropdowns/PopulationCap.js =================================================================== --- ps/trunk/binaries/data/mods/public/gui/gamesetup/Pages/GameSetupPage/GameSettings/Single/Dropdowns/PopulationCap.js (revision 25100) +++ ps/trunk/binaries/data/mods/public/gui/gamesetup/Pages/GameSetupPage/GameSettings/Single/Dropdowns/PopulationCap.js (revision 25101) @@ -1,71 +1,71 @@ GameSettingControls.PopulationCap = class PopulationCap extends GameSettingControlDropdown { constructor(...args) { super(...args); this.dropdown.list = g_PopulationCapacities.Title; this.dropdown.list_data = g_PopulationCapacities.Population; this.sprintfArgs = {}; g_GameSettings.population.watch(() => this.render(), ["useWorldPop", "cap", "perPlayer"]); g_GameSettings.map.watch(() => this.render(), ["type"]); this.render(); } render() { this.setHidden(g_GameSettings.population.useWorldPop); this.setEnabled(g_GameSettings.map.type != "scenario" && !g_GameSettings.population.perPlayer); if (g_GameSettings.population.perPlayer) this.label.caption = this.PerPlayerCaption; else this.setSelectedValue(g_GameSettings.population.cap); } onHoverChange() { let tooltip = this.Tooltip; if (this.dropdown.hovered != -1) { let popCap = g_PopulationCapacities.Population[this.dropdown.hovered]; let players = g_GameSettings.playerCount.nbPlayers; if (popCap * players >= this.PopulationCapacityRecommendation) { this.sprintfArgs.players = players; this.sprintfArgs.popCap = popCap; tooltip = setStringTags(sprintf(this.HoverTooltip, this.sprintfArgs), this.HoverTags); } } this.dropdown.tooltip = tooltip; } onSelectionChange(itemIdx) { g_GameSettings.population.setPopCap(false, g_PopulationCapacities.Population[itemIdx]); - this.gameSettingsControl.setNetworkInitAttributes(); + this.gameSettingsController.setNetworkInitAttributes(); } }; GameSettingControls.PopulationCap.prototype.TitleCaption = translate("Population Cap"); GameSettingControls.PopulationCap.prototype.Tooltip = translate("Select population limit."); GameSettingControls.PopulationCap.prototype.PerPlayerCaption = translateWithContext("population limit", "Per Player"); GameSettingControls.PopulationCap.prototype.HoverTooltip = translate("Warning: There might be performance issues if all %(players)s players reach %(popCap)s population."); GameSettingControls.PopulationCap.prototype.HoverTags = { "color": "orange" }; /** * Total number of units that the engine can run with smoothly. * It means a 4v4 with 150 population can still run nicely, but more than that might "lag". */ GameSettingControls.PopulationCap.prototype.PopulationCapacityRecommendation = 1200; Index: ps/trunk/binaries/data/mods/public/gui/gamesetup/Pages/GameSetupPage/GameSettings/Single/Dropdowns/TriggerDifficulty.js =================================================================== --- ps/trunk/binaries/data/mods/public/gui/gamesetup/Pages/GameSetupPage/GameSettings/Single/Dropdowns/TriggerDifficulty.js (revision 25100) +++ ps/trunk/binaries/data/mods/public/gui/gamesetup/Pages/GameSetupPage/GameSettings/Single/Dropdowns/TriggerDifficulty.js (revision 25101) @@ -1,44 +1,44 @@ GameSettingControls.TriggerDifficulty = class TriggerDifficulty extends GameSettingControlDropdown { constructor(...args) { super(...args); this.values = undefined; g_GameSettings.triggerDifficulty.watch(() => this.render(), ["value", "available"]); this.render(); } onHoverChange() { this.dropdown.tooltip = this.values && this.values.Tooltip[this.dropdown.hovered] || this.Tooltip; } render() { this.setHidden(!g_GameSettings.triggerDifficulty.available); if (!g_GameSettings.triggerDifficulty.available) return; this.values = prepareForDropdown(g_GameSettings.triggerDifficulty.getAvailableSettings()); this.dropdown.list = this.values.Title; this.dropdown.list_data = this.values.Difficulty; this.setSelectedValue(g_GameSettings.triggerDifficulty.value); } onSelectionChange(itemIdx) { g_GameSettings.triggerDifficulty.setValue(this.values.Difficulty[itemIdx]); - this.gameSettingsControl.setNetworkInitAttributes(); + this.gameSettingsController.setNetworkInitAttributes(); } }; GameSettingControls.TriggerDifficulty.prototype.TitleCaption = translate("Difficulty"); GameSettingControls.TriggerDifficulty.prototype.Tooltip = translate("Select the difficulty of this scenario."); Index: ps/trunk/binaries/data/mods/public/gui/gamesetup/Pages/GameSetupPage/GameSettings/Single/Sliders/RelicCount.js =================================================================== --- ps/trunk/binaries/data/mods/public/gui/gamesetup/Pages/GameSetupPage/GameSettings/Single/Sliders/RelicCount.js (revision 25100) +++ ps/trunk/binaries/data/mods/public/gui/gamesetup/Pages/GameSetupPage/GameSettings/Single/Sliders/RelicCount.js (revision 25101) @@ -1,51 +1,51 @@ GameSettingControls.RelicCount = class RelicCount extends GameSettingControlSlider { constructor(...args) { super(...args); this.sprintfValue = {}; this.available = false; g_GameSettings.relic.watch(() => this.render(), ["count", "available"]); g_GameSettings.map.watch(() => this.render(), ["type"]); this.render(); } render() { this.setHidden(!g_GameSettings.relic.available); this.setEnabled(g_GameSettings.map.type != "scenario"); if (g_GameSettings.relic.available) { let value = g_GameSettings.relic.count; this.sprintfValue.number = value; this.setSelectedValue( g_GameSettings.relic.count, value == 0 ? this.InstantVictory : sprintf(this.CaptionRelicCount(value), this.sprintfValue)); } } onValueChange(value) { g_GameSettings.relic.setCount(value); - this.gameSettingsControl.setNetworkInitAttributes(); + this.gameSettingsController.setNetworkInitAttributes(); } }; GameSettingControls.RelicCount.prototype.TitleCaption = translate("Relic Count"); GameSettingControls.RelicCount.prototype.CaptionRelicCount = relicCount => translatePlural("%(number)s relic", "%(number)s relics", relicCount); GameSettingControls.RelicCount.prototype.Tooltip = translate("Total number of relics spawned on the map. Relic victory is most realistic with only one or two relics. With greater numbers, the relics are important to capture to receive aura bonuses."); GameSettingControls.RelicCount.prototype.NameCaptureTheRelic = "capture_the_relic"; GameSettingControls.RelicCount.prototype.MinValue = 1; GameSettingControls.RelicCount.prototype.MaxValue = Object.keys(g_CivData).length; Index: ps/trunk/binaries/data/mods/public/gui/gamesetup/Pages/GameSetupPage/GameSettings/Single/Sliders/SeaLevelRiseTime.js =================================================================== --- ps/trunk/binaries/data/mods/public/gui/gamesetup/Pages/GameSetupPage/GameSettings/Single/Sliders/SeaLevelRiseTime.js (revision 25100) +++ ps/trunk/binaries/data/mods/public/gui/gamesetup/Pages/GameSetupPage/GameSettings/Single/Sliders/SeaLevelRiseTime.js (revision 25101) @@ -1,45 +1,45 @@ GameSettingControls.SeaLevelRiseTime = class SeaLevelRiseTime extends GameSettingControlSlider { constructor(...args) { super(...args); this.values = undefined; this.sprintfValue = {}; g_GameSettings.seaLevelRise.watch(() => this.render(), ["value"]); g_GameSettings.map.watch(() => this.render(), ["type"]); this.render(); } render() { this.setHidden(g_GameSettings.seaLevelRise.value === undefined); this.setEnabled(g_GameSettings.map.type != "scenario"); let value = g_GameSettings.seaLevelRise.value; this.sprintfValue.minutes = value; this.setSelectedValue( value, sprintf(this.SeaLevelRiseTimeCaption(value), this.sprintfValue)); } onValueChange(value) { g_GameSettings.seaLevelRise.setValue(value); - this.gameSettingsControl.setNetworkInitAttributes(); + this.gameSettingsController.setNetworkInitAttributes(); } }; GameSettingControls.SeaLevelRiseTime.prototype.TitleCaption = translate("Sea Level Rise Time"); GameSettingControls.SeaLevelRiseTime.prototype.Tooltip = translate("Set the time when the water will start to rise."); GameSettingControls.SeaLevelRiseTime.prototype.SeaLevelRiseTimeCaption = minutes => translatePluralWithContext("sea level rise time", "%(minutes)s minute", "%(minutes)s minutes", minutes); GameSettingControls.SeaLevelRiseTime.prototype.MinValue = 0; GameSettingControls.SeaLevelRiseTime.prototype.MaxValue = 60; Index: ps/trunk/binaries/data/mods/public/gui/gamesetup/Pages/GameSetupPage/GameSetupPage.xml =================================================================== --- ps/trunk/binaries/data/mods/public/gui/gamesetup/Pages/GameSetupPage/GameSetupPage.xml (revision 25100) +++ ps/trunk/binaries/data/mods/public/gui/gamesetup/Pages/GameSetupPage/GameSetupPage.xml (revision 25101) @@ -1,97 +1,94 @@ -