Ticket #3258: t3258_visual_replay_menu_v9.patch
File t3258_visual_replay_menu_v9.patch, 69.3 KB (added by , 9 years ago) |
---|
-
binaries/data/mods/public/gui/common/functions_utility.js
function initMapSizes() 129 129 } 130 130 131 131 return sizes; 132 132 } 133 133 134 /** 135 * Returns title or placeholder. Requires g_mapSizes. 136 * 137 * @param mapSize {Number} - tilecount 138 */ 139 function translateMapSize(tiles) 140 { 141 var idx = g_mapSizes.tiles.findIndex(tileCount => tileCount == tiles); 142 return (idx == -1) ? translateWithContext("map size", "Default") : g_mapSizes.shortNames[idx]; 143 } 144 145 /** 146 * Returns map description and preview image or placeholder. 147 */ 148 function getMapDescriptionAndPreview(mapType, mapName) 149 { 150 var mapData; 151 if (mapType == "random" && mapName == "random") 152 mapData = { "settings": { "Description": translate("A randomly selected map.") } }; 153 else if (mapType == "random" && Engine.FileExists(mapName + ".json")) 154 mapData = Engine.ReadJSONFile(mapName + ".json"); 155 else if (Engine.FileExists(mapName + ".xml")) 156 mapData = Engine.LoadMapSettings(mapName + ".xml"); 157 else 158 warn(sprintf("Map '%(mapName)s' not found locally.", { "mapName": mapName })); 159 160 return { 161 "description": mapData && mapData.settings.Description ? translate(mapData.settings.Description) : translate("Sorry, no description available."), 162 "preview": mapData && mapData.settings.Preview ? mapData.settings.Preview : "nopreview.png" 163 }; 164 } 165 134 166 // ==================================================================== 135 167 136 168 // Convert integer color values to string (for use in GUI objects) 137 169 function rgbToGuiColor(color, alpha) 138 170 { -
binaries/data/mods/public/gui/common/settings.js
function prepareForDropdown(settingValue 254 254 if (settingValues[index].Default) 255 255 settings.Default = +index; 256 256 } 257 257 return settings; 258 258 } 259 260 /** 261 * Returns title or placeholder. 262 * 263 * @param aiName {string} - for example "petra" 264 */ 265 function translateAIName(aiName) 266 { 267 var description = g_Settings.AIDescriptions.find(ai => ai.id == aiName); 268 return description ? translate(description.data.name) : translate("Unknown"); 269 } 270 271 /** 272 * Returns title or placeholder. 273 * 274 * @param index {Number} - index of AIDifficulties 275 */ 276 function translateAIDifficulty(index) 277 { 278 var difficulty = g_Settings.AIDifficulties[index]; 279 return difficulty ? difficulty.Title : translate("Unknown"); 280 } 281 282 /** 283 * Returns title or placeholder. 284 * 285 * @param mapType {string} - for example "skirmish" 286 */ 287 function translateMapType(mapType) 288 { 289 var type = g_Settings.MapTypes.find(t => t.Name == mapType); 290 return type ? type.Title : translate("Unknown"); 291 } 292 293 /** 294 * Returns title or placeholder. 295 * 296 * @param population {Number} - for example 300 297 */ 298 function translatePopulationCapacity(population) 299 { 300 var popCap = g_Settings.PopulationCapacities.find(p => p.Population == population); 301 return popCap ? popCap.Title : translate("Unknown"); 302 } 303 304 /** 305 * Returns title or placeholder. 306 * 307 * @param gameType {string} - for example "conquest" 308 */ 309 function translateVictoryCondition(gameType) 310 { 311 var vc = g_Settings.VictoryConditions.find(vc => vc.Name == gameType); 312 return vc ? vc.Title : translate("Unknown"); 313 } -
binaries/data/mods/public/gui/lobby/lobby.js
function updateGameList() 483 483 else 484 484 name = '[color="255 0 0"]' + name + '[/color]'; 485 485 list_name.push(name); 486 486 list_ip.push(g.ip); 487 487 list_mapName.push(translate(g.niceMapName)); 488 list_mapSize.push(translate dMapSize(g.mapSize));488 list_mapSize.push(translateMapSize(g.mapSize)); 489 489 let mapTypeIdx = g_MapTypes.Name.indexOf(g.mapType); 490 490 list_mapType.push(mapTypeIdx != -1 ? g_MapTypes.Title[mapTypeIdx] : ""); 491 491 list_nPlayers.push(g.nbp + "/" +g.tnbp); 492 492 list.push(name); 493 493 list_data.push(c); … … function formatPlayerListEntry(nickname, 561 561 // Push this player's name and status onto the list 562 562 return [formattedName, formattedStatus, formattedRating]; 563 563 } 564 564 565 565 /** 566 * Given a map size, returns that map size translated into the current567 * language.568 */569 function translatedMapSize(mapSize)570 {571 if (+mapSize !== +mapSize) // NaN572 return translate(mapSize);573 else574 return g_mapSizes.shortNames[g_mapSizes.tiles.indexOf(+mapSize)];575 }576 577 /**578 566 * Populate the game info area with information on the current game selection. 579 567 */ 580 568 function updateGameSelection() 581 569 { 582 570 var selected = Engine.GetGUIObjectByName("gamesBox").selected; … … function updateGameSelection() 587 575 Engine.GetGUIObjectByName("joinGameButton").hidden = true; 588 576 Engine.GetGUIObjectByName("gameInfoEmpty").hidden = false; 589 577 return; 590 578 } 591 579 592 var mapData;593 580 var g = Engine.GetGUIObjectByName("gamesBox").list_data[selected]; 594 581 595 // Load map data596 if (g_GameList[g].mapType == "random" && g_GameList[g].mapName == "random")597 mapData = {"settings": {"Description": translate("A randomly selected map.")}};598 else if (g_GameList[g].mapType == "random" && Engine.FileExists(g_GameList[g].mapName + ".json"))599 mapData = Engine.ReadJSONFile(g_GameList[g].mapName + ".json");600 else if (Engine.FileExists(g_GameList[g].mapName + ".xml"))601 mapData = Engine.LoadMapSettings(g_GameList[g].mapName + ".xml");602 else603 // Warn the player if we can't find the map.604 warn(sprintf("Map '%(mapName)s' not found locally.", { mapName: g_GameList[g].mapName }));605 606 582 // Show the game info panel and join button. 607 583 Engine.GetGUIObjectByName("gameInfo").hidden = false; 608 584 Engine.GetGUIObjectByName("joinGameButton").hidden = false; 609 585 Engine.GetGUIObjectByName("gameInfoEmpty").hidden = true; 610 586 … … function updateGameSelection() 614 590 Engine.GetGUIObjectByName("sgPlayersNames").caption = g_GameList[g].players; 615 591 Engine.GetGUIObjectByName("sgMapSize").caption = translatedMapSize(g_GameList[g].mapSize); 616 592 let mapTypeIdx = g_MapTypes.Name.indexOf(g_GameList[g].mapType); 617 593 Engine.GetGUIObjectByName("sgMapType").caption = mapTypeIdx != -1 ? g_MapTypes.Title[mapTypeIdx] : ""; 618 594 619 // Display map description if it exists, otherwise display a placeholder. 620 if (mapData && mapData.settings.Description) 621 var mapDescription = translate(mapData.settings.Description); 622 else 623 var mapDescription = translate("Sorry, no description available."); 624 625 // Display map preview if it exists, otherwise display a placeholder. 626 if (mapData && mapData.settings.Preview) 627 var mapPreview = mapData.settings.Preview; 628 else 629 var mapPreview = "nopreview.png"; 630 631 Engine.GetGUIObjectByName("sgMapDescription").caption = mapDescription; 632 Engine.GetGUIObjectByName("sgMapPreview").sprite = "cropped:(0.7812,0.5859)session/icons/mappreview/" + mapPreview; 595 // Display map description and preview (or placeholder) 596 var mapData = getMapDescriptionAndPreview(g_GameList[g].mapType, g_GameList[g].mapName); 597 Engine.GetGUIObjectByName("sgMapDescription").caption = mapData.description; 598 Engine.GetGUIObjectByName("sgMapPreview").sprite = "cropped:(0.7812,0.5859)session/icons/mappreview/" + mapData.preview; 633 599 } 634 600 635 601 /** 636 602 * Start the joining process on the currectly selected game. 637 603 */ -
binaries/data/mods/public/gui/page_replaymenu.xml
1 <?xml version="1.0" encoding="utf-8"?> 2 <page> 3 <include>common/modern/setup.xml</include> 4 <include>common/modern/styles.xml</include> 5 <include>common/modern/sprites.xml</include> 6 7 <include>common/setup.xml</include> 8 <include>common/sprite1.xml</include> 9 <include>common/styles.xml</include> 10 <include>common/common_sprites.xml</include> 11 <include>common/common_styles.xml</include> 12 13 <include>replaymenu/styles.xml</include> 14 <include>replaymenu/replay_menu.xml</include> 15 </page> -
binaries/data/mods/public/gui/pregame/mainmenu.xml
344 344 Engine.PushGuiPage("page_locale.xml"); 345 345 ]]> 346 346 </action> 347 347 </object> 348 348 349 <object name="submenuReplayButton" 350 type="button" 351 style="StoneButtonFancy" 352 size="0 64 100% 92" 353 tooltip_style="pgToolTip" 354 > 355 <translatableAttribute id="caption">Replay</translatableAttribute> 356 <translatableAttribute id="tooltip">Shows a replay of a past game.</translatableAttribute> 357 <action on="Press"> 358 closeMenu(); 359 Engine.SwitchGuiPage("page_replaymenu.xml"); 360 </action> 361 </object> 362 349 363 <object name="submenuEditorButton" 350 364 style="StoneButtonFancy" 351 365 type="button" 352 size="0 64 100% 92"366 size="0 96 100% 124" 353 367 tooltip_style="pgToolTip" 354 368 > 355 369 <translatableAttribute id="caption">Scenario Editor</translatableAttribute> 356 370 <translatableAttribute id="tooltip">Open the Atlas Scenario Editor in a new window. You can run this more reliably by starting the game with the command-line argument "-editor".</translatableAttribute> 357 371 <action on="Press"> … … 360 374 </object> 361 375 362 376 <object name="submenuWelcomeScreenButton" 363 377 style="StoneButtonFancy" 364 378 type="button" 365 size="0 96 100% 124"379 size="0 128 100% 156" 366 380 tooltip_style="pgToolTip" 367 381 > 368 382 <translatableAttribute id="caption">Welcome Screen</translatableAttribute> 369 383 <translatableAttribute id="tooltip">Show the Welcome Screen. Useful if you hid it by mistake.</translatableAttribute> 370 384 <action on="Press"> … … 375 389 </action> 376 390 </object> 377 391 <object name="submenuModSelection" 378 392 style="StoneButtonFancy" 379 393 type="button" 380 size="0 1 28 100% 156"394 size="0 156 100% 188" 381 395 tooltip_style="pgToolTip" 382 396 > 383 397 <translatableAttribute id="caption">Mod Selection</translatableAttribute> 384 398 <translatableAttribute id="tooltip">Select mods to use.</translatableAttribute> 385 399 <action on="Press"> … … 482 496 > 483 497 <translatableAttribute id="caption">Tools & Options</translatableAttribute> 484 498 <translatableAttribute id="tooltip">Game options and scenario design tools.</translatableAttribute> 485 499 <action on="Press"> 486 500 closeMenu(); 487 openMenu("submenuToolsAndOptions", (this.parent.size.top+this.size.top), (this.size.bottom-this.size.top), 5);501 openMenu("submenuToolsAndOptions", (this.parent.size.top+this.size.top), (this.size.bottom-this.size.top), 6); 488 502 </action> 489 503 </object> 490 504 491 505 <!-- EXIT BUTTON --> 492 506 <object name="menuExitButton" -
binaries/data/mods/public/gui/replaymenu/replay_actions.js
1 /** 2 * Starts the selected visual replay, or shows an error message in case of incompatibility. 3 */ 4 function startReplay() 5 { 6 var selected = Engine.GetGUIObjectByName("replaySelection").selected; 7 if (selected == -1) 8 return; 9 10 var replay = g_ReplaysFiltered[selected]; 11 if (isReplayCompatible(replay)) 12 reallyStartVisualReplay(replay.directory); 13 else 14 displayReplayCompatibilityError(replay); 15 } 16 17 /** 18 * Attempts the visual replay, regardless of the compatibility. 19 * 20 * @param replayDirectory {string} 21 */ 22 function reallyStartVisualReplay(replayDirectory) 23 { 24 // TODO: enhancement: restore filter settings and selected replay when returning from the summary screen. 25 Engine.StartVisualReplay(replayDirectory); 26 Engine.SwitchGuiPage("page_loading.xml", { 27 "attribs": Engine.GetReplayAttributes(replayDirectory), 28 "isNetworked" : false, 29 "playerAssignments": {}, 30 "savedGUIData": "", 31 "isReplay" : true 32 }); 33 } 34 35 /** 36 * Shows an error message stating why the replay is not compatible. 37 * 38 * @param replay {Object} 39 */ 40 function displayReplayCompatibilityError(replay) 41 { 42 var errMsg; 43 if (replayHasSameEngineVersion(replay)) 44 { 45 let gameMods = replay.attribs.mods ? replay.attribs.mods : []; 46 errMsg = translate("You don't have the same mods active as the replay.") + "\n"; 47 errMsg += sprintf(translate("Required: %(mods)s"), { "mods": gameMods.join(", ") }) + "\n"; 48 errMsg += sprintf(translate("Active: %(mods)s"), { "mods": g_EngineInfo.mods.join(", ") }); 49 } 50 else 51 errMsg = translate("This replay is not compatible with your version of the game!"); 52 53 messageBox(500, 200, errMsg, translate("REPLAY INCOMPATIBLE"), 0, [translate("Ok")], [null]); 54 } 55 56 /** 57 * Opens the summary screen of the given replay, if its data was found in that directory. 58 */ 59 function showReplaySummary() 60 { 61 var selected = Engine.GetGUIObjectByName("replaySelection").selected; 62 if (selected == -1) 63 return; 64 65 // Load summary screen data from the selected replay directory 66 var summary = Engine.GetReplayMetadata(g_ReplaysFiltered[selected].directory); 67 68 if (!summary) 69 { 70 messageBox(500, 200, translate("No summary data found!"), translate("ERROR"), 0, [translate("Ok")], [null]); 71 return; 72 } 73 74 // Open summary screen 75 summary.isReplay = true; 76 summary.gameResult = translate("Scores at the end of the game."); 77 Engine.SwitchGuiPage("page_summary.xml", summary); 78 } 79 80 /** 81 * Callback. 82 */ 83 function deleteReplayButtonPressed() 84 { 85 if (!Engine.GetGUIObjectByName("deleteReplayButton").enabled) 86 return; 87 88 if (Engine.HotkeyIsPressed("session.savedgames.noConfirmation")) 89 deleteReplayWithoutConfirmation(); 90 else 91 deleteReplay(); 92 } 93 /** 94 * Shows a confirmation dialog and deletes the selected replay from the disk in case. 95 */ 96 function deleteReplay() 97 { 98 // Get selected replay 99 var selected = Engine.GetGUIObjectByName("replaySelection").selected; 100 if (selected == -1) 101 return; 102 103 var replay = g_ReplaysFiltered[selected]; 104 105 // Show confirmation message 106 var btCaptions = [translate("Yes"), translate("No")]; 107 var btCode = [function() { reallyDeleteReplay(replay.directory); }, null]; 108 109 var title = translate("DELETE"); 110 var question = translate("Are you sure to delete this replay permanently?") + "\n" + replay.file; 111 112 messageBox(500, 200, question, title, 0, btCaptions, btCode); 113 } 114 115 /** 116 * Attempts to delete the selected replay from the disk. 117 */ 118 function deleteReplayWithoutConfirmation() 119 { 120 var selected = Engine.GetGUIObjectByName("replaySelection").selected; 121 if (selected > -1) 122 reallyDeleteReplay(g_ReplaysFiltered[selected].directory); 123 } 124 125 /** 126 * Attempts to delete the given replay directory from the disk. 127 * 128 * @param replayDirectory {string} 129 */ 130 function reallyDeleteReplay(replayDirectory) 131 { 132 if (!Engine.DeleteReplay(replayDirectory)) 133 error(sprintf("Could not delete replay '%(id)s'", { "id": replayDirectory })); 134 135 // Refresh replay list 136 init(); 137 } -
binaries/data/mods/public/gui/replaymenu/replay_filters.js
1 /** 2 * Allow to filter replays by duration in 15min / 30min intervals. 3 */ 4 const g_DurationFilterIntervals = [ 5 { "min": -1, "max": -1 }, 6 { "min": -1, "max": 15 }, 7 { "min": 15, "max": 30 }, 8 { "min": 30, "max": 45 }, 9 { "min": 45, "max": 60 }, 10 { "min": 60, "max": 90 }, 11 { "min": 90, "max": 120 }, 12 { "min": 120, "max": -1 } 13 ]; 14 15 /** 16 * Allow to filter by population capacity. 17 */ 18 const g_PopulationCapacities = prepareForDropdown(g_Settings ? g_Settings.PopulationCapacities : undefined); 19 20 /** 21 * Reloads the selectable values in the filters. The filters depend on g_Settings and g_Replays 22 * (including its derivatives g_MapSizes, g_MapNames). 23 */ 24 function initFilters() 25 { 26 initDateFilter(); 27 initMapNameFilter(); 28 initMapSizeFilter(); 29 initPopCapFilter(); 30 initDurationFilter(); 31 } 32 33 /** 34 * Allow to filter by month. Uses g_Replays. 35 */ 36 function initDateFilter() 37 { 38 var months = g_Replays.map(replay => getReplayMonth(replay)); 39 months = months.filter((month, index) => months.indexOf(month) == index).sort(); 40 months.unshift(translateWithContext("datetime", "Any")); 41 42 var dateTimeFilter = Engine.GetGUIObjectByName("dateTimeFilter"); 43 dateTimeFilter.list = months; 44 dateTimeFilter.list_data = months; 45 46 if (dateTimeFilter.selected == -1 || dateTimeFilter.selected >= dateTimeFilter.list.length) 47 dateTimeFilter.selected = 0; 48 } 49 50 /** 51 * Allow to filter by mapsize. Uses g_MapSizes. 52 */ 53 function initMapSizeFilter() 54 { 55 var mapSizeFilter = Engine.GetGUIObjectByName("mapSizeFilter"); 56 mapSizeFilter.list = [translateWithContext("map size", "Any")].concat(g_mapSizes.shortNames); 57 mapSizeFilter.list_data = [-1].concat(g_mapSizes.tiles); 58 59 if (mapSizeFilter.selected == -1 || mapSizeFilter.selected >= mapSizeFilter.list.length) 60 mapSizeFilter.selected = 0; 61 } 62 63 /** 64 * Allow to filter by mapname. Uses g_MapNames. 65 */ 66 function initMapNameFilter() 67 { 68 var mapNameFilter = Engine.GetGUIObjectByName("mapNameFilter"); 69 mapNameFilter.list = [translateWithContext("map name", "Any")].concat(g_MapNames); 70 mapNameFilter.list_data = [""].concat(g_MapNames.map(mapName => translate(mapName))); 71 72 if (mapNameFilter.selected == -1 || mapNameFilter.selected >= mapNameFilter.list.length) 73 mapNameFilter.selected = 0; 74 } 75 76 /** 77 * Allow to filter by population capacity. 78 */ 79 function initPopCapFilter() 80 { 81 var populationFilter = Engine.GetGUIObjectByName("populationFilter"); 82 populationFilter.list = [translateWithContext("population capacity", "Any")].concat(g_PopulationCapacities.Title); 83 populationFilter.list_data = [""].concat(g_PopulationCapacities.Population); 84 85 if (populationFilter.selected == -1 || populationFilter.selected >= populationFilter.list.length) 86 populationFilter.selected = 0; 87 } 88 89 /** 90 * Allow to filter by game duration. Uses g_DurationFilterIntervals. 91 */ 92 function initDurationFilter() 93 { 94 var durationFilter = Engine.GetGUIObjectByName("durationFilter"); 95 durationFilter.list = g_DurationFilterIntervals.map((interval, index) => { 96 97 if (index == 0) 98 return translateWithContext("duration", "Any"); 99 100 if (index == 1) 101 // Translation: Shorter duration than max minutes. 102 return sprintf(translateWithContext("duration filter", "< %(max)s min"), interval); 103 104 if (index == g_DurationFilterIntervals.length - 1) 105 // Translation: Longer duration than min minutes. 106 return sprintf(translateWithContext("duration filter", "> %(min)s min"), interval); 107 108 // Translation: Duration between min and max minutes. 109 return sprintf(translateWithContext("duration filter", "%(min)s - %(max)s min"), interval); 110 }); 111 durationFilter.list_data = g_DurationFilterIntervals.map((interval, index) => index); 112 113 if (durationFilter.selected == -1 || durationFilter.selected >= g_DurationFilterIntervals.length) 114 durationFilter.selected = 0; 115 } 116 117 /** 118 * Initializes g_ReplaysFiltered with replays that are not filtered out and sort it. 119 */ 120 function filterReplays() 121 { 122 const sortKey = Engine.GetGUIObjectByName("replaySelection").selected_column; 123 const sortOrder = Engine.GetGUIObjectByName("replaySelection").selected_column_order; 124 125 g_ReplaysFiltered = g_Replays.filter(replay => filterReplay(replay)).sort((a, b) => 126 { 127 let cmpA, cmpB; 128 switch (sortKey) 129 { 130 case 'name': 131 cmpA = a.timestamp; 132 cmpB = b.timestamp; 133 break; 134 case 'duration': 135 cmpA = a.duration; 136 cmpB = b.duration; 137 break; 138 case 'players': 139 cmpA = a.attribs.settings.PlayerData.length; 140 cmpB = b.attribs.settings.PlayerData.length; 141 break; 142 case 'mapName': 143 cmpA = getReplayMapName(a); 144 cmpB = getReplayMapName(b); 145 break; 146 case 'mapSize': 147 cmpA = a.attribs.settings.Size; 148 cmpB = b.attribs.settings.Size; 149 break; 150 case 'popCapacity': 151 cmpA = a.attribs.settings.PopulationCap; 152 cmpB = b.attribs.settings.PopulationCap; 153 break; 154 } 155 156 if (cmpA < cmpB) 157 return -sortOrder; 158 else if (cmpA > cmpB) 159 return +sortOrder; 160 161 return 0; 162 }); 163 } 164 165 /** 166 * Decides whether the replay should be listed. 167 * 168 * @returns {bool} - true if replay should be visible 169 */ 170 function filterReplay(replay) 171 { 172 // Check for compability first (most likely to filter) 173 var compabilityFilter = Engine.GetGUIObjectByName("compabilityFilter"); 174 if (compabilityFilter.checked && !isReplayCompatible(replay)) 175 return false; 176 177 // Filter date/time (select a month) 178 var dateTimeFilter = Engine.GetGUIObjectByName("dateTimeFilter"); 179 if (dateTimeFilter.selected > 0 && getReplayMonth(replay) != dateTimeFilter.list_data[dateTimeFilter.selected]) 180 return false; 181 182 // Filter by playernames 183 var playersFilter = Engine.GetGUIObjectByName("playersFilter"); 184 var keywords = playersFilter.caption.toLowerCase().split(" "); 185 if (keywords.length) 186 { 187 // We just check if all typed words are somewhere in the playerlist of that replay 188 let playerList = replay.attribs.settings.PlayerData.map(player => player ? player.Name : "").join(" ").toLowerCase(); 189 if (!keywords.every(keyword => playerList.indexOf(keyword) != -1)) 190 return false; 191 } 192 193 // Filter by map name 194 var mapNameFilter = Engine.GetGUIObjectByName("mapNameFilter"); 195 if (mapNameFilter.selected > 0 && getReplayMapName(replay) != mapNameFilter.list_data[mapNameFilter.selected]) 196 return false; 197 198 // Filter by map size 199 var mapSizeFilter = Engine.GetGUIObjectByName("mapSizeFilter"); 200 if (mapSizeFilter.selected > 0 && replay.attribs.settings.Size != mapSizeFilter.list_data[mapSizeFilter.selected]) 201 return false; 202 203 // Filter by population capacity 204 var populationFilter = Engine.GetGUIObjectByName("populationFilter"); 205 if (populationFilter.selected > 0 && replay.attribs.settings.PopulationCap != populationFilter.list_data[populationFilter.selected]) 206 return false; 207 208 // Filter by game duration 209 var durationFilter = Engine.GetGUIObjectByName("durationFilter"); 210 if (durationFilter.selected > 0) 211 { 212 let interval = g_DurationFilterIntervals[durationFilter.selected]; 213 214 if ((interval.min > -1 && replay.duration < interval.min * 60) || 215 (interval.max > -1 && replay.duration > interval.max * 60)) 216 return false; 217 } 218 219 return true; 220 } -
binaries/data/mods/public/gui/replaymenu/replay_menu.js
1 const g_EngineInfo = Engine.GetEngineInfo(); 2 const g_CivData = loadCivData(); 3 const g_DefaultPlayerData = initPlayerDefaults(); 4 const g_mapSizes = initMapSizes(); 5 6 /** 7 * All replays found in the directory. 8 */ 9 var g_Replays = []; 10 11 /** 12 * List of replays after applying the display filter. 13 */ 14 var g_ReplaysFiltered = []; 15 16 /** 17 * Array of unique usernames of all replays. Used for autocompleting usernames. 18 */ 19 var g_Playernames = []; 20 21 /** 22 * Sorted list of unique maptitles. Used by mapfilter. 23 */ 24 var g_MapNames = []; 25 26 /** 27 * Directory name of the currently selected replay. Used to restore the selection after changing filters. 28 */ 29 var g_selectedReplayDirectory = ""; 30 31 /** 32 * Initializes globals, loads replays and displays the list. 33 */ 34 function init() 35 { 36 if (!g_Settings) 37 { 38 Engine.SwitchGuiPage("page_pregame.xml"); 39 return; 40 } 41 42 // By default, sort replays by date in descending order 43 Engine.GetGUIObjectByName("replaySelection").selected_column_order = -1; 44 45 loadReplays(); 46 displayReplayList(); 47 } 48 49 /** 50 * Store the list of replays loaded in C++ in g_Replays. 51 * Check timestamp and compatibility and extract g_Playernames, g_MapNames 52 */ 53 function loadReplays() 54 { 55 g_Replays = Engine.GetReplays(); 56 57 g_Playernames = []; 58 for (let replay of g_Replays) 59 { 60 // Use time saved in file, otherwise file mod date 61 replay.timestamp = replay.attribs.timestamp ? replay.attribs.timestamp : replay.filemod_timestamp; 62 63 // Check replay for compability 64 replay.isCompatible = isReplayCompatible(replay); 65 66 sanitizeGameAttributes(replay.attribs); 67 68 // Extract map names 69 if (g_MapNames.indexOf(replay.attribs.settings.Name) == -1 && replay.attribs.settings.Name != "") 70 g_MapNames.push(replay.attribs.settings.Name); 71 72 // Extract playernames 73 for (let playerData of replay.attribs.settings.PlayerData) 74 { 75 if (!playerData || playerData.AI) 76 continue; 77 78 // Remove rating from nick 79 let playername = playerData.Name; 80 let ratingStart = playername.indexOf(" ("); 81 if (ratingStart != -1) 82 playername = playername.substr(0, ratingStart); 83 84 if (g_Playernames.indexOf(playername) == -1) 85 g_Playernames.push(playername); 86 } 87 } 88 g_MapNames.sort(); 89 90 // Reload filters (since they depend on g_Replays and its derivatives) 91 initFilters(); 92 } 93 94 /** 95 * We may encounter malformed replays. 96 */ 97 function sanitizeGameAttributes(attribs) 98 { 99 if (!attribs.settings) 100 attribs.settings = {}; 101 102 if (!attribs.settings.Size) 103 attribs.settings.Size = -1; 104 105 if (!attribs.settings.Name) 106 attribs.settings.Name = ""; 107 108 if (!attribs.settings.PlayerData) 109 attribs.settings.PlayerData = []; 110 111 if (!attribs.settings.PopulationCap) 112 attribs.settings.PopulationCap = 300; 113 114 if (!attribs.settings.mapType) 115 attribs.settings.mapType = "skirmish"; 116 117 if (!attribs.settings.GameType) 118 attribs.settings.GameType = "conquest"; 119 120 // Remove gaia 121 if (attribs.settings.PlayerData.length && attribs.settings.PlayerData[0] == null) 122 attribs.settings.PlayerData.shift(); 123 124 attribs.settings.PlayerData.forEach((pData, index) => { 125 if (!pData.Name) 126 pData.Name = ""; 127 }); 128 } 129 130 /** 131 * Filter g_Replays, fill the GUI list with that data and show the description of the current replay. 132 */ 133 function displayReplayList() 134 { 135 // Remember previously selected replay 136 var replaySelection = Engine.GetGUIObjectByName("replaySelection"); 137 if (replaySelection.selected != -1) 138 g_selectedReplayDirectory = g_ReplaysFiltered[replaySelection.selected].directory; 139 140 filterReplays(); 141 142 // Create GUI list data 143 var list = g_ReplaysFiltered.map(replay => { 144 let works = replay.isCompatible; 145 return { 146 "directories": replay.directory, 147 "months": greyout(getReplayDateTime(replay), works), 148 "popCaps": greyout(translatePopulationCapacity(replay.attribs.settings.PopulationCap), works), 149 "mapNames": greyout(getReplayMapName(replay), works), 150 "mapSizes": greyout(translateMapSize(replay.attribs.settings.Size), works), 151 "durations": greyout(getReplayDuration(replay), works), 152 "playerNames": greyout(getReplayPlayernames(replay), works) 153 }; 154 }); 155 156 // Extract arrays 157 if (list.length) 158 list = prepareForDropdown(list); 159 160 // Push to GUI 161 replaySelection.selected = -1; 162 replaySelection.list_name = list.months || []; 163 replaySelection.list_players = list.playerNames || []; 164 replaySelection.list_mapName = list.mapNames || []; 165 replaySelection.list_mapSize = list.mapSizes || []; 166 replaySelection.list_popCapacity = list.popCaps || []; 167 replaySelection.list_duration = list.durations || []; 168 169 // Change these last, otherwise crash 170 replaySelection.list = list.directories || []; 171 replaySelection.list_data = list.directories || []; 172 173 // Restore selection 174 replaySelection.selected = replaySelection.list.findIndex(directory => directory == g_selectedReplayDirectory); 175 176 displayReplayDetails(); 177 } 178 179 /** 180 * Shows preview image, description and player text in the right panel. 181 */ 182 function displayReplayDetails() 183 { 184 var selected = Engine.GetGUIObjectByName("replaySelection").selected; 185 var replaySelected = selected > -1; 186 187 Engine.GetGUIObjectByName("replayInfo").hidden = !replaySelected; 188 Engine.GetGUIObjectByName("replayInfoEmpty").hidden = replaySelected; 189 Engine.GetGUIObjectByName("startReplayButton").enabled = replaySelected; 190 Engine.GetGUIObjectByName("deleteReplayButton").enabled = replaySelected; 191 Engine.GetGUIObjectByName("summaryButton").enabled = replaySelected; 192 193 if (!replaySelected) 194 return; 195 196 var replay = g_ReplaysFiltered[selected]; 197 var mapData = getMapDescriptionAndPreview(replay.attribs.settings.mapType, replay.attribs.map); 198 199 // Update GUI 200 Engine.GetGUIObjectByName("sgMapName").caption = translate(replay.attribs.settings.Name); 201 Engine.GetGUIObjectByName("sgMapSize").caption = translateMapSize(replay.attribs.settings.Size); 202 Engine.GetGUIObjectByName("sgMapType").caption = translateMapType(replay.attribs.settings.mapType); 203 Engine.GetGUIObjectByName("sgVictory").caption = translateVictoryCondition(replay.attribs.settings.GameType); 204 Engine.GetGUIObjectByName("sgNbPlayers").caption = replay.attribs.settings.PlayerData.length; 205 Engine.GetGUIObjectByName("sgPlayersNames").caption = getReplayTeamText(replay); 206 Engine.GetGUIObjectByName("sgMapDescription").caption = mapData.description; 207 Engine.GetGUIObjectByName("sgMapPreview").sprite = "cropped:(0.7812,0.5859)session/icons/mappreview/" + mapData.preview; 208 } 209 210 /** 211 * Adds grey font if replay is not compatible. 212 */ 213 function greyout(text, isCompatible) 214 { 215 return isCompatible ? text : '[color="96 96 96"]' + text + '[/color]'; 216 } 217 218 /** 219 * Returns a human-readable version of the replay date. 220 */ 221 function getReplayDateTime(replay) 222 { 223 return Engine.FormatMillisecondsIntoDateString(replay.timestamp * 1000, translate("yyyy-MM-dd HH:mm")) 224 } 225 226 /** 227 * Returns a human-readable list of the playernames of that replay. 228 * 229 * @returns {string} 230 */ 231 function getReplayPlayernames(replay) 232 { 233 // TODO: colorize playernames like in the lobby. 234 return replay.attribs.settings.PlayerData.map(pData => pData.Name).join(", "); 235 } 236 237 /** 238 * Returns the name of the map of the given replay. 239 * 240 * @returns {string} 241 */ 242 function getReplayMapName(replay) 243 { 244 return translate(replay.attribs.settings.Name); 245 } 246 247 /** 248 * Returns the month of the given replay in the format "yyyy-MM". 249 * 250 * @returns {string} 251 */ 252 function getReplayMonth(replay) 253 { 254 return Engine.FormatMillisecondsIntoDateString(replay.timestamp * 1000, translate("yyyy-MM")); 255 } 256 257 /** 258 * Returns a human-readable version of the time when the replay started. 259 * 260 * @returns {string} 261 */ 262 function getReplayDuration(replay) 263 { 264 return timeToString(replay.duration * 1000); 265 } 266 267 /** 268 * True if we can start the given replay with the currently loaded mods. 269 */ 270 function isReplayCompatible(replay) 271 { 272 return replayHasSameEngineVersion(replay) && hasSameMods(replay.attribs, g_EngineInfo); 273 } 274 275 /** 276 * True if we can start the given replay with the currently loaded mods. 277 */ 278 function replayHasSameEngineVersion(replay) 279 { 280 return replay.attribs.engine_version && replay.attribs.engine_version == g_EngineInfo.engine_version; 281 } 282 283 /** 284 * Returns a description of the player assignments. 285 * Including civs, teams, AI settings and player colors. 286 * 287 * If the spoiler-checkbox is checked, it also shows defeated players. 288 * 289 * @returns {string} 290 */ 291 function getReplayTeamText(replay) 292 { 293 // Load replay metadata 294 const metadata = Engine.GetReplayMetadata(replay.directory); 295 const spoiler = Engine.GetGUIObjectByName("showSpoiler").checked; 296 297 var playerDescriptions = {}; 298 var playerIdx = 0; 299 for (let playerData of replay.attribs.settings.PlayerData) 300 { 301 // Get player info 302 ++playerIdx; 303 let teamIdx = playerData.Team; 304 let playerColor = playerData.Color ? playerData.Color : g_DefaultPlayerData[playerIdx].Color; 305 let showDefeated = spoiler && metadata && metadata.playerStates && metadata.playerStates[playerIdx].state == "defeated"; 306 let isAI = playerData.AI; 307 308 // Create human-readable player description 309 let playerDetails = { 310 "playerName": '[color="' + rgbToGuiColor(playerColor) + '"]' + escapeText(playerData.Name) + "[/color]", 311 "civ": translate(g_CivData[playerData.Civ].Name), 312 "AIname": isAI ? translateAIName(playerData.AI) : "", 313 "AIdifficulty": isAI ? translateAIDifficulty(playerData.AIDiff) : "" 314 }; 315 316 if (!isAI && !showDefeated) 317 playerDetails = sprintf(translateWithContext("replay", "%(playerName)s (%(civ)s)"), playerDetails); 318 else if (!isAI && showDefeated) 319 playerDetails = sprintf(translateWithContext("replay", "%(playerName)s (%(civ)s, defeated)"), playerDetails); 320 else if (isAI && !showDefeated) 321 playerDetails = sprintf(translateWithContext("replay", "%(playerName)s (%(civ)s, %(AIdifficulty)s %(AIname)s)"), playerDetails); 322 else 323 playerDetails = sprintf(translateWithContext("replay", "%(playerName)s (%(civ)s, %(AIdifficulty)s %(AIname)s, defeated)"), playerDetails); 324 325 // Sort player descriptions by team 326 if (!playerDescriptions[teamIdx]) 327 playerDescriptions[teamIdx] = []; 328 playerDescriptions[teamIdx].push(playerDetails); 329 } 330 331 var teams = Object.keys(playerDescriptions); 332 333 // If there are no teams, merge all playersDescriptions 334 if (teams.length == 1) 335 return playerDescriptions[teams[0]].join("\n") + "\n"; 336 337 // If there are teams, merge "Team N:" + playerDescriptions 338 return teams.map(team => { 339 let teamCaption = (team == -1) ? translate("No Team") : sprintf(translate("Team %(team)s"), { "team": +team + 1 }); 340 return '[font="sans-bold-14"]' + teamCaption + "[/font]:\n" + playerDescriptions[team].join("\n"); 341 }).join("\n\n"); 342 } -
binaries/data/mods/public/gui/replaymenu/replay_menu.xml
1 <?xml version="1.0" encoding="utf-8"?> 2 3 <objects> 4 5 <!-- Used to display game info. --> 6 <script file="gui/common/functions_civinfo.js" /> 7 <script file="gui/common/functions_utility.js" /> 8 <script file="gui/common/settings.js" /> 9 10 <!-- Used to display message boxes. --> 11 <script file="gui/common/functions_global_object.js" /> 12 13 <!-- Used for engine + mod version checks. --> 14 <script file="gui/common/functions_utility_loadsave.js" /> 15 16 <!-- Actual replay scripts after settings.js, as it initializes g_Settings. --> 17 <script file="gui/replaymenu/replay_menu.js" /> 18 <script file="gui/replaymenu/replay_actions.js" /> 19 <script file="gui/replaymenu/replay_filters.js" /> 20 21 <!-- Everything displayed in the replay menu. --> 22 <object type="image" style="ModernWindow" size="0 0 100% 100%" name="replayWindow"> 23 24 <!-- Title --> 25 <object style="ModernLabelText" type="text" size="50%-128 4 50%+128 36"> 26 <translatableAttribute id="caption">Replay Games</translatableAttribute> 27 </object> 28 29 <!-- Left Panel: Filters & Replay List --> 30 <object name="leftPanel" size="3% 5% 100%-255 100%-80"> 31 32 <!-- Filters --> 33 <object name="filterPanel" size="0 0 100% 24"> 34 35 <object name="dateTimeFilter" type="dropdown" style="ModernDropDown" size="5 0 12%-10 100%" font="sans-bold-13"> 36 <action on="SelectionChange">displayReplayList();</action> 37 </object> 38 39 <object name="playersFilter" type="input" style="ModernInput" size="12%-5 0 56%-10 100%" font="sans-bold-13"> 40 <action on="Press">displayReplayList();</action> 41 <action on="Tab">autoCompleteNick("playersFilter", g_Playernames.map(name => ({ "name": name })));</action> 42 </object> 43 44 <object name="mapNameFilter" type="dropdown" style="ModernDropDown" size="56%-5 0 70%-10 100%" font="sans-bold-13"> 45 <action on="SelectionChange">displayReplayList();</action> 46 </object> 47 48 <object name="mapSizeFilter" type="dropdown" style="ModernDropDown" size="70%-5 0 80%-10 100%" font="sans-bold-13"> 49 <action on="SelectionChange">displayReplayList();</action> 50 </object> 51 52 <object name="populationFilter" type="dropdown" style="ModernDropDown" size="80%-5 0 90%-10 100%" font="sans-bold-13"> 53 <action on="SelectionChange">displayReplayList();</action> 54 </object> 55 56 <object name="durationFilter" type="dropdown" style="ModernDropDown" size="90%-5 0 100%-10 100%" font="sans-bold-13"> 57 <action on="SelectionChange">displayReplayList();</action> 58 </object> 59 60 </object> 61 62 <!-- Replay List in that left panel --> 63 <object name="replaySelection" size="0 35 100% 100%" style="ModernList" type="olist" sortable="true" default_column="name" sprite_asc="ModernArrowDown" sprite_desc="ModernArrowUp" sprite_not_sorted="ModernNotSorted" font="sans-stroke-13"> 64 65 <action on="SelectionChange">displayReplayDetails();</action> 66 <action on="SelectionColumnChange">displayReplayList();</action> 67 68 <!-- Columns --> 69 <!-- We have to call one "name" as the GUI expects one. --> 70 <def id="name" color="172 172 212" width="12%"> 71 <translatableAttribute id="heading" context="replay">Date / Time</translatableAttribute> 72 </def> 73 74 <def id="players" color="192 192 192" width="44%"> 75 <translatableAttribute id="heading" context="replay">Players</translatableAttribute> 76 </def> 77 78 <def id="mapName" color="192 192 192" width="14%"> 79 <translatableAttribute id="heading" context="replay">Map Name</translatableAttribute> 80 </def> 81 82 <def id="mapSize" color="192 192 192" width="10%"> 83 <translatableAttribute id="heading" context="replay">Size</translatableAttribute> 84 </def> 85 86 <def id="popCapacity" color="192 192 192" width="10%"> 87 <translatableAttribute id="heading" context="replay">Population</translatableAttribute> 88 </def> 89 90 <def id="duration" color="192 192 192" width="10%"> 91 <translatableAttribute id="heading" context="replay">Duration</translatableAttribute> 92 </def> 93 94 </object> 95 96 </object> 97 98 <!-- Right Panel: Compability Filter & Replay Details --> 99 <object name="rightPanel" size="100%-250 30 100%-20 100%-20" > 100 101 <!-- Compability Filter Checkbox --> 102 <object name="compabilityFilter" type="checkbox" checked="true" style="ModernTickBox" size="0 4 20 100%" font="sans-bold-13"> 103 <action on="Press">displayReplayList();</action> 104 </object> 105 106 <!-- Compability Filter Label --> 107 <object type="text" size="20 2 100% 100%" text_align="left" textcolor="white"> 108 <translatableAttribute id="caption">Filter compatible replays</translatableAttribute> 109 </object> 110 111 <!-- Placeholder to show if no replay is selected --> 112 <object name="replayInfoEmpty" size="0 30 100% 100%-60" type="image" sprite="ModernDarkBoxGold" hidden="false"> 113 <object name="logo" size="50%-110 40 50%+110 140" type="image" sprite="logo"/> 114 <object name="subjectBox" type="image" sprite="ModernDarkBoxWhite" size="3% 180 97% 99%"> 115 <object name="subject" size="5 5 100%-5 100%-5" type="text" style="ModernText" text_align="center"/> 116 </object> 117 </object> 118 119 <!-- --> 120 <object name="replayInfo" size="0 30 100% 100%-60" type="image" sprite="ModernDarkBoxGold" hidden="true"> 121 122 <!-- Map Name Label --> 123 <object name="sgMapName" size="0 5 100% 20" type="text" style="ModernLabelText"/> 124 125 <!-- Map Preview Image --> 126 <object name="sgMapPreview" size="5 25 100%-5 190" type="image" sprite=""/> 127 128 <!-- Separator Line --> 129 <object size="5 194 100%-5 195" type="image" sprite="ModernWhiteLine" z="25"/> 130 131 <!-- Map Type Caption --> 132 <object size="5 195 50% 225" type="image" sprite="ModernItemBackShadeLeft"> 133 <object size="0 0 100%-10 100%" type="text" style="ModernLabelText" text_align="right"> 134 <translatableAttribute id="caption">Map Type:</translatableAttribute> 135 </object> 136 </object> 137 138 <!-- Map Type Label --> 139 <object size="50% 195 100%-5 225" type="image" sprite="ModernItemBackShadeRight"> 140 <object name="sgMapType" size="0 0 100% 100%" type="text" style="ModernLabelText" text_align="left"/> 141 </object> 142 143 <!-- Separator Line --> 144 <object size="5 224 100%-5 225" type="image" sprite="ModernWhiteLine" z="25"/> 145 146 <!-- Map Size Caption --> 147 <object size="5 225 50% 255" type="image" sprite="ModernItemBackShadeLeft"> 148 <object size="0 0 100%-10 100%" type="text" style="ModernLabelText" text_align="right"> 149 <translatableAttribute id="caption">Map Size:</translatableAttribute> 150 </object> 151 </object> 152 153 <!-- Map Size Label --> 154 <object size="50% 225 100%-5 255" type="image" sprite="ModernItemBackShadeRight"> 155 <object name="sgMapSize" size="0 0 100% 100%" type="text" style="ModernLabelText" text_align="left"/> 156 </object> 157 158 <!-- Separator Line --> 159 <object size="5 254 100%-5 255" type="image" sprite="ModernWhiteLine" z="25"/> 160 161 <!-- Victory Condition Caption --> 162 <object size="5 255 50% 285" type="image" sprite="ModernItemBackShadeLeft"> 163 <object size="0 0 100%-10 100%" type="text" style="ModernLabelText" text_align="right"> 164 <translatableAttribute id="caption">Victory:</translatableAttribute> 165 </object> 166 </object> 167 168 <!-- Victory Condition Label --> 169 <object size="50% 255 100%-5 285" type="image" sprite="ModernItemBackShadeRight"> 170 <object name="sgVictory" size="0 0 100% 100%" type="text" style="ModernLabelText" text_align="left"/> 171 </object> 172 173 <!-- Separator Line --> 174 <object size="5 284 100%-5 285" type="image" sprite="ModernWhiteLine" z="25"/> 175 176 <!-- Map Description Text --> 177 <object type="image" sprite="ModernDarkBoxWhite" size="3% 290 97% 60%"> 178 <object name="sgMapDescription" size="0 0 100% 100%" type="text" style="ModernText" font="sans-12"/> 179 </object> 180 181 <object type="image" sprite="ModernDarkBoxWhite" size="3% 60%+5 97% 100%-30"> 182 183 <!-- Number of Players Caption--> 184 <object size="0% 3% 57% 12%" type="text" style="ModernRightLabelText"> 185 <translatableAttribute id="caption">Players:</translatableAttribute> 186 </object> 187 188 <!-- Number of Players Label--> 189 <object name="sgNbPlayers" size="58% 3% 70% 12%" type="text" style="ModernLeftLabelText" text_align="left"/> 190 191 <!-- Player Names --> 192 <object name="sgPlayersNames" size="0 15% 100% 100%" type="text" style="MapPlayerList"/> 193 </object> 194 195 <!-- "Show Spoiler" Checkbox --> 196 <object name="showSpoiler" type="checkbox" checked="false" style="ModernTickBox" size="10 100%-27 30 100%" font="sans-bold-13"> 197 <action on="Press">displayReplayDetails();</action> 198 </object> 199 200 <!-- "Show Spoiler" Label --> 201 <object type="text" size="30 100%-28 100% 100%" text_align="left" textcolor="white"> 202 <translatableAttribute id="caption">Spoiler</translatableAttribute> 203 </object> 204 205 </object> 206 </object> 207 208 209 <!-- Bottom Panel: Buttons. --> 210 <object name="bottomPanel" size="25 100%-55 100%-5 100%-25" > 211 212 <!-- Main Menu Button --> 213 <object type="button" style="StoneButton" size="25 0 17%+25 100%"> 214 <translatableAttribute id="caption">Main Menu</translatableAttribute> 215 <action on="Press">Engine.SwitchGuiPage("page_pregame.xml");</action> 216 </object> 217 218 <!-- Delete Button --> 219 <object name="deleteReplayButton" type="button" style="StoneButton" size="20%+25 0 37%+25 100%" hotkey="session.savedgames.delete"> 220 <translatableAttribute id="caption">Delete</translatableAttribute> 221 <action on="Press">deleteReplayButtonPressed();</action> 222 </object> 223 224 <!-- Summary Button --> 225 <object name="summaryButton" type="button" style="StoneButton" size="65%-50 0 82%-50 100%"> 226 <translatableAttribute id="caption">Summary</translatableAttribute> 227 <action on="Press">showReplaySummary();</action> 228 </object> 229 230 <!-- Start Replay Button --> 231 <object name="startReplayButton" type="button" style="StoneButton" size="83%-25 0 100%-25 100%"> 232 <translatableAttribute id="caption">Start Replay</translatableAttribute> 233 <action on="Press">startReplay();</action> 234 </object> 235 236 </object> 237 </object> 238 </objects> -
binaries/data/mods/public/gui/replaymenu/styles.xml
1 <?xml version="1.0" encoding="utf-8"?> 2 3 <styles> 4 <style name="MapPlayerList" 5 buffer_zone="8" 6 font="sans-14" 7 scrollbar="true" 8 scrollbar_style="ModernScrollBar" 9 scroll_bottom="true" 10 textcolor="white" 11 text_align="left" 12 text_valign="top" 13 /> 14 </styles> -
binaries/data/mods/public/gui/session/session.js
function leaveGame(willRejoin) 335 335 resignGame(true); 336 336 } 337 337 } 338 338 } 339 339 340 let summary = { 341 "timeElapsed" : extendedSimState.timeElapsed, 342 "playerStates": extendedSimState.players, 343 "players": g_Players, 344 "mapSettings": Engine.GetMapSettings(), 345 } 346 347 if (!g_IsReplay) 348 Engine.SaveReplayMetadata(JSON.stringify(summary)); 349 340 350 stopAmbient(); 341 351 Engine.EndGame(); 342 352 343 353 if (g_IsController && Engine.HasXmppClient()) 344 354 Engine.SendUnregisterGame(); 345 355 346 Engine.SwitchGuiPage("page_summary.xml", { 347 "gameResult" : gameResult, 348 "timeElapsed" : extendedSimState.timeElapsed, 349 "playerStates": extendedSimState.players, 350 "players": g_Players, 351 "mapSettings": mapSettings 352 }); 356 summary.gameResult = gameResult; 357 summary.isReplay = g_IsReplay; 358 Engine.SwitchGuiPage("page_summary.xml", summary); 353 359 } 354 360 355 361 // Return some data that we'll use when hotloading this file after changes 356 362 function getHotloadData() 357 363 { -
binaries/data/mods/public/gui/summary/summary.xml
156 156 </object> 157 157 158 158 <object type="button" style="ModernButtonRed" size="100%-160 100%-48 100%-20 100%-20"> 159 159 <translatableAttribute id="caption">Continue</translatableAttribute> 160 160 <action on="Press"><![CDATA[ 161 if (!Engine.HasXmppClient()) 161 if (g_GameData.isReplay) 162 { 163 Engine.SwitchGuiPage("page_replaymenu.xml"); 164 } 165 else if (!Engine.HasXmppClient()) 162 166 { 163 167 Engine.SwitchGuiPage("page_pregame.xml"); 164 168 } 165 169 else 166 170 { -
source/gui/scripting/ScriptFunctions.cpp
54 54 #include "ps/World.h" 55 55 #include "ps/scripting/JSInterface_ConfigDB.h" 56 56 #include "ps/scripting/JSInterface_Console.h" 57 57 #include "ps/scripting/JSInterface_Mod.h" 58 58 #include "ps/scripting/JSInterface_VFS.h" 59 #include "ps/scripting/JSInterface_VisualReplay.h" 59 60 #include "renderer/scripting/JSInterface_Renderer.h" 60 61 #include "simulation2/Simulation2.h" 61 62 #include "simulation2/components/ICmpAIManager.h" 62 63 #include "simulation2/components/ICmpCommandQueue.h" 63 64 #include "simulation2/components/ICmpGuiInterface.h" … … void GuiScriptingInit(ScriptInterface& s 926 927 JSI_ConfigDB::RegisterScriptFunctions(scriptInterface); 927 928 JSI_Mod::RegisterScriptFunctions(scriptInterface); 928 929 JSI_Sound::RegisterScriptFunctions(scriptInterface); 929 930 JSI_L10n::RegisterScriptFunctions(scriptInterface); 930 931 JSI_Lobby::RegisterScriptFunctions(scriptInterface); 932 JSI_VisualReplay::RegisterScriptFunctions(scriptInterface); 931 933 932 934 // VFS (external) 933 935 scriptInterface.RegisterFunction<JS::Value, std::wstring, std::wstring, bool, &JSI_VFS::BuildDirEntList>("BuildDirEntList"); 934 936 scriptInterface.RegisterFunction<bool, CStrW, JSI_VFS::FileExists>("FileExists"); 935 937 scriptInterface.RegisterFunction<double, std::wstring, &JSI_VFS::GetFileMTime>("GetFileMTime"); -
source/ps/Game.cpp
int CGame::LoadVisualReplayData() 159 159 else if (type == "end") 160 160 ++currentTurn; 161 161 else 162 162 CancelLoad(L"Failed to load replay data (unrecognized content)"); 163 163 } 164 m_ReplayStream->close(); 164 165 SAFE_DELETE(m_ReplayStream); 165 166 m_FinalReplayTurn = currentTurn; 166 167 replayTurnMgr->StoreFinalReplayTurn(currentTurn); 167 168 return 0; 168 169 } -
source/ps/Game.h
private: 184 184 bool m_IsSavedGame; // true if loading a saved game; false for a new game 185 185 186 186 int LoadVisualReplayData(); 187 187 std::string m_ReplayPath; 188 188 bool m_IsVisualReplay; 189 std::i stream* m_ReplayStream;189 std::ifstream* m_ReplayStream; 190 190 u32 m_FinalReplayTurn; 191 191 }; 192 192 193 193 extern CGame *g_Game; 194 194 -
source/ps/Replay.cpp
24 24 #include "lib/file/file_system.h" 25 25 #include "lib/res/h_mgr.h" 26 26 #include "lib/tex/tex.h" 27 27 #include "ps/Game.h" 28 28 #include "ps/Loader.h" 29 #include "ps/Mod.h" 29 30 #include "ps/Profile.h" 30 31 #include "ps/ProfileViewer.h" 32 #include "ps/Pyrogenesis.h" 31 33 #include "scriptinterface/ScriptInterface.h" 32 34 #include "scriptinterface/ScriptStats.h" 33 35 #include "simulation2/Simulation2.h" 34 36 #include "simulation2/helpers/SimulationCommand.h" 35 37 … … CReplayLogger::~CReplayLogger() 61 63 delete m_Stream; 62 64 } 63 65 64 66 void CReplayLogger::StartGame(JS::MutableHandleValue attribs) 65 67 { 68 // Add timestamp, since the file-modification-date can change 69 m_ScriptInterface.SetProperty(attribs, "timestamp", std::time(nullptr)); 70 71 // Add engine version and currently loaded mods for sanity checks when replaying 72 m_ScriptInterface.SetProperty(attribs, "engine_version", CStr(engine_version)); 73 m_ScriptInterface.SetProperty(attribs, "mods", g_modsLoaded); 74 66 75 // Construct the directory name based on the PID, to be relatively unique. 67 76 // Append "-1", "-2" etc if we run multiple matches in a single session, 68 77 // to avoid accidentally overwriting earlier logs. 69 70 78 std::wstringstream name; 71 79 name << getpid(); 72 80 73 81 static int run = -1; 74 82 if (++run) 75 83 name << "-" << run; 76 84 77 OsPath path = psLogDir() / L"sim_log" / name.str() / L"commands.txt"; 78 CreateDirectories(path.Parent(), 0700); 79 m_Stream = new std::ofstream(OsString(path).c_str(), std::ofstream::out | std::ofstream::trunc); 85 m_Directory = psLogDir() / L"sim_log" / name.str(); 86 CreateDirectories(m_Directory, 0700); 80 87 88 m_Stream = new std::ofstream(OsString(m_Directory / L"commands.txt").c_str(), std::ofstream::out | std::ofstream::trunc); 81 89 *m_Stream << "start " << m_ScriptInterface.StringifyJSON(attribs, false) << "\n"; 82 90 } 83 91 84 92 void CReplayLogger::Turn(u32 n, u32 turnLength, std::vector<SimulationCommand>& commands) 85 93 { … … void CReplayLogger::Hash(const std::stri 101 109 *m_Stream << "hash-quick " << Hexify(hash) << "\n"; 102 110 else 103 111 *m_Stream << "hash " << Hexify(hash) << "\n"; 104 112 } 105 113 114 OsPath CReplayLogger::GetDirectory() const 115 { 116 return m_Directory; 117 } 118 106 119 //////////////////////////////////////////////////////////////// 107 120 108 121 CReplayPlayer::CReplayPlayer() : 109 122 m_Stream(NULL) 110 123 { … … void CReplayPlayer::Replay(bool serializ 233 246 debug_printf("Unrecognised replay token %s\n", type.c_str()); 234 247 } 235 248 } 236 249 } 237 250 251 m_Stream->close(); 238 252 SAFE_DELETE(m_Stream); 239 240 253 g_Profiler2.SaveToFile(); 241 254 242 255 std::string hash; 243 256 bool ok = g_Game->GetSimulation2()->ComputeStateHash(hash, false); 244 257 ENSURE(ok); -
source/ps/Replay.h
public: 45 45 46 46 /** 47 47 * Optional hash of simulation state (for sync checking). 48 48 */ 49 49 virtual void Hash(const std::string& hash, bool quick) = 0; 50 51 /** 52 * Remember the directory containing the commands.txt file, so that we can save additional files to it. 53 */ 54 virtual OsPath GetDirectory() const = 0; 50 55 }; 51 56 52 57 /** 53 58 * Implementation of IReplayLogger that simply throws away all data. 54 59 */ … … class CDummyReplayLogger : public IRepla 56 61 { 57 62 public: 58 63 virtual void StartGame(JS::MutableHandleValue UNUSED(attribs)) { } 59 64 virtual void Turn(u32 UNUSED(n), u32 UNUSED(turnLength), std::vector<SimulationCommand>& UNUSED(commands)) { } 60 65 virtual void Hash(const std::string& UNUSED(hash), bool UNUSED(quick)) { } 66 virtual OsPath GetDirectory() const { return OsPath(); } 61 67 }; 62 68 63 69 /** 64 70 * Implementation of IReplayLogger that saves data to a file in the logs directory. 65 71 */ … … public: 71 77 ~CReplayLogger(); 72 78 73 79 virtual void StartGame(JS::MutableHandleValue attribs); 74 80 virtual void Turn(u32 n, u32 turnLength, std::vector<SimulationCommand>& commands); 75 81 virtual void Hash(const std::string& hash, bool quick); 82 virtual OsPath GetDirectory() const; 76 83 77 84 private: 78 85 ScriptInterface& m_ScriptInterface; 79 86 std::ostream* m_Stream; 87 OsPath m_Directory; 80 88 }; 81 89 82 90 /** 83 91 * Replay log replayer. Runs the log with no graphics and dumps some info to stdout. 84 92 */ … … public: 90 98 91 99 void Load(const std::string& path); 92 100 void Replay(bool serializationtest, bool ooslog); 93 101 94 102 private: 95 std::i stream* m_Stream;103 std::ifstream* m_Stream; 96 104 }; 97 105 98 106 #endif // INCLUDED_REPLAY -
source/ps/VisualReplay.cpp
1 /* Copyright (C) 2015 Wildfire Games. 2 * This file is part of 0 A.D. 3 * 4 * 0 A.D. is free software: you can redistribute it and/or modify 5 * it under the terms of the GNU General Public License as published by 6 * the Free Software Foundation, either version 2 of the License, or 7 * (at your option) any later version. 8 * 9 * 0 A.D. is distributed in the hope that it will be useful, 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 * GNU General Public License for more details. 13 * 14 * You should have received a copy of the GNU General Public License 15 * along with 0 A.D. If not, see <http://www.gnu.org/licenses/>. 16 */ 17 18 #include "precompiled.h" 19 20 #include "VisualReplay.h" 21 #include "graphics/GameView.h" 22 #include "gui/GUIManager.h" 23 #include "lib/allocators/shared_ptr.h" 24 #include "lib/utf8.h" 25 #include "ps/CLogger.h" 26 #include "ps/Filesystem.h" 27 #include "ps/Game.h" 28 #include "ps/Pyrogenesis.h" 29 #include "ps/Replay.h" 30 #include "scriptinterface/ScriptInterface.h" 31 32 /** 33 * Filter too short replays (value in seconds). 34 */ 35 const u8 minimumReplayDuration = 3; 36 37 /** 38 * Infinite loop protection. Should never occur. 39 */ 40 const u64 maxReads = 20 * 1024 * 1024; 41 42 OsPath VisualReplay::GetDirectoryName() 43 { 44 return OsPath(psLogDir() / L"sim_log"); 45 } 46 47 JS::Value VisualReplay::GetReplays(ScriptInterface& scriptInterface) 48 { 49 TIMER(L"GetReplays"); 50 JSContext* cx = scriptInterface.GetContext(); 51 JSAutoRequest rq(cx); 52 53 u32 i = 0; 54 DirectoryNames directories; 55 JS::RootedObject replays(cx, JS_NewArrayObject(cx, 0)); 56 if (GetDirectoryEntries(GetDirectoryName(), NULL, &directories) == INFO::OK) 57 for (OsPath& directory : directories) 58 { 59 //debug_printf("Loading %s\n", OsString(directory).c_str()); 60 JS::RootedValue replayData(cx, LoadReplayData(scriptInterface, directory)); 61 if (!replayData.isNull()) 62 JS_SetElement(cx, replays, i++, replayData); 63 } 64 return JS::ObjectValue(*replays); 65 } 66 67 /** 68 * Move the cursor backwards until a newline or the beginning of the file was found. 69 */ 70 bool goToLineBeginning(std::ifstream* replayStream, const CStr& filename) 71 { 72 char character; 73 u64 reads = 0; 74 while (true) 75 { 76 if (reads++ > maxReads) 77 { 78 LOGERROR("Infinite loop when parsing %s", filename.c_str()); 79 return false; 80 } 81 82 // Stop when reached the beginning of the file 83 if ((int) replayStream->tellg() == 0) 84 { 85 replayStream->seekg(0); 86 return true; 87 } 88 89 replayStream->get(character); 90 if (character == '\n') 91 return true; 92 93 // Go back one character. 94 // Notice: calling seekg with -1 will set the cursor back to the most recently read character. 95 replayStream->seekg(-2, std::ios_base::cur); 96 } 97 } 98 99 JS::Value VisualReplay::LoadReplayData(ScriptInterface& scriptInterface, OsPath& directory) 100 { 101 // The directory argument must not be constant, otherwise concatenating will fail 102 const OsPath replayFile = GetDirectoryName() / directory / L"commands.txt"; 103 104 if (!FileExists(replayFile)) 105 return JSVAL_NULL; 106 107 // Get file size and modification date 108 CFileInfo fileInfo; 109 GetFileInfo(replayFile, &fileInfo); 110 const u64 fileTime = (u64)fileInfo.MTime() & ~1; // skip lowest bit, since zip and FAT don't preserve it (according to CCacheLoader::LooseCachePath) 111 const u64 fileSize = (u64)fileInfo.Size(); 112 113 if (fileSize == 0) 114 return JSVAL_NULL; 115 116 // Open file TODO: support unicode when OsString() is implemented for windows 117 const CStr filename = utf8_from_wstring(replayFile.string()); 118 std::ifstream* replayStream = new std::ifstream(filename.c_str()); 119 120 // File must begin with "start" 121 CStr type; 122 if (!(*replayStream >> type).good() || type != "start") 123 { 124 LOGERROR("Couldn't open %s. Non-latin characters are not supported yet.", filename.c_str()); 125 return JSVAL_NULL; 126 } 127 128 // Parse header / first line 129 CStr header; 130 std::getline(*replayStream, header); 131 JSContext* cx = scriptInterface.GetContext(); 132 JSAutoRequest rq(cx); 133 JS::RootedValue attribs(cx); 134 if (!scriptInterface.ParseJSON(header, &attribs)) 135 { 136 LOGERROR("Couldn't parse replay header of %s", filename.c_str()); 137 return JSVAL_NULL; 138 } 139 140 // File must continue with "turn" 141 if (!(*replayStream >> type).good() || type != "turn") 142 return JSVAL_NULL; // there are no turns at all 143 144 // File must start with turn 0 (i.e. not a file of a rejoined client) 145 u32 turn = 1; 146 *replayStream >> turn; 147 if (turn != 0) 148 return JSVAL_NULL; 149 150 // Infinite loop protection 151 u64 reads = 0; 152 153 // Compute game duration. Assume constant turn length. 154 // Find the last line that starts with "turn" by reading the file backwards. 155 i64 duration = -1; 156 replayStream->seekg(-2, std::ios_base::end); 157 while (duration == -1) 158 { 159 if (reads++ > maxReads) 160 { 161 LOGERROR("Infinite loop when parsing %s", filename.c_str()); 162 return JSVAL_NULL; 163 } 164 165 if (!goToLineBeginning(replayStream, filename)) 166 return JSVAL_NULL; 167 168 if (!replayStream->good()) 169 { 170 LOGERROR("Unknown error when parsing %s", filename.c_str()); 171 return JSVAL_NULL; 172 } 173 174 // Remember cursor position 175 int lastNewline = (int) replayStream->tellg(); 176 177 // Parse the line 178 if ((*replayStream >> type).good() && type == "turn") 179 { 180 u32 turn = 0, turnLength = 0; 181 *replayStream >> turn >> turnLength; 182 duration = (turn+1) * turnLength; // add +1 as turn starts with 0 183 } 184 else if (lastNewline > 0) 185 { 186 // Reset stream state when reaching the end of file 187 replayStream->clear(); 188 189 // Move cursor back to the character before the newline 190 replayStream->seekg(lastNewline - 2, std::ios_base::beg); 191 } 192 193 if (!replayStream->good()) 194 { 195 LOGERROR("Unknown error when parsing %s", filename.c_str()); 196 return JSVAL_NULL; 197 } 198 } 199 200 replayStream->close(); 201 delete replayStream; 202 203 // Convert to seconds 204 duration = duration / 1000; 205 if (duration < minimumReplayDuration) 206 return JSVAL_NULL; 207 208 JS::RootedValue replayData(cx); 209 scriptInterface.Eval("({})", &replayData); 210 scriptInterface.SetProperty(replayData, "file", replayFile); 211 scriptInterface.SetProperty(replayData, "directory", directory); 212 scriptInterface.SetProperty(replayData, "filemod_timestamp", fileTime); 213 scriptInterface.SetProperty(replayData, "attribs", attribs); 214 scriptInterface.SetProperty(replayData, "duration", duration); 215 return replayData; 216 } 217 218 bool VisualReplay::DeleteReplay(const CStrW& replayDirectory) 219 { 220 if (replayDirectory.empty()) 221 return false; 222 223 const OsPath directory = GetDirectoryName() / replayDirectory; 224 return DirectoryExists(directory) && DeleteDirectory(directory) == INFO::OK; 225 } 226 227 228 JS::Value VisualReplay::GetReplayAttributes(ScriptInterface::CxPrivate* pCxPrivate, const CStrW& directoryName) 229 { 230 // Create empty JS object 231 JSContext* cx = pCxPrivate->pScriptInterface->GetContext(); 232 JSAutoRequest rq(cx); 233 JS::RootedValue attribs(cx); 234 pCxPrivate->pScriptInterface->Eval("({})", &attribs); 235 236 // Return empty object if file doesn't exist 237 const OsPath replayFile = GetDirectoryName() / directoryName / L"commands.txt"; 238 if (!FileExists(replayFile)) 239 return attribs; 240 241 // Open file 242 std::ifstream* replayStream = new std::ifstream(OsString(replayFile).c_str()); 243 CStr type, line; 244 ENSURE((*replayStream >> type).good() && type == "start"); 245 246 // Read and return first line 247 std::getline(*replayStream, line); 248 pCxPrivate->pScriptInterface->ParseJSON(line, &attribs); 249 replayStream->close(); 250 delete replayStream; 251 return attribs; 252 } 253 254 // TODO: enhancement: how to save the data if the process is killed? (case SDL_QUIT in main.cpp) 255 void VisualReplay::SaveReplayMetadata(const CStrW& data) 256 { 257 // TODO: enhancement: use JS::HandleValue similar to SaveGame 258 if (!g_Game) 259 return; 260 261 // Get the directory of the currently active replay 262 const OsPath filename = g_Game->GetReplayLogger().GetDirectory() / L"metadata.json"; 263 CreateDirectories(filename.Parent(), 0700); 264 265 std::ofstream stream (OsString(filename).c_str(), std::ofstream::out | std::ofstream::trunc); 266 stream << utf8_from_wstring(data); 267 stream.close(); 268 } 269 270 JS::Value VisualReplay::GetReplayMetadata(ScriptInterface::CxPrivate* pCxPrivate, const CStrW& directoryName) 271 { 272 const OsPath filePath = GetDirectoryName() / directoryName / L"metadata.json"; 273 274 JSContext* cx = pCxPrivate->pScriptInterface->GetContext(); 275 JSAutoRequest rq(cx); 276 JS::RootedValue metadata(cx); 277 278 if (!FileExists(filePath)) 279 return JSVAL_NULL; 280 281 std::ifstream* stream = new std::ifstream(OsString(filePath).c_str()); 282 ENSURE(stream->good()); 283 CStr line; 284 std::getline(*stream, line); 285 stream->close(); 286 delete stream; 287 pCxPrivate->pScriptInterface->ParseJSON(line, &metadata); 288 289 return metadata; 290 } -
source/ps/VisualReplay.h
1 /* Copyright (C) 2015 Wildfire Games. 2 * This file is part of 0 A.D. 3 * 4 * 0 A.D. is free software: you can redistribute it and/or modify 5 * it under the terms of the GNU General Public License as published by 6 * the Free Software Foundation, either version 2 of the License, or 7 * (at your option) any later version. 8 * 9 * 0 A.D. is distributed in the hope that it will be useful, 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 * GNU General Public License for more details. 13 * 14 * You should have received a copy of the GNU General Public License 15 * along with 0 A.D. If not, see <http://www.gnu.org/licenses/>. 16 */ 17 18 #ifndef INCLUDED_REPlAY 19 #define INCLUDED_REPlAY 20 21 #include "scriptinterface/ScriptInterface.h" 22 class CSimulation2; 23 class CGUIManager; 24 25 /** 26 * Contains functions for visually replaying past games. 27 */ 28 namespace VisualReplay 29 { 30 31 /** 32 * Returns the path to the sim-log directory (that contains the directories with the replay files. 33 * 34 * @param scriptInterface the ScriptInterface in which to create the return data. 35 * @return OsPath the absolute file path 36 */ 37 OsPath GetDirectoryName(); 38 39 /** 40 * Get a list of replays to display in the GUI. 41 * 42 * @param scriptInterface the ScriptInterface in which to create the return data. 43 * @return array of objects containing replay data 44 */ 45 JS::Value GetReplays(ScriptInterface& scriptInterface); 46 47 /** 48 * Parses a commands.txt file and extracts metadata. 49 * Works similarly to CGame::LoadReplayData(). 50 */ 51 JS::Value LoadReplayData(ScriptInterface& scriptInterface, OsPath& directory); 52 53 /** 54 * Permanently deletes the visual replay (including the parent directory) 55 * 56 * @param replayFile path to commands.txt, whose parent directory will be deleted 57 * @return true if deletion was successful, false on error 58 */ 59 bool DeleteReplay(const CStrW& replayFile); 60 61 /** 62 * Returns the parsed header of the replay file (commands.txt). 63 */ 64 JS::Value GetReplayAttributes(ScriptInterface::CxPrivate* pCxPrivate, const CStrW& directoryName); 65 66 /** 67 * Returns the metadata of a replay. 68 */ 69 JS::Value GetReplayMetadata(ScriptInterface::CxPrivate* pCxPrivate, const CStrW& directoryName); 70 71 /** 72 * Saves the metadata from the session to metadata.json 73 */ 74 void SaveReplayMetadata(const CStrW& data); 75 76 } 77 78 #endif -
source/ps/scripting/JSInterface_VisualReplay.cpp
1 /* Copyright (C) 2015 Wildfire Games. 2 * This file is part of 0 A.D. 3 * 4 * 0 A.D. is free software: you can redistribute it and/or modify 5 * it under the terms of the GNU General Public License as published by 6 * the Free Software Foundation, either version 2 of the License, or 7 * (at your option) any later version. 8 * 9 * 0 A.D. is distributed in the hope that it will be useful, 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 * GNU General Public License for more details. 13 * 14 * You should have received a copy of the GNU General Public License 15 * along with 0 A.D. If not, see <http://www.gnu.org/licenses/>. 16 */ 17 18 #include "precompiled.h" 19 20 #include "network/NetClient.h" 21 #include "network/NetServer.h" 22 #include "ps/Filesystem.h" 23 #include "ps/Game.h" 24 #include "ps/VisualReplay.h" 25 #include "ps/scripting/JSInterface_VisualReplay.h" 26 27 void JSI_VisualReplay::StartVisualReplay(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), CStrW directory) 28 { 29 ENSURE(!g_NetServer); 30 ENSURE(!g_NetClient); 31 ENSURE(!g_Game); 32 33 const OsPath replayFile = VisualReplay::GetDirectoryName() / directory / L"commands.txt"; 34 if (FileExists(replayFile)) 35 { 36 g_Game = new CGame(false, false); 37 // TODO: support unicode when OsString() is implemented for windows 38 g_Game->StartVisualReplay(utf8_from_wstring(replayFile.string())); 39 } 40 } 41 42 bool JSI_VisualReplay::DeleteReplay(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), CStrW replayFile) 43 { 44 return VisualReplay::DeleteReplay(replayFile); 45 } 46 47 JS::Value JSI_VisualReplay::GetReplays(ScriptInterface::CxPrivate* pCxPrivate) 48 { 49 return VisualReplay::GetReplays(*(pCxPrivate->pScriptInterface)); 50 } 51 52 JS::Value JSI_VisualReplay::GetReplayAttributes(ScriptInterface::CxPrivate* pCxPrivate, CStrW directoryName) 53 { 54 return VisualReplay::GetReplayAttributes(pCxPrivate, directoryName); 55 } 56 57 JS::Value JSI_VisualReplay::GetReplayMetadata(ScriptInterface::CxPrivate* pCxPrivate, CStrW directoryName) 58 { 59 return VisualReplay::GetReplayMetadata(pCxPrivate, directoryName); 60 } 61 62 void JSI_VisualReplay::SaveReplayMetadata(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), CStrW data) 63 { 64 VisualReplay::SaveReplayMetadata(data); 65 } 66 67 void JSI_VisualReplay::RegisterScriptFunctions(ScriptInterface& scriptInterface) 68 { 69 scriptInterface.RegisterFunction<JS::Value, &GetReplays>("GetReplays"); 70 scriptInterface.RegisterFunction<bool, CStrW, &DeleteReplay>("DeleteReplay"); 71 scriptInterface.RegisterFunction<void, CStrW, &StartVisualReplay>("StartVisualReplay"); 72 scriptInterface.RegisterFunction<JS::Value, CStrW, &GetReplayAttributes>("GetReplayAttributes"); 73 scriptInterface.RegisterFunction<JS::Value, CStrW, &GetReplayMetadata>("GetReplayMetadata"); 74 scriptInterface.RegisterFunction<void, CStrW, &SaveReplayMetadata>("SaveReplayMetadata"); 75 } -
source/ps/scripting/JSInterface_VisualReplay.h
1 /* Copyright (C) 2015 Wildfire Games. 2 * This file is part of 0 A.D. 3 * 4 * 0 A.D. is free software: you can redistribute it and/or modify 5 * it under the terms of the GNU General Public License as published by 6 * the Free Software Foundation, either version 2 of the License, or 7 * (at your option) any later version. 8 * 9 * 0 A.D. is distributed in the hope that it will be useful, 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 * GNU General Public License for more details. 13 * 14 * You should have received a copy of the GNU General Public License 15 * along with 0 A.D. If not, see <http://www.gnu.org/licenses/>. 16 */ 17 18 #ifndef INCLUDED_JSI_VISUALREPLAY 19 #define INCLUDED_JSI_VISUALREPLAY 20 21 #include "ps/VisualReplay.h" 22 #include "scriptinterface/ScriptInterface.h" 23 24 namespace JSI_VisualReplay 25 { 26 void StartVisualReplay(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), CStrW directory); 27 bool DeleteReplay(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), CStrW replayFile); 28 JS::Value GetReplays(ScriptInterface::CxPrivate* pCxPrivate); 29 JS::Value GetReplayAttributes(ScriptInterface::CxPrivate* pCxPrivate, CStrW directoryName); 30 JS::Value GetReplayMetadata(ScriptInterface::CxPrivate* pCxPrivate, CStrW directoryName); 31 void SaveReplayMetadata(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), CStrW data); 32 void RegisterScriptFunctions(ScriptInterface& scriptInterface); 33 } 34 35 #endif