Index: ps/trunk/binaries/data/mods/public/gui/session/top_panel.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/session/top_panel.xml (revision 23075)
+++ ps/trunk/binaries/data/mods/public/gui/session/top_panel.xml (nonexistent)
@@ -1,57 +0,0 @@
-
-
Property changes on: ps/trunk/binaries/data/mods/public/gui/session/top_panel.xml
___________________________________________________________________
Deleted: svn:eol-style
## -1 +0,0 ##
-native
\ No newline at end of property
Deleted: svn:mime-type
## -1 +0,0 ##
-text/xml
\ No newline at end of property
Index: ps/trunk/binaries/data/mods/public/gui/session/objectives_window.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/session/objectives_window.xml (revision 23075)
+++ ps/trunk/binaries/data/mods/public/gui/session/objectives_window.xml (nonexistent)
@@ -1,48 +0,0 @@
-
-
-
- Objectives
-
-
-
-
-
-
-
-
-
- Close
- closeObjectives();
-
-
-
-
Property changes on: ps/trunk/binaries/data/mods/public/gui/session/objectives_window.xml
___________________________________________________________________
Deleted: svn:eol-style
## -1 +0,0 ##
-native
\ No newline at end of property
Index: ps/trunk/binaries/data/mods/public/gui/session/top_panel/label.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/session/top_panel/label.xml (revision 23075)
+++ ps/trunk/binaries/data/mods/public/gui/session/top_panel/label.xml (nonexistent)
@@ -1,19 +0,0 @@
-
-
-
- ALPHA XXIV
-
-
-
- this.caption = getBuildString()
-
-
Property changes on: ps/trunk/binaries/data/mods/public/gui/session/top_panel/label.xml
___________________________________________________________________
Deleted: svn:eol-style
## -1 +0,0 ##
-native
\ No newline at end of property
Deleted: svn:mime-type
## -1 +0,0 ##
-text/xml
\ No newline at end of property
Index: ps/trunk/binaries/data/mods/public/gui/session/top_panel/button_trade.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/session/top_panel/button_trade.xml (revision 23075)
+++ ps/trunk/binaries/data/mods/public/gui/session/top_panel/button_trade.xml (nonexistent)
@@ -1,10 +0,0 @@
-
-
-
-
Property changes on: ps/trunk/binaries/data/mods/public/gui/session/top_panel/button_trade.xml
___________________________________________________________________
Deleted: svn:eol-style
## -1 +0,0 ##
-native
\ No newline at end of property
Deleted: svn:mime-type
## -1 +0,0 ##
-text/xml
\ No newline at end of property
Index: ps/trunk/binaries/data/mods/public/gui/session/top_panel/civ_icon.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/session/top_panel/civ_icon.xml (revision 23075)
+++ ps/trunk/binaries/data/mods/public/gui/session/top_panel/civ_icon.xml (nonexistent)
@@ -1,11 +0,0 @@
-
-
-
-
- openStrucTree(g_CivInfo.page)
-
-
Property changes on: ps/trunk/binaries/data/mods/public/gui/session/top_panel/civ_icon.xml
___________________________________________________________________
Deleted: svn:eol-style
## -1 +0,0 ##
-native
\ No newline at end of property
Deleted: svn:mime-type
## -1 +0,0 ##
-text/xml
\ No newline at end of property
Index: ps/trunk/binaries/data/mods/public/gui/session/top_panel/DiplomacyButton.js
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/session/top_panel/DiplomacyButton.js (revision 23075)
+++ ps/trunk/binaries/data/mods/public/gui/session/top_panel/DiplomacyButton.js (nonexistent)
@@ -1,22 +0,0 @@
-/**
- * This class handles the button which opens the diplomacy dialog.
- */
-class DiplomacyButton
-{
- constructor(diplomacyDialog)
- {
- this.diplomacyButton = Engine.GetGUIObjectByName("diplomacyButton");
- this.diplomacyButton.enabled = !Engine.IsAtlasRunning();
- this.diplomacyButton.onPress = diplomacyDialog.toggle.bind(diplomacyDialog);
- }
-
- update()
- {
- this.diplomacyButton.hidden = g_ViewedPlayer < 1;
- this.diplomacyButton.tooltip =
- colorizeHotkey("%(hotkey)s" + " ", "session.gui.diplomacy.toggle") +
- translate(this.Tooltip);
- }
-}
-
-DiplomacyButton.prototype.Tooltip = markForTranslation("Diplomacy");
Property changes on: ps/trunk/binaries/data/mods/public/gui/session/top_panel/DiplomacyButton.js
___________________________________________________________________
Deleted: svn:eol-style
## -1 +0,0 ##
-native
\ No newline at end of property
Index: ps/trunk/binaries/data/mods/public/gui/session/top_panel/button_objectives.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/session/top_panel/button_objectives.xml (revision 23075)
+++ ps/trunk/binaries/data/mods/public/gui/session/top_panel/button_objectives.xml (nonexistent)
@@ -1,19 +0,0 @@
-
-
-
-
- toggleObjectives();
-
-
Property changes on: ps/trunk/binaries/data/mods/public/gui/session/top_panel/button_objectives.xml
___________________________________________________________________
Deleted: svn:eol-style
## -1 +0,0 ##
-native
\ No newline at end of property
Index: ps/trunk/binaries/data/mods/public/gui/session/top_panel/button_menu.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/session/top_panel/button_menu.xml (revision 23075)
+++ ps/trunk/binaries/data/mods/public/gui/session/top_panel/button_menu.xml (nonexistent)
@@ -1,19 +0,0 @@
-
-
-
-
-
- Menu
-
-
- toggleMenu();
-
-
Property changes on: ps/trunk/binaries/data/mods/public/gui/session/top_panel/button_menu.xml
___________________________________________________________________
Deleted: svn:eol-style
## -1 +0,0 ##
-native
\ No newline at end of property
Deleted: svn:mime-type
## -1 +0,0 ##
-text/xml
\ No newline at end of property
Index: ps/trunk/binaries/data/mods/public/gui/session/top_panel/button_diplomacy.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/session/top_panel/button_diplomacy.xml (revision 23075)
+++ ps/trunk/binaries/data/mods/public/gui/session/top_panel/button_diplomacy.xml (nonexistent)
@@ -1,11 +0,0 @@
-
-
-
-
-
Property changes on: ps/trunk/binaries/data/mods/public/gui/session/top_panel/button_diplomacy.xml
___________________________________________________________________
Deleted: svn:eol-style
## -1 +0,0 ##
-native
\ No newline at end of property
Deleted: svn:mime-type
## -1 +0,0 ##
-text/xml
\ No newline at end of property
Index: ps/trunk/binaries/data/mods/public/gui/session/top_panel/resources.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/session/top_panel/resources.xml (revision 23075)
+++ ps/trunk/binaries/data/mods/public/gui/session/top_panel/resources.xml (nonexistent)
@@ -1,14 +0,0 @@
-
-
-
-
-
-
-
-
- saveResPopTooltipSort();
- updatePlayerDisplay();
-
-
-
-
Property changes on: ps/trunk/binaries/data/mods/public/gui/session/top_panel/resources.xml
___________________________________________________________________
Deleted: svn:eol-style
## -1 +0,0 ##
-native
\ No newline at end of property
Index: ps/trunk/binaries/data/mods/public/gui/session/top_panel/resource_population.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/session/top_panel/resource_population.xml (revision 23075)
+++ ps/trunk/binaries/data/mods/public/gui/session/top_panel/resource_population.xml (nonexistent)
@@ -1,10 +0,0 @@
-
-
-
-
-
-
- saveResPopTooltipSort();
- updatePlayerDisplay();
-
-
Property changes on: ps/trunk/binaries/data/mods/public/gui/session/top_panel/resource_population.xml
___________________________________________________________________
Deleted: svn:eol-style
## -1 +0,0 ##
-native
\ No newline at end of property
Deleted: svn:mime-type
## -1 +0,0 ##
-text/xml
\ No newline at end of property
Index: ps/trunk/binaries/data/mods/public/gui/session/top_panel/TradeDialogButton.js
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/session/top_panel/TradeDialogButton.js (revision 23075)
+++ ps/trunk/binaries/data/mods/public/gui/session/top_panel/TradeDialogButton.js (nonexistent)
@@ -1,23 +0,0 @@
-/**
- * This class handles the button which opens the diplomacy dialog.
- */
-class TradeDialogButton
-{
- constructor(tradeDialog)
- {
- this.tradeButton = Engine.GetGUIObjectByName("tradeButton");
- this.tradeButton.onPress = tradeDialog.toggle.bind(tradeDialog);
- this.isAvailable = g_ResourceData.GetTradableCodes().length || g_ResourceData.GetBarterableCodes().length;
- }
-
- update()
- {
- this.tradeButton.hidden = g_ViewedPlayer < 1 || !this.isAvailable;
-
- this.tradeButton.tooltip =
- colorizeHotkey("%(hotkey)s" + " ", "session.gui.barter.toggle") +
- translate(this.Tooltip);
- }
-}
-
-TradeDialogButton.prototype.Tooltip = markForTranslation("Barter & Trade");
Property changes on: ps/trunk/binaries/data/mods/public/gui/session/top_panel/TradeDialogButton.js
___________________________________________________________________
Deleted: svn:eol-style
## -1 +0,0 ##
-native
\ No newline at end of property
Index: ps/trunk/binaries/data/mods/public/gui/session/top_panel/button_game_speed.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/session/top_panel/button_game_speed.xml (revision 23075)
+++ ps/trunk/binaries/data/mods/public/gui/session/top_panel/button_game_speed.xml (nonexistent)
@@ -1,20 +0,0 @@
-
-
-
- Game Speed
-
-
- toggleGameSpeed();
-
-
-
-
- Choose game speed
-
-
Property changes on: ps/trunk/binaries/data/mods/public/gui/session/top_panel/button_game_speed.xml
___________________________________________________________________
Deleted: svn:eol-style
## -1 +0,0 ##
-native
\ No newline at end of property
Deleted: svn:mime-type
## -1 +0,0 ##
-text/xml
\ No newline at end of property
Index: ps/trunk/binaries/data/mods/public/gui/session/DiplomacyColors.js
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/session/DiplomacyColors.js (revision 23075)
+++ ps/trunk/binaries/data/mods/public/gui/session/DiplomacyColors.js (revision 23076)
@@ -1,101 +1,111 @@
/**
* This class determines the player colors to be displayed.
* If diplomacy color mode is disabled, it picks the player color chosen by the map or the players.
* If diplomacy color mode is enabled, it choses the player chosen color based on diplomatic stance.
* Observers that didn't chose a specific player perspective see each team in one representative color.
*/
class DiplomacyColors
{
constructor()
{
this.enabled = false;
// The array of displayed player colors (either the diplomacy color or regular color for each player).
this.displayedPlayerColors = undefined;
+
+ this.diplomacyColorsChangeHandlers = [];
+
+ registerPlayersInitHandler(this.onPlayersInit.bind(this));
+ }
+
+ registerDiplomacyColorsChangeHandler(handler)
+ {
+ this.diplomacyColorsChangeHandlers.push(handler);
}
- onPlayerInit()
+ onPlayersInit()
{
this.computeTeamColors();
}
onDiplomacyChange()
{
if (this.enabled)
this.updateDisplayedPlayerColors();
}
onCeasefireEnded()
{
if (this.enabled)
this.updateDisplayedPlayerColors();
}
isEnabled()
{
return this.enabled;
}
toggle()
{
this.enabled = !this.enabled;
this.updateDisplayedPlayerColors();
}
getPlayerColor(playerID, alpha)
{
return rgbToGuiColor(this.displayedPlayerColors[playerID], alpha);
}
/**
* Updates the displayed colors of players in the simulation and GUI.
*/
updateDisplayedPlayerColors()
{
this.computeTeamColors();
Engine.GuiInterfaceCall("UpdateDisplayedPlayerColors", {
"displayedPlayerColors": this.displayedPlayerColors,
"displayDiplomacyColors": this.enabled,
"showAllStatusBars": g_ShowAllStatusBars,
"selected": g_Selection.toList()
});
- updateGUIObjects();
+ for (let handler of this.diplomacyColorsChangeHandlers)
+ handler(this.enabled);
}
computeTeamColors()
{
if (!this.enabled)
{
this.displayedPlayerColors = g_Players.map(player => player.color);
return;
}
let teamRepresentatives = {};
for (let i = 1; i < g_Players.length; ++i)
if (g_ViewedPlayer <= 0)
{
// Observers and gaia see team colors
let team = g_Players[i].team;
this.displayedPlayerColors[i] = g_Players[teamRepresentatives[team] || i].color;
if (team != -1 && !teamRepresentatives[team])
teamRepresentatives[team] = i;
}
else
// Players see colors depending on diplomacy
this.displayedPlayerColors[i] =
g_ViewedPlayer == i ? this.getDiplomacyColor("self") :
g_Players[g_ViewedPlayer].isAlly[i] ? this.getDiplomacyColor("ally") :
g_Players[g_ViewedPlayer].isNeutral[i] ? this.getDiplomacyColor("neutral") :
this.getDiplomacyColor("enemy");
this.displayedPlayerColors[0] = g_Players[0].color;
}
getDiplomacyColor(stance)
{
return guiToRgbColor(Engine.ConfigDB_GetValue("user", "gui.session.diplomacycolors." + stance)) ||
guiToRgbColor(Engine.ConfigDB_GetValue("default", "gui.session.diplomacycolors." + stance));
}
}
Index: ps/trunk/binaries/data/mods/public/gui/session/GameSpeedControl.js
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/session/GameSpeedControl.js (nonexistent)
+++ ps/trunk/binaries/data/mods/public/gui/session/GameSpeedControl.js (revision 23076)
@@ -0,0 +1,45 @@
+/**
+ * This class controls the gamespeed.
+ * The control is only available in singleplayer and replaymode.
+ * Fast forwarding is enabled if and only if there is no human player assigned.
+ */
+class GameSpeedControl
+{
+ constructor(playerViewControl)
+ {
+ this.gameSpeed = Engine.GetGUIObjectByName("gameSpeed");
+ this.gameSpeed.onSelectionChange = this.onSelectionChange.bind(this);
+
+ registerPlayersInitHandler(this.rebuild.bind(this));
+ registerPlayersFinishedHandler(this.rebuild.bind(this));
+ playerViewControl.registerPlayerIDChangeHandler(this.rebuild.bind(this));
+ }
+
+ rebuild()
+ {
+ let player = g_Players[Engine.GetPlayerID()];
+
+ let gameSpeeds = prepareForDropdown(g_Settings.GameSpeeds.filter(speed =>
+ !speed.FastForward || !player || player.state != "active"));
+
+ this.gameSpeed.list = gameSpeeds.Title;
+ this.gameSpeed.list_data = gameSpeeds.Speed;
+
+ let simRate = Engine.GetSimRate();
+
+ // If the gamespeed is something like 0.100001 from the gamesetup, set it to 0.1
+ let gameSpeedIdx = gameSpeeds.Speed.indexOf(+simRate.toFixed(2));
+ this.gameSpeed.selected = gameSpeedIdx != -1 ? gameSpeedIdx : gameSpeeds.Default;
+ }
+
+ toggle()
+ {
+ this.gameSpeed.hidden = !this.gameSpeed.hidden;
+ }
+
+ onSelectionChange()
+ {
+ if (!g_IsNetworked)
+ Engine.SetSimRate(+this.gameSpeed.list_data[this.gameSpeed.selected]);
+ }
+}
Property changes on: ps/trunk/binaries/data/mods/public/gui/session/GameSpeedControl.js
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Index: ps/trunk/binaries/data/mods/public/gui/session/GameSpeedControl.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/session/GameSpeedControl.xml (nonexistent)
+++ ps/trunk/binaries/data/mods/public/gui/session/GameSpeedControl.xml (revision 23076)
@@ -0,0 +1,12 @@
+
+
+ Choose game speed
+
Property changes on: ps/trunk/binaries/data/mods/public/gui/session/GameSpeedControl.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 (nonexistent)
+++ ps/trunk/binaries/data/mods/public/gui/session/PauseControl.js (revision 23076)
@@ -0,0 +1,86 @@
+/**
+ * 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 = [];
+ }
+
+ 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)
+ {
+ // 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;
+
+ 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();
+ }
+}
Property changes on: ps/trunk/binaries/data/mods/public/gui/session/PauseControl.js
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Index: ps/trunk/binaries/data/mods/public/gui/session/PauseOverlay.js
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/session/PauseOverlay.js (nonexistent)
+++ ps/trunk/binaries/data/mods/public/gui/session/PauseOverlay.js (revision 23076)
@@ -0,0 +1,44 @@
+/**
+ * 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));
+ }
+
+ onPress()
+ {
+ if (this.pauseControl.explicitPause)
+ this.pauseControl.setPaused(false, true);
+ }
+
+ onPauseChange()
+ {
+ let hidden = !this.pauseControl.explicitPause && !this.pauseControl.pausingClients.length;
+ 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");
Property changes on: ps/trunk/binaries/data/mods/public/gui/session/PauseOverlay.js
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Index: ps/trunk/binaries/data/mods/public/gui/session/PauseOverlay.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/session/PauseOverlay.xml (nonexistent)
+++ ps/trunk/binaries/data/mods/public/gui/session/PauseOverlay.xml (revision 23076)
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+ Game Paused
+
+
+
+ Click to Resume Game
+
+
+
+
+
Property changes on: ps/trunk/binaries/data/mods/public/gui/session/PauseOverlay.xml
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Index: ps/trunk/binaries/data/mods/public/gui/session/TopPanel.js
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/session/TopPanel.js (nonexistent)
+++ ps/trunk/binaries/data/mods/public/gui/session/TopPanel.js (revision 23076)
@@ -0,0 +1,19 @@
+/**
+ * This class owns all classes that are part of the top panel.
+ */
+class TopPanel
+{
+ constructor(playerViewControl, diplomacyDialog, tradeDialog, objectivesDialog, gameSpeedControl)
+ {
+ this.counterManager = new CounterManager(playerViewControl);
+ this.civIcon = new CivIcon(playerViewControl);
+ this.buildLabel = new BuildLabel(playerViewControl);
+
+ this.followPlayer = new FollowPlayer(playerViewControl);
+
+ this.diplomacyDialogButton = new DiplomacyDialogButton(playerViewControl, diplomacyDialog);
+ this.gameSpeedButton = new GameSpeedButton(gameSpeedControl);
+ this.objectivesDialogButton = new ObjectivesDialogButton(objectivesDialog);
+ this.tradeDialogButton = new TradeDialogButton(playerViewControl, tradeDialog);
+ }
+}
Property changes on: ps/trunk/binaries/data/mods/public/gui/session/TopPanel.js
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Index: ps/trunk/binaries/data/mods/public/gui/session/TopPanel.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/session/TopPanel.xml (nonexistent)
+++ ps/trunk/binaries/data/mods/public/gui/session/TopPanel.xml (revision 23076)
@@ -0,0 +1,9 @@
+
+
+
+
+
Property changes on: ps/trunk/binaries/data/mods/public/gui/session/TopPanel.xml
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Added: svn:mime-type
## -0,0 +1 ##
+text/xml
\ No newline at end of property
Index: ps/trunk/binaries/data/mods/public/gui/session/chat/Chat.js
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/session/chat/Chat.js (revision 23075)
+++ ps/trunk/binaries/data/mods/public/gui/session/chat/Chat.js (revision 23076)
@@ -1,87 +1,90 @@
/**
* This class is only concerned with owning the helper classes and linking them.
* The class is not dealing with specific GUI objects and doesn't provide own handlers.
*/
class Chat
{
- constructor()
+ constructor(playerViewControl)
{
this.ChatWindow = new ChatWindow();
this.ChatOverlay = new ChatOverlay();
this.ChatHistory = new ChatHistory();
this.ChatHistory.registerSelectionChangeHandler(this.ChatWindow.onSelectionChange.bind(this.ChatWindow));
this.ChatInput = new ChatInput();
this.ChatInput.registerChatSubmitHandler(executeNetworkCommand);
this.ChatInput.registerChatSubmitHandler(executeCheat);
this.ChatInput.registerChatSubmitHandler(this.submitChat.bind(this));
this.ChatInput.registerChatSubmittedHandler(this.closePage.bind(this));
this.ChatAddressees = new ChatAddressees();
this.ChatAddressees.registerSelectionChangeHandler(this.ChatInput.onSelectionChange.bind(this.ChatInput));
this.ChatAddressees.registerSelectionChangeHandler(this.ChatWindow.onSelectionChange.bind(this.ChatWindow));
this.ChatMessageHandler = new ChatMessageHandler();
this.ChatMessageHandler.registerMessageFormatClass(ChatMessageFormatNetwork);
this.ChatMessageHandler.registerMessageFormatClass(ChatMessageFormatSimulation);
this.ChatMessageFormatPlayer = new ChatMessageFormatPlayer();
this.ChatMessageFormatPlayer.registerAddresseeTypes(this.ChatAddressees.AddresseeTypes);
this.ChatMessageHandler.registerMessageFormat("message", this.ChatMessageFormatPlayer);
this.ChatMessageHandler.registerMessageHandler(this.ChatOverlay.onChatMessage.bind(this.ChatOverlay));
this.ChatMessageHandler.registerMessageHandler(this.ChatHistory.onChatMessage.bind(this.ChatHistory));
this.ChatMessageHandler.registerMessageHandler(() => {
if (this.ChatWindow.isOpen() && this.ChatWindow.isExtended())
this.ChatHistory.displayChatHistory();
});
+ registerPlayersFinishedHandler(this.onUpdatePlayers.bind(this));
+ playerViewControl.registerViewedPlayerChangeHandler(this.onUpdatePlayers.bind(this));
+
Engine.SetGlobalHotkey("chat", this.openPage.bind(this));
Engine.SetGlobalHotkey("privatechat", this.openPage.bind(this));
Engine.SetGlobalHotkey("teamchat", () => { this.openPage(g_IsObserver ? "/observers" : "/allies"); });
}
/**
* Called by the owner whenever g_PlayerAssignments or g_Players changed.
*/
onUpdatePlayers()
{
this.ChatAddressees.onUpdatePlayers();
}
openPage(command = "")
{
if (g_Disconnected)
return;
closeOpenDialogs();
this.ChatAddressees.select(command);
this.ChatHistory.displayChatHistory();
this.ChatWindow.openPage(command);
}
closePage()
{
this.ChatWindow.closePage();
}
/**
* Send the given chat message.
*/
submitChat(text, command = "")
{
if (command.startsWith("/msg "))
Engine.SetGlobalHotkey("privatechat", () => { this.openPage(command); });
let msg = command ? command + " " + text : text;
if (Engine.HasNetClient())
Engine.SendNetworkChat(msg);
else
this.ChatMessageHandler.handleMessage({
"type": "message",
"guid": "local",
"text": msg
});
}
}
Index: ps/trunk/binaries/data/mods/public/gui/session/chat/ChatWindow.js
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/session/chat/ChatWindow.js (revision 23075)
+++ ps/trunk/binaries/data/mods/public/gui/session/chat/ChatWindow.js (revision 23076)
@@ -1,94 +1,95 @@
/**
* This class is concerned with opening, closing the chat page, and
* resizing it depending on whether the chat history is shown.
*/
class ChatWindow
{
constructor()
{
this.chatInput = Engine.GetGUIObjectByName("chatInput");
this.closeChat = Engine.GetGUIObjectByName("closeChat");
this.extendedChat = Engine.GetGUIObjectByName("extendedChat");
this.chatHistoryText = Engine.GetGUIObjectByName("chatHistoryText");
this.chatHistoryPage = Engine.GetGUIObjectByName("chatHistoryPage");
this.chatDialogPanel = Engine.GetGUIObjectByName("chatDialogPanel");
this.chatDialogPanelSmallSize = Engine.GetGUIObjectByName("chatDialogPanelSmall").size;
this.chatDialogPanelLargeSize = Engine.GetGUIObjectByName("chatDialogPanelLarge").size;
// Adjust the width so that the chat history is in the golden ratio
this.aspectRatio = (1 + Math.sqrt(5)) / 2;
this.initPage();
}
initPage()
{
this.closeChat.onPress = this.closePage.bind(this);
this.extendedChat.onPress = () => {
Engine.ConfigDB_CreateAndWriteValueToFile("user", "chat.session.extended", String(this.isExtended()), "config/user.cfg");
this.resizeChatWindow();
this.chatInput.focus();
};
this.extendedChat.checked = Engine.ConfigDB_GetValue("user", "chat.session.extended") == "true";
+ this.chatDialogPanel.onWindowResized = this.resizeChatWindow.bind(this);
this.resizeChatWindow();
}
/**
* Called if the addressee or history filter selection changed.
*/
onSelectionChange()
{
if (this.isOpen())
this.chatInput.focus();
}
isOpen()
{
return !this.chatDialogPanel.hidden;
}
isExtended()
{
return this.extendedChat.checked;
}
openPage(command)
{
this.chatInput.focus();
this.chatDialogPanel.hidden = false;
}
closePage()
{
this.chatInput.caption = "";
this.chatInput.blur();
this.chatDialogPanel.hidden = true;
}
resizeChatWindow()
{
// Hide/show the panel
this.chatHistoryPage.hidden = !this.isExtended();
// Resize the window
if (this.isExtended())
{
this.chatDialogPanel.size = this.chatDialogPanelLargeSize;
let chatHistoryTextSize = this.chatHistoryText.getComputedSize();
let width = this.aspectRatio * (chatHistoryTextSize.bottom - chatHistoryTextSize.top);
let size = this.chatDialogPanel.size;
size.left = -width / 2 - this.chatHistoryText.size.left;
size.right = width / 2 + this.chatHistoryText.size.left;
this.chatDialogPanel.size = size;
}
else
this.chatDialogPanel.size = this.chatDialogPanelSmallSize;
}
}
Index: ps/trunk/binaries/data/mods/public/gui/session/developer_overlay.js
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/session/developer_overlay.js (revision 23075)
+++ ps/trunk/binaries/data/mods/public/gui/session/developer_overlay.js (revision 23076)
@@ -1,270 +1,259 @@
-function DeveloperOverlay()
+function DeveloperOverlay(playerViewControl)
{
this.commandHeight = 20;
this.displayState = false;
this.timeWarp = false;
- this.changePerspective = false;
this.controlAll = false;
-
+ this.playerViewControl = playerViewControl;
Engine.GetGUIObjectByName("devCommandsOverlay").onPress = this.toggle;
this.layout();
+
+ registerSimulationUpdateHandler(this.update.bind(this));
+ registerEntitySelectionChangeHandler(this.update.bind(this));
}
DeveloperOverlay.prototype.getCommands = function() {
return [
{
"label": translate("Control all units"),
"onPress": checked => {
Engine.PostNetworkCommand({
"type": "control-all",
"flag": checked
});
},
"checked": () => this.controlAll,
},
{
"label": translate("Change perspective"),
"onPress": checked => {
- this.setChangePerspective(checked);
+ this.playerViewControl.setChangePerspective(checked);
},
},
{
"label": translate("Display selection state"),
"onPress": checked => {
this.displayState = checked;
+ this.update();
},
},
{
"label": translate("Pathfinder overlay"),
"onPress": checked => {
Engine.GuiInterfaceCall("SetPathfinderDebugOverlay", checked);
},
},
{
"label": translate("Obstruction overlay"),
"onPress": checked => {
Engine.GuiInterfaceCall("SetObstructionDebugOverlay", checked);
},
},
{
"label": translate("Unit motion overlay"),
"onPress": checked => {
g_Selection.SetMotionDebugOverlay(checked);
},
},
{
"label": translate("Range overlay"),
"onPress": checked => {
Engine.GuiInterfaceCall("SetRangeDebugOverlay", checked);
},
},
{
"label": translate("Bounding box overlay"),
"onPress": checked => {
Engine.SetBoundingBoxDebugOverlay(checked);
},
},
{
"label": translate("Restrict camera"),
"onPress": checked => {
Engine.GameView_SetConstrainCameraEnabled(checked);
},
"checked": () => Engine.GameView_GetConstrainCameraEnabled(),
},
{
"label": translate("Reveal map"),
"onPress": checked => {
Engine.PostNetworkCommand({
"type": "reveal-map",
"enable": checked
});
},
"checked": () => Engine.GuiInterfaceCall("IsMapRevealed"),
},
{
"label": translate("Enable time warp"),
"onPress": checked => {
this.timeWarp = checked;
if (checked)
messageBox(
500, 250,
translate("Note: time warp mode is a developer option, and not intended for use over long periods of time. Using it incorrectly may cause the game to run out of memory or crash."),
translate("Time warp mode"));
Engine.EnableTimeWarpRecording(checked ? 10 : 0);
},
},
{
"label": translate("Promote selected units"),
"onPress": checked => {
Engine.PostNetworkCommand({
"type": "promote",
"entities": g_Selection.toList()
});
},
},
{
"label": translate("Hierarchical pathfinder overlay"),
"onPress": checked => {
Engine.GuiInterfaceCall("SetPathfinderHierDebugOverlay", checked);
},
},
{
"label": translate("Enable culling"),
"onPress": checked => {
Engine.GameView_SetCullingEnabled(checked);
},
"checked": () => Engine.GameView_GetCullingEnabled(),
},
{
"label": translate("Lock cull camera"),
"onPress": checked => {
Engine.GameView_SetLockCullCameraEnabled(checked);
},
"checked": () => Engine.GameView_GetLockCullCameraEnabled(),
},
{
"label": translate("Display camera frustum"),
"onPress": checked => {
Engine.Renderer_SetDisplayFrustumEnabled(checked);
},
"checked": () => Engine.Renderer_GetDisplayFrustumEnabled(),
},
];
};
DeveloperOverlay.prototype.layout = function()
{
for (let body of Engine.GetGUIObjectByName("dev_commands").children)
body.hidden = true;
let overlayHeight = 0;
let commands = this.getCommands();
for (let i = 0; i < commands.length; ++i)
{
let command = commands[i];
let body = Engine.GetGUIObjectByName("dev_command[" + i + "]");
let bodySize = body.size;
bodySize.top = i * this.commandHeight;
bodySize.bottom = bodySize.top + this.commandHeight;
body.size = bodySize;
body.hidden = false;
let label = Engine.GetGUIObjectByName("dev_command_label[" + i + "]");
label.caption = command.label;
let checkbox = Engine.GetGUIObjectByName("dev_command_checkbox[" + i + "]");
checkbox.onPress = function() {
command.onPress(checkbox.checked);
if (command.checked)
checkbox.checked = command.checked();
};
overlayHeight += this.commandHeight;
}
let overlay = Engine.GetGUIObjectByName("devCommandsOverlay");
let overlaySize = overlay.size;
overlaySize.bottom = overlaySize.top + overlayHeight;
overlay.size = overlaySize;
this.updateValues();
};
DeveloperOverlay.prototype.updateValues = function()
{
- let commands = this.getCommands();
- for (let i = 0; i < commands.length; ++i)
- {
- let command = commands[i];
-
- let body = Engine.GetGUIObjectByName("dev_command[" + i + "]");
- if (body.hidden)
- continue;
-
+ this.getCommands().forEach((command, i) => {
let checkbox = Engine.GetGUIObjectByName("dev_command_checkbox[" + i + "]");
if (command.checked)
checkbox.checked = command.checked();
- }
+ });
};
DeveloperOverlay.prototype.toggle = function()
{
if (!g_GameAttributes.settings.CheatsEnabled && !g_IsReplay)
return;
let overlay = Engine.GetGUIObjectByName("devCommandsOverlay");
overlay.hidden = !overlay.hidden;
let message = overlay.hidden ?
markForTranslation("The Developer Overlay was closed.") :
markForTranslation("The Developer Overlay was opened.");
// Only players can send the simulation chat command
if (Engine.GetPlayerID() == -1)
g_Chat.submitChat(message);
else
Engine.PostNetworkCommand({
"type": "aichat",
"message": message,
"translateMessage": true,
"translateParameters": [],
"parameters": {}
});
};
DeveloperOverlay.prototype.update = function()
{
+ let playerState = g_SimState.players[g_ViewedPlayer];
+ this.controlAll = playerState ? playerState.controlsAll : false;
+
this.updateValues();
+ this.updateEntityState();
+}
+DeveloperOverlay.prototype.updateEntityState = function()
+{
let debug = Engine.GetGUIObjectByName("debugEntityState");
if (!this.displayState)
{
debug.hidden = true;
return;
}
debug.hidden = false;
let conciseSimState = clone(GetSimState());
conciseSimState.players = "<<>>";
let text = "simulation: " + uneval(conciseSimState);
let selection = g_Selection.toList();
if (selection.length)
{
let entState = GetEntityState(selection[0]);
if (entState)
{
let template = GetTemplateData(entState.template);
text += "\n\nentity: {\n";
for (let k in entState)
text += " " + k + ":" + uneval(entState[k]) + "\n";
text += "}\n\ntemplate: " + uneval(template);
}
}
debug.caption = text.replace(/\[/g, "\\[");
};
DeveloperOverlay.prototype.isTimeWarpEnabled = function() {
return this.timeWarp;
};
-DeveloperOverlay.prototype.isChangePerspective = function() {
- return this.changePerspective;
-};
-
-DeveloperOverlay.prototype.setChangePerspective = function(value) {
- this.changePerspective = value;
- selectViewPlayer(g_ViewedPlayer);
-};
-
DeveloperOverlay.prototype.isControlAll = function() {
return this.controlAll;
};
-
-DeveloperOverlay.prototype.setControlAll = function(value) {
- this.controlAll = value;
-};
Index: ps/trunk/binaries/data/mods/public/gui/session/diplomacy/DiplomacyDialog.js
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/session/diplomacy/DiplomacyDialog.js (revision 23075)
+++ ps/trunk/binaries/data/mods/public/gui/session/diplomacy/DiplomacyDialog.js (revision 23076)
@@ -1,89 +1,95 @@
/**
* This class is concerned with opening, closing and resizing the diplomacy dialog and
* relaying events to the classes that update individual elements of the dialog.
*/
class DiplomacyDialog
{
- constructor(diplomacyColors)
+ constructor(playerViewControl, diplomacyColors)
{
this.diplomacyDialogCeasefireCounter = new DiplomacyDialogCeasefireCounter();
this.diplomacyDialogColorsButton = new DiplomacyDialogColorsButton(diplomacyColors);
this.diplomacyDialogPlayerControlManager = undefined;
this.diplomacyDialogPanel = Engine.GetGUIObjectByName("diplomacyDialogPanel");
Engine.GetGUIObjectByName("diplomacyClose").onPress = this.close.bind(this);
+
+ registerPlayersInitHandler(this.onPlayersInit.bind(this));
+ registerSimulationUpdateHandler(this.onViewedPlayerChange.bind(this));
+ playerViewControl.registerViewedPlayerChangeHandler(this.updateIfOpen.bind(this));
}
- onPlayerInit()
+ onPlayersInit()
{
this.diplomacyDialogPlayerControlManager = new DiplomacyDialogPlayerControlManager();
this.resize();
}
+ onViewedPlayerChange()
+ {
+ if (g_ViewedPlayer >= 1)
+ this.updateIfOpen();
+ else
+ this.close();
+ }
+
onSpyResponse(notification, player)
{
this.diplomacyDialogPlayerControlManager.onSpyResponse(notification, player);
}
- update()
+ updateIfOpen()
{
- if (!this.isOpen())
- return;
-
- if (g_ViewedPlayer >= 1)
+ if (this.isOpen())
this.updatePanels();
- else
- this.close();
}
updatePanels()
{
this.diplomacyDialogCeasefireCounter.update();
- this.diplomacyDialogColorsButton.update();
this.diplomacyDialogPlayerControlManager.update();
}
open()
{
closeOpenDialogs();
if (g_ViewedPlayer < 1)
return;
this.updatePanels();
this.diplomacyDialogPanel.hidden = false;
}
close()
{
this.diplomacyDialogPanel.hidden = true;
}
isOpen()
{
return !this.diplomacyDialogPanel.hidden;
}
toggle()
{
let open = this.isOpen();
closeOpenDialogs();
if (!open)
this.open();
}
resize()
{
let widthOffset = DiplomacyDialogPlayerControl.prototype.TributeButtonManager.getWidthOffset() / 2;
let heightOffset = DiplomacyDialogPlayerControl.prototype.DiplomacyPlayerText.getHeightOffset() / 2;
let size = this.diplomacyDialogPanel.size;
size.left -= widthOffset;
size.right += widthOffset;
size.top -= heightOffset;
size.bottom += heightOffset;
this.diplomacyDialogPanel.size = size;
}
}
Index: ps/trunk/binaries/data/mods/public/gui/session/diplomacy/DiplomacyDialogColorsButton.js
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/session/diplomacy/DiplomacyDialogColorsButton.js (revision 23075)
+++ ps/trunk/binaries/data/mods/public/gui/session/diplomacy/DiplomacyDialogColorsButton.js (revision 23076)
@@ -1,31 +1,34 @@
/**
* This class updates the diplomacy colors button within the diplomacy dialog.
*/
class DiplomacyDialogColorsButton
{
constructor(diplomacyColors)
{
- this.diplomacyColors = diplomacyColors;
-
this.diplomacyColorsWindowButton = Engine.GetGUIObjectByName("diplomacyColorsWindowButton");
this.diplomacyColorsWindowButtonIcon = Engine.GetGUIObjectByName("diplomacyColorsWindowButtonIcon");
this.diplomacyColorsWindowButton.onPress = diplomacyColors.toggle.bind(diplomacyColors);
+ registerHotkeyChangeHandler(this.onHotkeyChange.bind(this));
+ diplomacyColors.registerDiplomacyColorsChangeHandler(this.onDiplomacyColorsChange.bind(this))
}
- update()
+ onHotkeyChange()
{
this.diplomacyColorsWindowButton.tooltip =
colorizeHotkey("%(hotkey)s" + " ", "session.diplomacycolors") +
translate(this.Tooltip);
+ }
+ onDiplomacyColorsChange(enabled)
+ {
this.diplomacyColorsWindowButtonIcon.sprite =
"stretched:" +
- (this.diplomacyColors.isEnabled() ? this.SpriteEnabled : this.SpriteDisabled);
+ (enabled ? this.SpriteEnabled : this.SpriteDisabled);
}
}
DiplomacyDialogColorsButton.prototype.Tooltip = markForTranslation("Toggle Diplomacy Colors");
DiplomacyDialogColorsButton.prototype.SpriteEnabled = "session/icons/diplomacy-on.png";
DiplomacyDialogColorsButton.prototype.SpriteDisabled = "session/icons/diplomacy.png";
Index: ps/trunk/binaries/data/mods/public/gui/session/hotkeys/misc.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/session/hotkeys/misc.xml (revision 23075)
+++ ps/trunk/binaries/data/mods/public/gui/session/hotkeys/misc.xml (revision 23076)
@@ -1,106 +1,102 @@
closeOpenDialogs();
toggleGUI();
toggleMenu();
toggleTutorial();
openGameSummary();
openStrucTree("page_civinfo.xml");
openStrucTree("page_structree.xml");
toggleConfigBool("silhouettes");
var newSetting = !Engine.Renderer_GetShowSkyEnabled();
Engine.Renderer_SetShowSkyEnabled(newSetting);
-
- togglePause();
-
-
Engine.QuickSave(getSavedGameData());
Engine.QuickLoad();
performCommand(g_Selection.toList().map(ent => GetEntityState(ent)), "delete");
unloadAll();
stopUnits(g_Selection.toList());
backToWork();
updateSelectionDetails();
updateSelectionDetails();
findIdleUnit(g_MilitaryTypes);
findIdleUnit(["!Domestic"]);
clearSelection();
toggleRangeOverlay("Attack");
toggleRangeOverlay("Auras");
toggleRangeOverlay("Heal");
g_ShowAllStatusBars = !g_ShowAllStatusBars;
recalculateStatusBarDisplay();
Index: ps/trunk/binaries/data/mods/public/gui/session/menu.js
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/session/menu.js (revision 23075)
+++ ps/trunk/binaries/data/mods/public/gui/session/menu.js (revision 23076)
@@ -1,442 +1,311 @@
// Menu / panel border size
var MARGIN = 4;
// Includes the main menu button
const NUM_BUTTONS = 10;
// Regular menu buttons
var BUTTON_HEIGHT = 32;
// The position where the bottom of the menu will end up (currently 228)
const END_MENU_POSITION = (BUTTON_HEIGHT * NUM_BUTTONS) + MARGIN;
// Menu starting position: bottom
const MENU_BOTTOM = 0;
// Menu starting position: top
const MENU_TOP = MENU_BOTTOM - END_MENU_POSITION;
// Number of pixels per millisecond to move
var MENU_SPEED = 1.2;
/**
* Store civilization code and page (structree or history) opened in civilization info.
*/
var g_CivInfo = {
"civ": "",
"page": "page_structree.xml"
};
var g_IsMenuOpen = false;
-var g_IsObjectivesOpen = false;
-
/**
* Remember last viewed summary panel and charts.
*/
var g_SummarySelectedData;
-function initMenu()
+function initMenu(playerViewControl, pauseControl)
{
Engine.GetGUIObjectByName("menu").size = "100%-164 " + MENU_TOP + " 100% " + MENU_BOTTOM;
+ Engine.GetGUIObjectByName("lobbyButton").enabled = Engine.HasXmppClient();
+ playerViewControl.registerViewedPlayerChangeHandler(updateResignButton);
+ updatePauseButton();
+ pauseControl.registerPauseHandler(updatePauseButton);
// TODO: Atlas should pass g_GameAttributes.settings
- for (let button of ["menuExitButton", "summaryButton", "objectivesButton"])
+ for (let button of ["menuExitButton", "summaryButton"])
Engine.GetGUIObjectByName(button).enabled = !Engine.IsAtlasRunning();
}
function updateMenuPosition(dt)
{
let menu = Engine.GetGUIObjectByName("menu");
let maxOffset = g_IsMenuOpen ?
END_MENU_POSITION - menu.size.bottom :
menu.size.top - MENU_TOP;
if (maxOffset <= 0)
return;
let offset = Math.min(MENU_SPEED * dt, maxOffset) * (g_IsMenuOpen ? +1 : -1);
let size = menu.size;
size.top += offset;
size.bottom += offset;
menu.size = size;
}
// Opens the menu by revealing the screen which contains the menu
function openMenu()
{
g_IsMenuOpen = true;
}
// Closes the menu and resets position
function closeMenu()
{
g_IsMenuOpen = false;
}
function toggleMenu()
{
g_IsMenuOpen = !g_IsMenuOpen;
}
+function updatePauseButton()
+{
+ let pauseButton = Engine.GetGUIObjectByName("pauseButton");
+ pauseButton.caption = g_PauseControl.explicitPause ? translate("Resume") : translate("Pause");
+ pauseButton.enabled = !g_IsObserver || !g_IsNetworked || g_IsController;
+ pauseButton.onPress = () => {
+ closeMenu();
+ g_PauseControl.setPaused(!g_PauseControl.explicitPause, true);
+ };
+}
+
+function updateResignButton()
+{
+ Engine.GetGUIObjectByName("menuResignButton").enabled = !g_IsObserver;
+}
+
function optionsMenuButton()
{
closeOpenDialogs();
openOptions();
}
function lobbyDialogButton()
{
if (!Engine.HasXmppClient())
return;
closeOpenDialogs();
Engine.PushGuiPage("page_lobby.xml", { "dialog": true });
}
function chatMenuButton()
{
g_Chat.openPage();
}
function resignMenuButton()
{
closeOpenDialogs();
- pauseGame();
+ g_PauseControl.implicitPause();
messageBox(
400, 200,
translate("Are you sure you want to resign?"),
translate("Confirmation"),
[translate("No"), translate("Yes")],
- [resumeGame, resignGame]
- );
+ [resumeGame, resignGame]);
}
function exitMenuButton()
{
closeOpenDialogs();
- pauseGame();
+ g_PauseControl.implicitPause();
let messageTypes = {
"host": {
"caption": translate("Are you sure you want to quit? Leaving will disconnect all other players."),
- "buttons": [resumeGame, leaveGame]
+ "buttons": [resumeGame, endGame]
},
"client": {
"caption": translate("Are you sure you want to quit?"),
"buttons": [resumeGame, resignQuestion]
},
"singleplayer": {
"caption": translate("Are you sure you want to quit?"),
- "buttons": [resumeGame, leaveGame]
+ "buttons": [resumeGame, endGame]
}
};
let messageType = g_IsNetworked && g_IsController ? "host" :
(g_IsNetworked && !g_IsObserver ? "client" : "singleplayer");
messageBox(
400, 200,
messageTypes[messageType].caption,
translate("Confirmation"),
[translate("No"), translate("Yes")],
messageTypes[messageType].buttons
);
}
function resignQuestion()
{
messageBox(
400, 200,
translate("Do you want to resign or will you return soon?"),
translate("Confirmation"),
[translate("I will return"), translate("I resign")],
- [leaveGame, resignGame],
- [true, false]
- );
+ [endGame, resignGame]);
}
function openDeleteDialog(selection)
{
closeOpenDialogs();
+ g_PauseControl.implicitPause();
let deleteSelectedEntities = function(selectionArg)
{
Engine.PostNetworkCommand({
"type": "delete-entities",
"entities": selectionArg
});
+ resumeGame();
};
messageBox(
400, 200,
translate("Destroy everything currently selected?"),
translate("Delete"),
[translate("No"), translate("Yes")],
[resumeGame, deleteSelectedEntities],
[null, selection]
);
}
function openSave()
{
closeOpenDialogs();
- pauseGame();
+ g_PauseControl.implicitPause();
Engine.PushGuiPage(
"page_loadgame.xml",
{ "savedGameData": getSavedGameData() },
resumeGame);
}
function openOptions()
{
closeOpenDialogs();
- pauseGame();
+ g_PauseControl.implicitPause();
Engine.PushGuiPage(
"page_options.xml",
{},
callbackFunctionNames => {
for (let functionName of callbackFunctionNames)
if (global[functionName])
global[functionName]();
resumeGame();
});
}
function toggleTutorial()
{
let tutorialPanel = Engine.GetGUIObjectByName("tutorialPanel");
tutorialPanel.hidden = !tutorialPanel.hidden ||
!Engine.GetGUIObjectByName("tutorialText").caption;
}
-function updateGameSpeedControl()
-{
- Engine.GetGUIObjectByName("gameSpeedButton").hidden = g_IsNetworked;
-
- let player = g_Players[Engine.GetPlayerID()];
- g_GameSpeeds = getGameSpeedChoices(!player || player.state != "active");
-
- let gameSpeed = Engine.GetGUIObjectByName("gameSpeed");
- gameSpeed.list = g_GameSpeeds.Title;
- gameSpeed.list_data = g_GameSpeeds.Speed;
-
- let simRate = Engine.GetSimRate();
-
- let gameSpeedIdx = g_GameSpeeds.Speed.indexOf(+simRate.toFixed(2));
- if (gameSpeedIdx == -1)
- warn("Unknown gamespeed:" + simRate);
-
- gameSpeed.selected = gameSpeedIdx != -1 ? gameSpeedIdx : g_GameSpeeds.Default;
- gameSpeed.onSelectionChange = function() {
- changeGameSpeed(+this.list_data[this.selected]);
- };
-}
-
-function toggleGameSpeed()
-{
- let gameSpeed = Engine.GetGUIObjectByName("gameSpeed");
- gameSpeed.hidden = !gameSpeed.hidden;
-}
-
-function toggleObjectives()
-{
- let open = g_IsObjectivesOpen;
- closeOpenDialogs();
-
- if (!open)
- openObjectives();
-}
-
-function openObjectives()
-{
- g_IsObjectivesOpen = true;
-
- let player = g_Players[Engine.GetPlayerID()];
- let playerState = player && player.state;
- let isActive = !playerState || playerState == "active";
-
- Engine.GetGUIObjectByName("gameDescriptionText").caption = getGameDescription();
-
- let objectivesPlayerstate = Engine.GetGUIObjectByName("objectivesPlayerstate");
- objectivesPlayerstate.hidden = isActive;
- objectivesPlayerstate.caption = g_PlayerStateMessages[playerState] || "";
-
- let gameDescription = Engine.GetGUIObjectByName("gameDescription");
- let gameDescriptionSize = gameDescription.size;
- gameDescriptionSize.top = Engine.GetGUIObjectByName(
- isActive ? "objectivesTitle" : "objectivesPlayerstate").size.bottom;
- gameDescription.size = gameDescriptionSize;
-
- Engine.GetGUIObjectByName("objectivesPanel").hidden = false;
-}
-
-function closeObjectives()
-{
- g_IsObjectivesOpen = false;
- Engine.GetGUIObjectByName("objectivesPanel").hidden = true;
-}
-
/**
* Allows players to see their own summary.
* If they have shared ally vision researched, they are able to see the summary of there allies too.
*/
function openGameSummary()
{
closeOpenDialogs();
- pauseGame();
+ g_PauseControl.implicitPause();
let extendedSimState = Engine.GuiInterfaceCall("GetExtendedSimulationState");
Engine.PushGuiPage(
"page_summary.xml",
{
"sim": {
"mapSettings": g_GameAttributes.settings,
"playerStates": extendedSimState.players.filter((state, player) =>
g_IsObserver || player == 0 || player == g_ViewedPlayer ||
extendedSimState.players[g_ViewedPlayer].hasSharedLos && g_Players[player].isMutualAlly[g_ViewedPlayer]),
"timeElapsed": extendedSimState.timeElapsed
},
"gui": {
"dialog": true,
"isInGame": true
},
"selectedData": g_SummarySelectedData
},
- resumeGameAndSaveSummarySelectedData);
+ data =>
+ {
+ g_SummarySelectedData = data.summarySelectedData;
+ g_PauseControl.implicitResume();
+ });
}
function openStrucTree(page)
{
closeOpenDialogs();
- pauseGame();
+ g_PauseControl.implicitPause();
Engine.PushGuiPage(
page,
{
"civ": g_CivInfo.civ || g_Players[g_ViewedPlayer].civ
// TODO add info about researched techs and unlocked entities
},
storeCivInfoPage);
}
function storeCivInfoPage(data)
{
if (data.nextPage)
Engine.PushGuiPage(
data.nextPage,
{ "civ": data.civ },
storeCivInfoPage);
else
{
g_CivInfo = data;
resumeGame();
}
}
-/**
- * Pause or resume the game.
- *
- * @param explicit - true if the player explicitly wants to pause or resume.
- * If this argument isn't set, a multiplayer game won't be paused and the pause overlay
- * won't be shown in single player.
- */
-function pauseGame(pause = true, explicit = false)
-{
- // The NetServer only supports pausing after all clients finished loading the game.
- if (g_IsNetworked && (!explicit || !g_IsNetworkedActive))
- return;
-
- if (explicit)
- g_Paused = pause;
-
- Engine.SetPaused(g_Paused || pause, !!explicit);
-
- if (g_IsNetworked)
- {
- setClientPauseState(Engine.GetPlayerGUID(), g_Paused);
- return;
- }
-
- updatePauseOverlay();
-}
-
-function resumeGame(explicit = false)
-{
- pauseGame(false, explicit);
-}
-
-function resumeGameAndSaveSummarySelectedData(data)
-{
- g_SummarySelectedData = data.summarySelectedData;
- resumeGame(data.explicitResume);
-}
-
-/**
- * Called when the current player toggles a pause button.
- */
-function togglePause()
-{
- if (!Engine.GetGUIObjectByName("pauseButton").enabled)
- return;
-
- closeOpenDialogs();
-
- pauseGame(!g_Paused, true);
-}
-
-/**
- * Called when a client pauses or resumes in a multiplayer game.
- */
-function setClientPauseState(guid, paused)
-{
- // Update the list of pausing clients.
- let index = g_PausingClients.indexOf(guid);
- if (paused && index == -1)
- g_PausingClients.push(guid);
- else if (!paused && index != -1)
- g_PausingClients.splice(index, 1);
-
- updatePauseOverlay();
-
- Engine.SetPaused(!!g_PausingClients.length, false);
-}
-
-/**
- * Update the pause overlay.
- */
-function updatePauseOverlay()
-{
- Engine.GetGUIObjectByName("pauseButton").caption = g_Paused ? translate("Resume") : translate("Pause");
- Engine.GetGUIObjectByName("resumeMessage").hidden = !g_Paused;
-
- Engine.GetGUIObjectByName("pausedByText").hidden = !g_IsNetworked;
- Engine.GetGUIObjectByName("pausedByText").caption = sprintf(translate("Paused by %(players)s"),
- { "players": g_PausingClients.map(guid => colorizePlayernameByGUID(guid)).join(translateWithContext("Separator for a list of players", ", ")) });
-
- Engine.GetGUIObjectByName("pauseOverlay").hidden = !(g_Paused || g_PausingClients.length);
- Engine.GetGUIObjectByName("pauseOverlay").onPress = g_Paused ? togglePause : function() {};
-}
-
function openManual()
{
closeOpenDialogs();
- pauseGame();
+ g_PauseControl.implicitPause();
Engine.PushGuiPage("page_manual.xml", {}, resumeGame);
}
function closeOpenDialogs()
{
closeMenu();
- closeObjectives();
-
g_Chat.closePage();
g_DiplomacyDialog.close();
+ g_ObjectivesDialog.close();
g_TradeDialog.close();
}
Index: ps/trunk/binaries/data/mods/public/gui/session/menu.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/session/menu.xml (revision 23075)
+++ ps/trunk/binaries/data/mods/public/gui/session/menu.xml (revision 23076)
@@ -1,114 +1,112 @@
Manual
openManual();
Chat
chatMenuButton();
Save
openSave();
Summary
openGameSummary();
Lobby
Show the multiplayer lobby in a dialog window.
lobbyDialogButton();
Options
optionsMenuButton();
- Pause
- togglePause();
-
+ />
Resign
resignMenuButton();
Exit
exitMenuButton();
Index: ps/trunk/binaries/data/mods/public/gui/session/messages.js
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/session/messages.js (revision 23075)
+++ ps/trunk/binaries/data/mods/public/gui/session/messages.js (revision 23076)
@@ -1,693 +1,693 @@
/**
* 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" };
/**
* Handle all netmessage types that can occur.
*/
var g_NetMessageTypes = {
"netstatus": msg => {
handleNetStatusMessage(msg);
},
"netwarn": msg => {
addNetworkWarning(msg);
},
"out-of-sync": msg => {
onNetworkOutOfSync(msg);
},
"players": msg => {
handlePlayerAssignmentsMessage(msg);
},
"paused": msg => {
- setClientPauseState(msg.guid, msg.pause);
+ g_PauseControl.setClientPauseState(msg.guid, msg.pause);
},
"clients-loading": msg => {
handleClientsLoadingMessage(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();
g_DiplomacyColors.OnCeasefireEnded();
},
"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);
}
};
/**
* 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);
}
}
/**
* 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 = leaveGame;
+ 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", ", "));
}
function onNetworkOutOfSync(msg)
{
let txt = [
sprintf(translate("Out-Of-Sync error on turn %(turn)s."), {
"turn": msg.turn
}),
sprintf(translateWithContext("Out-Of-Sync", "Players: %(players)s"), {
"players": msg.players.join(translateWithContext("Separator for a list of players", ", "))
}),
msg.hash == msg.expectedHash ?
translateWithContext("Out-Of-Sync", "Your game state is identical to the hosts game state.") :
translateWithContext("Out-Of-Sync", "Your game state differs from the hosts game state."),
""
];
if (msg.turn > 1 && g_GameAttributes.settings.PlayerData.some(pData => pData && pData.AI))
txt.push(translateWithContext("Out-Of-Sync", "Rejoining Multiplayer games with AIs is not supported yet!"));
else
txt.push(
translateWithContext("Out-Of-Sync", "Ensure all players use the same mods."),
translateWithContext("Out-Of-Sync", 'Click on "Report a Bug" in the main menu to help fix this.'),
sprintf(translateWithContext("Out-Of-Sync", "Replay saved to %(filepath)s"), {
"filepath": escapeText(msg.path_replay)
}),
sprintf(translateWithContext("Out-Of-Sync", "Dumping current state to %(filepath)s"), {
"filepath": escapeText(msg.path_oos_dump)
})
);
messageBox(
600, 280,
txt.join("\n"),
translate("Out of Sync")
);
}
function onReplayOutOfSync(turn, hash, expectedHash)
{
messageBox(
500, 140,
sprintf(translate("Out-Of-Sync error on turn %(turn)s."), {
"turn": turn
}) + "\n" +
// Translation: This is shown if replay is out of sync
translateWithContext("Out-Of-Sync", "The current game state is different from the original game state."),
translate("Out of Sync")
);
}
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);
});
updateGUIObjects();
g_Chat.onUpdatePlayers();
sendLobbyPlayerlistUpdate();
}
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)
{
- setClientPauseState(guid, false);
+ 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;
}
}
- pauseGame();
+ g_PauseControl.implicitPause();
}
Index: ps/trunk/binaries/data/mods/public/gui/session/minimap/MiniMapDiplomacyColorsButton.js
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/session/minimap/MiniMapDiplomacyColorsButton.js (revision 23075)
+++ ps/trunk/binaries/data/mods/public/gui/session/minimap/MiniMapDiplomacyColorsButton.js (revision 23076)
@@ -1,36 +1,40 @@
/**
* The purpose of this class is to exclusively manage the diplomacy colors button within the minimap.
*/
class MiniMapDiplomacyColorsButton
{
constructor(diplomacyColors)
{
this.diplomacyColorsButton = Engine.GetGUIObjectByName("diplomacyColorsButton");
this.diplomacyColorsButton.onPress = diplomacyColors.toggle.bind(diplomacyColors);
- this.diplomacyColors = diplomacyColors;
+ diplomacyColors.registerDiplomacyColorsChangeHandler(this.onDiplomacyColorsChange.bind(this));
+ registerHotkeyChangeHandler(this.onHotkeyChange.bind(this));
}
- update()
+ onHotkeyChange()
{
this.diplomacyColorsButton.tooltip =
colorizeHotkey("%(hotkey)s" + " ", "session.diplomacycolors") +
translate(this.Tooltip);
+ }
+ onDiplomacyColorsChange(enabled)
+ {
this.diplomacyColorsButton.sprite =
"stretched:" +
- (this.diplomacyColors.isEnabled() ? this.SpriteEnabled : this.SpriteDisabled);
+ (enabled ? this.SpriteEnabled : this.SpriteDisabled);
this.diplomacyColorsButton.sprite_over =
"stretched:" +
- (this.diplomacyColors.isEnabled() ? this.SpriteEnabledOver : this.SpriteDisabledOver);
+ (enabled ? this.SpriteEnabledOver : this.SpriteDisabledOver);
}
}
MiniMapDiplomacyColorsButton.prototype.Tooltip = markForTranslation("Toggle Diplomacy Colors");
MiniMapDiplomacyColorsButton.prototype.SpriteEnabled = "session/minimap-diplomacy-on.png";
MiniMapDiplomacyColorsButton.prototype.SpriteDisabled = "session/minimap-diplomacy-off.png";
MiniMapDiplomacyColorsButton.prototype.SpriteEnabledOver = "session/minimap-diplomacy-on-highlight.png";
MiniMapDiplomacyColorsButton.prototype.SpriteDisabledOver = "session/minimap-diplomacy-off-highlight.png";
Index: ps/trunk/binaries/data/mods/public/gui/session/minimap/MiniMapIdleWorkerButton.js
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/session/minimap/MiniMapIdleWorkerButton.js (revision 23075)
+++ ps/trunk/binaries/data/mods/public/gui/session/minimap/MiniMapIdleWorkerButton.js (revision 23076)
@@ -1,32 +1,39 @@
/**
* If the button that this class manages is pressed, an idle unit having one of the given classes is selected.
*/
class MiniMapIdleWorkerButton
{
- constructor(idleClasses)
+ constructor(playerViewControl, idleClasses)
{
this.idleWorkerButton = Engine.GetGUIObjectByName("idleWorkerButton");
this.idleWorkerButton.onPress = this.onPress.bind(this);
this.idleClasses = idleClasses;
+
+ registerHotkeyChangeHandler(this.onHotkeyChange.bind(this));
+ registerSimulationUpdateHandler(this.rebuild.bind(this));
+ playerViewControl.registerViewedPlayerChangeHandler(this.rebuild.bind(this));
}
- update()
+ onHotkeyChange()
{
this.idleWorkerButton.tooltip =
colorizeHotkey("%(hotkey)s" + " ", "selection.idleworker") +
translate(this.Tooltip);
+ }
+ rebuild()
+ {
this.idleWorkerButton.enabled = Engine.GuiInterfaceCall("HasIdleUnits", {
"viewedPlayer": g_ViewedPlayer,
"idleClasses": this.idleClasses,
"excludeUnits": []
});
}
onPress()
{
findIdleUnit(this.idleClasses);
}
}
MiniMapIdleWorkerButton.prototype.Tooltip = markForTranslation("Find idle worker");
Index: ps/trunk/binaries/data/mods/public/gui/session/minimap/MiniMapPanel.js
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/session/minimap/MiniMapPanel.js (revision 23075)
+++ ps/trunk/binaries/data/mods/public/gui/session/minimap/MiniMapPanel.js (revision 23076)
@@ -1,18 +1,12 @@
/**
* This class is concerned with managing the different elements of the minimap panel.
*/
class MiniMapPanel
{
- constructor(diplomacyColors, idleWorkerClasses)
+ constructor(playerViewControl, diplomacyColors, idleWorkerClasses)
{
this.diplomacyColorsButton = new MiniMapDiplomacyColorsButton(diplomacyColors);
- this.idleWorkerButton = new MiniMapIdleWorkerButton(idleWorkerClasses);
+ this.idleWorkerButton = new MiniMapIdleWorkerButton(playerViewControl, idleWorkerClasses);
this.minimap = new Minimap();
}
-
- update()
- {
- this.diplomacyColorsButton.update();
- this.idleWorkerButton.update();
- }
}
Index: ps/trunk/binaries/data/mods/public/gui/session/objectives/ObjectivesDialog.js
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/session/objectives/ObjectivesDialog.js (nonexistent)
+++ ps/trunk/binaries/data/mods/public/gui/session/objectives/ObjectivesDialog.js (revision 23076)
@@ -0,0 +1,59 @@
+class ObjectivesDialog
+{
+ constructor(playerViewControl)
+ {
+ this.gameDescription = Engine.GetGUIObjectByName("gameDescription");
+ this.objectivesPlayerstate = Engine.GetGUIObjectByName("objectivesPlayerstate");
+ this.objectivesPanel = Engine.GetGUIObjectByName("objectivesPanel");
+ this.objectivesTitle = Engine.GetGUIObjectByName("objectivesTitle");
+
+ // TODO: atlas should support this
+ if (g_GameAttributes.settings)
+ Engine.GetGUIObjectByName("gameDescriptionText").caption = getGameDescription();
+
+ Engine.GetGUIObjectByName("closeObjectives").onPress = this.close.bind(this);
+
+ registerPlayersInitHandler(this.rebuild.bind(this));
+ registerPlayersFinishedHandler(this.rebuild.bind(this));
+ playerViewControl.registerPlayerIDChangeHandler(this.rebuild.bind(this));
+ }
+
+ open()
+ {
+ this.objectivesPanel.hidden = false;
+ }
+
+ close()
+ {
+ this.objectivesPanel.hidden = true;
+ }
+
+ isOpen()
+ {
+ return !this.objectivesPanel.hidden;
+ }
+
+ toggle()
+ {
+ let open = this.isOpen();
+
+ closeOpenDialogs();
+
+ if (!open)
+ this.open();
+ }
+
+ rebuild()
+ {
+ let player = g_Players[Engine.GetPlayerID()];
+ let playerState = player && player.state;
+ let isActive = !playerState || playerState == "active";
+
+ this.objectivesPlayerstate.hidden = isActive;
+ this.objectivesPlayerstate.caption = g_PlayerStateMessages[playerState] || "";
+
+ let size = this.gameDescription.size;
+ size.top = (isActive ? this.objectivesTitle : this.objectivesPlayerstate).size.bottom;
+ this.gameDescription.size = size;
+ }
+}
Property changes on: ps/trunk/binaries/data/mods/public/gui/session/objectives/ObjectivesDialog.js
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Index: ps/trunk/binaries/data/mods/public/gui/session/objectives/ObjectivesDialog.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/session/objectives/ObjectivesDialog.xml (nonexistent)
+++ ps/trunk/binaries/data/mods/public/gui/session/objectives/ObjectivesDialog.xml (revision 23076)
@@ -0,0 +1,47 @@
+
+
+
+ Objectives
+
+
+
+
+
+
+
+
+
+ Close
+
+
+
+
Property changes on: ps/trunk/binaries/data/mods/public/gui/session/objectives/ObjectivesDialog.xml
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Index: ps/trunk/binaries/data/mods/public/gui/session/selection_panels.js
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/session/selection_panels.js (revision 23075)
+++ ps/trunk/binaries/data/mods/public/gui/session/selection_panels.js (revision 23076)
@@ -1,1188 +1,1188 @@
/**
* Contains the layout and button settings per selection panel
*
* getItems returns a list of basic items used to fill the panel.
* This method is obligated. If the items list is empty, the panel
* won't be rendered.
*
* Then there's a loop over all items provided. In the loop,
* the item and some other standard data is added to a data object.
*
* The standard data is
* {
* "i": index
* "item": item coming from the getItems function
* "playerState": playerState
* "unitEntStates": states of the selected entities
* "rowLength": rowLength
* "numberOfItems": number of items that will be processed
* "button": gui Button object
* "icon": gui Icon object
* "guiSelection": gui button Selection overlay
* "countDisplay": gui caption space
* }
*
* Then for every data object, the setupButton function is called which
* sets the view and handlers of the button.
*/
// Cache some formation info
// Available formations per player
var g_AvailableFormations = new Map();
var g_FormationsInfo = new Map();
var g_SelectionPanels = {};
var g_SelectionPanelBarterButtonManager;
g_SelectionPanels.Alert = {
"getMaxNumberOfItems": function()
{
return 2;
},
"getItems": function(unitEntStates)
{
return unitEntStates.some(state => !!state.alertRaiser) ? ["raise", "end"] : [];
},
"setupButton": function(data)
{
data.button.onPress = function() {
switch (data.item)
{
case "raise":
raiseAlert();
return;
case "end":
endOfAlert();
return;
}
};
switch (data.item)
{
case "raise":
data.icon.sprite = "stretched:session/icons/bell_level1.png";
data.button.tooltip = translate("Raise an alert!");
break;
case "end":
data.button.tooltip = translate("End of alert.");
data.icon.sprite = "stretched:session/icons/bell_level0.png";
break;
}
data.button.enabled = controlsPlayer(data.player);
setPanelObjectPosition(data.button, this.getMaxNumberOfItems() - data.i, data.rowLength);
return true;
}
};
g_SelectionPanels.Barter = {
"getMaxNumberOfItems": function()
{
return 4;
},
"rowLength": 4,
"conflictsWith": ["Garrison"],
"getItems": function(unitEntStates)
{
// If more than `rowLength` resources, don't display icons.
if (unitEntStates.every(state => !state.isBarterMarket) || g_ResourceData.GetBarterableCodes().length > this.rowLength)
return [];
return g_ResourceData.GetBarterableCodes();
},
"setupButton": function(data)
{
if (g_SelectionPanelBarterButtonManager)
{
g_SelectionPanelBarterButtonManager.setViewedPlayer(data.player);
g_SelectionPanelBarterButtonManager.update();
}
return true;
}
};
g_SelectionPanels.Command = {
"getMaxNumberOfItems": function()
{
return 6;
},
"getItems": function(unitEntStates)
{
let commands = [];
for (let command in g_EntityCommands)
{
let info = g_EntityCommands[command].getInfo(unitEntStates);
if (info)
{
info.name = command;
commands.push(info);
}
}
return commands;
},
"setupButton": function(data)
{
data.button.tooltip = data.item.tooltip;
data.button.onPress = function() {
if (data.item.callback)
data.item.callback(data.item);
else
performCommand(data.unitEntStates, data.item.name);
};
data.countDisplay.caption = data.item.count || "";
data.button.enabled =
g_IsObserver && data.item.name == "focus-rally" ||
controlsPlayer(data.player) && (data.item.name != "delete" ||
data.unitEntStates.some(state => !isUndeletable(state)));
data.icon.sprite = "stretched:session/icons/" + data.item.icon;
let size = data.button.size;
// relative to the center ( = 50%)
size.rleft = 50;
size.rright = 50;
// offset from the center calculation, count on square buttons, so size.bottom is the width too
size.left = (data.i - data.numberOfItems / 2) * (size.bottom + 1);
size.right = size.left + size.bottom;
data.button.size = size;
return true;
}
};
g_SelectionPanels.AllyCommand = {
"getMaxNumberOfItems": function()
{
return 2;
},
"conflictsWith": ["Command"],
"getItems": function(unitEntStates)
{
let commands = [];
for (let command in g_AllyEntityCommands)
for (let state of unitEntStates)
{
let info = g_AllyEntityCommands[command].getInfo(state);
if (info)
{
info.name = command;
commands.push(info);
break;
}
}
return commands;
},
"setupButton": function(data)
{
data.button.tooltip = data.item.tooltip;
data.button.onPress = function() {
if (data.item.callback)
data.item.callback(data.item);
else
performAllyCommand(data.unitEntStates[0].id, data.item.name);
};
data.countDisplay.caption = data.item.count || "";
data.button.enabled = !!data.item.count;
let grayscale = data.button.enabled ? "" : "grayscale:";
data.icon.sprite = "stretched:" + grayscale + "session/icons/" + data.item.icon;
let size = data.button.size;
// relative to the center ( = 50%)
size.rleft = 50;
size.rright = 50;
// offset from the center calculation, count on square buttons, so size.bottom is the width too
size.left = (data.i - data.numberOfItems / 2) * (size.bottom + 1);
size.right = size.left + size.bottom;
data.button.size = size;
return true;
}
};
g_SelectionPanels.Construction = {
"getMaxNumberOfItems": function()
{
return 24 - getNumberOfRightPanelButtons();
},
"getItems": function()
{
return getAllBuildableEntitiesFromSelection();
},
"setupButton": function(data)
{
let template = GetTemplateData(data.item);
if (!template)
return false;
let technologyEnabled = Engine.GuiInterfaceCall("IsTechnologyResearched", {
"tech": template.requiredTechnology,
"player": data.player
});
let neededResources;
if (template.cost)
neededResources = Engine.GuiInterfaceCall("GetNeededResources", {
"cost": multiplyEntityCosts(template, 1),
"player": data.player
});
data.button.onPress = function() { startBuildingPlacement(data.item, data.playerState); };
data.button.onPressRight = function() { showTemplateDetails(data.item); };
let tooltips = [
getEntityNamesFormatted,
getVisibleEntityClassesFormatted,
getAurasTooltip,
getEntityTooltip,
getEntityCostTooltip,
getGarrisonTooltip,
getPopulationBonusTooltip,
showTemplateViewerOnRightClickTooltip
].map(func => func(template));
let limits = getEntityLimitAndCount(data.playerState, data.item);
tooltips.push(
formatLimitString(limits.entLimit, limits.entCount, limits.entLimitChangers),
getRequiredTechnologyTooltip(technologyEnabled, template.requiredTechnology, GetSimState().players[data.player].civ),
getNeededResourcesTooltip(neededResources));
data.button.tooltip = tooltips.filter(tip => tip).join("\n");
let modifier = "";
if (!technologyEnabled || limits.canBeAddedCount == 0)
{
data.button.enabled = false;
modifier += "color:0 0 0 127:grayscale:";
}
else if (neededResources)
{
data.button.enabled = false;
modifier += resourcesToAlphaMask(neededResources) + ":";
}
else
data.button.enabled = controlsPlayer(data.player);
if (template.icon)
data.icon.sprite = modifier + "stretched:session/portraits/" + template.icon;
setPanelObjectPosition(data.button, data.i + getNumberOfRightPanelButtons(), data.rowLength);
return true;
}
};
g_SelectionPanels.Formation = {
"getMaxNumberOfItems": function()
{
return 16;
},
"rowLength": 4,
"conflictsWith": ["Garrison"],
"getItems": function(unitEntStates)
{
if (unitEntStates.some(state => !hasClass(state, "Unit")))
return [];
if (!g_AvailableFormations.has(unitEntStates[0].player))
g_AvailableFormations.set(unitEntStates[0].player, Engine.GuiInterfaceCall("GetAvailableFormations", unitEntStates[0].player));
let availableFormations = g_AvailableFormations.get(unitEntStates[0].player);
// Hide the panel if all formations are disabled
if (availableFormations.some(formation => canMoveSelectionIntoFormation(formation)))
return availableFormations;
return [];
},
"setupButton": function(data)
{
if (!g_FormationsInfo.has(data.item))
g_FormationsInfo.set(data.item, Engine.GuiInterfaceCall("GetFormationInfoFromTemplate", { "templateName": data.item }));
let formationInfo = g_FormationsInfo.get(data.item);
let formationOk = canMoveSelectionIntoFormation(data.item);
let unitIds = data.unitEntStates.map(state => state.id);
let formationSelected = Engine.GuiInterfaceCall("IsFormationSelected", {
"ents": unitIds,
"formationTemplate": data.item
});
data.button.onPress = function() {
performFormation(unitIds, data.item);
};
let tooltip = translate(formationInfo.name);
if (!formationOk && formationInfo.tooltip)
tooltip += "\n" + coloredText(translate(formationInfo.tooltip), "red");
data.button.tooltip = tooltip;
data.button.enabled = formationOk && controlsPlayer(data.player);
let grayscale = formationOk ? "" : "grayscale:";
data.guiSelection.hidden = !formationSelected;
data.icon.sprite = "stretched:" + grayscale + "session/icons/" + formationInfo.icon;
setPanelObjectPosition(data.button, data.i, data.rowLength);
return true;
}
};
g_SelectionPanels.Garrison = {
"getMaxNumberOfItems": function()
{
return 12;
},
"rowLength": 4,
"conflictsWith": ["Barter"],
"getItems": function(unitEntStates)
{
if (unitEntStates.every(state => !state.garrisonHolder))
return [];
let groups = new EntityGroups();
for (let state of unitEntStates)
if (state.garrisonHolder)
groups.add(state.garrisonHolder.entities);
return groups.getEntsGrouped();
},
"setupButton": function(data)
{
let entState = GetEntityState(data.item.ents[0]);
let template = GetTemplateData(entState.template);
if (!template)
return false;
data.button.onPress = function() {
unloadTemplate(template.selectionGroupName || entState.template, entState.player);
};
data.countDisplay.caption = data.item.ents.length || "";
let canUngarrison =
g_ViewedPlayer == data.player ||
g_ViewedPlayer == entState.player;
data.button.enabled = canUngarrison && controlsPlayer(g_ViewedPlayer);
data.button.tooltip = (canUngarrison || g_IsObserver ?
sprintf(translate("Unload %(name)s"), { "name": getEntityNames(template) }) + "\n" +
translate("Single-click to unload 1. Shift-click to unload all of this type.") :
getEntityNames(template)) + "\n" +
sprintf(translate("Player: %(playername)s"), {
"playername": g_Players[entState.player].name
});
data.guiSelection.sprite = "color:" + g_DiplomacyColors.getPlayerColor(entState.player, 160);
data.button.sprite_disabled = data.button.sprite;
// Selection panel buttons only appear disabled if they
// also appear disabled to the owner of the building.
data.icon.sprite =
(canUngarrison || g_IsObserver ? "" : "grayscale:") +
"stretched:session/portraits/" + template.icon;
setPanelObjectPosition(data.button, data.i, data.rowLength);
return true;
}
};
g_SelectionPanels.Gate = {
"getMaxNumberOfItems": function()
{
return 24 - getNumberOfRightPanelButtons();
},
"getItems": function(unitEntStates)
{
let hideLocked = unitEntStates.every(state => !state.gate || !state.gate.locked);
let hideUnlocked = unitEntStates.every(state => !state.gate || state.gate.locked);
if (hideLocked && hideUnlocked)
return [];
return [
{
"hidden": hideLocked,
"tooltip": translate("Lock Gate"),
"icon": "session/icons/lock_locked.png",
"locked": true
},
{
"hidden": hideUnlocked,
"tooltip": translate("Unlock Gate"),
"icon": "session/icons/lock_unlocked.png",
"locked": false
}
];
},
"setupButton": function(data)
{
data.button.onPress = function() { lockGate(data.item.locked); };
data.button.tooltip = data.item.tooltip;
data.button.enabled = controlsPlayer(data.player);
data.guiSelection.hidden = data.item.hidden;
data.icon.sprite = "stretched:" + data.item.icon;
setPanelObjectPosition(data.button, data.i + getNumberOfRightPanelButtons(), data.rowLength);
return true;
}
};
g_SelectionPanels.Pack = {
"getMaxNumberOfItems": function()
{
return 24 - getNumberOfRightPanelButtons();
},
"getItems": function(unitEntStates)
{
let checks = {};
for (let state of unitEntStates)
{
if (!state.pack)
continue;
if (state.pack.progress == 0)
{
if (state.pack.packed)
checks.unpackButton = true;
else
checks.packButton = true;
}
else if (state.pack.packed)
checks.unpackCancelButton = true;
else
checks.packCancelButton = true;
}
let items = [];
if (checks.packButton)
items.push({
"packing": false,
"packed": false,
"tooltip": translate("Pack"),
"callback": function() { packUnit(true); }
});
if (checks.unpackButton)
items.push({
"packing": false,
"packed": true,
"tooltip": translate("Unpack"),
"callback": function() { packUnit(false); }
});
if (checks.packCancelButton)
items.push({
"packing": true,
"packed": false,
"tooltip": translate("Cancel Packing"),
"callback": function() { cancelPackUnit(true); }
});
if (checks.unpackCancelButton)
items.push({
"packing": true,
"packed": true,
"tooltip": translate("Cancel Unpacking"),
"callback": function() { cancelPackUnit(false); }
});
return items;
},
"setupButton": function(data)
{
data.button.onPress = function() {data.item.callback(data.item); };
data.button.tooltip = data.item.tooltip;
if (data.item.packing)
data.icon.sprite = "stretched:session/icons/cancel.png";
else if (data.item.packed)
data.icon.sprite = "stretched:session/icons/unpack.png";
else
data.icon.sprite = "stretched:session/icons/pack.png";
data.button.enabled = controlsPlayer(data.player);
setPanelObjectPosition(data.button, data.i + getNumberOfRightPanelButtons(), data.rowLength);
return true;
}
};
g_SelectionPanels.Queue = {
"getMaxNumberOfItems": function()
{
return 16;
},
/**
* Returns a list of all items in the productionqueue of the selection
* The first entry of every entity's production queue will come before
* the second entry of every entity's production queue
*/
"getItems": function(unitEntStates)
{
let queue = [];
let foundNew = true;
for (let i = 0; foundNew; ++i)
{
foundNew = false;
for (let state of unitEntStates)
{
if (!state.production || !state.production.queue[i])
continue;
queue.push({
"producingEnt": state.id,
"queuedItem": state.production.queue[i]
});
foundNew = true;
}
}
return queue;
},
"resizePanel": function(numberOfItems, rowLength)
{
let numRows = Math.ceil(numberOfItems / rowLength);
let panel = Engine.GetGUIObjectByName("unitQueuePanel");
let size = panel.size;
let buttonSize = Engine.GetGUIObjectByName("unitQueueButton[0]").size.bottom;
let margin = 4;
size.top = size.bottom - numRows * buttonSize - (numRows + 2) * margin;
panel.size = size;
},
"setupButton": function(data)
{
let queuedItem = data.item.queuedItem;
// Differentiate between units and techs
let template;
if (queuedItem.unitTemplate)
template = GetTemplateData(queuedItem.unitTemplate);
else if (queuedItem.technologyTemplate)
template = GetTechnologyData(queuedItem.technologyTemplate, GetSimState().players[data.player].civ);
else
{
warning("Unknown production queue template " + uneval(queuedItem));
return false;
}
data.button.onPress = function() { removeFromProductionQueue(data.item.producingEnt, queuedItem.id); };
let tooltip = getEntityNames(template);
if (queuedItem.neededSlots)
{
tooltip += "\n" + coloredText(translate("Insufficient population capacity:"), "red");
tooltip += "\n" + sprintf(translate("%(population)s %(neededSlots)s"), {
"population": resourceIcon("population"),
"neededSlots": queuedItem.neededSlots
});
}
data.button.tooltip = tooltip;
data.countDisplay.caption = queuedItem.count > 1 ? queuedItem.count : "";
// Show the time remaining to finish the first item
if (data.i == 0)
Engine.GetGUIObjectByName("queueTimeRemaining").caption =
Engine.FormatMillisecondsIntoDateStringGMT(queuedItem.timeRemaining, translateWithContext("countdown format", "m:ss"));
let guiObject = Engine.GetGUIObjectByName("unitQueueProgressSlider[" + data.i + "]");
let size = guiObject.size;
// Buttons are assumed to be square, so left/right offsets can be used for top/bottom.
size.top = size.left + Math.round(queuedItem.progress * (size.right - size.left));
guiObject.size = size;
if (template.icon)
data.icon.sprite = "stretched:session/portraits/" + template.icon;
data.button.enabled = controlsPlayer(data.player);
setPanelObjectPosition(data.button, data.i, data.rowLength);
return true;
}
};
g_SelectionPanels.Research = {
"getMaxNumberOfItems": function()
{
return 8;
},
"getItems": function(unitEntStates)
{
let ret = [];
if (unitEntStates.length == 1)
return !unitEntStates[0].production || !unitEntStates[0].production.technologies ? ret :
unitEntStates[0].production.technologies.map(tech => ({
"tech": tech,
"techCostMultiplier": unitEntStates[0].production.techCostMultiplier,
"researchFacilityId": unitEntStates[0].id
}));
for (let state of unitEntStates)
{
if (!state.production || !state.production.technologies)
continue;
// Remove the techs we already have in ret (with the same name and techCostMultiplier)
let filteredTechs = state.production.technologies.filter(
tech => tech != null && !ret.some(
item =>
(item.tech == tech ||
item.tech.pair &&
tech.pair &&
item.tech.bottom == tech.bottom &&
item.tech.top == tech.top) &&
Object.keys(item.techCostMultiplier).every(
k => item.techCostMultiplier[k] == state.production.techCostMultiplier[k])
));
if (filteredTechs.length + ret.length <= this.getMaxNumberOfItems() &&
getNumberOfRightPanelButtons() <= this.getMaxNumberOfItems() * (filteredTechs.some(tech => !!tech.pair) ? 1 : 2))
ret = ret.concat(filteredTechs.map(tech => ({
"tech": tech,
"techCostMultiplier": state.production.techCostMultiplier,
"researchFacilityId": state.id
})));
}
return ret;
},
"hideItem": function(i, rowLength) // Called when no item is found
{
Engine.GetGUIObjectByName("unitResearchButton[" + i + "]").hidden = true;
// We also remove the paired tech and the pair symbol
Engine.GetGUIObjectByName("unitResearchButton[" + (i + rowLength) + "]").hidden = true;
Engine.GetGUIObjectByName("unitResearchPair[" + i + "]").hidden = true;
},
"setupButton": function(data)
{
if (!data.item.tech)
{
g_SelectionPanels.Research.hideItem(data.i, data.rowLength);
return false;
}
// Start position (start at the bottom)
let position = data.i + data.rowLength;
// Only show the top button for pairs
if (!data.item.tech.pair)
Engine.GetGUIObjectByName("unitResearchButton[" + data.i + "]").hidden = true;
// Set up the tech connector
let pair = Engine.GetGUIObjectByName("unitResearchPair[" + data.i + "]");
pair.hidden = data.item.tech.pair == null;
setPanelObjectPosition(pair, data.i, data.rowLength);
// Handle one or two techs (tech pair)
let player = data.player;
let playerState = GetSimState().players[player];
for (let tech of data.item.tech.pair ? [data.item.tech.bottom, data.item.tech.top] : [data.item.tech])
{
// Don't change the object returned by GetTechnologyData
let template = clone(GetTechnologyData(tech, playerState.civ));
if (!template)
return false;
for (let res in template.cost)
template.cost[res] *= data.item.techCostMultiplier[res];
let neededResources = Engine.GuiInterfaceCall("GetNeededResources", {
"cost": template.cost,
"player": player
});
let requirementsPassed = Engine.GuiInterfaceCall("CheckTechnologyRequirements", {
"tech": tech,
"player": player
});
let button = Engine.GetGUIObjectByName("unitResearchButton[" + position + "]");
let icon = Engine.GetGUIObjectByName("unitResearchIcon[" + position + "]");
let tooltips = [
getEntityNamesFormatted,
getEntityTooltip,
getEntityCostTooltip,
showTemplateViewerOnRightClickTooltip
].map(func => func(template));
if (!requirementsPassed)
{
let tip = template.requirementsTooltip;
let reqs = template.reqs;
for (let req of reqs)
{
if (!req.entities)
continue;
let entityCounts = [];
for (let entity of req.entities)
{
let current = 0;
switch (entity.check)
{
case "count":
current = playerState.classCounts[entity.class] || 0;
break;
case "variants":
current = playerState.typeCountsByClass[entity.class] ?
Object.keys(playerState.typeCountsByClass[entity.class]).length : 0;
break;
}
let remaining = entity.number - current;
if (remaining < 1)
continue;
entityCounts.push(sprintf(translatePlural("%(number)s entity of class %(class)s", "%(number)s entities of class %(class)s", remaining), {
"number": remaining,
"class": entity.class
}));
}
tip += " " + sprintf(translate("Remaining: %(entityCounts)s"), {
"entityCounts": entityCounts.join(translateWithContext("Separator for a list of entity counts", ", "))
});
}
tooltips.push(tip);
}
tooltips.push(getNeededResourcesTooltip(neededResources));
button.tooltip = tooltips.filter(tip => tip).join("\n");
button.onPress = (t => function() {
addResearchToQueue(data.item.researchFacilityId, t);
})(tech);
button.onPressRight = (t => function () {
showTemplateDetails(
t,
GetTemplateData(data.unitEntStates.find(state => state.id == data.item.researchFacilityId).template).nativeCiv);
})(tech);
if (data.item.tech.pair)
{
// On mouse enter, show a cross over the other icon
let unchosenIcon = Engine.GetGUIObjectByName("unitResearchUnchosenIcon[" + (position + data.rowLength) % (2 * data.rowLength) + "]");
button.onMouseEnter = function() {
unchosenIcon.hidden = false;
};
button.onMouseLeave = function() {
unchosenIcon.hidden = true;
};
}
button.hidden = false;
let modifier = "";
if (!requirementsPassed)
{
button.enabled = false;
modifier += "color:0 0 0 127:grayscale:";
}
else if (neededResources)
{
button.enabled = false;
modifier += resourcesToAlphaMask(neededResources) + ":";
}
else
button.enabled = controlsPlayer(data.player);
if (template.icon)
icon.sprite = modifier + "stretched:session/portraits/" + template.icon;
setPanelObjectPosition(button, position, data.rowLength);
// Prepare to handle the top button (if any)
position -= data.rowLength;
}
return true;
}
};
g_SelectionPanels.Selection = {
"getMaxNumberOfItems": function()
{
return 16;
},
"rowLength": 4,
"getItems": function(unitEntStates)
{
if (unitEntStates.length < 2)
return [];
return g_Selection.groups.getEntsGrouped();
},
"setupButton": function(data)
{
let entState = GetEntityState(data.item.ents[0]);
let template = GetTemplateData(entState.template);
if (!template)
return false;
for (let ent of data.item.ents)
{
let state = GetEntityState(ent);
if (state.resourceCarrying && state.resourceCarrying.length !== 0)
{
if (!data.carried)
data.carried = {};
let carrying = state.resourceCarrying[0];
if (data.carried[carrying.type])
data.carried[carrying.type] += carrying.amount;
else
data.carried[carrying.type] = carrying.amount;
}
if (state.trader && state.trader.goods && state.trader.goods.amount)
{
if (!data.carried)
data.carried = {};
let amount = state.trader.goods.amount;
let type = state.trader.goods.type;
let totalGain = amount.traderGain;
if (amount.market1Gain)
totalGain += amount.market1Gain;
if (amount.market2Gain)
totalGain += amount.market2Gain;
if (data.carried[type])
data.carried[type] += totalGain;
else
data.carried[type] = totalGain;
}
}
let unitOwner = GetEntityState(data.item.ents[0]).player;
let tooltip = getEntityNames(template);
if (data.carried)
tooltip += "\n" + Object.keys(data.carried).map(res =>
resourceIcon(res) + data.carried[res]
).join(" ");
if (g_IsObserver)
tooltip += "\n" + sprintf(translate("Player: %(playername)s"), {
"playername": g_Players[unitOwner].name
});
data.button.tooltip = tooltip;
data.guiSelection.sprite = "color:" + g_DiplomacyColors.getPlayerColor(unitOwner, 160);
data.guiSelection.hidden = !g_IsObserver;
data.countDisplay.caption = data.item.ents.length || "";
data.button.onPress = function() { changePrimarySelectionGroup(data.item.key, false); };
data.button.onPressRight = function() { changePrimarySelectionGroup(data.item.key, true); };
if (template.icon)
data.icon.sprite = "stretched:session/portraits/" + template.icon;
setPanelObjectPosition(data.button, data.i, data.rowLength);
return true;
}
};
g_SelectionPanels.Stance = {
"getMaxNumberOfItems": function()
{
return 5;
},
"getItems": function(unitEntStates)
{
if (unitEntStates.some(state => !state.unitAI || !hasClass(state, "Unit") || hasClass(state, "Animal")))
return [];
return unitEntStates[0].unitAI.selectableStances;
},
"setupButton": function(data)
{
let unitIds = data.unitEntStates.map(state => state.id);
data.button.onPress = function() { performStance(unitIds, data.item); };
data.button.tooltip = getStanceDisplayName(data.item) + "\n" +
"[font=\"sans-13\"]" + getStanceTooltip(data.item) + "[/font]";
data.guiSelection.hidden = !Engine.GuiInterfaceCall("IsStanceSelected", {
"ents": unitIds,
"stance": data.item
});
data.icon.sprite = "stretched:session/icons/stances/" + data.item + ".png";
data.button.enabled = controlsPlayer(data.player);
setPanelObjectPosition(data.button, data.i, data.rowLength);
return true;
}
};
g_SelectionPanels.Training = {
"getMaxNumberOfItems": function()
{
return 24 - getNumberOfRightPanelButtons();
},
"getItems": function()
{
return getAllTrainableEntitiesFromSelection();
},
"setupButton": function(data)
{
let template = GetTemplateData(data.item);
if (!template)
return false;
let technologyEnabled = Engine.GuiInterfaceCall("IsTechnologyResearched", {
"tech": template.requiredTechnology,
"player": data.player
});
let unitIds = data.unitEntStates.map(status => status.id);
let [buildingsCountToTrainFullBatch, fullBatchSize, remainderBatch] =
getTrainingStatus(unitIds, data.item, data.playerState);
let trainNum = buildingsCountToTrainFullBatch * fullBatchSize + remainderBatch;
let neededResources;
if (template.cost)
neededResources = Engine.GuiInterfaceCall("GetNeededResources", {
"cost": multiplyEntityCosts(template, trainNum),
"player": data.player
});
data.button.onPress = function() {
if (!neededResources)
addTrainingToQueue(unitIds, data.item, data.playerState);
};
data.button.onPressRight = function() {
showTemplateDetails(data.item);
};
data.countDisplay.caption = trainNum > 1 ? trainNum : "";
let tooltips = [
"[font=\"sans-bold-16\"]" +
colorizeHotkey("%(hotkey)s", "session.queueunit." + (data.i + 1)) +
"[/font]" + " " + getEntityNamesFormatted(template),
getVisibleEntityClassesFormatted(template),
getAurasTooltip(template),
getEntityTooltip(template),
getEntityCostTooltip(template, unitIds[0], buildingsCountToTrainFullBatch, fullBatchSize, remainderBatch)
];
let limits = getEntityLimitAndCount(data.playerState, data.item);
tooltips.push(formatLimitString(limits.entLimit, limits.entCount, limits.entLimitChangers));
if (Engine.ConfigDB_GetValue("user", "showdetailedtooltips") === "true")
tooltips = tooltips.concat([
getHealthTooltip,
getAttackTooltip,
getSplashDamageTooltip,
getHealerTooltip,
getArmorTooltip,
getGarrisonTooltip,
getProjectilesTooltip,
getSpeedTooltip
].map(func => func(template)));
tooltips.push(showTemplateViewerOnRightClickTooltip());
tooltips.push(
formatBatchTrainingString(buildingsCountToTrainFullBatch, fullBatchSize, remainderBatch),
getRequiredTechnologyTooltip(technologyEnabled, template.requiredTechnology, GetSimState().players[data.player].civ),
getNeededResourcesTooltip(neededResources));
data.button.tooltip = tooltips.filter(tip => tip).join("\n");
let modifier = "";
if (!technologyEnabled || limits.canBeAddedCount == 0)
{
data.button.enabled = false;
modifier = "color:0 0 0 127:grayscale:";
}
else
{
data.button.enabled = controlsPlayer(data.player);
if (neededResources)
modifier = resourcesToAlphaMask(neededResources) + ":";
}
if (template.icon)
data.icon.sprite = modifier + "stretched:session/portraits/" + template.icon;
let index = data.i + getNumberOfRightPanelButtons();
setPanelObjectPosition(data.button, index, data.rowLength);
return true;
}
};
g_SelectionPanels.Upgrade = {
"getMaxNumberOfItems": function()
{
return 24 - getNumberOfRightPanelButtons();
},
"getItems": function(unitEntStates)
{
// Interface becomes complicated with multiple different units and this is meant per-entity, so prevent it if the selection has multiple different units.
if (unitEntStates.some(state => state.template != unitEntStates[0].template))
return false;
return unitEntStates[0].upgrade && unitEntStates[0].upgrade.upgrades;
},
"setupButton": function(data)
{
let template = GetTemplateData(data.item.entity);
if (!template)
return false;
let technologyEnabled = true;
if (data.item.requiredTechnology)
technologyEnabled = Engine.GuiInterfaceCall("IsTechnologyResearched", {
"tech": data.item.requiredTechnology,
"player": data.player
});
let neededResources = data.item.cost && Engine.GuiInterfaceCall("GetNeededResources", {
"cost": multiplyEntityCosts(data.item, data.unitEntStates.length),
"player": data.player
});
let limits = getEntityLimitAndCount(data.playerState, data.item.entity);
let progress = data.unitEntStates[0].upgrade.progress || 0;
let isUpgrading = data.unitEntStates[0].upgrade.template == data.item.entity;
let tooltip;
if (!progress)
{
let tooltips = [];
if (data.item.tooltip)
tooltips.push(sprintf(translate("Upgrade to %(name)s. %(tooltip)s"), {
"name": template.name.generic,
"tooltip": translate(data.item.tooltip)
}));
else
tooltips.push(sprintf(translate("Upgrade to %(name)s."), {
"name": template.name.generic
}));
tooltips.push(
getEntityCostComponentsTooltipString(data.item, undefined, data.unitEntStates.length),
formatLimitString(limits.entLimit, limits.entCount, limits.entLimitChangers),
getRequiredTechnologyTooltip(technologyEnabled, data.item.requiredTechnology, GetSimState().players[data.player].civ),
getNeededResourcesTooltip(neededResources),
showTemplateViewerOnRightClickTooltip());
tooltip = tooltips.filter(tip => tip).join("\n");
data.button.onPress = function() { upgradeEntity(data.item.entity); };
}
else if (isUpgrading)
{
tooltip = translate("Cancel Upgrading");
data.button.onPress = function() { cancelUpgradeEntity(); };
}
else
{
tooltip = translate("Cannot upgrade when the entity is already upgrading.");
data.button.onPress = function() {};
}
data.button.enabled = controlsPlayer(data.player);
data.button.tooltip = tooltip;
data.button.onPressRight = function() {
showTemplateDetails(data.item.entity);
};
let modifier = "";
if (!isUpgrading)
if (progress || !technologyEnabled || limits.canBeAddedCount == 0 &&
!hasSameRestrictionCategory(data.item.entity, data.unitEntStates[0].template))
{
data.button.enabled = false;
modifier = "color:0 0 0 127:grayscale:";
}
else if (neededResources)
{
data.button.enabled = false;
modifier = resourcesToAlphaMask(neededResources) + ":";
}
data.icon.sprite = modifier + "stretched:session/" +
(data.item.icon || "portraits/" + template.icon);
data.countDisplay.caption = data.unitEntStates.length > 1 ? data.unitEntStates.length : "";
let progressOverlay = Engine.GetGUIObjectByName("unitUpgradeProgressSlider[" + data.i + "]");
if (isUpgrading)
{
let size = progressOverlay.size;
size.top = size.left + Math.round(progress * (size.right - size.left));
progressOverlay.size = size;
}
progressOverlay.hidden = !isUpgrading;
setPanelObjectPosition(data.button, data.i + getNumberOfRightPanelButtons(), data.rowLength);
return true;
}
};
function initSelectionPanels()
{
let unitBarterPanel = Engine.GetGUIObjectByName("unitBarterPanel");
if (BarterButtonManager.IsAvailable(unitBarterPanel))
g_SelectionPanelBarterButtonManager = new BarterButtonManager(unitBarterPanel);
}
/**
* Pauses game and opens the template details viewer for a selected entity or technology.
*
* Technologies don't have a set civ, so we pass along the native civ of
* the template of the entity that's researching it.
*
* @param {string} [civCode] - The template name of the entity that researches the selected technology.
*/
function showTemplateDetails(templateName, civCode)
{
- pauseGame();
+ g_PauseControl.implicitPause();
Engine.PushGuiPage(
"page_viewer.xml",
{
"templateName": templateName,
"civ": civCode
},
resumeGame);
}
/**
* If two panels need the same space, so they collide,
* the one appearing first in the order is rendered.
*
* Note that the panel needs to appear in the list to get rendered.
*/
let g_PanelsOrder = [
// LEFT PANE
"Barter", // Must always be visible on markets
"Garrison", // More important than Formation, as you want to see the garrisoned units in ships
"Alert",
"Formation",
"Stance", // Normal together with formation
// RIGHT PANE
"Gate", // Must always be shown on gates
"Pack", // Must always be shown on packable entities
"Upgrade", // Must always be shown on upgradable entities
"Training",
"Construction",
"Research", // Normal together with training
// UNIQUE PANES (importance doesn't matter)
"Command",
"AllyCommand",
"Queue",
"Selection",
];
Index: ps/trunk/binaries/data/mods/public/gui/session/session.js
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/session/session.js (revision 23075)
+++ ps/trunk/binaries/data/mods/public/gui/session/session.js (revision 23076)
@@ -1,1619 +1,1415 @@
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_DiplomacyButton;
var g_DiplomacyColors;
var g_DiplomacyDialog;
+var g_GameSpeedControl;
var g_MiniMapPanel;
+var g_ObjectivesDialog;
+var g_PauseControl;
+var g_PauseOverlay;
+var g_PlayerViewControl;
var g_TradeDialog;
-var g_TradeDialogButton;
-
-var g_GameSpeeds;
-
-/**
- * Colors to flash when pop limit reached.
- */
-var g_DefaultPopulationColor = "white";
-var g_PopulationAlertColor = "orange";
-
-/**
- * Seen in the tooltip of the top panel.
- */
-var g_ResourceTitleFont = "sans-bold-16";
+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;
/**
* Shows a message box asking the user to leave if "won" or "defeated".
*/
var g_ConfirmExit = false;
/**
- * True if the current player has paused the game explicitly.
- */
-var g_Paused = false;
-
-/**
- * The list of GUIDs of players who have currently paused the game, if the game is networked.
- */
-var g_PausingClients = [];
-
-/**
* 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;
var g_DeveloperOverlay;
/**
* 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;
/**
- * Blink the population counter if the player can't train more units.
- */
-var g_IsTrainingBlocked = 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 = [];
+
+/**
+ * These handlers are called after the player states have been initialized.
+ */
+var g_PlayersInitHandlers = [];
+
+/**
+ * These handlers are called when a player has been defeated or won the game.
+ */
+var g_PlayerFinishedHandlers = [];
+
+/**
+ * These events are fired whenever the player added or removed entities from the selection.
+ */
+var g_EntitySelectionChangeHandlers = [];
+
+/**
+ * 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 = [];
+
+/**
* Top coordinate of the research list.
* Changes depending on the number of displayed counters.
*/
var g_ResearchListTop = 4;
/**
* List of additional entities to highlight.
*/
var g_ShowGuarding = false;
var g_ShowGuarded = false;
var g_AdditionalHighlight = [];
/**
* Display data of the current players entities shown in the top panel.
*/
var g_PanelEntities = [];
/**
* 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_Chat = new Chat();
- g_DeveloperOverlay = new DeveloperOverlay();
g_DiplomacyColors = new DiplomacyColors();
- g_DiplomacyDialog = new DiplomacyDialog(g_DiplomacyColors);
- g_DiplomacyButton = new DiplomacyButton(g_DiplomacyDialog);
- g_MiniMapPanel = new MiniMapPanel(g_DiplomacyColors, g_WorkerTypes);
- g_TradeDialog = new TradeDialog();
- g_TradeDialogButton = new TradeDialogButton(g_TradeDialog);
+
+ 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_PlayerViewControl.registerPreViewedPlayerChangeHandler(removeStatusBarDisplay);
+ g_PlayerViewControl.registerViewedPlayerChangeHandler(resetTemplates);
+
+ g_Chat = new Chat(g_PlayerViewControl);
+ g_DeveloperOverlay = new DeveloperOverlay(g_PlayerViewControl);
+ g_DiplomacyDialog = new DiplomacyDialog(g_PlayerViewControl, g_DiplomacyColors);
+ g_GameSpeedControl = new GameSpeedControl(g_PlayerViewControl);
+ g_MiniMapPanel = new MiniMapPanel(g_PlayerViewControl, g_DiplomacyColors, g_WorkerTypes);
+ g_ObjectivesDialog = new ObjectivesDialog(g_PlayerViewControl);
+ g_PauseControl = new PauseControl();
+ g_PauseOverlay = new PauseOverlay(g_PauseControl);
+ g_TradeDialog = new TradeDialog(g_PlayerViewControl);
+ g_TopPanel = new TopPanel(g_PlayerViewControl, g_DiplomacyDialog, g_TradeDialog, g_ObjectivesDialog, g_GameSpeedControl);
initSelectionPanels();
LoadModificationTemplates();
updatePlayerData();
initializeMusic(); // before changing the perspective
- initGUIObjects();
+ initMenu(g_PlayerViewControl, g_PauseControl);
+ initPanelEntities();
+ Engine.SetBoundingBoxDebugOverlay(false);
+ updateEnabledRangeOverlayTypes();
+
+ 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;
}
sendLobbyPlayerlistUpdate();
+
+ // TODO: use event instead
onSimulationUpdate();
+
setTimeout(displayGamestateNotifications, 1000);
}
-function initGUIObjects()
+function registerPlayersInitHandler(handler)
{
- initMenu();
- updateGameSpeedControl();
- g_TradeDialog.resize();
- initPanelEntities();
- g_DiplomacyColors.onPlayerInit();
- initViewedPlayerDropdown();
- Engine.SetBoundingBoxDebugOverlay(false);
- updateEnabledRangeOverlayTypes();
- g_DiplomacyDialog.onPlayerInit();
+ g_PlayersInitHandlers.push(handler);
+}
+
+function registerPlayersFinishedHandler(handler)
+{
+ g_PlayerFinishedHandlers.push(handler);
+}
+
+function registerSimulationUpdateHandler(handler)
+{
+ g_SimulationUpdateHandlers.push(handler);
+}
+
+function registerEntitySelectionChangeHandler(handler)
+{
+ g_EntitySelectionChangeHandlers.push(handler);
+}
+
+function registerHotkeyChangeHandler(handler)
+{
+ g_HotkeyChangeHandlers.push(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;
}
/**
* Called when the user changed the diplomacy colors in the options.
* TODO: Remove this proxy and make the options page agnostic of the session page.
*/
function updateDisplayedPlayerColors()
{
g_DiplomacyColors.updateDisplayedPlayerColors();
}
-/**
- * Depends on the current player (g_IsObserver).
- */
-function updateHotkeyTooltips()
-{
- Engine.GetGUIObjectByName("objectivesButton").tooltip =
- colorizeHotkey("%(hotkey)s" + " ", "session.gui.objectives.toggle") +
- translate("Objectives");
-}
-
function initPanelEntities()
{
Engine.GetGUIObjectByName("panelEntityPanel").children.forEach((button, slot) => {
button.onPress = function() {
let panelEnt = g_PanelEntities.find(ent => ent.slot !== undefined && ent.slot == slot);
if (!panelEnt)
return;
if (!Engine.HotkeyIsPressed("selection.add"))
g_Selection.reset();
g_Selection.addList([panelEnt.ent]);
};
button.onDoublePress = function() {
let panelEnt = g_PanelEntities.find(ent => ent.slot !== undefined && ent.slot == slot);
if (panelEnt)
selectAndMoveTo(getEntityOrHolder(panelEnt.ent));
};
});
}
/**
* 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 initViewedPlayerDropdown()
+function resetTemplates()
{
- updateViewedPlayerDropdown();
-
- // Select "observer" in the view player dropdown when rejoining as a defeated player
- let player = g_Players[Engine.GetPlayerID()];
- Engine.GetGUIObjectByName("viewPlayer").selected = player && player.state == "defeated" ? 0 : Engine.GetPlayerID() + 1;
-}
-
-function updateViewedPlayerDropdown()
-{
- let viewPlayer = Engine.GetGUIObjectByName("viewPlayer");
- viewPlayer.list_data = [-1].concat(g_Players.map((player, i) => i));
- viewPlayer.list = [translate("Observer")].concat(g_Players.map(
- (player, i) => colorizePlayernameHelper("■", i) + " " + player.name
- ));
-}
-
-/**
- * Change perspective tool.
- * Shown to observers or when enabling the developers option.
- */
-function selectViewPlayer(playerID)
-{
- if (playerID < -1 || playerID > g_Players.length - 1)
- return;
-
- if (g_ShowAllStatusBars)
- recalculateStatusBarDisplay(true);
-
- g_IsObserver = isPlayerObserver(Engine.GetPlayerID());
-
- if (g_IsObserver || g_DeveloperOverlay.isChangePerspective())
- {
- if (g_ViewedPlayer != playerID)
- clearSelection();
- g_ViewedPlayer = playerID;
- }
-
- if (g_DeveloperOverlay.isChangePerspective())
- {
- Engine.SetPlayerID(g_ViewedPlayer);
- g_IsObserver = isPlayerObserver(g_ViewedPlayer);
- }
-
- Engine.SetViewedPlayer(g_ViewedPlayer);
- g_DiplomacyColors.updateDisplayedPlayerColors();
- updateTopPanel();
- g_Chat.onUpdatePlayers();
- updateHotkeyTooltips();
-
// Update GUI and clear player-dependent cache
g_TemplateData = {};
Engine.GuiInterfaceCall("ResetTemplateModified");
- onSimulationUpdate();
- g_DiplomacyDialog.update();
- g_TradeDialog.update();
+ // 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
});
if (players.indexOf(Engine.GetPlayerID()) != -1)
reportGame();
sendLobbyPlayerlistUpdate();
updatePlayerData();
- g_Chat.onUpdatePlayers();
- updateGameSpeedControl();
+
+ // TODO: The other calls in this function should move too
+ for (let handler of g_PlayerFinishedHandlers)
+ handler();
if (players.indexOf(g_ViewedPlayer) == -1)
return;
// Select "observer" item on loss. On win enable observermode without changing perspective
- Engine.GetGUIObjectByName("viewPlayer").selected = won ? g_ViewedPlayer + 1 : 0;
+ g_PlayerViewControl.selectViewPlayer(won ? g_ViewedPlayer + 1 : 0);
if (players.indexOf(Engine.GetPlayerID()) == -1 || Engine.IsAtlasRunning())
return;
global.music.setState(
won ?
global.music.states.VICTORY :
global.music.states.DEFEAT
);
g_ConfirmExit = won ? "won" : "defeated";
}
-/**
- * Sets civ icon for the currently viewed player.
- * Hides most gui objects for observers.
- */
-function updateTopPanel()
+function resumeGame()
{
- let isPlayer = g_ViewedPlayer > 0;
-
- let civIcon = Engine.GetGUIObjectByName("civIcon");
- civIcon.hidden = !isPlayer;
- if (isPlayer)
- {
- civIcon.sprite = "stretched:" + g_CivData[g_Players[g_ViewedPlayer].civ].Emblem;
- Engine.GetGUIObjectByName("civIconOverlay").tooltip =
- sprintf(
- translate("%(civ)s\n%(hotkey_civinfo)s / %(hotkey_structree)s: View History / Structure Tree\nLast opened will be reopened on click."), {
- "civ": setStringTags(g_CivData[g_Players[g_ViewedPlayer].civ].Name, { "font": "sans-bold-stroke-14" }),
- "hotkey_civinfo": colorizeHotkey("%(hotkey)s", "civinfo"),
- "hotkey_structree": colorizeHotkey("%(hotkey)s", "structree")
- });
- }
-
- // Following gaia can be interesting on scripted maps
- Engine.GetGUIObjectByName("optionFollowPlayer").hidden = !g_IsObserver || g_ViewedPlayer == -1;
-
- let viewPlayer = Engine.GetGUIObjectByName("viewPlayer");
- viewPlayer.hidden = !g_IsObserver && !g_DeveloperOverlay.isChangePerspective();
-
- let followPlayerLabel = Engine.GetGUIObjectByName("followPlayerLabel");
- followPlayerLabel.hidden = Engine.GetTextWidth(followPlayerLabel.font, followPlayerLabel.caption + " ") +
- followPlayerLabel.getComputedSize().left > viewPlayer.getComputedSize().left;
-
- let resCodes = g_ResourceData.GetCodes();
- let r = 0;
- for (let res of resCodes)
- {
- if (!Engine.GetGUIObjectByName("resource[" + r + "]"))
- {
- warn("Current GUI limits prevent displaying more than " + r + " resources in the top panel!");
- break;
- }
- Engine.GetGUIObjectByName("resource[" + r + "]_icon").sprite = "stretched:session/icons/resources/" + res + ".png";
- Engine.GetGUIObjectByName("resource[" + r + "]").hidden = !isPlayer;
- ++r;
- }
- horizontallySpaceObjects("resourceCounts", 5);
- hideRemaining("resourceCounts", r);
-
- let resPop = Engine.GetGUIObjectByName("population");
- let resPopSize = resPop.size;
- resPopSize.left = Engine.GetGUIObjectByName("resource[" + (r - 1) + "]").size.right;
- resPop.size = resPopSize;
-
- Engine.GetGUIObjectByName("population").hidden = !isPlayer;
- g_DiplomacyButton.update();
- g_TradeDialogButton.update();
-
- Engine.GetGUIObjectByName("observerText").hidden = isPlayer;
-
- let alphaLabel = Engine.GetGUIObjectByName("alphaLabel");
- alphaLabel.hidden = isPlayer && !viewPlayer.hidden;
- alphaLabel.size = isPlayer ? "50%+44 0 100%-283 100%" : "155 0 85%-279 100%";
-
- Engine.GetGUIObjectByName("pauseButton").enabled = !g_IsObserver || !g_IsNetworked || g_IsController;
- Engine.GetGUIObjectByName("menuResignButton").enabled = !g_IsObserver;
- Engine.GetGUIObjectByName("lobbyButton").enabled = Engine.HasXmppClient();
+ g_PauseControl.implicitResume();
}
-/**
- * Resign a player.
- * @param leaveGameAfterResign If player is quitting after resignation.
- */
-function resignGame(leaveGameAfterResign)
+function resignGame()
{
- if (g_IsObserver || g_Disconnected)
- return;
-
Engine.PostNetworkCommand({
"type": "resign"
});
-
- if (!leaveGameAfterResign)
- resumeGame(true);
+ g_PauseControl.implicitResume();
}
-/**
- * Leave the game
- * @param willRejoin If player is going to be rejoining a networked game.
- */
-function leaveGame(willRejoin)
+function endGame()
{
- if (!willRejoin && !g_IsObserver)
- resignGame(true);
-
// 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();
-
updateMenuPosition(tickLength);
-
- // When training is blocked, flash population (alternates color every 500msec)
- Engine.GetGUIObjectByName("resourcePop").textcolor = g_IsTrainingBlocked && now % 1000 < 500 ? g_PopulationAlertColor : g_DefaultPopulationColor;
-
Engine.GuiInterfaceCall("ClearRenamedEntities");
}
-function onWindowResized()
-{
- // Update followPlayerLabel
- updateTopPanel();
-
- g_Chat.ChatWindow.resizeChatWindow();
-}
-
-function changeGameSpeed(speed)
-{
- if (!g_IsNetworked)
- Engine.SetSimRate(speed);
-}
-
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();
if (g_ConfirmExit)
confirmExit();
}
/**
* Don't show the message box before all playerstate changes are processed.
*/
function confirmExit()
{
if (g_IsNetworked && !g_IsNetworkedActive)
return;
closeOpenDialogs();
+ g_PauseControl.implicitPause();
// Don't ask for exit if other humans are still playing
let askExit = !Engine.HasNetServer() || g_Players.every((player, i) =>
i == 0 ||
player.state != "active" ||
g_GameAttributes.settings.PlayerData[i].AI != "");
let subject = g_PlayerStateMessages[g_ConfirmExit];
if (askExit)
subject += "\n" + translate("Do you want to quit?");
messageBox(
400, 200,
subject,
g_ConfirmExit == "won" ?
translate("VICTORIOUS!") :
translate("DEFEATED!"),
askExit ? [translate("No"), translate("Yes")] : [translate("OK")],
- askExit ? [resumeGame, leaveGame] : [resumeGame]
- );
+ askExit ? [resumeGame, endGame] : [resumeGame]);
g_ConfirmExit = false;
}
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();
updatePanelEntities();
displayPanelEntities();
updateGroups();
- updatePlayerDisplay();
updateResearchDisplay();
updateSelectionDetails();
updateBuildingPlacementPreview();
updateTimeNotifications();
- if (g_ViewedPlayer > 0)
- {
- let playerState = GetSimState().players[g_ViewedPlayer];
- g_DeveloperOverlay.setControlAll(playerState && playerState.controlsAll);
- }
-
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]);
}
-
- updateViewedPlayerDropdown();
- g_DeveloperOverlay.update();
- g_DiplomacyDialog.update();
- g_MiniMapPanel.update();
- g_TradeDialog.update();
-}
-
-function saveResPopTooltipSort()
-{
- Engine.ConfigDB_CreateAndWriteValueToFile("user", "gui.session.respoptooltipsort", String((+Engine.ConfigDB_GetValue("user", "gui.session.respoptooltipsort") + 2) % 3 - 1), "config/user.cfg");
}
function onReplayFinished()
{
closeOpenDialogs();
- pauseGame();
+ g_PauseControl.implicitPause();
messageBox(400, 200,
translateWithContext("replayFinished", "The replay has finished. Do you want to quit?"),
translateWithContext("replayFinished", "Confirmation"),
[translateWithContext("replayFinished", "No"), translateWithContext("replayFinished", "Yes")],
- [resumeGame, leaveGame]);
+ [resumeGame, endGame]);
}
/**
* updates a status bar on the GUI
* nameOfBar: name of the bar
* points: points to show
* maxPoints: max points
* direction: gets less from (right to left) 0; (top to bottom) 1; (left to right) 2; (bottom to top) 3;
*/
function updateGUIStatusBar(nameOfBar, points, maxPoints, direction)
{
// check, if optional direction parameter is valid.
if (!direction || !(direction >= 0 && direction < 4))
direction = 0;
// get the bar and update it
let statusBar = Engine.GetGUIObjectByName(nameOfBar);
if (!statusBar)
return;
let healthSize = statusBar.size;
let value = 100 * Math.max(0, Math.min(1, points / maxPoints));
// inverse bar
if (direction == 2 || direction == 3)
value = 100 - value;
if (direction == 0)
healthSize.rright = value;
else if (direction == 1)
healthSize.rbottom = value;
else if (direction == 2)
healthSize.rleft = value;
else if (direction == 3)
healthSize.rtop = value;
statusBar.size = healthSize;
}
function updatePanelEntities()
{
let panelEnts =
g_ViewedPlayer == -1 ?
GetSimState().players.reduce((ents, pState) => ents.concat(pState.panelEntities), []) :
GetSimState().players[g_ViewedPlayer].panelEntities;
g_PanelEntities = g_PanelEntities.filter(panelEnt => panelEnts.find(ent => ent == panelEnt.ent));
for (let ent of panelEnts)
{
let panelEntState = GetEntityState(ent);
let template = GetTemplateData(panelEntState.template);
let panelEnt = g_PanelEntities.find(pEnt => ent == pEnt.ent);
if (!panelEnt)
{
panelEnt = {
"ent": ent,
"tooltip": undefined,
"sprite": "stretched:session/portraits/" + template.icon,
"maxHitpoints": undefined,
"currentHitpoints": panelEntState.hitpoints,
"previousHitpoints": undefined
};
g_PanelEntities.push(panelEnt);
}
panelEnt.tooltip = createPanelEntityTooltip(panelEntState, template);
panelEnt.previousHitpoints = panelEnt.currentHitpoints;
panelEnt.currentHitpoints = panelEntState.hitpoints;
panelEnt.maxHitpoints = panelEntState.maxHitpoints;
}
let panelEntIndex = ent => g_PanelEntityOrder.findIndex(entClass =>
GetEntityState(ent).identity.classes.indexOf(entClass) != -1);
g_PanelEntities.sort((panelEntA, panelEntB) =>
panelEntIndex(panelEntA.ent) - panelEntIndex(panelEntB.ent)
).splice(Engine.GetGUIObjectByName("panelEntityPanel").children.length);
}
function createPanelEntityTooltip(panelEntState, template)
{
let getPanelEntNameTooltip = panelEntState => "[font=\"sans-bold-16\"]" + template.name.specific + "[/font]";
return [
getPanelEntNameTooltip,
getCurrentHealthTooltip,
getAttackTooltip,
getArmorTooltip,
getEntityTooltip,
getAurasTooltip
].map(tooltip => tooltip(panelEntState)).filter(tip => tip).join("\n");
}
function displayPanelEntities()
{
let buttons = Engine.GetGUIObjectByName("panelEntityPanel").children;
buttons.forEach((button, slot) => {
if (button.hidden || g_PanelEntities.some(ent => ent.slot !== undefined && ent.slot == slot))
return;
button.hidden = true;
stopColorFade("panelEntityHitOverlay[" + slot + "]");
});
// The slot identifies the button, displayIndex determines its position.
for (let displayIndex = 0; displayIndex < Math.min(g_PanelEntities.length, buttons.length); ++displayIndex)
{
let panelEnt = g_PanelEntities[displayIndex];
// Find the first unused slot if new, otherwise reuse previous.
let slot = panelEnt.slot === undefined ?
buttons.findIndex(button => button.hidden) :
panelEnt.slot;
let panelEntButton = Engine.GetGUIObjectByName("panelEntityButton[" + slot + "]");
panelEntButton.tooltip = panelEnt.tooltip;
updateGUIStatusBar("panelEntityHealthBar[" + slot + "]", panelEnt.currentHitpoints, panelEnt.maxHitpoints);
if (panelEnt.slot === undefined)
{
let panelEntImage = Engine.GetGUIObjectByName("panelEntityImage[" + slot + "]");
panelEntImage.sprite = panelEnt.sprite;
panelEntButton.hidden = false;
panelEnt.slot = slot;
}
// If the health of the panelEnt changed since the last update, trigger the animation.
if (panelEnt.previousHitpoints > panelEnt.currentHitpoints)
startColorFade("panelEntityHitOverlay[" + slot + "]", 100, 0,
colorFade_attackUnit, true, smoothColorFadeRestart_attackUnit);
// TODO: Instead of instant position changes, animate button movement.
setPanelObjectPosition(panelEntButton, displayIndex, buttons.length);
}
}
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);
}
}
-/**
- * Create ally player stat tooltip.
- * @param {string} resource - Resource type, on which values will be sorted.
- * @param {object} playerStates - Playerstates from players whos stats are viewed in the tooltip.
- * @param {number} sort - 0 no order, -1 descending, 1 ascending order.
- * @returns {string} Tooltip string.
- */
-function getAllyStatTooltip(resource, playerStates, sort)
-{
- let tooltip = [];
-
- for (let player in playerStates)
- tooltip.push({
- "playername": colorizePlayernameHelper("■", player) + " " + g_Players[player].name,
- "statValue": resource == "pop" ?
- sprintf(translate("%(popCount)s/%(popLimit)s/%(popMax)s"), playerStates[player]) :
- Math.round(playerStates[player].resourceCounts[resource]),
- "orderValue": resource == "pop" ? playerStates[player].popCount :
- Math.round(playerStates[player].resourceCounts[resource])
- });
- if (sort)
- tooltip.sort((a, b) => sort * (b.orderValue - a.orderValue));
- return "\n" + tooltip.map(stat => sprintf(translate("%(playername)s: %(statValue)s"), stat)).join("\n");
-}
-
-function updatePlayerDisplay()
-{
- let allPlayerStates = GetSimState().players;
- let viewedPlayerState = allPlayerStates[g_ViewedPlayer];
- let viewablePlayerStates = {};
- for (let player in allPlayerStates)
- if (player != 0 &&
- player != g_ViewedPlayer &&
- g_Players[player].state != "defeated" &&
- (g_IsObserver ||
- viewedPlayerState.hasSharedLos &&
- g_Players[player].isMutualAlly[g_ViewedPlayer]))
- viewablePlayerStates[player] = allPlayerStates[player];
-
- if (!viewedPlayerState)
- return;
-
- let tooltipSort = +Engine.ConfigDB_GetValue("user", "gui.session.respoptooltipsort");
-
- let orderHotkeyTooltip = Object.keys(viewablePlayerStates).length <= 1 ? "" :
- "\n" + sprintf(translate("%(order)s: %(hotkey)s to change order."), {
- "hotkey": setStringTags("\\[Click]", g_HotkeyTags),
- "order": tooltipSort == 0 ? translate("Unordered") : tooltipSort == 1 ? translate("Descending") : translate("Ascending")
- });
-
- let resCodes = g_ResourceData.GetCodes();
- for (let r = 0; r < resCodes.length; ++r)
- {
- let resourceObj = Engine.GetGUIObjectByName("resource[" + r + "]");
- if (!resourceObj)
- break;
-
- let res = resCodes[r];
-
- let tooltip = '[font="' + g_ResourceTitleFont + '"]' +
- resourceNameFirstWord(res) + '[/font]';
-
- let descr = g_ResourceData.GetResource(res).description;
- if (descr)
- tooltip += "\n" + translate(descr);
-
- tooltip += orderHotkeyTooltip + getAllyStatTooltip(res, viewablePlayerStates, tooltipSort);
-
- resourceObj.tooltip = tooltip;
-
- Engine.GetGUIObjectByName("resource[" + r + "]_count").caption = Math.floor(viewedPlayerState.resourceCounts[res]);
- }
-
- Engine.GetGUIObjectByName("resourcePop").caption = sprintf(translate("%(popCount)s/%(popLimit)s"), viewedPlayerState);
- Engine.GetGUIObjectByName("population").tooltip = translate("Population (current / limit)") + "\n" +
- sprintf(translate("Maximum population: %(popCap)s"), { "popCap": viewedPlayerState.popMax }) +
- orderHotkeyTooltip +
- getAllyStatTooltip("pop", viewablePlayerStates, tooltipSort);
-
- g_IsTrainingBlocked = viewedPlayerState.trainingBlocked;
-}
-
function selectAndMoveTo(ent)
{
let entState = GetEntityState(ent);
if (!entState || !entState.position)
return;
g_Selection.reset();
g_Selection.addList([ent]);
let position = entState.position;
Engine.CameraMoveTo(position.x, position.z);
}
function updateResearchDisplay()
{
let researchStarted = Engine.GuiInterfaceCall("GetStartedResearch", g_ViewedPlayer);
// Set up initial positioning.
let buttonSideLength = Engine.GetGUIObjectByName("researchStartedButton[0]").size.right;
for (let i = 0; i < 10; ++i)
{
let button = Engine.GetGUIObjectByName("researchStartedButton[" + i + "]");
let size = button.size;
size.top = g_ResearchListTop + (4 + buttonSideLength) * i;
size.bottom = size.top + buttonSideLength;
button.size = size;
}
let numButtons = 0;
for (let tech in researchStarted)
{
// Show at most 10 in-progress techs.
if (numButtons >= 10)
break;
let template = GetTechnologyData(tech, g_Players[g_ViewedPlayer].civ);
let button = Engine.GetGUIObjectByName("researchStartedButton[" + numButtons + "]");
button.hidden = false;
button.tooltip = getEntityNames(template);
button.onpress = (function(e) { return function() { selectAndMoveTo(e); }; })(researchStarted[tech].researcher);
let icon = "stretched:session/portraits/" + template.icon;
Engine.GetGUIObjectByName("researchStartedIcon[" + numButtons + "]").sprite = icon;
// Scale the progress indicator.
let size = Engine.GetGUIObjectByName("researchStartedProgressSlider[" + numButtons + "]").size;
// Buttons are assumed to be square, so left/right offsets can be used for top/bottom.
size.top = size.left + Math.round(researchStarted[tech].progress * (size.right - size.left));
Engine.GetGUIObjectByName("researchStartedProgressSlider[" + numButtons + "]").size = size;
Engine.GetGUIObjectByName("researchStartedTimeRemaining[" + numButtons + "]").caption =
Engine.FormatMillisecondsIntoDateStringGMT(researchStarted[tech].timeRemaining, translateWithContext("countdown format", "m:ss"));
++numButtons;
}
// Hide unused buttons.
for (let i = numButtons; i < 10; ++i)
Engine.GetGUIObjectByName("researchStartedButton[" + i + "]").hidden = true;
}
/**
* 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;
}
/**
* Toggles the display of range overlays of selected entities for the given range type.
* @param {string} type - for example "Auras"
*/
function toggleRangeOverlay(type)
{
let enabled = toggleConfigBool("gui.session." + type.toLowerCase() + "range");
Engine.GuiInterfaceCall("EnableVisualRangeOverlayType", {
"type": type,
"enabled": enabled
});
let selected = g_Selection.toList();
for (let ent in g_Selection.highlighted)
selected.push(g_Selection.highlighted[ent]);
Engine.GuiInterfaceCall("SetRangeOverlays", {
"entities": selected,
"enabled": enabled
});
}
function updateEnabledRangeOverlayTypes()
{
for (let type of ["Attack", "Auras", "Heal"])
Engine.GuiInterfaceCall("EnableVisualRangeOverlayType", {
"type": type,
"enabled": Engine.ConfigDB_GetValue("user", "gui.session." + type.toLowerCase() + "range") == "true"
});
}
// 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);
}
/**
* Adds the ingame time and ceasefire counter to the global FPS and
* realtime counters shown in the top right corner.
*/
function appendSessionCounters(counters)
{
let simState = GetSimState();
if (Engine.ConfigDB_GetValue("user", "gui.session.timeelapsedcounter") === "true")
{
let currentSpeed = Engine.GetSimRate();
if (currentSpeed != 1.0)
// Translation: The "x" means "times", with the mathematical meaning of multiplication.
counters.push(sprintf(translate("%(time)s (%(speed)sx)"), {
"time": timeToString(simState.timeElapsed),
"speed": Engine.FormatDecimalNumberIntoString(currentSpeed)
}));
else
counters.push(timeToString(simState.timeElapsed));
}
if (simState.ceasefireActive && Engine.ConfigDB_GetValue("user", "gui.session.ceasefirecounter") === "true")
counters.push(timeToString(simState.ceasefireTimeRemaining));
g_ResearchListTop = 4 + 14 * counters.length;
}
/**
* Send the current list of players, teams, AIs, observers and defeated/won and offline states to the lobby.
* The playerData format from g_GameAttributes is kept to reuse the GUI function presenting the data.
*/
function sendLobbyPlayerlistUpdate()
{
if (!g_IsController || !Engine.HasXmppClient())
return;
// Extract the relevant player data and minimize packet load
let minPlayerData = [];
for (let playerID in g_GameAttributes.settings.PlayerData)
{
if (+playerID == 0)
continue;
let pData = g_GameAttributes.settings.PlayerData[playerID];
let minPData = { "Name": pData.Name, "Civ": pData.Civ };
if (g_GameAttributes.settings.LockTeams)
minPData.Team = pData.Team;
if (pData.AI)
{
minPData.AI = pData.AI;
minPData.AIDiff = pData.AIDiff;
minPData.AIBehavior = pData.AIBehavior;
}
if (g_Players[playerID].offline)
minPData.Offline = true;
// Whether the player has won or was defeated
let state = g_Players[playerID].state;
if (state != "active")
minPData.State = state;
minPlayerData.push(minPData);
}
// Add observers
let connectedPlayers = 0;
for (let guid in g_PlayerAssignments)
{
let pData = g_GameAttributes.settings.PlayerData[g_PlayerAssignments[guid].player];
if (pData)
++connectedPlayers;
else
minPlayerData.push({
"Name": g_PlayerAssignments[guid].name,
"Team": "observer"
});
}
Engine.SendChangeStateGame(connectedPlayers, playerDataToStringifiedTeamList(minPlayerData));
}
/**
* Send a report on the gamestatus to the lobby.
* Keep in sync with source/tools/XpartaMuPP/LobbyRanking.py
*/
function reportGame()
{
// Only 1v1 games are rated (and Gaia is part of g_Players)
if (!Engine.HasXmppClient() || !Engine.IsRankedGame() ||
g_Players.length != 3 || Engine.GetPlayerID() == -1)
return;
let extendedSimState = Engine.GuiInterfaceCall("GetExtendedSimulationState");
let unitsClasses = [
"total",
"Infantry",
"Worker",
"FemaleCitizen",
"Cavalry",
"Champion",
"Hero",
"Siege",
"Ship",
"Trader"
];
let unitsCountersTypes = [
"unitsTrained",
"unitsLost",
"enemyUnitsKilled"
];
let buildingsClasses = [
"total",
"CivCentre",
"House",
"Economic",
"Outpost",
"Military",
"Fortress",
"Wonder"
];
let buildingsCountersTypes = [
"buildingsConstructed",
"buildingsLost",
"enemyBuildingsDestroyed"
];
let resourcesTypes = [
"wood",
"food",
"stone",
"metal"
];
let resourcesCounterTypes = [
"resourcesGathered",
"resourcesUsed",
"resourcesSold",
"resourcesBought"
];
let misc = [
"tradeIncome",
"tributesSent",
"tributesReceived",
"treasuresCollected",
"lootCollected",
"percentMapExplored"
];
let playerStatistics = {};
// Unit Stats
for (let unitCounterType of unitsCountersTypes)
{
if (!playerStatistics[unitCounterType])
playerStatistics[unitCounterType] = { };
for (let unitsClass of unitsClasses)
playerStatistics[unitCounterType][unitsClass] = "";
}
playerStatistics.unitsLostValue = "";
playerStatistics.unitsKilledValue = "";
// Building stats
for (let buildingCounterType of buildingsCountersTypes)
{
if (!playerStatistics[buildingCounterType])
playerStatistics[buildingCounterType] = { };
for (let buildingsClass of buildingsClasses)
playerStatistics[buildingCounterType][buildingsClass] = "";
}
playerStatistics.buildingsLostValue = "";
playerStatistics.enemyBuildingsDestroyedValue = "";
// Resources
for (let resourcesCounterType of resourcesCounterTypes)
{
if (!playerStatistics[resourcesCounterType])
playerStatistics[resourcesCounterType] = { };
for (let resourcesType of resourcesTypes)
playerStatistics[resourcesCounterType][resourcesType] = "";
}
playerStatistics.resourcesGathered.vegetarianFood = "";
for (let type of misc)
playerStatistics[type] = "";
// Total
playerStatistics.economyScore = "";
playerStatistics.militaryScore = "";
playerStatistics.totalScore = "";
let mapName = g_GameAttributes.settings.Name;
let playerStates = "";
let playerCivs = "";
let teams = "";
let teamsLocked = true;
// Serialize the statistics for each player into a comma-separated list.
// Ignore gaia
for (let i = 1; i < extendedSimState.players.length; ++i)
{
let player = extendedSimState.players[i];
let maxIndex = player.sequences.time.length - 1;
playerStates += player.state + ",";
playerCivs += player.civ + ",";
teams += player.team + ",";
teamsLocked = teamsLocked && player.teamsLocked;
for (let resourcesCounterType of resourcesCounterTypes)
for (let resourcesType of resourcesTypes)
playerStatistics[resourcesCounterType][resourcesType] += player.sequences[resourcesCounterType][resourcesType][maxIndex] + ",";
playerStatistics.resourcesGathered.vegetarianFood += player.sequences.resourcesGathered.vegetarianFood[maxIndex] + ",";
for (let unitCounterType of unitsCountersTypes)
for (let unitsClass of unitsClasses)
playerStatistics[unitCounterType][unitsClass] += player.sequences[unitCounterType][unitsClass][maxIndex] + ",";
for (let buildingCounterType of buildingsCountersTypes)
for (let buildingsClass of buildingsClasses)
playerStatistics[buildingCounterType][buildingsClass] += player.sequences[buildingCounterType][buildingsClass][maxIndex] + ",";
let total = 0;
for (let type in player.sequences.resourcesGathered)
total += player.sequences.resourcesGathered[type][maxIndex];
playerStatistics.economyScore += total + ",";
playerStatistics.militaryScore += Math.round((player.sequences.enemyUnitsKilledValue[maxIndex] +
player.sequences.enemyBuildingsDestroyedValue[maxIndex]) / 10) + ",";
playerStatistics.totalScore += (total + Math.round((player.sequences.enemyUnitsKilledValue[maxIndex] +
player.sequences.enemyBuildingsDestroyedValue[maxIndex]) / 10)) + ",";
for (let type of misc)
playerStatistics[type] += player.sequences[type][maxIndex] + ",";
}
// Send the report with serialized data
let reportObject = {};
reportObject.timeElapsed = extendedSimState.timeElapsed;
reportObject.playerStates = playerStates;
reportObject.playerID = Engine.GetPlayerID();
reportObject.matchID = g_GameAttributes.matchID;
reportObject.civs = playerCivs;
reportObject.teams = teams;
reportObject.teamsLocked = String(teamsLocked);
reportObject.ceasefireActive = String(extendedSimState.ceasefireActive);
reportObject.ceasefireTimeRemaining = String(extendedSimState.ceasefireTimeRemaining);
reportObject.mapName = mapName;
reportObject.economyScore = playerStatistics.economyScore;
reportObject.militaryScore = playerStatistics.militaryScore;
reportObject.totalScore = playerStatistics.totalScore;
for (let rct of resourcesCounterTypes)
for (let rt of resourcesTypes)
reportObject[rt + rct.substr(9)] = playerStatistics[rct][rt];
// eg. rt = food rct.substr = Gathered rct = resourcesGathered
reportObject.vegetarianFoodGathered = playerStatistics.resourcesGathered.vegetarianFood;
for (let type of unitsClasses)
{
// eg. type = Infantry (type.substr(0,1)).toLowerCase()+type.substr(1) = infantry
reportObject[(type.substr(0, 1)).toLowerCase() + type.substr(1) + "UnitsTrained"] = playerStatistics.unitsTrained[type];
reportObject[(type.substr(0, 1)).toLowerCase() + type.substr(1) + "UnitsLost"] = playerStatistics.unitsLost[type];
reportObject["enemy" + type + "UnitsKilled"] = playerStatistics.enemyUnitsKilled[type];
}
for (let type of buildingsClasses)
{
reportObject[(type.substr(0, 1)).toLowerCase() + type.substr(1) + "BuildingsConstructed"] = playerStatistics.buildingsConstructed[type];
reportObject[(type.substr(0, 1)).toLowerCase() + type.substr(1) + "BuildingsLost"] = playerStatistics.buildingsLost[type];
reportObject["enemy" + type + "BuildingsDestroyed"] = playerStatistics.enemyBuildingsDestroyed[type];
}
for (let type of misc)
reportObject[type] = playerStatistics[type];
Engine.SendGameReport(reportObject);
}
Index: ps/trunk/binaries/data/mods/public/gui/session/session.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/session/session.xml (revision 23075)
+++ ps/trunk/binaries/data/mods/public/gui/session/session.xml (revision 23076)
@@ -1,165 +1,153 @@
+
+
onTick();
-
- onWindowResized();
-
-
restoreSavedGameData(arguments[0]);
onSimulationUpdate();
onReplayFinished();
onReplayOutOfSync(arguments[0], arguments[1], arguments[2]);
Engine.ConfigDB_CreateValue("user", "gui.session.timeelapsedcounter", String(Engine.ConfigDB_GetValue("user", "gui.session.timeelapsedcounter") != "true"));
Engine.ConfigDB_CreateValue("user", "gui.session.ceasefirecounter", String(Engine.ConfigDB_GetValue("user", "gui.session.ceasefirecounter") != "true"));
Exit
- leaveGame();
+ endGame();
-
-
-
-
- Game Paused
-
-
- Click to Resume Game
-
-
- togglePause();
-
+
+
-
-
+
+
Index: ps/trunk/binaries/data/mods/public/gui/session/top_panel/BuildLabel.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/session/top_panel/BuildLabel.xml (nonexistent)
+++ ps/trunk/binaries/data/mods/public/gui/session/top_panel/BuildLabel.xml (revision 23076)
@@ -0,0 +1,19 @@
+
+
+
+
+
+ ALPHA XXIV
+
+
+
+
+
Property changes on: ps/trunk/binaries/data/mods/public/gui/session/top_panel/BuildLabel.xml
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Added: svn:mime-type
## -0,0 +1 ##
+text/xml
\ No newline at end of property
Index: ps/trunk/binaries/data/mods/public/gui/session/top_panel/CivIcon.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/session/top_panel/CivIcon.xml (nonexistent)
+++ ps/trunk/binaries/data/mods/public/gui/session/top_panel/CivIcon.xml (revision 23076)
@@ -0,0 +1,9 @@
+
+
+
+
+
Property changes on: ps/trunk/binaries/data/mods/public/gui/session/top_panel/CivIcon.xml
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Added: svn:mime-type
## -0,0 +1 ##
+text/xml
\ No newline at end of property
Index: ps/trunk/binaries/data/mods/public/gui/session/top_panel/CounterPopulation.js
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/session/top_panel/CounterPopulation.js (nonexistent)
+++ ps/trunk/binaries/data/mods/public/gui/session/top_panel/CounterPopulation.js (revision 23076)
@@ -0,0 +1,68 @@
+/**
+ * This class manages the population counter in the top panel.
+ * It flashes the counter if the training of any owned entity is blocked.
+ */
+class CounterPopulation
+{
+ constructor(resCode, panel, icon, count)
+ {
+ this.resCode = resCode;
+ this.panel = panel;
+ this.icon = icon;
+ this.count = count;
+ this.count.onTick = this.onTick.bind(this);
+ this.isTrainingBlocked = false;
+ this.color = this.DefaultPopulationColor;
+ }
+
+ rebuild(playerState, getAllyStatTooltip)
+ {
+ this.count.caption = sprintf(translate(this.CounterCaption), playerState);
+
+ this.isTrainingBlocked = playerState.trainingBlocked;
+
+ this.panel.tooltip =
+ setStringTags(translate(this.PopulationTooltip), CounterManager.ResourceTitleTags) + "\n" +
+ sprintf(translate(this.MaximumPopulationTooltip), { "popCap": playerState.popMax }) +
+ getAllyStatTooltip(this.getTooltipData.bind(this));
+ }
+
+ getTooltipData(playerState, playername)
+ {
+ return {
+ "playername": playername,
+ "statValue": sprintf(translate(this.AllyPopulationTooltip), playerState),
+ "orderValue": playerState.popCount
+ };
+ }
+
+ onTick()
+ {
+ if (this.panel.hidden)
+ return;
+
+ let newColor = this.isTrainingBlocked && Date.now() % 1000 < 500 ?
+ this.PopulationAlertColor :
+ this.DefaultPopulationColor;
+
+ if (newColor == this.color)
+ return;
+
+ this.color = newColor;
+ this.count.textcolor = newColor;
+ }
+}
+
+CounterPopulation.prototype.CounterCaption = markForTranslation("%(popCount)s/%(popLimit)s");
+
+CounterPopulation.prototype.PopulationTooltip = markForTranslation("Population (current / limit)");
+
+CounterPopulation.prototype.MaximumPopulationTooltip = markForTranslation("Maximum population: %(popCap)s");
+
+CounterPopulation.prototype.AllyPopulationTooltip = markForTranslation("%(popCount)s/%(popLimit)s/%(popMax)s");
+
+/**
+ * Colors to flash when pop limit reached.
+ */
+CounterPopulation.prototype.DefaultPopulationColor = "white";
+CounterPopulation.prototype.PopulationAlertColor = "orange";
Property changes on: ps/trunk/binaries/data/mods/public/gui/session/top_panel/CounterPopulation.js
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Index: ps/trunk/binaries/data/mods/public/gui/session/top_panel/Counters.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/session/top_panel/Counters.xml (nonexistent)
+++ ps/trunk/binaries/data/mods/public/gui/session/top_panel/Counters.xml (revision 23076)
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
Property changes on: ps/trunk/binaries/data/mods/public/gui/session/top_panel/Counters.xml
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Index: ps/trunk/binaries/data/mods/public/gui/session/top_panel/IconButtons/DiplomacyDialogButton.js
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/session/top_panel/IconButtons/DiplomacyDialogButton.js (nonexistent)
+++ ps/trunk/binaries/data/mods/public/gui/session/top_panel/IconButtons/DiplomacyDialogButton.js (revision 23076)
@@ -0,0 +1,29 @@
+/**
+ * This class handles the button which opens the diplomacy dialog.
+ */
+class DiplomacyDialogButton
+{
+ constructor(playerViewControl, diplomacyDialog)
+ {
+ this.diplomacyButton = Engine.GetGUIObjectByName("diplomacyButton");
+ this.diplomacyButton.enabled = !Engine.IsAtlasRunning();
+ this.diplomacyButton.onPress = diplomacyDialog.toggle.bind(diplomacyDialog);
+
+ registerHotkeyChangeHandler(this.onHotkeyChange.bind(this));
+ playerViewControl.registerViewedPlayerChangeHandler(this.onViewedPlayerChange.bind(this));
+ }
+
+ onHotkeyChange()
+ {
+ this.diplomacyButton.tooltip =
+ colorizeHotkey("%(hotkey)s" + " ", "session.gui.diplomacy.toggle") +
+ translate(this.Tooltip);
+ }
+
+ onViewedPlayerChange()
+ {
+ this.diplomacyButton.hidden = g_ViewedPlayer < 1;
+ }
+}
+
+DiplomacyDialogButton.prototype.Tooltip = markForTranslation("Diplomacy");
Property changes on: ps/trunk/binaries/data/mods/public/gui/session/top_panel/IconButtons/DiplomacyDialogButton.js
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Index: ps/trunk/binaries/data/mods/public/gui/session/top_panel/IconButtons/GameSpeedButton.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/session/top_panel/IconButtons/GameSpeedButton.xml (nonexistent)
+++ ps/trunk/binaries/data/mods/public/gui/session/top_panel/IconButtons/GameSpeedButton.xml (revision 23076)
@@ -0,0 +1,11 @@
+
+
+ Game Speed
+
+
Property changes on: ps/trunk/binaries/data/mods/public/gui/session/top_panel/IconButtons/GameSpeedButton.xml
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Index: ps/trunk/binaries/data/mods/public/gui/session/top_panel/IconButtons/TradeDialogButton.js
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/session/top_panel/IconButtons/TradeDialogButton.js (nonexistent)
+++ ps/trunk/binaries/data/mods/public/gui/session/top_panel/IconButtons/TradeDialogButton.js (revision 23076)
@@ -0,0 +1,31 @@
+/**
+ * This class handles the button which opens the diplomacy dialog.
+ */
+class TradeDialogButton
+{
+ constructor(playerViewControl, tradeDialog)
+ {
+ this.tradeButton = Engine.GetGUIObjectByName("tradeButton");
+ this.tradeButton.onPress = tradeDialog.toggle.bind(tradeDialog);
+ this.isAvailable =
+ g_ResourceData.GetTradableCodes().length ||
+ g_ResourceData.GetBarterableCodes().length;
+
+ playerViewControl.registerViewedPlayerChangeHandler(this.onViewedPlayerChange.bind(this));
+ registerHotkeyChangeHandler(this.onHotkeyChange.bind(this));
+ }
+
+ onHotkeyChange()
+ {
+ this.tradeButton.tooltip =
+ colorizeHotkey("%(hotkey)s" + " ", "session.gui.barter.toggle") +
+ translate(this.Tooltip);
+ }
+
+ onViewedPlayerChange()
+ {
+ this.tradeButton.hidden = g_ViewedPlayer < 1 || !this.isAvailable;
+ }
+}
+
+TradeDialogButton.prototype.Tooltip = markForTranslation("Barter & Trade");
Property changes on: ps/trunk/binaries/data/mods/public/gui/session/top_panel/IconButtons/TradeDialogButton.js
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Index: ps/trunk/binaries/data/mods/public/gui/session/top_panel/PlayerViewControl.js
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/session/top_panel/PlayerViewControl.js (nonexistent)
+++ ps/trunk/binaries/data/mods/public/gui/session/top_panel/PlayerViewControl.js (revision 23076)
@@ -0,0 +1,111 @@
+/**
+ * This class manages the player selection dropdown.
+ * This dropdown is available in observermode and when enabling the developers option.
+ * For observers, the user can view the player but not send commands.
+ * If the developer feature is enabled and cheats enabled, the player becomes
+ * assigned to and can control the selected player.
+ */
+class PlayerViewControl
+{
+ constructor(diplomacyColors)
+ {
+ // State
+ this.viewPlayer = Engine.GetGUIObjectByName("viewPlayer");
+ this.observerText = Engine.GetGUIObjectByName("observerText");
+ this.changePerspective = false;
+ this.playerIDChangeHandlers = [];
+ this.viewedPlayerChangeHandlers = [];
+ this.preViewedPlayerChangeHandlers = [];
+
+ // Events
+ this.viewPlayer.onSelectionChange = this.onSelectionChange.bind(this);
+ registerPlayersInitHandler(this.onPlayersInit.bind(this));
+ this.registerViewedPlayerChangeHandler(this.rebuild.bind(this));
+ }
+
+ registerPlayerIDChangeHandler(handler)
+ {
+ this.playerIDChangeHandlers.push(handler);
+ }
+
+ registerViewedPlayerChangeHandler(handler)
+ {
+ this.viewedPlayerChangeHandlers.push(handler);
+ }
+
+ registerPreViewedPlayerChangeHandler(handler)
+ {
+ this.preViewedPlayerChangeHandlers.push(handler);
+ }
+
+ rebuild()
+ {
+ this.viewPlayer.list_data = [-1].concat(g_Players.map((player, i) => i));
+ this.viewPlayer.list = [translate(this.ObserverTitle)].concat(g_Players.map(
+ (player, i) => colorizePlayernameHelper("■", i) + " " + player.name
+ ));
+ this.viewPlayer.hidden = !g_IsObserver && !this.changePerspective;
+ this.observerText.hidden = g_ViewedPlayer > 0;
+ }
+
+ onPlayersInit()
+ {
+ this.rebuild();
+
+ // Select "observer" in the view player dropdown when rejoining as a defeated player
+ let playerState = g_Players[Engine.GetPlayerID()];
+ this.viewPlayer.selected = playerState && playerState.state == "defeated" ? 0 : Engine.GetPlayerID() + 1;
+ }
+
+ setChangePerspective(enabled)
+ {
+ this.changePerspective = enabled;
+ this.rebuild();
+ this.onSelectionChange();
+ }
+
+ selectViewPlayer(playerID)
+ {
+ this.viewPlayer.selected = playerID;
+ }
+
+ onSelectionChange()
+ {
+ let playerID = this.viewPlayer.selected - 1;
+ if (playerID < -1 || playerID > g_Players.length - 1)
+ {
+ error("Can't assume invalid player ID: " + playerID);
+ return;
+ }
+
+ for (let handler of this.preViewedPlayerChangeHandlers)
+ handler();
+
+ // TODO: should set this state variable only once in this scope
+ g_IsObserver = isPlayerObserver(Engine.GetPlayerID());
+
+ if (g_IsObserver || this.changePerspective)
+ {
+ if (g_ViewedPlayer != playerID)
+ clearSelection();
+ g_ViewedPlayer = playerID;
+ }
+
+ if (this.changePerspective)
+ {
+ Engine.SetPlayerID(g_ViewedPlayer);
+ g_IsObserver = isPlayerObserver(g_ViewedPlayer);
+ }
+ Engine.SetViewedPlayer(g_ViewedPlayer);
+
+ // Send events after all states were updated
+ if (this.changePerspective)
+ for (let handler of this.playerIDChangeHandlers)
+ handler();
+
+ for (let handler of this.viewedPlayerChangeHandlers)
+ handler();
+ }
+}
+
+PlayerViewControl.prototype.ObserverTitle = markForTranslation("Observer");
Property changes on: ps/trunk/binaries/data/mods/public/gui/session/top_panel/PlayerViewControl.js
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Index: ps/trunk/binaries/data/mods/public/gui/session/top_panel/BuildLabel.js
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/session/top_panel/BuildLabel.js (nonexistent)
+++ ps/trunk/binaries/data/mods/public/gui/session/top_panel/BuildLabel.js (revision 23076)
@@ -0,0 +1,26 @@
+/**
+ * This class displays the version information in the top panel.
+ */
+class BuildLabel
+{
+ constructor(playerViewControl)
+ {
+ this.viewPlayer = Engine.GetGUIObjectByName("viewPlayer");
+ this.buildLabel = Engine.GetGUIObjectByName("buildLabel");
+
+ Engine.GetGUIObjectByName("buildTimeLabel").caption = getBuildString();
+
+ playerViewControl.registerViewedPlayerChangeHandler(this.onViewedPlayerChanged.bind(this));
+ }
+
+ onViewedPlayerChanged()
+ {
+ let isPlayer = g_ViewedPlayer > 0;
+ this.buildLabel.hidden = isPlayer && !this.viewPlayer.hidden;
+ this.buildLabel.size = isPlayer ? this.SizePlayer : this.SizeObserver;
+ }
+}
+
+BuildLabel.prototype.SizePlayer = "50%+44 0 100%-283 100%";
+
+BuildLabel.prototype.SizeObserver = "155 0 85%-279 100%";
Property changes on: ps/trunk/binaries/data/mods/public/gui/session/top_panel/BuildLabel.js
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Index: ps/trunk/binaries/data/mods/public/gui/session/top_panel/CivIcon.js
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/session/top_panel/CivIcon.js (nonexistent)
+++ ps/trunk/binaries/data/mods/public/gui/session/top_panel/CivIcon.js (revision 23076)
@@ -0,0 +1,42 @@
+/**
+ * This displas the emblem of the civilization of the currently viewed player in the top panel.
+ * If clicked, it opens the structure tree or history dialog for the last viewed civilization.
+ */
+class CivIcon
+{
+ constructor(playerViewControl)
+ {
+ this.civIcon = Engine.GetGUIObjectByName("civIcon");
+ this.civIconOverlay = Engine.GetGUIObjectByName("civIconOverlay");
+ this.civIconOverlay.onPress = this.onPress.bind(this);
+ playerViewControl.registerViewedPlayerChangeHandler(this.rebuild.bind(this));
+ registerHotkeyChangeHandler(this.rebuild.bind(this));
+ }
+
+ onPress()
+ {
+ openStrucTree(g_CivInfo.page);
+ }
+
+ rebuild()
+ {
+ let hidden = g_ViewedPlayer <= 0;
+ this.civIcon.hidden = hidden;
+ if (hidden)
+ return;
+
+ let civData = g_CivData[g_Players[g_ViewedPlayer].civ];
+
+ this.civIcon.sprite = "stretched:" + civData.Emblem;
+ this.civIconOverlay.tooltip = sprintf(translate(this.OverlayTooltip), {
+ "civ": setStringTags(civData.Name, this.CivTags),
+ "hotkey_civinfo": colorizeHotkey("%(hotkey)s", "civinfo"),
+ "hotkey_structree": colorizeHotkey("%(hotkey)s", "structree")
+ });
+ }
+}
+
+CivIcon.prototype.OverlayTooltip =
+ markForTranslation("%(civ)s\n%(hotkey_civinfo)s / %(hotkey_structree)s: View History / Structure Tree\nLast opened will be reopened on click.");
+
+CivIcon.prototype.CivTags = { "font": "sans-bold-stroke-14" };
Property changes on: ps/trunk/binaries/data/mods/public/gui/session/top_panel/CivIcon.js
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Index: ps/trunk/binaries/data/mods/public/gui/session/top_panel/CounterManager.js
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/session/top_panel/CounterManager.js (nonexistent)
+++ ps/trunk/binaries/data/mods/public/gui/session/top_panel/CounterManager.js (revision 23076)
@@ -0,0 +1,132 @@
+/**
+ * This class manages the counters in the top panel.
+ * For allies who researched team vision and observers,
+ * it displays the resources in a tooltip in a player chosen order.
+ */
+class CounterManager
+{
+ constructor(playerViewControl)
+ {
+ this.allyPlayerStates = {};
+
+ this.counters = [];
+
+ this.resourceCounts = Engine.GetGUIObjectByName("resourceCounts");
+
+ // TODO: filter resources depending on JSON file
+ for (let resCode of g_ResourceData.GetCodes())
+ this.addCounter(resCode, CounterResource);
+
+ this.addCounter("population", CounterPopulation);
+
+ this.init();
+
+ registerSimulationUpdateHandler(this.rebuild.bind(this));
+ playerViewControl.registerViewedPlayerChangeHandler(this.rebuild.bind(this));
+ }
+
+ addCounter(resCode, type)
+ {
+ let panelCount = this.resourceCounts.children.length;
+ if (this.counters.length + 1 > panelCount)
+ throw "There are " + (this.counters.length + 1) + " resource counters to display, but only " + panelCount + " panel items!";
+
+ let id = "[" + this.counters.length + "]";
+ this.counters.push(
+ new type(
+ resCode,
+ Engine.GetGUIObjectByName("resource" + id),
+ Engine.GetGUIObjectByName("resource" + id + "_icon"),
+ Engine.GetGUIObjectByName("resource" + id + "_count")));
+ }
+
+ init()
+ {
+ horizontallySpaceObjects("resourceCounts", this.counters.length);
+ hideRemaining("resourceCounts", this.counters.length);
+
+ for (let counter of this.counters)
+ {
+ counter.icon.sprite = "stretched:session/icons/resources/" + counter.resCode + ".png";
+ counter.panel.onPress = this.onPress.bind(this);
+ }
+ }
+
+ onPress()
+ {
+ Engine.ConfigDB_CreateAndWriteValueToFile(
+ "user",
+ "gui.session.respoptooltipsort",
+ String((+Engine.ConfigDB_GetValue("user", "gui.session.respoptooltipsort") + 2) % 3 - 1),
+ "config/user.cfg");
+ this.rebuild();
+ }
+
+ rebuild()
+ {
+ let hidden = g_ViewedPlayer <= 0;
+ this.resourceCounts.hidden = hidden;
+ if (hidden)
+ return;
+
+ let viewedPlayerState = g_SimState.players[g_ViewedPlayer];
+ this.allyPlayerStates = {};
+ for (let player in g_SimState.players)
+ if (player != 0 &&
+ player != g_ViewedPlayer &&
+ g_Players[player].state != "defeated" &&
+ (g_IsObserver ||
+ viewedPlayerState.hasSharedLos &&
+ g_Players[player].isMutualAlly[g_ViewedPlayer]))
+ this.allyPlayerStates[player] = g_SimState.players[player];
+
+ this.selectedOrder = +Engine.ConfigDB_GetValue("user", "gui.session.respoptooltipsort");
+ this.orderTooltip = this.getOrderTooltip();
+
+ for (let counter of this.counters)
+ {
+ let hidden = g_ViewedPlayer <= 0;
+ counter.panel.hidden = hidden;
+ if (!hidden)
+ counter.rebuild(viewedPlayerState, this.getAllyStatTooltip.bind(this));
+ }
+ }
+
+ getOrderTooltip()
+ {
+ if (!Object.keys(this.allyPlayerStates).length)
+ return "";
+
+ return "\n" + sprintf(translate("%(order)s: %(hotkey)s to change order."), {
+ "hotkey": setStringTags("\\[Click]", g_HotkeyTags),
+ "order":
+ this.selectedOrder == 0 ?
+ translate("Unordered") :
+ this.selectedOrder == 1 ?
+ translate("Descending") :
+ translate("Ascending")
+ })
+ }
+
+ getAllyStatTooltip(getTooltipData)
+ {
+ let tooltipData = [];
+
+ for (let playerID in this.allyPlayerStates)
+ {
+ let playername = colorizePlayernameHelper("■", playerID) + " " + g_Players[playerID].name;
+ tooltipData.push(getTooltipData(this.allyPlayerStates[playerID], playername));
+ }
+
+ if (this.selectedOrder)
+ tooltipData.sort((a, b) => this.selectedOrder * (b.orderValue - a.orderValue));
+
+ return this.orderTooltip +
+ tooltipData.reduce((result, data) =>
+ result + "\n" + sprintf(translate(this.AllyStatTooltip), data), "");
+ }
+}
+
+CounterManager.ResourceTitleTags = { "font": "sans-bold-16" };
+
+CounterManager.prototype.AllyStatTooltip = markForTranslation("%(playername)s: %(statValue)s");
Property changes on: ps/trunk/binaries/data/mods/public/gui/session/top_panel/CounterManager.js
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Index: ps/trunk/binaries/data/mods/public/gui/session/top_panel/CounterResource.js
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/session/top_panel/CounterResource.js (nonexistent)
+++ ps/trunk/binaries/data/mods/public/gui/session/top_panel/CounterResource.js (revision 23076)
@@ -0,0 +1,37 @@
+/**
+ * This class manages the counter in the top panel for one resource type.
+ */
+class CounterResource
+{
+ constructor(resCode, panel, icon, count)
+ {
+ this.resCode = resCode;
+ this.panel = panel;
+ this.icon = icon;
+ this.count = count;
+ }
+
+ rebuild(playerState, getAllyStatTooltip)
+ {
+ this.count.caption = Math.floor(playerState.resourceCounts[this.resCode]);
+
+ // TODO: Set the tooltip only if hovered?
+ let description = g_ResourceData.GetResource(this.resCode).description;
+ if (description)
+ description = "\n" + translate(description);
+
+ this.panel.tooltip =
+ setStringTags(resourceNameFirstWord(this.resCode), CounterManager.ResourceTitleTags) +
+ description +
+ getAllyStatTooltip(this.getTooltipData.bind(this));
+ }
+
+ getTooltipData(playerState, playername)
+ {
+ return {
+ "playername": playername,
+ "statValue": Math.round(playerState.resourceCounts[this.resCode]),
+ "orderValue": Math.round(playerState.resourceCounts[this.resCode])
+ };
+ }
+}
Property changes on: ps/trunk/binaries/data/mods/public/gui/session/top_panel/CounterResource.js
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Index: ps/trunk/binaries/data/mods/public/gui/session/top_panel/FollowPlayer.js
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/session/top_panel/FollowPlayer.js (nonexistent)
+++ ps/trunk/binaries/data/mods/public/gui/session/top_panel/FollowPlayer.js (revision 23076)
@@ -0,0 +1,35 @@
+/**
+ * This class manages the checkbox that enables the observermode option to follow the commands of a player.
+ */
+class FollowPlayer
+{
+ constructor(playerViewControl)
+ {
+ this.viewPlayer = Engine.GetGUIObjectByName("viewPlayer");
+ this.followPlayerLabel = Engine.GetGUIObjectByName("followPlayerLabel");
+ this.optionFollowPlayer = Engine.GetGUIObjectByName("optionFollowPlayer");
+ this.followPlayer = Engine.GetGUIObjectByName("followPlayer");
+
+ this.followPlayer.onPress = this.onPress.bind(this);
+ this.followPlayer.onWindowResized = this.onWindowResized.bind(this);
+ playerViewControl.registerViewedPlayerChangeHandler(this.onViewedPlayerChange.bind(this));
+ }
+
+ onPress()
+ {
+ g_FollowPlayer = !g_FollowPlayer;
+ }
+
+ onViewedPlayerChange()
+ {
+ // Following gaia can be interesting on scripted maps
+ this.optionFollowPlayer.hidden = !g_IsObserver || g_ViewedPlayer == -1;
+ }
+
+ onWindowResized()
+ {
+ this.followPlayerLabel.hidden =
+ this.followPlayerLabel.getComputedSize().left + this.followPlayerLabel.getTextSize().width >
+ this.viewPlayer.getComputedSize().left;
+ }
+}
Property changes on: ps/trunk/binaries/data/mods/public/gui/session/top_panel/FollowPlayer.js
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Index: ps/trunk/binaries/data/mods/public/gui/session/top_panel/IconButtons/DiplomacyDialogButton.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/session/top_panel/IconButtons/DiplomacyDialogButton.xml (nonexistent)
+++ ps/trunk/binaries/data/mods/public/gui/session/top_panel/IconButtons/DiplomacyDialogButton.xml (revision 23076)
@@ -0,0 +1,11 @@
+
+
+
+
+
Property changes on: ps/trunk/binaries/data/mods/public/gui/session/top_panel/IconButtons/DiplomacyDialogButton.xml
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Index: ps/trunk/binaries/data/mods/public/gui/session/top_panel/IconButtons/ObjectivesDialogButton.js
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/session/top_panel/IconButtons/ObjectivesDialogButton.js (nonexistent)
+++ ps/trunk/binaries/data/mods/public/gui/session/top_panel/IconButtons/ObjectivesDialogButton.js (revision 23076)
@@ -0,0 +1,23 @@
+/**
+ * This class handles the button that displays the games objectives.
+ */
+class ObjectivesDialogButton
+{
+ constructor(objectivesDialog)
+ {
+ this.objectivesButton = Engine.GetGUIObjectByName("objectivesButton");
+ this.objectivesButton.enabled = !Engine.IsAtlasRunning();
+ this.objectivesButton.onPress = objectivesDialog.toggle.bind(objectivesDialog);
+
+ registerHotkeyChangeHandler(this.onHotkeyChange.bind(this));
+ }
+
+ onHotkeyChange()
+ {
+ this.objectivesButton.tooltip =
+ colorizeHotkey("%(hotkey)s" + " ", "session.gui.objectives.toggle") +
+ translate(this.Tooltip);
+ }
+}
+
+ObjectivesDialogButton.prototype.Tooltip = markForTranslation("Objectives");
Property changes on: ps/trunk/binaries/data/mods/public/gui/session/top_panel/IconButtons/ObjectivesDialogButton.js
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Index: ps/trunk/binaries/data/mods/public/gui/session/top_panel/IconButtons/TradeDialogButton.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/session/top_panel/IconButtons/TradeDialogButton.xml (nonexistent)
+++ ps/trunk/binaries/data/mods/public/gui/session/top_panel/IconButtons/TradeDialogButton.xml (revision 23076)
@@ -0,0 +1,10 @@
+
+
+
+
Property changes on: ps/trunk/binaries/data/mods/public/gui/session/top_panel/IconButtons/TradeDialogButton.xml
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Index: ps/trunk/binaries/data/mods/public/gui/session/top_panel/PlayerViewControl.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/session/top_panel/PlayerViewControl.xml (nonexistent)
+++ ps/trunk/binaries/data/mods/public/gui/session/top_panel/PlayerViewControl.xml (revision 23076)
@@ -0,0 +1,20 @@
+
+
+
+ Choose player to view
+
+
+
+ Observer Mode
+
+
+
Property changes on: ps/trunk/binaries/data/mods/public/gui/session/top_panel/PlayerViewControl.xml
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Index: ps/trunk/binaries/data/mods/public/gui/session/top_panel/FollowPlayer.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/session/top_panel/FollowPlayer.xml (nonexistent)
+++ ps/trunk/binaries/data/mods/public/gui/session/top_panel/FollowPlayer.xml (revision 23076)
@@ -0,0 +1,18 @@
+
+
+
+
+ Follow Player
+
+
+
+ Follow Player
+
+
+
Property changes on: ps/trunk/binaries/data/mods/public/gui/session/top_panel/FollowPlayer.xml
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Index: ps/trunk/binaries/data/mods/public/gui/session/top_panel/IconButtons/GameSpeedButton.js
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/session/top_panel/IconButtons/GameSpeedButton.js (nonexistent)
+++ ps/trunk/binaries/data/mods/public/gui/session/top_panel/IconButtons/GameSpeedButton.js (revision 23076)
@@ -0,0 +1,12 @@
+/**
+ * This class handles the button that shows the gamespeed control.
+ */
+class GameSpeedButton
+{
+ constructor(gameSpeedControl)
+ {
+ let gameSpeedButton = Engine.GetGUIObjectByName("gameSpeedButton");
+ gameSpeedButton.onPress = gameSpeedControl.toggle.bind(gameSpeedControl);
+ gameSpeedButton.hidden = g_IsNetworked;
+ }
+}
Property changes on: ps/trunk/binaries/data/mods/public/gui/session/top_panel/IconButtons/GameSpeedButton.js
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Index: ps/trunk/binaries/data/mods/public/gui/session/top_panel/IconButtons/ObjectivesDialogButton.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/session/top_panel/IconButtons/ObjectivesDialogButton.xml (nonexistent)
+++ ps/trunk/binaries/data/mods/public/gui/session/top_panel/IconButtons/ObjectivesDialogButton.xml (revision 23076)
@@ -0,0 +1,16 @@
+
+
+
+
Property changes on: ps/trunk/binaries/data/mods/public/gui/session/top_panel/IconButtons/ObjectivesDialogButton.xml
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Index: ps/trunk/binaries/data/mods/public/gui/session/top_panel/MenuButton.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/session/top_panel/MenuButton.xml (nonexistent)
+++ ps/trunk/binaries/data/mods/public/gui/session/top_panel/MenuButton.xml (revision 23076)
@@ -0,0 +1,19 @@
+
+
+
+
+
+ Menu
+
+
+ toggleMenu();
+
+
Property changes on: ps/trunk/binaries/data/mods/public/gui/session/top_panel/MenuButton.xml
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Added: svn:mime-type
## -0,0 +1 ##
+text/xml
\ No newline at end of property
Index: ps/trunk/binaries/data/mods/public/gui/session/trade/TradeDialog.js
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/session/trade/TradeDialog.js (revision 23075)
+++ ps/trunk/binaries/data/mods/public/gui/session/trade/TradeDialog.js (revision 23076)
@@ -1,72 +1,83 @@
/**
* This class manages the trading good selection, idle trader information and barter panel.
*/
class TradeDialog
{
- constructor()
+ constructor(playerViewControl)
{
this.tradePanel = new this.TradePanel();
this.barterPanel = new this.BarterPanel();
this.tradeDialogPanel = Engine.GetGUIObjectByName("tradeDialogPanel");
+ registerPlayersInitHandler(this.onPlayersInit.bind(this));
Engine.GetGUIObjectByName("closeTrade").onPress = this.close.bind(this);
+
+ registerSimulationUpdateHandler(this.updateIfOpen.bind(this))
+ registerEntitySelectionChangeHandler(this.updateIfOpen.bind(this));
+ playerViewControl.registerViewedPlayerChangeHandler(this.onViewedPlayerChange.bind(this));
}
open()
{
closeOpenDialogs();
if (g_ViewedPlayer < 1)
return;
this.updatePanels();
this.tradeDialogPanel.hidden = false;
}
close()
{
this.tradeDialogPanel.hidden = true;
}
isOpen()
{
return !this.tradeDialogPanel.hidden;
}
+ onViewedPlayerChange()
+ {
+ if (g_ViewedPlayer >= 1)
+ this.updateIfOpen();
+ else
+ this.close();
+ }
+
toggle()
{
let open = this.isOpen();
closeOpenDialogs();
if (!open)
this.open();
}
- update()
+ updateIfOpen()
{
- if (!this.isOpen())
- return;
-
- this.updatePanels();
+ if (this.isOpen())
+ this.updatePanels();
}
updatePanels()
{
this.barterPanel.update();
this.tradePanel.update();
}
- resize()
+ onPlayersInit()
{
let size = this.tradeDialogPanel.size;
let width = 1/2 * Math.max(
TradeDialog.prototype.BarterPanel.getWidthOffset(),
TradeDialog.prototype.TradePanel.getWidthOffset());
size.left -= width;
size.right += width;
this.tradeDialogPanel.size = size;
}
}