Index: binaries/data/mods/mod/gui/common/mod.js =================================================================== --- /dev/null +++ binaries/data/mods/mod/gui/common/mod.js @@ -0,0 +1,41 @@ +/** + * Check the mod compatibility between the saved game to be loaded and the engine + */ +function hasSameMods(metadata, engineInfo) +{ + if (!metadata.mods || !engineInfo.mods) + return false; + + // Ignore the "user" mod which is loaded for releases but not working-copies + let modsA = metadata.mods.filter(mod => mod[0] != "user"); + let modsB = engineInfo.mods.filter(mod => mod[0] != "user"); + + if (modsA.length != modsB.length) + return false; + + // Mods must be loaded in the same order. 0: modname, 1: modversion + return modsA.every((mod, index) => [0, 1].every(i => mod[i] == modsB[index][i])); +} + +/** + * Convert the required and active mods and their version into a humanreadable translated string. + * + * @param required {string[]} - Required mods. + * @param active {string[]} - Active mods. + * @returns {string} + */ +function comparedModsString(required, active) +{ + let modDataToString = (mods) => + mods.map(mod => mod[1] === "" ? mod[0] : + sprintf(translateWithContext("Mod comparison", "%(mod)s (%(version)s)"), { + "mod": mod[0], + "version": mod[1] + })).join(translate(", ")); + + return sprintf(translateWithContext("Mod comparison", "Required: %(mods)s"), + { "mods": modDataToString(required) }) + "\n" + + sprintf(translateWithContext("Mod comparison", "Active: %(mods)s"), + { "mods": modDataToString(active) } + ); +} Index: binaries/data/mods/public/gui/common/functions_utility_loadsave.js =================================================================== --- binaries/data/mods/public/gui/common/functions_utility_loadsave.js +++ binaries/data/mods/public/gui/common/functions_utility_loadsave.js @@ -37,29 +37,6 @@ return metadata.engine_version && metadata.engine_version == engineInfo.engine_version; } -/** - * Check the mod compatibility between the saved game to be loaded and the engine - * - * @param metadata {string[]} - * @param engineInfo {string[]} - * @returns {boolean} - */ -function hasSameMods(metadata, engineInfo) -{ - if (!metadata.mods || !engineInfo.mods) - return false; - - // Ignore the "user" mod which is loaded for releases but not working-copies - let modsA = metadata.mods.filter(mod => mod != "user"); - let modsB = engineInfo.mods.filter(mod => mod != "user"); - - if (modsA.length != modsB.length) - return false; - - // Mods must be loaded in the same order - return modsA.every((mod, index) => mod == modsB[index]); -} - function deleteGame() { let gameSelection = Engine.GetGUIObjectByName("gameSelection"); Index: binaries/data/mods/public/gui/loadgame/load.js =================================================================== --- binaries/data/mods/public/gui/loadgame/load.js +++ binaries/data/mods/public/gui/loadgame/load.js @@ -160,12 +160,7 @@ metadata.mods = []; message += translate("The savegame needs a different set of mods:") + "\n" + - sprintf(translate("Required: %(mods)s"), { - "mods": metadata.mods.join(translate(", ")) - }) + "\n" + - sprintf(translate("Active: %(mods)s"), { - "mods": engineInfo.mods.join(translate(", ")) - }); + comparedModsString(metadata.mods, engineInfo.mods); } message += "\n" + translate("Do you still want to proceed?"); Index: source/ps/Mod.h =================================================================== --- source/ps/Mod.h +++ source/ps/Mod.h @@ -28,5 +28,6 @@ namespace Mod { JS::Value GetAvailableMods(const ScriptInterface& scriptInterface); + JS::Value GetLoadedModsWithVersions(const ScriptInterface& scriptInterface); } #endif // INCLUDED_MOD Index: source/ps/Mod.cpp =================================================================== --- source/ps/Mod.cpp +++ source/ps/Mod.cpp @@ -99,3 +99,23 @@ return JS::ObjectValue(*obj); } + +JS::Value Mod::GetLoadedModsWithVersions(const ScriptInterface& scriptInterface) +{ + JSContext* cx = scriptInterface.GetContext(); + JSAutoRequest rq(cx); + + JS::RootedValue availableMods(cx, GetAvailableMods(scriptInterface)); + + JS::RootedValue ret(cx); + scriptInterface.Eval("([])", &ret); + for (size_t i = 0; i < g_modsLoaded.size(); ++i) + { + CStr version; + JS::RootedValue modData(cx); + if (scriptInterface.GetProperty(availableMods, g_modsLoaded[i].c_str(), &modData)) + scriptInterface.GetProperty(modData, "version", version); + scriptInterface.SetPropertyInt(ret, i, std::vector{g_modsLoaded[i], version}); + } + return ret; +} Index: source/ps/Replay.cpp =================================================================== --- source/ps/Replay.cpp +++ source/ps/Replay.cpp @@ -31,6 +31,7 @@ #include "ps/Profile.h" #include "ps/ProfileViewer.h" #include "ps/Pyrogenesis.h" +#include "ps/Mod.h" #include "ps/Util.h" #include "ps/VisualReplay.h" #include "scriptinterface/ScriptInterface.h" @@ -53,12 +54,15 @@ void CReplayLogger::StartGame(JS::MutableHandleValue attribs) { + JSContext* cx = m_ScriptInterface.GetContext(); + JSAutoRequest rq(cx); + // Add timestamp, since the file-modification-date can change m_ScriptInterface.SetProperty(attribs, "timestamp", (double)std::time(nullptr)); // Add engine version and currently loaded mods for sanity checks when replaying m_ScriptInterface.SetProperty(attribs, "engine_version", CStr(engine_version)); - m_ScriptInterface.SetProperty(attribs, "mods", g_modsLoaded); + m_ScriptInterface.SetProperty(attribs, "mods", JS::RootedValue(cx, Mod::GetLoadedModsWithVersions(m_ScriptInterface))); m_Directory = createDateIndexSubdirectory(VisualReplay::GetDirectoryName()); debug_printf("Writing replay to %s\n", m_Directory.string8().c_str()); Index: source/ps/SavedGame.cpp =================================================================== --- source/ps/SavedGame.cpp +++ source/ps/SavedGame.cpp @@ -82,7 +82,7 @@ JS::RootedValue initAttributes(cx, simulation.GetInitAttributes()); simulation.GetScriptInterface().Eval("({})", &metadata); simulation.GetScriptInterface().SetProperty(metadata, "engine_version", std::string(engine_version)); - simulation.GetScriptInterface().SetProperty(metadata, "mods", g_modsLoaded); + simulation.GetScriptInterface().SetProperty(metadata, "mods", JS::RootedValue(cx, Mod::GetLoadedModsWithVersions(simulation.GetScriptInterface()))); simulation.GetScriptInterface().SetProperty(metadata, "time", (double)now); simulation.GetScriptInterface().SetProperty(metadata, "playerID", g_Game->GetPlayerID()); simulation.GetScriptInterface().SetProperty(metadata, "initAttributes", initAttributes); @@ -296,7 +296,7 @@ JS::RootedValue metainfo(cx); scriptInterface.Eval("({})", &metainfo); scriptInterface.SetProperty(metainfo, "engine_version", std::string(engine_version)); - scriptInterface.SetProperty(metainfo, "mods", g_modsLoaded); + scriptInterface.SetProperty(metainfo, "mods", JS::RootedValue(cx, Mod::GetLoadedModsWithVersions(scriptInterface))); scriptInterface.FreezeObject(metainfo, true);