mirror of
https://gitea.wildfiregames.com/0ad/0ad
synced 2026-06-16 05:13:58 -07:00
Check for and save mod version for savegames and replays
Reviewed by: elexis Fixes: #4887 Differential Revision: https://code.wildfiregames.com/D955 This was SVN commit r21239.
This commit is contained in:
parent
246d57a81d
commit
d5807cd59f
9 changed files with 83 additions and 50 deletions
32
binaries/data/mods/mod/gui/common/mod.js
Normal file
32
binaries/data/mods/mod/gui/common/mod.js
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
/**
|
||||
* Check the mod compatibility between the saved game to be loaded and the engine
|
||||
*/
|
||||
function hasSameMods(modsA, modsB)
|
||||
{
|
||||
if (!modsA || !modsB || 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]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a list of mods and their version into a human-readable string.
|
||||
*/
|
||||
function modsToString(mods)
|
||||
{
|
||||
return mods.map(mod => sprintf(translateWithContext("Mod comparison", "%(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.
|
||||
*/
|
||||
function comparedModsString(required, active)
|
||||
{
|
||||
return sprintf(translateWithContext("Mod comparison", "Required: %(mods)s"),
|
||||
{ "mods": modsToString(required) }) + "\n" +
|
||||
sprintf(translateWithContext("Mod comparison", "Active: %(mods)s"),
|
||||
{ "mods": modsToString(active) });
|
||||
}
|
||||
|
|
@ -5,7 +5,7 @@ function sortDecreasingDate(a, b)
|
|||
|
||||
function isCompatibleSavegame(metadata, engineInfo)
|
||||
{
|
||||
return engineInfo && hasSameEngineVersion(metadata, engineInfo) & hasSameMods(metadata, engineInfo);
|
||||
return engineInfo && hasSameEngineVersion(metadata, engineInfo) & hasSameMods(metadata.mods, engineInfo.mods);
|
||||
}
|
||||
|
||||
function generateSavegameDateString(metadata, engineInfo)
|
||||
|
|
@ -37,29 +37,6 @@ function hasSameEngineVersion(metadata, engineInfo)
|
|||
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");
|
||||
|
|
|
|||
|
|
@ -114,8 +114,8 @@ function selectionChanged()
|
|||
Engine.GetGUIObjectByName("savedMapSize").caption = translateMapSize(metadata.initAttributes.settings.Size);
|
||||
Engine.GetGUIObjectByName("savedVictory").caption = translateVictoryCondition(metadata.initAttributes.settings.GameType);
|
||||
|
||||
let caption = sprintf(translate("Mods: %(mods)s"), { "mods": metadata.mods.join(translate(", ")) });
|
||||
if (!hasSameMods(metadata, Engine.GetEngineInfo()))
|
||||
let caption = sprintf(translate("Mods: %(mods)s"), { "mods": modsToString(metadata.mods) });
|
||||
if (!hasSameMods(metadata.mods, Engine.GetEngineInfo().mods))
|
||||
caption = coloredText(caption, "orange");
|
||||
Engine.GetGUIObjectByName("savedMods").caption = caption;
|
||||
|
||||
|
|
@ -133,7 +133,7 @@ function loadGame()
|
|||
|
||||
// Check compatibility before really loading it
|
||||
let engineInfo = Engine.GetEngineInfo();
|
||||
let sameMods = hasSameMods(metadata, engineInfo);
|
||||
let sameMods = hasSameMods(metadata.mods, engineInfo.mods);
|
||||
let sameEngineVersion = hasSameEngineVersion(metadata, engineInfo);
|
||||
|
||||
if (sameEngineVersion && sameMods)
|
||||
|
|
@ -143,32 +143,27 @@ function loadGame()
|
|||
}
|
||||
|
||||
// Version not compatible ... ask for confirmation
|
||||
let message = translate("This saved game may not be compatible:");
|
||||
let message = "";
|
||||
|
||||
if (!sameEngineVersion)
|
||||
if (metadata.engine_version)
|
||||
message += "\n" + sprintf(translate("It needs 0 A.D. version %(requiredVersion)s, while you are running version %(currentVersion)s."), {
|
||||
message += sprintf(translate("This savegame needs 0 A.D. version %(requiredVersion)s, while you are running version %(currentVersion)s."), {
|
||||
"requiredVersion": metadata.engine_version,
|
||||
"currentVersion": engineInfo.engine_version
|
||||
});
|
||||
}) + "\n";
|
||||
else
|
||||
message += "\n" + translate("It needs an older version of 0 A.D.");
|
||||
message += translate("This savegame needs an older version of 0 A.D.") + "\n";
|
||||
|
||||
if (!sameMods)
|
||||
{
|
||||
if (!metadata.mods)
|
||||
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(", "))
|
||||
});
|
||||
message += translate("This savegame needs a different sequence of mods:") + "\n" +
|
||||
comparedModsString(metadata.mods, engineInfo.mods) + "\n";
|
||||
}
|
||||
|
||||
message += "\n" + translate("Do you still want to proceed?");
|
||||
message += translate("Do you still want to proceed?");
|
||||
|
||||
messageBox(
|
||||
500, 250,
|
||||
|
|
|
|||
|
|
@ -89,9 +89,8 @@ function displayReplayCompatibilityError(replay)
|
|||
if (replayHasSameEngineVersion(replay))
|
||||
{
|
||||
let gameMods = replay.attribs.mods || [];
|
||||
errMsg = translate("You don't have the same mods active as the replay does.") + "\n";
|
||||
errMsg += sprintf(translate("Required: %(mods)s"), { "mods": gameMods.join(translate(", ")) }) + "\n";
|
||||
errMsg += sprintf(translate("Active: %(mods)s"), { "mods": g_EngineInfo.mods.join(translate(", ")) });
|
||||
errMsg = translate("This replay needs a different sequence of mods:") + "\n" +
|
||||
comparedModsString(gameMods, g_EngineInfo.mods);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
|||
|
|
@ -352,7 +352,7 @@ function getReplayDuration(replay)
|
|||
*/
|
||||
function isReplayCompatible(replay)
|
||||
{
|
||||
return replayHasSameEngineVersion(replay) && hasSameMods(replay.attribs, g_EngineInfo);
|
||||
return replayHasSameEngineVersion(replay) && hasSameMods(replay.attribs.mods, g_EngineInfo.mods);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
/* Copyright (C) 2017 Wildfire Games.
|
||||
/* Copyright (C) 2018 Wildfire Games.
|
||||
* This file is part of 0 A.D.
|
||||
*
|
||||
* 0 A.D. is free software: you can redistribute it and/or modify
|
||||
|
|
@ -99,3 +99,28 @@ JS::Value Mod::GetAvailableMods(const ScriptInterface& scriptInterface)
|
|||
|
||||
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, JS::ObjectValue(*JS_NewArrayObject(cx, 0)));
|
||||
|
||||
// Index of the created array
|
||||
size_t j = 0;
|
||||
for (size_t i = 0; i < g_modsLoaded.size(); ++i)
|
||||
{
|
||||
// Ignore user and mod mod as they are irrelevant for compatibility checks
|
||||
if (g_modsLoaded[i] == "mod" || g_modsLoaded[i] == "user")
|
||||
continue;
|
||||
CStr version;
|
||||
JS::RootedValue modData(cx);
|
||||
if (scriptInterface.GetProperty(availableMods, g_modsLoaded[i].c_str(), &modData))
|
||||
scriptInterface.GetProperty(modData, "version", version);
|
||||
scriptInterface.SetPropertyInt(ret, j++, std::vector<CStr>{g_modsLoaded[i], version});
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
/* Copyright (C) 2017 Wildfire Games.
|
||||
/* Copyright (C) 2018 Wildfire Games.
|
||||
* This file is part of 0 A.D.
|
||||
*
|
||||
* 0 A.D. is free software: you can redistribute it and/or modify
|
||||
|
|
@ -28,5 +28,6 @@ extern CmdLineArgs g_args;
|
|||
namespace Mod
|
||||
{
|
||||
JS::Value GetAvailableMods(const ScriptInterface& scriptInterface);
|
||||
JS::Value GetLoadedModsWithVersions(const ScriptInterface& scriptInterface);
|
||||
}
|
||||
#endif // INCLUDED_MOD
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
/* Copyright (C) 2017 Wildfire Games.
|
||||
/* Copyright (C) 2018 Wildfire Games.
|
||||
* This file is part of 0 A.D.
|
||||
*
|
||||
* 0 A.D. is free software: you can redistribute it and/or modify
|
||||
|
|
@ -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 @@ CReplayLogger::~CReplayLogger()
|
|||
|
||||
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());
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
/* Copyright (C) 2017 Wildfire Games.
|
||||
/* Copyright (C) 2018 Wildfire Games.
|
||||
* This file is part of 0 A.D.
|
||||
*
|
||||
* 0 A.D. is free software: you can redistribute it and/or modify
|
||||
|
|
@ -82,7 +82,7 @@ Status SavedGames::Save(const CStrW& name, const CStrW& description, CSimulation
|
|||
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::Value SavedGames::GetEngineInfo(const ScriptInterface& scriptInterface)
|
|||
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);
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue