diff --git a/binaries/data/mods/public/gui/common/functions_utility_loadsave.js b/binaries/data/mods/public/gui/common/functions_utility_loadsave.js index 769a667dba..cd6ddb451a 100644 --- a/binaries/data/mods/public/gui/common/functions_utility_loadsave.js +++ b/binaries/data/mods/public/gui/common/functions_utility_loadsave.js @@ -8,11 +8,48 @@ function twoDigits(n) return n < 10 ? "0" + n : n; } -function generateLabel(metadata) +function generateLabel(metadata, engineInfo) { var t = new Date(metadata.time*1000); - var date = t.getFullYear()+"-"+twoDigits(1+t.getMonth())+"-"+twoDigits(t.getDate()); var time = twoDigits(t.getHours())+":"+twoDigits(t.getMinutes())+":"+twoDigits(t.getSeconds()); - return "["+date+" "+time+"] "+metadata.initAttributes.map+(metadata.description ? " - "+metadata.description : ""); + var label = "["+date+" "+time+"] "; + + if (engineInfo) + { + if (!hasSameVersion(metadata, engineInfo)) + label = "[color=\"red\"]" + label + "[/color]"; + else if (!hasSameMods(metadata, engineInfo)) + label = "[color=\"orange\"]" + label + "[/color]"; + } + + label += metadata.initAttributes.map.replace("maps/","") + + (metadata.description ? " - "+metadata.description : ""); + return label; +} + +/** + * Check the version compatibility between the saved game to be loaded and the engine + */ +function hasSameVersion(metadata, engineInfo) +{ + return (metadata.version_major == engineInfo.version_major); +} + +/** + * Check the mod compatibility between the saved game to be loaded and the engine + */ +function hasSameMods(metadata, engineInfo) +{ + if (!metadata.mods) // only here for backwards compatibility with previous saved games + var gameMods = []; + else + var gameMods = metadata.mods; + + if (gameMods.length != engineInfo.mods.length) + return false; + for (var i = 0; i < gameMods.length; ++i) + if (gameMods[i] != engineInfo.mods[i]) + return false; + return true; } diff --git a/binaries/data/mods/public/gui/savedgames/load.js b/binaries/data/mods/public/gui/savedgames/load.js index 3d1d1f1296..85ba3a91cf 100644 --- a/binaries/data/mods/public/gui/savedgames/load.js +++ b/binaries/data/mods/public/gui/savedgames/load.js @@ -1,38 +1,74 @@ +var gameMetadatas = []; + function init() { var gameSelection = Engine.GetGUIObjectByName("gameSelection"); var savedGames = Engine.GetSavedGames(); - if (savedGames.length == 0) - { - gameSelection.list = [ "No saved games found" ]; - gameSelection.selected = 0; - Engine.GetGUIObjectByName("loadGameButton").enabled = false; - Engine.GetGUIObjectByName("deleteGameButton").enabled = false; - return; - } + + // get current game version and loaded mods + var engineInfo = Engine.GetEngineInfo(); savedGames.sort(sortDecreasingDate); - var gameListIDs = [ game.id for each (game in savedGames) ]; - var gameListLabels = [ generateLabel(game.metadata) for each (game in savedGames) ]; + var gameListIds = [ game.id for each (game in savedGames) ]; + var gameListLabels = [ generateLabel(game.metadata, engineInfo) for each (game in savedGames) ]; + gameMetadatas = [ game.metadata for each (game in savedGames) ]; gameSelection.list = gameListLabels; - gameSelection.list_data = gameListIDs; - gameSelection.selected = 0; + gameSelection.list_data = gameListIds; + if (gameSelection.selected == -1) + gameSelection.selected = 0; } function loadGame() { var gameSelection = Engine.GetGUIObjectByName("gameSelection"); - var gameID = gameSelection.list_data[gameSelection.selected]; + var gameId = gameSelection.list_data[gameSelection.selected]; + var gameLabel = gameSelection.list[gameSelection.selected]; + var metadata = gameMetadatas[gameSelection.selected]; - var metadata = Engine.StartSavedGame(gameID); + // check game compatibility before really loading it + var engineInfo = Engine.GetEngineInfo(); + if (!hasSameVersion(metadata, engineInfo) || !hasSameMods(metadata, engineInfo)) + { + // version not compatible ... ask for confirmation + var btCaptions = ["Yes", "No"]; + var btCode = [function(){ reallyLoadGame(gameId); }, init]; + var message = "This saved game may not be compatible:"; + if (!hasSameVersion(metadata, engineInfo)) + message += "\nIt needs 0AD version " + metadata.version_major + + " while you are running version " + engineInfo.version_major + "."; + + if (!hasSameMods(metadata, engineInfo)) + { + if (!metadata.mods) // only for backwards compatibility with previous saved games + metadata.mods = []; + if (metadata.mods.length == 0) + message += "\nIt does not need any mod" + + " while you are running with \"" + engineInfo.mods.join() + "\"."; + else if (engineInfo.mods.length == 0) + message += "\nIt needs the mod \"" + metadata.mods.join() + "\"" + + " while you are running without mod."; + else + message += "\nIt needs the mod \"" + metadata.mods.join() + "\"" + + " while you are running with \"" + engineInfo.mods.join() + "\"."; + } + message += "\nDo you still want to proceed ?"; + messageBox(500, 250, message, "Warning", 0, btCaptions, btCode); + } + else + reallyLoadGame(gameId); +} + +function reallyLoadGame(gameId) +{ + var metadata = Engine.StartSavedGame(gameId); if (!metadata) { // Probably the file wasn't found // Show error and refresh saved game list - error("Could not load saved game '"+gameID+"'"); + error("Could not load saved game '"+gameId+"'"); init(); } else diff --git a/source/gui/scripting/ScriptFunctions.cpp b/source/gui/scripting/ScriptFunctions.cpp index 6f60277a85..9a2c42eb66 100644 --- a/source/gui/scripting/ScriptFunctions.cpp +++ b/source/gui/scripting/ScriptFunctions.cpp @@ -194,6 +194,11 @@ void SetPlayerID(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), int id) g_Game->SetPlayerID(id); } +CScriptValRooted GetEngineInfo(ScriptInterface::CxPrivate* pCxPrivate) +{ + return SavedGames::GetEngineInfo(*(pCxPrivate->pScriptInterface)); +} + void StartNetworkGame(ScriptInterface::CxPrivate* UNUSED(pCxPrivate)) { ENSURE(g_NetServer); @@ -822,6 +827,7 @@ void GuiScriptingInit(ScriptInterface& scriptInterface) scriptInterface.RegisterFunction("AssignNetworkPlayer"); scriptInterface.RegisterFunction("SendNetworkChat"); scriptInterface.RegisterFunction, &GetAIs>("GetAIs"); + scriptInterface.RegisterFunction("GetEngineInfo"); // Saved games scriptInterface.RegisterFunction("StartSavedGame"); diff --git a/source/ps/GameSetup/GameSetup.cpp b/source/ps/GameSetup/GameSetup.cpp index bdb00e10c2..ad2f8cf8f1 100644 --- a/source/ps/GameSetup/GameSetup.cpp +++ b/source/ps/GameSetup/GameSetup.cpp @@ -69,6 +69,7 @@ #include "ps/ProfileViewer.h" #include "ps/Profiler2.h" #include "ps/Pyrogenesis.h" // psSetLogDir +#include "ps/SavedGame.h" #include "ps/scripting/JSInterface_Console.h" #include "ps/TouchInput.h" #include "ps/UserReport.h" @@ -391,6 +392,10 @@ ErrorReactionInternal psDisplayError(const wchar_t* UNUSED(text), size_t UNUSED( static std::vector GetMods(const CmdLineArgs& args, bool dev) { std::vector mods = args.GetMultiple("mod"); + // List of the mods, to be used by the Gui + g_modsLoaded.clear(); + for (size_t i = 0; i < mods.size(); ++i) + g_modsLoaded.push_back((std::string)mods[i]); // TODO: It would be nice to remove this hard-coding mods.insert(mods.begin(), "public"); diff --git a/source/ps/SavedGame.cpp b/source/ps/SavedGame.cpp index 180ba9d0db..ab6a4d4996 100644 --- a/source/ps/SavedGame.cpp +++ b/source/ps/SavedGame.cpp @@ -29,6 +29,8 @@ static const int SAVED_GAME_VERSION_MAJOR = 1; // increment on incompatible changes to the format static const int SAVED_GAME_VERSION_MINOR = 0; // increment on compatible changes to the format +std::vector g_modsLoaded; // list of mods loaded + // TODO: we ought to check version numbers when loading files @@ -76,6 +78,7 @@ Status SavedGames::Save(const std::wstring& name, const std::wstring& descriptio simulation.GetScriptInterface().Eval("({})", metadata); simulation.GetScriptInterface().SetProperty(metadata.get(), "version_major", SAVED_GAME_VERSION_MAJOR); simulation.GetScriptInterface().SetProperty(metadata.get(), "version_minor", SAVED_GAME_VERSION_MINOR); + simulation.GetScriptInterface().SetProperty(metadata.get(), "mods", g_modsLoaded); simulation.GetScriptInterface().SetProperty(metadata.get(), "time", (double)now); simulation.GetScriptInterface().SetProperty(metadata.get(), "player", playerID); simulation.GetScriptInterface().SetProperty(metadata.get(), "initAttributes", simulation.GetInitAttributes()); @@ -241,3 +244,14 @@ bool SavedGames::DeleteSavedGame(const std::wstring& name) // Successfully deleted file return true; } + +CScriptValRooted SavedGames::GetEngineInfo(ScriptInterface& scriptInterface) +{ + CScriptValRooted metainfo; + scriptInterface.Eval("({})", metainfo); + scriptInterface.SetProperty(metainfo.get(), "version_major", SAVED_GAME_VERSION_MAJOR); + scriptInterface.SetProperty(metainfo.get(), "version_minor", SAVED_GAME_VERSION_MINOR); + scriptInterface.SetProperty(metainfo.get(), "mods" , g_modsLoaded); + return metainfo; +} + diff --git a/source/ps/SavedGame.h b/source/ps/SavedGame.h index e64673a3f6..29f5254ee1 100644 --- a/source/ps/SavedGame.h +++ b/source/ps/SavedGame.h @@ -92,6 +92,17 @@ std::vector GetSavedGames(ScriptInterface& scriptInterface); */ bool DeleteSavedGame(const std::wstring& name); +/** + * Gets info (version and mods loaded) on the running engine + * + * @param scriptInterface the ScriptInterface in which to create the return data. + * @return list of objects containing saved game data + */ +CScriptValRooted GetEngineInfo(ScriptInterface& scriptInterface); + } +// list of mods currently loaded +extern std::vector g_modsLoaded; + #endif // INCLUDED_SAVEDGAME