Differential D3243 Diff 16284 binaries/data/mods/public/gui/gamesetup/Controls/GameSettingsControl.js
Changeset View
Changeset View
Standalone View
Standalone View
binaries/data/mods/public/gui/gamesetup/Controls/GameSettingsControl.js
/** | /** | ||||
* This class provides a property independent interface to g_GameAttributes events. | * 'Controller' for the GUI handling of gamesettings. | ||||
* Classes may use this interface in order to react to changing g_GameAttributes. | |||||
*/ | */ | ||||
class GameSettingsControl | class GameSettingsControl | ||||
{ | { | ||||
constructor(setupWindow, netMessages, startGameControl, mapCache) | constructor(setupWindow, netMessages, startGameControl, mapCache) | ||||
{ | { | ||||
this.startGameControl = startGameControl; | this.startGameControl = startGameControl; | ||||
this.mapCache = mapCache; | this.mapCache = mapCache; | ||||
this.gameSettingsFile = new GameSettingsFile(this); | this.gameSettingsFile = new GameSettingsFile(this); | ||||
this.previousMap = undefined; | this.guiData = new GameSettingsGuiData(); | ||||
this.depth = 0; | |||||
// This property may be read from publicly | |||||
this.autostart = false; | |||||
this.gameAttributesChangeHandlers = new Set(); | this.gameAttributesChangeHandlers = new Set(); | ||||
this.gameAttributesBatchChangeHandlers = new Set(); | this.gameAttributesBatchChangeHandlers = new Set(); | ||||
this.gameAttributesFinalizeHandlers = new Set(); | this.gameAttributesFinalizeHandlers = new Set(); | ||||
this.pickRandomItemsHandlers = new Set(); | |||||
this.assignPlayerHandlers = new Set(); | this.assignPlayerHandlers = new Set(); | ||||
this.mapChangeHandlers = new Set(); | |||||
setupWindow.registerLoadHandler(this.onLoad.bind(this)); | setupWindow.registerLoadHandler(this.onLoad.bind(this)); | ||||
setupWindow.registerGetHotloadDataHandler(this.onGetHotloadData.bind(this)); | setupWindow.registerGetHotloadDataHandler(this.onGetHotloadData.bind(this)); | ||||
startGameControl.registerLaunchGameHandler(this.onLaunchGame.bind(this)); | startGameControl.registerLaunchGameHandler(this.onLaunchGame.bind(this)); | ||||
setupWindow.registerClosePageHandler(this.onClose.bind(this)); | setupWindow.registerClosePageHandler(this.onClose.bind(this)); | ||||
if (g_IsNetworked) | if (g_IsNetworked) | ||||
netMessages.registerNetMessageHandler("gamesetup", this.onGamesetupMessage.bind(this)); | netMessages.registerNetMessageHandler("gamesetup", this.onGamesetupMessage.bind(this)); | ||||
} | } | ||||
registerMapChangeHandler(handler) | |||||
{ | |||||
this.mapChangeHandlers.add(handler); | |||||
} | |||||
unregisterMapChangeHandler(handler) | |||||
{ | |||||
this.mapChangeHandlers.delete(handler); | |||||
} | |||||
/** | /** | ||||
* This message is triggered everytime g_GameAttributes change. | * This message is triggered when any game setting changes, though | ||||
* Handlers may subsequently change g_GameAttributes and trigger this message again. | * not necessarily for each individual change. | ||||
*/ | |||||
registerGameAttributesChangeHandler(handler) | |||||
{ | |||||
this.gameAttributesChangeHandlers.add(handler); | |||||
} | |||||
unregisterGameAttributesChangeHandler(handler) | |||||
{ | |||||
this.gameAttributesChangeHandlers.delete(handler); | |||||
} | |||||
/** | |||||
* This message is triggered after g_GameAttributes changed and recursed gameAttributesChangeHandlers finished. | |||||
* The use case for this is to update GUI objects which do not change g_GameAttributes but only display the attributes. | |||||
*/ | */ | ||||
registerGameAttributesBatchChangeHandler(handler) | registerGameAttributesBatchChangeHandler(handler) | ||||
{ | { | ||||
this.gameAttributesBatchChangeHandlers.add(handler); | this.gameAttributesBatchChangeHandlers.add(handler); | ||||
} | } | ||||
unregisterGameAttributesBatchChangeHandler(handler) | unregisterGameAttributesBatchChangeHandler(handler) | ||||
{ | { | ||||
this.gameAttributesBatchChangeHandlers.delete(handler); | this.gameAttributesBatchChangeHandlers.delete(handler); | ||||
} | } | ||||
registerGameAttributesFinalizeHandler(handler) | |||||
{ | |||||
this.gameAttributesFinalizeHandlers.add(handler); | |||||
} | |||||
unregisterGameAttributesFinalizeHandler(handler) | |||||
{ | |||||
this.gameAttributesFinalizeHandlers.delete(handler); | |||||
} | |||||
registerAssignPlayerHandler(handler) | registerAssignPlayerHandler(handler) | ||||
{ | { | ||||
this.assignPlayerHandlers.add(handler); | this.assignPlayerHandlers.add(handler); | ||||
} | } | ||||
unregisterAssignPlayerHandler(handler) | unregisterAssignPlayerHandler(handler) | ||||
{ | { | ||||
this.assignPlayerHandlers.delete(handler); | this.assignPlayerHandlers.delete(handler); | ||||
} | } | ||||
registerPickRandomItemsHandler(handler) | |||||
{ | |||||
this.pickRandomItemsHandlers.add(handler); | |||||
} | |||||
unregisterPickRandomItemsHandler(handler) | |||||
{ | |||||
this.pickRandomItemsHandlers.delete(handler); | |||||
} | |||||
onLoad(initData, hotloadData) | onLoad(initData, hotloadData) | ||||
{ | { | ||||
if (initData && initData.map && initData.mapType) | |||||
{ | |||||
if (initData.autostart) | |||||
Object.defineProperty(this, "autostart", { | |||||
"value": true, | |||||
"writable": false, | |||||
"configurable": false | |||||
}); | |||||
// TODO: Fix g_GameAttributes, g_GameAttributes.settings, | |||||
// g_GameAttributes.settings.PlayerData object references and | |||||
// copy over each attribute individually when receiving | |||||
// settings from the server or the local file. | |||||
g_GameAttributes = initData; | |||||
this.updateGameAttributes(); | |||||
// Don't launchGame before all Load handlers finished | |||||
} | |||||
else | |||||
{ | |||||
if (hotloadData) | if (hotloadData) | ||||
g_GameAttributes = hotloadData.gameAttributes; | this.parseSettings(hotloadData.gameAttributes); | ||||
else if (g_IsController && this.gameSettingsFile.enabled) | else if (g_IsController && this.gameSettingsFile.enabled) | ||||
g_GameAttributes = this.gameSettingsFile.loadFile(); | this.parseSettings(this.gameSettingsFile.loadFile()); | ||||
this.updateGameAttributes(); | this.updateGameAttributes(); | ||||
this.setNetworkGameAttributes(); | this.setNetworkGameAttributes(); | ||||
} | } | ||||
} | |||||
onClose() | onClose() | ||||
{ | { | ||||
if (!this.autostart) | if (!this.autostart) | ||||
this.gameSettingsFile.saveFile(); | this.gameSettingsFile.saveFile(); | ||||
} | } | ||||
onGetHotloadData(object) | onGetHotloadData(object) | ||||
{ | { | ||||
object.gameAttributes = g_GameAttributes; | object.gameAttributes = this.getSettings(); | ||||
} | } | ||||
onGamesetupMessage(message) | onGamesetupMessage(message) | ||||
{ | { | ||||
if (!message.data) | if (!message.data || g_IsController) | ||||
return; | return; | ||||
g_GameAttributes = message.data; | this.parseSettings(message.data); | ||||
this.updateGameAttributes(); | |||||
} | } | ||||
/** | /** | ||||
* This is to be called whenever g_GameAttributes has been changed except on gameAttributes finalization. | * Returns the game settings, augmented by GUI-specific data. | ||||
*/ | */ | ||||
updateGameAttributes() | getSettings() | ||||
{ | |||||
if (this.depth == 0) | |||||
Engine.ProfileStart("updateGameAttributes"); | |||||
if (this.depth >= this.MaxDepth) | |||||
{ | |||||
error("Infinite loop: " + new Error().stack); | |||||
Engine.ProfileStop(); | |||||
return; | |||||
} | |||||
++this.depth; | |||||
// Basic sanitization | |||||
{ | { | ||||
if (!g_GameAttributes.settings) | let ret = g_NewGameSettings.LegacySerialize(); | ||||
g_GameAttributes.settings = {}; | ret.guiData = this.guiData.Serialize(); | ||||
return ret; | |||||
if (!g_GameAttributes.settings.PlayerData) | |||||
g_GameAttributes.settings.PlayerData = new Array(this.DefaultPlayerCount); | |||||
for (let i = 0; i < g_GameAttributes.settings.PlayerData.length; ++i) | |||||
if (!g_GameAttributes.settings.PlayerData[i]) | |||||
g_GameAttributes.settings.PlayerData[i] = {}; | |||||
} | } | ||||
// Map change handlers are triggered first, so that GameSettingControls can update their | /** | ||||
// gameAttributes model prior to applying that model in their gameAttributesChangeHandler. | * Parse the following settings. | ||||
if (g_GameAttributes.map && this.previousMap != g_GameAttributes.map && g_GameAttributes.mapType) | */ | ||||
{ | parseSettings(settings) | ||||
this.previousMap = g_GameAttributes.map; | |||||
// Use a try..catch to avoid completely failing in case of an error | |||||
// as this prevents even going back to the main menu. | |||||
try | |||||
{ | { | ||||
let mapData = this.mapCache.getMapData(g_GameAttributes.mapType, g_GameAttributes.map); | if (settings.guiData) | ||||
for (let handler of this.mapChangeHandlers) | this.guiData.Deserialize(settings.guiData); | ||||
handler(mapData); | g_NewGameSettings.LegacyDeserialize(settings); | ||||
} catch(err) { | |||||
// Report the error regardless so that the underlying bug gets fixed. | |||||
error(err); | |||||
error(err.stack); | |||||
} | |||||
} | } | ||||
for (let handler of this.gameAttributesChangeHandlers) | /** | ||||
handler(); | * This is to be called whenever g_GameAttributes has been changed except on gameAttributes finalization. | ||||
*/ | |||||
--this.depth; | updateGameAttributes() | ||||
if (this.depth == 0) | |||||
{ | { | ||||
for (let handler of this.gameAttributesBatchChangeHandlers) | for (let handler of this.gameAttributesBatchChangeHandlers) | ||||
handler(); | handler(); | ||||
Engine.ProfileStop(); | |||||
} | |||||
} | } | ||||
/** | /** | ||||
* This function is to be called when a GUI control has initiated a value change. | * 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 | * 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. | * received and the data had only been modified deterministically. | ||||
* | * | ||||
* This is run on a timer to avoid flooding the network with messages, | * This is run on a timer to avoid flooding the network with messages, | ||||
* e.g. when modifying a slider. | * e.g. when modifying a slider. | ||||
*/ | */ | ||||
setNetworkGameAttributes() | setNetworkGameAttributes() | ||||
{ | { | ||||
for (let handler of this.gameAttributesBatchChangeHandlers) | |||||
handler(); | |||||
if (g_IsNetworked && this.timer === undefined) | if (g_IsNetworked && this.timer === undefined) | ||||
this.timer = setTimeout(this.setNetworkGameAttributesImmediately.bind(this), this.Timeout); | this.timer = setTimeout(this.setNetworkGameAttributesImmediately.bind(this), this.Timeout); | ||||
} | } | ||||
setNetworkGameAttributesImmediately() | setNetworkGameAttributesImmediately() | ||||
{ | { | ||||
delete this.timer; | if (this.timer) | ||||
if (g_IsNetworked) | |||||
Engine.SetNetworkGameAttributes(g_GameAttributes); | |||||
} | |||||
getPlayerData(gameAttributes, playerIndex) | |||||
{ | |||||
return gameAttributes && | |||||
gameAttributes.settings && | |||||
gameAttributes.settings.PlayerData && | |||||
gameAttributes.settings.PlayerData[playerIndex] || undefined; | |||||
} | |||||
assignPlayer(sourcePlayerIndex, playerIndex) | |||||
{ | { | ||||
if (playerIndex == -1) | clearTimeout(this.timer); | ||||
return; | delete this.timer; | ||||
let target = this.getPlayerData(g_GameAttributes, playerIndex); | |||||
let source = this.getPlayerData(g_GameAttributes, sourcePlayerIndex); | |||||
for (let handler of this.assignPlayerHandlers) | |||||
handler(source, target); | |||||
this.updateGameAttributes(); | |||||
this.setNetworkGameAttributes(); | |||||
} | } | ||||
/** | if (g_IsNetworked && g_IsController) | ||||
* This function is called everytime a random setting selection was resolved, | Engine.SetNetworkGameAttributes(this.getSettings()); | ||||
* so that subsequent random settings are triggered too, | |||||
* for example picking a random biome after picking a random map. | |||||
*/ | |||||
pickRandomItems() | |||||
{ | |||||
for (let handler of this.pickRandomItemsHandlers) | |||||
handler(); | |||||
} | } | ||||
onLaunchGame() | onLaunchGame() | ||||
{ | { | ||||
if (!this.autostart) | if (!this.autostart) | ||||
this.gameSettingsFile.saveFile(); | this.gameSettingsFile.saveFile(); | ||||
this.pickRandomItems(); | |||||
for (let handler of this.gameAttributesFinalizeHandlers) | for (let handler of this.gameAttributesFinalizeHandlers) | ||||
handler(); | handler(); | ||||
g_NewGameSettings.prepareForLaunch(); | |||||
// Replace player names with the real players. | |||||
for (let guid in g_PlayerAssignments) | |||||
if (g_PlayerAssignments[guid].player !== -1) | |||||
g_NewGameSettings.playerName.values[g_PlayerAssignments[guid].player -1] = g_PlayerAssignments[guid].name; | |||||
this.setNetworkGameAttributesImmediately(); | this.setNetworkGameAttributesImmediately(); | ||||
} | } | ||||
} | } | ||||
GameSettingsControl.prototype.MaxDepth = 512; | |||||
/** | /** | ||||
* Wait (at most) this many milliseconds before sending network messages. | * Wait (at most) this many milliseconds before sending network messages. | ||||
*/ | */ | ||||
GameSettingsControl.prototype.Timeout = 400; | GameSettingsControl.prototype.Timeout = 400; | ||||
/** | |||||
* This number is used when selecting the random map type, which doesn't provide PlayerData. | |||||
*/ | |||||
GameSettingsControl.prototype.DefaultPlayerCount = 4; |
Wildfire Games · Phabricator