Index: ps/trunk/binaries/data/mods/public/gui/session/MenuButtons.js
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/session/MenuButtons.js (revision 23114)
+++ ps/trunk/binaries/data/mods/public/gui/session/MenuButtons.js (revision 23115)
@@ -1,250 +1,251 @@
/**
* This class is extended in subclasses.
* Each subclass represents one button in the session menu.
* All subclasses store the button member so that mods can change it easily.
*/
class MenuButtons
{
}
MenuButtons.prototype.Manual = class
{
constructor(button, pauseControl)
{
this.button = button;
this.button.caption = translate(translate("Manual"));
this.pauseControl = pauseControl;
}
onPress()
{
closeOpenDialogs();
this.pauseControl.implicitPause();
Engine.PushGuiPage("page_manual.xml", {}, resumeGame);
}
};
MenuButtons.prototype.Chat = class
{
constructor(button, pauseControl, playerViewControl, chat)
{
this.button = button;
this.button.caption = translate("Chat");
this.chat = chat;
registerHotkeyChangeHandler(this.rebuild.bind(this));
}
rebuild()
{
this.button.tooltip = this.chat.getOpenHotkeyTooltip().trim();
}
onPress()
{
this.chat.openPage();
}
};
MenuButtons.prototype.Save = class
{
constructor(button, pauseControl)
{
this.button = button;
this.button.caption = translate("Save");
this.pauseControl = pauseControl;
}
onPress()
{
closeOpenDialogs();
this.pauseControl.implicitPause();
Engine.PushGuiPage(
"page_loadgame.xml",
{ "savedGameData": getSavedGameData() },
resumeGame);
}
};
MenuButtons.prototype.Summary = class
{
constructor(button, pauseControl)
{
this.button = button;
this.button.caption = translate("Summary");
this.button.hotkey = "summary";
// TODO: Atlas should pass g_GameAttributes.settings
this.button.enabled = !Engine.IsAtlasRunning();
this.pauseControl = pauseControl;
this.selectedData = undefined;
registerHotkeyChangeHandler(this.rebuild.bind(this));
}
rebuild()
{
this.button.tooltip = sprintf(translate("Press %(hotkey)s to open the summary screen."), {
"hotkey": colorizeHotkey("%(hotkey)s", this.button.hotkey),
});
}
onPress()
{
if (Engine.IsAtlasRunning())
return;
closeOpenDialogs();
this.pauseControl.implicitPause();
// Allows players to see their own summary.
// If they have shared ally vision researched, they are able to see the summary of there allies too.
let simState = Engine.GuiInterfaceCall("GetExtendedSimulationState");
Engine.PushGuiPage(
"page_summary.xml",
{
"sim": {
"mapSettings": g_GameAttributes.settings,
"playerStates": simState.players.filter((state, player) =>
g_IsObserver || player == 0 || player == g_ViewedPlayer ||
simState.players[g_ViewedPlayer].hasSharedLos && g_Players[player].isMutualAlly[g_ViewedPlayer]),
"timeElapsed": simState.timeElapsed
},
"gui": {
"dialog": true,
"isInGame": true
},
"selectedData": this.selectedData
},
data => {
this.selectedData = data.summarySelectedData;
this.pauseControl.implicitResume();
});
}
};
MenuButtons.prototype.Lobby = class
{
constructor(button)
{
this.button = button;
this.button.caption = translate("Lobby");
this.button.hotkey = "lobby";
this.button.enabled = Engine.HasXmppClient();
registerHotkeyChangeHandler(this.rebuild.bind(this));
}
rebuild()
{
this.button.tooltip = sprintf(translate("Press %(hotkey)s to open the multiplayer lobby page without leaving the game."), {
"hotkey": colorizeHotkey("%(hotkey)s", this.button.hotkey),
});
}
onPress()
{
if (!Engine.HasXmppClient())
return;
closeOpenDialogs();
Engine.PushGuiPage("page_lobby.xml", { "dialog": true });
}
};
MenuButtons.prototype.Options = class
{
constructor(button, pauseControl)
{
this.button = button;
this.button.caption = translate("Options");
this.pauseControl = pauseControl;
}
onPress()
{
closeOpenDialogs();
this.pauseControl.implicitPause();
Engine.PushGuiPage(
"page_options.xml",
{},
changes => {
fireConfigChangeHandlers(changes);
resumeGame();
});
}
};
MenuButtons.prototype.Pause = class
{
constructor(button, pauseControl, playerViewControl)
{
this.button = button;
this.button.hotkey = "pause";
this.pauseControl = pauseControl;
registerPlayersInitHandler(this.rebuild.bind(this));
registerPlayersFinishedHandler(this.rebuild.bind(this));
playerViewControl.registerPlayerIDChangeHandler(this.rebuild.bind(this));
pauseControl.registerPauseHandler(this.rebuild.bind(this));
registerHotkeyChangeHandler(this.rebuild.bind(this));
+ registerNetworkStatusChangeHandler(this.rebuild.bind(this));
}
rebuild()
{
- this.button.enabled = !g_IsObserver || !g_IsNetworked || g_IsController;
+ this.button.enabled = this.pauseControl.canPause(true);
this.button.caption = this.pauseControl.explicitPause ? translate("Resume") : translate("Pause");
this.button.tooltip = sprintf(translate("Press %(hotkey)s to pause or resume the game."), {
"hotkey": colorizeHotkey("%(hotkey)s", this.button.hotkey),
});
}
onPress()
{
this.pauseControl.setPaused(!g_PauseControl.explicitPause, true);
}
};
MenuButtons.prototype.Resign = class
{
constructor(button, pauseControl, playerViewControl)
{
this.button = button;
this.button.caption = translate("Resign");
this.pauseControl = pauseControl;
registerPlayersInitHandler(this.rebuild.bind(this));
registerPlayersFinishedHandler(this.rebuild.bind(this));
playerViewControl.registerPlayerIDChangeHandler(this.rebuild.bind(this));
}
rebuild()
{
this.button.enabled = !g_IsObserver;
}
onPress()
{
(new ResignConfirmation()).display();
}
};
MenuButtons.prototype.Exit = class
{
constructor(button, pauseControl)
{
this.button = button;
this.button.caption = translate("Exit");
this.button.enabled = !Engine.IsAtlasRunning();
this.pauseControl = pauseControl;
}
onPress()
{
for (let name in QuitConfirmationMenu.prototype)
{
let quitConfirmation = new QuitConfirmationMenu.prototype[name]();
if (quitConfirmation.enabled())
quitConfirmation.display();
}
}
};
Index: ps/trunk/binaries/data/mods/public/gui/session/NetworkStatusOverlay.js
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/session/NetworkStatusOverlay.js (nonexistent)
+++ ps/trunk/binaries/data/mods/public/gui/session/NetworkStatusOverlay.js (revision 23115)
@@ -0,0 +1,47 @@
+/**
+ * This class shows an overlay if the game is stalled due to some clients not being
+ * ready to simulate yet, and in that case provides specifics to that reason.
+ */
+class NetworkStatusOverlay
+{
+ constructor()
+ {
+ this.netStatus = Engine.GetGUIObjectByName("netStatus");
+ this.loadingClientsText = Engine.GetGUIObjectByName("loadingClientsText");
+
+ Engine.GetGUIObjectByName("disconnectedExitButton").onPress = endGame;
+
+ registerNetworkStatusChangeHandler(this.onNetStatusMessage.bind(this));
+ registerClientsLoadingHandler(this.onClientsLoadingMessage.bind(this));
+ }
+
+ onNetStatusMessage(message)
+ {
+ if (!this.StatusCaption[message.status])
+ {
+ error("Unrecognized netstatus type '" + message.status + "'");
+ return;
+ }
+
+ this.netStatus.caption = this.StatusCaption[message.status](message);
+ this.netStatus.hidden = message.status == "active";
+ this.loadingClientsText.hidden = message.status != "waiting_for_players";
+ }
+
+ onClientsLoadingMessage(guids)
+ {
+ this.loadingClientsText.caption = guids.map(guid => colorizePlayernameByGUID(guid)).join(this.Comma);
+ }
+}
+
+NetworkStatusOverlay.prototype.StatusCaption = {
+ "authenticated": msg => translate("Connection to the server has been authenticated."),
+ "connected": msg => translate("Connected to the server."),
+ "disconnected": msg => translate("Connection to the server has been lost.") + "\n" +
+ getDisconnectReason(msg.reason, true),
+ "waiting_for_players": msg => translate("Waiting for players to connect:"),
+ "join_syncing": msg => translate("Synchronizing gameplay with other players…"),
+ "active": msg => ""
+};
+
+NetworkStatusOverlay.prototype.Comma = translateWithContext("Separator for a list of client loading messages", ", ");
Property changes on: ps/trunk/binaries/data/mods/public/gui/session/NetworkStatusOverlay.js
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Index: ps/trunk/binaries/data/mods/public/gui/session/NetworkStatusOverlay.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/session/NetworkStatusOverlay.xml (nonexistent)
+++ ps/trunk/binaries/data/mods/public/gui/session/NetworkStatusOverlay.xml (revision 23115)
@@ -0,0 +1,12 @@
+
+
Property changes on: ps/trunk/binaries/data/mods/public/gui/session/NetworkStatusOverlay.xml
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Index: ps/trunk/binaries/data/mods/public/gui/session/PauseControl.js
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/session/PauseControl.js (revision 23114)
+++ ps/trunk/binaries/data/mods/public/gui/session/PauseControl.js (revision 23115)
@@ -1,86 +1,105 @@
/**
* Controller to pause or resume the game and remember which players paused the game.
*
* If the current player ordered a pause manually, it is called explicit pause.
* If the player opened a dialog in singleplayer mode, the game is paused implicitly.
*/
class PauseControl
{
constructor()
{
/**
* This is true if the current player has paused the game using the pause button or hotkey.
* The game may also be paused without this being true in singleplayermode when opening a dialog.
*/
this.explicitPause = false;
/**
* List of GUIDs of players who have currently paused the game, if the game is networked.
*/
this.pausingClients = [];
/**
* Event handlers called when anyone paused.
*/
this.pauseHandlers = [];
+
+ registerNetworkStatusChangeHandler(this.onNetworkStatusChangeHandler.bind(this));
+ }
+
+ onNetworkStatusChangeHandler()
+ {
+ if (g_Disconnected)
+ {
+ Engine.SetPaused(true, false);
+ this.callPauseHandlers();
+ }
}
registerPauseHandler(handler)
{
this.pauseHandlers.push(handler);
}
callPauseHandlers()
{
for (let handler of this.pauseHandlers)
handler();
}
/**
* Called from UI dialogs, but only in singleplayermode.
*/
implicitPause()
{
this.setPaused(true, false);
}
implicitResume()
{
this.setPaused(false, false);
}
- setPaused(pause, explicit)
+ /**
+ * Returns true if the current player is allowed to pause the game currently.
+ */
+ canPause(explicit)
{
// Don't pause the game in multiplayer mode when opening dialogs.
// The NetServer only supports pausing after all clients finished loading the game.
- if (g_IsNetworked && (!explicit || !g_IsNetworkedActive))
+ return !g_IsNetworked || explicit && g_IsNetworkedActive && (!g_IsObserver || g_IsController);
+ }
+
+ setPaused(pause, explicit)
+ {
+ if (!this.canPause(explicit))
return;
if (explicit)
this.explicitPause = pause;
// If explicit, send network message informing other clients
Engine.SetPaused(this.explicitPause || pause || g_Disconnected, explicit);
if (g_IsNetworked)
this.setClientPauseState(Engine.GetPlayerGUID(), this.explicitPause);
else
this.callPauseHandlers();
}
/**
* Called when a client pauses or resumes in a multiplayer game.
*/
setClientPauseState(guid, paused)
{
// Update the list of pausing clients.
let index = this.pausingClients.indexOf(guid);
if (paused && index == -1)
this.pausingClients.push(guid);
else if (!paused && index != -1)
this.pausingClients.splice(index, 1);
Engine.SetPaused(!!this.pausingClients.length, false);
this.callPauseHandlers();
}
}
Index: ps/trunk/binaries/data/mods/public/gui/session/PauseOverlay.js
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/session/PauseOverlay.js (revision 23114)
+++ ps/trunk/binaries/data/mods/public/gui/session/PauseOverlay.js (revision 23115)
@@ -1,44 +1,45 @@
/**
* Displays an overlay while any player pauses the game.
* Indicates which players have paused.
*/
class PauseOverlay
{
constructor(pauseControl)
{
this.pauseControl = pauseControl;
this.pausedByText = Engine.GetGUIObjectByName("pausedByText");
this.pausedByText.hidden = !g_IsNetworked;
this.pauseOverlay = Engine.GetGUIObjectByName("pauseOverlay");
this.pauseOverlay.onPress = this.onPress.bind(this);
this.resumeMessage = Engine.GetGUIObjectByName("resumeMessage");
- pauseControl.registerPauseHandler(this.onPauseChange.bind(this));
+ registerNetworkStatusChangeHandler(this.rebuild.bind(this));
+ pauseControl.registerPauseHandler(this.rebuild.bind(this));
}
onPress()
{
if (this.pauseControl.explicitPause)
this.pauseControl.setPaused(false, true);
}
- onPauseChange()
+ rebuild()
{
- let hidden = !this.pauseControl.explicitPause && !this.pauseControl.pausingClients.length;
+ let hidden = !this.pauseControl.explicitPause && !this.pauseControl.pausingClients.length || g_Disconnected;
this.pauseOverlay.hidden = hidden;
if (hidden)
return;
this.resumeMessage.hidden = !this.pauseControl.explicitPause;
this.pausedByText.caption = sprintf(translate(this.PausedByCaption), {
"players": this.pauseControl.pausingClients.map(guid =>
colorizePlayernameByGUID(guid)).join(translateWithContext("Separator for a list of players", ", "))
});
}
}
PauseOverlay.prototype.PausedByCaption = markForTranslation("Paused by %(players)s");
Index: ps/trunk/binaries/data/mods/public/gui/session/messages.js
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/session/messages.js (revision 23114)
+++ ps/trunk/binaries/data/mods/public/gui/session/messages.js (revision 23115)
@@ -1,681 +1,668 @@
/**
* All known cheat commands.
*/
const g_Cheats = getCheatsData();
/**
* All tutorial messages received so far.
*/
var g_TutorialMessages = [];
/**
* GUI tags applied to the most recent tutorial message.
*/
var g_TutorialNewMessageTags = { "color": "yellow" };
/**
* These handlers are called everytime a client joins or disconnects.
*/
var g_PlayerAssignmentsChangeHandlers = new Set();
/**
* These handlers are called when the ceasefire time has run out.
*/
var g_CeasefireEndedHandlers = new Set();
/**
+ * These handlers are fired when the match is networked and
+ * the current client established the connection, authenticated,
+ * finished the loading screen, starts or finished synchronizing after a rejoin.
+ * The messages are constructed in NetClient.cpp.
+ */
+var g_NetworkStatusChangeHandlers = new Set();
+
+/**
+ * These handlers are triggered whenever a client finishes the loading screen.
+ */
+var g_ClientsLoadingHandlers = new Set();
+
+/**
* These handlers are fired if the server informed the players that the networked game is out of sync.
*/
var g_NetworkOutOfSyncHandlers = new Set();
/**
* Handle all netmessage types that can occur.
*/
var g_NetMessageTypes = {
"netstatus": msg => {
handleNetStatusMessage(msg);
},
"netwarn": msg => {
addNetworkWarning(msg);
},
"out-of-sync": msg => {
for (let handler of g_NetworkOutOfSyncHandlers)
handler(msg);
},
"players": msg => {
handlePlayerAssignmentsMessage(msg);
},
"paused": msg => {
g_PauseControl.setClientPauseState(msg.guid, msg.pause);
},
"clients-loading": msg => {
- handleClientsLoadingMessage(msg.guids);
+ for (let handler of g_ClientsLoadingHandlers)
+ handler(msg.guids);
},
"rejoined": msg => {
addChatMessage({
"type": "rejoined",
"guid": msg.guid
});
},
"kicked": msg => {
addChatMessage({
"type": "kicked",
"username": msg.username,
"banned": msg.banned
});
},
"chat": msg => {
addChatMessage({
"type": "message",
"guid": msg.guid,
"text": msg.text
});
},
"aichat": msg => {
addChatMessage({
"type": "message",
"guid": msg.guid,
"text": msg.text,
"translate": true
});
},
"gamesetup": msg => {}, // Needed for autostart
"start": msg => {}
};
-/**
- * Show a label and grey overlay or hide both on connection change.
- */
-var g_StatusMessageTypes = {
- "authenticated": msg => translate("Connection to the server has been authenticated."),
- "connected": msg => translate("Connected to the server."),
- "disconnected": msg => translate("Connection to the server has been lost.") + "\n" +
- getDisconnectReason(msg.reason, true),
- "waiting_for_players": msg => translate("Waiting for players to connect:"),
- "join_syncing": msg => translate("Synchronizing gameplay with other players…"),
- "active": msg => ""
-};
-
var g_PlayerStateMessages = {
"won": translate("You have won!"),
"defeated": translate("You have been defeated!")
};
/**
* Defines how the GUI reacts to notifications that are sent by the simulation.
* Don't open new pages (message boxes) here! Otherwise further notifications
* handled in the same turn can't access the GUI objects anymore.
*/
var g_NotificationsTypes =
{
"chat": function(notification, player)
{
let message = {
"type": "message",
"guid": findGuidForPlayerID(player) || -1,
"text": notification.message
};
if (message.guid == -1)
message.player = player;
addChatMessage(message);
},
"aichat": function(notification, player)
{
let message = {
"type": "message",
"text": notification.message,
"guid": findGuidForPlayerID(player) || -1,
"player": player,
"translate": true
};
if (notification.translateParameters)
{
message.translateParameters = notification.translateParameters;
message.parameters = notification.parameters;
colorizePlayernameParameters(notification.parameters);
}
addChatMessage(message);
},
"defeat": function(notification, player)
{
playersFinished(notification.allies, notification.message, false);
},
"won": function(notification, player)
{
playersFinished(notification.allies, notification.message, true);
},
"diplomacy": function(notification, player)
{
updatePlayerData();
g_DiplomacyColors.onDiplomacyChange();
addChatMessage({
"type": "diplomacy",
"sourcePlayer": player,
"targetPlayer": notification.targetPlayer,
"status": notification.status
});
},
"ceasefire-ended": function(notification, player)
{
updatePlayerData();
for (let handler of g_CeasefireEndedHandlers)
handler();
},
"tutorial": function(notification, player)
{
updateTutorial(notification);
},
"tribute": function(notification, player)
{
addChatMessage({
"type": "tribute",
"sourcePlayer": notification.donator,
"targetPlayer": player,
"amounts": notification.amounts
});
},
"barter": function(notification, player)
{
addChatMessage({
"type": "barter",
"player": player,
"amountsSold": notification.amountsSold,
"amountsBought": notification.amountsBought,
"resourceSold": notification.resourceSold,
"resourceBought": notification.resourceBought
});
},
"spy-response": function(notification, player)
{
g_DiplomacyDialog.onSpyResponse(notification, player);
if (notification.entity && g_ViewedPlayer == player)
{
g_DiplomacyDialog.close();
setCameraFollow(notification.entity);
}
},
"attack": function(notification, player)
{
if (player != g_ViewedPlayer)
return;
// Focus camera on attacks
if (g_FollowPlayer)
{
setCameraFollow(notification.target);
g_Selection.reset();
if (notification.target)
g_Selection.addList([notification.target]);
}
if (Engine.ConfigDB_GetValue("user", "gui.session.notifications.attack") !== "true")
return;
addChatMessage({
"type": "attack",
"player": player,
"attacker": notification.attacker,
"targetIsDomesticAnimal": notification.targetIsDomesticAnimal
});
},
"phase": function(notification, player)
{
addChatMessage({
"type": "phase",
"player": player,
"phaseName": notification.phaseName,
"phaseState": notification.phaseState
});
},
"dialog": function(notification, player)
{
if (player == Engine.GetPlayerID())
openDialog(notification.dialogName, notification.data, player);
},
"resetselectionpannel": function(notification, player)
{
if (player != Engine.GetPlayerID())
return;
g_Selection.rebuildSelection({});
},
"playercommand": function(notification, player)
{
// For observers, focus the camera on units commanded by the selected player
if (!g_FollowPlayer || player != g_ViewedPlayer)
return;
let cmd = notification.cmd;
// Ignore rallypoint commands of trained animals
let entState = cmd.entities && cmd.entities[0] && GetEntityState(cmd.entities[0]);
if (g_ViewedPlayer != 0 &&
entState && entState.identity && entState.identity.classes &&
entState.identity.classes.indexOf("Animal") != -1)
return;
// Focus the building to construct
if (cmd.type == "repair")
{
let targetState = GetEntityState(cmd.target);
if (targetState)
Engine.CameraMoveTo(targetState.position.x, targetState.position.z);
}
else if (cmd.type == "delete-entities" && notification.position)
Engine.CameraMoveTo(notification.position.x, notification.position.y);
// Focus commanded entities, but don't lose previous focus when training units
else if (cmd.type != "train" && cmd.type != "research" && entState)
setCameraFollow(cmd.entities[0]);
if (["walk", "attack-walk", "patrol"].indexOf(cmd.type) != -1)
DrawTargetMarker(cmd);
// Select units affected by that command
let selection = [];
if (cmd.entities)
selection = cmd.entities;
if (cmd.target)
selection.push(cmd.target);
// Allow gaia in selection when gathering
g_Selection.reset();
g_Selection.addList(selection, false, cmd.type == "gather");
},
"play-tracks": function(notification, player)
{
if (notification.lock)
{
global.music.storeTracks(notification.tracks.map(track => ({ "Type": "custom", "File": track })));
global.music.setState(global.music.states.CUSTOM);
}
global.music.setLocked(notification.lock);
}
};
function registerPlayerAssignmentsChangeHandler(handler)
{
g_PlayerAssignmentsChangeHandlers.add(handler);
}
function registerCeasefireEndedHandler(handler)
{
g_CeasefireEndedHandlers.add(handler);
}
function registerNetworkOutOfSyncHandler(handler)
{
g_NetworkOutOfSyncHandlers.add(handler);
}
+function registerNetworkStatusChangeHandler(handler)
+{
+ g_NetworkStatusChangeHandlers.add(handler);
+}
+
+function registerClientsLoadingHandler(handler)
+{
+ g_ClientsLoadingHandlers.add(handler);
+}
+
/**
* Loads all known cheat commands.
*/
function getCheatsData()
{
let cheats = {};
for (let fileName of Engine.ListDirectoryFiles("simulation/data/cheats/", "*.json", false))
{
let currentCheat = Engine.ReadJSONFile(fileName);
if (cheats[currentCheat.Name])
warn("Cheat name '" + currentCheat.Name + "' is already present");
else
cheats[currentCheat.Name] = currentCheat.Data;
}
return deepfreeze(cheats);
}
/**
* Reads userinput from the chat and sends a simulation command in case it is a known cheat.
*
* @returns {boolean} - True if a cheat was executed.
*/
function executeCheat(text)
{
if (!controlsPlayer(Engine.GetPlayerID()) ||
!g_Players[Engine.GetPlayerID()].cheatsEnabled)
return false;
// Find the cheat code that is a prefix of the user input
let cheatCode = Object.keys(g_Cheats).find(code => text.indexOf(code) == 0);
if (!cheatCode)
return false;
let cheat = g_Cheats[cheatCode];
let parameter = text.substr(cheatCode.length + 1);
if (cheat.isNumeric)
parameter = +parameter;
if (cheat.DefaultParameter && !parameter)
parameter = cheat.DefaultParameter;
Engine.PostNetworkCommand({
"type": "cheat",
"action": cheat.Action,
"text": cheat.Type,
"player": Engine.GetPlayerID(),
"parameter": parameter,
"templates": cheat.Templates,
"selected": g_Selection.toList()
});
return true;
}
function findGuidForPlayerID(playerID)
{
return Object.keys(g_PlayerAssignments).find(guid => g_PlayerAssignments[guid].player == playerID);
}
/**
* Processes all pending notifications sent from the GUIInterface simulation component.
*/
function handleNotifications()
{
for (let notification of Engine.GuiInterfaceCall("GetNotifications"))
{
if (!notification.players || !notification.type || !g_NotificationsTypes[notification.type])
{
error("Invalid GUI notification: " + uneval(notification));
continue;
}
for (let player of notification.players)
g_NotificationsTypes[notification.type](notification, player);
}
}
function toggleTutorial()
{
let tutorialPanel = Engine.GetGUIObjectByName("tutorialPanel");
tutorialPanel.hidden = !tutorialPanel.hidden || !Engine.GetGUIObjectByName("tutorialText").caption;
}
/**
* Updates the tutorial panel when a new goal.
*/
function updateTutorial(notification)
{
// Show the tutorial panel if not yet done
Engine.GetGUIObjectByName("tutorialPanel").hidden = false;
if (notification.warning)
{
Engine.GetGUIObjectByName("tutorialWarning").caption = coloredText(translate(notification.warning), "orange");
return;
}
let notificationText =
notification.instructions.reduce((instructions, item) =>
instructions + (typeof item == "string" ? translate(item) : colorizeHotkey(translate(item.text), item.hotkey)),
"");
Engine.GetGUIObjectByName("tutorialText").caption = g_TutorialMessages.concat(setStringTags(notificationText, g_TutorialNewMessageTags)).join("\n");
g_TutorialMessages.push(notificationText);
if (notification.readyButton)
{
Engine.GetGUIObjectByName("tutorialReady").hidden = false;
if (notification.leave)
{
Engine.GetGUIObjectByName("tutorialWarning").caption = translate("Click to quit this tutorial.");
Engine.GetGUIObjectByName("tutorialReady").caption = translate("Quit");
Engine.GetGUIObjectByName("tutorialReady").onPress = endGame;
}
else
Engine.GetGUIObjectByName("tutorialWarning").caption = translate("Click when ready.");
}
else
{
Engine.GetGUIObjectByName("tutorialWarning").caption = translate("Follow the instructions.");
Engine.GetGUIObjectByName("tutorialReady").hidden = true;
}
}
/**
* Displays all active counters (messages showing the remaining time) for wonder-victory, ceasefire etc.
*/
function updateTimeNotifications()
{
let notifications = Engine.GuiInterfaceCall("GetTimeNotifications", g_ViewedPlayer);
let notificationText = "";
for (let n of notifications)
{
let message = n.message;
if (n.translateMessage)
message = translate(message);
let parameters = n.parameters || {};
if (n.translateParameters)
translateObjectKeys(parameters, n.translateParameters);
parameters.time = timeToString(n.endTime - GetSimState().timeElapsed);
colorizePlayernameParameters(parameters);
notificationText += sprintf(message, parameters) + "\n";
}
Engine.GetGUIObjectByName("notificationText").caption = notificationText;
}
/**
* Process every CNetMessage (see NetMessage.h, NetMessages.h) sent by the CNetServer.
* Saves the received object to mainlog.html.
*/
function handleNetMessages()
{
while (true)
{
let msg = Engine.PollNetworkClient();
if (!msg)
return;
log("Net message: " + uneval(msg));
if (g_NetMessageTypes[msg.type])
g_NetMessageTypes[msg.type](msg);
else
error("Unrecognised net message type '" + msg.type + "'");
}
}
-/**
- * @param {Object} message
- */
function handleNetStatusMessage(message)
{
if (g_Disconnected)
return;
- if (!g_StatusMessageTypes[message.status])
- {
- error("Unrecognised netstatus type '" + message.status + "'");
- return;
- }
-
g_IsNetworkedActive = message.status == "active";
- let netStatus = Engine.GetGUIObjectByName("netStatus");
- let statusMessage = g_StatusMessageTypes[message.status](message);
- netStatus.caption = statusMessage;
- netStatus.hidden = !statusMessage;
-
- let loadingClientsText = Engine.GetGUIObjectByName("loadingClientsText");
- loadingClientsText.hidden = message.status != "waiting_for_players";
-
if (message.status == "disconnected")
{
- // Hide the pause overlay, and pause animations.
- Engine.GetGUIObjectByName("pauseOverlay").hidden = true;
- Engine.SetPaused(true, false);
-
g_Disconnected = true;
updateCinemaPath();
closeOpenDialogs();
}
-}
-function handleClientsLoadingMessage(guids)
-{
- let loadingClientsText = Engine.GetGUIObjectByName("loadingClientsText");
- loadingClientsText.caption = guids.map(guid => colorizePlayernameByGUID(guid)).join(translateWithContext("Separator for a list of client loading messages", ", "));
+ for (let handler of g_NetworkStatusChangeHandlers)
+ handler(message);
}
function handlePlayerAssignmentsMessage(message)
{
for (let guid in g_PlayerAssignments)
if (!message.newAssignments[guid])
onClientLeave(guid);
let joins = Object.keys(message.newAssignments).filter(guid => !g_PlayerAssignments[guid]);
g_PlayerAssignments = message.newAssignments;
joins.forEach(guid => {
onClientJoin(guid);
});
for (let handler of g_PlayerAssignmentsChangeHandlers)
handler();
// TODO: use subscription instead
updateGUIObjects();
}
function onClientJoin(guid)
{
let playerID = g_PlayerAssignments[guid].player;
if (g_Players[playerID])
{
g_Players[playerID].guid = guid;
g_Players[playerID].name = g_PlayerAssignments[guid].name;
g_Players[playerID].offline = false;
}
addChatMessage({
"type": "connect",
"guid": guid
});
}
function onClientLeave(guid)
{
g_PauseControl.setClientPauseState(guid, false);
for (let id in g_Players)
if (g_Players[id].guid == guid)
g_Players[id].offline = true;
addChatMessage({
"type": "disconnect",
"guid": guid
});
}
function addChatMessage(msg)
{
g_Chat.ChatMessageHandler.handleMessage(msg);
}
function clearChatMessages()
{
g_Chat.ChatOverlay.clearChatMessages();
}
/**
* This function is used for AIs, whose names don't exist in g_PlayerAssignments.
*/
function colorizePlayernameByID(playerID)
{
let username = g_Players[playerID] && escapeText(g_Players[playerID].name);
return colorizePlayernameHelper(username, playerID);
}
function colorizePlayernameByGUID(guid)
{
let username = g_PlayerAssignments[guid] ? g_PlayerAssignments[guid].name : "";
let playerID = g_PlayerAssignments[guid] ? g_PlayerAssignments[guid].player : -1;
return colorizePlayernameHelper(username, playerID);
}
function colorizePlayernameHelper(username, playerID)
{
let playerColor = playerID > -1 ? g_DiplomacyColors.getPlayerColor(playerID) : "white";
return coloredText(username || translate("Unknown Player"), playerColor);
}
/**
* Insert the colorized playername to chat messages sent by the AI and time notifications.
*/
function colorizePlayernameParameters(parameters)
{
for (let param in parameters)
if (param.startsWith("_player_"))
parameters[param] = colorizePlayernameByID(parameters[param]);
}
/**
* Custom dialog response handling, usable by trigger maps.
*/
function sendDialogAnswer(guiObject, dialogName)
{
Engine.GetGUIObjectByName(dialogName + "-dialog").hidden = true;
Engine.PostNetworkCommand({
"type": "dialog-answer",
"dialog": dialogName,
"answer": guiObject.name.split("-").pop(),
});
resumeGame();
}
/**
* Custom dialog opening, usable by trigger maps.
*/
function openDialog(dialogName, data, player)
{
let dialog = Engine.GetGUIObjectByName(dialogName + "-dialog");
if (!dialog)
{
warn("messages.js: Unknow dialog with name " + dialogName);
return;
}
dialog.hidden = false;
for (let objName in data)
{
let obj = Engine.GetGUIObjectByName(dialogName + "-dialog-" + objName);
if (!obj)
{
warn("messages.js: Key '" + objName + "' not found in '" + dialogName + "' dialog.");
continue;
}
for (let key in data[objName])
{
let n = data[objName][key];
if (typeof n == "object" && n.message)
{
let message = n.message;
if (n.translateMessage)
message = translate(message);
let parameters = n.parameters || {};
if (n.translateParameters)
translateObjectKeys(parameters, n.translateParameters);
obj[key] = sprintf(message, parameters);
}
else
obj[key] = n;
}
}
g_PauseControl.implicitPause();
}
Index: ps/trunk/binaries/data/mods/public/gui/session/session.js
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/session/session.js (revision 23114)
+++ ps/trunk/binaries/data/mods/public/gui/session/session.js (revision 23115)
@@ -1,804 +1,806 @@
const g_IsReplay = Engine.IsVisualReplay();
const g_CivData = loadCivData(false, true);
const g_Ceasefire = prepareForDropdown(g_Settings && g_Settings.Ceasefire);
const g_MapSizes = prepareForDropdown(g_Settings && g_Settings.MapSizes);
const g_MapTypes = prepareForDropdown(g_Settings && g_Settings.MapTypes);
const g_PopulationCapacities = prepareForDropdown(g_Settings && g_Settings.PopulationCapacities);
const g_StartingResources = prepareForDropdown(g_Settings && g_Settings.StartingResources);
const g_VictoryDurations = prepareForDropdown(g_Settings && g_Settings.VictoryDurations);
const g_VictoryConditions = g_Settings && g_Settings.VictoryConditions;
var g_Chat;
var g_DeveloperOverlay;
var g_DiplomacyColors;
var g_DiplomacyDialog;
var g_GameSpeedControl;
var g_Menu;
var g_MiniMapPanel;
+var g_NetworkStatusOverlay;
var g_ObjectivesDialog;
var g_OutOfSyncNetwork;
var g_OutOfSyncReplay;
var g_PanelEntityManager;
var g_PauseControl;
var g_PauseOverlay;
var g_PlayerViewControl;
var g_QuitConfirmationDefeat;
var g_QuitConfirmationReplay;
var g_RangeOverlayManager;
var g_ResearchProgress;
var g_TradeDialog;
var g_TopPanel;
/**
* A random file will be played. TODO: more variety
*/
var g_Ambient = ["audio/ambient/dayscape/day_temperate_gen_03.ogg"];
/**
* Map, player and match settings set in gamesetup.
*/
const g_GameAttributes = deepfreeze(Engine.GuiInterfaceCall("GetInitAttributes"));
/**
* True if this is a multiplayer game.
*/
const g_IsNetworked = Engine.HasNetClient();
/**
* Is this user in control of game settings (i.e. is a network server, or offline player).
*/
var g_IsController = !g_IsNetworked || Engine.HasNetServer();
/**
* Whether we have finished the synchronization and
* can start showing simulation related message boxes.
*/
var g_IsNetworkedActive = false;
/**
* True if the connection to the server has been lost.
*/
var g_Disconnected = false;
/**
* True if the current user has observer capabilities.
*/
var g_IsObserver = false;
/**
* True if the current user has rejoined (or joined the game after it started).
*/
var g_HasRejoined = false;
/**
* The playerID selected in the change perspective tool.
*/
var g_ViewedPlayer = Engine.GetPlayerID();
/**
* True if the camera should focus on attacks and player commands
* and select the affected units.
*/
var g_FollowPlayer = false;
/**
* Cache the basic player data (name, civ, color).
*/
var g_Players = [];
/**
* Last time when onTick was called().
* Used for animating the main menu.
*/
var g_LastTickTime = Date.now();
/**
* Recalculate which units have their status bars shown with this frequency in milliseconds.
*/
var g_StatusBarUpdate = 200;
/**
* For restoring selection, order and filters when returning to the replay menu
*/
var g_ReplaySelectionData;
/**
* Remembers which clients are assigned to which player slots.
* The keys are guids or "local" in Singleplayer.
*/
var g_PlayerAssignments;
/**
* Whether the entire UI should be hidden (useful for promotional screenshots).
* Can be toggled with a hotkey.
*/
var g_ShowGUI = true;
/**
* Whether status bars should be shown for all of the player's units.
*/
var g_ShowAllStatusBars = false;
/**
* Cache of simulation state and template data (apart from TechnologyData, updated on every simulation update).
*/
var g_SimState;
var g_EntityStates = {};
var g_TemplateData = {};
var g_TechnologyData = {};
var g_ResourceData = new Resources();
/**
* These handlers are called each time a new turn was simulated.
* Use this as sparely as possible.
*/
var g_SimulationUpdateHandlers = new Set();
/**
* These handlers are called after the player states have been initialized.
*/
var g_PlayersInitHandlers = new Set();
/**
* These handlers are called when a player has been defeated or won the game.
*/
var g_PlayerFinishedHandlers = new Set();
/**
* These events are fired whenever the player added or removed entities from the selection.
*/
var g_EntitySelectionChangeHandlers = new Set();
/**
* These events are fired when the user has performed a hotkey assignment change.
* Currently only fired on init, but to be fired from any hotkey editor dialog.
*/
var g_HotkeyChangeHandlers = new Set();
/**
* List of additional entities to highlight.
*/
var g_ShowGuarding = false;
var g_ShowGuarded = false;
var g_AdditionalHighlight = [];
/**
* Order in which the panel entities are shown.
*/
var g_PanelEntityOrder = ["Hero", "Relic"];
/**
* Unit classes to be checked for the idle-worker-hotkey.
*/
var g_WorkerTypes = ["FemaleCitizen", "Trader", "FishingBoat", "Citizen"];
/**
* Unit classes to be checked for the military-only-selection modifier and for the idle-warrior-hotkey.
*/
var g_MilitaryTypes = ["Melee", "Ranged"];
function GetSimState()
{
if (!g_SimState)
g_SimState = deepfreeze(Engine.GuiInterfaceCall("GetSimulationState"));
return g_SimState;
}
function GetMultipleEntityStates(ents)
{
if (!ents.length)
return null;
let entityStates = Engine.GuiInterfaceCall("GetMultipleEntityStates", ents);
for (let item of entityStates)
g_EntityStates[item.entId] = item.state && deepfreeze(item.state);
return entityStates;
}
function GetEntityState(entId)
{
if (!g_EntityStates[entId])
{
let entityState = Engine.GuiInterfaceCall("GetEntityState", entId);
g_EntityStates[entId] = entityState && deepfreeze(entityState);
}
return g_EntityStates[entId];
}
function GetTemplateData(templateName)
{
if (!(templateName in g_TemplateData))
{
let template = Engine.GuiInterfaceCall("GetTemplateData", templateName);
translateObjectKeys(template, ["specific", "generic", "tooltip"]);
g_TemplateData[templateName] = deepfreeze(template);
}
return g_TemplateData[templateName];
}
function GetTechnologyData(technologyName, civ)
{
if (!g_TechnologyData[civ])
g_TechnologyData[civ] = {};
if (!(technologyName in g_TechnologyData[civ]))
{
let template = GetTechnologyDataHelper(TechnologyTemplates.Get(technologyName), civ, g_ResourceData);
translateObjectKeys(template, ["specific", "generic", "description", "tooltip", "requirementsTooltip"]);
g_TechnologyData[civ][technologyName] = deepfreeze(template);
}
return g_TechnologyData[civ][technologyName];
}
function init(initData, hotloadData)
{
if (!g_Settings)
{
Engine.EndGame();
Engine.SwitchGuiPage("page_pregame.xml");
return;
}
// Fallback used by atlas
g_PlayerAssignments = initData ? initData.playerAssignments : { "local": { "player": 1 } };
// Fallback used by atlas and autostart games
if (g_PlayerAssignments.local && !g_PlayerAssignments.local.name)
g_PlayerAssignments.local.name = singleplayerName();
if (initData)
{
g_ReplaySelectionData = initData.replaySelectionData;
g_HasRejoined = initData.isRejoining;
if (initData.savedGUIData)
restoreSavedGameData(initData.savedGUIData);
}
g_DiplomacyColors = new DiplomacyColors();
g_PlayerViewControl = new PlayerViewControl();
g_PlayerViewControl.registerViewedPlayerChangeHandler(g_DiplomacyColors.updateDisplayedPlayerColors.bind(g_DiplomacyColors));
g_DiplomacyColors.registerDiplomacyColorsChangeHandler(g_PlayerViewControl.rebuild.bind(g_PlayerViewControl));
g_DiplomacyColors.registerDiplomacyColorsChangeHandler(updateGUIObjects);
g_PauseControl = new PauseControl();
g_PlayerViewControl.registerPreViewedPlayerChangeHandler(removeStatusBarDisplay);
g_PlayerViewControl.registerViewedPlayerChangeHandler(resetTemplates);
g_Chat = new Chat(g_PlayerViewControl);
g_DeveloperOverlay = new DeveloperOverlay(g_PlayerViewControl, g_Selection);
g_DiplomacyDialog = new DiplomacyDialog(g_PlayerViewControl, g_DiplomacyColors);
g_GameSpeedControl = new GameSpeedControl(g_PlayerViewControl);
g_Menu = new Menu(g_PauseControl, g_PlayerViewControl, g_Chat);
g_MiniMapPanel = new MiniMapPanel(g_PlayerViewControl, g_DiplomacyColors, g_WorkerTypes);
+ g_NetworkStatusOverlay = new NetworkStatusOverlay();
g_ObjectivesDialog = new ObjectivesDialog(g_PlayerViewControl);
g_OutOfSyncNetwork = new OutOfSyncNetwork();
g_OutOfSyncReplay = new OutOfSyncReplay();
g_PanelEntityManager = new PanelEntityManager(g_PlayerViewControl, g_Selection, g_PanelEntityOrder);
g_PauseOverlay = new PauseOverlay(g_PauseControl);
g_QuitConfirmationDefeat = new QuitConfirmationDefeat();
g_QuitConfirmationReplay = new QuitConfirmationReplay();
g_RangeOverlayManager = new RangeOverlayManager(g_Selection);
g_ResearchProgress = new ResearchProgress(g_PlayerViewControl, g_Selection);
g_TradeDialog = new TradeDialog(g_PlayerViewControl);
g_TopPanel = new TopPanel(g_PlayerViewControl, g_DiplomacyDialog, g_TradeDialog, g_ObjectivesDialog, g_GameSpeedControl);
initBatchTrain();
initSelectionPanels();
LoadModificationTemplates();
updatePlayerData();
initializeMusic(); // before changing the perspective
Engine.SetBoundingBoxDebugOverlay(false);
for (let handler of g_PlayersInitHandlers)
handler();
for (let handler of g_HotkeyChangeHandlers)
handler();
if (hotloadData)
{
g_Selection.selected = hotloadData.selection;
g_PlayerAssignments = hotloadData.playerAssignments;
g_Players = hotloadData.player;
}
// TODO: use event instead
onSimulationUpdate();
setTimeout(displayGamestateNotifications, 1000);
}
function registerPlayersInitHandler(handler)
{
g_PlayersInitHandlers.add(handler);
}
function registerPlayersFinishedHandler(handler)
{
g_PlayerFinishedHandlers.add(handler);
}
function registerSimulationUpdateHandler(handler)
{
g_SimulationUpdateHandlers.add(handler);
}
function unregisterSimulationUpdateHandler(handler)
{
g_SimulationUpdateHandlers.delete(handler);
}
function registerEntitySelectionChangeHandler(handler)
{
g_EntitySelectionChangeHandlers.add(handler);
}
function unregisterEntitySelectionChangeHandler(handler)
{
g_EntitySelectionChangeHandlers.delete(handler);
}
function registerHotkeyChangeHandler(handler)
{
g_HotkeyChangeHandlers.add(handler);
}
function updatePlayerData()
{
let simState = GetSimState();
if (!simState)
return;
let playerData = [];
for (let i = 0; i < simState.players.length; ++i)
{
let playerState = simState.players[i];
playerData.push({
"name": playerState.name,
"civ": playerState.civ,
"color": {
"r": playerState.color.r * 255,
"g": playerState.color.g * 255,
"b": playerState.color.b * 255,
"a": playerState.color.a * 255
},
"team": playerState.team,
"teamsLocked": playerState.teamsLocked,
"cheatsEnabled": playerState.cheatsEnabled,
"state": playerState.state,
"isAlly": playerState.isAlly,
"isMutualAlly": playerState.isMutualAlly,
"isNeutral": playerState.isNeutral,
"isEnemy": playerState.isEnemy,
"guid": undefined, // network guid for players controlled by hosts
"offline": g_Players[i] && !!g_Players[i].offline
});
}
for (let guid in g_PlayerAssignments)
{
let playerID = g_PlayerAssignments[guid].player;
if (!playerData[playerID])
continue;
playerData[playerID].guid = guid;
playerData[playerID].name = g_PlayerAssignments[guid].name;
}
g_Players = playerData;
}
/**
* Returns the entity itself except when garrisoned where it returns its garrisonHolder
*/
function getEntityOrHolder(ent)
{
let entState = GetEntityState(ent);
if (entState && !entState.position && entState.unitAI && entState.unitAI.orders.length &&
entState.unitAI.orders[0].type == "Garrison")
return getEntityOrHolder(entState.unitAI.orders[0].data.target);
return ent;
}
function initializeMusic()
{
initMusic();
if (g_ViewedPlayer != -1 && g_CivData[g_Players[g_ViewedPlayer].civ].Music)
global.music.storeTracks(g_CivData[g_Players[g_ViewedPlayer].civ].Music);
global.music.setState(global.music.states.PEACE);
playAmbient();
}
function resetTemplates()
{
// Update GUI and clear player-dependent cache
g_TemplateData = {};
Engine.GuiInterfaceCall("ResetTemplateModified");
// TODO: do this more selectively
onSimulationUpdate();
}
/**
* Returns true if the player with that ID is in observermode.
*/
function isPlayerObserver(playerID)
{
let playerStates = GetSimState().players;
return !playerStates[playerID] || playerStates[playerID].state != "active";
}
/**
* Returns true if the current user can issue commands for that player.
*/
function controlsPlayer(playerID)
{
let playerStates = GetSimState().players;
return !!playerStates[Engine.GetPlayerID()] &&
playerStates[Engine.GetPlayerID()].controlsAll ||
Engine.GetPlayerID() == playerID &&
!!playerStates[playerID] &&
playerStates[playerID].state != "defeated";
}
/**
* Called when one or more players have won or were defeated.
*
* @param {array} - IDs of the players who have won or were defeated.
* @param {object} - a plural string stating the victory reason.
* @param {boolean} - whether these players have won or lost.
*/
function playersFinished(players, victoryString, won)
{
addChatMessage({
"type": "playerstate",
"message": victoryString,
"players": players
});
updatePlayerData();
// TODO: The other calls in this function should move too
for (let handler of g_PlayerFinishedHandlers)
handler(players, won);
if (players.indexOf(Engine.GetPlayerID()) == -1 || Engine.IsAtlasRunning())
return;
global.music.setState(
won ?
global.music.states.VICTORY :
global.music.states.DEFEAT
);
}
function resumeGame()
{
g_PauseControl.implicitResume();
}
function closeOpenDialogs()
{
g_Menu.close();
g_Chat.closePage();
g_DiplomacyDialog.close();
g_ObjectivesDialog.close();
g_TradeDialog.close();
}
function endGame()
{
// Before ending the game
let replayDirectory = Engine.GetCurrentReplayDirectory();
let simData = Engine.GuiInterfaceCall("GetReplayMetadata");
let playerID = Engine.GetPlayerID();
Engine.EndGame();
// After the replay file was closed in EndGame
// Done here to keep EndGame small
if (!g_IsReplay)
Engine.AddReplayToCache(replayDirectory);
if (g_IsController && Engine.HasXmppClient())
Engine.SendUnregisterGame();
Engine.SwitchGuiPage("page_summary.xml", {
"sim": simData,
"gui": {
"dialog": false,
"assignedPlayer": playerID,
"disconnected": g_Disconnected,
"isReplay": g_IsReplay,
"replayDirectory": !g_HasRejoined && replayDirectory,
"replaySelectionData": g_ReplaySelectionData
}
});
}
// Return some data that we'll use when hotloading this file after changes
function getHotloadData()
{
return {
"selection": g_Selection.selected,
"playerAssignments": g_PlayerAssignments,
"player": g_Players,
};
}
function getSavedGameData()
{
return {
"groups": g_Groups.groups
};
}
function restoreSavedGameData(data)
{
// Restore camera if any
if (data.camera)
Engine.SetCameraData(data.camera.PosX, data.camera.PosY, data.camera.PosZ,
data.camera.RotX, data.camera.RotY, data.camera.Zoom);
// Clear selection when loading a game
g_Selection.reset();
// Restore control groups
for (let groupNumber in data.groups)
{
g_Groups.groups[groupNumber].groups = data.groups[groupNumber].groups;
g_Groups.groups[groupNumber].ents = data.groups[groupNumber].ents;
}
updateGroups();
}
/**
* Called every frame.
*/
function onTick()
{
if (!g_Settings)
return;
let now = Date.now();
let tickLength = now - g_LastTickTime;
g_LastTickTime = now;
handleNetMessages();
updateCursorAndTooltip();
if (g_Selection.dirty)
{
g_Selection.dirty = false;
// When selection changed, get the entityStates of new entities
GetMultipleEntityStates(g_Selection.toList().filter(entId => !g_EntityStates[entId]));
for (let handler of g_EntitySelectionChangeHandlers)
handler();
updateGUIObjects();
// Display rally points for selected buildings
if (Engine.GetPlayerID() != -1)
Engine.GuiInterfaceCall("DisplayRallyPoint", { "entities": g_Selection.toList() });
}
else if (g_ShowAllStatusBars && now % g_StatusBarUpdate <= tickLength)
recalculateStatusBarDisplay();
updateTimers();
Engine.GuiInterfaceCall("ClearRenamedEntities");
}
function onSimulationUpdate()
{
// Templates change depending on technologies and auras, so they have to be reloaded after such a change.
// g_TechnologyData data never changes, so it shouldn't be deleted.
g_EntityStates = {};
if (Engine.GuiInterfaceCall("IsTemplateModified"))
{
g_TemplateData = {};
Engine.GuiInterfaceCall("ResetTemplateModified");
}
g_SimState = undefined;
if (!GetSimState())
return;
GetMultipleEntityStates(g_Selection.toList());
for (let handler of g_SimulationUpdateHandlers)
handler();
// TODO: Move to handlers
updateCinemaPath();
handleNotifications();
updateGUIObjects();
}
function toggleGUI()
{
g_ShowGUI = !g_ShowGUI;
updateCinemaPath();
}
function updateCinemaPath()
{
let isPlayingCinemaPath = GetSimState().cinemaPlaying && !g_Disconnected;
Engine.GetGUIObjectByName("session").hidden = !g_ShowGUI || isPlayingCinemaPath;
Engine.Renderer_SetSilhouettesEnabled(!isPlayingCinemaPath && Engine.ConfigDB_GetValue("user", "silhouettes") == "true");
}
// TODO: Use event subscription onSimulationUpdate, onEntitySelectionChange, onPlayerViewChange, ... instead
function updateGUIObjects()
{
g_Selection.update();
if (g_ShowAllStatusBars)
recalculateStatusBarDisplay();
if (g_ShowGuarding || g_ShowGuarded)
updateAdditionalHighlight();
updateGroups();
updateSelectionDetails();
updateBuildingPlacementPreview();
updateTimeNotifications();
if (!g_IsObserver)
{
// Update music state on basis of battle state.
let battleState = Engine.GuiInterfaceCall("GetBattleState", g_ViewedPlayer);
if (battleState)
global.music.setState(global.music.states[battleState]);
}
}
function updateGroups()
{
g_Groups.update();
// Determine the sum of the costs of a given template
let getCostSum = (ent) => {
let cost = GetTemplateData(GetEntityState(ent).template).cost;
return cost ? Object.keys(cost).map(key => cost[key]).reduce((sum, cur) => sum + cur) : 0;
};
for (let i in Engine.GetGUIObjectByName("unitGroupPanel").children)
{
Engine.GetGUIObjectByName("unitGroupLabel[" + i + "]").caption = i;
let button = Engine.GetGUIObjectByName("unitGroupButton[" + i + "]");
button.hidden = g_Groups.groups[i].getTotalCount() == 0;
button.onpress = (function(i) { return function() { performGroup((Engine.HotkeyIsPressed("selection.add") ? "add" : "select"), i); }; })(i);
button.ondoublepress = (function(i) { return function() { performGroup("snap", i); }; })(i);
button.onpressright = (function(i) { return function() { performGroup("breakUp", i); }; })(i);
// Choose the icon of the most common template (or the most costly if it's not unique)
if (g_Groups.groups[i].getTotalCount() > 0)
{
let icon = GetTemplateData(GetEntityState(g_Groups.groups[i].getEntsGrouped().reduce((pre, cur) => {
if (pre.ents.length == cur.ents.length)
return getCostSum(pre.ents[0]) > getCostSum(cur.ents[0]) ? pre : cur;
return pre.ents.length > cur.ents.length ? pre : cur;
}).ents[0]).template).icon;
Engine.GetGUIObjectByName("unitGroupIcon[" + i + "]").sprite =
icon ? ("stretched:session/portraits/" + icon) : "groupsIcon";
}
setPanelObjectPosition(button, i, 1);
}
}
/**
* Toggles the display of status bars for all of the player's entities.
*
* @param {Boolean} remove - Whether to hide all previously shown status bars.
*/
function recalculateStatusBarDisplay(remove = false)
{
let entities;
if (g_ShowAllStatusBars && !remove)
entities = g_ViewedPlayer == -1 ?
Engine.PickNonGaiaEntitiesOnScreen() :
Engine.PickPlayerEntitiesOnScreen(g_ViewedPlayer);
else
{
let selected = g_Selection.toList();
for (let ent in g_Selection.highlighted)
selected.push(g_Selection.highlighted[ent]);
// Remove selected entities from the 'all entities' array,
// to avoid disabling their status bars.
entities = Engine.GuiInterfaceCall(
g_ViewedPlayer == -1 ? "GetNonGaiaEntities" : "GetPlayerEntities", {
"viewedPlayer": g_ViewedPlayer
}).filter(idx => selected.indexOf(idx) == -1);
}
Engine.GuiInterfaceCall("SetStatusBars", {
"entities": entities,
"enabled": g_ShowAllStatusBars && !remove,
"showRank": Engine.ConfigDB_GetValue("user", "gui.session.rankabovestatusbar") == "true",
"showExperience": Engine.ConfigDB_GetValue("user", "gui.session.experiencestatusbar") == "true"
});
}
function removeStatusBarDisplay()
{
if (g_ShowAllStatusBars)
recalculateStatusBarDisplay(true);
}
/**
* Inverts the given configuration boolean and returns the current state.
* For example "silhouettes".
*/
function toggleConfigBool(configName)
{
let enabled = Engine.ConfigDB_GetValue("user", configName) != "true";
Engine.ConfigDB_CreateAndWriteValueToFile("user", configName, String(enabled), "config/user.cfg");
return enabled;
}
// Update the additional list of entities to be highlighted.
function updateAdditionalHighlight()
{
let entsAdd = []; // list of entities units to be highlighted
let entsRemove = [];
let highlighted = g_Selection.toList();
for (let ent in g_Selection.highlighted)
highlighted.push(g_Selection.highlighted[ent]);
if (g_ShowGuarding)
// flag the guarding entities to add in this additional highlight
for (let sel in g_Selection.selected)
{
let state = GetEntityState(g_Selection.selected[sel]);
if (!state.guard || !state.guard.entities.length)
continue;
for (let ent of state.guard.entities)
if (highlighted.indexOf(ent) == -1 && entsAdd.indexOf(ent) == -1)
entsAdd.push(ent);
}
if (g_ShowGuarded)
// flag the guarded entities to add in this additional highlight
for (let sel in g_Selection.selected)
{
let state = GetEntityState(g_Selection.selected[sel]);
if (!state.unitAI || !state.unitAI.isGuarding)
continue;
let ent = state.unitAI.isGuarding;
if (highlighted.indexOf(ent) == -1 && entsAdd.indexOf(ent) == -1)
entsAdd.push(ent);
}
// flag the entities to remove (from the previously added) from this additional highlight
for (let ent of g_AdditionalHighlight)
if (highlighted.indexOf(ent) == -1 && entsAdd.indexOf(ent) == -1 && entsRemove.indexOf(ent) == -1)
entsRemove.push(ent);
_setHighlight(entsAdd, g_HighlightedAlpha, true);
_setHighlight(entsRemove, 0, false);
g_AdditionalHighlight = entsAdd;
}
function playAmbient()
{
Engine.PlayAmbientSound(pickRandom(g_Ambient), true);
}
Index: ps/trunk/binaries/data/mods/public/gui/session/session.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/session/session.xml (revision 23114)
+++ ps/trunk/binaries/data/mods/public/gui/session/session.xml (revision 23115)
@@ -1,128 +1,115 @@
onTick();
restoreSavedGameData(arguments[0]);
onSimulationUpdate();
-
-
-
- Exit
- endGame();
-
-
-
-
+