aboutsummaryrefslogtreecommitdiff
path: root/src/content
diff options
context:
space:
mode:
Diffstat (limited to 'src/content')
-rw-r--r--src/content/CMakeLists.txt1
-rw-r--r--src/content/content.cpp7
-rw-r--r--src/content/content.h7
-rw-r--r--src/content/mod_configuration.cpp255
-rw-r--r--src/content/mod_configuration.h111
-rw-r--r--src/content/mods.cpp362
-rw-r--r--src/content/mods.h119
-rw-r--r--src/content/subgames.cpp57
-rw-r--r--src/content/subgames.h20
9 files changed, 556 insertions, 383 deletions
diff --git a/src/content/CMakeLists.txt b/src/content/CMakeLists.txt
index 6dd049418..2aefd40a4 100644
--- a/src/content/CMakeLists.txt
+++ b/src/content/CMakeLists.txt
@@ -1,5 +1,6 @@
set(content_SRCS
${CMAKE_CURRENT_SOURCE_DIR}/content.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/mod_configuration.cpp
${CMAKE_CURRENT_SOURCE_DIR}/mods.cpp
${CMAKE_CURRENT_SOURCE_DIR}/subgames.cpp
PARENT_SCOPE
diff --git a/src/content/content.cpp b/src/content/content.cpp
index 66ef83d42..e576943ff 100644
--- a/src/content/content.cpp
+++ b/src/content/content.cpp
@@ -96,7 +96,12 @@ void parseContentInfo(ContentSpec &spec)
Settings conf;
if (!conf_path.empty() && conf.readConfigFile(conf_path.c_str())) {
- if (conf.exists("name"))
+ if (conf.exists("title"))
+ spec.title = conf.get("title");
+ else if (spec.type == "game" && conf.exists("name"))
+ spec.title = conf.get("name");
+
+ if (spec.type != "game" && conf.exists("name"))
spec.name = conf.get("name");
if (conf.exists("description"))
diff --git a/src/content/content.h b/src/content/content.h
index e246ed411..ce09a2eb9 100644
--- a/src/content/content.h
+++ b/src/content/content.h
@@ -27,7 +27,14 @@ struct ContentSpec
std::string type;
std::string author;
u32 release = 0;
+
+ /// Technical name / Id
std::string name;
+
+ /// Human-readable title
+ std::string title;
+
+ /// Short description
std::string desc;
std::string path;
};
diff --git a/src/content/mod_configuration.cpp b/src/content/mod_configuration.cpp
new file mode 100644
index 000000000..504cdaf6f
--- /dev/null
+++ b/src/content/mod_configuration.cpp
@@ -0,0 +1,255 @@
+/*
+Minetest
+Copyright (C) 2013-22 celeron55, Perttu Ahola <celeron55@gmail.com>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "mod_configuration.h"
+#include "log.h"
+#include "settings.h"
+#include "filesys.h"
+
+void ModConfiguration::printUnsatisfiedModsError() const
+{
+ for (const ModSpec &mod : m_unsatisfied_mods) {
+ errorstream << "mod \"" << mod.name
+ << "\" has unsatisfied dependencies: ";
+ for (const std::string &unsatisfied_depend : mod.unsatisfied_depends)
+ errorstream << " \"" << unsatisfied_depend << "\"";
+ errorstream << std::endl;
+ }
+}
+
+void ModConfiguration::addModsInPath(const std::string &path, const std::string &virtual_path)
+{
+ addMods(flattenMods(getModsInPath(path, virtual_path)));
+}
+
+void ModConfiguration::addMods(const std::vector<ModSpec> &new_mods)
+{
+ // Maintain a map of all existing m_unsatisfied_mods.
+ // Keys are mod names and values are indices into m_unsatisfied_mods.
+ std::map<std::string, u32> existing_mods;
+ for (u32 i = 0; i < m_unsatisfied_mods.size(); ++i) {
+ existing_mods[m_unsatisfied_mods[i].name] = i;
+ }
+
+ // Add new mods
+ for (int want_from_modpack = 1; want_from_modpack >= 0; --want_from_modpack) {
+ // First iteration:
+ // Add all the mods that come from modpacks
+ // Second iteration:
+ // Add all the mods that didn't come from modpacks
+
+ std::set<std::string> seen_this_iteration;
+
+ for (const ModSpec &mod : new_mods) {
+ if (mod.part_of_modpack != (bool)want_from_modpack)
+ continue;
+
+ if (existing_mods.count(mod.name) == 0) {
+ // GOOD CASE: completely new mod.
+ m_unsatisfied_mods.push_back(mod);
+ existing_mods[mod.name] = m_unsatisfied_mods.size() - 1;
+ } else if (seen_this_iteration.count(mod.name) == 0) {
+ // BAD CASE: name conflict in different levels.
+ u32 oldindex = existing_mods[mod.name];
+ const ModSpec &oldmod = m_unsatisfied_mods[oldindex];
+ warningstream << "Mod name conflict detected: \""
+ << mod.name << "\"" << std::endl
+ << "Will not load: " << oldmod.path
+ << std::endl
+ << "Overridden by: " << mod.path
+ << std::endl;
+ m_unsatisfied_mods[oldindex] = mod;
+
+ // If there was a "VERY BAD CASE" name conflict
+ // in an earlier level, ignore it.
+ m_name_conflicts.erase(mod.name);
+ } else {
+ // VERY BAD CASE: name conflict in the same level.
+ u32 oldindex = existing_mods[mod.name];
+ const ModSpec &oldmod = m_unsatisfied_mods[oldindex];
+ warningstream << "Mod name conflict detected: \""
+ << mod.name << "\"" << std::endl
+ << "Will not load: " << oldmod.path
+ << std::endl
+ << "Will not load: " << mod.path
+ << std::endl;
+ m_unsatisfied_mods[oldindex] = mod;
+ m_name_conflicts.insert(mod.name);
+ }
+
+ seen_this_iteration.insert(mod.name);
+ }
+ }
+}
+
+void ModConfiguration::addGameMods(const SubgameSpec &gamespec)
+{
+ std::string game_virtual_path;
+ game_virtual_path.append("games/").append(gamespec.id).append("/mods");
+ addModsInPath(gamespec.gamemods_path, game_virtual_path);
+}
+
+void ModConfiguration::addModsFromConfig(
+ const std::string &settings_path,
+ const std::unordered_map<std::string, std::string> &modPaths)
+{
+ Settings conf;
+ std::unordered_map<std::string, std::string> load_mod_names;
+
+ conf.readConfigFile(settings_path.c_str());
+ std::vector<std::string> names = conf.getNames();
+ for (const std::string &name : names) {
+ const auto &value = conf.get(name);
+ if (name.compare(0, 9, "load_mod_") == 0 && value != "false" &&
+ value != "nil")
+ load_mod_names[name.substr(9)] = value;
+ }
+
+ // List of enabled non-game non-world mods
+ std::vector<ModSpec> addon_mods;
+
+ // Map of modname to a list candidate mod paths. Used to list
+ // alternatives if a particular mod cannot be found.
+ std::unordered_map<std::string, std::vector<std::string>> candidates;
+
+ /*
+ * Iterate through all installed mods except game mods and world mods
+ *
+ * If the mod is enabled, add it to `addon_mods`. *
+ *
+ * Alternative candidates for a modname are stored in `candidates`,
+ * and used in an error message later.
+ *
+ * If not enabled, add `load_mod_modname = false` to world.mt
+ */
+ for (const auto &modPath : modPaths) {
+ std::vector<ModSpec> addon_mods_in_path = flattenMods(getModsInPath(modPath.second, modPath.first));
+ for (const auto &mod : addon_mods_in_path) {
+ const auto &pair = load_mod_names.find(mod.name);
+ if (pair != load_mod_names.end()) {
+ if (is_yes(pair->second) || pair->second == mod.virtual_path) {
+ addon_mods.push_back(mod);
+ } else {
+ candidates[pair->first].emplace_back(mod.virtual_path);
+ }
+ } else {
+ conf.setBool("load_mod_" + mod.name, false);
+ }
+ }
+ }
+ conf.updateConfigFile(settings_path.c_str());
+
+ addMods(addon_mods);
+
+ // Remove all loaded mods from `load_mod_names`
+ // NB: as deps have not yet been resolved, `m_unsatisfied_mods` will contain all mods.
+ for (const ModSpec &mod : m_unsatisfied_mods)
+ load_mod_names.erase(mod.name);
+
+ // Complain about mods declared to be loaded, but not found
+ if (!load_mod_names.empty()) {
+ errorstream << "The following mods could not be found:";
+ for (const auto &pair : load_mod_names)
+ errorstream << " \"" << pair.first << "\"";
+ errorstream << std::endl;
+
+ for (const auto &pair : load_mod_names) {
+ const auto &candidate = candidates.find(pair.first);
+ if (candidate != candidates.end()) {
+ errorstream << "Unable to load " << pair.first << " as the specified path "
+ << pair.second << " could not be found. "
+ << "However, it is available in the following locations:"
+ << std::endl;
+ for (const auto &path : candidate->second) {
+ errorstream << " - " << path << std::endl;
+ }
+ }
+ }
+ }
+}
+
+void ModConfiguration::checkConflictsAndDeps()
+{
+ // report on name conflicts
+ if (!m_name_conflicts.empty()) {
+ std::string s = "Unresolved name conflicts for mods ";
+
+ bool add_comma = false;
+ for (const auto& it : m_name_conflicts) {
+ if (add_comma)
+ s.append(", ");
+ s.append("\"").append(it).append("\"");
+ add_comma = true;
+ }
+ s.append(".");
+
+ throw ModError(s);
+ }
+
+ // get the mods in order
+ resolveDependencies();
+}
+
+void ModConfiguration::resolveDependencies()
+{
+ // Step 1: Compile a list of the mod names we're working with
+ std::set<std::string> modnames;
+ for (const ModSpec &mod : m_unsatisfied_mods) {
+ modnames.insert(mod.name);
+ }
+
+ // Step 2: get dependencies (including optional dependencies)
+ // of each mod, split mods into satisfied and unsatisfied
+ std::list<ModSpec> satisfied;
+ std::list<ModSpec> unsatisfied;
+ for (ModSpec mod : m_unsatisfied_mods) {
+ mod.unsatisfied_depends = mod.depends;
+ // check which optional dependencies actually exist
+ for (const std::string &optdep : mod.optdepends) {
+ if (modnames.count(optdep) != 0)
+ mod.unsatisfied_depends.insert(optdep);
+ }
+ // if a mod has no depends it is initially satisfied
+ if (mod.unsatisfied_depends.empty())
+ satisfied.push_back(mod);
+ else
+ unsatisfied.push_back(mod);
+ }
+
+ // Step 3: mods without unmet dependencies can be appended to
+ // the sorted list.
+ while (!satisfied.empty()) {
+ ModSpec mod = satisfied.back();
+ m_sorted_mods.push_back(mod);
+ satisfied.pop_back();
+ for (auto it = unsatisfied.begin(); it != unsatisfied.end();) {
+ ModSpec &mod2 = *it;
+ mod2.unsatisfied_depends.erase(mod.name);
+ if (mod2.unsatisfied_depends.empty()) {
+ satisfied.push_back(mod2);
+ it = unsatisfied.erase(it);
+ } else {
+ ++it;
+ }
+ }
+ }
+
+ // Step 4: write back list of unsatisfied mods
+ m_unsatisfied_mods.assign(unsatisfied.begin(), unsatisfied.end());
+}
diff --git a/src/content/mod_configuration.h b/src/content/mod_configuration.h
new file mode 100644
index 000000000..1595d1ca3
--- /dev/null
+++ b/src/content/mod_configuration.h
@@ -0,0 +1,111 @@
+/*
+Minetest
+Copyright (C) 2013-22 celeron55, Perttu Ahola <celeron55@gmail.com>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#pragma once
+
+#include "mods.h"
+
+
+/**
+ * ModConfiguration is a subset of installed mods. This class
+ * is used to resolve dependencies and return a sorted list of mods.
+ *
+ * This class should not be extended from, but instead used as a
+ * component in other classes.
+ */
+class ModConfiguration
+{
+public:
+ /**
+ * @returns true if all dependencies are fullfilled.
+ */
+ inline bool isConsistent() const { return m_unsatisfied_mods.empty(); }
+
+ inline const std::vector<ModSpec> &getUnsatisfiedMods() const
+ {
+ return m_unsatisfied_mods;
+ }
+
+ /**
+ * List of mods sorted such that they can be loaded in the
+ * given order with all dependencies being fulfilled.
+ *
+ * I.e: every mod in this list has only dependencies on mods which
+ * appear earlier in the vector.
+ */
+ const std::vector<ModSpec> &getMods() const { return m_sorted_mods; }
+
+ void printUnsatisfiedModsError() const;
+
+ /**
+ * Adds all mods in the given path. used for games, modpacks
+ * and world-specific mods (worldmods-folders)
+ *
+ * @param path To search, should be absolute
+ * @param virtual_path Virtual path for this directory, see comment in ModSpec
+ */
+ void addModsInPath(const std::string &path, const std::string &virtual_path);
+
+ /**
+ * Adds all mods in `new_mods`
+ */
+ void addMods(const std::vector<ModSpec> &new_mods);
+
+ /**
+ * Adds game mods
+ */
+ void addGameMods(const SubgameSpec &gamespec);
+
+ /**
+ * Adds mods specifed by a world.mt config
+ *
+ * @param settings_path Path to world.mt
+ * @param modPaths Map from virtual name to mod path
+ */
+ void addModsFromConfig(const std::string &settings_path,
+ const std::unordered_map<std::string, std::string> &modPaths);
+
+ /**
+ * Call this function once all mods have been added
+ */
+ void checkConflictsAndDeps();
+
+private:
+ std::vector<ModSpec> m_sorted_mods;
+
+ /**
+ * move mods from m_unsatisfied_mods to m_sorted_mods
+ * in an order that satisfies dependencies
+ */
+ void resolveDependencies();
+
+ // mods with unmet dependencies. Before dependencies are resolved,
+ // this is where all mods are stored. Afterwards this contains
+ // only the ones with really unsatisfied dependencies.
+ std::vector<ModSpec> m_unsatisfied_mods;
+
+ // set of mod names for which an unresolved name conflict
+ // exists. A name conflict happens when two or more mods
+ // at the same level have the same name but different paths.
+ // Levels (mods in higher levels override mods in lower levels):
+ // 1. game mod in modpack; 2. game mod;
+ // 3. world mod in modpack; 4. world mod;
+ // 5. addon mod in modpack; 6. addon mod.
+ std::unordered_set<std::string> m_name_conflicts;
+};
diff --git a/src/content/mods.cpp b/src/content/mods.cpp
index 455506967..cec6fc2cd 100644
--- a/src/content/mods.cpp
+++ b/src/content/mods.cpp
@@ -69,7 +69,7 @@ bool parseDependsString(std::string &dep, std::unordered_set<char> &symbols)
return !dep.empty();
}
-void parseModContents(ModSpec &spec)
+bool parseModContents(ModSpec &spec)
{
// NOTE: this function works in mutual recursion with getModsInPath
@@ -79,101 +79,100 @@ void parseModContents(ModSpec &spec)
spec.modpack_content.clear();
// Handle modpacks (defined by containing modpack.txt)
- std::ifstream modpack_is((spec.path + DIR_DELIM + "modpack.txt").c_str());
- std::ifstream modpack2_is((spec.path + DIR_DELIM + "modpack.conf").c_str());
- if (modpack_is.good() || modpack2_is.good()) {
- if (modpack_is.good())
- modpack_is.close();
+ if (fs::IsFile(spec.path + DIR_DELIM + "modpack.txt") ||
+ fs::IsFile(spec.path + DIR_DELIM + "modpack.conf")) {
+ spec.is_modpack = true;
+ spec.modpack_content = getModsInPath(spec.path, spec.virtual_path, true);
+ return true;
+ } else if (!fs::IsFile(spec.path + DIR_DELIM + "init.lua")) {
+ return false;
+ }
- if (modpack2_is.good())
- modpack2_is.close();
- spec.is_modpack = true;
- spec.modpack_content = getModsInPath(spec.path, true);
+ Settings info;
+ info.readConfigFile((spec.path + DIR_DELIM + "mod.conf").c_str());
- } else {
- Settings info;
- info.readConfigFile((spec.path + DIR_DELIM + "mod.conf").c_str());
+ if (info.exists("name"))
+ spec.name = info.get("name");
+ else
+ spec.deprecation_msgs.push_back("Mods not having a mod.conf file with the name is deprecated.");
- if (info.exists("name"))
- spec.name = info.get("name");
- else
- spec.deprecation_msgs.push_back("Mods not having a mod.conf file with the name is deprecated.");
-
- if (info.exists("author"))
- spec.author = info.get("author");
-
- if (info.exists("release"))
- spec.release = info.getS32("release");
-
- // Attempt to load dependencies from mod.conf
- bool mod_conf_has_depends = false;
- if (info.exists("depends")) {
- mod_conf_has_depends = true;
- std::string dep = info.get("depends");
- // clang-format off
- dep.erase(std::remove_if(dep.begin(), dep.end(),
- static_cast<int (*)(int)>(&std::isspace)), dep.end());
- // clang-format on
- for (const auto &dependency : str_split(dep, ',')) {
- spec.depends.insert(dependency);
- }
+ if (info.exists("author"))
+ spec.author = info.get("author");
+
+ if (info.exists("release"))
+ spec.release = info.getS32("release");
+
+ // Attempt to load dependencies from mod.conf
+ bool mod_conf_has_depends = false;
+ if (info.exists("depends")) {
+ mod_conf_has_depends = true;
+ std::string dep = info.get("depends");
+ // clang-format off
+ dep.erase(std::remove_if(dep.begin(), dep.end(),
+ static_cast<int (*)(int)>(&std::isspace)), dep.end());
+ // clang-format on
+ for (const auto &dependency : str_split(dep, ',')) {
+ spec.depends.insert(dependency);
}
+ }
- if (info.exists("optional_depends")) {
- mod_conf_has_depends = true;
- std::string dep = info.get("optional_depends");
- // clang-format off
- dep.erase(std::remove_if(dep.begin(), dep.end(),
- static_cast<int (*)(int)>(&std::isspace)), dep.end());
- // clang-format on
- for (const auto &dependency : str_split(dep, ',')) {
- spec.optdepends.insert(dependency);
- }
+ if (info.exists("optional_depends")) {
+ mod_conf_has_depends = true;
+ std::string dep = info.get("optional_depends");
+ // clang-format off
+ dep.erase(std::remove_if(dep.begin(), dep.end(),
+ static_cast<int (*)(int)>(&std::isspace)), dep.end());
+ // clang-format on
+ for (const auto &dependency : str_split(dep, ',')) {
+ spec.optdepends.insert(dependency);
}
+ }
- // Fallback to depends.txt
- if (!mod_conf_has_depends) {
- std::vector<std::string> dependencies;
+ // Fallback to depends.txt
+ if (!mod_conf_has_depends) {
+ std::vector<std::string> dependencies;
- std::ifstream is((spec.path + DIR_DELIM + "depends.txt").c_str());
+ std::ifstream is((spec.path + DIR_DELIM + "depends.txt").c_str());
- if (is.good())
- spec.deprecation_msgs.push_back("depends.txt is deprecated, please use mod.conf instead.");
+ if (is.good())
+ spec.deprecation_msgs.push_back("depends.txt is deprecated, please use mod.conf instead.");
- while (is.good()) {
- std::string dep;
- std::getline(is, dep);
- dependencies.push_back(dep);
- }
+ while (is.good()) {
+ std::string dep;
+ std::getline(is, dep);
+ dependencies.push_back(dep);
+ }
- for (auto &dependency : dependencies) {
- std::unordered_set<char> symbols;
- if (parseDependsString(dependency, symbols)) {
- if (symbols.count('?') != 0) {
- spec.optdepends.insert(dependency);
- } else {
- spec.depends.insert(dependency);
- }
+ for (auto &dependency : dependencies) {
+ std::unordered_set<char> symbols;
+ if (parseDependsString(dependency, symbols)) {
+ if (symbols.count('?') != 0) {
+ spec.optdepends.insert(dependency);
+ } else {
+ spec.depends.insert(dependency);
}
}
}
-
- if (info.exists("description"))
- spec.desc = info.get("description");
- else if (fs::ReadFile(spec.path + DIR_DELIM + "description.txt", spec.desc))
- spec.deprecation_msgs.push_back("description.txt is deprecated, please use mod.conf instead.");
}
+
+ if (info.exists("description"))
+ spec.desc = info.get("description");
+ else if (fs::ReadFile(spec.path + DIR_DELIM + "description.txt", spec.desc))
+ spec.deprecation_msgs.push_back("description.txt is deprecated, please use mod.conf instead.");
+
+ return true;
}
std::map<std::string, ModSpec> getModsInPath(
- const std::string &path, bool part_of_modpack)
+ const std::string &path, const std::string &virtual_path, bool part_of_modpack)
{
// NOTE: this function works in mutual recursion with parseModContents
std::map<std::string, ModSpec> result;
std::vector<fs::DirListNode> dirlist = fs::GetDirListing(path);
- std::string modpath;
+ std::string mod_path;
+ std::string mod_virtual_path;
for (const fs::DirListNode &dln : dirlist) {
if (!dln.dir)
@@ -185,10 +184,14 @@ std::map<std::string, ModSpec> getModsInPath(
if (modname[0] == '.')
continue;
- modpath.clear();
- modpath.append(path).append(DIR_DELIM).append(modname);
+ mod_path.clear();
+ mod_path.append(path).append(DIR_DELIM).append(modname);
- ModSpec spec(modname, modpath, part_of_modpack);
+ mod_virtual_path.clear();
+ // Intentionally uses / to keep paths same on different platforms
+ mod_virtual_path.append(virtual_path).append("/").append(modname);
+
+ ModSpec spec(modname, mod_path, part_of_modpack, mod_virtual_path);
parseModContents(spec);
result.insert(std::make_pair(modname, spec));
}
@@ -213,215 +216,6 @@ std::vector<ModSpec> flattenMods(const std::map<std::string, ModSpec> &mods)
return result;
}
-ModConfiguration::ModConfiguration(const std::string &worldpath)
-{
-}
-
-void ModConfiguration::printUnsatisfiedModsError() const
-{
- for (const ModSpec &mod : m_unsatisfied_mods) {
- errorstream << "mod \"" << mod.name
- << "\" has unsatisfied dependencies: ";
- for (const std::string &unsatisfied_depend : mod.unsatisfied_depends)
- errorstream << " \"" << unsatisfied_depend << "\"";
- errorstream << std::endl;
- }
-}
-
-void ModConfiguration::addModsInPath(const std::string &path)
-{
- addMods(flattenMods(getModsInPath(path)));
-}
-
-void ModConfiguration::addMods(const std::vector<ModSpec> &new_mods)
-{
- // Maintain a map of all existing m_unsatisfied_mods.
- // Keys are mod names and values are indices into m_unsatisfied_mods.
- std::map<std::string, u32> existing_mods;
- for (u32 i = 0; i < m_unsatisfied_mods.size(); ++i) {
- existing_mods[m_unsatisfied_mods[i].name] = i;
- }
-
- // Add new mods
- for (int want_from_modpack = 1; want_from_modpack >= 0; --want_from_modpack) {
- // First iteration:
- // Add all the mods that come from modpacks
- // Second iteration:
- // Add all the mods that didn't come from modpacks
-
- std::set<std::string> seen_this_iteration;
-
- for (const ModSpec &mod : new_mods) {
- if (mod.part_of_modpack != (bool)want_from_modpack)
- continue;
-
- if (existing_mods.count(mod.name) == 0) {
- // GOOD CASE: completely new mod.
- m_unsatisfied_mods.push_back(mod);
- existing_mods[mod.name] = m_unsatisfied_mods.size() - 1;
- } else if (seen_this_iteration.count(mod.name) == 0) {
- // BAD CASE: name conflict in different levels.
- u32 oldindex = existing_mods[mod.name];
- const ModSpec &oldmod = m_unsatisfied_mods[oldindex];
- warningstream << "Mod name conflict detected: \""
- << mod.name << "\"" << std::endl
- << "Will not load: " << oldmod.path
- << std::endl
- << "Overridden by: " << mod.path
- << std::endl;
- m_unsatisfied_mods[oldindex] = mod;
-
- // If there was a "VERY BAD CASE" name conflict
- // in an earlier level, ignore it.
- m_name_conflicts.erase(mod.name);
- } else {
- // VERY BAD CASE: name conflict in the same level.
- u32 oldindex = existing_mods[mod.name];
- const ModSpec &oldmod = m_unsatisfied_mods[oldindex];
- warningstream << "Mod name conflict detected: \""
- << mod.name << "\"" << std::endl
- << "Will not load: " << oldmod.path
- << std::endl
- << "Will not load: " << mod.path
- << std::endl;
- m_unsatisfied_mods[oldindex] = mod;
- m_name_conflicts.insert(mod.name);
- }
-
- seen_this_iteration.insert(mod.name);
- }
- }
-}
-
-void ModConfiguration::addModsFromConfig(
- const std::string &settings_path, const std::set<std::string> &mods)
-{
- Settings conf;
- std::set<std::string> load_mod_names;
-
- conf.readConfigFile(settings_path.c_str());
- std::vector<std::string> names = conf.getNames();
- for (const std::string &name : names) {
- if (name.compare(0, 9, "load_mod_") == 0 && conf.get(name) != "false" &&
- conf.get(name) != "nil")
- load_mod_names.insert(name.substr(9));
- }
-
- std::vector<ModSpec> addon_mods;
- for (const std::string &i : mods) {
- std::vector<ModSpec> addon_mods_in_path = flattenMods(getModsInPath(i));
- for (std::vector<ModSpec>::const_iterator it = addon_mods_in_path.begin();
- it != addon_mods_in_path.end(); ++it) {
- const ModSpec &mod = *it;
- if (load_mod_names.count(mod.name) != 0)
- addon_mods.push_back(mod);
- else
- conf.setBool("load_mod_" + mod.name, false);
- }
- }
- conf.updateConfigFile(settings_path.c_str());
-
- addMods(addon_mods);
- checkConflictsAndDeps();
-
- // complain about mods declared to be loaded, but not found
- for (const ModSpec &addon_mod : addon_mods)
- load_mod_names.erase(addon_mod.name);
-
- std::vector<ModSpec> unsatisfiedMods = getUnsatisfiedMods();
-
- for (const ModSpec &unsatisfiedMod : unsatisfiedMods)
- load_mod_names.erase(unsatisfiedMod.name);
-
- if (!load_mod_names.empty()) {
- errorstream << "The following mods could not be found:";
- for (const std::string &mod : load_mod_names)
- errorstream << " \"" << mod << "\"";
- errorstream << std::endl;
- }
-}
-
-void ModConfiguration::checkConflictsAndDeps()
-{
- // report on name conflicts
- if (!m_name_conflicts.empty()) {
- std::string s = "Unresolved name conflicts for mods ";
- for (std::unordered_set<std::string>::const_iterator it =
- m_name_conflicts.begin();
- it != m_name_conflicts.end(); ++it) {
- if (it != m_name_conflicts.begin())
- s += ", ";
- s += std::string("\"") + (*it) + "\"";
- }
- s += ".";
- throw ModError(s);
- }
-
- // get the mods in order
- resolveDependencies();
-}
-
-void ModConfiguration::resolveDependencies()
-{
- // Step 1: Compile a list of the mod names we're working with
- std::set<std::string> modnames;
- for (const ModSpec &mod : m_unsatisfied_mods) {
- modnames.insert(mod.name);
- }
-
- // Step 2: get dependencies (including optional dependencies)
- // of each mod, split mods into satisfied and unsatisfied
- std::list<ModSpec> satisfied;
- std::list<ModSpec> unsatisfied;
- for (ModSpec mod : m_unsatisfied_mods) {
- mod.unsatisfied_depends = mod.depends;
- // check which optional dependencies actually exist
- for (const std::string &optdep : mod.optdepends) {
- if (modnames.count(optdep) != 0)
- mod.unsatisfied_depends.insert(optdep);
- }
- // if a mod has no depends it is initially satisfied
- if (mod.unsatisfied_depends.empty())
- satisfied.push_back(mod);
- else
- unsatisfied.push_back(mod);
- }
-
- // Step 3: mods without unmet dependencies can be appended to
- // the sorted list.
- while (!satisfied.empty()) {
- ModSpec mod = satisfied.back();
- m_sorted_mods.push_back(mod);
- satisfied.pop_back();
- for (auto it = unsatisfied.begin(); it != unsatisfied.end();) {
- ModSpec &mod2 = *it;
- mod2.unsatisfied_depends.erase(mod.name);
- if (mod2.unsatisfied_depends.empty()) {
- satisfied.push_back(mod2);
- it = unsatisfied.erase(it);
- } else {
- ++it;
- }
- }
- }
-
- // Step 4: write back list of unsatisfied mods
- m_unsatisfied_mods.assign(unsatisfied.begin(), unsatisfied.end());
-}
-
-#ifndef SERVER
-ClientModConfiguration::ClientModConfiguration(const std::string &path) :
- ModConfiguration(path)
-{
- std::set<std::string> paths;
- std::string path_user = porting::path_user + DIR_DELIM + "clientmods";
- paths.insert(path);
- paths.insert(path_user);
-
- std::string settings_path = path_user + DIR_DELIM + "mods.conf";
- addModsFromConfig(settings_path, paths);
-}
-#endif
ModMetadata::ModMetadata(const std::string &mod_name, ModMetadataDatabase *database):
m_mod_name(mod_name), m_database(database)
diff --git a/src/content/mods.h b/src/content/mods.h
index dd3b6e0e6..5ed5b8171 100644
--- a/src/content/mods.h
+++ b/src/content/mods.h
@@ -30,6 +30,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "util/basic_macros.h"
#include "config.h"
#include "metadata.h"
+#include "subgames.h"
class ModMetadataDatabase;
@@ -51,101 +52,63 @@ struct ModSpec
bool part_of_modpack = false;
bool is_modpack = false;
+ /**
+ * A constructed canonical path to represent this mod's location.
+ * This intended to be used as an identifier for a modpath that tolerates file movement,
+ * and cannot be used to read the mod files.
+ *
+ * Note that `mymod` is the directory name, not the mod name specified in mod.conf.
+ *
+ * Ex:
+ *
+ * - mods/mymod
+ * - mods/mymod (1)
+ * (^ this would have name=mymod in mod.conf)
+ * - mods/modpack1/mymod
+ * - games/mygame/mods/mymod
+ * - worldmods/mymod
+ */
+ std::string virtual_path;
+
// For logging purposes
std::vector<const char *> deprecation_msgs;
// if modpack:
std::map<std::string, ModSpec> modpack_content;
- ModSpec(const std::string &name = "", const std::string &path = "") :
- name(name), path(path)
+
+ ModSpec()
{
}
- ModSpec(const std::string &name, const std::string &path, bool part_of_modpack) :
- name(name), path(path), part_of_modpack(part_of_modpack)
+
+ ModSpec(const std::string &name, const std::string &path, bool part_of_modpack, const std::string &virtual_path) :
+ name(name), path(path), part_of_modpack(part_of_modpack), virtual_path(virtual_path)
{
}
void checkAndLog() const;
};
-// Retrieves depends, optdepends, is_modpack and modpack_content
-void parseModContents(ModSpec &mod);
-
-std::map<std::string, ModSpec> getModsInPath(
- const std::string &path, bool part_of_modpack = false);
+/**
+ * Retrieves depends, optdepends, is_modpack and modpack_content
+ *
+ * @returns false if not a mod
+ */
+bool parseModContents(ModSpec &mod);
+
+/**
+ * Gets a list of all mods and modpacks in path
+ *
+ * @param Path to search, should be absolute
+ * @param part_of_modpack Is this searching within a modpack?
+ * @param virtual_path Virtual path for this directory, see comment in ModSpec
+ * @returns map of mods
+ */
+std::map<std::string, ModSpec> getModsInPath(const std::string &path,
+ const std::string &virtual_path, bool part_of_modpack = false);
// replaces modpack Modspecs with their content
std::vector<ModSpec> flattenMods(const std::map<std::string, ModSpec> &mods);
-// a ModConfiguration is a subset of installed mods, expected to have
-// all dependencies fullfilled, so it can be used as a list of mods to
-// load when the game starts.
-class ModConfiguration
-{
-public:
- // checks if all dependencies are fullfilled.
- bool isConsistent() const { return m_unsatisfied_mods.empty(); }
-
- const std::vector<ModSpec> &getMods() const { return m_sorted_mods; }
-
- const std::vector<ModSpec> &getUnsatisfiedMods() const
- {
- return m_unsatisfied_mods;
- }
-
- void printUnsatisfiedModsError() const;
-
-protected:
- ModConfiguration(const std::string &worldpath);
- // adds all mods in the given path. used for games, modpacks
- // and world-specific mods (worldmods-folders)
- void addModsInPath(const std::string &path);
-
- // adds all mods in the set.
- void addMods(const std::vector<ModSpec> &new_mods);
-
- void addModsFromConfig(const std::string &settings_path,
- const std::set<std::string> &mods);
-
- void checkConflictsAndDeps();
-
-protected:
- // list of mods sorted such that they can be loaded in the
- // given order with all dependencies being fullfilled. I.e.,
- // every mod in this list has only dependencies on mods which
- // appear earlier in the vector.
- std::vector<ModSpec> m_sorted_mods;
-
-private:
- // move mods from m_unsatisfied_mods to m_sorted_mods
- // in an order that satisfies dependencies
- void resolveDependencies();
-
- // mods with unmet dependencies. Before dependencies are resolved,
- // this is where all mods are stored. Afterwards this contains
- // only the ones with really unsatisfied dependencies.
- std::vector<ModSpec> m_unsatisfied_mods;
-
- // set of mod names for which an unresolved name conflict
- // exists. A name conflict happens when two or more mods
- // at the same level have the same name but different paths.
- // Levels (mods in higher levels override mods in lower levels):
- // 1. game mod in modpack; 2. game mod;
- // 3. world mod in modpack; 4. world mod;
- // 5. addon mod in modpack; 6. addon mod.
- std::unordered_set<std::string> m_name_conflicts;
-
- // Deleted default constructor
- ModConfiguration() = default;
-};
-
-#ifndef SERVER
-class ClientModConfiguration : public ModConfiguration
-{
-public:
- ClientModConfiguration(const std::string &path);
-};
-#endif
class ModMetadata : public Metadata
{
diff --git a/src/content/subgames.cpp b/src/content/subgames.cpp
index 62e82e0e4..d0de926ef 100644
--- a/src/content/subgames.cpp
+++ b/src/content/subgames.cpp
@@ -17,6 +17,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include <common/c_internal.h>
#include "content/subgames.h"
#include "porting.h"
#include "filesys.h"
@@ -45,6 +46,25 @@ bool getGameMinetestConfig(const std::string &game_path, Settings &conf)
}
+
+void SubgameSpec::checkAndLog() const
+{
+ // Log deprecation messages
+ auto handling_mode = get_deprecated_handling_mode();
+ if (!deprecation_msgs.empty() && handling_mode != DeprecatedHandlingMode::Ignore) {
+ std::ostringstream os;
+ os << "Game " << title << " at " << path << ":" << std::endl;
+ for (auto msg : deprecation_msgs)
+ os << "\t" << msg << std::endl;
+
+ if (handling_mode == DeprecatedHandlingMode::Error)
+ throw ModError(os.str());
+ else
+ warningstream << os.str();
+ }
+}
+
+
struct GameFindPath
{
std::string path;
@@ -107,14 +127,13 @@ SubgameSpec findSubgame(const std::string &id)
std::string gamemod_path = game_path + DIR_DELIM + "mods";
// Find mod directories
- std::set<std::string> mods_paths;
- if (!user_game)
- mods_paths.insert(share + DIR_DELIM + "mods");
- if (user != share || user_game)
- mods_paths.insert(user + DIR_DELIM + "mods");
+ std::unordered_map<std::string, std::string> mods_paths;
+ mods_paths["mods"] = user + DIR_DELIM + "mods";
+ if (!user_game && user != share)
+ mods_paths["share"] = share + DIR_DELIM + "mods";
for (const std::string &mod_path : getEnvModPaths()) {
- mods_paths.insert(mod_path);
+ mods_paths[fs::AbsolutePath(mod_path)] = mod_path;
}
// Get meta
@@ -122,11 +141,13 @@ SubgameSpec findSubgame(const std::string &id)
Settings conf;
conf.readConfigFile(conf_path.c_str());
- std::string game_name;
- if (conf.exists("name"))
- game_name = conf.get("name");
+ std::string game_title;
+ if (conf.exists("title"))
+ game_title = conf.get("title");
+ else if (conf.exists("name"))
+ game_title = conf.get("name");
else
- game_name = id;
+ game_title = id;
std::string game_author;
if (conf.exists("author"))
@@ -141,8 +162,14 @@ SubgameSpec findSubgame(const std::string &id)
menuicon_path = getImagePath(
game_path + DIR_DELIM + "menu" + DIR_DELIM + "icon.png");
#endif
- return SubgameSpec(id, game_path, gamemod_path, mods_paths, game_name,
+
+ SubgameSpec spec(id, game_path, gamemod_path, mods_paths, game_title,
menuicon_path, game_author, game_release);
+
+ if (conf.exists("name") && !conf.exists("title"))
+ spec.deprecation_msgs.push_back("\"name\" setting in game.conf is deprecated, please use \"title\" instead");
+
+ return spec;
}
SubgameSpec findWorldSubgame(const std::string &world_path)
@@ -160,10 +187,12 @@ SubgameSpec findWorldSubgame(const std::string &world_path)
std::string conf_path = world_gamepath + DIR_DELIM + "game.conf";
conf.readConfigFile(conf_path.c_str());
- if (conf.exists("name"))
- gamespec.name = conf.get("name");
+ if (conf.exists("title"))
+ gamespec.title = conf.get("title");
+ else if (conf.exists("name"))
+ gamespec.title = conf.get("name");
else
- gamespec.name = world_gameid;
+ gamespec.title = world_gameid;
return gamespec;
}
diff --git a/src/content/subgames.h b/src/content/subgames.h
index 4a50803e8..d5d168243 100644
--- a/src/content/subgames.h
+++ b/src/content/subgames.h
@@ -21,6 +21,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include <string>
#include <set>
+#include <unordered_map>
#include <vector>
class Settings;
@@ -28,29 +29,36 @@ class Settings;
struct SubgameSpec
{
std::string id;
- std::string name;
+ std::string title;
std::string author;
int release;
std::string path;
std::string gamemods_path;
- std::set<std::string> addon_mods_paths;
+
+ /**
+ * Map from virtual path to mods path
+ */
+ std::unordered_map<std::string, std::string> addon_mods_paths;
std::string menuicon_path;
+ // For logging purposes
+ std::vector<const char *> deprecation_msgs;
+
SubgameSpec(const std::string &id = "", const std::string &path = "",
const std::string &gamemods_path = "",
- const std::set<std::string> &addon_mods_paths =
- std::set<std::string>(),
- const std::string &name = "",
+ const std::unordered_map<std::string, std::string> &addon_mods_paths = {},
+ const std::string &title = "",
const std::string &menuicon_path = "",
const std::string &author = "", int release = 0) :
id(id),
- name(name), author(author), release(release), path(path),
+ title(title), author(author), release(release), path(path),
gamemods_path(gamemods_path), addon_mods_paths(addon_mods_paths),
menuicon_path(menuicon_path)
{
}
bool isValid() const { return (!id.empty() && !path.empty()); }
+ void checkAndLog() const;
};
SubgameSpec findSubgame(const std::string &id);