Implement session event subscription system and rewrite TopPanel, PlayerViewControl, GameSpeed, Pausing, ObjectivesDialog to use object orientation, refs #5387.

New controller classes: PlayerViewControl, PauseControl,
GameSpeedControl
New viewer classes: ObjectivesDialog, PauseOverlay, FollowPlayer,
TopPanel (BuildLabel, CivIcon, CounterManager, CounterPopulation,
CounterResource refs 7e14a33411/D1113, GameSpeedButton,
ObjectivesDialogButton)

New events: SimulationUpdate, EntitySelectionChange, ViewedPlayerChange,
PreViewedPlayerChangeHandler, PlayerIDChange, PlayersInit,
PlayersFinished, Pause, DiplomacyColorsChange, HotkeyChange, refs #2604
Improves GUI onSimuationUpdate performance without selected entities by
allegedly 30%.

Delete misleading dead code resign command from leaveGame and rename to
endGame. The command is not sent via network (see fa85527baf) nor
processed in simulation, because the Game instance is deleted
immediately thereafter, introduced in fcedcae052, refs a3e1c68b9a,
39ffb0a6bd, 9f796068f8.
Remove explicitResume 0 value from e57c99c6f6 and 8ae67ed15f which
should have been a false if defined, and is equivalent to the default.
Restore fast forwarding option from cd571035bb/D595 for developers
changing the perspective to observer or player following 56308ec1ad.
Add pausing for the delete dialog missing following 7a7ebaa983.

Differential Revision: https://code.wildfiregames.com/D2378
This was SVN commit r23076.
This commit is contained in:
elexis 2019-10-17 15:08:56 +00:00
parent ed11b3f039
commit e3f43f6352
50 changed files with 1128 additions and 709 deletions

View file

@ -12,9 +12,18 @@ class DiplomacyColors
// 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));
}
onPlayerInit()
registerDiplomacyColorsChangeHandler(handler)
{
this.diplomacyColorsChangeHandlers.push(handler);
}
onPlayersInit()
{
this.computeTeamColors();
}
@ -61,7 +70,8 @@ class DiplomacyColors
"selected": g_Selection.toList()
});
updateGUIObjects();
for (let handler of this.diplomacyColorsChangeHandlers)
handler(this.enabled);
}
computeTeamColors()

View file

@ -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]);
}
}

View file

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<object
name="gameSpeed"
type="dropdown"
style="ModernDropDown"
size="100%-390 40 100%-230 65"
hidden="true"
tooltip_style="sessionToolTip"
dropdown_size="300"
>
<translatableAttribute id="tooltip">Choose game speed</translatableAttribute>
</object>

View file

@ -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();
}
}

View file

@ -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");

View file

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<object type="button" name="pauseOverlay" tooltip_style="sessionToolTip" hidden="true" z="0">
<object type="image" sprite="sessionOverlayBackground" ghost="true"/>
<object size="50%-128 40%-20 50%+128 40%+20" type="text" style="PauseText" ghost="true">
<translatableAttribute id="caption">Game Paused</translatableAttribute>
</object>
<object name="resumeMessage" size="50%-128 40%+20 50%+128 40%+40" type="text" style="ResumeMessageText" ghost="true">
<translatableAttribute id="caption">Click to Resume Game</translatableAttribute>
</object>
<object name="pausedByText" size="30% 40%+50 70% 100%" type="text" style="netStatusPlayersText" ghost="true" hidden="true"/>
</object>

View file

@ -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);
}
}

View file

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<object name="topPanel"
type="image"
sprite="topPanel"
size="-3 0 100%+3 36"
>
<include directory="gui/session/top_panel/"/>
<include directory="gui/session/top_panel/IconButtons/"/>
</object>

View file

