Index: binaries/data/mods/mod/gui/common/mod.js =================================================================== --- /dev/null +++ binaries/data/mods/mod/gui/common/mod.js @@ -0,0 +1,44 @@ +/** + * 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[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 + return modsA.every((mod, index) => [0, 1].every(i => mod[i] == modsB[index][i])); +} + +function modDataToString(mods) +{ + return mods.map(mod => + sprintf(translate("%(mod)s (%(version)s)"), { + "mod": mod[0], + "version": mod[1] + })).join(translate(", ")); +} + +/** + * 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) +{ + return sprintf(translate("Required: %(mods)s"), { "mods": modDataToString(required) }) + + "\n" + sprintf(translate("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 @@ -46,29 +46,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 @@ -167,12 +167,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,25 @@ return JS::ObjectValue(*obj); } + +JS::Value Mod::GetLoadedModsWithVersions(const ScriptInterface& scriptInterface) +{ + JSContext* cx = scriptInterface.GetContext(); + JSAutoRequest rq(cx); + + JS::RootedValue availableMods(cx, GetAvailableMods(scriptInterface)); + + //std::vector modVersions; + //JS::RootedObject ret(cx, JS_NewArrayObject(cx, 0)); + JS::RootedValue ret(cx); + scriptInterface.Eval("([])", &ret); + for (u32 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 @@ -88,7 +88,7 @@ simulation.GetScriptInterface().SetProperty(metadata, "version_major", SAVED_GAME_VERSION_MAJOR); simulation.GetScriptInterface().SetProperty(metadata, "version_minor", SAVED_GAME_VERSION_MINOR); 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); @@ -304,7 +304,7 @@ scriptInterface.SetProperty(metainfo, "version_major", SAVED_GAME_VERSION_MAJOR); scriptInterface.SetProperty(metainfo, "version_minor", SAVED_GAME_VERSION_MINOR); 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);