Index: ps/trunk/binaries/data/mods/public/gui/replaymenu/replay_actions.js =================================================================== --- ps/trunk/binaries/data/mods/public/gui/replaymenu/replay_actions.js (revision 18110) +++ ps/trunk/binaries/data/mods/public/gui/replaymenu/replay_actions.js (revision 18111) @@ -1,180 +1,186 @@ /** * Creates the data for restoring selection, order and filters when returning to the replay menu. */ function createReplaySelectionData(selectedDirectory) { let replaySelection = Engine.GetGUIObjectByName("replaySelection"); let dateTimeFilter = Engine.GetGUIObjectByName("dateTimeFilter"); let playersFilter = Engine.GetGUIObjectByName("playersFilter"); let mapNameFilter = Engine.GetGUIObjectByName("mapNameFilter"); let mapSizeFilter = Engine.GetGUIObjectByName("mapSizeFilter"); let populationFilter = Engine.GetGUIObjectByName("populationFilter"); let durationFilter = Engine.GetGUIObjectByName("durationFilter"); let compatibilityFilter = Engine.GetGUIObjectByName("compatibilityFilter"); + let singleplayerFilter = Engine.GetGUIObjectByName("singleplayerFilter"); + let victoryConFilter = Engine.GetGUIObjectByName("victoryConditionFilter"); + let ratedGamesFilter = Engine.GetGUIObjectByName("ratedGamesFilter"); return { "directory": selectedDirectory, "column": replaySelection.selected_column, "columnOrder": replaySelection.selected_column_order, "filters": { "date": dateTimeFilter.list_data[dateTimeFilter.selected], "playernames": playersFilter.caption, "mapName": mapNameFilter.list_data[mapNameFilter.selected], "mapSize": mapSizeFilter.list_data[mapSizeFilter.selected], "popCap": populationFilter.list_data[populationFilter.selected], "duration": durationFilter.list_data[durationFilter.selected], - "compatibility": compatibilityFilter.checked + "compatibility": compatibilityFilter.checked, + "singleplayer": singleplayerFilter.list_data[singleplayerFilter.selected], + "victoryCondition": victoryConFilter.list_data[victoryConFilter.selected], + "ratedGames": ratedGamesFilter.selected } }; } /** * Starts the selected visual replay, or shows an error message in case of incompatibility. */ function startReplay() { var selected = Engine.GetGUIObjectByName("replaySelection").selected; if (selected == -1) return; var replay = g_ReplaysFiltered[selected]; if (isReplayCompatible(replay)) reallyStartVisualReplay(replay.directory); else displayReplayCompatibilityError(replay); } /** * Attempts the visual replay, regardless of the compatibility. * * @param replayDirectory {string} */ function reallyStartVisualReplay(replayDirectory) { // TODO: enhancement: restore filter settings and selected replay when returning from the summary screen. Engine.StartVisualReplay(replayDirectory); Engine.SwitchGuiPage("page_loading.xml", { "attribs": Engine.GetReplayAttributes(replayDirectory), "isNetworked": false, "playerAssignments": { "local":{ "name": translate("You"), "player": -1 } }, "savedGUIData": "", "isReplay": true, "replaySelectionData": createReplaySelectionData(replayDirectory) }); } /** * Shows an error message stating why the replay is not compatible. * * @param replay {Object} */ function displayReplayCompatibilityError(replay) { var errMsg; if (replayHasSameEngineVersion(replay)) { let gameMods = replay.attribs.mods ? replay.attribs.mods : []; errMsg = translate("You don't have the same mods active as the replay does.") + "\n"; errMsg += sprintf(translate("Required: %(mods)s"), { "mods": gameMods.join(", ") }) + "\n"; errMsg += sprintf(translate("Active: %(mods)s"), { "mods": g_EngineInfo.mods.join(", ") }); } else errMsg = translate("This replay is not compatible with your version of the game!"); messageBox(500, 200, errMsg, translate("Incompatible replay"), 0, [translate("Ok")], [null]); } /** * Opens the summary screen of the given replay, if its data was found in that directory. */ function showReplaySummary() { var selected = Engine.GetGUIObjectByName("replaySelection").selected; if (selected == -1) return; // Load summary screen data from the selected replay directory var summary = Engine.GetReplayMetadata(g_ReplaysFiltered[selected].directory); if (!summary) { messageBox(500, 200, translate("No summary data available."), translate("Error"), 0, [translate("Ok")], [null]); return; } // Open summary screen summary.isReplay = true; summary.gameResult = translate("Scores at the end of the game."); summary.replayDirectory = g_ReplaysFiltered[selected].directory; summary.replaySelectionData = createReplaySelectionData(g_ReplaysFiltered[selected].directory); Engine.SwitchGuiPage("page_summary.xml", summary); } /** * Callback. */ function deleteReplayButtonPressed() { if (!Engine.GetGUIObjectByName("deleteReplayButton").enabled) return; if (Engine.HotkeyIsPressed("session.savedgames.noConfirmation")) deleteReplayWithoutConfirmation(); else deleteReplay(); } /** * Shows a confirmation dialog and deletes the selected replay from the disk in case. */ function deleteReplay() { // Get selected replay var selected = Engine.GetGUIObjectByName("replaySelection").selected; if (selected == -1) return; var replay = g_ReplaysFiltered[selected]; // Show confirmation message var btCaptions = [translate("No"), translate("Yes")]; var btCode = [null, function() { reallyDeleteReplay(replay.directory); }]; var title = translate("Delete replay"); var question = translate("Are you sure you want to delete this replay permanently?") + "\n" + escapeText(replay.file); messageBox(500, 200, question, title, 0, btCaptions, btCode); } /** * Attempts to delete the selected replay from the disk. */ function deleteReplayWithoutConfirmation() { var selected = Engine.GetGUIObjectByName("replaySelection").selected; if (selected > -1) reallyDeleteReplay(g_ReplaysFiltered[selected].directory); } /** * Attempts to delete the given replay directory from the disk. * * @param replayDirectory {string} */ function reallyDeleteReplay(replayDirectory) { var replaySelection = Engine.GetGUIObjectByName("replaySelection"); var selectedIndex = replaySelection.selected; if (!Engine.DeleteReplay(replayDirectory)) error(sprintf("Could not delete replay '%(id)s'", { "id": replayDirectory })); // Refresh replay list init(); replaySelection.selected = Math.min(selectedIndex, g_ReplaysFiltered.length - 1); } Index: ps/trunk/binaries/data/mods/public/gui/replaymenu/replay_filters.js =================================================================== --- ps/trunk/binaries/data/mods/public/gui/replaymenu/replay_filters.js (revision 18110) +++ ps/trunk/binaries/data/mods/public/gui/replaymenu/replay_filters.js (revision 18111) @@ -1,239 +1,298 @@ /** * Allow to filter replays by duration in 15min / 30min intervals. */ const g_DurationFilterIntervals = [ { "min": -1, "max": -1 }, { "min": -1, "max": 15 }, { "min": 15, "max": 30 }, { "min": 30, "max": 45 }, { "min": 45, "max": 60 }, { "min": 60, "max": 90 }, { "min": 90, "max": 120 }, { "min": 120, "max": -1 } ]; /** * Allow to filter by population capacity. */ const g_PopulationCapacities = prepareForDropdown(g_Settings && g_Settings.PopulationCapacities); /** * Reloads the selectable values in the filters. The filters depend on g_Settings and g_Replays * (including its derivatives g_MapSizes, g_MapNames). */ function initFilters(filters) { Engine.GetGUIObjectByName("compatibilityFilter").checked = !filters || filters.compatibility; if (filters && filters.playernames) Engine.GetGUIObjectByName("playersFilter").caption = filters.playernames; initDateFilter(filters && filters.date); initMapSizeFilter(filters && filters.mapSize); initMapNameFilter(filters && filters.mapName); initPopCapFilter(filters && filters.popCap); initDurationFilter(filters && filters.duration); + initSingleplayerFilter(filters && filters.singleplayer); + initVictoryConditionFilter(filters && filters.victoryCondition); + initRatedGamesFilter(filters && filters.ratedGames); } /** * Allow to filter by month. Uses g_Replays. */ function initDateFilter(date) { var months = g_Replays.map(replay => getReplayMonth(replay)); months = months.filter((month, index) => months.indexOf(month) == index).sort(); var dateTimeFilter = Engine.GetGUIObjectByName("dateTimeFilter"); dateTimeFilter.list = [translateWithContext("datetime", "Any")].concat(months); dateTimeFilter.list_data = [""].concat(months); if (date) dateTimeFilter.selected = dateTimeFilter.list_data.indexOf(date); if (dateTimeFilter.selected == -1 || dateTimeFilter.selected >= dateTimeFilter.list.length) dateTimeFilter.selected = 0; } /** * Allow to filter by mapsize. Uses g_MapSizes. */ function initMapSizeFilter(mapSize) { var mapSizeFilter = Engine.GetGUIObjectByName("mapSizeFilter"); mapSizeFilter.list = [translateWithContext("map size", "Any")].concat(g_MapSizes.Name); mapSizeFilter.list_data = [-1].concat(g_MapSizes.Tiles); if (mapSize) mapSizeFilter.selected = mapSizeFilter.list_data.indexOf(mapSize); if (mapSizeFilter.selected == -1 || mapSizeFilter.selected >= mapSizeFilter.list.length) mapSizeFilter.selected = 0; } /** * Allow to filter by mapname. Uses g_MapNames. */ function initMapNameFilter(mapName) { var mapNameFilter = Engine.GetGUIObjectByName("mapNameFilter"); mapNameFilter.list = [translateWithContext("map name", "Any")].concat(g_MapNames.map(mapName => translate(mapName))); mapNameFilter.list_data = [""].concat(g_MapNames); if (mapName) mapNameFilter.selected = mapNameFilter.list_data.indexOf(mapName); if (mapNameFilter.selected == -1 || mapNameFilter.selected >= mapNameFilter.list.length) mapNameFilter.selected = 0; } /** * Allow to filter by population capacity. */ function initPopCapFilter(popCap) { var populationFilter = Engine.GetGUIObjectByName("populationFilter"); populationFilter.list = [translateWithContext("population capacity", "Any")].concat(g_PopulationCapacities.Title); populationFilter.list_data = [""].concat(g_PopulationCapacities.Population); if (popCap) populationFilter.selected = populationFilter.list_data.indexOf(popCap); if (populationFilter.selected == -1 || populationFilter.selected >= populationFilter.list.length) populationFilter.selected = 0; } /** * Allow to filter by game duration. Uses g_DurationFilterIntervals. */ function initDurationFilter(duration) { var durationFilter = Engine.GetGUIObjectByName("durationFilter"); durationFilter.list = g_DurationFilterIntervals.map((interval, index) => { if (index == 0) return translateWithContext("duration", "Any"); if (index == 1) // Translation: Shorter duration than max minutes. return sprintf(translatePluralWithContext("duration filter", "< %(max)s min", "< %(max)s min", interval.max), interval); if (index == g_DurationFilterIntervals.length - 1) // Translation: Longer duration than min minutes. return sprintf(translatePluralWithContext("duration filter", "> %(min)s min", "> %(min)s min", interval.min), interval); // Translation: Duration between min and max minutes. return sprintf(translateWithContext("duration filter", "%(min)s - %(max)s min"), interval); }); durationFilter.list_data = g_DurationFilterIntervals.map((interval, index) => index); if (duration) durationFilter.selected = durationFilter.list_data.indexOf(duration); if (durationFilter.selected == -1 || durationFilter.selected >= g_DurationFilterIntervals.length) durationFilter.selected = 0; } +function initSingleplayerFilter(singleplayer) +{ + let singleplayerFilter = Engine.GetGUIObjectByName("singleplayerFilter"); + singleplayerFilter.list = [translate("Single- and multiplayer"), translate("Single Player"), translate("Multiplayer")]; + singleplayerFilter.list_data = ["", "Singleplayer", "Multiplayer"]; + + if (singleplayer) + singleplayerFilter.selected = singleplayerFilter.list_data.indexOf(singleplayer); + + if (singleplayerFilter.selected < 0 || singleplayerFilter.selected >= singleplayerFilter.list.length) + singleplayerFilter.selected = 0; +} + +function initVictoryConditionFilter(victoryCondition) +{ + let victoryConditionFilter = Engine.GetGUIObjectByName("victoryConditionFilter"); + victoryConditionFilter.list = [translateWithContext("victory condition", "Any gametype")].concat(g_VictoryConditions.map(victoryCondition => translateVictoryCondition(victoryCondition))); + victoryConditionFilter.list_data = [""].concat(g_VictoryConditions); + + if (victoryCondition) + victoryConditionFilter.selected = victoryConditionFilter.list_data.indexOf(victoryCondition); + + if (victoryConditionFilter.selected < 0 || victoryConditionFilter.selected >= victoryConditionFilter.list.length) + victoryConditionFilter.selected = 0; +} + +function initRatedGamesFilter(ratedGames) +{ + let ratedGamesFilter = Engine.GetGUIObjectByName("ratedGamesFilter"); + ratedGamesFilter.list = [translate("Rated and unrated games"), translate("Rated games"), translate("Unrated games")]; + ratedGamesFilter.list_data = ["", "rated", "not rated"]; + + if (ratedGames) + ratedGamesFilter.selected = ratedGamesFilter.list_data.indexOf(ratedGames); + + if (ratedGamesFilter.selected < 0 || ratedGamesFilter.selected >= ratedGamesFilter.list.length) + ratedGamesFilter.selected = 0; +} + /** * Initializes g_ReplaysFiltered with replays that are not filtered out and sort it. */ function filterReplays() { const sortKey = Engine.GetGUIObjectByName("replaySelection").selected_column; const sortOrder = Engine.GetGUIObjectByName("replaySelection").selected_column_order; g_ReplaysFiltered = g_Replays.filter(replay => filterReplay(replay)).sort((a, b) => { let cmpA, cmpB; switch (sortKey) { case 'name': cmpA = +a.timestamp; cmpB = +b.timestamp; break; case 'duration': cmpA = +a.duration; cmpB = +b.duration; break; case 'players': cmpA = +a.attribs.settings.PlayerData.length; cmpB = +b.attribs.settings.PlayerData.length; break; case 'mapName': cmpA = getReplayMapName(a); cmpB = getReplayMapName(b); break; case 'mapSize': cmpA = +a.attribs.settings.Size; cmpB = +b.attribs.settings.Size; break; case 'popCapacity': cmpA = +a.attribs.settings.PopulationCap; cmpB = +b.attribs.settings.PopulationCap; break; } if (cmpA < cmpB) return -sortOrder; else if (cmpA > cmpB) return +sortOrder; return 0; }); } /** * Decides whether the replay should be listed. * * @returns {bool} - true if replay should be visible */ function filterReplay(replay) { // Check for compatibility first (most likely to filter) let compatibilityFilter = Engine.GetGUIObjectByName("compatibilityFilter"); if (compatibilityFilter.checked && !isReplayCompatible(replay)) return false; + // Filter by singleplayer / multiplayer + let singleplayerFilter = Engine.GetGUIObjectByName("singleplayerFilter"); + if (singleplayerFilter.selected == 1 && replay.isMultiplayer || + singleplayerFilter.selected == 2 && !replay.isMultiplayer) + return false; + + // Filter by victory condition + let victoryConditionFilter = Engine.GetGUIObjectByName("victoryConditionFilter"); + if (victoryConditionFilter.selected > 0 && replay.attribs.settings.GameType != victoryConditionFilter.list_data[victoryConditionFilter.selected]) + return false; + + // Filter by rating + let ratedGamesFilter = Engine.GetGUIObjectByName("ratedGamesFilter"); + if (ratedGamesFilter.selected == 1 && !replay.isRated || + ratedGamesFilter.selected == 2 && replay.isRated) + return false + // Filter date/time (select a month) - var dateTimeFilter = Engine.GetGUIObjectByName("dateTimeFilter"); + let dateTimeFilter = Engine.GetGUIObjectByName("dateTimeFilter"); if (dateTimeFilter.selected > 0 && getReplayMonth(replay) != dateTimeFilter.list_data[dateTimeFilter.selected]) return false; // Filter by playernames - var playersFilter = Engine.GetGUIObjectByName("playersFilter"); - var keywords = playersFilter.caption.toLowerCase().split(" "); + let playersFilter = Engine.GetGUIObjectByName("playersFilter"); + let keywords = playersFilter.caption.toLowerCase().split(" "); if (keywords.length) { // We just check if all typed words are somewhere in the playerlist of that replay let playerList = replay.attribs.settings.PlayerData.map(player => player ? player.Name : "").join(" ").toLowerCase(); if (!keywords.every(keyword => playerList.indexOf(keyword) != -1)) return false; } // Filter by map name - var mapNameFilter = Engine.GetGUIObjectByName("mapNameFilter"); + let mapNameFilter = Engine.GetGUIObjectByName("mapNameFilter"); if (mapNameFilter.selected > 0 && getReplayMapName(replay) != mapNameFilter.list_data[mapNameFilter.selected]) return false; // Filter by map size - var mapSizeFilter = Engine.GetGUIObjectByName("mapSizeFilter"); + let mapSizeFilter = Engine.GetGUIObjectByName("mapSizeFilter"); if (mapSizeFilter.selected > 0 && replay.attribs.settings.Size != mapSizeFilter.list_data[mapSizeFilter.selected]) return false; // Filter by population capacity - var populationFilter = Engine.GetGUIObjectByName("populationFilter"); + let populationFilter = Engine.GetGUIObjectByName("populationFilter"); if (populationFilter.selected > 0 && replay.attribs.settings.PopulationCap != populationFilter.list_data[populationFilter.selected]) return false; // Filter by game duration - var durationFilter = Engine.GetGUIObjectByName("durationFilter"); + let durationFilter = Engine.GetGUIObjectByName("durationFilter"); if (durationFilter.selected > 0) { let interval = g_DurationFilterIntervals[durationFilter.selected]; if ((interval.min > -1 && replay.duration < interval.min * 60) || (interval.max > -1 && replay.duration > interval.max * 60)) return false; } return true; } Index: ps/trunk/binaries/data/mods/public/gui/replaymenu/replay_menu.js =================================================================== --- ps/trunk/binaries/data/mods/public/gui/replaymenu/replay_menu.js (revision 18110) +++ ps/trunk/binaries/data/mods/public/gui/replaymenu/replay_menu.js (revision 18111) @@ -1,375 +1,395 @@ /** * Used for checking replay compatibility. */ const g_EngineInfo = Engine.GetEngineInfo(); /** * To show the titles of the selected civs in the replay details. */ const g_CivData = loadCivData(); /** * 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 = ""; /** * Initializes globals, loads replays and displays the list. */ function init(data) { if (!g_Settings) { Engine.SwitchGuiPage("page_pregame.xml"); return; } loadReplays(data && data.replaySelectionData); if (!g_Replays) { Engine.SwitchGuiPage("page_pregame.xml"); return; } displayReplayList(); } /** * Store the list of replays loaded in C++ in g_Replays. - * Check timestamp and compatibility and extract g_Playernames, g_MapNames. + * Check timestamp and compatibility and extract g_Playernames, g_MapNames, g_VictoryConditions. * Restore selected filters and item. */ function loadReplays(replaySelectionData) { g_Replays = Engine.GetReplays(); if (!g_Replays) return; g_Playernames = []; for (let replay of g_Replays) { + let nonAIPlayers = 0; // Use time saved in file, otherwise file mod date replay.timestamp = replay.attribs.timestamp ? +replay.attribs.timestamp : +replay.filemod_timestamp-replay.duration; // 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; } } /** * 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 = ""; }); } /** * Filter g_Replays, fill the GUI list with that data and show the description of the current replay. */ function displayReplayList() { // Remember previously selected replay var replaySelection = Engine.GetGUIObjectByName("replaySelection"); if (replaySelection.selected != -1) g_SelectedReplayDirectory = g_ReplaysFiltered[replaySelection.selected].directory; filterReplays(); // Create GUI list data var list = g_ReplaysFiltered.map(replay => { let works = replay.isCompatible; return { "directories": replay.directory, "months": greyout(getReplayDateTime(replay), works), "popCaps": greyout(translatePopulationCapacity(replay.attribs.settings.PopulationCap), works), "mapNames": greyout(getReplayMapName(replay), works), "mapSizes": greyout(translateMapSize(replay.attribs.settings.Size), works), "durations": greyout(getReplayDuration(replay), works), "playerNames": greyout(getReplayPlayernames(replay), works) }; }); // Extract arrays if (list.length) list = prepareForDropdown(list); // Push to GUI replaySelection.selected = -1; replaySelection.list_name = 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 || []; // Restore selection replaySelection.selected = replaySelection.list.findIndex(directory => directory == g_SelectedReplayDirectory); displayReplayDetails(); } /** * Shows preview image, description and player text in the right panel. */ function displayReplayDetails() { var selected = Engine.GetGUIObjectByName("replaySelection").selected; var 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("summaryButton").hidden = true; if (!replaySelected) return; var replay = g_ReplaysFiltered[selected]; var mapData = getMapDescriptionAndPreview(replay.attribs.settings.mapType, replay.attribs.map); // Update GUI 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 = replay.attribs.settings.PlayerData.length; Engine.GetGUIObjectByName("sgPlayersNames").caption = getReplayTeamText(replay); Engine.GetGUIObjectByName("sgMapDescription").caption = mapData.description; Engine.GetGUIObjectByName("summaryButton").hidden = !Engine.HasReplayMetadata(replay.directory); setMapPreviewImage("sgMapPreview", mapData.preview); } /** * Adds grey font if replay is not compatible. */ function greyout(text, isCompatible) { return isCompatible ? text : '[color="96 96 96"]' + text + '[/color]'; } /** * Returns a human-readable version of the replay date. */ function getReplayDateTime(replay) { return Engine.FormatMillisecondsIntoDateString(replay.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.FormatMillisecondsIntoDateString(replay.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; } /** * Returns a description of the player assignments. * Including civs, teams, AI settings and player colors. * * If the spoiler-checkbox is checked, it also shows defeated players. * * @returns {string} */ function getReplayTeamText(replay) { // Load replay metadata const metadata = Engine.GetReplayMetadata(replay.directory); const spoiler = Engine.GetGUIObjectByName("showSpoiler").checked; var playerDescriptions = {}; var playerIdx = 0; for (let playerData of replay.attribs.settings.PlayerData) { // Get player info ++playerIdx; let teamIdx = playerData.Team; let playerColor = playerData.Color ? playerData.Color : g_Settings.PlayerDefaults[playerIdx].Color; let playerCiv = !playerData.Civ ? translate("Unknown Civilization") : (g_CivData[playerData.Civ] && g_CivData[playerData.Civ].Name ? translate(g_CivData[playerData.Civ].Name) : playerData.Civ); let showDefeated = spoiler && metadata && metadata.playerStates && metadata.playerStates[playerIdx].state == "defeated"; let isAI = playerData.AI; // Create human-readable player description let playerDetails = { "playerName": '[color="' + rgbToGuiColor(playerColor) + '"]' + escapeText(playerData.Name) + "[/color]", "civ": playerCiv, "AIname": isAI ? translateAIName(playerData.AI) : "", "AIdifficulty": isAI ? translateAIDifficulty(playerData.AIDiff) : "" }; if (!isAI && !showDefeated) playerDetails = sprintf(translateWithContext("replay", "%(playerName)s (%(civ)s)"), playerDetails); else if (!isAI && showDefeated) playerDetails = sprintf(translateWithContext("replay", "%(playerName)s (%(civ)s, defeated)"), playerDetails); else if (isAI && !showDefeated) playerDetails = sprintf(translateWithContext("replay", "%(playerName)s (%(civ)s, %(AIdifficulty)s %(AIname)s)"), playerDetails); else playerDetails = sprintf(translateWithContext("replay", "%(playerName)s (%(civ)s, %(AIdifficulty)s %(AIname)s, defeated)"), playerDetails); // Sort player descriptions by team if (!playerDescriptions[teamIdx]) playerDescriptions[teamIdx] = []; playerDescriptions[teamIdx].push(playerDetails); } var teams = Object.keys(playerDescriptions); // If there are no teams, merge all playersDescriptions if (teams.length == 1) return playerDescriptions[teams[0]].join("\n") + "\n"; // If there are teams, merge "Team N:" + playerDescriptions return teams.map(team => { let teamCaption = (team == -1) ? translate("No Team") : sprintf(translate("Team %(team)s"), { "team": +team + 1 }); return '[font="sans-bold-14"]' + teamCaption + "[/font]:\n" + playerDescriptions[team].join("\n"); }).join("\n"); } Index: ps/trunk/binaries/data/mods/public/gui/replaymenu/replay_menu.xml =================================================================== --- ps/trunk/binaries/data/mods/public/gui/replaymenu/replay_menu.xml (revision 18110) +++ ps/trunk/binaries/data/mods/public/gui/replaymenu/replay_menu.xml (revision 18111) @@ -1,240 +1,255 @@