@ -4,7 +4,7 @@
*/
class Chat
{
constructor()
constructor(playerViewControl)
{
this.ChatWindow = new ChatWindow();
this.ChatOverlay = new ChatOverlay();
@ -35,6 +35,9 @@ class Chat
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"); });

View file

@ -35,6 +35,7 @@ class ChatWindow
this.extendedChat.checked = Engine.ConfigDB_GetValue("user", "chat.session.extended") == "true";
this.chatDialogPanel.onWindowResized = this.resizeChatWindow.bind(this);
this.resizeChatWindow();
}

View file

@ -1,13 +1,15 @@
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() {
@ -25,13 +27,14 @@ DeveloperOverlay.prototype.getCommands = function() {
{
"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();
},
},
{
@ -173,19 +176,11 @@ DeveloperOverlay.prototype.layout = function()
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()
@ -215,8 +210,15 @@ DeveloperOverlay.prototype.toggle = function()
DeveloperOverlay.prototype.update = function()
{
this.updateValues();
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)
@ -252,19 +254,6 @@ 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;
};

View file

@ -4,7 +4,7 @@
*/
class DiplomacyDialog
{
constructor(diplomacyColors)
constructor(playerViewControl, diplomacyColors)
{
this.diplomacyDialogCeasefireCounter = new DiplomacyDialogCeasefireCounter();
this.diplomacyDialogColorsButton = new DiplomacyDialogColorsButton(diplomacyColors);
@ -12,34 +12,40 @@ class DiplomacyDialog
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();
}

View file

@ -5,22 +5,25 @@ 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);
}
}

View file

@ -39,10 +39,6 @@
</action>
</object>
<object hotkey="pause">
<action on="Press">togglePause();</action>
</object>
<object hotkey="quicksave">
<action on="Press">Engine.QuickSave(getSavedGameData());</action>
</object>

View file

