Ticket #3258: replay_menu_v8.3_16877.patch
File replay_menu_v8.3_16877.patch, 92.1 KB (added by , 9 years ago) |
---|
-
binaries/data/mods/public/gui/aiconfig/aiconfig.js
1 1 var g_AIs; // [ {"id": ..., "data": {"name": ..., "description": ..., ...} }, ... ] 2 var g_AIDiffs; // translated AI difficulties 2 3 var g_PlayerSlot; 3 4 4 5 function init(settings) 5 6 { 6 7 g_PlayerSlot = settings.playerSlot; 7 8 8 translateObjectKeys(settings.ais, ["name", "description"]);9 9 g_AIs = [ 10 10 {id: "", data: {name: translateWithContext("ai", "None"), description: translate("AI will be disabled for this player.")}} 11 11 ].concat(settings.ais); 12 12 13 13 var aiSelection = Engine.GetGUIObjectByName("aiSelection"); … … 22 22 break; 23 23 } 24 24 } 25 25 aiSelection.selected = selected; 26 26 27 g_AIDiffs = initAIDifficulties(); // translated AI difficulties 27 28 var aiDiff = Engine.GetGUIObjectByName("aiDifficulty"); 28 // Translation: AI difficulty level. 29 aiDiff.list = [translateWithContext("aiDiff", "Sandbox"), translateWithContext("aiDiff", "Very Easy"), translateWithContext("aiDiff", "Easy"), translateWithContext("aiDiff", "Medium"), translateWithContext("aiDiff", "Hard"), translateWithContext("aiDiff", "Very Hard")]; 29 aiDiff.list = g_AIDiffs.titles; 30 30 aiDiff.selected = settings.difficulty; 31 31 } 32 32 33 33 function selectAI(idx) 34 34 { -
binaries/data/mods/public/gui/aiconfig/aiconfig.xml
1 1 <?xml version="1.0" encoding="utf-8"?> 2 2 3 3 <objects> 4 4 5 5 <script file="gui/aiconfig/aiconfig.js"/> 6 <script file="gui/common/functions_utility.js"/> 6 7 7 8 <!-- Add a translucent black background to fade out the menu page --> 8 9 <object type="image" sprite="ModernFade"/> 9 10 10 11 <object type="image" style="ModernDialog" size="50%-230 50%-130 50%+230 50%+130"> -
binaries/data/mods/public/gui/common/functions_utility.js
101 101 // ==================================================================== 102 102 103 103 // Load map size data 104 104 function initMapSizes() 105 105 { 106 varsizes = {106 let sizes = { 107 107 "shortNames":[], 108 108 "names":[], 109 109 "tiles": [], 110 110 "default": 0 111 111 }; 112 112 113 vardata = Engine.ReadJSONFile("simulation/data/map_sizes.json");113 let data = Engine.ReadJSONFile("simulation/data/map_sizes.json"); 114 114 if (!data || !data.Sizes) 115 115 { 116 116 error("Failed to parse map sizes in map_sizes.json (check for valid JSON data)"); 117 117 return sizes; 118 118 } 119 119 120 120 translateObjectKeys(data, ["Name", "LongName"]); 121 for ( vari = 0; i < data.Sizes.length; ++i)121 for (let i = 0; i < data.Sizes.length; ++i) 122 122 { 123 123 sizes.shortNames.push(data.Sizes[i].Name); 124 124 sizes.names.push(data.Sizes[i].LongName); 125 125 sizes.tiles.push(data.Sizes[i].Tiles); 126 126 127 127 if (data.Sizes[i].Default) 128 sizes ["default"]= i;128 sizes.default = i; 129 129 } 130 130 131 131 return sizes; 132 132 } 133 133 134 // Returns translated map size for a given a numeric size. Requires g_MapSizes to be present. 135 function translateMapSize(mapSize) 136 { 137 let index = g_MapSizes.tiles.indexOf(parseInt(mapSize)) 138 if (index > -1) 139 return g_MapSizes.shortNames[index]; 140 else 141 return translateWithContext("map size", "Default"); 142 } 143 144 // ==================================================================== 145 146 // Load map size data 147 function initPopulationCapacity() 148 { 149 let population = { 150 "population":[], 151 "titles": [], 152 "default": 0 153 }; 154 155 let data = Engine.ReadJSONFile("simulation/data/population_capacity.json"); 156 if (!data || !data.PopulationCapacity) 157 { 158 error("Failed to parse map sizes in population_capacity.json (check for valid JSON data)"); 159 return population; 160 } 161 162 translateObjectKeys(data, ["Title"]); 163 for (let i = 0; i < data.PopulationCapacity.length; ++i) 164 { 165 population.population.push(data.PopulationCapacity[i].Population); 166 population.titles.push(data.PopulationCapacity[i].Title); 167 168 if (data.PopulationCapacity[i].Default) 169 population.default = i; 170 } 171 172 return population; 173 } 174 175 // Returns the translation for a given numeric value. Requires g_PopCaps to be present. 176 function translatePopulationCapacity(popCap) 177 { 178 let index = g_PopCaps.population.indexOf(parseInt(popCap)) 179 if (index > -1) 180 return g_PopCaps.titles[index]; 181 else 182 return translateWithContext("population capacity", "Unknown"); 183 } 184 185 // ==================================================================== 186 187 // Load map type data 188 function initMapTypes() 189 { 190 let types = { 191 "names":[], 192 "titles":[], 193 "default": 0 194 }; 195 196 let data = Engine.ReadJSONFile("simulation/data/map_types.json"); 197 if (!data || !data.Types) 198 { 199 error("Failed to parse map types in map_types.json (check for valid JSON data)"); 200 return types; 201 } 202 203 translateObjectKeys(data, ["Title"]); 204 for (let i = 0; i < data.Types.length; ++i) 205 { 206 types.names.push(data.Types[i].Name); 207 types.titles.push(data.Types[i].Title); 208 209 if (data.Types[i].Default) 210 types.default = i; 211 } 212 213 return types; 214 } 215 216 // Returns the translation of the given map type. Requires g_MapTypes to be present. 217 function translateMapType(mapType) 218 { 219 let index = g_MapTypes.names.indexOf(mapType) 220 if (index > -1) 221 return g_MapTypes.titles[index]; 222 else 223 return translateWithContext("map type", "Unknown"); 224 } 225 226 // ==================================================================== 227 228 function initAIDescription() 229 { 230 let AIs = Engine.GetAIs(); 231 translateObjectKeys(AIs, ["name", "description"]); 232 // Sort AIs by displayed name 233 AIs.sort((a, b) => a.data.name < b.data.name ? -1 : b.data.name < a.data.name ? +1 : 0); 234 return AIs; 235 } 236 237 // Returns the translation of the given AI name (like "petra"). Requires g_AIs to be present. 238 function translateAIName(aiName) 239 { 240 let AI_description = g_AIs.filter(ai => ai.id == aiName); 241 if (AI_description.length > 0) 242 return translate(AI_description[0].data.name); 243 else 244 return translateWithContext("AI name", "Unknown"); 245 } 246 247 // ==================================================================== 248 249 function initAIDifficulties() 250 { 251 let difficulties = { 252 "names":[], 253 "titles":[], 254 "default": 0 255 }; 256 257 let data = Engine.ReadJSONFile("simulation/ai/difficulties.json"); 258 if (!data || !data.Difficulties) 259 { 260 error("Failed to parse AI difficulties (check difficulties.json for valid data)"); 261 return ["Default"]; 262 } 263 264 translateObjectKeys(data, ["Title"]); 265 for (let i = 0; i < data.Difficulties.length; ++i) 266 { 267 difficulties.names.push(data.Difficulties[i].Name); 268 difficulties.titles.push(data.Difficulties[i].Title); 269 270 if (data.Difficulties[i].Default) 271 difficulties.default = i; 272 } 273 return difficulties; 274 } 275 276 // Returns the translation of the given AI difficulty. Requires g_AIDiffs to be present. 277 function translateAIDifficulty(index) 278 { 279 if (0 <= index && index < g_AIDiffs.titles.length) 280 return g_AIDiffs.titles[index]; 281 else 282 return translateWithContext("aiDiff", "Unknown"); 283 } 284 285 // ==================================================================== 286 287 // Returns the translation of the given map type. Requires g_VictoryConditions to be present. 288 function translateGameType(gameType) 289 { 290 if (g_VictoryConditions.hasOwnProperty(gameType)) 291 return g_VictoryConditions[gameType].name; 292 else 293 return translateWithContext("game type", "Unknown"); 294 } 295 134 296 // ==================================================================== 135 297 136 298 // Load game speed data 137 299 function initGameSpeeds() 138 300 { -
binaries/data/mods/public/gui/common/functions_utility_loadsave.js
23 23 /** 24 24 * Check the version compatibility between the saved game to be loaded and the engine 25 25 */ 26 26 function hasSameVersion(metadata, engineInfo) 27 27 { 28 return (metadata.version_major == engineInfo.version_major );28 return (metadata.version_major == engineInfo.version_major && metadata.hasOwnProperty("engine_version") && metadata.engine_version == engineInfo.engine_version); 29 29 } 30 30 31 31 /** 32 32 * Check the mod compatibility between the saved game to be loaded and the engine 33 33 */ -
binaries/data/mods/public/gui/gamesetup/gamesetup.js
2 2 // Constants 3 3 const DEFAULT_NETWORKED_MAP = "Acropolis 01"; 4 4 const DEFAULT_OFFLINE_MAP = "Acropolis 01"; 5 5 6 6 const VICTORY_DEFAULTIDX = 1; 7 8 7 // TODO: Move these somewhere like simulation\data\game_types.json, Atlas needs them too 9 // Translation: Type of victory condition.10 const POPULATION_CAP = ["50", "100", "150", "200", "250", "300", translate("Unlimited")];11 const POPULATION_CAP_DATA = [50, 100, 150, 200, 250, 300, 10000];12 const POPULATION_CAP_DEFAULTIDX = 5;13 8 // Translation: Amount of starting resources. 14 9 const STARTING_RESOURCES = [translateWithContext("startingResources", "Very Low"), translateWithContext("startingResources", "Low"), translateWithContext("startingResources", "Medium"), translateWithContext("startingResources", "High"), translateWithContext("startingResources", "Very High"), translateWithContext("startingResources", "Deathmatch")]; 15 10 const STARTING_RESOURCES_DATA = [100, 300, 500, 1000, 3000, 50000]; 16 11 const STARTING_RESOURCES_DEFAULTIDX = 1; 17 12 // Translation: Ceasefire. … … 55 50 var g_DefaultPlayerData = []; 56 51 var g_GameAttributes = { 57 52 settings: {} 58 53 }; 59 54 60 var g_GameSpeeds = {};61 var g_MapSizes = {};62 55 var g_GameSpeeds = []; 56 var g_MapSizes = []; 57 var g_MapTypes = []; 63 58 var g_AIs = []; 64 59 var g_PopCaps = []; 65 60 var g_ChatMessages = []; 66 61 67 62 // Data caches 68 63 var g_MapData = {}; 69 64 var g_CivData = {}; … … 116 111 117 112 // Called after the map data is loaded and cached 118 113 function initMain() 119 114 { 120 115 // Load AI list 121 g_AIs = Engine.GetAIs(); 122 123 // Sort AIs by displayed name 124 g_AIs.sort(function (a, b) { 125 return a.data.name < b.data.name ? -1 : b.data.name < a.data.name ? +1 : 0; 126 }); 116 g_AIs = initAIDescription(); 127 117 128 118 // Get default player data - remove gaia 129 119 g_DefaultPlayerData = initPlayerDefaults(); 130 120 g_DefaultPlayerData.shift(); 131 121 for (var i = 0; i < g_DefaultPlayerData.length; ++i) 132 122 g_DefaultPlayerData[i].Civ = "random"; 133 123 134 124 g_GameSpeeds = initGameSpeeds(); 135 125 g_MapSizes = initMapSizes(); 126 g_MapTypes = initMapTypes(); 127 g_PopCaps = initPopulationCapacity(); 136 128 137 129 // Init civs 138 130 initCivNameList(); 139 131 140 132 // Init map types 141 133 var mapTypes = Engine.GetGUIObjectByName("mapTypeSelection"); 142 mapTypes.list = [translateWithContext("map", "Skirmish"), translateWithContext("map", "Random"), translate("Scenario")];143 mapTypes.list_data = ["skirmish","random","scenario"];134 mapTypes.list = g_MapTypes.titles; 135 mapTypes.list_data = g_MapTypes.names; 144 136 145 137 // Setup map filters - will appear in order they are added 146 138 addFilter("default", translate("Default"), function(settings) { return settings && (settings.Keywords === undefined || !keywordTestOR(settings.Keywords, ["naval", "demo", "hidden"])); }); 147 139 addFilter("naval", translate("Naval Maps"), function(settings) { return settings && settings.Keywords !== undefined && keywordTestAND(settings.Keywords, ["naval"]); }); 148 140 addFilter("demo", translate("Demo Maps"), function(settings) { return settings && settings.Keywords !== undefined && keywordTestAND(settings.Keywords, ["demo"]); }); … … 186 178 updateGameAttributes(); 187 179 } 188 180 gameSpeed.selected = g_GameSpeeds["default"]; 189 181 190 182 var populationCaps = Engine.GetGUIObjectByName("populationCap"); 191 populationCaps.list = POPULATION_CAP;192 populationCaps.list_data = POPULATION_CAP_DATA;193 populationCaps.selected = POPULATION_CAP_DEFAULTIDX;183 populationCaps.list = g_PopCaps.titles; 184 populationCaps.list_data = g_PopCaps.population; 185 populationCaps.selected = g_PopCaps.default; 194 186 populationCaps.onSelectionChange = function() { 195 187 if (this.selected != -1) 196 g_GameAttributes.settings.PopulationCap = POPULATION_CAP_DATA[this.selected];188 g_GameAttributes.settings.PopulationCap = g_PopCaps.population[this.selected]; 197 189 198 190 updateGameAttributes(); 199 191 } 200 192 201 193 var startingResourcesL = Engine.GetGUIObjectByName("startingResources"); … … 487 479 } 488 480 Engine.SwitchGuiPage("page_loading.xml", { 489 481 "attribs": g_GameAttributes, 490 482 "isNetworked" : g_IsNetworked, 491 483 "playerAssignments": g_PlayerAssignments, 492 "isController": g_IsController 484 "isController": g_IsController, 485 "g_IsReplay" : false 493 486 }); 494 487 break; 495 488 496 489 case "chat": 497 490 addChatMessage({ "type": "message", "guid": message.guid, "text": message.text }); … … 1222 1215 } 1223 1216 else 1224 1217 enableRatingText.caption = "Unknown"; 1225 1218 gameSpeedText.caption = g_GameSpeeds.names[speedIdx]; 1226 1219 gameSpeedBox.selected = speedIdx; 1227 populationCap.selected = (mapSettings.PopulationCap !== undefined && POPULATION_CAP_DATA.indexOf(mapSettings.PopulationCap) != -1 ? POPULATION_CAP_DATA.indexOf(mapSettings.PopulationCap) : POPULATION_CAP_DEFAULTIDX);1228 populationCapText.caption = POPULATION_CAP[populationCap.selected];1220 populationCap.selected = (mapSettings.PopulationCap !== undefined && g_PopCaps.population.indexOf(mapSettings.PopulationCap) != -1 ? g_PopCaps.population.indexOf(mapSettings.PopulationCap) : g_PopCaps.default); 1221 populationCapText.caption = g_PopCaps.titles[populationCap.selected]; 1229 1222 startingResources.selected = (mapSettings.StartingResources !== undefined && STARTING_RESOURCES_DATA.indexOf(mapSettings.StartingResources) != -1 ? STARTING_RESOURCES_DATA.indexOf(mapSettings.StartingResources) : STARTING_RESOURCES_DEFAULTIDX); 1230 1223 startingResourcesText.caption = STARTING_RESOURCES[startingResources.selected]; 1231 1224 ceasefire.selected = (mapSettings.Ceasefire !== undefined && CEASEFIRE_DATA.indexOf(mapSettings.Ceasefire) != -1 ? CEASEFIRE_DATA.indexOf(mapSettings.Ceasefire) : CEASEFIRE_DEFAULTIDX); 1232 1225 ceasefireText.caption = CEASEFIRE[ceasefire.selected]; 1233 1226 -
binaries/data/mods/public/gui/gamesetup/gamesetup.xml
1 1 <?xml version="1.0" encoding="utf-8"?> 2 2 3 3 <objects> 4 4 5 <script file="gui/aiconfig/aiconfig.js"/> 5 6 <script file="gui/common/network.js"/> 6 7 <script file="gui/common/functions_civinfo.js"/> 7 8 <script file="gui/common/functions_global_object.js"/> 8 9 <script file="gui/common/functions_utility.js"/> 9 10 <script file="gui/gamesetup/gamesetup.js"/> -
binaries/data/mods/public/gui/lobby/lobby.js
7 7 var g_PlayerListOrder = 1; 8 8 var g_specialKey = Math.random(); 9 9 // This object looks like {"name":[numMessagesSinceReset, lastReset, timeBlocked]} when in use. 10 10 var g_spamMonitor = {}; 11 11 var g_timestamp = Engine.ConfigDB_GetValue("user", "lobby.chattimestamp") == "true"; 12 var g_mapSizes = {}; 13 const g_mapTypesText = [translateWithContext("map", "Skirmish"), translateWithContext("map", "Random"), translate("Scenario")]; 14 const g_mapTypes = ["skirmish", "random", "scenario"]; 12 var g_MapSizes = {}; 13 var g_MapTypes = {}; 15 14 var g_userRating = ""; // Rating of user, defaults to Unrated 16 15 var g_modPrefix = "@"; 17 16 var g_joined = false; 18 17 // Block spammers for 30 seconds. 19 18 var SPAM_BLOCK_LENGTH = 30; … … 26 25 initMusic(); 27 26 global.music.setState(global.music.states.MENU); 28 27 29 28 g_Name = Engine.LobbyGetNick(); 30 29 31 g_ mapSizes = initMapSizes();32 g_ mapSizes.shortNames.splice(0, 0, translateWithContext("map size", "Any"));33 g_ mapSizes.tiles.splice(0, 0, "");30 g_MapSizes = initMapSizes(); 31 g_MapSizes.shortNames.splice(0, 0, translateWithContext("map size", "Any")); 32 g_MapSizes.tiles.splice(0, 0, ""); 34 33 35 34 var mapSizeFilter = Engine.GetGUIObjectByName("mapSizeFilter"); 36 mapSizeFilter.list = g_ mapSizes.shortNames;37 mapSizeFilter.list_data = g_ mapSizes.tiles;35 mapSizeFilter.list = g_MapSizes.shortNames; 36 mapSizeFilter.list_data = g_MapSizes.tiles; 38 37 39 38 var playersNumberFilter = Engine.GetGUIObjectByName("playersNumberFilter"); 40 39 playersNumberFilter.list = [translateWithContext("player number", "Any"),2,3,4,5,6,7,8]; 41 40 playersNumberFilter.list_data = ["",2,3,4,5,6,7,8]; 42 41 42 g_MapTypes = initMapTypes(); 43 g_MapTypes.titles.splice(0, 0, translateWithContext("map type", "Any")); 44 g_MapTypes.names.splice(0, 0, ""); 45 43 46 var mapTypeFilter = Engine.GetGUIObjectByName("mapTypeFilter"); 44 mapTypeFilter.list = [translateWithContext("map", "Any")].concat(g_mapTypesText);45 mapTypeFilter.list_data = [""].concat(g_mapTypes);47 mapTypeFilter.list = g_MapTypes.titles; 48 mapTypeFilter.list_data = g_MapTypes.names; 46 49 47 50 Engine.LobbySetPlayerPresence("available"); 48 51 Engine.SendGetGameList(); 49 52 Engine.SendGetBoardList(); 50 53 Engine.SendGetRatingList(); … … 468 471 else 469 472 name = '[color="255 0 0"]' + name + '[/color]'; 470 473 list_name.push(name); 471 474 list_ip.push(g.ip); 472 475 list_mapName.push(translate(g.niceMapName)); 473 list_mapSize.push(translatedMapSize(g.mapSize)); 474 let idx = g_mapTypes.indexOf(g.mapType); 475 list_mapType.push(idx != -1 ? g_mapTypesText[idx] : ""); 476 list_mapSize.push(translateMapSize(g.mapSize)); 477 list_mapType.push(translateMapType(g.mapType)); 476 478 list_nPlayers.push(g.nbp + "/" +g.tnbp); 477 479 list.push(name); 478 480 list_data.push(c); 479 481 } 480 482 c++; … … 546 548 // Push this player's name and status onto the list 547 549 return [formattedName, formattedStatus, formattedRating]; 548 550 } 549 551 550 552 /** 551 * Given a map size, returns that map size translated into the current552 * language.553 */554 function translatedMapSize(mapSize)555 {556 if (+mapSize !== +mapSize) // NaN557 return translate(mapSize);558 else559 return g_mapSizes.shortNames[g_mapSizes.tiles.indexOf(+mapSize)];560 }561 562 /**563 553 * Populate the game info area with information on the current game selection. 564 554 */ 565 555 function updateGameSelection() 566 556 { 567 557 var selected = Engine.GetGUIObjectByName("gamesBox").selected; … … 595 585 596 586 // Display the map name, number of players, the names of the players, the map size and the map type. 597 587 Engine.GetGUIObjectByName("sgMapName").caption = translate(g_GameList[g].niceMapName); 598 588 Engine.GetGUIObjectByName("sgNbPlayers").caption = g_GameList[g].nbp + "/" + g_GameList[g].tnbp; 599 589 Engine.GetGUIObjectByName("sgPlayersNames").caption = g_GameList[g].players; 600 Engine.GetGUIObjectByName("sgMapSize").caption = translatedMapSize(g_GameList[g].mapSize); 601 let idx = g_mapTypes.indexOf(g_GameList[g].mapType); 602 Engine.GetGUIObjectByName("sgMapType").caption = idx != -1 ? g_mapTypesText[idx] : ""; 590 Engine.GetGUIObjectByName("sgMapSize").caption = translateMapSize(g_GameList[g].mapSize); 591 Engine.GetGUIObjectByName("sgMapType").caption = translateMapType(g_GameList[g].mapType); 603 592 604 593 // Display map description if it exists, otherwise display a placeholder. 605 594 if (mapData && mapData.settings.Description) 606 595 var mapDescription = translate(mapData.settings.Description); 607 596 else -
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/replaymenu.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/replaymenu.js
1 var g_Replays = []; // All replays found 2 var g_ReplaysFiltered = []; // List of replays after filtering 3 var g_Playernames = []; // All playernames of all replays, used for autocomplete 4 var g_MapNames = []; // All map names of all replays 5 var g_ReplayListSortBy = "name"; // Used by sorting 6 var g_ReplayListOrder = -1; // Used by sorting 7 var g_AIs = initAIDescription(); // translated AI names and description 8 var g_AIDiffs = initAIDifficulties(); // translated AI difficulties 9 const g_VictoryConditions = {}; // translated game types 10 const g_MapSizes = initMapSizes(); // translated map sizes 11 const g_MapTypes = initMapTypes(); // translated map types 12 const g_PopCaps = initPopulationCapacity(); // translated population capacities 13 const g_CivData = loadCivData(); // names and description of civs 14 const g_EngineInfo = Engine.GetEngineInfo(); // current engine version 15 const g_DefaultPlayerData = initPlayerDefaults(); 16 const g_DurationFilterMin = [ 0, 0, 15, 30, 45, 60, 90, 120]; 17 const g_DurationFilterMax = [-1, 15, 30, 45, 60, 90, 120, -1]; 18 19 function init() 20 { 21 loadReplays(); 22 updateReplayList(); 23 } 24 25 function loadReplays() 26 { 27 g_Playernames = []; 28 g_Replays = Engine.GetReplays(); 29 for (let replay of g_Replays) 30 { 31 // Use time saved in file, otherwise file mod date 32 replay.timestamp = replay.attribs.hasOwnProperty("timestamp") ? replay.attribs.timestamp : replay.filemod_timestamp; 33 34 // Skirmish maps don't have that attribute 35 if (!replay.attribs.settings.hasOwnProperty("Size")) 36 replay.attribs.settings.Size = -1; 37 38 // Extract all map names 39 if (g_MapNames.indexOf(replay.attribs.settings.Name) == -1) 40 g_MapNames.push(replay.attribs.settings.Name); 41 42 // Extract all playernames, used by autocomplete feature 43 for (let playerData of replay.attribs.settings.PlayerData) 44 if (playerData) 45 { 46 let name = playerData.Name; 47 if (g_Playernames.indexOf(name) == -1) 48 g_Playernames.push({"name": name}); 49 } 50 } 51 g_MapNames.sort(); 52 } 53 54 function updateReplayList() 55 { 56 let replaySelection = Engine.GetGUIObjectByName("replaySelection"); 57 58 // Filters depend on the replay list 59 initFilters(); 60 61 if (g_Replays.length == 0) 62 replaySelection.selected = -1; 63 64 // Sort replays 65 g_Replays.sort((a,b) => 66 { 67 let cmpA, cmpB; 68 switch (g_ReplayListSortBy) 69 { 70 case 'name': 71 cmpA = a.timestamp; 72 cmpB = b.timestamp; 73 break; 74 case 'duration': 75 cmpA = a.duration; 76 cmpB = b.duration; 77 break; 78 case 'players': 79 cmpA = a.attribs.settings.PlayerData.length; 80 cmpB = b.attribs.settings.PlayerData.length; 81 break; 82 case 'mapName': 83 cmpA = getReplayMapName(a); 84 cmpB = getReplayMapName(b); 85 break; 86 case 'mapSize': 87 cmpA = a.attribs.settings.Size; 88 cmpB = b.attribs.settings.Size; 89 break; 90 case 'popCapacity': 91 cmpA = a.attribs.settings.PopulationCap; 92 cmpB = b.attribs.settings.PopulationCap; 93 break; 94 } 95 96 // Sort by selected column 97 if (cmpA < cmpB) 98 return -g_ReplayListOrder; 99 else if (cmpA > cmpB) 100 return g_ReplayListOrder; 101 102 // Sort by date/time as a tiebreaker and keep most recent replays at the top 103 if (a.timestamp < b.timestamp) 104 return 1; 105 else if (a.timestamp > b.timestamp) 106 return -1; 107 108 return 0; 109 }); 110 111 // Filter replays and create GUI list data 112 let replayListLabels = []; 113 let replayListDirectories = []; 114 let list_name = []; 115 let list_players = []; 116 let list_mapName = []; 117 let list_mapSize = []; 118 let list_popCapacity = []; 119 let list_duration = []; 120 g_ReplaysFiltered = []; 121 for (let replay of g_Replays) 122 { 123 if (filterReplay(replay)) 124 continue; 125 126 g_ReplaysFiltered.push(replay); 127 replayListLabels.push(replay.directory); 128 replayListDirectories.push(replay.directory); 129 130 list_name.push(Greyout(replay, getReplayDateTime(replay))); 131 list_players.push(Greyout(replay, getReplayPlayernames(replay))); 132 list_mapName.push(Greyout(replay, getReplayMapName(replay))); 133 list_mapSize.push(Greyout(replay, translateMapSize(replay.attribs.settings.Size))); 134 list_popCapacity.push(Greyout(replay, translatePopulationCapacity(replay.attribs.settings.PopulationCap))); 135 list_duration.push(Greyout(replay, getReplayDuration(replay))); 136 } 137 138 // TODO: enhancement: remember last selection, like #3244 139 if (replaySelection.selected >= list_name.length) 140 replaySelection.selected = -1; 141 142 // Update list 143 replaySelection.list_name = list_name; 144 replaySelection.list_players = list_players; 145 replaySelection.list_mapName = list_mapName; 146 replaySelection.list_mapSize = list_mapSize; 147 replaySelection.list_popCapacity = list_popCapacity; 148 replaySelection.list_duration = list_duration; 149 150 replaySelection.list = replayListLabels; 151 replaySelection.list_data = replayListDirectories; 152 } 153 154 function updateReplayListOrderSelection() 155 { 156 g_ReplayListSortBy = Engine.GetGUIObjectByName("replaySelection").selected_column; 157 g_ReplayListOrder = Engine.GetGUIObjectByName("replaySelection").selected_column_order; 158 applyFilters(); 159 } 160 161 function Greyout(replay, text) 162 { 163 if (isReplayCompatible(replay)) 164 return text; 165 else 166 return '[color="96 96 96"]' + text + '[/color]'; 167 } 168 169 function getReplayDateTime(replay) 170 { 171 // TODO: enhancement: use local time instead of UTC 172 return Engine.FormatMillisecondsIntoDateString(replay.timestamp * 1000, translate("yyyy-MM-dd HH:mm")) 173 } 174 175 function getReplayPlayernames(replay) 176 { 177 // Extract playernames 178 let playernames = []; 179 for (let playerData of replay.attribs.settings.PlayerData) 180 if (playerData) 181 // TODO: enhancement: colorize playernames like in lobby using colorPlayerName 182 // #3205 moves the function to common/color.js 183 playernames.push(escapeText(playerData.Name)); 184 185 return playernames.join(", "); 186 } 187 188 function getReplayMapName(replay) 189 { 190 return escapeText(translate(replay.attribs.settings.Name)); 191 } 192 193 function getReplayMonth(replay) 194 { 195 return Engine.FormatMillisecondsIntoDateString(replay.timestamp * 1000, translate("yyyy-MM")); 196 } 197 198 function getReplayDuration(replay) 199 { 200 return timeToString(replay.duration * 1000); 201 } 202 203 function getReplayTeamText(replay) 204 { 205 let metadata = Engine.GetReplayMetadata(replay.directory); 206 let spoiler = Engine.GetGUIObjectByName("showSpoiler").checked; 207 208 // Extract players and civs by team 209 let teams = {}; 210 let teamCount = 0; 211 let i = 1; 212 for (let playerData of replay.attribs.settings.PlayerData) 213 { 214 if (!playerData) 215 continue; 216 217 // Get player info 218 let playerCiv = translate(g_CivData[playerData.Civ].Name); 219 // TODO: enhancement: too few contrast, see #3205 220 let playerColor = playerData.Color ? playerData.Color : g_DefaultPlayerData[i].Color; 221 let playerName = '[color="' + playerColor.r + " " + playerColor.g + " " + playerColor.b + '"]' + escapeText(playerData.Name) + "[/color]"; 222 let showDefeated = spoiler && metadata.playerStates && metadata.playerStates[i].state == "defeated"; 223 let isAI = playerData.AI; 224 let AIname = translateAIName(playerData.AI); 225 let AIdiff = translateAIDifficulty(playerData.AIDiff); 226 227 // Create player text 228 let playerDetails; 229 if (!isAI && !showDefeated) 230 playerDetails = sprintf(translateWithContext("replay player description", "%(playerName)s (%(civilization)s)"), 231 { "playerName": playerName, "civilization": playerCiv}); 232 else if (isAI && !showDefeated) 233 playerDetails = sprintf(translateWithContext("replay player description", "%(playerName)s (%(civilization)s, %(AIdifficulty)s %(AIname)s)"), 234 { "playerName": playerName, "civilization": playerCiv, "AIdifficulty": AIdiff, "AIname": AIname }); 235 else if (!isAI && showDefeated) 236 playerDetails = sprintf(translateWithContext("replay player description", "%(playerName)s (%(civilization)s, defeated)"), 237 { "playerName": playerName, "civilization": playerCiv}); 238 else 239 playerDetails = sprintf(translateWithContext("replay player description", "%(playerName)s (%(civilization)s, %(AIdifficulty)s %(AIname)s, defeated)"), 240 { "playerName": playerName, "civilization": playerCiv, "AIdifficulty": AIdiff, "AIname": AIname }); 241 242 // Add player text to playerlist 243 let team = playerData.Team; 244 if (teams.hasOwnProperty(team)) 245 teams[team].push(playerDetails); 246 else 247 { 248 teams[team] = [playerDetails]; 249 teamCount++; 250 } 251 i++; 252 } 253 254 // Create player info text sorted by team 255 let teamString = ""; 256 for (let team in teams) 257 { 258 if (teamCount > 1) 259 { 260 teamString += '[font="sans-bold-14"]'; 261 if (team == -1) 262 teamString += translate("No Team"); 263 else 264 teamString += sprintf(translate("Team %(teamNumber)s"), {"teamNumber": parseInt(team) + 1}); 265 teamString += "[/font]:\n"; 266 } 267 teamString += teams[team].join("\n") + "\n"; 268 } 269 270 return teamString; 271 } 272 273 function isReplayCompatible(replay) 274 { 275 if (!isReplayCompatible_EngineVersion(replay)) 276 return false; 277 278 // Now check mods 279 let gameMods = replay.attribs.mods ? replay.attribs.mods : []; 280 281 if (gameMods.length != g_EngineInfo.mods.length) 282 return false; 283 284 for (let i = 0; i < gameMods.length; ++i) 285 if (gameMods[i] != g_EngineInfo.mods[i]) 286 return false; 287 288 return true; 289 } 290 291 function isReplayCompatible_EngineVersion(replay) 292 { 293 return replay.attribs.hasOwnProperty("engine_version") 294 && replay.attribs.engine_version == g_EngineInfo.engine_version; 295 } 296 297 function initFilters() 298 { 299 initDateFilter(); 300 initMapNameFilter(); 301 initMapSizeFilter(); 302 initPopCapFilter(); 303 initDurationFilter(); 304 } 305 306 function initDateFilter() 307 { 308 // Extract every month from the list of replays 309 let months = g_Replays.map(replay => getReplayMonth(replay)); 310 months = months.filter((month, index) => months.indexOf(month) == index).sort(); 311 months.unshift(translateWithContext("DateTime", "Any")); 312 313 let dateTimeFilter = Engine.GetGUIObjectByName("dateTimeFilter"); 314 dateTimeFilter.list = months; 315 dateTimeFilter.list_data = months; 316 317 if (dateTimeFilter.selected == -1 || dateTimeFilter.selected >= dateTimeFilter.list.length) 318 dateTimeFilter.selected = 0; 319 } 320 321 function initMapSizeFilter() 322 { 323 let mapSizeFilter = Engine.GetGUIObjectByName("mapSizeFilter"); 324 mapSizeFilter.list = [translateWithContext("map size", "Any")].concat(g_MapSizes.names); 325 mapSizeFilter.list_data = [-1].concat(g_MapSizes.tiles); 326 327 if (mapSizeFilter.selected == -1 || mapSizeFilter.selected >= mapSizeFilter.list.length) 328 mapSizeFilter.selected = 0; 329 } 330 331 function initMapNameFilter() 332 { 333 let mapNameFilter = Engine.GetGUIObjectByName("mapNameFilter"); 334 mapNameFilter.list = [translateWithContext("map name", "Any")].concat(g_MapNames); 335 mapNameFilter.list_data = [""].concat(g_MapNames.map(mapName => translate(mapName))); 336 337 if (mapNameFilter.selected == -1 || mapNameFilter.selected >= mapNameFilter.list.length) 338 mapNameFilter.selected = 0; 339 } 340 341 function initPopCapFilter() 342 { 343 let populationFilter = Engine.GetGUIObjectByName("populationFilter"); 344 populationFilter.list = [translateWithContext("population capacity", "Any")].concat(g_PopCaps.titles); 345 populationFilter.list_data = [""].concat(g_PopCaps.population); 346 347 if (populationFilter.selected == -1 || populationFilter.selected >= populationFilter.list.length) 348 populationFilter.selected = 0; 349 } 350 351 function initDurationFilter() 352 { 353 let data = [-1]; 354 let labels = ["Any"]; 355 356 for (let i in g_DurationFilterMin) 357 { 358 let label; 359 if (i == 0) 360 continue; 361 else if (i == 1) 362 label = sprintf(translateWithContext("duration filter", "< %(min)s min"), { "min": g_DurationFilterMax[i] }); 363 else if (i == g_DurationFilterMin.length -1) 364 label = sprintf(translateWithContext("duration filter", "> %(min)s min"), { "min": g_DurationFilterMin[i] }); 365 else 366 label = sprintf(translateWithContext("duration filter", "%(min1)s - %(min2)s min"), { "min1": g_DurationFilterMin[i], "min2": g_DurationFilterMax[i] }); 367 368 data.push(i); 369 labels.push(label); 370 } 371 372 let durationFilter = Engine.GetGUIObjectByName("durationFilter"); 373 durationFilter.list = labels; 374 durationFilter.list_data = data; 375 376 if (durationFilter.selected == -1 || durationFilter.selected >= data.length) 377 durationFilter.selected = 0; 378 } 379 380 function applyFilters() 381 { 382 updateReplayList(); 383 updateReplaySelection(); 384 } 385 386 // Returns true if the replay should not be listed. 387 function filterReplay(replay) 388 { 389 let dateTimeFilter = Engine.GetGUIObjectByName("dateTimeFilter"); 390 let playersFilter = Engine.GetGUIObjectByName("playersFilter"); 391 let mapNameFilter = Engine.GetGUIObjectByName("mapNameFilter"); 392 let mapSizeFilter = Engine.GetGUIObjectByName("mapSizeFilter"); 393 let populationFilter = Engine.GetGUIObjectByName("populationFilter"); 394 let durationFilter = Engine.GetGUIObjectByName("durationFilter"); 395 let compabilityFilter = Engine.GetGUIObjectByName("compabilityFilter"); 396 397 // Check for compability first (most likely to filter) 398 if (compabilityFilter.checked && !isReplayCompatible(replay)) 399 return true; 400 401 // Filter date/time (select a month) 402 if (dateTimeFilter.selected > 0) 403 { 404 let selectedMonth = dateTimeFilter.list_data[dateTimeFilter.selected]; 405 if (getReplayMonth(replay) != selectedMonth) 406 return true; 407 } 408 409 // Filter selected players 410 let playerText = playersFilter.caption; 411 if (playerText.length) 412 { 413 // Player and botnames can contain spaces 414 // We just check if all words of all players are somewhere in the playerlist 415 playerText = playerText.toLowerCase().split(" "); 416 let replayPlayers = replay.attribs.settings.PlayerData.map(player => player ? player.Name.toLowerCase() : "").join(" "); 417 for (let word of playerText) 418 if (replayPlayers.indexOf(word) == -1) 419 return true; 420 } 421 422 // Filter map name 423 if (mapNameFilter.selected > 0 && getReplayMapName(replay) != mapNameFilter.list_data[mapNameFilter.selected]) 424 return true; 425 426 // Filter map size 427 if (mapSizeFilter.selected > 0 && replay.attribs.settings.Size != mapSizeFilter.list_data[mapSizeFilter.selected]) 428 return true; 429 430 // Filter population capacity 431 if (populationFilter.selected > 0 && replay.attribs.settings.PopulationCap != populationFilter.list_data[populationFilter.selected]) 432 return true; 433 434 // Filter game duration 435 if (durationFilter.selected > 0) 436 { 437 let minutes = replay.duration / 60; 438 let min = g_DurationFilterMin[durationFilter.selected]; 439 let max = g_DurationFilterMax[durationFilter.selected]; 440 441 if (minutes < min || (max > -1 && minutes > max)) 442 return true; 443 } 444 445 return false; 446 } 447 448 function updateReplaySelection() 449 { 450 let selected = Engine.GetGUIObjectByName("replaySelection").selected; 451 let replaySelected = selected > -1; 452 453 Engine.GetGUIObjectByName("replayInfo").hidden = !replaySelected; 454 Engine.GetGUIObjectByName("replayInfoEmpty").hidden = replaySelected; 455 Engine.GetGUIObjectByName("startReplayButton").enabled = replaySelected; 456 Engine.GetGUIObjectByName("deleteReplayButton").enabled = replaySelected; 457 Engine.GetGUIObjectByName("summaryButton").enabled = replaySelected; 458 459 if (!replaySelected) 460 return; 461 462 let replay = g_ReplaysFiltered[selected]; 463 464 // Load map data 465 let mapData = {}; 466 if (replay.attribs.settings.mapType == "random" && replay.attribs.map == "random") 467 mapData = {"settings": {"Description": translate("A randomly selected map.")}}; 468 else if (replay.attribs.settings.mapType == "random" && Engine.FileExists(replay.attribs.map + ".json")) 469 mapData = Engine.ReadJSONFile(replay.attribs.map + ".json"); 470 else if (Engine.FileExists(replay.attribs.map + ".xml")) 471 mapData = Engine.LoadMapSettings(replay.attribs.map + ".xml"); 472 else 473 // Warn the player if we can't find the map. 474 warn(sprintf("Map '%(mapName)s' not found locally.", { mapName: replay.attribs.map })); 475 476 // Load map description 477 let mapDescription; 478 if (mapData && mapData.settings.Description) 479 mapDescription = translate(mapData.settings.Description); 480 else 481 mapDescription = translate("Sorry, no description available."); 482 483 // Load map preview image 484 let mapPreview; 485 if (mapData && mapData.settings.Preview) 486 mapPreview = mapData.settings.Preview; 487 else 488 mapPreview = "nopreview.png"; 489 490 // Update GUI, compare with gamesetup.js for translations 491 Engine.GetGUIObjectByName("sgMapName").caption = translate(replay.attribs.settings.Name); 492 Engine.GetGUIObjectByName("sgMapSize").caption = translateMapSize(replay.attribs.settings.Size); 493 Engine.GetGUIObjectByName("sgMapType").caption = translateMapType(replay.attribs.settings.mapType); 494 Engine.GetGUIObjectByName("sgVictory").caption = translateGameType(replay.attribs.settings.GameType); 495 Engine.GetGUIObjectByName("sgNbPlayers").caption = replay.attribs.settings.PlayerData.length; 496 Engine.GetGUIObjectByName("sgPlayersNames").caption = getReplayTeamText(replay); 497 Engine.GetGUIObjectByName("sgMapDescription").caption = mapDescription; 498 Engine.GetGUIObjectByName("sgMapPreview").sprite = "cropped:(0.7812,0.5859)session/icons/mappreview/" + mapPreview; 499 } 500 501 function startReplay() 502 { 503 let selected = Engine.GetGUIObjectByName("replaySelection").selected; 504 if (selected == -1) 505 return; 506 507 let replay = g_ReplaysFiltered[selected]; 508 if (isReplayCompatible(replay)) 509 reallyStartVisualReplay(replay.directory); 510 else 511 displayReplayCompatibilityError(replay); 512 } 513 514 function reallyStartVisualReplay(replayDirectory) 515 { 516 // TODO: enhancement: remember filter settings and selected item. restore settings when returning from the replay. 517 Engine.StartVisualReplay(replayDirectory); 518 Engine.SwitchGuiPage("page_loading.xml", { 519 "attribs": Engine.GetReplayAttributes(replayDirectory), 520 "isNetworked" : false, 521 "playerAssignments": {}, 522 "savedGUIData": "", 523 "isReplay" : true 524 }); 525 } 526 527 function displayReplayCompatibilityError(replay) 528 { 529 let errMsg; 530 if (isReplayCompatible_EngineVersion(replay)) 531 { 532 let gameMods = replay.attribs.mods ? replay.attribs.mods : []; 533 errMsg = translate("The replay is compatible with your version of the game, but you don't have the same mods as in the replay.") + "\n"; 534 errMsg += sprintf(translate("Mods enabled in the replay: %(mods)s"), { "mods": gameMods.join(",") }) + "\n"; 535 errMsg += sprintf(translate("Currently enabled mods: %(mods)s"), { "mods": g_EngineInfo.mods.join(",") }); 536 } 537 else 538 errMsg = translate("This replay is not compatible with your version of the game!"); 539 540 messageBox(500, 200, errMsg, translate("REPLAY INCOMPATIBLE"), 0, [translate("Ok")], [null]); 541 } 542 543 function showReplaySummary() 544 { 545 let selected = Engine.GetGUIObjectByName("replaySelection").selected; 546 if (selected == -1) 547 return; 548 549 let replay = g_ReplaysFiltered[selected]; 550 let summary = Engine.GetReplayMetadata(replay.directory); 551 if (Object.keys(summary).length == 0) 552 messageBox(500, 200, translateWithContext("replay", "No summary data found!"), translate("ERROR"), 0, [translate("Ok")], [null]); 553 else 554 { 555 summary.isReplay = true; 556 summary.gameResult = translateWithContext("replay", "Scores at the end of the game."); 557 Engine.SwitchGuiPage("page_summary.xml", summary); 558 } 559 } 560 561 function deleteReplay() 562 { 563 let selected = Engine.GetGUIObjectByName("replaySelection").selected; 564 if (selected == -1) 565 return; 566 567 // Ask for confirmation 568 let replay = g_ReplaysFiltered[selected]; 569 let btCaptions = [translate("Yes"), translate("No")]; 570 let btCode = [function(){ reallyDeleteReplay(replay.directory); }, null]; 571 messageBox(500, 200, translate("Are you sure to delete this replay permanently?") + "\n" + replay.file, translate("DELETE"), 0, btCaptions, btCode); 572 } 573 574 function deleteReplayWithoutConfirmation() 575 { 576 let selected = Engine.GetGUIObjectByName("replaySelection").selected; 577 if (selected > -1) 578 reallyDeleteReplay(g_ReplaysFiltered[selected].directory); 579 } 580 581 function reallyDeleteReplay(replayDirectory) 582 { 583 if (!Engine.DeleteReplay(replayDirectory)) 584 error(sprintf("Could not delete replay '%(id)s'", { id: replayDirectory })); 585 586 // Refresh replay list 587 init(); 588 } 589 No newline at end of file -
binaries/data/mods/public/gui/replaymenu/replaymenu.xml
1 <?xml version="1.0" encoding="utf-8"?> 2 3 <objects> 4 5 <script file="gui/aiconfig/aiconfig.js"/> 6 <script file="gui/common/functions_civinfo.js" /> 7 <script file="gui/common/functions_global_object.js" /> 8 <script file="gui/common/functions_utility.js" /> 9 <script file="gui/replaymenu/replaymenu.js" /> 10 <script directory="gui/gamesetup/victory_conditions/"/> 11 12 <object type="image" style="ModernWindow" size="0 0 100% 100%" name="replayWindow"> 13 14 <object style="ModernLabelText" type="text" size="50%-128 4 50%+128 36"> 15 <translatableAttribute id="caption">Replay Games</translatableAttribute> 16 </object> 17 18 <!-- Left Panel: Filters & Replay List --> 19 <object name="leftPanel" size="3% 5% 100%-255 100%-80"> 20 21 <!-- Filters --> 22 <object name="filterPanel" size="0 0 100% 24"> 23 <object name="dateTimeFilter" 24 type="dropdown" 25 style="ModernDropDown" 26 size="5 0 12%-10 100%" 27 font="sans-bold-13"> 28 <action on="SelectionChange">applyFilters();</action> 29 </object> 30 <object name="playersFilter" 31 type="input" 32 style="ModernInput" 33 size="12%-5 0 56%-10 100%" 34 font="sans-bold-13"> 35 <action on="Press">applyFilters();</action> 36 <action on="Tab">autoCompleteNick("playersFilter", g_Playernames);</action> 37 </object> 38 <object name="mapNameFilter" 39 type="dropdown" 40 style="ModernDropDown" 41 size="56%-5 0 70%-10 100%" 42 font="sans-bold-13"> 43 <action on="SelectionChange">applyFilters();</action> 44 </object> 45 <object name="mapSizeFilter" 46 type="dropdown" 47 style="ModernDropDown" 48 size="70%-5 0 80%-10 100%" 49 font="sans-bold-13"> 50 <action on="SelectionChange">applyFilters();</action> 51 </object> 52 <object name="populationFilter" 53 type="dropdown" 54 style="ModernDropDown" 55 size="80%-5 0 90%-10 100%" 56 font="sans-bold-13"> 57 <action on="SelectionChange">applyFilters();</action> 58 </object> 59 <object name="durationFilter" 60 type="dropdown" 61 style="ModernDropDown" 62 size="90%-5 0 100%-10 100%" 63 font="sans-bold-13"> 64 <action on="SelectionChange">applyFilters();</action> 65 </object> 66 </object> 67 68 <!-- Replay list --> 69 <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"> 70 <action on="SelectionChange">updateReplaySelection();</action> 71 <action on="SelectionColumnChange">updateReplayListOrderSelection();</action> 72 <!-- Columns --> 73 <!-- 0ad crashes if there is no column with the id "name"! --> 74 <def id="name" color="128 128 172" width="12%"> 75 <translatableAttribute id="heading" context="replay">Date / Time</translatableAttribute> 76 </def> 77 <def id="players" color="192 192 192" width="44%"> 78 <translatableAttribute id="heading" context="replay">Players</translatableAttribute> 79 </def> 80 <def id="mapName" color="192 192 192" width="14%"> 81 <translatableAttribute id="heading" context="replay">Map Name</translatableAttribute> 82 </def> 83 <def id="mapSize" color="192 192 192" width="10%"> 84 <translatableAttribute id="heading" context="replay">Size</translatableAttribute> 85 </def> 86 <def id="popCapacity" color="192 192 192" width="10%"> 87 <translatableAttribute id="heading" context="replay">Population</translatableAttribute> 88 </def> 89 <def id="duration" color="192 192 192" width="10%"> 90 <translatableAttribute id="heading" context="replay">Duration</translatableAttribute> 91 </def> 92 </object> 93 </object> 94 95 <!-- Right Panel: Compability Filter & Replay Details --> 96 <object name="rightPanel" size="100%-250 30 100%-20 100%-20" > 97 98 <object name="compabilityFilter" 99 type="checkbox" 100 checked="true" 101 style="ModernTickBox" 102 size="0 4 20 100%" 103 font="sans-bold-13"> 104 <action on="Press">applyFilters();</action> 105 </object> 106 <object type="text" size="20 2 100% 100%" text_align="left" textcolor="white"> 107 <translatableAttribute id="caption">Filter compatible replays</translatableAttribute> 108 </object> 109 110 <object name="replayInfoEmpty" size="0 30 100% 100%-60" type="image" sprite="ModernDarkBoxGold" hidden="false"> 111 <object name="logo" size="50%-110 40 50%+110 140" type="image" sprite="logo"/> 112 <object name="subjectBox" type="image" sprite="ModernDarkBoxWhite" size="3% 180 97% 99%"> 113 <object name="subject" size="5 5 100%-5 100%-5" type="text" style="ModernText" text_align="center"/> 114 </object> 115 </object> 116 117 <object name="replayInfo" size="0 30 100% 100%-60" type="image" sprite="ModernDarkBoxGold" hidden="true"> 118 119 <!-- Map Name --> 120 <object name="sgMapName" size="0 5 100% 20" type="text" style="ModernLabelText"/> 121 122 <!-- Map Preview --> 123 <object name="sgMapPreview" size="5 25 100%-5 190" type="image" sprite=""/> 124 125 <object size="5 194 100%-5 195" type="image" sprite="ModernWhiteLine" z="25"/> 126 127 <!-- Map Type --> 128 <object size="5 195 50% 225" type="image" sprite="ModernItemBackShadeLeft"> 129 <object size="0 0 100%-10 100%" type="text" style="ModernLabelText" text_align="right"> 130 <translatableAttribute id="caption">Map Type:</translatableAttribute> 131 </object> 132 </object> 133 <object size="50% 195 100%-5 225" type="image" sprite="ModernItemBackShadeRight"> 134 <object name="sgMapType" size="0 0 100% 100%" type="text" style="ModernLabelText" text_align="left"/> 135 </object> 136 137 <object size="5 224 100%-5 225" type="image" sprite="ModernWhiteLine" z="25"/> 138 139 <!-- Map Size --> 140 <object size="5 225 50% 255" type="image" sprite="ModernItemBackShadeLeft"> 141 <object size="0 0 100%-10 100%" type="text" style="ModernLabelText" text_align="right"> 142 <translatableAttribute id="caption">Map Size:</translatableAttribute> 143 </object> 144 </object> 145 <object size="50% 225 100%-5 255" type="image" sprite="ModernItemBackShadeRight"> 146 <object name="sgMapSize" size="0 0 100% 100%" type="text" style="ModernLabelText" text_align="left"/> 147 </object> 148 149 <object size="5 254 100%-5 255" type="image" sprite="ModernWhiteLine" z="25"/> 150 151 <!-- Victory Condition --> 152 <object size="5 255 50% 285" type="image" sprite="ModernItemBackShadeLeft"> 153 <object size="0 0 100%-10 100%" type="text" style="ModernLabelText" text_align="right"> 154 <translatableAttribute id="caption">Victory:</translatableAttribute> 155 </object> 156 </object> 157 <object size="50% 255 100%-5 285" type="image" sprite="ModernItemBackShadeRight"> 158 <object name="sgVictory" size="0 0 100% 100%" type="text" style="ModernLabelText" text_align="left"/> 159 </object> 160 161 <object size="5 284 100%-5 285" type="image" sprite="ModernWhiteLine" z="25"/> 162 163 <!-- Map Description --> 164 <object type="image" sprite="ModernDarkBoxWhite" size="3% 290 97% 70%"> 165 <object name="sgMapDescription" size="0 0 100% 100%" type="text" style="ModernText" font="sans-12"/> 166 </object> 167 168 <object type="image" sprite="ModernDarkBoxWhite" size="3% 70%+5 97% 100%-30"> 169 <!-- Number of Players --> 170 <object size="0% 3% 57% 12%" type="text" style="ModernRightLabelText"> 171 <translatableAttribute id="caption">Players:</translatableAttribute> 172 </object> 173 <object name="sgNbPlayers" size="58% 3% 70% 12%" type="text" style="ModernLeftLabelText" text_align="left"/> 174 175 <!-- Player Names --> 176 <object name="sgPlayersNames" size="0 15% 100% 100%" type="text" style="MapPlayerList"/> 177 </object> 178 179 <object name="showSpoiler" 180 type="checkbox" 181 checked="false" 182 style="ModernTickBox" 183 size="10 100%-27 30 100%" 184 font="sans-bold-13"> 185 <action on="Press">updateReplaySelection();</action> 186 </object> 187 <object type="text" size="30 100%-28 100% 100%" text_align="left" textcolor="white"> 188 <translatableAttribute id="caption">Spoiler</translatableAttribute> 189 </object> 190 191 </object> 192 </object> 193 194 <!-- Bottom Panel: Buttons. --> 195 <object name="bottomPanel" size="25 100%-55 100%-5 100%-25" > 196 197 <object type="button" style="StoneButton" size="25 0 17%+25 100%"> 198 <translatableAttribute id="caption">Main Menu</translatableAttribute> 199 <action on="Press"> 200 Engine.SwitchGuiPage("page_pregame.xml"); 201 </action> 202 </object> 203 204 <object name="deleteReplayButton" type="button" style="StoneButton" size="20%+25 0 37%+25 100%" hotkey="session.savedgames.delete"> 205 <translatableAttribute id="caption">Delete</translatableAttribute> 206 <action on="Press"> 207 if (this.enabled) 208 { 209 if (Engine.HotkeyIsPressed("session.savedgames.noConfirmation")) 210 deleteReplayWithoutConfirmation(); 211 else 212 deleteReplay(); 213 } 214 </action> 215 </object> 216 217 <object name="summaryButton" type="button" style="StoneButton" size="65%-50 0 82%-50 100%"> 218 <translatableAttribute id="caption">Summary</translatableAttribute> 219 <action on="Press"> 220 showReplaySummary(); 221 </action> 222 </object> 223 224 <object name="startReplayButton" type="button" style="StoneButton" size="83%-25 0 100%-25 100%"> 225 <translatableAttribute id="caption">Start Replay</translatableAttribute> 226 <action on="Press"> 227 startReplay(); 228 </action> 229 </object> 230 231 </object> 232 </object> 233 </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/menu.js
649 649 Engine.SetPaused(false); 650 650 } 651 651 652 652 function togglePause() 653 653 { 654 if ( g_IsObserver)654 if (!Engine.GetGUIObjectByName("pauseButton").enabled) 655 655 return; 656 656 657 657 closeMenu(); 658 658 closeOpenDialogs(); 659 659 -
binaries/data/mods/public/gui/session/session.js
5 5 var g_IsController; 6 6 // Match ID for tracking 7 7 var g_MatchID; 8 8 // Is this user an observer? 9 9 var g_IsObserver = false; 10 var g_IsReplay = false; 10 11 11 12 // Cache the basic player data (name, civ, color) 12 13 var g_Players = []; 13 14 // Cache the useful civ data 14 15 var g_CivData = {}; … … 149 150 { 150 151 if (initData) 151 152 { 152 153 g_IsNetworked = initData.isNetworked; // Set network mode 153 154 g_IsController = initData.isController; // Set controller mode 155 g_IsReplay = initData.isReplay; // Set replay mode 154 156 g_PlayerAssignments = initData.playerAssignments; 155 157 g_MatchID = initData.attribs.matchID; 156 158 157 159 // Cache the player data 158 160 // (This may be updated at runtime by handleNetMessage) … … 310 312 * @param willRejoin If player is going to be rejoining a networked game. 311 313 */ 312 314 function leaveGame(willRejoin) 313 315 { 314 316 var extendedSimState = Engine.GuiInterfaceCall("GetExtendedSimulationState"); 315 var mapSettings = Engine.GetMapSettings();316 317 var gameResult; 317 318 318 319 if (g_IsObserver) 319 320 { 320 321 // Observers don't win/lose. … … 341 342 resignGame(true); 342 343 } 343 344 } 344 345 } 345 346 347 let summary = { 348 "timeElapsed" : extendedSimState.timeElapsed, 349 "playerStates": extendedSimState.players, 350 "players": g_Players, 351 "mapSettings": Engine.GetMapSettings(), 352 } 353 354 if (!g_IsReplay) 355 Engine.SaveReplayMetadata(JSON.stringify(summary)); 356 346 357 stopAmbient(); 347 358 Engine.EndGame(); 348 359 349 360 if (g_IsController && Engine.HasXmppClient()) 350 361 Engine.SendUnregisterGame(); 351 362 352 Engine.SwitchGuiPage("page_summary.xml", { 353 "gameResult" : gameResult, 354 "timeElapsed" : extendedSimState.timeElapsed, 355 "playerStates": extendedSimState.players, 356 "players": g_Players, 357 "mapSettings": mapSettings 358 }); 363 summary.gameResult = gameResult; 364 summary.isReplay = g_IsReplay; 365 Engine.SwitchGuiPage("page_summary.xml", summary); 359 366 } 360 367 361 368 // Return some data that we'll use when hotloading this file after changes 362 369 function getHotloadData() 363 370 { -
binaries/data/mods/public/gui/summary/summary.js
28 28 const INCOME_COLOR = '[color="201 255 200"]'; 29 29 const OUTCOME_COLOR = '[color="255 213 213"]'; 30 30 31 31 const DEFAULT_DECIMAL = "0.00"; 32 32 const INFINITE_SYMBOL = "\u221E"; 33 var g_IsReplay = false; 33 34 // Load data 34 35 var g_CivData = loadCivData(); 35 36 var g_Teams = []; 36 37 // TODO set g_MaxPlayers as playerCounters.length 37 38 var g_MaxPlayers = 0; … … 132 133 133 134 function init(data) 134 135 { 135 136 updateObjectPlayerPosition(); 136 137 g_GameData = data; 138 g_IsReplay = data.isReplay; 137 139 138 140 // Map 139 141 var mapDisplayType = translate("Scenario"); 140 142 141 143 Engine.GetGUIObjectByName("timeElapsed").caption = sprintf(translate("Game time elapsed: %(time)s"), { time: timeToString(data.timeElapsed) }); -
binaries/data/mods/public/gui/summary/summary.xml
155 155 </object> 156 156 157 157 <object type="button" style="ModernButtonRed" size="100%-160 100%-48 100%-20 100%-20"> 158 158 <translatableAttribute id="caption">Continue</translatableAttribute> 159 159 <action on="Press"><![CDATA[ 160 if ( !Engine.HasXmppClient())160 if (Engine.HasXmppClient()) 161 161 { 162 Engine.SwitchGuiPage("page_pregame.xml"); 162 Engine.LobbySetPlayerPresence("available"); 163 Engine.SwitchGuiPage("page_lobby.xml"); 164 } 165 else if (g_IsReplay) 166 { 167 Engine.SwitchGuiPage("page_replaymenu.xml"); 163 168 } 164 169 else 165 170 { 166 Engine.LobbySetPlayerPresence("available"); 167 Engine.SwitchGuiPage("page_lobby.xml"); 171 Engine.SwitchGuiPage("page_pregame.xml"); 168 172 } 169 173 ]]> 170 174 </action> 171 175 </object> 172 176 </object> -
binaries/data/mods/public/l10n/messages.json
463 463 } 464 464 }, 465 465 { 466 466 "extractor": "json", 467 467 "filemasks": [ 468 "simulation/data/map_types.json" 469 ], 470 "options": { 471 "keywords": [ 472 "Title" 473 ] 474 } 475 }, 476 { 477 "extractor": "json", 478 "filemasks": [ 468 479 "simulation/data/map_sizes.json" 469 480 ], 470 481 "options": { 471 482 "keywords": [ 472 483 "Name", … … 475 486 } 476 487 }, 477 488 { 478 489 "extractor": "json", 479 490 "filemasks": [ 491 "simulation/data/population_capacity.json" 492 ], 493 "options": { 494 "keywords": [ 495 "Title" 496 ] 497 } 498 }, 499 { 500 "extractor": "json", 501 "filemasks": [ 480 502 "simulation/ai/**.json" 481 503 ], 482 504 "options": { 483 505 "keywords": [ 484 506 "name", 485 507 "description" 486 508 ] 487 509 } 488 } 510 }, 511 { 512 "extractor": "json", 513 "filemasks": [ 514 "simulation/ai/difficulties.json" 515 ], 516 "options": { 517 "keywords": [ 518 "Title" 519 ] 520 } 521 }, 489 522 ] 490 523 }, 491 524 { 492 525 "output": "public-maps.pot", 493 526 "inputRoot": "..", -
binaries/data/mods/public/simulation/ai/difficulties.json
1 { 2 "Difficulties": 3 [ 4 { 5 "Name": "sandbox", 6 "Title": "Sandbox" 7 }, 8 { 9 "Name": "very easy", 10 "Title": "Very Easy" 11 }, 12 { 13 "Name": "easy", 14 "Title": "Easy" 15 }, 16 { 17 "Name": "medium", 18 "Title": "Medium", 19 "Default": true 20 }, 21 { 22 "Name": "hard", 23 "Title": "Hard" 24 }, 25 { 26 "Name": "very hard", 27 "Title": "Very Hard" 28 } 29 ] 30 } 31 No newline at end of file -
binaries/data/mods/public/simulation/data/game_speeds.json
31 31 "Speed": 1.5 32 32 }, 33 33 { 34 34 "Name": "Insane (2x)", 35 35 "Speed": 2.0 36 }, 37 { 38 "Name": "Fast forward (5x)", 39 "Speed": 5.0 40 }, 41 { 42 "Name": "Faster forward (10x)", 43 "Speed": 10.0 44 }, 45 { 46 "Name": "Very fast forward (20x)", 47 "Speed": 20.0 36 48 } 37 49 ] 38 50 } -
binaries/data/mods/public/simulation/data/map_types.json
1 { 2 "Types": 3 [ 4 { 5 "Name": "skirmish", 6 "Title": "Skirmish", 7 "Default": true 8 }, 9 { 10 "Name": "random", 11 "Title": "Random" 12 }, 13 { 14 "Name": "scenario", 15 "Title": "Scenario" 16 } 17 ] 18 } 19 No newline at end of file -
binaries/data/mods/public/simulation/data/population_capacity.json
1 { 2 "PopulationCapacity": 3 [ 4 { 5 "Population": 50, 6 "Title": "50" 7 }, 8 { 9 "Population": 100, 10 "Title": "100" 11 }, 12 { 13 "Population": 150, 14 "Title": "150" 15 }, 16 { 17 "Population": 200, 18 "Title": "200" 19 }, 20 { 21 "Population": 250, 22 "Title": "250" 23 }, 24 { 25 "Population": 300, 26 "Title": "300", 27 "Default": true 28 }, 29 { 30 "Population": 10000, 31 "Title": "Unlimited" 32 } 33 ] 34 } -
source/gui/COList.cpp
406 406 407 407 DrawText(def, color, leftTopCorner + CPos(0, 4), bz + 0.1f, rect_head); 408 408 xpos += width; 409 409 } 410 410 411 const unsigned int m_ObjectsCount = m_ObjectsDefs.size(); 411 412 for (int i=0; i<(int)pList->m_Items.size(); ++i) 412 413 { 413 414 if (m_ItemsYPositions[i+1] - scroll < 0 || 414 415 m_ItemsYPositions[i] - scroll > rect.GetHeight()) 415 416 continue; 416 417 418 float rowHeight = m_ItemsYPositions[i+1] - m_ItemsYPositions[i]; 419 417 420 // Clipping area (we'll have to substract the scrollbar) 418 421 CRect cliparea = GetListRect(); 419 422 420 423 if (scrollbar) 421 424 { … … 427 430 cliparea.left < GetScrollBar(0).GetOuterRect().right) 428 431 cliparea.left = GetScrollBar(0).GetOuterRect().right; 429 432 } 430 433 431 434 xpos = 0; 432 for (unsigned int def=0; def < m_ObjectsDefs.size(); ++def)435 for (unsigned int def=0; def < m_ObjectsCount; ++def) 433 436 { 434 DrawText(m_ObjectsDefs.size() * (i+/*Heading*/1) + def, m_ObjectsDefs[def].m_TextColor, rect.TopLeft() + CPos(xpos, -scroll + m_ItemsYPositions[i]), bz+0.1f, cliparea); 437 // Determine text position and width 438 CPos textPos = rect.TopLeft() + CPos(xpos, -scroll + m_ItemsYPositions[i]); 439 435 440 // Check if it's a decimal value, and if so, assume relative positioning. 441 float width = m_ObjectsDefs[def].m_Width;; 436 442 if (m_ObjectsDefs[def].m_Width < 1 && m_ObjectsDefs[def].m_Width > 0) 437 xpos += m_ObjectsDefs[def].m_Width * m_TotalAvalibleColumnWidth; 438 else 439 xpos += m_ObjectsDefs[def].m_Width; 443 width *= m_TotalAvalibleColumnWidth; 444 445 // Clip text to the column (to prevent drawing text into the neighboring column) 446 CRect cliparea2 = cliparea; 447 cliparea2.right = std::min(cliparea2.right, textPos.x + width); 448 cliparea2.bottom = std::min(cliparea2.bottom, textPos.y + rowHeight); 449 450 DrawText(m_ObjectsCount * (i+/*Heading*/1) + def, m_ObjectsDefs[def].m_TextColor, textPos, bz+0.1f, cliparea2); 451 xpos += width; 440 452 } 441 453 } 442 454 } 443 455 } -
source/gui/scripting/ScriptFunctions.cpp
48 48 #include "ps/Globals.h" // g_frequencyFilter 49 49 #include "ps/Hotkey.h" 50 50 #include "ps/ProfileViewer.h" 51 51 #include "ps/Pyrogenesis.h" 52 52 #include "ps/SavedGame.h" 53 #include "ps/VisualReplay.h" 53 54 #include "ps/UserReport.h" 54 55 #include "ps/World.h" 55 56 #include "ps/scripting/JSInterface_ConfigDB.h" 56 57 #include "ps/scripting/JSInterface_Console.h" 57 58 #include "ps/scripting/JSInterface_Mod.h" … … 281 282 shared_ptr<ScriptInterface::StructuredClone> GUIMetadataClone = pCxPrivate->pScriptInterface->WriteStructuredClone(GUIMetadata); 282 283 if (SavedGames::SavePrefix(prefix, description, *g_Game->GetSimulation2(), GUIMetadataClone, g_Game->GetPlayerID()) < 0) 283 284 LOGERROR("Failed to save game"); 284 285 } 285 286 287 void StartVisualReplay(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), CStrW directoryName) 288 { 289 ENSURE(!g_NetServer); 290 ENSURE(!g_NetClient); 291 ENSURE(!g_Game); 292 293 CStrW replayFilePath = OsPath(VisualReplay::GetDirectoryName() / directoryName / L"commands.txt").string(); 294 CStr replayFile( replayFilePath.begin(), replayFilePath.end() ); 295 296 if (FileExists(OsPath(replayFile))) 297 { 298 g_Game = new CGame(false, false); 299 g_Game->StartReplay(replayFile); 300 } 301 } 302 303 JS::Value GetReplays(ScriptInterface::CxPrivate* pCxPrivate) 304 { 305 return VisualReplay::GetReplays(*(pCxPrivate->pScriptInterface)); 306 } 307 308 bool DeleteReplay(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), CStrW replayFile) 309 { 310 return VisualReplay::DeleteReplay(replayFile); 311 } 312 313 JS::Value GetReplayAttributes(ScriptInterface::CxPrivate* pCxPrivate, CStrW directoryName) 314 { 315 return VisualReplay::GetReplayAttributes(pCxPrivate, directoryName); 316 } 317 318 JS::Value GetReplayMetadata(ScriptInterface::CxPrivate* pCxPrivate, CStrW directoryName) 319 { 320 return VisualReplay::GetReplayMetadata(pCxPrivate, directoryName); 321 } 322 323 void SaveReplayMetadata(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), CStrW data) 324 { 325 VisualReplay::SaveReplayMetadata(data); 326 } 327 286 328 void SetNetworkGameAttributes(ScriptInterface::CxPrivate* pCxPrivate, JS::HandleValue attribs1) 287 329 { 288 330 ENSURE(g_NetServer); 289 331 //TODO: This is a workaround because we need to pass a MutableHandle to a JSAPI functions somewhere 290 332 // (with no obvious reason). … … 974 1016 scriptInterface.RegisterFunction<void, std::wstring, std::wstring, JS::HandleValue, &SaveGame>("SaveGame"); 975 1017 scriptInterface.RegisterFunction<void, std::wstring, std::wstring, JS::HandleValue, &SaveGamePrefix>("SaveGamePrefix"); 976 1018 scriptInterface.RegisterFunction<void, &QuickSave>("QuickSave"); 977 1019 scriptInterface.RegisterFunction<void, &QuickLoad>("QuickLoad"); 978 1020 1021 // Visual replay 1022 scriptInterface.RegisterFunction<JS::Value, &GetReplays>("GetReplays"); 1023 scriptInterface.RegisterFunction<bool, CStrW, &DeleteReplay>("DeleteReplay"); 1024 scriptInterface.RegisterFunction<void, CStrW, &StartVisualReplay>("StartVisualReplay"); 1025 scriptInterface.RegisterFunction<JS::Value, CStrW, &GetReplayAttributes>("GetReplayAttributes"); 1026 scriptInterface.RegisterFunction<JS::Value, CStrW, &GetReplayMetadata>("GetReplayMetadata"); 1027 scriptInterface.RegisterFunction<void, CStrW, &SaveReplayMetadata>("SaveReplayMetadata"); 1028 979 1029 // Misc functions 980 1030 scriptInterface.RegisterFunction<std::wstring, std::wstring, &SetCursor>("SetCursor"); 981 1031 scriptInterface.RegisterFunction<int, &GetPlayerID>("GetPlayerID"); 982 1032 scriptInterface.RegisterFunction<void, int, &SetPlayerID>("SetPlayerID"); 983 1033 scriptInterface.RegisterFunction<void, std::string, &OpenURL>("OpenURL"); -
source/lobby/XmppClient.cpp
22 22 #include "glooxwrapper/glooxwrapper.h" 23 23 #include "i18n/L10n.h" 24 24 #include "lib/utf8.h" 25 25 #include "ps/CLogger.h" 26 26 #include "ps/ConfigDB.h" 27 #include "ps/EngineVersion.h" 27 28 #include "scriptinterface/ScriptInterface.h" 28 29 29 30 //debug 30 31 #if 1 31 32 #define DbgXMPP(x) … … 95 96 const int mechs = gloox::SaslMechAll ^ gloox::SaslMechPlain; 96 97 m_client->setSASLMechanisms(mechs); 97 98 98 99 m_client->registerConnectionListener(this); 99 100 m_client->setPresence(gloox::Presence::Available, -1); 100 m_client->disco()->setVersion("Pyrogenesis", "0.0.19");101 m_client->disco()->setVersion("Pyrogenesis", utf8_from_wstring(engine_version)); 101 102 m_client->disco()->setIdentity("client", "bot"); 102 103 m_client->setCompression(false); 103 104 104 105 m_client->registerStanzaExtension(new GameListQuery()); 105 106 m_client->registerIqHandler(this, EXTGAMELISTQUERY); -
source/network/NetTurnManager.cpp
533 533 ENSURE(m_Simulation2.ComputeStateHash(hash, quickHash)); 534 534 hash = Hexify(hash); 535 535 536 536 if (hash != expectedHash) 537 537 DisplayOOSError(turn, hash, expectedHash, true); 538 539 if (turn == m_FinalReplayTurn) 540 g_GUI->SendEventToAll("ReplayFinished"); 538 541 } 539 542 540 543 void CNetReplayTurnManager::DoTurn(u32 turn) 541 544 { 542 545 // Save turn length -
source/ps/EngineVersion.h
1 const wchar_t engine_version[] = L"0.0.19"; -
source/ps/Game.cpp
134 134 if (type == "turn") 135 135 { 136 136 u32 turn = 0; 137 137 u32 turnLength = 0; 138 138 *m_ReplayStream >> turn >> turnLength; 139 if (turn == 0 && turn != currentTurn) 140 LOGERROR("Looks like you tried to replay a commands.txt file of a rejoined client, which is not supported yet.\n"); 139 141 ENSURE(turn == currentTurn); 140 142 replayTurnMgr->StoreReplayTurnLength(currentTurn, turnLength); 141 143 } 142 144 else if (type == "cmd") 143 145 { … … 154 156 std::string replayHash; 155 157 *m_ReplayStream >> replayHash; 156 158 replayTurnMgr->StoreReplayHash(currentTurn, replayHash, quick); 157 159 } 158 160 else if (type == "end") 159 {160 161 currentTurn++; 161 }162 162 else 163 {164 163 CancelLoad(L"Failed to load replay data (unrecognized content)"); 165 }166 164 } 165 m_ReplayStream->close(); 167 166 m_FinalReplayTurn = currentTurn; 168 167 replayTurnMgr->StoreFinalReplayTurn(currentTurn); 169 168 return 0; 170 169 } 171 170 … … 389 388 { 390 389 { 391 390 PROFILE3("gui sim update"); 392 391 g_GUI->SendEventToAll("SimulationUpdate"); 393 392 } 394 if (m_IsReplay && m_TurnManager->GetCurrentTurn() == m_FinalReplayTurn - 1)395 g_GUI->SendEventToAll("ReplayFinished");396 393 397 394 GetView()->GetLOSTexture().MakeDirty(); 398 395 } 399 396 400 397 if (CRenderer::IsInitialised()) 401 398 g_Renderer.GetTimeManager().Update(deltaSimTime); 402 399 } 403 400 404 401 if (doInterpolate) -
source/ps/Game.h
176 176 bool m_IsSavedGame; // true if loading a saved game; false for a new game 177 177 178 178 int LoadReplayData(); 179 179 std::string m_ReplayPath; 180 180 bool m_IsReplay; 181 std::i stream* m_ReplayStream;181 std::ifstream* m_ReplayStream; 182 182 u32 m_FinalReplayTurn; 183 183 }; 184 184 185 185 extern CGame *g_Game; 186 186 -
source/ps/GameSetup/GameSetup.cpp
879 879 srand(time(NULL)); // NOTE: this rand should *not* be used for simulation! 880 880 } 881 881 882 882 bool Autostart(const CmdLineArgs& args); 883 883 884 // Returns true if and only if the user has intended to replay a file 885 bool VisualReplay(const std::string replayFile); 884 bool StartVisualReplay(const std::string replayFile); 886 885 887 886 bool Init(const CmdLineArgs& args, int flags) 888 887 { 889 888 h_mgr_init(); 890 889 … … 1078 1077 if (VfsDirectoryExists(L"maps/")) 1079 1078 CXeromyces::AddValidator(g_VFS, "map", "maps/scenario.rng"); 1080 1079 1081 1080 try 1082 1081 { 1083 if (! VisualReplay(args.Get("replay-visual")) && !Autostart(args))1082 if (!StartVisualReplay(args.Get("replay-visual")) && !Autostart(args)) 1084 1083 { 1085 1084 const bool setup_gui = ((flags & INIT_NO_GUI) == 0); 1086 1085 // We only want to display the splash screen at startup 1087 1086 shared_ptr<ScriptInterface> scriptInterface = g_GUI->GetScriptInterface(); 1088 1087 JSContext* cx = scriptInterface->GetContext(); … … 1475 1474 } 1476 1475 1477 1476 return true; 1478 1477 } 1479 1478 1480 bool VisualReplay(const std::string replayFile)1479 bool StartVisualReplay(const std::string replayFile) 1481 1480 { 1482 1481 if (!FileExists(OsPath(replayFile))) 1483 1482 return false; 1484 1483 1485 1484 g_Game = new CGame(false, false); -
source/ps/Pyrogenesis.cpp
1 /* Copyright (C) 20 09Wildfire Games.1 /* Copyright (C) 2015 Wildfire Games. 2 2 * This file is part of 0 A.D. 3 3 * 4 4 * 0 A.D. is free software: you can redistribute it and/or modify 5 5 * it under the terms of the GNU General Public License as published by 6 6 * the Free Software Foundation, either version 2 of the License, or … … 19 19 20 20 #include <cstdio> 21 21 22 22 #include "Pyrogenesis.h" 23 23 24 #include "ps/EngineVersion.h" 24 25 #include "lib/sysdep/sysdep.h" 25 26 #include "lib/svn_revision.h" 26 27 27 28 static const wchar_t* translate_no_mem = L"(no mem)"; 28 29 … … 71 72 72 73 // for user convenience, bundle all logs into this file: 73 74 void psBundleLogs(FILE* f) 74 75 { 75 76 fwprintf(f, L"SVN Revision: %ls\n\n", svn_revision); 77 fwprintf(f, L"Engine Version: %ls\n\n", engine_version); 76 78 77 79 fwprintf(f, L"System info:\n\n"); 78 80 OsPath path1 = psLogDir()/"system_info.txt"; 79 81 AppendAsciiFile(f, path1); 80 82 fwprintf(f, L"\n\n====================================\n\n"); -
source/ps/Replay.cpp
22 22 #include "graphics/TerrainTextureManager.h" 23 23 #include "lib/timer.h" 24 24 #include "lib/file/file_system.h" 25 25 #include "lib/res/h_mgr.h" 26 26 #include "lib/tex/tex.h" 27 #include "ps/EngineVersion.h" 27 28 #include "ps/Game.h" 28 29 #include "ps/Loader.h" 29 30 #include "ps/Profile.h" 31 #include "ps/Mod.h" 30 32 #include "ps/ProfileViewer.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" … … 50 52 str << std::setfill('0') << std::setw(2) << (int)(unsigned char)s[i]; 51 53 return str.str(); 52 54 } 53 55 54 56 CReplayLogger::CReplayLogger(ScriptInterface& scriptInterface) : 55 m_ScriptInterface(scriptInterface) 57 m_ScriptInterface(scriptInterface), m_Stream(NULL) 58 { 59 } 60 61 CReplayLogger::~CReplayLogger() 62 { 63 delete m_Stream; 64 } 65 66 void CReplayLogger::StartGame(JS::MutableHandleValue attribs) 56 67 { 57 68 // Construct the directory name based on the PID, to be relatively unique. 58 69 // Append "-1", "-2" etc if we run multiple matches in a single session, 59 70 // to avoid accidentally overwriting earlier logs. 60 71 … … 63 74 64 75 static int run = -1; 65 76 if (++run) 66 77 name << "-" << run; 67 78 68 OsPath path = psLogDir() / L"sim_log" / name.str() / L"commands.txt";69 CreateDirectories(path.Parent(), 0700);70 m_S tream = new std::ofstream(OsString(path).c_str(), std::ofstream::out | std::ofstream::trunc);71 } 72 73 CReplayLogger::~CReplayLogger() 74 { 75 delete m_Stream;76 } 77 78 void CReplayLogger::StartGame(JS::MutableHandleValue attribs) 79 { 79 // Add timestamp and engine version to game attributes 80 m_ScriptInterface.SetProperty(attribs, "timestamp", std::time(0)); 81 m_ScriptInterface.SetProperty(attribs, "engine_version", utf8_from_wstring(engine_version)); 82 m_ScriptInterface.SetProperty(attribs, "mods", g_modsLoaded); 83 84 // Remember the directory, so that we can save additional files to it 85 m_Directory = psLogDir() / L"sim_log" / name.str(); 86 OsPath filepath = m_Directory / L"commands.txt"; 87 88 // Open the file when starting the game to prevent empty files 89 CreateDirectories(m_Directory, 0700); 90 m_Stream = new std::ofstream(OsString(filepath).c_str(), std::ofstream::out | std::ofstream::trunc); 80 91 *m_Stream << "start " << m_ScriptInterface.StringifyJSON(attribs, false) << "\n"; 81 92 } 82 93 83 94 void CReplayLogger::Turn(u32 n, u32 turnLength, std::vector<SimulationCommand>& commands) 84 95 { … … 100 111 *m_Stream << "hash-quick " << Hexify(hash) << "\n"; 101 112 else 102 113 *m_Stream << "hash " << Hexify(hash) << "\n"; 103 114 } 104 115 116 const OsPath CReplayLogger::GetReplayDirectory() 117 { 118 return m_Directory; 119 } 105 120 //////////////////////////////////////////////////////////////// 106 121 107 122 CReplayPlayer::CReplayPlayer() : 108 123 m_Stream(NULL) 109 124 { … … 157 172 JSContext* cx = g_Game->GetSimulation2()->GetScriptInterface().GetContext(); 158 173 JSAutoRequest rq(cx); 159 174 std::string type; 160 175 while ((*m_Stream >> type).good()) 161 176 { 162 // if (turn >= 1400) break;163 164 177 if (type == "start") 165 178 { 166 179 std::string line; 167 180 std::getline(*m_Stream, line); 168 181 JS::RootedValue attribs(cx); … … 198 211 std::string replayHash; 199 212 *m_Stream >> replayHash; 200 213 201 214 bool quick = (type == "hash-quick"); 202 215 203 // if (turn >= 1300)204 // if (turn >= 0)205 216 if (turn % 100 == 0) 206 217 { 207 218 std::string hash; 208 219 bool ok = g_Game->GetSimulation2()->ComputeStateHash(hash, quick); 209 220 ENSURE(ok); … … 224 235 225 236 g_Game->GetSimulation2()->Update(turnLength, commands); 226 237 commands.clear(); 227 238 } 228 239 229 // std::string hash;230 // bool ok = g_Game->GetSimulation2()->ComputeStateHash(hash, true);231 // ENSURE(ok);232 // debug_printf("%s\n", Hexify(hash).c_str());233 234 240 g_Profiler.Frame(); 235 241 236 // if (turn % 1000 == 0)237 // JS_GC(g_Game->GetSimulation2()->GetScriptInterface().GetContext());238 239 242 if (turn % 20 == 0) 240 243 g_ProfileViewer.SaveToFile(); 241 244 } 242 245 else 243 246 { 244 247 debug_printf("Unrecognised replay token %s\n", type.c_str()); 245 248 } 246 249 } 247 250 } 248 251 m_Stream->close(); 249 252 g_Profiler2.SaveToFile(); 250 253 251 254 std::string hash; 252 255 bool ok = g_Game->GetSimulation2()->ComputeStateHash(hash, false); 253 256 ENSURE(ok); -
source/ps/Replay.h
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 * Returns the directory that contains the replay file. 53 */ 54 virtual const OsPath GetReplayDirectory() = 0; 50 55 }; 51 56 52 57 /** 53 58 * Implementation of IReplayLogger that simply throws away all data. 54 59 */ … … 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 const OsPath GetReplayDirectory() { 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 */ … … 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 const OsPath GetReplayDirectory(); 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 */ … … 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/SavedGame.cpp
1 /* Copyright (C) 201 4Wildfire Games.1 /* Copyright (C) 2015 Wildfire Games. 2 2 * This file is part of 0 A.D. 3 3 * 4 4 * 0 A.D. is free software: you can redistribute it and/or modify 5 5 * it under the terms of the GNU General Public License as published by 6 6 * the Free Software Foundation, either version 2 of the License, or … … 24 24 #include "lib/allocators/shared_ptr.h" 25 25 #include "lib/file/archive/archive_zip.h" 26 26 #include "i18n/L10n.h" 27 27 #include "lib/utf8.h" 28 28 #include "ps/CLogger.h" 29 #include "ps/EngineVersion.h" 29 30 #include "ps/Filesystem.h" 30 31 #include "ps/Game.h" 31 32 #include "ps/Mod.h" 32 33 #include "scriptinterface/ScriptInterface.h" 33 34 #include "simulation2/Simulation2.h" … … 84 85 JS::RootedValue metadata(cx); 85 86 JS::RootedValue initAttributes(cx, simulation.GetInitAttributes()); 86 87 simulation.GetScriptInterface().Eval("({})", &metadata); 87 88 simulation.GetScriptInterface().SetProperty(metadata, "version_major", SAVED_GAME_VERSION_MAJOR); 88 89 simulation.GetScriptInterface().SetProperty(metadata, "version_minor", SAVED_GAME_VERSION_MINOR); 90 simulation.GetScriptInterface().SetProperty(metadata, "engine_version", utf8_from_wstring(engine_version)); 89 91 simulation.GetScriptInterface().SetProperty(metadata, "mods", g_modsLoaded); 90 92 simulation.GetScriptInterface().SetProperty(metadata, "time", (double)now); 91 93 simulation.GetScriptInterface().SetProperty(metadata, "player", playerID); 92 94 simulation.GetScriptInterface().SetProperty(metadata, "initAttributes", initAttributes); 93 95 … … 298 300 299 301 JS::RootedValue metainfo(cx); 300 302 scriptInterface.Eval("({})", &metainfo); 301 303 scriptInterface.SetProperty(metainfo, "version_major", SAVED_GAME_VERSION_MAJOR); 302 304 scriptInterface.SetProperty(metainfo, "version_minor", SAVED_GAME_VERSION_MINOR); 303 scriptInterface.SetProperty(metainfo, "mods" , g_modsLoaded); 305 scriptInterface.SetProperty(metainfo, "engine_version", utf8_from_wstring(engine_version)); 306 scriptInterface.SetProperty(metainfo, "mods", g_modsLoaded); 304 307 return metainfo; 305 308 } 306 309 -
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/EngineVersion.h" 27 #include "ps/Filesystem.h" 28 #include "ps/Game.h" 29 #include "ps/Replay.h" 30 #include "scriptinterface/ScriptInterface.h" 31 32 OsPath VisualReplay::GetDirectoryName() 33 { 34 return OsPath(psLogDir() / L"sim_log"); 35 } 36 37 JS::Value VisualReplay::GetReplays(ScriptInterface& scriptInterface) 38 { 39 // TODO: enhancement: make a cache file, so that only one file needs to be loaded 40 TIMER(L"GetReplays"); 41 JSContext* cx = scriptInterface.GetContext(); 42 JSAutoRequest rq(cx); 43 44 u32 i = 0; 45 DirectoryNames directories; 46 JS::RootedObject replays(cx, JS_NewArrayObject(cx, 0)); 47 if (GetDirectoryEntries(GetDirectoryName(), NULL, &directories) == INFO::OK) 48 for (OsPath& directory : directories) 49 { 50 JS::RootedValue replayData(cx, LoadReplayData(scriptInterface, directory)); 51 if (!replayData.isNull()) 52 JS_SetElement(cx, replays, i++, replayData); 53 } 54 return JS::ObjectValue(*replays); 55 } 56 57 JS::Value VisualReplay::LoadReplayData(ScriptInterface& scriptInterface, const OsPath& directory) 58 { 59 OsPath replayFile(GetDirectoryName() / directory / L"commands.txt"); 60 61 if (!FileExists(replayFile)) 62 return JSVAL_NULL; 63 64 // Get fileinfo (size, timestamp) 65 CFileInfo fileInfo; 66 GetFileInfo(replayFile, &fileInfo); 67 u64 fileTime = (u64)fileInfo.MTime() & ~1; // skip lowest bit, since zip and FAT don't preserve it (according to CCacheLoader::LooseCachePath) 68 u64 fileSize = (u64)fileInfo.Size(); 69 70 if (fileSize == 0) 71 return JSVAL_NULL; 72 73 // Open file 74 CStrW filenameW = replayFile.string(); 75 CStr filename( filenameW.begin(), filenameW.end() ); 76 std::ifstream* replayStream = new std::ifstream(filename.c_str()); 77 78 // File must begin with "start" 79 CStr type; 80 ENSURE((*replayStream >> type).good() && type == "start"); 81 82 // Parse header 83 CStr header; 84 std::getline(*replayStream, header); 85 JSContext* cx = scriptInterface.GetContext(); 86 JSAutoRequest rq(cx); 87 JS::RootedValue attribs(cx); 88 if (!scriptInterface.ParseJSON(header, &attribs)) 89 return JSVAL_NULL; 90 91 // Parse commands & turns 92 u32 currentTurn = 0; 93 u64 duration = 0; 94 while ((*replayStream >> type).good()) 95 { 96 if (type == "turn") 97 { 98 // Store turn & turn length 99 u32 turn = 0; 100 u32 turnLength = 0; 101 *replayStream >> turn >> turnLength; 102 if (turn != currentTurn) // Happens for replays of rejoined clients 103 return JSVAL_NULL; 104 105 // Compute game duration 106 duration += turnLength; 107 } 108 else if (type == "cmd") 109 { 110 // Read command 111 CStr command; 112 std::getline(*replayStream, command); 113 } 114 else if (type == "hash" || type == "hash-quick") 115 { 116 // Skip hash values 117 CStr replayHash; 118 *replayStream >> replayHash; 119 } 120 else if (type == "end") 121 currentTurn++; 122 else 123 LOGERROR("Unrecognized replay data '%s' in file %s", type, filename.c_str()); 124 } 125 replayStream->close(); 126 127 duration = duration / 1000; 128 if (duration < 5) // Don't list too short replays 129 return JSVAL_NULL; 130 131 JS::RootedValue replayData(cx); 132 scriptInterface.Eval("({})", &replayData); 133 scriptInterface.SetProperty(replayData, "file", replayFile); 134 scriptInterface.SetProperty(replayData, "directory", directory); 135 scriptInterface.SetProperty(replayData, "filemod_timestamp", fileTime); 136 scriptInterface.SetProperty(replayData, "attribs", attribs); 137 scriptInterface.SetProperty(replayData, "duration", duration); 138 return replayData; 139 } 140 141 bool VisualReplay::DeleteReplay(const CStrW& replayDirectory) 142 { 143 if (replayDirectory.empty()) 144 return false; 145 146 const OsPath directory = OsPath(GetDirectoryName() / replayDirectory); 147 return DirectoryExists(directory) && DeleteDirectory(directory) == INFO::OK; 148 } 149 150 151 JS::Value VisualReplay::GetReplayAttributes(ScriptInterface::CxPrivate* pCxPrivate, const CStrW& directoryName) 152 { 153 CStrW replayFilePath = OsPath(VisualReplay::GetDirectoryName() / directoryName / "commands.txt").string(); 154 CStr replayFile( replayFilePath.begin(), replayFilePath.end() ); 155 156 JSContext* cx = pCxPrivate->pScriptInterface->GetContext(); 157 JSAutoRequest rq(cx); 158 JS::RootedValue attribs(cx); 159 pCxPrivate->pScriptInterface->Eval("({})", &attribs); 160 161 if (!FileExists(OsPath(replayFile))) 162 return attribs; 163 164 std::ifstream* replayStream = new std::ifstream(replayFile.c_str()); 165 CStr type, line; 166 ENSURE((*replayStream >> type).good() && type == "start"); 167 168 std::getline(*replayStream, line); 169 pCxPrivate->pScriptInterface->ParseJSON(line, &attribs); 170 replayStream->close(); 171 return attribs; 172 } 173 174 // TODO: enhancement: how to save the data if the process is killed (case SDL_QUIT in main.cpp)? 175 void VisualReplay::SaveReplayMetadata(const CStrW& data) 176 { 177 // Returns directory of the currently active replay 178 // TODO: enhancement: use JS::HandleValue similar to SaveGame 179 if (!g_Game) 180 return; 181 182 OsPath directory = g_Game->GetReplayLogger().GetReplayDirectory(); 183 CreateDirectories(directory, 0700); 184 185 OsPath filepath = OsPath(directory / L"metadata.json"); 186 187 CStrW filenameW = filepath.string(); 188 CStr filename( filenameW.begin(), filenameW.end() ); 189 190 std::ofstream stream (filename.c_str(), std::ofstream::out | std::ofstream::trunc); 191 stream << utf8_from_wstring(data); 192 stream.close(); 193 } 194 195 JS::Value VisualReplay::GetReplayMetadata(ScriptInterface::CxPrivate* pCxPrivate, const CStrW& directoryName) 196 { 197 CStrW filePathW = OsPath(VisualReplay::GetDirectoryName() / directoryName / "metadata.json").string(); 198 CStr filePath( filePathW.begin(), filePathW.end() ); 199 200 JSContext* cx = pCxPrivate->pScriptInterface->GetContext(); 201 JSAutoRequest rq(cx); 202 JS::RootedValue metadata(cx); 203 204 if (FileExists(OsPath(filePathW))) 205 { 206 std::ifstream* stream = new std::ifstream(filePath.c_str()); 207 ENSURE(stream->good()); 208 209 CStr type, line; 210 std::getline(*stream, line); 211 stream->close(); 212 pCxPrivate->pScriptInterface->ParseJSON(line, &metadata); 213 } 214 else 215 pCxPrivate->pScriptInterface->Eval("({})", &metadata); 216 217 return metadata; 218 } -
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 29 namespace VisualReplay 30 { 31 32 /** 33 * Returns the path to the sim-log directory (that contains the directories with the replay files. 34 * 35 * @param scriptInterface the ScriptInterface in which to create the return data. 36 * @return OsPath the absolute file path 37 */ 38 OsPath GetDirectoryName(); 39 40 /** 41 * Get a list of replays to display in the GUI. 42 * 43 * @param scriptInterface the ScriptInterface in which to create the return data. 44 * @return array of objects containing replay data 45 */ 46 JS::Value GetReplays(ScriptInterface& scriptInterface); 47 48 /** 49 * Parses a commands.txt file and extracts metadata. 50 * Works similarly to CGame::LoadReplayData(). 51 */ 52 JS::Value LoadReplayData(ScriptInterface& scriptInterface, const OsPath& directory); 53 54 /** 55 * Permanently deletes the visual replay (including the parent directory) 56 * 57 * @param replayFile path to commands.txt, whose parent directory will be deleted 58 * @return true if deletion was successful, false on error 59 */ 60 bool DeleteReplay(const CStrW& replayFile); 61 62 /** 63 * Returns the parsed header of the replay file (commands.txt). 64 */ 65 JS::Value GetReplayAttributes(ScriptInterface::CxPrivate* pCxPrivate, const CStrW& directoryName); 66 67 /** 68 * Returns the metadata of a replay. 69 */ 70 JS::Value GetReplayMetadata(ScriptInterface::CxPrivate* pCxPrivate, const CStrW& directoryName); 71 72 /** 73 * Saves the metadata from the session to metadata.json 74 */ 75 void SaveReplayMetadata(const CStrW& data); 76 77 } 78 79 #endif -
source/simulation2/components/ICmpAIManager.cpp
47 47 m_AIs.set(JS_NewArrayObject(cx, 0)); 48 48 } 49 49 50 50 void Run() 51 51 { 52 vfs::ForEachFile(g_VFS, L"simulation/ai/", Callback, (uintptr_t)this, L" *.json", vfs::DIR_RECURSIVE);52 vfs::ForEachFile(g_VFS, L"simulation/ai/", Callback, (uintptr_t)this, L"data.json", vfs::DIR_RECURSIVE); 53 53 } 54 54 55 55 static Status Callback(const VfsPath& pathname, const CFileInfo& UNUSED(fileInfo), const uintptr_t cbData) 56 56 { 57 57 GetAIsHelper* self = (GetAIsHelper*)cbData;