Index: ps/trunk/binaries/data/mods/public/gui/common/tab_buttons.js =================================================================== --- ps/trunk/binaries/data/mods/public/gui/common/tab_buttons.js (revision 20784) +++ ps/trunk/binaries/data/mods/public/gui/common/tab_buttons.js (revision 20785) @@ -1,71 +1,71 @@ /** * Number of categories. */ var g_TabCategoryCount; /** * Index of the currently visible tab, set first tab as default. */ var g_TabCategorySelected = 0; /** * Function to be executed when selecting a tab. The new category index is passed. */ var g_OnSelectTab; /** * Create tab buttons. * * @param {Array} categoriesData - Arrays of objects containing for every tab a (translated) label and tooltip. * @param {number} buttonHeight - Vertical distance between the top and bottom of a button. * @param {number} spacing - Vertical distance between two buttons. * @param {function} onPress - Function to be executed when a button is pressed, it gets the new category index passed. * @param {function} onSelect - Function to be executed whenever the selection changes (so also for scrolling), it gets the new category index passed. */ function placeTabButtons(categoriesData, buttonHeight, spacing, onPress, onSelect) { g_OnSelectTab = onSelect; g_TabCategoryCount = categoriesData.length; for (let category in categoriesData) { let button = Engine.GetGUIObjectByName("tabButton[" + category + "]"); if (!button) { warn("Too few tab-buttons!"); break; } button.hidden = false; let size = button.size; size.top = category * (buttonHeight + spacing) + spacing / 2; size.bottom = size.top + buttonHeight; button.size = size; button.tooltip = categoriesData[category].tooltip || ""; button.onPress = (category => function() { onPress(category); })(+category); Engine.GetGUIObjectByName("tabButtonText[" + category + "]").caption = categoriesData[category].label; } selectPanel(g_TabCategorySelected); } -/* +/** * Show next/previous panel. * @param direction - +1/-1 for forward/backward. */ function selectNextTab(direction) { selectPanel((g_TabCategorySelected + direction + g_TabCategoryCount) % g_TabCategoryCount); } function selectPanel(category) { g_TabCategorySelected = category; Engine.GetGUIObjectByName("tabButtons").children.forEach((button, j) => { button.sprite = category == j ? "ModernTabVerticalForeground" : "ModernTabVerticalBackground"; }); g_OnSelectTab(category); } Index: ps/trunk/binaries/data/mods/public/gui/replaymenu/replay_menu.js =================================================================== --- ps/trunk/binaries/data/mods/public/gui/replaymenu/replay_menu.js (revision 20784) +++ ps/trunk/binaries/data/mods/public/gui/replaymenu/replay_menu.js (revision 20785) @@ -1,364 +1,364 @@ /** * Used for checking replay compatibility. */ const g_EngineInfo = Engine.GetEngineInfo(); /** * Needed for formatPlayerInfo to show the player civs in the details. */ const g_CivData = loadCivData(false, false); /** * Used for creating the mapsize filter. */ const g_MapSizes = prepareForDropdown(g_Settings && g_Settings.MapSizes); /** * All replays found in the directory. */ var g_Replays = []; /** * List of replays after applying the display filter. */ var g_ReplaysFiltered = []; /** * Array of unique usernames of all replays. Used for autocompleting usernames. */ var g_Playernames = []; /** * Sorted list of unique maptitles. Used by mapfilter. */ var g_MapNames = []; /** * Sorted list of the victory conditions occuring in the replays */ var g_VictoryConditions = []; /** * Directory name of the currently selected replay. Used to restore the selection after changing filters. */ var g_SelectedReplayDirectory = ""; /** * Skip duplicate expensive GUI updates before init is complete. */ var g_ReplaysLoaded = false; /** - * Remember the name of the currently opened view panel. + * Remember last viewed summary panel and charts. */ -var g_SummarySelectedData = ""; +var g_SummarySelectedData; /** * Initializes globals, loads replays and displays the list. */ function init(data) { if (!g_Settings) { Engine.SwitchGuiPage("page_pregame.xml"); return; } loadReplays(data && data.replaySelectionData, false); if (!g_Replays) { Engine.SwitchGuiPage("page_pregame.xml"); return; } initHotkeyTooltips(); displayReplayList(); if (data && data.summarySelectedData) g_SummarySelectedData = data.summarySelectedData; } /** * Store the list of replays loaded in C++ in g_Replays. * Check timestamp and compatibility and extract g_Playernames, g_MapNames, g_VictoryConditions. * Restore selected filters and item. * @param replaySelectionData - Currently selected filters and item to be restored after the loading. * @param compareFiles - If true, compares files briefly (which might be slow with optical harddrives), * otherwise blindly trusts the replay cache. */ function loadReplays(replaySelectionData, compareFiles) { g_Replays = Engine.GetReplays(compareFiles); if (!g_Replays) return; g_Playernames = []; for (let replay of g_Replays) { let nonAIPlayers = 0; // Check replay for compatibility replay.isCompatible = isReplayCompatible(replay); sanitizeGameAttributes(replay.attribs); // Extract map names if (g_MapNames.indexOf(replay.attribs.settings.Name) == -1 && replay.attribs.settings.Name != "") g_MapNames.push(replay.attribs.settings.Name); // Extract victory conditions if (replay.attribs.settings.GameType && g_VictoryConditions.indexOf(replay.attribs.settings.GameType) == -1) g_VictoryConditions.push(replay.attribs.settings.GameType); // Extract playernames for (let playerData of replay.attribs.settings.PlayerData) { if (!playerData || playerData.AI) continue; // Remove rating from nick let playername = playerData.Name; let ratingStart = playername.indexOf(" ("); if (ratingStart != -1) playername = playername.substr(0, ratingStart); if (g_Playernames.indexOf(playername) == -1) g_Playernames.push(playername); ++nonAIPlayers; } replay.isMultiplayer = nonAIPlayers > 1; replay.isRated = nonAIPlayers == 2 && replay.attribs.settings.PlayerData.length == 2 && replay.attribs.settings.RatingEnabled; } g_MapNames.sort(); g_VictoryConditions.sort(); // Reload filters (since they depend on g_Replays and its derivatives) initFilters(replaySelectionData && replaySelectionData.filters); // Restore user selection if (replaySelectionData) { if (replaySelectionData.directory) g_SelectedReplayDirectory = replaySelectionData.directory; let replaySelection = Engine.GetGUIObjectByName("replaySelection"); if (replaySelectionData.column) replaySelection.selected_column = replaySelectionData.column; if (replaySelectionData.columnOrder) replaySelection.selected_column_order = replaySelectionData.columnOrder; } g_ReplaysLoaded = true; } /** * We may encounter malformed replays. */ function sanitizeGameAttributes(attribs) { if (!attribs.settings) attribs.settings = {}; if (!attribs.settings.Size) attribs.settings.Size = -1; if (!attribs.settings.Name) attribs.settings.Name = ""; if (!attribs.settings.PlayerData) attribs.settings.PlayerData = []; if (!attribs.settings.PopulationCap) attribs.settings.PopulationCap = 300; if (!attribs.settings.mapType) attribs.settings.mapType = "skirmish"; if (!attribs.settings.GameType) attribs.settings.GameType = "conquest"; // Remove gaia if (attribs.settings.PlayerData.length && attribs.settings.PlayerData[0] == null) attribs.settings.PlayerData.shift(); attribs.settings.PlayerData.forEach((pData, index) => { if (!pData.Name) pData.Name = ""; }); } function initHotkeyTooltips() { Engine.GetGUIObjectByName("playersFilter").tooltip = translate("Filter replays by typing one or more, partial or complete playernames.") + " " + colorizeAutocompleteHotkey(); Engine.GetGUIObjectByName("deleteReplayButton").tooltip = deleteTooltip(); } /** * Filter g_Replays, fill the GUI list with that data and show the description of the current replay. */ function displayReplayList() { if (!g_ReplaysLoaded) return; // Remember previously selected replay var replaySelection = Engine.GetGUIObjectByName("replaySelection"); if (replaySelection.selected != -1) g_SelectedReplayDirectory = g_ReplaysFiltered[replaySelection.selected].directory; filterReplays(); var list = g_ReplaysFiltered.map(replay => { let works = replay.isCompatible; return { "directories": replay.directory, "months": compatibilityColor(getReplayDateTime(replay), works), "popCaps": compatibilityColor(translatePopulationCapacity(replay.attribs.settings.PopulationCap), works), "mapNames": compatibilityColor(getReplayMapName(replay), works), "mapSizes": compatibilityColor(translateMapSize(replay.attribs.settings.Size), works), "durations": compatibilityColor(getReplayDuration(replay), works), "playerNames": compatibilityColor(getReplayPlayernames(replay), works) }; }); if (list.length) list = prepareForDropdown(list); // Push to GUI replaySelection.selected = -1; replaySelection.list_months = list.months || []; replaySelection.list_players = list.playerNames || []; replaySelection.list_mapName = list.mapNames || []; replaySelection.list_mapSize = list.mapSizes || []; replaySelection.list_popCapacity = list.popCaps || []; replaySelection.list_duration = list.durations || []; // Change these last, otherwise crash replaySelection.list = list.directories || []; replaySelection.list_data = list.directories || []; replaySelection.selected = replaySelection.list.findIndex(directory => directory == g_SelectedReplayDirectory); displayReplayDetails(); } /** * Shows preview image, description and player text in the right panel. */ function displayReplayDetails() { let selected = Engine.GetGUIObjectByName("replaySelection").selected; let replaySelected = selected > -1; Engine.GetGUIObjectByName("replayInfo").hidden = !replaySelected; Engine.GetGUIObjectByName("replayInfoEmpty").hidden = replaySelected; Engine.GetGUIObjectByName("startReplayButton").enabled = replaySelected; Engine.GetGUIObjectByName("deleteReplayButton").enabled = replaySelected; Engine.GetGUIObjectByName("replayFilename").hidden = !replaySelected; Engine.GetGUIObjectByName("summaryButton").hidden = true; if (!replaySelected) return; let replay = g_ReplaysFiltered[selected]; Engine.GetGUIObjectByName("sgMapName").caption = translate(replay.attribs.settings.Name); Engine.GetGUIObjectByName("sgMapSize").caption = translateMapSize(replay.attribs.settings.Size); Engine.GetGUIObjectByName("sgMapType").caption = translateMapType(replay.attribs.settings.mapType); Engine.GetGUIObjectByName("sgVictory").caption = translateVictoryCondition(replay.attribs.settings.GameType); Engine.GetGUIObjectByName("sgNbPlayers").caption = sprintf(translate("Players: %(numberOfPlayers)s"), { "numberOfPlayers": replay.attribs.settings.PlayerData.length }); Engine.GetGUIObjectByName("replayFilename").caption = Engine.GetReplayDirectoryName(replay.directory); let metadata = Engine.GetReplayMetadata(replay.directory); Engine.GetGUIObjectByName("sgPlayersNames").caption = formatPlayerInfo( replay.attribs.settings.PlayerData, Engine.GetGUIObjectByName("showSpoiler").checked && metadata && metadata.playerStates && metadata.playerStates.map(pState => pState.state) ); let mapData = getMapDescriptionAndPreview(replay.attribs.settings.mapType, replay.attribs.map); Engine.GetGUIObjectByName("sgMapDescription").caption = mapData.description; Engine.GetGUIObjectByName("summaryButton").hidden = !Engine.HasReplayMetadata(replay.directory); setMapPreviewImage("sgMapPreview", mapData.preview); } /** * Returns a human-readable version of the replay date. */ function getReplayDateTime(replay) { return Engine.FormatMillisecondsIntoDateStringLocal(replay.attribs.timestamp * 1000, translate("yyyy-MM-dd HH:mm")); } /** * Returns a human-readable list of the playernames of that replay. * * @returns {string} */ function getReplayPlayernames(replay) { return replay.attribs.settings.PlayerData.map(pData => pData.Name).join(", "); } /** * Returns the name of the map of the given replay. * * @returns {string} */ function getReplayMapName(replay) { return translate(replay.attribs.settings.Name); } /** * Returns the month of the given replay in the format "yyyy-MM". * * @returns {string} */ function getReplayMonth(replay) { return Engine.FormatMillisecondsIntoDateStringLocal(replay.attribs.timestamp * 1000, translate("yyyy-MM")); } /** * Returns a human-readable version of the time when the replay started. * * @returns {string} */ function getReplayDuration(replay) { return timeToString(replay.duration * 1000); } /** * True if we can start the given replay with the currently loaded mods. */ function isReplayCompatible(replay) { return replayHasSameEngineVersion(replay) && hasSameMods(replay.attribs, g_EngineInfo); } /** * True if we can start the given replay with the currently loaded mods. */ function replayHasSameEngineVersion(replay) { return replay.attribs.engine_version && replay.attribs.engine_version == g_EngineInfo.engine_version; } Index: ps/trunk/binaries/data/mods/public/gui/session/menu.js =================================================================== --- ps/trunk/binaries/data/mods/public/gui/session/menu.js (revision 20784) +++ ps/trunk/binaries/data/mods/public/gui/session/menu.js (revision 20785) @@ -1,1250 +1,1250 @@ // Menu / panel border size var MARGIN = 4; // Includes the main menu button const NUM_BUTTONS = 9; // 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; // Trade menu: step for probability changes var STEP = 5; // Shown in the trade dialog. var g_IdleTraderTextColor = "orange"; /** * The barter constants should match with the simulation * Quantity of goods to sell per click. */ const g_BarterResourceSellQuantity = 100; /** * Multiplier to be applied when holding the massbarter hotkey. */ const g_BarterMultiplier = 5; /** * Barter actions, as mapped to the names of GUI Buttons. */ const g_BarterActions = ["Buy", "Sell"]; /** * Currently selected resource type to sell in the barter GUI. */ var g_BarterSell; var g_IsMenuOpen = false; var g_IsDiplomacyOpen = false; var g_IsTradeOpen = false; var g_IsObjectivesOpen = false; /** * Used to disable a specific bribe button for the time we are waiting for the result of the bribe after it was clicked. * It contains an array per viewedPlayer. This array is a list of the players that were bribed. */ var g_BribeButtonsWaiting = {}; /** - * Currently viewed summary panel. + * Remember last viewed summary panel and charts. */ -var g_SummarySelectedData = ""; +var g_SummarySelectedData; // Redefined every time someone makes a tribute (so we can save some data in a closure). Called in input.js handleInputBeforeGui. var g_FlushTributing = function() {}; function initSessionMenuButtons() { initMenuPosition(); updateGameSpeedControl(); resizeDiplomacyDialog(); resizeTradeDialog(); } function initMenuPosition() { Engine.GetGUIObjectByName("menu").size = "100%-164 " + MENU_TOP + " 100% " + MENU_BOTTOM; } 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 optionsMenuButton() { closeOpenDialogs(); openOptions(); } function chatMenuButton() { closeOpenDialogs(); openChat(); } function diplomacyMenuButton() { closeOpenDialogs(); openDiplomacy(); } function pauseMenuButton() { togglePause(); } function resignMenuButton() { closeOpenDialogs(); pauseGame(); messageBox( 400, 200, translate("Are you sure you want to resign?"), translate("Confirmation"), [translate("No"), translate("Yes")], [resumeGame, resignGame] ); } function exitMenuButton() { closeOpenDialogs(); pauseGame(); let messageTypes = { "host": { "caption": translate("Are you sure you want to quit? Leaving will disconnect all other players."), "buttons": [resumeGame, leaveGame] }, "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] } }; 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] ); } function openDeleteDialog(selection) { closeOpenDialogs(); let deleteSelectedEntities = function(selectionArg) { Engine.PostNetworkCommand({ "type": "delete-entities", "entities": selectionArg }); }; messageBox( 400, 200, translate("Destroy everything currently selected?"), translate("Delete"), [translate("No"), translate("Yes")], [resumeGame, deleteSelectedEntities], [null, selection] ); } function openSave() { closeOpenDialogs(); pauseGame(); Engine.PushGuiPage("page_savegame.xml", { "savedGameData": getSavedGameData(), "callback": "resumeGame" }); } function openOptions() { closeOpenDialogs(); pauseGame(); Engine.PushGuiPage("page_options.xml", { "callback": "resumeGame" }); } function openChat(command = "") { if (g_Disconnected) return; closeOpenDialogs(); let chatAddressee = Engine.GetGUIObjectByName("chatAddressee"); chatAddressee.selected = chatAddressee.list_data.indexOf(command); Engine.GetGUIObjectByName("chatInput").focus(); Engine.GetGUIObjectByName("chatDialogPanel").hidden = false; updateChatHistory(); } function closeChat() { Engine.GetGUIObjectByName("chatInput").caption = ""; Engine.GetGUIObjectByName("chatInput").blur(); // Remove focus Engine.GetGUIObjectByName("chatDialogPanel").hidden = true; } function resizeDiplomacyDialog() { let dialog = Engine.GetGUIObjectByName("diplomacyDialogPanel"); let size = dialog.size; let tribSize = Engine.GetGUIObjectByName("diplomacyPlayer[0]_tribute[0]").size; let widthOffset = g_ResourceData.GetCodes().length * (tribSize.right - tribSize.left) / 2; size.left -= widthOffset; size.right += widthOffset; let firstRow = Engine.GetGUIObjectByName("diplomacyPlayer[0]").size; let heightOffset = (g_Players.length - 1) * (firstRow.bottom - firstRow.top) / 2; size.top -= heightOffset; size.bottom += heightOffset; dialog.size = size; } function initChatWindow() { let filters = prepareForDropdown(g_ChatHistoryFilters); let chatHistoryFilter = Engine.GetGUIObjectByName("chatHistoryFilter"); chatHistoryFilter.list = filters.text; chatHistoryFilter.list_data = filters.key; chatHistoryFilter.selected = 0; Engine.GetGUIObjectByName("extendedChat").checked = Engine.ConfigDB_GetValue("user", "chat.session.extended") == "true"; resizeChatWindow(); } function resizeChatWindow() { // Hide/show the panel let chatHistoryPage = Engine.GetGUIObjectByName("chatHistoryPage"); let extended = Engine.GetGUIObjectByName("extendedChat").checked; chatHistoryPage.hidden = !extended; // Resize the window let chatDialogPanel = Engine.GetGUIObjectByName("chatDialogPanel"); if (extended) { chatDialogPanel.size = Engine.GetGUIObjectByName("chatDialogPanelLarge").size; // Adjust the width so that the chat history is in the golden ratio let chatHistory = Engine.GetGUIObjectByName("chatHistory"); let height = chatHistory.getComputedSize().bottom - chatHistory.getComputedSize().top; let width = (1 + Math.sqrt(5)) / 2 * height; let size = chatDialogPanel.size; size.left = -width / 2 - chatHistory.size.left; size.right = width / 2 + chatHistory.size.left; chatDialogPanel.size = size; } else chatDialogPanel.size = Engine.GetGUIObjectByName("chatDialogPanelSmall").size; } function updateChatHistory() { if (Engine.GetGUIObjectByName("chatDialogPanel").hidden || !Engine.GetGUIObjectByName("extendedChat").checked) return; let chatHistoryFilter = Engine.GetGUIObjectByName("chatHistoryFilter"); let selected = chatHistoryFilter.list_data[chatHistoryFilter.selected]; Engine.GetGUIObjectByName("chatHistory").caption = g_ChatHistory.filter(msg => msg.filter[selected]).map(msg => Engine.ConfigDB_GetValue("user", "chat.timestamp") == "true" ? sprintf(translate("%(time)s %(message)s"), { "time": msg.timePrefix, "message": msg.txt }) : msg.txt ).join("\n"); } function onToggleChatWindowExtended() { // Save user preference let extended = Engine.GetGUIObjectByName("extendedChat").checked.toString(); Engine.ConfigDB_CreateValue("user", "chat.session.extended", extended); Engine.ConfigDB_WriteValueToFile("user", "chat.session.extended", extended, "config/user.cfg"); resizeChatWindow(); Engine.GetGUIObjectByName("chatInput").focus(); } function openDiplomacy() { closeOpenDialogs(); if (g_ViewedPlayer < 1) return; g_IsDiplomacyOpen = true; updateDiplomacy(true); Engine.GetGUIObjectByName("diplomacyDialogPanel").hidden = false; } function closeDiplomacy() { g_IsDiplomacyOpen = false; Engine.GetGUIObjectByName("diplomacyDialogPanel").hidden = true; } function toggleDiplomacy() { let open = g_IsDiplomacyOpen; closeOpenDialogs(); if (!open) openDiplomacy(); } function updateDiplomacy(opening = false) { if (g_ViewedPlayer < 1 || !g_IsDiplomacyOpen) return; let simState = GetSimState(); let isCeasefireActive = simState.ceasefireActive; let hasSharedLos = GetSimState().players[g_ViewedPlayer].hasSharedLos; // Get offset for one line let onesize = Engine.GetGUIObjectByName("diplomacyPlayer[0]").size; let rowsize = onesize.bottom - onesize.top; // We don't include gaia for (let i = 1; i < g_Players.length; ++i) { let myself = i == g_ViewedPlayer; let playerInactive = isPlayerObserver(g_ViewedPlayer) || isPlayerObserver(i); let hasAllies = g_Players.filter(player => player.isMutualAlly[g_ViewedPlayer]).length > 1; diplomacySetupTexts(i, rowsize); diplomacyFormatStanceButtons(i, myself || playerInactive || isCeasefireActive || g_Players[g_ViewedPlayer].teamsLocked); // Tribute buttons do not need to be updated onTick, and should not because of massTributing if (opening) diplomacyFormatTributeButtons(i, myself || playerInactive); diplomacyFormatAttackRequestButton(i, myself || playerInactive || isCeasefireActive || !hasAllies || !g_Players[i].isEnemy[g_ViewedPlayer]); diplomacyFormatSpyRequestButton(i, myself || playerInactive || g_Players[i].isMutualAlly[g_ViewedPlayer] && hasSharedLos); } let diplomacyCeasefireCounter = Engine.GetGUIObjectByName("diplomacyCeasefireCounter"); diplomacyCeasefireCounter.caption = sprintf( translateWithContext("ceasefire", "Remaining ceasefire time: %(time)s."), { "time": timeToString(simState.ceasefireTimeRemaining) } ); diplomacyCeasefireCounter.hidden = !isCeasefireActive; } function diplomacySetupTexts(i, rowsize) { // Apply offset let row = Engine.GetGUIObjectByName("diplomacyPlayer[" + (i - 1) + "]"); let size = row.size; size.top = rowsize * (i - 1); size.bottom = rowsize * i; row.size = size; row.hidden = false; row.sprite = "color: " + rgbToGuiColor(g_Players[i].color) + " 32"; setOutcomeIcon(g_Players[i].state, "diplomacyPlayerOutcome[" + (i - 1) + "]"); let diplomacyPlayerName = Engine.GetGUIObjectByName("diplomacyPlayerName[" + (i - 1) + "]"); diplomacyPlayerName.caption = colorizePlayernameByID(i); diplomacyPlayerName.tooltip = translateAISettings(g_GameAttributes.settings.PlayerData[i]); Engine.GetGUIObjectByName("diplomacyPlayerCiv[" + (i - 1) + "]").caption = g_CivData[g_Players[i].civ].Name; Engine.GetGUIObjectByName("diplomacyPlayerTeam[" + (i - 1) + "]").caption = g_Players[i].team < 0 ? translateWithContext("team", "None") : g_Players[i].team + 1; Engine.GetGUIObjectByName("diplomacyPlayerTheirs[" + (i - 1) + "]").caption = i == g_ViewedPlayer ? "" : g_Players[i].isAlly[g_ViewedPlayer] ? translate("Ally") : g_Players[i].isNeutral[g_ViewedPlayer] ? translate("Neutral") : translate("Enemy"); } function diplomacyFormatStanceButtons(i, hidden) { for (let stance of ["Ally", "Neutral", "Enemy"]) { let button = Engine.GetGUIObjectByName("diplomacyPlayer" + stance + "[" + (i - 1) + "]"); button.hidden = hidden; if (hidden) continue; let isCurrentStance = g_Players[g_ViewedPlayer]["is" + stance][i]; button.caption = isCurrentStance ? translate("x") : ""; button.enabled = controlsPlayer(g_ViewedPlayer) && !isCurrentStance; button.onPress = (function(player, stance) { return function() { Engine.PostNetworkCommand({ "type": "diplomacy", "player": i, "to": stance.toLowerCase() }); }; })(i, stance); } } function diplomacyFormatTributeButtons(i, hidden) { let resCodes = g_ResourceData.GetCodes(); let r = 0; for (let resCode of resCodes) { let button = Engine.GetGUIObjectByName("diplomacyPlayer[" + (i - 1) + "]_tribute[" + r + "]"); if (!button) { warn("Current GUI limits prevent displaying more than " + r + " tribute buttons!"); break; } Engine.GetGUIObjectByName("diplomacyPlayer[" + (i - 1) + "]_tribute[" + r + "]_image").sprite = "stretched:session/icons/resources/" + resCode + ".png"; button.hidden = hidden; setPanelObjectPosition(button, r, r + 1, 0); ++r; if (hidden) continue; button.enabled = controlsPlayer(g_ViewedPlayer); button.tooltip = formatTributeTooltip(i, resCode, 100); button.onPress = (function(i, resCode, button) { // Shift+click to send 500, shift+click+click to send 1000, etc. // See INPUT_MASSTRIBUTING in input.js let multiplier = 1; return function() { let isBatchTrainPressed = Engine.HotkeyIsPressed("session.masstribute"); if (isBatchTrainPressed) { inputState = INPUT_MASSTRIBUTING; multiplier += multiplier == 1 ? 4 : 5; } let amounts = {}; for (let res of resCodes) amounts[res] = 0; amounts[resCode] = 100 * multiplier; button.tooltip = formatTributeTooltip(i, resCode, amounts[resCode]); // This is in a closure so that we have access to `player`, `amounts`, and `multiplier` without some // evil global variable hackery. g_FlushTributing = function() { Engine.PostNetworkCommand({ "type": "tribute", "player": i, "amounts": amounts }); multiplier = 1; button.tooltip = formatTributeTooltip(i, resCode, 100); }; if (!isBatchTrainPressed) g_FlushTributing(); }; })(i, resCode, button); } } function diplomacyFormatAttackRequestButton(i, hidden) { let button = Engine.GetGUIObjectByName("diplomacyAttackRequest[" + (i - 1) + "]"); button.hidden = hidden; if (hidden) return; button.enabled = controlsPlayer(g_ViewedPlayer); button.tooltip = translate("Request your allies to attack this enemy"); button.onPress = (function(i) { return function() { Engine.PostNetworkCommand({ "type": "attack-request", "source": g_ViewedPlayer, "player": i }); }; })(i); } function diplomacyFormatSpyRequestButton(i, hidden) { let button = Engine.GetGUIObjectByName("diplomacySpyRequest[" + (i - 1) + "]"); let template = GetTemplateData("special/spy"); button.hidden = hidden || !template || GetSimState().players[g_ViewedPlayer].disabledTemplates["special/spy"]; if (button.hidden) return; button.enabled = controlsPlayer(g_ViewedPlayer) && !(g_BribeButtonsWaiting[g_ViewedPlayer] && g_BribeButtonsWaiting[g_ViewedPlayer].indexOf(i) != -1); let modifier = ""; let tooltips = [translate("Bribe a random unit from this player and share its vision during a limited period.")]; if (!button.enabled) modifier = "color:0 0 0 127:grayscale:"; else { if (template.requiredTechnology) { let technologyEnabled = Engine.GuiInterfaceCall("IsTechnologyResearched", { "tech": template.requiredTechnology, "player": g_ViewedPlayer }); if (!technologyEnabled) { modifier = "color:0 0 0 127:grayscale:"; button.enabled = false; tooltips.push(getRequiredTechnologyTooltip(technologyEnabled, template.requiredTechnology, GetSimState().players[g_ViewedPlayer].civ)); } } if (template.cost) { let modifiedTemplate = clone(template); for (let res in template.cost) modifiedTemplate.cost[res] = Math.floor(GetSimState().players[i].spyCostMultiplier * template.cost[res]); tooltips.push(getEntityCostTooltip(modifiedTemplate)); let neededResources = Engine.GuiInterfaceCall("GetNeededResources", { "cost": modifiedTemplate.cost, "player": g_ViewedPlayer }); let costRatio = Engine.GetTemplate("special/spy").VisionSharing.FailureCostRatio; if (costRatio > 0) { tooltips.push(translate("A failed bribe will cost you:")); for (let res in modifiedTemplate.cost) modifiedTemplate.cost[res] = Math.floor(costRatio * modifiedTemplate.cost[res]); tooltips.push(getEntityCostTooltip(modifiedTemplate)); } if (neededResources) { if (button.enabled) modifier = resourcesToAlphaMask(neededResources) + ":"; button.enabled = false; tooltips.push(getNeededResourcesTooltip(neededResources)); } } } let icon = Engine.GetGUIObjectByName("diplomacySpyRequestImage[" + (i - 1) + "]"); icon.sprite = modifier + "stretched:session/icons/bribes.png"; button.tooltip = tooltips.filter(tip => tip).join("\n"); button.onPress = (function(i, button) { return function() { Engine.PostNetworkCommand({ "type": "spy-request", "source": g_ViewedPlayer, "player": i }); if (!g_BribeButtonsWaiting[g_ViewedPlayer]) g_BribeButtonsWaiting[g_ViewedPlayer] = []; // Don't push i twice if (g_BribeButtonsWaiting[g_ViewedPlayer].indexOf(i) == -1) g_BribeButtonsWaiting[g_ViewedPlayer].push(i); diplomacyFormatSpyRequestButton(i, false); }; })(i, button); } function resizeTradeDialog() { let dialog = Engine.GetGUIObjectByName("tradeDialogPanel"); let size = dialog.size; let width = size.right - size.left; let tradeSize = Engine.GetGUIObjectByName("tradeResource[0]").size; width += g_ResourceData.GetCodes().length * (tradeSize.right - tradeSize.left); size.left = -width / 2; size.right = width / 2; dialog.size = size; } function openTrade() { closeOpenDialogs(); if (g_ViewedPlayer < 1) return; g_IsTradeOpen = true; let proba = Engine.GuiInterfaceCall("GetTradingGoods", g_ViewedPlayer); let button = {}; let resCodes = g_ResourceData.GetCodes(); let currTradeSelection = resCodes[0]; let updateTradeButtons = function() { for (let res in button) { button[res].label.caption = proba[res] + "%"; button[res].sel.hidden = !controlsPlayer(g_ViewedPlayer) || res != currTradeSelection; button[res].up.hidden = !controlsPlayer(g_ViewedPlayer) || res == currTradeSelection || proba[res] == 100 || proba[currTradeSelection] == 0; button[res].dn.hidden = !controlsPlayer(g_ViewedPlayer) || res == currTradeSelection || proba[res] == 0 || proba[currTradeSelection] == 100; } }; hideRemaining("tradeResources", resCodes.length); Engine.GetGUIObjectByName("tradeHelp").hidden = false; for (let i = 0; i < resCodes.length; ++i) { let resCode = resCodes[i]; let barterResource = Engine.GetGUIObjectByName("barterResource[" + i + "]"); if (!barterResource) { warn("Current GUI limits prevent displaying more than " + i + " resources in the barter dialog!"); break; } // Barter: barterOpenCommon(resCode, i, "barter"); setPanelObjectPosition(barterResource, i, i + 1); // Trade: let tradeResource = Engine.GetGUIObjectByName("tradeResource[" + i + "]"); if (!tradeResource) { warn("Current GUI limits prevent displaying more than " + i + " resources in the trading goods selection dialog!"); break; } setPanelObjectPosition(tradeResource, i, i + 1); let icon = Engine.GetGUIObjectByName("tradeResourceIcon[" + i + "]"); icon.sprite = "stretched:session/icons/resources/" + resCode + ".png"; let buttonUp = Engine.GetGUIObjectByName("tradeArrowUp[" + i + "]"); let buttonDn = Engine.GetGUIObjectByName("tradeArrowDn[" + i + "]"); button[resCode] = { "up": buttonUp, "dn": buttonDn, "label": Engine.GetGUIObjectByName("tradeResourceText[" + i + "]"), "sel": Engine.GetGUIObjectByName("tradeResourceSelection[" + i + "]") }; proba[resCode] = proba[resCode] || 0; let buttonResource = Engine.GetGUIObjectByName("tradeResourceButton[" + i + "]"); buttonResource.enabled = controlsPlayer(g_ViewedPlayer); buttonResource.onPress = (resource => { return () => { if (Engine.HotkeyIsPressed("session.fulltradeswap")) { for (let res of resCodes) proba[res] = 0; proba[resource] = 100; Engine.PostNetworkCommand({ "type": "set-trading-goods", "tradingGoods": proba }); } currTradeSelection = resource; updateTradeButtons(); }; })(resCode); buttonUp.enabled = controlsPlayer(g_ViewedPlayer); buttonUp.onPress = (resource => { return () => { proba[resource] += Math.min(STEP, proba[currTradeSelection]); proba[currTradeSelection] -= Math.min(STEP, proba[currTradeSelection]); Engine.PostNetworkCommand({ "type": "set-trading-goods", "tradingGoods": proba }); updateTradeButtons(); }; })(resCode); buttonDn.enabled = controlsPlayer(g_ViewedPlayer); buttonDn.onPress = (resource => { return () => { proba[currTradeSelection] += Math.min(STEP, proba[resource]); proba[resource] -= Math.min(STEP, proba[resource]); Engine.PostNetworkCommand({ "type": "set-trading-goods", "tradingGoods": proba }); updateTradeButtons(); }; })(resCode); } updateTradeButtons(); updateTraderTexts(); Engine.GetGUIObjectByName("tradeDialogPanel").hidden = false; } function updateTraderTexts() { let traderNumber = Engine.GuiInterfaceCall("GetTraderNumber", g_ViewedPlayer); Engine.GetGUIObjectByName("traderCountText").caption = getIdleLandTradersText(traderNumber) + "\n\n" + getIdleShipTradersText(traderNumber); } /** * Code common to both the Barter Panel and the Trade/Barter Dialog, that * only needs to be run when the panel or dialog is opened by the player. * * @param {string} resourceCode * @param {number} idx - Element index within its set * @param {string} prefix - Common prefix of the gui elements to be worked upon */ function barterOpenCommon(resourceCode, idx, prefix) { let barterButton = {}; for (let action of g_BarterActions) barterButton[action] = Engine.GetGUIObjectByName(prefix + action + "Button[" + idx + "]"); let resource = resourceNameWithinSentence(resourceCode); barterButton.Buy.tooltip = sprintf(translate("Buy %(resource)s"), { "resource": resource }); barterButton.Sell.tooltip = sprintf(translate("Sell %(resource)s"), { "resource": resource }); barterButton.Sell.onPress = function() { g_BarterSell = resourceCode; updateSelectionDetails(); updateBarterButtons(); }; } /** * Code common to both the Barter Panel and the Trade/Barter Dialog, that * needs to be run on simulation update and when relevant hotkeys * (i.e. massbarter) are pressed. * * @param {string} resourceCode * @param {number} idx - Element index within its set * @param {string} prefix - Common prefix of the gui elements to be worked upon * @param {number} player */ function barterUpdateCommon(resourceCode, idx, prefix, player) { let barterButton = {}; let barterIcon = {}; let barterAmount = {}; for (let action of g_BarterActions) { barterButton[action] = Engine.GetGUIObjectByName(prefix + action + "Button[" + idx + "]"); barterIcon[action] = Engine.GetGUIObjectByName(prefix + action + "Icon[" + idx + "]"); barterAmount[action] = Engine.GetGUIObjectByName(prefix + action + "Amount[" + idx + "]"); } let selectionIcon = Engine.GetGUIObjectByName(prefix + "SellSelection[" + idx + "]"); let amountToSell = g_BarterResourceSellQuantity; if (Engine.HotkeyIsPressed("session.massbarter")) amountToSell *= g_BarterMultiplier; let isSelected = resourceCode == g_BarterSell; let grayscale = isSelected ? "color:0 0 0 100:grayscale:" : ""; // Select color of the sell button let neededRes = {}; neededRes[resourceCode] = amountToSell; let canSellCurrent = Engine.GuiInterfaceCall("GetNeededResources", { "cost": neededRes, "player": player }) ? "color:255 0 0 80:" : ""; // Select color of the buy button neededRes = {}; neededRes[g_BarterSell] = amountToSell; let canBuyAny = Engine.GuiInterfaceCall("GetNeededResources", { "cost": neededRes, "player": player }) ? "color:255 0 0 80:" : ""; barterIcon.Sell.sprite = canSellCurrent + "stretched:" + grayscale + "session/icons/resources/" + resourceCode + ".png"; barterIcon.Buy.sprite = canBuyAny + "stretched:" + grayscale + "session/icons/resources/" + resourceCode + ".png"; barterAmount.Sell.caption = "-" + amountToSell; let prices = GetSimState().players[player].barterPrices; barterAmount.Buy.caption = "+" + Math.round(prices.sell[g_BarterSell] / prices.buy[resourceCode] * amountToSell); barterButton.Buy.onPress = function() { Engine.PostNetworkCommand({ "type": "barter", "sell": g_BarterSell, "buy": resourceCode, "amount": amountToSell }); }; barterButton.Buy.hidden = isSelected; barterButton.Buy.enabled = controlsPlayer(player); barterButton.Sell.hidden = false; selectionIcon.hidden = !isSelected; } function updateBarterButtons() { let playerState = GetSimState().players[g_ViewedPlayer]; if (!playerState) return; let canBarter = playerState.canBarter; Engine.GetGUIObjectByName("barterNoMarketsMessage").hidden = canBarter; Engine.GetGUIObjectByName("barterResources").hidden = !canBarter; Engine.GetGUIObjectByName("barterHelp").hidden = !canBarter; if (!canBarter) return; let resCodes = g_ResourceData.GetCodes(); for (let i = 0; i < resCodes.length; ++i) barterUpdateCommon(resCodes[i], i, "barter", g_ViewedPlayer); } function getIdleLandTradersText(traderNumber) { let active = traderNumber.landTrader.trading; let garrisoned = traderNumber.landTrader.garrisoned; let inactive = traderNumber.landTrader.total - active - garrisoned; let messageTypes = { "active": { "garrisoned": { "no-inactive": translate("%(openingTradingString)s, and %(garrisonedString)s."), "inactive": translate("%(openingTradingString)s, %(garrisonedString)s, and %(inactiveString)s.") }, "no-garrisoned": { "no-inactive": translate("%(openingTradingString)s."), "inactive": translate("%(openingTradingString)s, and %(inactiveString)s.") } }, "no-active": { "garrisoned": { "no-inactive": translate("%(openingGarrisonedString)s."), "inactive": translate("%(openingGarrisonedString)s, and %(inactiveString)s.") }, "no-garrisoned": { "inactive": translatePlural("There is %(inactiveString)s.", "There are %(inactiveString)s.", inactive), "no-inactive": translate("There are no land traders.") } } }; let message = messageTypes[active ? "active" : "no-active"][garrisoned ? "garrisoned" : "no-garrisoned"][inactive ? "inactive" : "no-inactive"]; let activeString = sprintf( translatePlural( "There is %(numberTrading)s land trader trading", "There are %(numberTrading)s land traders trading", active ), { "numberTrading": active } ); let inactiveString = sprintf( active || garrisoned ? translatePlural( "%(numberOfLandTraders)s inactive", "%(numberOfLandTraders)s inactive", inactive ) : translatePlural( "%(numberOfLandTraders)s land trader inactive", "%(numberOfLandTraders)s land traders inactive", inactive ), { "numberOfLandTraders": inactive } ); let garrisonedString = sprintf( active || inactive ? translatePlural( "%(numberGarrisoned)s garrisoned on a trading merchant ship", "%(numberGarrisoned)s garrisoned on a trading merchant ship", garrisoned ) : translatePlural( "There is %(numberGarrisoned)s land trader garrisoned on a trading merchant ship", "There are %(numberGarrisoned)s land traders garrisoned on a trading merchant ship", garrisoned ), { "numberGarrisoned": garrisoned } ); return sprintf(message, { "openingTradingString": activeString, "openingGarrisonedString": garrisonedString, "garrisonedString": garrisonedString, "inactiveString": coloredText(inactiveString, g_IdleTraderTextColor) }); } function getIdleShipTradersText(traderNumber) { let active = traderNumber.shipTrader.trading; let inactive = traderNumber.shipTrader.total - active; let messageTypes = { "active": { "inactive": translate("%(openingTradingString)s, and %(inactiveString)s."), "no-inactive": translate("%(openingTradingString)s.") }, "no-active": { "inactive": translatePlural("There is %(inactiveString)s.", "There are %(inactiveString)s.", inactive), "no-inactive": translate("There are no merchant ships.") } }; let message = messageTypes[active ? "active" : "no-active"][inactive ? "inactive" : "no-inactive"]; let activeString = sprintf( translatePlural( "There is %(numberTrading)s merchant ship trading", "There are %(numberTrading)s merchant ships trading", active ), { "numberTrading": active } ); let inactiveString = sprintf( active ? translatePlural( "%(numberOfShipTraders)s inactive", "%(numberOfShipTraders)s inactive", inactive ) : translatePlural( "%(numberOfShipTraders)s merchant ship inactive", "%(numberOfShipTraders)s merchant ships inactive", inactive ), { "numberOfShipTraders": inactive } ); return sprintf(message, { "openingTradingString": activeString, "inactiveString": coloredText(inactiveString, g_IdleTraderTextColor) }); } function closeTrade() { g_IsTradeOpen = false; Engine.GetGUIObjectByName("tradeDialogPanel").hidden = true; } function toggleTrade() { let open = g_IsTradeOpen; closeOpenDialogs(); if (!open) openTrade(); } function toggleTutorial() { let tutorialPanel = Engine.GetGUIObjectByName("tutorialPanel"); tutorialPanel.hidden = !tutorialPanel.hidden || !Engine.GetGUIObjectByName("tutorialText").caption; } function updateGameSpeedControl() { 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(true); 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(); 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, "callback": "resumeGameAndSaveSummarySelectedData" }); } function openStrucTree() { closeOpenDialogs(); pauseGame(); // TODO add info about researched techs and unlocked entities Engine.PushGuiPage("page_structree.xml", { "civ": g_Players[g_ViewedPlayer].civ, "callback": "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); + 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(); Engine.PushGuiPage("page_manual.xml", { "page": "manual/intro", "title": translate("Manual"), "url": "http://trac.wildfiregames.com/wiki/0adManual", "callback": "resumeGame" }); } function toggleDeveloperOverlay() { if (!g_GameAttributes.settings.CheatsEnabled && !g_IsReplay) return; let devCommands = Engine.GetGUIObjectByName("devCommands"); devCommands.hidden = !devCommands.hidden; let message = devCommands.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) submitChatDirectly(message); else Engine.PostNetworkCommand({ "type": "aichat", "message": message, "translateMessage": true, "translateParameters": [], "parameters": {} }); } function closeOpenDialogs() { closeMenu(); closeChat(); closeDiplomacy(); closeTrade(); closeObjectives(); } function formatTributeTooltip(playerID, resourceCode, amount) { return sprintf(translate("Tribute %(resourceAmount)s %(resourceType)s to %(playerName)s. Shift-click to tribute %(greaterAmount)s."), { "resourceAmount": amount, "resourceType": resourceNameWithinSentence(resourceCode), "playerName": colorizePlayernameByID(playerID), "greaterAmount": amount < 500 ? 500 : amount + 500 }); } Index: ps/trunk/binaries/data/mods/public/gui/summary/summary.js =================================================================== --- ps/trunk/binaries/data/mods/public/gui/summary/summary.js (revision 20784) +++ ps/trunk/binaries/data/mods/public/gui/summary/summary.js (revision 20785) @@ -1,559 +1,561 @@ const g_CivData = loadCivData(false, false); var g_ScorePanelsData; var g_MaxHeadingTitle = 9; var g_LongHeadingWidth = 250; var g_PlayerBoxYSize = 40; var g_PlayerBoxGap = 2; var g_PlayerBoxAlpha = 50; var g_TeamsBoxYStart = 40; var g_TypeColors = { "blue": "196 198 255", "green": "201 255 200", "red": "255 213 213", "yellow": "255 255 157" }; /** * Colors, captions and format used for units, buildings, etc. types */ var g_SummaryTypes = { "percent": { "color": "", "caption": "%", "postfix": "%" }, "trained": { "color": g_TypeColors.green, "caption": translate("Trained"), "postfix": " / " }, "constructed": { "color": g_TypeColors.green, "caption": translate("Constructed"), "postfix": " / " }, "gathered": { "color": g_TypeColors.green, "caption": translate("Gathered"), "postfix": " / " }, "sent": { "color": g_TypeColors.green, "caption": translate("Sent"), "postfix": " / " }, "bought": { "color": g_TypeColors.green, "caption": translate("Bought"), "postfix": " / " }, "income": { "color": g_TypeColors.green, "caption": translate("Income"), "postfix": " / " }, "captured": { "color": g_TypeColors.yellow, "caption": translate("Captured"), "postfix": " / " }, "succeeded": { "color": g_TypeColors.green, "caption": translate("Succeeded"), "postfix": " / " }, "destroyed": { "color": g_TypeColors.blue, "caption": translate("Destroyed"), "postfix": "\n" }, "killed": { "color": g_TypeColors.blue, "caption": translate("Killed"), "postfix": "\n" }, "lost": { "color": g_TypeColors.red, "caption": translate("Lost"), "postfix": "\n" }, "used": { "color": g_TypeColors.red, "caption": translate("Used"), "postfix": "\n" }, "received": { "color": g_TypeColors.red, "caption": translate("Received"), "postfix": "\n" }, "sold": { "color": g_TypeColors.red, "caption": translate("Sold"), "postfix": "\n" }, "outcome": { "color": g_TypeColors.red, "caption": translate("Outcome"), "postfix": "\n" }, "failed": { "color": g_TypeColors.red, "caption": translate("Failed"), "postfix": "\n" } }; // Translation: Unicode encoded infinity symbol indicating a division by zero in the summary screen. var g_InfinitySymbol = translate("\u221E"); var g_Teams = []; // TODO set g_PlayerCount as playerCounters.length var g_PlayerCount = 0; var g_GameData; var g_ResourceData = new Resources(); -// Selected chart indexes +/** + * Selected chart indexes. + */ var g_SelectedChart = { "category": [0, 0], "value": [0, 1], "type": [0, 0] }; -/* +/** * Array of the panel button names. */ var g_PanelButtons = []; -/* +/** * Remember the name of the currently opened view panel. */ var g_SelectedPanel = ""; function init(data) { // Fill globals g_GameData = data; g_ScorePanelsData = getScorePanelsData(); g_PanelButtons = Object.keys(g_ScorePanelsData).concat(["charts"]).map(panel => panel + "PanelButton"); g_SelectedPanel = g_PanelButtons[0]; if (data && data.selectedData) { g_SelectedPanel = data.selectedData.panel; g_SelectedChart = data.selectedData.charts; } initTeamData(); calculateTeamCounterDataHelper(); // Output globals initGUIWindow(); initPlayerBoxPositions(); initGUICharts(); initGUILabelsAndButtons(); selectPanel(Engine.GetGUIObjectByName(g_SelectedPanel)); for (let button of g_PanelButtons) { let tab = Engine.GetGUIObjectByName(button); tab.onMouseWheelUp = () => selectNextTab(1); tab.onMouseWheelDown = () => selectNextTab(-1); } } /** * Sets the style and title of the page. */ function initGUIWindow() { let summaryWindow = Engine.GetGUIObjectByName("summaryWindow"); summaryWindow.sprite = g_GameData.gui.dialog ? "ModernDialog" : "ModernWindow"; summaryWindow.size = g_GameData.gui.dialog ? "16 24 100%-16 100%-24" : "0 0 100% 100%"; Engine.GetGUIObjectByName("summaryWindowTitle").size = g_GameData.gui.dialog ? "50%-128 -16 50%+128 16" : "50%-128 4 50%+128 36"; } /** * Show next/previous panel. * @param direction - 1/-1 forward, backward panel. */ function selectNextTab(direction) { selectPanel(Engine.GetGUIObjectByName(g_PanelButtons[ (g_PanelButtons.indexOf(g_SelectedPanel) + direction + g_PanelButtons.length) % g_PanelButtons.length])); } function selectPanel(panel) { // TODO: move panel buttons to a custom parent object for (let button of Engine.GetGUIObjectByName("summaryWindow").children) if (button.name.endsWith("PanelButton")) button.sprite = "ModernTabHorizontalBackground"; panel.sprite = "ModernTabHorizontalForeground"; adjustTabDividers(panel.size); let generalPanel = Engine.GetGUIObjectByName("generalPanel"); let chartsPanel = Engine.GetGUIObjectByName("chartsPanel"); let chartsHidden = panel.name != "chartsPanelButton"; generalPanel.hidden = !chartsHidden; chartsPanel.hidden = chartsHidden; if (chartsHidden) updatePanelData(g_ScorePanelsData[panel.name.substr(0, panel.name.length - "PanelButton".length)]); else [0, 1].forEach(updateCategoryDropdown); g_SelectedPanel = panel.name; } function initGUICharts() { let player_colors = []; for (let i = 1; i <= g_PlayerCount; ++i) { let playerState = g_GameData.sim.playerStates[i]; player_colors.push( Math.floor(playerState.color.r * 255) + " " + Math.floor(playerState.color.g * 255) + " " + Math.floor(playerState.color.b * 255) ); } for (let i = 0; i < 2; ++i) Engine.GetGUIObjectByName("chart[" + i + "]").series_color = player_colors; let chartLegend = Engine.GetGUIObjectByName("chartLegend"); chartLegend.caption = g_GameData.sim.playerStates.slice(1).map( (state, index) => coloredText("■", player_colors[index]) + state.name ).join(" "); let chart1Part = Engine.GetGUIObjectByName("chart[1]Part"); let chart1PartSize = chart1Part.size; chart1PartSize.rright += 50; chart1PartSize.rleft += 50; chart1PartSize.right -= 5; chart1PartSize.left -= 5; chart1Part.size = chart1PartSize; } function resizeDropdown(dropdown) { let size = dropdown.size; size.bottom = dropdown.size.top + (Engine.GetTextWidth(dropdown.font, dropdown.list[dropdown.selected]) > dropdown.size.right - dropdown.size.left - 32 ? 42 : 27); dropdown.size = size; } function updateCategoryDropdown(number) { let chartCategory = Engine.GetGUIObjectByName("chart[" + number + "]CategorySelection"); chartCategory.list_data = Object.keys(g_ScorePanelsData); chartCategory.list = Object.keys(g_ScorePanelsData).map(panel => g_ScorePanelsData[panel].caption); chartCategory.onSelectionChange = function() { if (!this.list_data[this.selected]) return; if (g_SelectedChart.category[number] != this.selected) { g_SelectedChart.category[number] = this.selected; g_SelectedChart.value[number] = 0; g_SelectedChart.type[number] = 0; } resizeDropdown(this); updateValueDropdown(number, this.list_data[this.selected]); }; chartCategory.selected = g_SelectedChart.category[number]; } function updateValueDropdown(number, category) { let chartValue = Engine.GetGUIObjectByName("chart[" + number + "]ValueSelection"); let list = g_ScorePanelsData[category].headings.map(heading => heading.caption); list.shift(); chartValue.list = list; let list_data = g_ScorePanelsData[category].headings.map(heading => heading.identifier); list_data.shift(); chartValue.list_data = list_data; chartValue.onSelectionChange = function() { if (!this.list_data[this.selected]) return; if (g_SelectedChart.value[number] != this.selected) { g_SelectedChart.value[number] = this.selected; g_SelectedChart.type[number] = 0; } resizeDropdown(this); updateTypeDropdown(number, category, this.list_data[this.selected], this.selected); }; chartValue.selected = g_SelectedChart.value[number]; } function updateTypeDropdown(number, category, item, itemNumber) { let testValue = g_ScorePanelsData[category].counters[itemNumber].fn(g_GameData.sim.playerStates[1], 0, item); let hide = !g_ScorePanelsData[category].counters[itemNumber].fn || typeof testValue != "object" || Object.keys(testValue).length < 2; Engine.GetGUIObjectByName("chart[" + number + "]TypeLabel").hidden = hide; let chartType = Engine.GetGUIObjectByName("chart[" + number + "]TypeSelection"); chartType.hidden = hide; if (hide) { updateChart(number, category, item, itemNumber, Object.keys(testValue)[0] || undefined); return; } chartType.list = Object.keys(testValue).map(type => g_SummaryTypes[type].caption); chartType.list_data = Object.keys(testValue); chartType.onSelectionChange = function() { if (!this.list_data[this.selected]) return; g_SelectedChart.type[number] = this.selected; resizeDropdown(this); updateChart(number, category, item, itemNumber, this.list_data[this.selected]); }; chartType.selected = g_SelectedChart.type[number]; } function updateChart(number, category, item, itemNumber, type) { if (!g_ScorePanelsData[category].counters[itemNumber].fn) return; let chart = Engine.GetGUIObjectByName("chart[" + number + "]"); let series = []; for (let j = 1; j <= g_PlayerCount; ++j) { let playerState = g_GameData.sim.playerStates[j]; let data = []; for (let index in playerState.sequences.time) { let value = g_ScorePanelsData[category].counters[itemNumber].fn(playerState, index, item); if (type) value = value[type]; data.push([playerState.sequences.time[index], value]); } series.push(data); } chart.series = series; } function adjustTabDividers(tabSize) { let leftSpacer = Engine.GetGUIObjectByName("tabDividerLeft"); let rightSpacer = Engine.GetGUIObjectByName("tabDividerRight"); leftSpacer.size = [ 20, leftSpacer.size.top, tabSize.left + 2, leftSpacer.size.bottom ].join(" "); rightSpacer.size = [ tabSize.right - 2, rightSpacer.size.top, "100%-20", rightSpacer.size.bottom ].join(" "); } function updatePanelData(panelInfo) { resetGeneralPanel(); updateGeneralPanelHeadings(panelInfo.headings); updateGeneralPanelTitles(panelInfo.titleHeadings); let rowPlayerObjectWidth = updateGeneralPanelCounter(panelInfo.counters); updateGeneralPanelTeams(); let index = g_GameData.sim.playerStates[1].sequences.time.length - 1; let playerBoxesCounts = []; for (let i = 0; i < g_PlayerCount; ++i) { let playerState = g_GameData.sim.playerStates[i + 1]; if (!playerBoxesCounts[playerState.team + 1]) playerBoxesCounts[playerState.team + 1] = 1; else playerBoxesCounts[playerState.team + 1] += 1; let positionObject = playerBoxesCounts[playerState.team + 1] - 1; let rowPlayer = "playerBox[" + positionObject + "]"; let playerOutcome = "playerOutcome[" + positionObject + "]"; let playerNameColumn = "playerName[" + positionObject + "]"; let playerCivicBoxColumn = "civIcon[" + positionObject + "]"; let playerCounterValue = "valueData[" + positionObject + "]"; if (playerState.team != -1) { rowPlayer = "playerBoxt[" + playerState.team + "][" + positionObject + "]"; playerOutcome = "playerOutcomet[" + playerState.team + "][" + positionObject + "]"; playerNameColumn = "playerNamet[" + playerState.team + "][" + positionObject + "]"; playerCivicBoxColumn = "civIcont[" + playerState.team + "][" + positionObject + "]"; playerCounterValue = "valueDataTeam[" + playerState.team + "][" + positionObject + "]"; } let colorString = "color: " + Math.floor(playerState.color.r * 255) + " " + Math.floor(playerState.color.g * 255) + " " + Math.floor(playerState.color.b * 255); let rowPlayerObject = Engine.GetGUIObjectByName(rowPlayer); rowPlayerObject.hidden = false; rowPlayerObject.sprite = colorString + " " + g_PlayerBoxAlpha; let boxSize = rowPlayerObject.size; boxSize.right = rowPlayerObjectWidth; rowPlayerObject.size = boxSize; setOutcomeIcon(playerState.state, playerOutcome); playerNameColumn = Engine.GetGUIObjectByName(playerNameColumn); playerNameColumn.caption = g_GameData.sim.playerStates[i + 1].name; playerNameColumn.tooltip = translateAISettings(g_GameData.sim.mapSettings.PlayerData[i + 1]); let civIcon = Engine.GetGUIObjectByName(playerCivicBoxColumn); civIcon.sprite = "stretched:" + g_CivData[playerState.civ].Emblem; civIcon.tooltip = g_CivData[playerState.civ].Name; updateCountersPlayer(playerState, panelInfo.counters, panelInfo.headings, playerCounterValue, index); } let teamCounterFn = panelInfo.teamCounterFn; if (g_Teams && teamCounterFn) updateCountersTeam(teamCounterFn, panelInfo.counters, panelInfo.headings, index); } function confirmStartReplay() { if (Engine.HasXmppClient()) messageBox( 400, 200, translate("Are you sure you want to quit the lobby?"), translate("Confirmation"), [translate("No"), translate("Yes")], [null, startReplay] ); else startReplay(); } function continueButton() { let summarySelectedData = { "panel": g_SelectedPanel, "charts": g_SelectedChart }; if (g_GameData.gui.isInGame) Engine.PopGuiPageCB({ "explicitResume": 0, "summarySelectedData": summarySelectedData }); else if (g_GameData.gui.dialog) Engine.PopGuiPage(); else if (g_GameData.gui.isReplay) Engine.SwitchGuiPage("page_replaymenu.xml", { "replaySelectionData": g_GameData.gui.replaySelectionData, "summarySelectedData": summarySelectedData }); else if (Engine.HasXmppClient()) Engine.SwitchGuiPage("page_lobby.xml"); else Engine.SwitchGuiPage("page_pregame.xml"); } function startReplay() { if (Engine.HasXmppClient()) Engine.StopXmppClient(); if (!Engine.StartVisualReplay(g_GameData.gui.replayDirectory)) { warn("Replay file not found!"); return; } Engine.SwitchGuiPage("page_loading.xml", { "attribs": Engine.GetReplayAttributes(g_GameData.gui.replayDirectory), "isNetworked": false, "playerAssignments": { "local": { "name": singleplayerName(), "player": -1 } }, "savedGUIData": "", "isReplay": true, "replaySelectionData": g_GameData.gui.replaySelectionData }); } function initGUILabelsAndButtons() { let assignedState = g_GameData.sim.playerStates[g_GameData.gui.assignedPlayer || -1]; Engine.GetGUIObjectByName("summaryText").caption = g_GameData.gui.isInGame ? translate("Current Scores") : g_GameData.gui.isReplay ? translate("Scores at the end of the game.") : g_GameData.gui.disconnected ? translate("You have been disconnected.") : !assignedState ? translate("You have left the game.") : assignedState.state == "won" ? translate("You have won the battle!") : assignedState.state == "defeated" ? translate("You have been defeated…") : translate("You have abandoned the game."); Engine.GetGUIObjectByName("timeElapsed").caption = sprintf( translate("Game time elapsed: %(time)s"), { "time": timeToString(g_GameData.sim.timeElapsed) }); let mapType = g_Settings.MapTypes.find(type => type.Name == g_GameData.sim.mapSettings.mapType); let mapSize = g_Settings.MapSizes.find(size => size.Tiles == g_GameData.sim.mapSettings.Size || 0); Engine.GetGUIObjectByName("mapName").caption = sprintf( translate("%(mapName)s - %(mapType)s"), { "mapName": translate(g_GameData.sim.mapSettings.Name), "mapType": mapSize ? mapSize.Name : (mapType ? mapType.Title : "") }); Engine.GetGUIObjectByName("replayButton").hidden = g_GameData.gui.isInGame || !g_GameData.gui.replayDirectory; } function initTeamData() { // Panels g_PlayerCount = g_GameData.sim.playerStates.length - 1; if (g_GameData.sim.mapSettings.LockTeams) { // Count teams for (let player = 1; player <= g_PlayerCount; ++player) { let playerTeam = g_GameData.sim.playerStates[player].team; if (!g_Teams[playerTeam]) g_Teams[playerTeam] = []; g_Teams[playerTeam].push(player); } if (g_Teams.every(team => team && team.length < 2)) g_Teams = false; // Each player has his own team. Displaying teams makes no sense. } else g_Teams = false; // Erase teams data if teams are not displayed if (!g_Teams) for (let p = 0; p < g_PlayerCount; ++p) g_GameData.sim.playerStates[p+1].team = -1; }