@ -29,19 +29,21 @@ var g_CivInfo = {
var g_IsMenuOpen = false;
var g_IsObjectivesOpen = false;
/**
* Remember last viewed summary panel and charts.
*/
var g_SummarySelectedData;
function initMenu()
function initMenu(playerViewControl, pauseControl)
{
Engine.GetGUIObjectByName("menu").size = "100%-164 " + MENU_TOP + " 100% " + MENU_BOTTOM;
Engine.GetGUIObjectByName("lobbyButton").enabled = Engine.HasXmppClient();
playerViewControl.registerViewedPlayerChangeHandler(updateResignButton);
updatePauseButton();
pauseControl.registerPauseHandler(updatePauseButton);
// TODO: Atlas should pass g_GameAttributes.settings
for (let button of ["menuExitButton", "summaryButton", "objectivesButton"])
for (let button of ["menuExitButton", "summaryButton"])
Engine.GetGUIObjectByName(button).enabled = !Engine.IsAtlasRunning();
}
@ -81,6 +83,22 @@ 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();
@ -104,26 +122,25 @@ function chatMenuButton()
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?"),
@ -131,7 +148,7 @@ function exitMenuButton()
},
"singleplayer": {
"caption": translate("Are you sure you want to quit?"),
"buttons": [resumeGame, leaveGame]
"buttons": [resumeGame, endGame]
}
};
@ -154,14 +171,13 @@ function resignQuestion()
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)
{
@ -169,6 +185,7 @@ function openDeleteDialog(selection)
"type": "delete-entities",
"entities": selectionArg
});
resumeGame();
};
messageBox(
@ -184,7 +201,7 @@ function openDeleteDialog(selection)
function openSave()
{
closeOpenDialogs();
pauseGame();
g_PauseControl.implicitPause();
Engine.PushGuiPage(
"page_loadgame.xml",
@ -195,7 +212,7 @@ function openSave()
function openOptions()
{
closeOpenDialogs();
pauseGame();
g_PauseControl.implicitPause();
Engine.PushGuiPage(
"page_options.xml",
@ -216,73 +233,6 @@ function toggleTutorial()
!Engine.GetGUIObjectByName("tutorialText").caption;
}
function updateGameSpeedControl()
{
Engine.GetGUIObjectByName("gameSpeedButton").hidden = g_IsNetworked;
let player = g_Players[Engine.GetPlayerID()];
g_GameSpeeds = getGameSpeedChoices(!player || player.state != "active");
let gameSpeed = Engine.GetGUIObjectByName("gameSpeed");
gameSpeed.list = g_GameSpeeds.Title;
gameSpeed.list_data = g_GameSpeeds.Speed;
let simRate = Engine.GetSimRate();
let gameSpeedIdx = g_GameSpeeds.Speed.indexOf(+simRate.toFixed(2));
if (gameSpeedIdx == -1)
warn("Unknown gamespeed:" + simRate);
gameSpeed.selected = gameSpeedIdx != -1 ? gameSpeedIdx : g_GameSpeeds.Default;
gameSpeed.onSelectionChange = function() {
changeGameSpeed(+this.list_data[this.selected]);
};
}
function toggleGameSpeed()
{
let gameSpeed = Engine.GetGUIObjectByName("gameSpeed");
gameSpeed.hidden = !gameSpeed.hidden;
}
function toggleObjectives()
{
let open = g_IsObjectivesOpen;
closeOpenDialogs();
if (!open)
openObjectives();
}
function openObjectives()
{
g_IsObjectivesOpen = true;
let player = g_Players[Engine.GetPlayerID()];
let playerState = player && player.state;
let isActive = !playerState || playerState == "active";
Engine.GetGUIObjectByName("gameDescriptionText").caption = getGameDescription();
let objectivesPlayerstate = Engine.GetGUIObjectByName("objectivesPlayerstate");
objectivesPlayerstate.hidden = isActive;
objectivesPlayerstate.caption = g_PlayerStateMessages[playerState] || "";
let gameDescription = Engine.GetGUIObjectByName("gameDescription");
let gameDescriptionSize = gameDescription.size;
gameDescriptionSize.top = Engine.GetGUIObjectByName(
isActive ? "objectivesTitle" : "objectivesPlayerstate").size.bottom;
gameDescription.size = gameDescriptionSize;
Engine.GetGUIObjectByName("objectivesPanel").hidden = false;
}
function closeObjectives()
{
g_IsObjectivesOpen = false;
Engine.GetGUIObjectByName("objectivesPanel").hidden = true;
}
/**
* Allows players to see their own summary.
* If they have shared ally vision researched, they are able to see the summary of there allies too.
@ -290,7 +240,7 @@ function closeObjectives()
function openGameSummary()
{
closeOpenDialogs();
pauseGame();
g_PauseControl.implicitPause();
let extendedSimState = Engine.GuiInterfaceCall("GetExtendedSimulationState");
Engine.PushGuiPage(
@ -309,13 +259,17 @@ function openGameSummary()
},
"selectedData": g_SummarySelectedData
},
resumeGameAndSaveSummarySelectedData);
data =>
{
g_SummarySelectedData = data.summarySelectedData;
g_PauseControl.implicitResume();
});
}
function openStrucTree(page)
{
closeOpenDialogs();
pauseGame();
g_PauseControl.implicitPause();
Engine.PushGuiPage(
page,
@ -340,103 +294,18 @@ function storeCivInfoPage(data)
}
}
/**
* 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();
}

View file

@ -79,13 +79,11 @@
<!-- Pause / Resume Button -->
<object type="button"
name="pauseButton"
hotkey="pause"
style="StoneButtonFancy"
size="0 192 100% 220"
tooltip_style="sessionToolTip"
>
<translatableAttribute id="caption">Pause</translatableAttribute>
<action on="Press">togglePause();</action>
</object>
/>
<!-- Resign button -->
<object type="button"

View file

@ -30,7 +30,7 @@ var g_NetMessageTypes = {
handlePlayerAssignmentsMessage(msg);
},
"paused": msg => {
setClientPauseState(msg.guid, msg.pause);
g_PauseControl.setClientPauseState(msg.guid, msg.pause);
},
"clients-loading": msg => {
handleClientsLoadingMessage(msg.guids);
@ -388,7 +388,7 @@ function updateTutorial(notification)
{
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.");
@ -579,7 +579,7 @@ function onClientJoin(guid)
function onClientLeave(guid)
{
setClientPauseState(guid, false);
g_PauseControl.setClientPauseState(guid, false);
for (let id in g_Players)
if (g_Players[id].guid == guid)
@ -689,5 +689,5 @@ function openDialog(dialogName, data, player)
}
}
pauseGame();
g_PauseControl.implicitPause();
}

View file

@ -8,22 +8,26 @@ class MiniMapDiplomacyColorsButton
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);
}
}

View file

@ -3,19 +3,26 @@
*/
class MiniMapIdleWorkerButton
{
constructor(idleClasses)
constructor(playerViewControl, idleClasses)
{
this.idleWorkerButton = Engine.GetGUIObjectByName("idleWorkerButton");
this.idleWorkerButton.onPress = this.onPress.bind(this);
this.idleClasses = idleClasses;
registerHotkeyChangeHandler(this.onHotkeyChange.bind(this));
registerSimulationUpdateHandler(this.rebuild.bind(this));
playerViewControl.registerViewedPlayerChangeHandler(this.rebuild.bind(this));
}
update()
onHotkeyChange()
{
this.idleWorkerButton.tooltip =
colorizeHotkey("%(hotkey)s" + " ", "selection.idleworker") +
translate(this.Tooltip);
}
rebuild()
{
this.idleWorkerButton.enabled = Engine.GuiInterfaceCall("HasIdleUnits", {
"viewedPlayer": g_ViewedPlayer,
"idleClasses": this.idleClasses,

View file

@ -3,16 +3,10 @@
*/
class MiniMapPanel
{
constructor(diplomacyColors, idleWorkerClasses)
constructor(playerViewControl, diplomacyColors, idleWorkerClasses)
{
this.diplomacyColorsButton = new MiniMapDiplomacyColorsButton(diplomacyColors);
this.idleWorkerButton = new MiniMapIdleWorkerButton(idleWorkerClasses);
this.idleWorkerButton = new MiniMapIdleWorkerButton(playerViewControl, idleWorkerClasses);
this.minimap = new Minimap();
}
update()
{
this.diplomacyColorsButton.update();
this.idleWorkerButton.update();
}
}

View file

@ -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;
}
}

View file

@ -39,9 +39,8 @@
/>
</object>
<object size="50%-64 100%-50 50%+64 100%-22" type="button" style="StoneButton">
<object name="closeObjectives" size="50%-64 100%-50 50%+64 100%-22" type="button" style="StoneButton">
<translatableAttribute id="caption">Close</translatableAttribute>
<action on="Press">closeObjectives();</action>
</object>
</object>

View file

@ -1147,7 +1147,7 @@ function initSelectionPanels()
*/
function showTemplateDetails(templateName, civCode)
{
pauseGame();
g_PauseControl.implicitPause();
Engine.PushGuiPage(
"page_viewer.xml",

View file

@ -11,25 +11,16 @@ const g_VictoryDurations = prepareForDropdown(g_Settings && g_Settings.VictoryDu
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
@ -77,16 +68,6 @@ var g_HasRejoined = false;
*/
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.
*/
@ -138,11 +119,6 @@ var g_ShowGUI = true;
*/
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).
*/
@ -153,6 +129,33 @@ 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.
@ -267,20 +270,40 @@ function init(initData, hotloadData)
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)
{
@ -290,21 +313,36 @@ function init(initData, hotloadData)
}
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()
@ -364,16 +402,6 @@ 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) => {
@ -419,64 +447,14 @@ function initializeMusic()
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();
}
/**
@ -523,14 +501,16 @@ function playersFinished(players, victoryString, won)
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;
@ -544,100 +524,21 @@ function playersFinished(players, victoryString, won)
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");
@ -724,6 +625,9 @@ function onTick()
// 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
@ -734,29 +638,10 @@ function onTick()
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.
@ -774,6 +659,10 @@ function onSimulationUpdate()
GetMultipleEntityStates(g_Selection.toList());
for (let handler of g_SimulationUpdateHandlers)
handler();
// TODO: Move to handlers
updateCinemaPath();
handleNotifications();
updateGUIObjects();
@ -791,6 +680,7 @@ function confirmExit()
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) =>
@ -809,8 +699,7 @@ function confirmExit()
translate("VICTORIOUS!") :
translate("DEFEATED!"),
askExit ? [translate("No"), translate("Yes")] : [translate("OK")],
askExit ? [resumeGame, leaveGame] : [resumeGame]
);
askExit ? [resumeGame, endGame] : [resumeGame]);
g_ConfirmExit = false;
}
@ -829,6 +718,7 @@ function updateCinemaPath()
Engine.Renderer_SetSilhouettesEnabled(!isPlayingCinemaPath && Engine.ConfigDB_GetValue("user", "silhouettes") == "true");
}
// TODO: Use event subscription onSimulationUpdate, onEntitySelectionChange, onPlayerViewChange, ... instead
function updateGUIObjects()
{
g_Selection.update();
@ -843,18 +733,11 @@ function updateGUIObjects()
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.
@ -862,29 +745,18 @@ function updateGUIObjects()
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]);
}
/**
@ -1065,88 +937,6 @@ function updateGroups()
}
}
/**
* 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);
@ -1243,6 +1033,12 @@ function recalculateStatusBarDisplay(remove = false)
});
}
function removeStatusBarDisplay()
{
if (g_ShowAllStatusBars)
recalculateStatusBarDisplay(true);
}
/**
* Inverts the given configuration boolean and returns the current state.
* For example "silhouettes".

View file

@ -9,7 +9,9 @@
<script directory="gui/session/diplomacy/playercontrols/"/>
<script directory="gui/session/minimap/"/>
<script directory="gui/session/top_panel/"/>
<script directory="gui/session/top_panel/IconButtons/"/>
<script directory="gui/session/trade/"/>
<script directory="gui/session/objectives/"/>
<object name="session">
@ -17,10 +19,6 @@
onTick();
</action>
<action on="WindowResized">
onWindowResized();
</action>
<action on="SavegameLoaded">
restoreSavedGameData(arguments[0]);
</action>
@ -63,23 +61,12 @@
tooltip_style="sessionToolTip"
>
<translatableAttribute id="caption">Exit</translatableAttribute>
<action on="Press">leaveGame();</action>
<action on="Press">endGame();</action>
</object>
<object name="loadingClientsText" size="50%-300 50%+60 50%+300 50%+110" type="text" style="netStatusPlayersText" hidden="true"/>
</object>
<!-- Pause Overlay -->
<object type="button" name="pauseOverlay" tooltip_style="sessionToolTip" hidden="true" z="0">
<object type="image" sprite="sessionOverlayBackground" ghost="true"/>
<object size="50%-128 40%-20 50%+128 40%+20" type="text" style="PauseText" ghost="true">
<translatableAttribute id="caption">Game Paused</translatableAttribute>
</object>
<object name="resumeMessage" size="50%-128 40%+20 50%+128 40%+40" type="text" style="ResumeMessageText" ghost="true">
<translatableAttribute id="caption">Click to Resume Game</translatableAttribute>
</object>
<object name="pausedByText" size="30% 40%+50 70% 100%" type="text" style="netStatusPlayersText" ghost="true" hidden="true"/>
<action on="Press">togglePause();</action>
</object>
<include file="gui/session/PauseOverlay.xml"/>
<!-- Notification Area -->
<object name="notificationPanel" type="image" size="50%-300 60 50%+300 120" ghost="true">
@ -103,9 +90,10 @@
<include directory="gui/session/chat/"/>
<include directory="gui/session/dialogs/"/>
<include directory="gui/session/diplomacy/"/>
<include directory="gui/session/objectives/"/>
<include file="gui/session/developer_overlay.xml"/>
<include file="gui/session/objectives_window.xml"/>
<include file="gui/session/top_panel.xml"/>
<include file="gui/session/GameSpeedControl.xml"/>
<include file="gui/session/TopPanel.xml"/>
<include file="gui/session/trade/TradeDialog.xml"/>
<include file="gui/session/tutorial_panel.xml"/>
<include file="gui/session/menu.xml"/>

View file

@ -1,57 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<object name="topPanel"
type="image"
sprite="topPanel"
size="-3 0 100%+3 36"
>
<!-- most elements are defined in this directory -->
<include directory="gui/session/top_panel/"/>
<!-- ================================ ================================ -->
<!-- "Follow Player" option for observers -->
<!-- ================================ ================================ -->
<object name="optionFollowPlayer" size="50%+54 4 50%+256 100%" hidden="true">
<!-- Checkbox -->
<object name="followPlayer"
type="checkbox"
checked="false"
style="ModernTickBox"
size="0 4 20 100%"
tooltip_style="sessionToolTip"
>
<translatableAttribute id="tooltip" context="observer mode">Follow Player</translatableAttribute>
<action on="Press">g_FollowPlayer = !g_FollowPlayer;</action>
</object>
<!-- Label -->
<object name="followPlayerLabel" type="text" size="20 2 100% 100%" text_align="left" textcolor="white">
<translatableAttribute id="caption" context="observer mode">Follow Player</translatableAttribute>
</object>
</object>
<!-- ================================ ================================ -->
<!-- Switch the view perspective to another player's (for observers and for eased development) -->
<!-- ================================ ================================ -->
<object
size="85%-279 5 100%-293 100%-5"
name="viewPlayer"
type="dropdown"
hidden="true"
z="50"
style="ModernDropDown"
tooltip_style="sessionToolTipBold"
dropdown_size="500"
>
<translatableAttribute id="tooltip">Choose player to view</translatableAttribute>
<action on="SelectionChange">selectViewPlayer(this.selected - 1);</action>
</object>
<!-- ================================ ================================ -->
<!-- Observer Mode Label -->
<!-- ================================ ================================ -->
<object size="50 4 50% 100%-2" name="observerText" type="text" style="ModernLeftLabelText" hidden="true">
<translatableAttribute id="caption">Observer Mode</translatableAttribute>
</object>
</object>

View file

@ -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%";

View file

@ -1,19 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<object size="50%+20 0 100%-226 100%" name="alphaLabel" type="text" style="ModernLabelText" text_valign="top" ghost="true">
<object name="buildLabel" ghost="true">
<!-- IMPORTANT: remember to update pregame/ProjectInformation.js in sync with this: -->
<translatableAttribute id="caption">ALPHA XXIV</translatableAttribute>
<object type="text" style="ModernLabelText" text_valign="top" ghost="true">
<translatableAttribute id="caption">ALPHA XXIV</translatableAttribute>
</object>
<!-- Displays build date and revision number-->
<object
name="buildTimeLabel"
type="text"
size="50%-128 0 50%+128 100%-2"
style="ModernLabelText"
ghost="true"
size="50%-128 0 50%+128 100%-2"
font="sans-stroke-12"
textcolor="white"
text_align="center"
text_valign="bottom"
>
<action on="Load">this.caption = getBuildString()</action>
</object>
/>
</object>

View file

@ -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" };

View file

@ -5,7 +5,5 @@
name="civIcon"
tooltip_style="sessionToolTipBold"
>
<object name="civIconOverlay" style="CivIconOverlay" type="button">
<action on="Press">openStrucTree(g_CivInfo.page)</action>
</object>
<object name="civIconOverlay" style="CivIconOverlay" type="button"/>
</object>

View file

@ -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");

View file

@ -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";

View file

@ -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])
};
}
}

View file

@ -1,14 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- This list encompasses resources and population -->
<object size="0 0 50%-90-52 100%" name="resourceCounts">
<repeat count="4">
<repeat count="5">
<object name="resource[n]" size="0 0 89 100%" type="button" style="resourceCounter" tooltip_style="sessionToolTipBold">
<object size="0 -2 40 38" type="image" name="resource[n]_icon" ghost="true"/>
<object size="34 0 100%-2 100%-2" type="text" style="resourceText" name="resource[n]_count"/>
<action on="Press">
saveResPopTooltipSort();
updatePlayerDisplay();
</action>
</object>
</repeat>
</object>

View file

@ -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;
}
}

View file

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<object name="optionFollowPlayer" size="50%+54 4 50%+256 100%" hidden="true">
<object name="followPlayer"
type="checkbox"
checked="false"
style="ModernTickBox"
size="0 4 20 100%"
tooltip_style="sessionToolTip"
>
<translatableAttribute id="tooltip" context="observer mode">Follow Player</translatableAttribute>
</object>
<object name="followPlayerLabel" type="text" size="20 2 100% 100%" text_align="left" textcolor="white">
<translatableAttribute id="caption" context="observer mode">Follow Player</translatableAttribute>
</object>
</object>

View file

@ -1,22 +1,29 @@
/**
* This class handles the button which opens the diplomacy dialog.
*/
class DiplomacyButton
class DiplomacyDialogButton
{
constructor(diplomacyDialog)
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));
}
update()
onHotkeyChange()
{
this.diplomacyButton.hidden = g_ViewedPlayer < 1;
this.diplomacyButton.tooltip =
colorizeHotkey("%(hotkey)s" + " ", "session.gui.diplomacy.toggle") +
translate(this.Tooltip);
}
onViewedPlayerChange()
{
this.diplomacyButton.hidden = g_ViewedPlayer < 1;
}
}
DiplomacyButton.prototype.Tooltip = markForTranslation("Diplomacy");
DiplomacyDialogButton.prototype.Tooltip = markForTranslation("Diplomacy");

View file

@ -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;
}
}

View file

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<object
type="button"
name="gameSpeedButton"
size="100%-284 4 100%-256 32"
style="iconButton"
tooltip_style="sessionToolTip"
>
<translatableAttribute id="tooltip">Game Speed</translatableAttribute>
<object size="5 5 100%-5 100%-5" type="image" sprite="stretched:session/icons/resources/time_small.png" ghost="true"/>
</object>

View file

@ -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");

View file

@ -13,7 +13,4 @@
sprite="stretched:session/icons/objectives.png"
ghost="true"
/>
<action on="Press">
toggleObjectives();
</action>
</object>

View file

@ -3,21 +3,29 @@
*/
class TradeDialogButton
{
constructor(tradeDialog)
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;
this.isAvailable =
g_ResourceData.GetTradableCodes().length ||
g_ResourceData.GetBarterableCodes().length;
playerViewControl.registerViewedPlayerChangeHandler(this.onViewedPlayerChange.bind(this));
registerHotkeyChangeHandler(this.onHotkeyChange.bind(this));
}
update()
onHotkeyChange()
{
this.tradeButton.hidden = g_ViewedPlayer < 1 || !this.isAvailable;
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");

View file

@ -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");

View file

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<object>
<object
size="85%-282 5 100%-290 31"
name="viewPlayer"
type="dropdown"
hidden="true"
z="50"
style="ModernDropDown"
tooltip_style="sessionToolTipBold"
dropdown_size="500"
>
<translatableAttribute id="tooltip">Choose player to view</translatableAttribute>
</object>
<object size="50 4 50% 100%-2" name="observerText" type="text" style="ModernLeftLabelText" hidden="true">
<translatableAttribute id="caption">Observer Mode</translatableAttribute>
</object>
</object>

View file

@ -1,20 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<object>
<object
type="button"
name="gameSpeedButton"
size="100%-284 4 100%-256 32"
style="iconButton"
tooltip_style="sessionToolTip"
>
<translatableAttribute id="tooltip">Game Speed</translatableAttribute>
<object size="5 5 100%-5 100%-5" type="image" sprite="stretched:session/icons/resources/time_small.png" ghost="true"/>
<action on="Press">
toggleGameSpeed();
</action>
</object>
<object size="100%-390 40 100%-230 65" name="gameSpeed" type="dropdown" style="ModernDropDown" hidden="true" tooltip_style="sessionToolTip" dropdown_size="300">
<translatableAttribute id="tooltip">Choose game speed</translatableAttribute>
</object>
</object>

View file

@ -1,10 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<object name="population" size="0 0 50%-52 100%" type="button" style="resourceCounter" tooltip_style="sessionToolTipBold">
<object size="0 -2 40 38" type="image" sprite="stretched:session/icons/resources/population.png" ghost="true"/>
<object size="32 0 100% 100%-2" type="text" style="resourceText" name="resourcePop"/>
<action on="Press">
saveResPopTooltipSort();
updatePlayerDisplay();
</action>
</object>

View file

@ -3,14 +3,19 @@
*/
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()
@ -34,6 +39,14 @@ class TradeDialog
return !this.tradeDialogPanel.hidden;
}
onViewedPlayerChange()
{
if (g_ViewedPlayer >= 1)
this.updateIfOpen();
else
this.close();
}
toggle()
{
let open = this.isOpen();
@ -43,12 +56,10 @@ class TradeDialog
this.open();
}
update()
updateIfOpen()
{
if (!this.isOpen())
return;
this.updatePanels();
if (this.isOpen())
this.updatePanels();
}
updatePanels()
@ -57,7 +68,7 @@ class TradeDialog
this.tradePanel.update();
}
resize()
onPlayersInit()
{
let size = this.tradeDialogPanel.size;