Index: binaries/data/mods/mod/gui/modmod/modmod.js
===================================================================
--- binaries/data/mods/mod/gui/modmod/modmod.js
+++ binaries/data/mods/mod/gui/modmod/modmod.js
@@ -89,7 +89,7 @@
function loadEnabledMods()
{
- g_ModsEnabled = Engine.ConfigDB_GetValue("user", "mod.enabledmods").split(/\s+/).filter(folder => !!g_Mods[folder]);
+ g_ModsEnabled = Engine.GetEnabledMods().filter(folder => !!g_Mods[folder]);
g_ModsDisabled = Object.keys(g_Mods).filter(folder => g_ModsEnabled.indexOf(folder) == -1);
g_ModsEnabledFiltered = g_ModsEnabled;
g_ModsDisabledFiltered = g_ModsDisabled;
@@ -111,9 +111,11 @@
function initGUIButtons(data)
{
// Either get back to the previous page or quit if there is no previous page
- let cancelButton = !data || data.cancelbutton;
- Engine.GetGUIObjectByName("cancelButton").hidden = !cancelButton;
- Engine.GetGUIObjectByName("quitButton").hidden = cancelButton;
+ let hasPreviousPage = !data || data.cancelbutton;
+ Engine.GetGUIObjectByName("cancelButton").hidden = !hasPreviousPage;
+ Engine.GetGUIObjectByName("quitButton").hidden = hasPreviousPage;
+ Engine.GetGUIObjectByName("startModsButton").hidden = !hasPreviousPage;
+ Engine.GetGUIObjectByName("startButton").hidden = hasPreviousPage;
Engine.GetGUIObjectByName("toggleModButton").caption = translateWithContext("mod activation", "Enable");
}
@@ -127,8 +129,13 @@
function startMods()
{
sortEnabledMods();
- Engine.SetMods(["mod"].concat(g_ModsEnabled));
- Engine.RestartEngine();
+ if (!g_ModsEnabled.length)
+ {
+ Engine.GetGUIObjectByName("message").caption = coloredText(translate('Enable at least one mod'), g_ColorDependenciesNotMet);
+ return;
+ }
+ if (!Engine.SetModsAndRestartEngine(["mod"].concat(g_ModsEnabled)))
+ Engine.GetGUIObjectByName("message").caption = coloredText(translate('Dependencies not met'), g_ColorDependenciesNotMet);
}
function displayModLists()
Index: binaries/data/mods/mod/gui/modmod/modmod.xml
===================================================================
--- binaries/data/mods/mod/gui/modmod/modmod.xml
+++ binaries/data/mods/mod/gui/modmod/modmod.xml
@@ -202,5 +202,10 @@
Start Mods
startMods();
+
+
Index: binaries/data/mods/public/gui/pregame/MainMenuPage.js
===================================================================
--- binaries/data/mods/public/gui/pregame/MainMenuPage.js
+++ binaries/data/mods/public/gui/pregame/MainMenuPage.js
@@ -7,17 +7,20 @@
{
this.backgroundHandler = new BackgroundHandler(pickRandom(backgroundLayerData));
this.menuHandler = new MainMenuItemHandler(mainMenuItems);
+ this.incompatibleModsHanlder = new IncompatibleModsHandler(data, hotloadData && hotloadData.incompatibleModsHanlder);
this.splashScreenHandler = new SplashScreenHandler(data, hotloadData && hotloadData.splashScreenHandler);
new MusicHandler();
new ProjectInformationHandler(projectInformation);
new CommunityButtonHandler(communityButtons);
+ warn(uneval(data));
}
getHotloadData()
{
return {
- "splashScreenHandler": this.splashScreenHandler.getHotloadData()
+ "splashScreenHandler": this.splashScreenHandler.getHotloadData(),
+ "incompatibleModsHanlder": this.incompatibleModsHanlder.getHotloadData()
};
}
}
Index: binaries/data/mods/public/gui/pregame/SplashscreenHandler.js
===================================================================
--- binaries/data/mods/public/gui/pregame/SplashscreenHandler.js
+++ binaries/data/mods/public/gui/pregame/SplashscreenHandler.js
@@ -2,10 +2,13 @@
{
constructor(initData, hotloadData)
{
- this.showSplashScreen = hotloadData ? hotloadData.showSplashScreen : initData && initData.isStartup;
+ this.showSplashScreen = hotloadData ? hotloadData.showSplashScreen && !hotloadData.incompatibleMods : initData && initData.isStartup && !initData.incompatibleMods;
- this.mainMenuPage = Engine.GetGUIObjectByName("mainMenuPage");
- this.mainMenuPage.onTick = this.onFirstTick.bind(this);
+ if (this.showSplashScreen)
+ {
+ this.mainMenuPage = Engine.GetGUIObjectByName("mainMenuPage");
+ this.mainMenuPage.onTick = this.onFirstTick.bind(this);
+ }
}
getHotloadData()
Index: source/main.cpp
===================================================================
--- source/main.cpp
+++ source/main.cpp
@@ -49,6 +49,7 @@
#include "ps/Globals.h"
#include "ps/Hotkey.h"
#include "ps/Loader.h"
+#include "ps/Mod.h"
#include "ps/ModInstaller.h"
#include "ps/Profile.h"
#include "ps/Profiler2.h"
@@ -594,7 +595,7 @@
Paths paths(args);
g_VFS = CreateVfs();
g_VFS->Mount(L"cache/", paths.Cache(), VFS_MOUNT_ARCHIVABLE);
- MountMods(paths, GetMods(args, INIT_MODS));
+ MountMods(paths, Mod::GetModsFromArguments(args, INIT_MODS));
{
CReplayPlayer replay;
Index: source/ps/GameSetup/GameSetup.h
===================================================================
--- source/ps/GameSetup/GameSetup.h
+++ source/ps/GameSetup/GameSetup.h
@@ -57,7 +57,10 @@
// mount the public mod
// needed by the map editor as "mod" does not provide everything it needs
- INIT_MODS_PUBLIC = 16
+ INIT_MODS_PUBLIC = 16,
+
+ // inform user about incompatible mods
+ INIT_MODS_INFORM_INCOMPATIBLE = 32
};
enum ShutdownFlags
@@ -82,7 +85,6 @@
class CmdLineArgs;
class Paths;
-extern const std::vector& GetMods(const CmdLineArgs& args, int flags);
/**
* Mounts all files of the given mods in the global VFS.
@@ -93,7 +95,7 @@
* Returns true if successful, false if mods changed and restart_engine was called.
* In the latter case the caller should call Shutdown() with SHUTDOWN_FROM_CONFIG.
*/
-extern bool Init(const CmdLineArgs& args, int flags);
+extern bool Init(const CmdLineArgs& args, int& flags);
extern void InitInput();
extern void InitGraphics(const CmdLineArgs& args, int flags, const std::vector& installedMods = std::vector());
extern void InitNonVisual(const CmdLineArgs& args);
Index: source/ps/GameSetup/GameSetup.cpp
===================================================================
--- source/ps/GameSetup/GameSetup.cpp
+++ source/ps/GameSetup/GameSetup.cpp
@@ -372,36 +372,6 @@
return ERI_NOT_IMPLEMENTED;
}
-const std::vector& GetMods(const CmdLineArgs& args, int flags)
-{
- const bool init_mods = (flags & INIT_MODS) == INIT_MODS;
- const bool add_user = !InDevelopmentCopy() && !args.Has("noUserMod");
- const bool add_public = (flags & INIT_MODS_PUBLIC) == INIT_MODS_PUBLIC;
-
- if (!init_mods)
- {
- // Add the user mod if it should be present
- if (add_user && (g_modsLoaded.empty() || g_modsLoaded.back() != "user"))
- g_modsLoaded.push_back("user");
-
- return g_modsLoaded;
- }
-
- g_modsLoaded = args.GetMultiple("mod");
-
- if (add_public)
- g_modsLoaded.insert(g_modsLoaded.begin(), "public");
-
- g_modsLoaded.insert(g_modsLoaded.begin(), "mod");
-
- // Add the user mod if not explicitly disabled or we have a dev copy so
- // that saved files end up in version control and not in the user mod.
- if (add_user)
- g_modsLoaded.push_back("user");
-
- return g_modsLoaded;
-}
-
void MountMods(const Paths& paths, const std::vector& mods)
{
OsPath modPath = paths.RData()/"mods";
@@ -460,7 +430,7 @@
// Engine localization files.
g_VFS->Mount(L"l10n/", paths.RData()/"l10n"/"");
- MountMods(paths, GetMods(args, flags));
+ MountMods(paths, Mod::GetModsFromArguments(args, flags));
// We mount these dirs last as otherwise writing could result in files being placed in a mod's dir.
g_VFS->Mount(L"screenshots/", paths.UserData()/"screenshots"/"");
@@ -871,7 +841,29 @@
*/
bool AutostartVisualReplay(const std::string& replayFile);
-bool Init(const CmdLineArgs& args, int flags)
+bool EnableModsOrSetDefault(const CmdLineArgs& args, int flags, const std::vector& mods, bool fromConfig)
+{
+ if (Mod::CheckAndEnableMods(g_ScriptContext, mods))
+ return true;
+ // Here we refuse to start as there is no gui anyway
+ if (args.Has("autostart-nonvisual"))
+ {
+ if (fromConfig)
+ LOGERROR("Mods defined in config file are not compatible.");
+ else
+ LOGERROR("Trying to start with incompatible mods.");
+ return false;
+ }
+ // When mods are incompatible, we enforce default ones so user is able
+ // to start the game without wall of error strings
+ if (!fromConfig)
+ LOGERROR("Trying to start with incompatible mods. Setting default mods.");
+ Mod::SetDefaultMods(g_ScriptContext, args, flags);
+ RestartEngine();
+ return false;
+}
+
+bool Init(const CmdLineArgs& args, int& flags)
{
h_mgr_init();
@@ -931,21 +923,30 @@
// else check if there are mods that should be loaded specified
// in the config and load those (by aborting init and restarting
// the engine).
- if (!args.Has("mod") && (flags & INIT_MODS) == INIT_MODS)
+
+ if (!args.Has("mod"))
{
- CStr modstring;
- CFG_GET_VAL("mod.enabledmods", modstring);
- if (!modstring.empty())
- {
- std::vector mods;
- boost::split(mods, modstring, boost::is_any_of(" "), boost::token_compress_on);
- std::swap(g_modsLoaded, mods);
-
- // Abort init and restart
- RestartEngine();
- return false;
+ if ((flags & INIT_MODS) == INIT_MODS)
+ {
+ CStr modstring;
+ CFG_GET_VAL("mod.enabledmods", modstring);
+ if (!modstring.empty())
+ {
+ std::vector mods;
+ boost::split(mods, modstring, boost::is_any_of(" "), boost::token_compress_on);
+ if (!EnableModsOrSetDefault(args, flags, mods, true))
+ {
+ flags &= INIT_MODS_INFORM_INCOMPATIBLE;
+ return false;
+ }
+
+ RestartEngine();
+ return false;
+ }
}
}
+ else if (!EnableModsOrSetDefault(args, flags, g_modsLoaded, false))
+ return false;
new L10n;
@@ -1078,6 +1079,8 @@
ScriptInterface::CreateObject(rq, &data, "isStartup", true);
if (!installedMods.empty())
scriptInterface->SetProperty(data, "installedMods", installedMods);
+ if ((flags & INIT_MODS_INFORM_INCOMPATIBLE) == 0)
+ scriptInterface->SetProperty(data, "incompatibleMods", true);
}
InitPs(setup_gui, installedMods.empty() ? L"page_pregame.xml" : L"page_modmod.xml", g_GUI->GetScriptInterface().get(), data);
}
Index: source/ps/Mod.h
===================================================================
--- source/ps/Mod.h
+++ source/ps/Mod.h
@@ -1,4 +1,4 @@
-/* Copyright (C) 2018 Wildfire Games.
+/* Copyright (C) 2021 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@@ -30,7 +30,7 @@
namespace Mod
{
JS::Value GetAvailableMods(const ScriptInterface& scriptInterface);
-
+ std::vector GetEnabledMods(const ScriptInterface& scriptInterface);
/**
* This reads the version numbers from the launched mods.
* It caches the result, since the reading of zip files is slow and
@@ -39,6 +39,12 @@
*/
void CacheEnabledModVersions(const shared_ptr& scriptContext);
+ const std::vector& GetModsFromArguments(const CmdLineArgs& args, int flags);
+ bool CheckAndEnableMods(const shared_ptr& scriptContext, const std::vector& mods);
+ bool CheckAndEnableMods(const ScriptInterface& scriptInterface, const std::vector& mods);
+ bool CompareVersionStrings(const CStr& version, const CStr& op, const CStr& target);
+ void SetDefaultMods(const shared_ptr& scriptContext, const CmdLineArgs& args, int flags);
+
/**
* Get the loaded mods and their version.
* "user" mod and "mod" mod are ignored as they are irrelevant for compatibility checks.
Index: source/ps/Mod.cpp
===================================================================
--- source/ps/Mod.cpp
+++ source/ps/Mod.cpp
@@ -1,4 +1,4 @@
-/* Copyright (C) 2020 Wildfire Games.
+/* Copyright (C) 2021 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@@ -19,8 +19,6 @@
#include "ps/Mod.h"
-#include
-
#include "lib/file/file_system.h"
#include "lib/file/vfs/vfs.h"
#include "lib/utf8.h"
@@ -30,6 +28,11 @@
#include "ps/Pyrogenesis.h"
#include "scriptinterface/ScriptInterface.h"
+#include
+#include
+#include
+#include
+
std::vector g_modsLoaded;
std::vector> g_LoadedModVersions;
@@ -103,6 +106,11 @@
return JS::ObjectValue(*obj);
}
+std::vector Mod::GetEnabledMods(const ScriptInterface& scriptInterface)
+{
+ return g_modsLoaded;
+}
+
void Mod::CacheEnabledModVersions(const shared_ptr& scriptContext)
{
ScriptInterface scriptInterface("Engine", "CacheEnabledModVersions", scriptContext);
@@ -127,6 +135,160 @@
}
}
+
+const std::vector& Mod::GetModsFromArguments(const CmdLineArgs& args, int flags)
+{
+ const bool init_mods = (flags & INIT_MODS) == INIT_MODS;
+ const bool add_user = !InDevelopmentCopy() && !args.Has("noUserMod");
+ const bool add_public = (flags & INIT_MODS_PUBLIC) == INIT_MODS_PUBLIC;
+
+ if (!init_mods)
+ {
+ // Add the user mod if it should be present
+ if (add_user && (g_modsLoaded.empty() || g_modsLoaded.back() != "user"))
+ g_modsLoaded.push_back("user");
+
+ return g_modsLoaded;
+ }
+
+ g_modsLoaded = args.GetMultiple("mod");
+
+ if (add_public)
+ g_modsLoaded.insert(g_modsLoaded.begin(), "public");
+
+ g_modsLoaded.insert(g_modsLoaded.begin(), "mod");
+
+ // Add the user mod if not explicitly disabled or we have a dev copy so
+ // that saved files end up in version control and not in the user mod.
+ if (add_user)
+ g_modsLoaded.push_back("user");
+
+ return g_modsLoaded;
+}
+
+void Mod::SetDefaultMods(const shared_ptr& scriptContext, const CmdLineArgs& args, int flags)
+{
+ const bool add_user = !InDevelopmentCopy() && !args.Has("noUserMod");
+
+ g_modsLoaded.clear();
+
+ g_modsLoaded.insert(g_modsLoaded.begin(), "public");
+ g_modsLoaded.insert(g_modsLoaded.begin(), "mod");
+
+ // Add the user mod if not explicitly disabled or we have a dev copy so
+ // that saved files end up in version control and not in the user mod.
+ if (add_user)
+ g_modsLoaded.push_back("user");
+}
+
+bool Mod::CheckAndEnableMods(const shared_ptr& scriptContext, const std::vector& mods)
+{
+ ScriptInterface scriptInterface("Engine", "CheckAndEnableMods", scriptContext);
+ return Mod::CheckAndEnableMods(scriptInterface, mods);
+}
+
+bool Mod::CheckAndEnableMods(const ScriptInterface& scriptInterface, const std::vector& mods)
+{
+ ScriptRequest rq(scriptInterface);
+
+ JS::RootedValue availableMods(rq.cx, GetAvailableMods(scriptInterface));
+ std::unordered_map> modDependencies;
+ std::unordered_map map;
+ std::unordered_map names;
+ for (const CStr& mod : mods)
+ {
+ if (mod == "mod" || mod == "user")
+ continue;
+
+ CStr dependencies;
+ CStr version;
+ CStr name;
+ JS::RootedValue modData(rq.cx);
+ if (scriptInterface.GetProperty(availableMods, mod.c_str(), &modData))
+ {
+ scriptInterface.GetProperty(modData, "dependencies", dependencies);
+ scriptInterface.GetProperty(modData, "version", version);
+ scriptInterface.GetProperty(modData, "name", name);
+ }
+ map.emplace(name, version);
+ std::vector deps;
+ boost::split(deps, dependencies, boost::is_any_of(","), boost::token_compress_on);
+ modDependencies.emplace(mod, deps);
+ }
+
+ const std::vector toCheck = { "<=", ">=", "=", "<", ">" };
+ for (const CStr& mod : mods)
+ {
+ if (mod == "mod" || mod == "user")
+ continue;
+
+ const std::unordered_map>::iterator res = modDependencies.find(mod);
+ if (res == modDependencies.end())
+ continue;
+ const std::vector deps = res->second;
+ if (deps.empty())
+ continue;
+
+ for (const CStr& dep : deps)
+ {
+ if (dep.empty())
+ continue;
+
+ for (const CStr& op : toCheck)
+ {
+ const int pos = dep.Find(op.c_str());
+ if (pos == -1)
+ continue;
+ const CStr modToCheck = dep.substr(0, pos);
+ const CStr versionToCheck = dep.substr(pos + op.size());
+ const std::unordered_map::iterator it = map.find(modToCheck);
+ if (it == map.end())
+ return false;
+ if (!CompareVersionStrings(versionToCheck, op, it->second))
+ return false;
+ break;
+ }
+ }
+
+ }
+
+ g_modsLoaded = mods;
+ return true;
+}
+
+bool Mod::CompareVersionStrings(const CStr& target, const CStr& op, const CStr& version)
+{
+ std::vector vSplit;
+ std::vector tSplit;
+ std::string toIgnore = "-,_";
+ boost::split(vSplit, version, boost::is_any_of(toIgnore), boost::token_compress_on);
+ boost::split(tSplit, target, boost::is_any_of(toIgnore), boost::token_compress_on);
+ boost::split(vSplit, vSplit.at(0), boost::is_any_of("."), boost::token_compress_on);
+ boost::split(tSplit, tSplit.at(0), boost::is_any_of("."), boost::token_compress_on);
+
+ const bool eq = op.Find("=") != -1;
+ const bool lt = op.Find("<") != -1;
+ const bool gt = op.Find(">") != -1;
+
+ const int min = std::min(vSplit.size(), tSplit.size());
+
+ for (int i = 0; i < min; ++i)
+ {
+ const int diff = vSplit.at(i).ToInt() - tSplit.at(i).ToInt();
+ if (gt && diff > 0 || lt && diff < 0)
+ return true;
+
+ if (gt && diff < 0 || lt && diff > 0 || eq && diff)
+ return false;
+ }
+
+ const int diff = vSplit.size() - tSplit.size();
+ if (diff == 0)
+ return eq;
+
+ return diff < 0 ? lt : gt;
+}
+
JS::Value Mod::GetLoadedModsWithVersions(const ScriptInterface& scriptInterface)
{
ScriptRequest rq(scriptInterface);
Index: source/ps/scripting/JSInterface_Mod.cpp
===================================================================
--- source/ps/scripting/JSInterface_Mod.cpp
+++ source/ps/scripting/JSInterface_Mod.cpp
@@ -47,9 +47,21 @@
return Mod::GetAvailableMods(*(pCmptPrivate->pScriptInterface));
}
-void SetMods(const std::vector& mods)
+/**
+ * Returns a vector of currently enabled mods.
+ */
+std::vector GetEnabledMods(ScriptInterface::CmptPrivate* pCmptPrivate)
+{
+ return Mod::GetEnabledMods(*(pCmptPrivate->pScriptInterface));
+}
+
+bool SetModsAndRestartEngine(ScriptInterface::CmptPrivate* pCmptPrivate, const std::vector& mods)
{
- g_modsLoaded = mods;
+ if (!Mod::CheckAndEnableMods(*(pCmptPrivate->pScriptInterface), mods))
+ return false;
+
+ ::RestartEngine();
+ return true;
}
}
@@ -57,6 +69,6 @@
{
ScriptFunction::Register<&GetEngineInfo>(rq, "GetEngineInfo");
ScriptFunction::Register<&GetAvailableMods>(rq, "GetAvailableMods");
- ScriptFunction::Register<&RestartEngine>(rq, "RestartEngine");
- ScriptFunction::Register<&SetMods>(rq, "SetMods");
+ ScriptFunction::Register<&GetEnabledMods>(rq, "GetEnabledMods");
+ ScriptFunction::Register<&SetModsAndRestartEngine>(rq, "SetModsAndRestartEngine");
}
Index: source/tools/atlas/GameInterface/Handlers/GraphicsSetupHandlers.cpp
===================================================================
--- source/tools/atlas/GameInterface/Handlers/GraphicsSetupHandlers.cpp
+++ source/tools/atlas/GameInterface/Handlers/GraphicsSetupHandlers.cpp
@@ -58,12 +58,16 @@
g_Quickstart = true;
// Mount mods if there are any specified as command line parameters
- if (!Init(g_AtlasGameLoop->args, g_InitFlags | INIT_MODS|INIT_MODS_PUBLIC))
+ int flags = g_InitFlags | INIT_MODS | INIT_MODS_PUBLIC;
+ if (!Init(g_AtlasGameLoop->args, flags))
{
// There are no mods specified on the command line,
// but there are in the config file, so mount those.
Shutdown(SHUTDOWN_FROM_CONFIG);
- ENSURE(Init(g_AtlasGameLoop->args, g_InitFlags));
+ //TODO: Init might set INIT_MODS_INFORM_INCOMPATIBLE flag,
+ // show that information in atlas
+ int initFlags = g_InitFlags;
+ ENSURE(Init(g_AtlasGameLoop->args, initFlags));
}
// Initialise some graphics state for Atlas.