Ticket #4387: campaign_V0.patch
File campaign_V0.patch, 66.7 KB (added by , 7 years ago) |
---|
-
new file inaries/data/mods/public/campaigns/example.json
diff --git a/binaries/data/mods/public/campaigns/example.json b/binaries/data/mods/public/campaigns/example.json new file mode 100644 index 0000000..f2a0d2f
- + 1 { 2 "Name" : "Example Campaign", 3 "Description" : "Lorem Ipsum and so on and so on", 4 "Interface" : "campaign/simple_campaign/page_menu.xml", 5 "Levels" : { 6 "Example_1" : { 7 "Name" : "Example 1", 8 "Map" : "scenarios/Serengeti.xml", 9 "Description" : "Whatever" 10 }, 11 "Example_2" : { 12 "Name" : "Example 2", 13 "Map" : "", 14 "Description" : "None", 15 "Requires" : "Example_1" 16 }, 17 "Example_3" : { 18 "Name" : "This one requires 1 and 2", 19 "Map" : "", 20 "Description" : "None", 21 "Requires" : "Example_1+Example_2" 22 }, 23 "Example_4" : { 24 "Name" : "This one requires 2 or 3", 25 "Map" : "", 26 "Description" : "None", 27 "Requires" : "Example_2 Example_3" 28 }, 29 "Example_5" : { 30 "Name" : "This one unavailable if 1", 31 "Map" : "", 32 "Description" : "None", 33 "Requires" : "!Example_1" 34 } 35 }, 36 "Order" : ["Example_1", "Example_2", "Example_3", "Example_4", "Example_5"], 37 "ShowUnavailable" : true 38 } 39 No newline at end of file -
new file inaries/data/mods/public/campaigns/tutorial.json
diff --git a/binaries/data/mods/public/campaigns/tutorial.json b/binaries/data/mods/public/campaigns/tutorial.json new file mode 100644 index 0000000..955dc76
- + 1 { 2 "Name" : "Tutorial", 3 "Description" : "Learn how to play 0 A.D.", 4 "Interface" : "campaign/simple_campaign/page_menu.xml", 5 "Image" : "session/icons/mappreview/Introductory Tutorial.png", 6 "Levels" : { 7 "introduction" : { 8 "Name" : "Introductory Tutorial", 9 "Map" : "scenarios/Introductory Tutorial.xml", 10 "Description" : "This is a basic tutorial to get you started playing 0 A.D.", 11 "Preview" : "session/icons/mappreview/Introductory Tutorial.png" 12 }, 13 "eco_walkthrough" : { 14 "Name" : "Economy Walkthrough", 15 "Map" : "scenarios/starting_economy_walkthrough.xml", 16 "Description" : "This map will give a rough guide for starting the game effectively. Early in the game the most important thing is to gather resources as fast as possible so you are able to build enough troops later.\u000a\u000aWarning: This is very fast at the start, be prepared to run through the initial bit several times.", 17 "Requires" : "introduction" 18 } 19 }, 20 "Order" : ["introduction", "eco_walkthrough"], 21 "ShowUnavailable" : true 22 } 23 No newline at end of file -
new file inaries/data/mods/public/gui/campaign/campaign.js
diff --git a/binaries/data/mods/public/gui/campaign/campaign.js b/binaries/data/mods/public/gui/campaign/campaign.js new file mode 100644 index 0000000..2050027
- + 1 // ID of the current campaign. This is the name of the json file (not the "human readable" name) 2 var g_CampaignID = null; 3 4 // Campaign template data from the JSON file. 5 var g_CampaignTemplate = null; 6 7 // name of the file we're saving campaign data in 8 var g_CampaignSave = null; 9 10 // Current campaign state, to be saved in/loaded from the above file 11 var g_CampaignData = null; 12 13 function startLevel(level) 14 { 15 let matchID = launchGame(g_CampaignTemplate.Levels[level], level); 16 17 g_CampaignData.currentlyPlaying = matchID; 18 19 saveCurrentCampaign(); 20 } 21 22 // this function is called by session.js at the end of a game. It should save the campaign save state immediately. 23 function campaignGameEnded(data) 24 { 25 g_CampaignID = data.ID; 26 g_CampaignTemplate = data.template; 27 g_CampaignSave = data.save; 28 g_CampaignData = data.data; 29 30 // TODO: Deal with the endGameData 31 32 // if we're not active, we either lost or won, so we're no longer playing the game. 33 if (data.endGameData.status !== "active") 34 { 35 if (g_CampaignData.currentlyPlaying) 36 g_CampaignData.currentlyPlaying = undefined; 37 } 38 if (data.endGameData.status === "won") 39 { 40 if (!g_CampaignData.completed) 41 g_CampaignData.completed = []; 42 if (g_CampaignData.completed.indexOf(data.level) == -1) 43 g_CampaignData.completed.push(data.level); 44 } 45 46 saveCurrentCampaign(); 47 } 48 49 // returns true if the level "level" is available. 50 function hasRequirements(level) 51 { 52 if (!level.Requires) 53 return true; 54 55 if (!g_CampaignData.completed) 56 return false; 57 58 // reuse class matching system, supporting "or", "+" and "!" 59 if (!MatchesClassList(g_CampaignData.completed, level.Requires)) 60 return false; 61 62 return true; 63 } 64 65 // return true if the player has completed the level with ID "level" 66 function hasCompleted(level) 67 { 68 if (!g_CampaignData.completed) 69 return false; 70 71 if (g_CampaignData.completed.indexOf(level.ID) === -1) 72 return false; 73 74 return true; 75 } 76 -
new file inaries/data/mods/public/gui/campaign/campaign_io.js
diff --git a/binaries/data/mods/public/gui/campaign/campaign_io.js b/binaries/data/mods/public/gui/campaign/campaign_io.js new file mode 100644 index 0000000..9497176
- + 1 // This file provides helper functions related to loading/saving campaign save states and campaign templates 2 // Most functions rely on loading campaign.js, but not all (such as canLoadCurrentCampaign) 3 4 /* 5 * TODOs in this file: 6 * - Provide a way to check if the campaign state changed? 7 * Other things 8 */ 9 10 function LoadAvailableCampaigns() 11 { 12 let campaigns = Engine.BuildDirEntList("campaigns/", "*.json", false); 13 14 let ret = {}; 15 16 for (let filename of campaigns) 17 { 18 let data = Engine.ReadJSONFile(filename); 19 if (!data) 20 continue; 21 22 // TODO: sanity checks, probably in their own function? 23 if (!data.Name) 24 continue; 25 26 ret[filename.replace("campaigns/","").replace(".json","")] = data; 27 } 28 29 return ret; 30 } 31 32 function canLoadCurrentCampaign(verbose = false) 33 { 34 let campaign = Engine.ConfigDB_GetValue("user", "currentcampaign"); 35 36 if (!campaign) 37 { 38 if (verbose) 39 warn("No campaign chosen, currentcampaign is not defined in user.cfg. Quitting campaign mode.") 40 return false; 41 } 42 43 if (!Engine.FileExists("campaignsaves/" + campaign + ".0adcampaign")) 44 { 45 if (verbose) 46 warn("Current campaign not found. Quitting campaign mode."); 47 return false; 48 } 49 50 // TODO: load up the file and do some checks? 51 52 return true; 53 } 54 55 function loadCurrentCampaignSave() 56 { 57 let campaign = Engine.ConfigDB_GetValue("user", "currentcampaign"); 58 59 if (!canLoadCurrentCampaign(true)) 60 return false; 61 62 if (g_CampaignSave) 63 { 64 warn("Campaign already loaded"); 65 return false; 66 } 67 g_CampaignSave = campaign; 68 69 let campaignData = loadCampaignSave(g_CampaignSave); 70 if (!campaignData) 71 { 72 warn("Campaign failed to load properly. Quitting campaign mode.") 73 return false; 74 } 75 76 g_CampaignData = campaignData; 77 g_CampaignID = g_CampaignData.campaign; 78 79 if (!loadCampaignTemplate(g_CampaignID)) 80 return false; 81 82 // actually fetch the menu 83 Engine.SwitchGuiPage(g_CampaignTemplate.Interface, {"ID" : g_CampaignID, "template" : g_CampaignTemplate, "save": g_CampaignSave, "data" : g_CampaignData}); 84 85 return true; 86 } 87 88 function loadCampaignTemplate(name) 89 { 90 let data = Engine.ReadJSONFile("campaigns/" + name + ".json"); 91 92 if (!data) 93 { 94 warn("Could not parse campaign data."); 95 return false; 96 } 97 98 g_CampaignTemplate = data; 99 100 return true; 101 } 102 103 function saveCurrentCampaign() 104 { 105 if (!g_CampaignSave) 106 { 107 warn("Cannot save current campaign, no campaign is currently loaded."); 108 return false; 109 } 110 111 return saveCampaign(g_CampaignSave, g_CampaignData); 112 } 113 114 function saveCampaign(filename, data) 115 { 116 Engine.WriteJSONFile("campaignsaves/" + filename + ".0adcampaign", data); 117 118 return true; 119 } 120 121 function loadCampaignSave(filename) 122 { 123 let campaignData = Engine.ReadJSONFile("campaignsaves/" + filename + ".0adcampaign"); 124 if (!campaignData) 125 { 126 error("Campaign " + filename + " failed to load properly."); 127 return undefined; 128 } 129 130 return campaignData; 131 } 132 -
new file inaries/data/mods/public/gui/campaign/gamesetup/gamesetup.js
diff --git a/binaries/data/mods/public/gui/campaign/gamesetup/gamesetup.js b/binaries/data/mods/public/gui/campaign/gamesetup/gamesetup.js new file mode 100644 index 0000000..c06100d
- + 1 // Here should go functions to set up Campaign games. 2 var g_GameAttributes = { "settings": {} }; 3 4 var g_DefaultPlayerData = []; 5 6 function sanitizePlayerData(playerData) 7 { 8 // Remove gaia 9 if (playerData.length && !playerData[0]) 10 playerData.shift(); 11 12 playerData.forEach((pData, index) => { 13 pData.Color = pData.Color; 14 pData.Civ = pData.Civ; 15 16 // Use default AI if the map doesn't specify any explicitly 17 if (!("AI" in pData)) 18 pData.AI = g_DefaultPlayerData[index].AI; 19 20 if (!("AIDiff" in pData)) 21 pData.AIDiff = g_DefaultPlayerData[index].AIDiff; 22 }); 23 } 24 25 26 // TODO: this is a minimalist patchwork from gamesetup.Js and only barely attempts to work. 27 28 function launchGame(level, levelID) 29 { 30 if (!level.Map) 31 { 32 warn("cannot start scenario: no maps specified."); 33 return; 34 } 35 36 loadSettingsValues(); 37 38 g_DefaultPlayerData = g_Settings.PlayerDefaults; 39 g_DefaultPlayerData.shift(); 40 41 let mapData = Engine.LoadMapSettings("maps/" + level.Map); 42 if (!mapData) 43 { 44 warn("Could not load map"); 45 return; 46 } 47 48 let mapSettings = mapData && mapData.settings ? deepcopy(mapData.settings) : {}; 49 50 if (mapSettings.PlayerData) 51 sanitizePlayerData(mapSettings.PlayerData); 52 53 // Copy any new settings 54 g_GameAttributes.map = "maps/" + level.Map; 55 g_GameAttributes.script = mapSettings.Script; 56 57 for (let prop in mapSettings) 58 g_GameAttributes.settings[prop] = mapSettings[prop]; 59 60 // TODO: support default victory conditions? 61 g_GameAttributes.settings.TriggerScripts = g_GameAttributes.settings.TriggerScripts || []; 62 63 g_GameAttributes.settings.mapType = "scenario"; 64 g_GameAttributes.mapType = "scenario"; 65 66 // Seed used for both map generation and simulation 67 g_GameAttributes.settings.Seed = Math.floor(Math.random() * Math.pow(2, 32)); 68 g_GameAttributes.settings.AISeed = Math.floor(Math.random() * Math.pow(2, 32)); 69 70 g_GameAttributes.matchID = Engine.GetMatchID(); 71 72 // TODO: player should be defined in the map or the campaign at the least. 73 let playerID = 1; 74 75 g_GameAttributes.campaignData = {"ID" : g_CampaignID, "template" : g_CampaignTemplate, "save": g_CampaignSave, "data" : g_CampaignData, "level" : levelID}; 76 77 Engine.StartGame(g_GameAttributes, playerID); 78 Engine.SwitchGuiPage("page_loading.xml", { 79 "attribs": g_GameAttributes, 80 "isNetworked" : false, 81 "playerAssignments": {} 82 }); 83 return g_GameAttributes.matchID; 84 } 85 No newline at end of file -
new file inaries/data/mods/public/gui/campaign/load.js
diff --git a/binaries/data/mods/public/gui/campaign/load.js b/binaries/data/mods/public/gui/campaign/load.js new file mode 100644 index 0000000..81752d6
- + 1 var g_Campaigns = []; 2 3 var g_CampaignTemplate = null; 4 5 function init() 6 { 7 let gameSelection = Engine.GetGUIObjectByName("gameSelection"); 8 9 let campaigns = Engine.BuildDirEntList("campaignsaves/", "*.0adcampaign", false); 10 11 if (!campaigns.length) 12 { 13 gameSelection.list = [translate("No ongoing campaigns found")]; 14 gameSelection.selected = -1; 15 selectionChanged(); 16 Engine.GetGUIObjectByName("loadGameButton").enabled = false; 17 Engine.GetGUIObjectByName("deleteGameButton").enabled = false; 18 return; 19 } 20 21 gameSelection.list = campaigns.map(path => generateLabel(pathToGame(path))); 22 gameSelection.list_data = campaigns.map(path => pathToGame(path)); 23 24 if (gameSelection.selected == -1) 25 gameSelection.selected = 0; 26 else if (gameSelection.selected >= campaigns.length) // happens when deleting the last saved game 27 gameSelection.selected = campaigns.length - 1; 28 else 29 selectionChanged(); 30 } 31 32 function pathToGame(path) 33 { 34 return path.replace("campaignsaves/","").replace(".0adcampaign",""); 35 } 36 37 function generateLabel(game) 38 { 39 let campaignData = loadCampaignSave(game); 40 if (!campaignData) 41 return "Incompatible - " + game; 42 43 if (!loadCampaignTemplate(campaignData.campaign)) 44 return "Incompatible - " + game; 45 46 return campaignData.userDescription + " - " + g_CampaignTemplate.Name; 47 } 48 49 function selectionChanged() 50 { 51 let gameSelection = Engine.GetGUIObjectByName("gameSelection"); 52 let selectionEmpty = gameSelection.selected == -1; 53 Engine.GetGUIObjectByName("invalidGame").hidden = !selectionEmpty; 54 Engine.GetGUIObjectByName("validGame").hidden = selectionEmpty; 55 56 if (selectionEmpty) 57 return; 58 59 /* 60 Engine.GetGUIObjectByName("savedMapName").caption = translate(metadata.initAttributes.settings.Name); 61 let mapData = getMapDescriptionAndPreview(metadata.initAttributes.mapType, metadata.initAttributes.map); 62 setMapPreviewImage("savedInfoPreview", mapData.preview); 63 64 Engine.GetGUIObjectByName("savedPlayers").caption = metadata.initAttributes.settings.PlayerData.length - 1; 65 Engine.GetGUIObjectByName("savedPlayedTime").caption = timeToString(metadata.gui.timeElapsed ? metadata.gui.timeElapsed : 0); 66 Engine.GetGUIObjectByName("savedMapType").caption = translateMapType(metadata.initAttributes.mapType); 67 Engine.GetGUIObjectByName("savedMapSize").caption = translateMapSize(metadata.initAttributes.settings.Size); 68 Engine.GetGUIObjectByName("savedVictory").caption = translateVictoryCondition(metadata.initAttributes.settings.GameType); 69 70 let caption = sprintf(translate("Mods: %(mods)s"), { "mods": metadata.mods.join(translate(", ")) }); 71 if (!hasSameMods(metadata, Engine.GetEngineInfo())) 72 caption = "[color=\"orange\"]" + caption + "[/color]"; 73 Engine.GetGUIObjectByName("savedMods").caption = caption; 74 75 Engine.GetGUIObjectByName("savedPlayersNames").caption = formatPlayerInfo( 76 metadata.initAttributes.settings.PlayerData, 77 metadata.gui.states 78 ); 79 */ 80 } 81 82 function loadCampaign() 83 { 84 let gameSelection = Engine.GetGUIObjectByName("gameSelection"); 85 86 let campaign = gameSelection.list_data[gameSelection.selected]; 87 reallyLoadCampaign(campaign); 88 89 // TODO: compatibility checks cf saved games 90 /* 91 // Check compatibility before really loading it 92 let sameMods = hasSameMods(metadata, engineInfo); 93 94 if (sameEngineVersion && sameSavegameVersion && sameMods) 95 { 96 reallyLoadGame(gameId); 97 return; 98 } 99 100 if (!sameMods) 101 { 102 if (!metadata.mods) 103 metadata.mods = []; 104 105 message += translate("The savegame needs a different set of mods:") + "\n" + 106 sprintf(translate("Required: %(mods)s"), { 107 "mods": metadata.mods.join(translate(", ")) 108 }) + "\n" + 109 sprintf(translate("Active: %(mods)s"), { 110 "mods": engineInfo.mods.join(translate(", ")) 111 }); 112 } 113 114 message += "\n" + translate("Do you still want to proceed?"); 115 116 messageBox( 117 500, 250, 118 message, 119 translate("Warning"), 120 [translate("No"), translate("Yes")], 121 [init, function(){ reallyLoadGame(gameId); }] 122 );*/ 123 } 124 125 function reallyLoadCampaign(name) 126 { 127 Engine.ConfigDB_CreateValue("user", "currentcampaign", name); 128 Engine.ConfigDB_WriteValueToFile("user", "currentcampaign", name, "config/user.cfg"); 129 130 loadCurrentCampaignSave(); 131 } 132 133 function deleteCampaign() 134 { 135 let gameSelection = Engine.GetGUIObjectByName("gameSelection"); 136 let campaign = gameSelection.list_data[gameSelection.selected]; 137 138 if (!campaign) 139 return; 140 141 messageBox( 142 500, 200, 143 sprintf(translate("\"%(label)s\""), { 144 "label": gameSelection.list[gameSelection.selected] 145 }) + "\n" + translate("Campaign will be permanently deleted, are you sure?"), 146 translate("DELETE"), 147 [translate("No"), translate("Yes")], 148 [null, function(){ reallyDeleteCampaign(campaign); }] 149 ); 150 } 151 152 function reallyDeleteCampaign(name) 153 { 154 if (!Engine.DeleteCampaignGame(name)) 155 error("Could not delete campaign game " + name); 156 157 if (Engine.ConfigDB_GetValue("user", "currentcampaign") === name) 158 { 159 Engine.ConfigDB_RemoveValue("user", "currentcampaign"); 160 Engine.ConfigDB_WriteFile("user", "config/user.cfg"); 161 162 // TODO: this doesn't seem to work. 163 if (Engine.GetGUIObjectByName("subMenuContinueCampaignButton")) 164 Engine.GetGUIObjectByName("subMenuContinueCampaignButton").enabled = false; 165 } 166 167 // re-run init to refresh. 168 init(); 169 } -
new file inaries/data/mods/public/gui/campaign/load.xml
diff --git a/binaries/data/mods/public/gui/campaign/load.xml b/binaries/data/mods/public/gui/campaign/load.xml new file mode 100644 index 0000000..380f83d
- + 1 <?xml version="1.0" encoding="utf-8"?> 2 3 <objects> 4 5 <script file="gui/common/color.js" /> 6 <script file="gui/common/functions_global_object.js" /> 7 <script file="gui/common/functions_utility.js" /> 8 9 <script file="gui/campaign/campaign.js" /> 10 <script file="gui/campaign/campaign_io.js" /> 11 <script file="gui/campaign/load.js" /> 12 13 <!-- Add a translucent black background to fade out the menu page --> 14 <object type="image" z="0" sprite="BackgroundTranslucent"/> 15 16 <object type="image" style="ModernDialog" size="50%-420 50%-325 50%+420 50%+325"> 17 18 <object type="text" style="TitleText" size="50%-128 -18 50%+128 14"> 19 <translatableAttribute id="caption">Load Campaign</translatableAttribute> 20 </object> 21 22 <object type="image" size="0 20 550 100%"> 23 <object name="gameSelection" style="ModernList" type="list" size="24 12 100%-24 100%-90"> 24 <action on="SelectionChange">selectionChanged();</action> 25 </object> 26 27 <object type="button" size="0%+25 100%-60 33%+10 100%-32" style="StoneButton" hotkey="cancel"> 28 <translatableAttribute id="caption">Cancel</translatableAttribute> 29 <action on="Press">Engine.PopGuiPage();</action> 30 </object> 31 32 <object name="deleteGameButton" type="button" size="33%+20 100%-60 66%-15 100%-32" style="StoneButton" hotkey="session.savedgames.delete"> 33 <translatableAttribute id="caption">Delete</translatableAttribute> 34 <action on="Press">deleteCampaign();</action> 35 </object> 36 37 <object name="loadGameButton" type="button" style="StoneButton" size="66%-5 100%-60 100%-25 100%-32"> 38 <translatableAttribute id="caption">Load</translatableAttribute> 39 <action on="Press">loadCampaign();</action> 40 </object> 41 42 </object> 43 44 <object name="validGame" type="image" size="550 20 100%-20 100%"> 45 <object name="savedMapName" size="0 0 100% 20" type="text" style="ModernLabelText" /> 46 <object name="savedInfoPreview" size="20 20 240 240" type="image" sprite="" /> 47 48 <object size="0 250 50% 270" type="text" style="ModernLabelText" text_align="left"> 49 <translatableAttribute id="caption">TODO:</translatableAttribute> 50 </object> 51 <object name="savedPlayers" size="50% 250 100%-15 270" type="text" style="ModernLabelText" text_align="left" /> 52 <object size="0 270 50% 290" type="text" style="ModernLabelText" text_align="left"> 53 <translatableAttribute id="caption">TODO:</translatableAttribute> 54 </object> 55 <object name="savedPlayedTime" size="50% 270 100%-15 290" type="text" style="ModernLabelText" text_align="left" /> 56 <object size="0 290 50% 310" type="text" style="ModernLabelText" text_align="left"> 57 <translatableAttribute id="caption">TODO:</translatableAttribute> 58 </object> 59 <object name="savedMapType" size="50% 290 100%-15 310" type="text" style="ModernLabelText" text_align="left" /> 60 <object size="0 310 50% 330" type="text" style="ModernLabelText" text_align="left"> 61 <translatableAttribute id="caption">TODO:</translatableAttribute> 62 </object> 63 <object name="savedMapSize" size="50% 310 100%-15 330" type="text" style="ModernLabelText" text_align="left" /> 64 <object size="0 330 50% 350" type="text" style="ModernLabelText" text_align="left"> 65 <translatableAttribute id="caption">TODO:</translatableAttribute> 66 </object> 67 <object name="savedVictory" size="50% 330 100%-15 350" type="text" style="ModernLabelText" text_align="left" /> 68 69 <object size="0 352 100%-15 353" type="image" sprite="ModernWhiteLine" z="25" /> 70 71 <object name="savedMods" size="0 355 100%-15 395" type="text" style="ModernLabelText" text_align="left" /> 72 73 <object size="0 397 100%-15 398" type="image" sprite="ModernWhiteLine" z="25" /> 74 <object name="savedPlayersNames" size="0 400 100%-10 100%-32" type="text" style="MapPlayerList" /> 75 <object size="0 100%-32 100%-15 100%-31" type="image" sprite="ModernWhiteLine" z="25" /> 76 </object> 77 78 <object name="invalidGame" size="570 55 790 155" type="image" sprite="logo" /> 79 </object> 80 81 </objects> -
new file inaries/data/mods/public/gui/campaign/newcampaign_modal.js
diff --git a/binaries/data/mods/public/gui/campaign/newcampaign_modal.js b/binaries/data/mods/public/gui/campaign/newcampaign_modal.js new file mode 100644 index 0000000..fac7bcc
- + 1 var g_CampaignID = null; 2 var g_CampaignData = null; 3 4 // TODO: refuse empty names 5 6 function init(data) 7 { 8 // load existing campaigns 9 let gameSelection = Engine.GetGUIObjectByName("gameSelection"); 10 gameSelection.selected = -1; 11 12 let campaigns = Engine.BuildDirEntList("campaignsaves/", "*.0adcampaign", false); 13 if (!campaigns.length) 14 gameSelection.list = [translate("No ongoing campaigns.")]; 15 else 16 { 17 gameSelection.list = campaigns.map(path => generateLabel(pathToGame(path))); 18 gameSelection.list_data = campaigns.map(path => pathToGame(path)); 19 } 20 21 if (data) 22 { 23 g_CampaignID = data.campaignID; 24 g_CampaignData = data.campaignData; 25 } 26 } 27 28 function selectionChanged() 29 { 30 let gameSelection = Engine.GetGUIObjectByName("gameSelection"); 31 if (gameSelection.selected === -1) 32 return; 33 34 // TODO: do something? 35 } 36 37 function startCampaign() 38 { 39 // TODO: handle overwrite and so on 40 41 // temp: prefill campaign name 42 realStartCampaign(Engine.GetGUIObjectByName("saveGameDesc").caption); 43 } 44 45 function realStartCampaign(desc) 46 { 47 let name = g_CampaignID + "_1"; 48 // if file already exists, pick the number above the existing ones. Don't bother making it dense. 49 if (Engine.FileExists("campaignsaves/" + name + ".0adcampaign")) 50 { 51 // get other campaigns following that template 52 let campaigns = Engine.BuildDirEntList("campaignsaves/", g_CampaignID + "_*.0adcampaign", false); 53 let max = 1; 54 for (let camp of campaigns) 55 { 56 let nb = camp.replace("campaignsaves/" + g_CampaignID + "_","").replace(".0adcampaign",""); 57 if (+nb > max) 58 max = +nb; 59 } 60 name = g_CampaignID + "_" + (max+1); 61 // sanity check 62 if (Engine.FileExists("campaignsaves/" + name + ".0adcampaign")) 63 { 64 error("tell wraitii he can't code"); 65 return; 66 } 67 } 68 69 saveCampaign(name, {"userDescription" : desc, "campaign" : g_CampaignID}) 70 71 // inform user config that we are playing this campaign 72 Engine.ConfigDB_CreateValue("user", "currentcampaign", name); 73 Engine.ConfigDB_WriteValueToFile("user", "currentcampaign", name, "config/user.cfg"); 74 75 loadCurrentCampaignSave(); 76 } 77 No newline at end of file -
new file inaries/data/mods/public/gui/campaign/newcampaign_modal.xml
diff --git a/binaries/data/mods/public/gui/campaign/newcampaign_modal.xml b/binaries/data/mods/public/gui/campaign/newcampaign_modal.xml new file mode 100644 index 0000000..6380442
- + 1 <?xml version="1.0" encoding="utf-8"?> 2 3 <objects> 4 5 <!-- Used for engine + mod version checks and deleteTooltip. --> 6 <script file="gui/common/functions_utility_loadsave.js" /> 7 <script file="gui/common/functions_global_object.js" /> 8 9 <script file="gui/campaign/campaign.js" /> 10 <script file="gui/campaign/campaign_io.js" /> 11 <script file="gui/campaign/load.js" /> 12 <script file="gui/campaign/newcampaign_modal.js" /> 13 14 <!-- Add a translucent black background to fade out the page --> 15 <object type="image" z="0" sprite="ModernFade"/> 16 17 <object type="image" style="ModernDialog" size="50%-300 50%-200 50%+300 50%+200"> 18 <object type="image" z="0" sprite="ModernFade"/> 19 <object type="text" style="TitleText" size="50%-128 -18 50%+128 14"> 20 <translatableAttribute id="caption">New Campaign</translatableAttribute> 21 </object> 22 23 <object name="gameSelection" style="ModernList" type="list" size="24 22 100%-24 100%-130"> 24 <action on="SelectionChange">selectionChanged();</action> 25 </object> 26 27 <object size="24 100%-124 100%-24 100%-100" name="descLabel" type="text" style="ModernLeftLabelText"> 28 <translatableAttribute id="caption">Name of this campaign run:</translatableAttribute> 29 </object> 30 31 <object name="saveGameDesc" size="24 100%-96 100%-24 100%-72" type="input" style="ModernInput"> 32 <action on="Press">startCampaign();</action> 33 </object> 34 35 <object name="saveButton" type="button" size="0%+25 100%-60 33%+10 100%-32" style="StoneButton" hotkey="cancel"> 36 <translatableAttribute id="caption">Cancel</translatableAttribute> 37 <action on="Press">Engine.PopGuiPage();</action> 38 </object> 39 40 <object name="deleteGameButton" type="button" size="33%+20 100%-60 66%-15 100%-32" style="StoneButton"> 41 <translatableAttribute id="caption">Delete</translatableAttribute> 42 <action on="Press">deleteCampaign();</action> 43 </object> 44 45 <object type="button" style="StoneButton" size="66%-5 100%-60 100%-25 100%-32"> 46 <translatableAttribute id="caption">Start</translatableAttribute> 47 <action on="Press">startCampaign();</action> 48 </object> 49 50 </object> 51 </objects> -
new file inaries/data/mods/public/gui/campaign/simple_campaign/mainmenu.js
diff --git a/binaries/data/mods/public/gui/campaign/simple_campaign/mainmenu.js b/binaries/data/mods/public/gui/campaign/simple_campaign/mainmenu.js new file mode 100644 index 0000000..9d31b4c
- + 1 var g_SelectedLevel = null; 2 3 var g_SavedGamesMetadata = []; 4 5 function init(data) 6 { 7 if (!data) 8 { 9 warn("Loading campaign menu without a campaign loaded") 10 return false; 11 } 12 13 g_CampaignID = data.ID; 14 g_CampaignTemplate = data.template; 15 g_CampaignSave = data.save; 16 g_CampaignData = data.data; 17 18 generateLevelList(); 19 selectionChanged(); 20 21 Engine.GetGUIObjectByName("mapPreview").sprite = "cropped:" + 400/512 + "," + 300/512 + ":session/icons/mappreview/nopreview.png"; 22 23 let gameSelection = Engine.GetGUIObjectByName("gameSelection"); 24 let savedGames = Engine.GetSavedGames().sort(sortDecreasingDate).filter(game => game.metadata.initAttributes && game.metadata.initAttributes.campaignData && game.metadata.initAttributes.campaignData.ID == g_CampaignID); 25 gameSelection.enabled = !!savedGames.length; 26 if (!savedGames.length) 27 { 28 gameSelection.list = [translate("No saved games found")]; 29 gameSelection.selected = -1; 30 return; 31 } 32 33 // Get current game version and loaded mods 34 let engineInfo = Engine.GetEngineInfo(); 35 36 g_SavedGamesMetadata = savedGames.map(game => game.metadata); 37 38 gameSelection.list = savedGames.map(game => generateCampaignLabel(game.metadata, engineInfo)); 39 gameSelection.list_data = savedGames.map(game => game.id); 40 41 saveSelectionChanged(); 42 } 43 44 function generateLevelList() 45 { 46 // TODO: remember old selection? 47 let selection = Engine.GetGUIObjectByName("levelSelection"); 48 49 let list = []; 50 for (let key in g_CampaignTemplate.Levels) 51 { 52 let level = g_CampaignTemplate.Levels[key]; 53 54 if (!("ShowUnavailable" in g_CampaignTemplate) || !g_CampaignTemplate.ShowUnavailable && !hasRequirements(level)) 55 continue; 56 57 let status = ""; 58 let name = level.Name; 59 if (!hasRequirements(level)) 60 { 61 status = "not unlocked yet"; 62 name = "[color=\"gray\"]" + name + "[/color]"; 63 } 64 list.push({ "ID" : key, "name" : name, "status" : status }); 65 } 66 list.sort((a, b) => g_CampaignTemplate.Order.indexOf(a.ID) - g_CampaignTemplate.Order.indexOf(b.ID)); 67 68 // change array of object into object of array. 69 list = prepareForDropdown(list); 70 71 // Push to GUI 72 selection.selected = -1; 73 selection.list_name = list.name || []; 74 selection.list_status = list.status || []; 75 76 // Change these last, otherwise crash 77 // TODO: do we need both of those? I'm unsure. 78 selection.list = list.ID || []; 79 selection.list_data = list.ID || []; 80 81 // replaySelection.selected = replaySelection.list.findIndex(directory => directory == g_SelectedReplayDirectory); 82 83 // displayReplayDetails(); 84 85 } 86 87 function displayLevelDetails(levelID) 88 { 89 let level = g_CampaignTemplate.Levels[levelID]; 90 91 // TODO: load from map file if not present 92 Engine.GetGUIObjectByName("scenarioName").caption = translate(level.Name); 93 Engine.GetGUIObjectByName("scenarioDesc").caption = translate(level.Description); 94 95 // todo: ibidem 96 if (level.Preview) 97 Engine.GetGUIObjectByName("mapPreview").sprite = "cropped:" + 400/512 + "," + 300/512 + ":" + level.Preview; 98 else 99 Engine.GetGUIObjectByName("mapPreview").sprite = "cropped:" + 400/512 + "," + 300/512 + ":session/icons/mappreview/nopreview.png"; 100 101 g_SelectedLevel = levelID; 102 103 if (!hasRequirements(level)) 104 { 105 Engine.GetGUIObjectByName("startButton").enabled = false; 106 return; 107 } 108 109 Engine.GetGUIObjectByName("startButton").enabled = true; 110 } 111 112 function selectionChanged() 113 { 114 let selection = Engine.GetGUIObjectByName("levelSelection"); 115 116 if (selection.selected === -1) 117 { 118 Engine.GetGUIObjectByName("startButton").enabled = false; 119 Engine.GetGUIObjectByName("startButton").hidden = false; 120 Engine.GetGUIObjectByName("loadSavedButton").hidden = true; 121 return; 122 } 123 124 Engine.GetGUIObjectByName("loadSavedButton").hidden = true; 125 Engine.GetGUIObjectByName("startButton").hidden = false; 126 127 let selec = Engine.GetGUIObjectByName("gameSelection"); 128 selec.selected = -1; 129 130 displayLevelDetails(selection.list[selection.selected]); 131 } 132 133 function saveSelectionChanged() 134 { 135 let metadata = g_SavedGamesMetadata[Engine.GetGUIObjectByName("gameSelection").selected]; 136 137 if (!metadata) 138 return; 139 140 // fetch campaign scenario metadata if present. 141 let scenarioID = metadata.initAttributes.campaignData.level; 142 143 let selection = Engine.GetGUIObjectByName("levelSelection"); 144 selection.selected = -1; 145 for (let level of selection.list_data) 146 if (level == scenarioID) 147 { 148 displayLevelDetails(level); 149 break; 150 } 151 152 Engine.GetGUIObjectByName("loadSavedButton").hidden = false; 153 Engine.GetGUIObjectByName("startButton").hidden = true; 154 /* 155 Engine.GetGUIObjectByName("savedMapName").caption = translate(metadata.initAttributes.settings.Name); 156 let mapData = getMapDescriptionAndPreview(metadata.initAttributes.mapType, metadata.initAttributes.map); 157 setMapPreviewImage("savedInfoPreview", mapData.preview); 158 159 Engine.GetGUIObjectByName("savedPlayers").caption = metadata.initAttributes.settings.PlayerData.length - 1; 160 Engine.GetGUIObjectByName("savedPlayedTime").caption = timeToString(metadata.gui.timeElapsed ? metadata.gui.timeElapsed : 0); 161 Engine.GetGUIObjectByName("savedMapType").caption = translateMapType(metadata.initAttributes.mapType); 162 Engine.GetGUIObjectByName("savedMapSize").caption = translateMapSize(metadata.initAttributes.settings.Size); 163 Engine.GetGUIObjectByName("savedVictory").caption = translateVictoryCondition(metadata.initAttributes.settings.GameType); 164 165 let caption = sprintf(translate("Mods: %(mods)s"), { "mods": metadata.mods.join(translate(", ")) }); 166 if (!hasSameMods(metadata, Engine.GetEngineInfo())) 167 caption = "[color=\"orange\"]" + caption + "[/color]"; 168 Engine.GetGUIObjectByName("savedMods").caption = caption; 169 170 Engine.GetGUIObjectByName("savedPlayersNames").caption = formatPlayerInfo( 171 metadata.initAttributes.settings.PlayerData, 172 metadata.gui.states 173 );*/ 174 } 175 176 function generateCampaignLabel(metadata, engineInfo) 177 { 178 let dateTimeString = Engine.FormatMillisecondsIntoDateString(metadata.time*1000, translate("yyyy-MM-dd HH:mm:ss")); 179 let dateString = sprintf(translate("\\[%(date)s]"), { "date": dateTimeString }); 180 181 if (engineInfo) 182 { 183 if (!hasSameSavegameVersion(metadata, engineInfo) || !hasSameEngineVersion(metadata, engineInfo)) 184 dateString = "[color=\"red\"]" + dateString + "[/color]"; 185 else if (!hasSameMods(metadata, engineInfo)) 186 dateString = "[color=\"orange\"]" + dateString + "[/color]"; 187 } 188 189 return sprintf( 190 metadata.description ? 191 translate("%(dateString)s %(level)s - %(description)s") : 192 translate("%(dateString)s %(level)s"), 193 { 194 "dateString": dateString, 195 "level": metadata.initAttributes.campaignData.template.Levels[metadata.initAttributes.campaignData.level].Name, 196 "description": metadata.description || "" 197 } 198 ); 199 } 200 201 function exitCampaignMode(exitGame = false) 202 { 203 // TODO: should this be here? 204 saveCurrentCampaign(); 205 206 if (exitGame) 207 { 208 messageBox( 209 400, 200, 210 translate("Are you sure you want to quit 0 A.D.?"), 211 translate("Confirmation"), 212 [translate("No"), translate("Yes")], 213 [null, Engine.Exit] 214 ); 215 return; 216 } 217 Engine.SwitchGuiPage("page_pregame.xml", {}); 218 } -
new file inaries/data/mods/public/gui/campaign/simple_campaign/mainmenu.xml
diff --git a/binaries/data/mods/public/gui/campaign/simple_campaign/mainmenu.xml b/binaries/data/mods/public/gui/campaign/simple_campaign/mainmenu.xml new file mode 100644 index 0000000..785c7b1
- + 1 <?xml version="1.0" encoding="utf-8"?> 2 3 <objects> 4 <script file="gui/common/music.js"/> 5 <script file="gui/common/functions_civinfo.js"/> 6 <script file="gui/common/functions_global_object.js"/> 7 <script file="gui/common/functions_utility_error.js"/> 8 <script file="gui/common/functions_utility_loadsave.js"/> 9 <script file="gui/common/settings.js"/> 10 11 <script file="gui/savedgames/load.js"/> 12 13 <script file="gui/campaign/campaign.js"/> 14 <script file="gui/campaign/campaign_io.js"/> 15 <script file="gui/campaign/gamesetup/gamesetup.js"/> 16 17 <script file="gui/campaign/simple_campaign/mainmenu.js"/> 18 19 <object type="image" style="ModernWindow" size="0 0 100% 100%" name="campaignMenuWindow"> 20 21 <!-- Title --> 22 <object style="ModernLabelText" name="CampaignTitle" type="text" size="50%-128 4 50%+128 36"> 23 <translatableAttribute id="caption">Campaign Name</translatableAttribute> 24 </object> 25 26 <object name="leftPanel" size="30 50 100%-447 100%-340"> 27 <!-- List of available levels --> 28 <object name="levelSelection" 29 size="0 0 100% 100%" 30 style="ModernList" 31 type="olist" 32 font="sans-stroke-16" 33 > 34 35 <action on="SelectionChange">selectionChanged();</action> 36 <!--<action on="SelectionColumnChange">displayReplayList();</action> 37 <action on="mouseleftdoubleclickitem">startCampaign();</action>--> 38 39 <!-- Columns --> 40 <column id="name" color="172 172 212" width="80%"> 41 <translatableAttribute id="heading" context="campaignLevelList">Scenario Name</translatableAttribute> 42 </column> 43 44 <column id="status" color="192 192 192" width="20%"> 45 <translatableAttribute id="heading" context="campaignLevelList">Status</translatableAttribute> 46 </column> 47 </object> 48 </object> 49 50 <object name="rightPanel" size="100%-432 50 100%-30 100%-340"> 51 <!-- Scenario Image --> 52 <object type="image" sprite="ModernDarkBoxGold" name="levelPreviewBox" size="0 0 100% 302"> 53 <object type="image" sprite="snLevelPreview" size="1 1 100%-1 301" name="mapPreview"/> 54 </object> 55 56 <!-- Name and description --> 57 <!-- TODO: figure out if we want an options button here too. --> 58 <object size="0 315 100% 100%"> 59 <object name="scenarioName" type="text" style="ScenarioNameText" size="0 0 100% 30"> 60 <translatableAttribute id="caption">No scenario selected</translatableAttribute> 61 </object> 62 <object type="image" sprite="ModernDarkBoxGold" size="0 30 100% 100%"> 63 <object name="scenarioDesc" type="text" style="ModernText" size="10 10 100%-10 100%-10"/> 64 </object> 65 </object> 66 67 </object> 68 69 <object name="savePanel" size="30 100%-330 100%-30 100%-85"> 70 71 <object style="ModernLabelText" type="text" size="50%-128 4 50%+128 36"> 72 <translatableAttribute id="caption">Saved Games</translatableAttribute> 73 </object> 74 75 <object name="gameSelection" style="ModernList" type="list" size="0 50 100% 100%"> 76 <action on="SelectionChange">saveSelectionChanged();</action> 77 </object> 78 79 </object> 80 81 <object name="bottomPanel" size="30 100%-55 100%-5 100%-25" > 82 83 <!-- Exit & Back to Main Menu --> 84 <object type="button" style="StoneButton" size="0 0 17% 100%"> 85 <translatableAttribute id="caption">Back to Main Menu</translatableAttribute> 86 <action on="Press">exitCampaignMode()</action> 87 </object> 88 89 <object type="button" style="StoneButton" size="17%+20 0 34%+20 100%"> 90 <translatableAttribute id="caption">Exit Game</translatableAttribute> 91 <action on="Press">exitCampaignMode(true);</action> 92 </object> 93 94 <object name="startButton" type="button" style="StoneButtonFancy" size="83%-25 0 100%-25 100%" enabled="false"> 95 <translatableAttribute id="caption">Start Scenario</translatableAttribute> 96 <action on="Press"> 97 startLevel(g_SelectedLevel); 98 </action> 99 </object> 100 <object name="loadSavedButton" type="button" style="StoneButtonFancy" size="83%-25 0 100%-25 100%" hidden="true"> 101 <translatableAttribute id="caption">Resume Saved Game</translatableAttribute> 102 <action on="Press"> 103 loadGame(); 104 </action> 105 </object> 106 107 </object> 108 </object> 109 110 </objects> -
new file inaries/data/mods/public/gui/campaign/simple_campaign/page_menu.xml
diff --git a/binaries/data/mods/public/gui/campaign/simple_campaign/page_menu.xml b/binaries/data/mods/public/gui/campaign/simple_campaign/page_menu.xml new file mode 100644 index 0000000..d9a1909
- + 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/sprites.xml</include> 9 <include>common/styles.xml</include> 10 <include>common/init.xml</include> 11 12 <include>campaign/simple_campaign/sprites.xml</include> 13 <include>campaign/simple_campaign/styles.xml</include> 14 <include>campaign/simple_campaign/mainmenu.xml</include> 15 </page> -
new file inaries/data/mods/public/gui/campaign/simple_campaign/sprites.xml
diff --git a/binaries/data/mods/public/gui/campaign/simple_campaign/sprites.xml b/binaries/data/mods/public/gui/campaign/simple_campaign/sprites.xml new file mode 100644 index 0000000..184f35f
- + 1 <?xml version="1.0" encoding="utf-8"?> 2 3 <sprites> 4 5 <sprite name="snLevelPreview"> 6 <image texture="session/icons/mappreview/nopreview.png" 7 size="0 0 100% 100%" 8 /> 9 </sprite> 10 11 </sprites> -
new file inaries/data/mods/public/gui/campaign/simple_campaign/styles.xml
diff --git a/binaries/data/mods/public/gui/campaign/simple_campaign/styles.xml b/binaries/data/mods/public/gui/campaign/simple_campaign/styles.xml new file mode 100644 index 0000000..89fe270
- + 1 <?xml version="1.0" encoding="utf-8"?> 2 3 <styles> 4 <style name="ScenarioNameText" 5 font="sans-bold-18" 6 textcolor="white" 7 text_align="center" 8 text_valign="top" 9 /> 10 </styles> -
new file inaries/data/mods/public/gui/campaign/simple_setup/campaignsetup.js
diff --git a/binaries/data/mods/public/gui/campaign/simple_setup/campaignsetup.js b/binaries/data/mods/public/gui/campaign/simple_setup/campaignsetup.js new file mode 100644 index 0000000..477f750
- + 1 var g_CampaignsAvailable = {}; // "name of JSON file/ID of campaign" : data as parsed JSON 2 3 var g_SelectedCampaign = null; 4 5 /* 6 * Initializes the campaign window. 7 * Loads all campaigns 8 * Allows you to start them. 9 */ 10 11 // TODO: Should we support mods? 12 13 function init(data) 14 { 15 g_CampaignsAvailable = LoadAvailableCampaigns(); 16 17 Engine.GetGUIObjectByName("CampaignImage").sprite = "cropped:" + 400/512 + "," + 300/512 + ":session/icons/mappreview/nopreview.png"; 18 19 GenerateCampaignList(); 20 } 21 22 function GenerateCampaignList() 23 { 24 let selection = Engine.GetGUIObjectByName("campaignSelection"); 25 if (selection.selected !== -1) 26 displayCampaignDetails(); 27 28 let list = []; 29 for (let key in g_CampaignsAvailable) 30 list.push({ "directories" : key, "name" : g_CampaignsAvailable[key].Name }); 31 32 // change array of object into object of array. 33 list = prepareForDropdown(list); 34 35 // Push to GUI 36 selection.selected = -1; 37 selection.list_name = list.name || []; 38 39 // Change these last, otherwise crash 40 // TODO: do we need both of those? I'm unsure. 41 selection.list = list.directories || []; 42 selection.list_data = list.directories || []; 43 44 // replaySelection.selected = replaySelection.list.findIndex(directory => directory == g_SelectedReplayDirectory); 45 46 // displayReplayDetails(); 47 } 48 49 function displayCampaignDetails() 50 { 51 let selection = Engine.GetGUIObjectByName("campaignSelection"); 52 if (selection.selected === -1) 53 return; 54 55 g_SelectedCampaign = selection.list[selection.selected]; 56 57 Engine.GetGUIObjectByName("startCampButton").enabled = true; 58 Engine.GetGUIObjectByName("campaignOptionsButton").enabled = true; 59 60 Engine.GetGUIObjectByName("CampaignTitle").caption = translate(g_CampaignsAvailable[g_SelectedCampaign].Name); 61 Engine.GetGUIObjectByName("campaignDesc").caption = translate(g_CampaignsAvailable[g_SelectedCampaign].Description); 62 63 if (g_CampaignsAvailable[g_SelectedCampaign].Image) 64 Engine.GetGUIObjectByName("CampaignImage").sprite = "stretched:" + g_CampaignsAvailable[g_SelectedCampaign].Image; 65 else 66 Engine.GetGUIObjectByName("CampaignImage").sprite = "cropped:" + 400/512 + "," + 300/512 + ":session/icons/mappreview/nopreview.png"; 67 68 } 69 70 function startCampaign() 71 { 72 Engine.PushGuiPage("page_newcampaign.xml", { "campaignID" : g_SelectedCampaign, "campaignData" : g_CampaignsAvailable[g_SelectedCampaign] }); 73 } -
new file inaries/data/mods/public/gui/campaign/simple_setup/campaignsetup.xml
diff --git a/binaries/data/mods/public/gui/campaign/simple_setup/campaignsetup.xml b/binaries/data/mods/public/gui/campaign/simple_setup/campaignsetup.xml new file mode 100644 index 0000000..a738040
- + 1 <?xml version="1.0" encoding="utf-8"?> 2 3 <objects> 4 5 <!-- Used to display game info. --> 6 <script file="gui/common/color.js" /> 7 <script file="gui/common/functions_civinfo.js" /> 8 <script file="gui/common/functions_utility.js" /> 9 <script file="gui/common/gamedescription.js"/> 10 <script file="gui/common/settings.js" /> 11 12 <!-- Used to display message boxes. --> 13 <script file="gui/common/functions_global_object.js" /> 14 15 <!-- Used for engine + mod version checks and deleteTooltip. --> 16 <script file="gui/common/functions_utility_loadsave.js" /> 17 18 <script file="gui/campaign/campaign_io.js" /> 19 <script file="gui/campaign/simple_setup/campaignsetup.js" /> 20 21 <!-- Everything displayed in the campaign menu. --> 22 <object type="image" style="ModernWindow" size="0 0 100% 100%" name="campaignWindow"> 23 24 <!-- Title --> 25 <object style="ModernLabelText" type="text" size="50%-128 4 50%+128 36"> 26 <translatableAttribute id="caption">Campaigns</translatableAttribute> 27 </object> 28 29 <object name="leftPanel" size="30 50 100%-515 100%-80"> 30 <!-- List of campaigns --> 31 <object name="campaignSelection" 32 size="0 0 100% 100%" 33 style="ModernList" 34 type="olist" 35 sortable="true" 36 selected_column="name" 37 selected_column_order="-1" 38 sprite_asc="ModernArrowDown" 39 sprite_desc="ModernArrowUp" 40 sprite_not_sorted="ModernNotSorted" 41 font="sans-stroke-16" 42 > 43 44 <action on="SelectionChange">displayCampaignDetails();</action> 45 <!--<action on="SelectionColumnChange">displayReplayList();</action>--> 46 <action on="mouseleftdoubleclickitem">startCampaign();</action> 47 48 <column id="name" color="172 172 212" width="99%"> 49 <translatableAttribute id="heading" context="campaignsetup">Name</translatableAttribute> 50 </column> 51 </object> 52 </object> 53 54 <object name="rightPanel" size="100%-485 50 100%-30 100%-80"> 55 <object type="image" sprite="ModernDarkBoxGold" size="0 0 100% 342"> 56 <object name="CampaignImage" type="image" sprite="snCampaignImage" size="1 1 100%-1 100%-1"> 57 </object> 58 </object> 59 <object type="text" name="CampaignTitle" style="CampaignNameText" size="0 360 100% 390"> 60 <translatableAttribute id="caption">No campaign selected</translatableAttribute> 61 </object> 62 <object type="image" sprite="ModernDarkBoxGold" name="campaignDescBox" size="0 400 100% 100%-50" hidden="false"> 63 <object type="text" name="campaignDesc" style="LeftButtonText" font="sans-16" size="10 10 100%-10 100%-10"> 64 </object> 65 </object> 66 <object type="button" name="campaignOptionsButton" style="ModernButtonRed" size="100%-140 100%-30 100% 100%-0" enabled="false"> 67 <translatableAttribute id="caption">Options</translatableAttribute> 68 </object> 69 </object> 70 </object> 71 72 <object name="bottomPanel" size="25 100%-55 100%-5 100%-25" > 73 74 <!-- Main Menu Button --> 75 <object type="button" style="StoneButton" size="25 0 17%+25 100%"> 76 <translatableAttribute id="caption">Main Menu</translatableAttribute> 77 <action on="Press">Engine.SwitchGuiPage("page_pregame.xml");</action> 78 </object> 79 80 <!-- Start Campaign Button --> 81 <object name="startCampButton" type="button" style="StoneButtonFancy" size="83%-25 0 100%-25 100%" enabled="false"> 82 <translatableAttribute id="caption">Start Campaign</translatableAttribute> 83 <action on="Press">startCampaign();</action> 84 </object> 85 86 </object> 87 </objects> -
new file inaries/data/mods/public/gui/campaign/simple_setup/sprites.xml
diff --git a/binaries/data/mods/public/gui/campaign/simple_setup/sprites.xml b/binaries/data/mods/public/gui/campaign/simple_setup/sprites.xml new file mode 100644 index 0000000..8a7e819
- + 1 <?xml version="1.0" encoding="utf-8"?> 2 3 <sprites> 4 5 <sprite name="snCampaignImage"> 6 <image texture="session/icons/mappreview/nopreview.png" 7 size="0 0 100% 100%" 8 /> 9 </sprite> 10 11 </sprites> -
new file inaries/data/mods/public/gui/campaign/simple_setup/styles.xml
diff --git a/binaries/data/mods/public/gui/campaign/simple_setup/styles.xml b/binaries/data/mods/public/gui/campaign/simple_setup/styles.xml new file mode 100644 index 0000000..1be4e97
- + 1 <?xml version="1.0" encoding="utf-8"?> 2 3 <styles> 4 <style name="CampaignNameText" 5 font="sans-bold-18" 6 textcolor="white" 7 text_align="center" 8 text_valign="top" 9 /> 10 </styles> -
new file inaries/data/mods/public/gui/page_campaignsetup_simple.xml
diff --git a/binaries/data/mods/public/gui/page_campaignsetup_simple.xml b/binaries/data/mods/public/gui/page_campaignsetup_simple.xml new file mode 100644 index 0000000..56630b2
- + 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/sprites.xml</include> 9 <include>common/styles.xml</include> 10 11 <include>campaign/simple_setup/sprites.xml</include> 12 <include>campaign/simple_setup/styles.xml</include> 13 <include>campaign/simple_setup/campaignsetup.xml</include> 14 </page> -
new file inaries/data/mods/public/gui/page_loadcampaign.xml
diff --git a/binaries/data/mods/public/gui/page_loadcampaign.xml b/binaries/data/mods/public/gui/page_loadcampaign.xml new file mode 100644 index 0000000..4d6770a
- + 1 <?xml version="1.0" encoding="utf-8"?> 2 <page> 3 <!-- Window to load/delete existing campaign runs--> 4 <include>common/modern/setup.xml</include> 5 <include>common/modern/styles.xml</include> 6 <include>common/modern/sprites.xml</include> 7 8 <include>common/setup.xml</include> 9 <include>common/sprites.xml</include> 10 <include>common/styles.xml</include> 11 12 <include>campaign/load.xml</include> 13 14 </page> -
new file inaries/data/mods/public/gui/page_newcampaign.xml
diff --git a/binaries/data/mods/public/gui/page_newcampaign.xml b/binaries/data/mods/public/gui/page_newcampaign.xml new file mode 100644 index 0000000..9ece28d
- + 1 <?xml version="1.0" encoding="utf-8"?> 2 <page> 3 <!-- Screen that pops up when you start a new campaign, asking you to name it. Will then create the file with the according name and start everything up. --> 4 <include>common/modern/setup.xml</include> 5 <include>common/modern/styles.xml</include> 6 <include>common/modern/sprites.xml</include> 7 8 <include>common/setup.xml</include> 9 <include>common/sprites.xml</include> 10 <include>common/styles.xml</include> 11 12 <include>campaign/newcampaign_modal.xml</include> 13 14 </page> -
binaries/data/mods/public/gui/pregame/mainmenu.js
diff --git a/binaries/data/mods/public/gui/pregame/mainmenu.js b/binaries/data/mods/public/gui/pregame/mainmenu.js index aa93c71..7706d73 100644
a b function init(initData, hotloadData) 35 35 guiObj.sprite = layerset[i].sprite; 36 36 guiObj.z = i; 37 37 } 38 39 // Enable campaign button if we have campaigns available 40 if (Object.keys(LoadAvailableCampaigns()).length !== 0) 41 { 42 Engine.GetGUIObjectByName("subMenuNewCampaignButton").enabled = true; 43 // TODO 44 // Engine.GetGUIObjectByName("subMenuLoadCampaignButton").enabled = true; 45 // Continue if it seems we would be able to. 46 if (canLoadCurrentCampaign()) 47 Engine.GetGUIObjectByName("subMenuContinueCampaignButton").enabled = true; 48 } 38 49 } 39 50 40 51 function getHotloadData() -
binaries/data/mods/public/gui/pregame/mainmenu.xml
diff --git a/binaries/data/mods/public/gui/pregame/mainmenu.xml b/binaries/data/mods/public/gui/pregame/mainmenu.xml index f040bd1..ce0d181 100644
a b 5 5 <script file="gui/common/functions_global_object.js"/> 6 6 <script file="gui/common/functions_utility_error.js"/> 7 7 <script file="gui/pregame/mainmenu.js"/> 8 <script file="gui/campaign/campaign.js"/> 9 <script file="gui/campaign/campaign_io.js"/> 8 10 <script directory="gui/pregame/backgrounds/"/> 9 11 10 12 … … 212 214 </action> 213 215 </object> 214 216 215 <object name="subMenu CampaignButton"217 <object name="subMenuLoadButton" 216 218 type="button" 217 219 style="StoneButtonFancy" 218 220 size="0 32 100% 60" 219 221 tooltip_style="pgToolTip" 220 enabled="false"221 222 > 222 <translatableAttribute id="caption"> Campaigns</translatableAttribute>223 <translatableAttribute id="tooltip"> Relive history through historical military campaigns. \[NOT YET IMPLEMENTED]</translatableAttribute>223 <translatableAttribute id="caption">Load Game</translatableAttribute> 224 <translatableAttribute id="tooltip">Click here to load a saved game.</translatableAttribute> 224 225 <action on="Press"> 225 closeMenu(); 226 <![CDATA[ 227 // Open Campaigns window. 228 // NOT IMPLEMENTED YET 229 ]]> 226 closeMenu(); 227 Engine.PushGuiPage("page_loadgame.xml", { type: "offline" }); 230 228 </action> 231 229 </object> 232 230 233 <object name="subMenu LoadButton"231 <object name="subMenuContinueCampaignButton" 234 232 type="button" 235 233 style="StoneButtonFancy" 236 234 size="0 64 100% 92" 237 235 tooltip_style="pgToolTip" 236 enabled="false" 238 237 > 239 <translatableAttribute id="caption">Load Game</translatableAttribute> 240 <translatableAttribute id="tooltip">Click here to load a saved game.</translatableAttribute> 238 <translatableAttribute id="caption">Continue Campaign</translatableAttribute> 239 <translatableAttribute id="tooltip">Click here to load your latest campaign.</translatableAttribute> 240 <action on="Press"> 241 loadCurrentCampaignSave(); 242 </action> 243 </object> 244 245 <object name="subMenuNewCampaignButton" 246 type="button" 247 style="StoneButtonFancy" 248 size="0 96 100% 124" 249 tooltip_style="pgToolTip" 250 enabled="false" 251 > 252 <translatableAttribute id="caption">New Campaign</translatableAttribute> 253 <translatableAttribute id="tooltip">Relive history through historical military campaigns. \[WIP]</translatableAttribute> 241 254 <action on="Press"> 242 255 closeMenu(); 243 Engine.PushGuiPage("page_ loadgame.xml", { type: "offline" });256 Engine.PushGuiPage("page_campaignsetup_simple.xml", { type: "offline" }); 244 257 </action> 245 258 </object> 246 259 260 <object name="subMenuLoadCampaignButton" 261 type="button" 262 style="StoneButtonFancy" 263 size="0 128 100% 156" 264 tooltip_style="pgToolTip" 265 > 266 <translatableAttribute id="caption">Load Campaign</translatableAttribute> 267 <translatableAttribute id="tooltip">Click here to resume an existing campaign.</translatableAttribute> 268 <action on="Press"> 269 closeMenu(); 270 Engine.PushGuiPage("page_loadcampaign.xml", { type: "offline" }); 271 </action> 272 </object> 247 273 </object> 248 274 249 275 <!-- submenuMultiplayer --> … … 468 494 <translatableAttribute id="tooltip">Challenge the computer player to a single player match.</translatableAttribute> 469 495 <action on="Press"> 470 496 closeMenu(); 471 openMenu("submenuSinglePlayer", (this.parent.size.top+this.size.top), (this.size.bottom-this.size.top), 3);497 openMenu("submenuSinglePlayer", (this.parent.size.top+this.size.top), (this.size.bottom-this.size.top), 5); 472 498 </action> 473 499 </object> 474 500 -
binaries/data/mods/public/gui/session/session.js
diff --git a/binaries/data/mods/public/gui/session/session.js b/binaries/data/mods/public/gui/session/session.js index ada8705..d7ff7b8 100644
a b function leaveGame(willRejoin) 581 581 let replayDirectory = Engine.GetCurrentReplayDirectory(); 582 582 let simData = getReplayMetadata(); 583 583 584 let campaignData = null; 585 if (Engine.GetInitAttributes().campaignData) 586 { 587 campaignData = Engine.GetInitAttributes().campaignData; 588 campaignData.endGameData = Engine.GuiInterfaceCall("GetEndGameCampaignData"); 589 campaignGameEnded(campaignData); 590 } 591 584 592 Engine.EndGame(); 585 593 586 594 if (g_IsController && Engine.HasXmppClient()) … … function leaveGame(willRejoin) 594 602 "isReplay": g_IsReplay, 595 603 "replayDirectory": !g_HasRejoined && replayDirectory, 596 604 "replaySelectionData": g_ReplaySelectionData 597 } 605 }, 606 "campaignData" : campaignData 598 607 }); 599 608 } 600 609 -
binaries/data/mods/public/gui/session/session.xml
diff --git a/binaries/data/mods/public/gui/session/session.xml b/binaries/data/mods/public/gui/session/session.xml index 510d4c6..871633f 100644
a b 15 15 <script file="gui/common/timer.js"/> 16 16 <script file="gui/common/tooltips.js"/> 17 17 18 <!-- If this was a campaign game, we need to tell the campaign --> 19 <script file="gui/campaign/campaign.js"/> 20 <script file="gui/campaign/campaign_io.js"/> 21 18 22 <script directory="gui/session/"/> 19 23 20 24 <object name="sn" hotkey="session.gui.toggle"> -
binaries/data/mods/public/gui/summary/summary.js
diff --git a/binaries/data/mods/public/gui/summary/summary.js b/binaries/data/mods/public/gui/summary/summary.js index c3cf891..9c2b869 100644
a b function continueButton() 170 170 }); 171 171 else if (Engine.HasXmppClient()) 172 172 Engine.SwitchGuiPage("page_lobby.xml"); 173 else if (g_GameData.campaignData) 174 loadCurrentCampaignSave(); 173 175 else 174 176 Engine.SwitchGuiPage("page_pregame.xml"); 175 177 } -
binaries/data/mods/public/gui/summary/summary.xml
diff --git a/binaries/data/mods/public/gui/summary/summary.xml b/binaries/data/mods/public/gui/summary/summary.xml index 137d1c4..72f28c6 100644
a b 13 13 <!-- Needs the colors from summary.js --> 14 14 <script file="gui/summary/layout.js"/> 15 15 16 <!-- Load the campaign screen if this was a campaign --> 17 <script file="gui/campaign/campaign.js"/> 18 <script file="gui/campaign/campaign_io.js"/> 19 16 20 <object name="summaryWindow" 17 21 type="image" 18 22 style="ModernWindow" -
binaries/data/mods/public/simulation/components/GuiInterface.js
diff --git a/binaries/data/mods/public/simulation/components/GuiInterface.js b/binaries/data/mods/public/simulation/components/GuiInterface.js index 932c8cd..1cacbe0 100644
a b GuiInterface.prototype.GetExtendedSimulationState = function() 199 199 return ret; 200 200 }; 201 201 202 GuiInterface.prototype.GetEndGameCampaignData = function(player) 203 { 204 // TODO here: return some relevant information, possibly based on initial attributes + some trigger data? 205 206 // return whether player lost or won at the very least. 207 let cmpPlayer = QueryPlayerIDInterface(player, IID_Player); 208 let status = cmpPlayer.GetState(); 209 210 return {"status" : status}; 211 } 212 202 213 GuiInterface.prototype.GetRenamedEntities = function(player) 203 214 { 204 215 if (this.miragedEntities[player]) … … let exposedFunctions = { 1968 1979 1969 1980 "GetSimulationState": 1, 1970 1981 "GetExtendedSimulationState": 1, 1982 "GetEndGameCampaignData": 1, 1971 1983 "GetRenamedEntities": 1, 1972 1984 "ClearRenamedEntities": 1, 1973 1985 "GetEntityState": 1, -
source/gui/scripting/ScriptFunctions.cpp
diff --git a/source/gui/scripting/ScriptFunctions.cpp b/source/gui/scripting/ScriptFunctions.cpp index a366c90..baf3382 100644
a b 39 39 #include "network/NetClient.h" 40 40 #include "network/NetServer.h" 41 41 #include "network/NetTurnManager.h" 42 #include "ps/Campaigns.h" 42 43 #include "ps/CConsole.h" 43 44 #include "ps/CLogger.h" 44 45 #include "ps/Errors.h" … … JS::Value GetEngineInfo(ScriptInterface::CxPrivate* pCxPrivate) 250 251 return SavedGames::GetEngineInfo(*(pCxPrivate->pScriptInterface)); 251 252 } 252 253 254 void SaveCampaign(ScriptInterface::CxPrivate* pCxPrivate, const std::wstring& filename, JS::HandleValue metadata) 255 { 256 shared_ptr<ScriptInterface::StructuredClone> metadataClone = pCxPrivate->pScriptInterface->WriteStructuredClone(metadata); 257 if (Campaigns::Save(*(pCxPrivate->pScriptInterface), filename, metadataClone) < 0) 258 LOGERROR("Failed to save campaign state"); 259 } 260 261 JS::Value LoadCampaign(ScriptInterface::CxPrivate* pCxPrivate, const std::wstring& campaignName) 262 { 263 JSContext* cx = pCxPrivate->pScriptInterface->GetContext(); 264 JSAutoRequest rq(cx); 265 266 JS::RootedValue campaignData(cx); 267 Status err = Campaigns::Load(*(pCxPrivate->pScriptInterface), campaignName, &campaignData); 268 if (err < 0) 269 return JS::UndefinedValue(); 270 271 return campaignData; 272 } 273 274 bool DeleteCampaignGame(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), const std::wstring& name) 275 { 276 return Campaigns::DeleteGame(name); 277 } 278 253 279 void StartNetworkGame(ScriptInterface::CxPrivate* UNUSED(pCxPrivate)) 254 280 { 255 281 ENSURE(g_NetClient); … … void SaveGamePrefix(ScriptInterface::CxPrivate* pCxPrivate, const std::wstring& 334 360 LOGERROR("Failed to save game"); 335 361 } 336 362 363 JS::Value GetSavedGames(ScriptInterface::CxPrivate* pCxPrivate) 364 { 365 return SavedGames::GetSavedGames(*(pCxPrivate->pScriptInterface)); 366 } 367 368 bool DeleteSavedGame(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), const std::wstring& name) 369 { 370 return SavedGames::DeleteSavedGame(name); 371 } 372 337 373 void SetNetworkGameAttributes(ScriptInterface::CxPrivate* pCxPrivate, JS::HandleValue attribs1) 338 374 { 339 375 ENSURE(g_NetClient); … … JS::Value GetAIs(ScriptInterface::CxPrivate* pCxPrivate) 465 501 return ICmpAIManager::GetAIs(*(pCxPrivate->pScriptInterface)); 466 502 } 467 503 468 JS::Value GetSavedGames(ScriptInterface::CxPrivate* pCxPrivate)469 {470 return SavedGames::GetSavedGames(*(pCxPrivate->pScriptInterface));471 }472 473 bool DeleteSavedGame(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), const std::wstring& name)474 {475 return SavedGames::DeleteSavedGame(name);476 }477 478 504 void OpenURL(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), const std::string& url) 479 505 { 480 506 sys_open_url(url); … … void GuiScriptingInit(ScriptInterface& scriptInterface) 1054 1080 scriptInterface.RegisterFunction<JS::Value, &GetAIs>("GetAIs"); 1055 1081 scriptInterface.RegisterFunction<JS::Value, &GetEngineInfo>("GetEngineInfo"); 1056 1082 1083 // Campaigns 1084 scriptInterface.RegisterFunction<void, std::wstring, JS::HandleValue, &SaveCampaign>("SaveCampaign"); 1085 scriptInterface.RegisterFunction<JS::Value, std::wstring, &LoadCampaign>("LoadCampaign"); 1086 scriptInterface.RegisterFunction<bool, std::wstring, &DeleteCampaignGame>("DeleteCampaignGame"); 1087 1057 1088 // Saved games 1058 1089 scriptInterface.RegisterFunction<JS::Value, std::wstring, &StartSavedGame>("StartSavedGame"); 1059 1090 scriptInterface.RegisterFunction<JS::Value, &GetSavedGames>("GetSavedGames"); -
new file source/ps/Campaigns.cpp
diff --git a/source/ps/Campaigns.cpp b/source/ps/Campaigns.cpp new file mode 100644 index 0000000..397e28c
- + 1 /* Copyright (C) 2016 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 "Campaigns.h" 21 22 #include "graphics/GameView.h" 23 #include "gui/GUIManager.h" 24 #include "lib/allocators/shared_ptr.h" 25 #include "i18n/L10n.h" 26 #include "lib/utf8.h" 27 #include "ps/CLogger.h" 28 #include "ps/FileIo.h" 29 #include "ps/Filesystem.h" 30 #include "ps/Game.h" 31 #include "ps/Mod.h" 32 #include "ps/Pyrogenesis.h" 33 #include "ps/XML/Xeromyces.h" 34 #include "scriptinterface/ScriptInterface.h" 35 #include "simulation2/Simulation2.h" 36 37 static const int CAMPAIGN_VERSION = 1; // increment on incompatible changes to the format 38 39 // TODO: we ought to check version numbers when loading files 40 41 Status Campaigns::Save(ScriptInterface& scriptInterface, const CStrW& name, const shared_ptr<ScriptInterface::StructuredClone>& metadataClone) 42 { 43 JSContext* cx = scriptInterface.GetContext(); 44 JSAutoRequest rq(cx); 45 46 // Determine the filename to save under 47 const VfsPath basenameFormat(L"campaignsaves/" + name); 48 const VfsPath filename = basenameFormat.ChangeExtension(L".0adcampaign"); 49 50 time_t now = time(NULL); 51 52 JS::RootedValue metadata(cx); 53 scriptInterface.Eval("({})", &metadata); 54 scriptInterface.SetProperty(metadata, "engine_version", std::string(engine_version)); 55 scriptInterface.SetProperty(metadata, "mods", g_modsLoaded); 56 scriptInterface.SetProperty(metadata, "time", (double)now); 57 58 JS::RootedValue campaignState(cx); 59 scriptInterface.ReadStructuredClone(metadataClone, &campaignState); 60 61 scriptInterface.SetProperty(metadata, "campaign_state", campaignState); 62 63 std::string dataString = scriptInterface.StringifyJSON(&metadata, true); 64 65 // ensure we won't crash if the save somehow fails. 66 try 67 { 68 CFilePacker packer(CAMPAIGN_VERSION, "CSST"); 69 packer.PackString(dataString); 70 packer.Write(filename); 71 } 72 catch (PSERROR_File_WriteFailed&) 73 { 74 LOGERROR("Failed to write campaign '%s'", filename.string8()); 75 return ERR::FAIL; 76 } 77 78 LOGMESSAGERENDER(g_L10n.Translate("Saved campaign to '%s'"), filename.string8()); 79 debug_printf("Saved campaign to '%s'\n", filename.string8().c_str()); 80 81 return INFO::OK; 82 } 83 84 Status Campaigns::Load(ScriptInterface& scriptInterface, const std::wstring& name, JS::MutableHandleValue campaignData) 85 { 86 // Determine the filename to load 87 const VfsPath basename(L"campaignsaves/" + name); 88 const VfsPath filename = basename.ChangeExtension(L".0adcampaign"); 89 90 if (!VfsFileExists(filename)) 91 return ERR::FILE_NOT_FOUND; 92 93 CFileUnpacker unpacker; 94 unpacker.Read(filename, "CSST"); 95 96 if (unpacker.GetVersion() < CAMPAIGN_VERSION) 97 { 98 LOGWARNING("Campaign file is too old, version %i, current version %i", unpacker.GetVersion(), CAMPAIGN_VERSION); 99 return ERR::FAIL; 100 } 101 102 CStr datastream; 103 unpacker.UnpackString(datastream); 104 105 106 if (!scriptInterface.ParseJSON(datastream, campaignData)) 107 { 108 LOGERROR("Error parsing campaign JSON."); 109 return ERR::FAIL; 110 } 111 112 return INFO::OK; 113 } 114 115 bool Campaigns::DeleteGame(const std::wstring& name) 116 { 117 const VfsPath basename(L"campaignsaves/" + name); 118 const VfsPath filename = basename.ChangeExtension(L".0adcampaign"); 119 OsPath realpath; 120 121 // Make sure it exists in VFS and find its real path 122 if (!VfsFileExists(filename) || g_VFS->GetRealPath(filename, realpath) != INFO::OK) 123 return false; // Error 124 125 // Remove from VFS 126 if (g_VFS->RemoveFile(filename) != INFO::OK) 127 return false; // Error 128 129 // Delete actual file 130 if (wunlink(realpath) != 0) 131 return false; // Error 132 133 // Successfully deleted file 134 return true; 135 } 136 -
new file source/ps/Campaigns.h
diff --git a/source/ps/Campaigns.h b/source/ps/Campaigns.h new file mode 100644 index 0000000..8e0f67a
- + 1 /* Copyright (C) 2016 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_CAMPAIGNS 19 #define INCLUDED_CAMPAIGNS 20 21 #include "scriptinterface/ScriptInterface.h" 22 class CSimulation2; 23 class CGUIManager; 24 25 /** 26 * @file 27 * Contains functions for managing campaign archives. 28 * 29 * A saved game is a simple *.0adcampaign file 30 * which is a binary JSON file containing the campaign metadata 31 */ 32 33 namespace Campaigns 34 { 35 36 /** 37 * Create new campaign file with given name and metadata 38 * 39 * @param scriptInterface 40 * @param name Name of the campaign 41 * @param MetadataClone Actual campaign Metadata 42 * @return INFO::OK if successfully saved, else an error Status 43 */ 44 Status Save(ScriptInterface& scriptInterface, const CStrW& name, const shared_ptr<ScriptInterface::StructuredClone>& metadataClone); 45 46 /** 47 * Load campaign with the given name 48 * 49 * @param scriptInterface 50 * @param name filename of campaign game (without path or extension) 51 * @param[out] metadata object containing metadata associated with saved game, 52 * parsed from metadata.json inside the archive. 53 * @return INFO::OK if successfully loaded, else an error Status 54 */ 55 Status Load(ScriptInterface& scriptInterface, const std::wstring& name, JS::MutableHandleValue campaignData); 56 57 /** 58 * Permanently deletes the saved campaign run with the given name 59 * 60 * @param name filename of saved campaign (without path or extension) 61 * @return true if deletion was successful, or false on error 62 */ 63 bool DeleteGame(const std::wstring& name); 64 } 65 66 #endif // INCLUDED_CAMPAIGNS -
source/ps/GameSetup/GameSetup.cpp
diff --git a/source/ps/GameSetup/GameSetup.cpp b/source/ps/GameSetup/GameSetup.cpp index b834b63..b789a4a 100644
a b static void InitVfs(const CmdLineArgs& args, int flags) 499 499 // We mount these dirs last as otherwise writing could result in files being placed in a mod's dir. 500 500 g_VFS->Mount(L"screenshots/", paths.UserData()/"screenshots"/""); 501 501 g_VFS->Mount(L"saves/", paths.UserData()/"saves"/"", VFS_MOUNT_WATCH); 502 g_VFS->Mount(L"campaignsaves/", paths.UserData()/"campaignsaves"/""); 503 502 504 // Mounting with highest priority, so that a mod supplied user.cfg is harmless 503 505 g_VFS->Mount(L"config/", readonlyConfig, 0, (size_t)-1); 504 506 if(readonlyConfig != paths.Config())