mirror of
https://github.com/minetest/minetest.git
synced 2024-11-23 16:13:46 +01:00
Refactor ModConfiguration
This commit is contained in:
parent
1d512ef7f4
commit
06de82fd86
@ -58,6 +58,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||||||
#include "game.h"
|
#include "game.h"
|
||||||
#include "chatmessage.h"
|
#include "chatmessage.h"
|
||||||
#include "translation.h"
|
#include "translation.h"
|
||||||
|
#include "content/mod_configuration.h"
|
||||||
|
|
||||||
extern gui::IGUIEnvironment* guienv;
|
extern gui::IGUIEnvironment* guienv;
|
||||||
|
|
||||||
@ -196,7 +197,21 @@ void Client::loadMods()
|
|||||||
scanModIntoMemory(BUILTIN_MOD_NAME, getBuiltinLuaPath());
|
scanModIntoMemory(BUILTIN_MOD_NAME, getBuiltinLuaPath());
|
||||||
m_script->loadModFromMemory(BUILTIN_MOD_NAME);
|
m_script->loadModFromMemory(BUILTIN_MOD_NAME);
|
||||||
|
|
||||||
ClientModConfiguration modconf(getClientModsLuaPath());
|
ModConfiguration modconf;
|
||||||
|
{
|
||||||
|
std::unordered_map<std::string, std::string> paths;
|
||||||
|
std::string path_user = porting::path_user + DIR_DELIM + "clientmods";
|
||||||
|
const auto modsPath = getClientModsLuaPath();
|
||||||
|
if (modsPath != path_user) {
|
||||||
|
paths["share"] = modsPath;
|
||||||
|
}
|
||||||
|
paths["mods"] = path_user;
|
||||||
|
|
||||||
|
std::string settings_path = path_user + DIR_DELIM + "mods.conf";
|
||||||
|
modconf.addModsFromConfig(settings_path, paths);
|
||||||
|
modconf.checkConflictsAndDeps();
|
||||||
|
}
|
||||||
|
|
||||||
m_mods = modconf.getMods();
|
m_mods = modconf.getMods();
|
||||||
// complain about mods with unsatisfied dependencies
|
// complain about mods with unsatisfied dependencies
|
||||||
if (!modconf.isConsistent()) {
|
if (!modconf.isConsistent()) {
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
set(content_SRCS
|
set(content_SRCS
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/content.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/content.cpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/mod_configuration.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/mods.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/mods.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/subgames.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/subgames.cpp
|
||||||
PARENT_SCOPE
|
PARENT_SCOPE
|
||||||
|
255
src/content/mod_configuration.cpp
Normal file
255
src/content/mod_configuration.cpp
Normal file
@ -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());
|
||||||
|
}
|
111
src/content/mod_configuration.h
Normal file
111
src/content/mod_configuration.h
Normal file
@ -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;
|
||||||
|
};
|
@ -69,7 +69,7 @@ bool parseDependsString(std::string &dep, std::unordered_set<char> &symbols)
|
|||||||
return !dep.empty();
|
return !dep.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
void parseModContents(ModSpec &spec)
|
bool parseModContents(ModSpec &spec)
|
||||||
{
|
{
|
||||||
// NOTE: this function works in mutual recursion with getModsInPath
|
// NOTE: this function works in mutual recursion with getModsInPath
|
||||||
|
|
||||||
@ -79,91 +79,89 @@ void parseModContents(ModSpec &spec)
|
|||||||
spec.modpack_content.clear();
|
spec.modpack_content.clear();
|
||||||
|
|
||||||
// Handle modpacks (defined by containing modpack.txt)
|
// Handle modpacks (defined by containing modpack.txt)
|
||||||
std::ifstream modpack_is((spec.path + DIR_DELIM + "modpack.txt").c_str());
|
if (fs::IsFile(spec.path + DIR_DELIM + "modpack.txt") ||
|
||||||
std::ifstream modpack2_is((spec.path + DIR_DELIM + "modpack.conf").c_str());
|
fs::IsFile(spec.path + DIR_DELIM + "modpack.conf")) {
|
||||||
if (modpack_is.good() || modpack2_is.good()) {
|
|
||||||
if (modpack_is.good())
|
|
||||||
modpack_is.close();
|
|
||||||
|
|
||||||
if (modpack2_is.good())
|
|
||||||
modpack2_is.close();
|
|
||||||
|
|
||||||
spec.is_modpack = true;
|
spec.is_modpack = true;
|
||||||
spec.modpack_content = getModsInPath(spec.path, spec.virtual_path, 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;
|
||||||
|
}
|
||||||
|
|
||||||
} else {
|
|
||||||
Settings info;
|
|
||||||
info.readConfigFile((spec.path + DIR_DELIM + "mod.conf").c_str());
|
|
||||||
|
|
||||||
if (info.exists("name"))
|
Settings info;
|
||||||
spec.name = info.get("name");
|
info.readConfigFile((spec.path + DIR_DELIM + "mod.conf").c_str());
|
||||||
else
|
|
||||||
spec.deprecation_msgs.push_back("Mods not having a mod.conf file with the name is deprecated.");
|
|
||||||
|
|
||||||
if (info.exists("author"))
|
if (info.exists("name"))
|
||||||
spec.author = info.get("author");
|
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("release"))
|
if (info.exists("author"))
|
||||||
spec.release = info.getS32("release");
|
spec.author = info.get("author");
|
||||||
|
|
||||||
// Attempt to load dependencies from mod.conf
|
if (info.exists("release"))
|
||||||
bool mod_conf_has_depends = false;
|
spec.release = info.getS32("release");
|
||||||
if (info.exists("depends")) {
|
|
||||||
mod_conf_has_depends = true;
|
// Attempt to load dependencies from mod.conf
|
||||||
std::string dep = info.get("depends");
|
bool mod_conf_has_depends = false;
|
||||||
// clang-format off
|
if (info.exists("depends")) {
|
||||||
dep.erase(std::remove_if(dep.begin(), dep.end(),
|
mod_conf_has_depends = true;
|
||||||
static_cast<int (*)(int)>(&std::isspace)), dep.end());
|
std::string dep = info.get("depends");
|
||||||
// clang-format on
|
// clang-format off
|
||||||
for (const auto &dependency : str_split(dep, ',')) {
|
dep.erase(std::remove_if(dep.begin(), dep.end(),
|
||||||
spec.depends.insert(dependency);
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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());
|
||||||
|
|
||||||
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (info.exists("optional_depends")) {
|
for (auto &dependency : dependencies) {
|
||||||
mod_conf_has_depends = true;
|
std::unordered_set<char> symbols;
|
||||||
std::string dep = info.get("optional_depends");
|
if (parseDependsString(dependency, symbols)) {
|
||||||
// clang-format off
|
if (symbols.count('?') != 0) {
|
||||||
dep.erase(std::remove_if(dep.begin(), dep.end(),
|
spec.optdepends.insert(dependency);
|
||||||
static_cast<int (*)(int)>(&std::isspace)), dep.end());
|
} else {
|
||||||
// clang-format on
|
spec.depends.insert(dependency);
|
||||||
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;
|
|
||||||
|
|
||||||
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.");
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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(
|
std::map<std::string, ModSpec> getModsInPath(
|
||||||
@ -218,240 +216,6 @@ std::vector<ModSpec> flattenMods(const std::map<std::string, ModSpec> &mods)
|
|||||||
return result;
|
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, 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::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;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<ModSpec> addon_mods;
|
|
||||||
std::unordered_map<std::string, std::vector<std::string>> candidates;
|
|
||||||
|
|
||||||
for (const auto &modPath : modPaths) {
|
|
||||||
std::vector<ModSpec> addon_mods_in_path = flattenMods(getModsInPath(modPath.second, modPath.first));
|
|
||||||
for (std::vector<ModSpec>::const_iterator it = addon_mods_in_path.begin();
|
|
||||||
it != addon_mods_in_path.end(); ++it) {
|
|
||||||
const ModSpec &mod = *it;
|
|
||||||
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);
|
|
||||||
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 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 ";
|
|
||||||
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::unordered_map<std::string, std::string> paths;
|
|
||||||
std::string path_user = porting::path_user + DIR_DELIM + "clientmods";
|
|
||||||
if (path != path_user) {
|
|
||||||
paths["share"] = path;
|
|
||||||
}
|
|
||||||
paths["mods"] = 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):
|
ModMetadata::ModMetadata(const std::string &mod_name, ModMetadataDatabase *database):
|
||||||
m_mod_name(mod_name), m_database(database)
|
m_mod_name(mod_name), m_database(database)
|
||||||
|
@ -30,6 +30,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||||||
#include "util/basic_macros.h"
|
#include "util/basic_macros.h"
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
#include "metadata.h"
|
#include "metadata.h"
|
||||||
|
#include "subgames.h"
|
||||||
|
|
||||||
class ModMetadataDatabase;
|
class ModMetadataDatabase;
|
||||||
|
|
||||||
@ -87,8 +88,12 @@ struct ModSpec
|
|||||||
void checkAndLog() const;
|
void checkAndLog() const;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Retrieves depends, optdepends, is_modpack and modpack_content
|
/**
|
||||||
void parseModContents(ModSpec &mod);
|
* 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
|
* Gets a list of all mods and modpacks in path
|
||||||
@ -104,85 +109,6 @@ std::map<std::string, ModSpec> getModsInPath(const std::string &path,
|
|||||||
// replaces modpack Modspecs with their content
|
// replaces modpack Modspecs with their content
|
||||||
std::vector<ModSpec> flattenMods(const std::map<std::string, ModSpec> &mods);
|
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)
|
|
||||||
*
|
|
||||||
* @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 the set.
|
|
||||||
void addMods(const std::vector<ModSpec> &new_mods);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @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);
|
|
||||||
|
|
||||||
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
|
class ModMetadata : public Metadata
|
||||||
{
|
{
|
||||||
|
@ -877,4 +877,3 @@ bool Rename(const std::string &from, const std::string &to)
|
|||||||
}
|
}
|
||||||
|
|
||||||
} // namespace fs
|
} // namespace fs
|
||||||
|
|
||||||
|
@ -60,6 +60,11 @@ bool IsPathAbsolute(const std::string &path);
|
|||||||
|
|
||||||
bool IsDir(const std::string &path);
|
bool IsDir(const std::string &path);
|
||||||
|
|
||||||
|
inline bool IsFile(const std::string &path)
|
||||||
|
{
|
||||||
|
return PathExists(path) && !IsDir(path);
|
||||||
|
}
|
||||||
|
|
||||||
bool IsDirDelimiter(char c);
|
bool IsDirDelimiter(char c);
|
||||||
|
|
||||||
// Only pass full paths to this one. True on success.
|
// Only pass full paths to this one. True on success.
|
||||||
|
@ -23,7 +23,6 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||||||
#include "scripting_server.h"
|
#include "scripting_server.h"
|
||||||
#include "content/subgames.h"
|
#include "content/subgames.h"
|
||||||
#include "porting.h"
|
#include "porting.h"
|
||||||
#include "util/metricsbackend.h"
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Manage server mods
|
* Manage server mods
|
||||||
@ -35,20 +34,19 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||||||
* Creates a ServerModManager which targets worldpath
|
* Creates a ServerModManager which targets worldpath
|
||||||
* @param worldpath
|
* @param worldpath
|
||||||
*/
|
*/
|
||||||
ServerModManager::ServerModManager(const std::string &worldpath) :
|
ServerModManager::ServerModManager(const std::string &worldpath):
|
||||||
ModConfiguration(worldpath)
|
configuration()
|
||||||
{
|
{
|
||||||
SubgameSpec gamespec = findWorldSubgame(worldpath);
|
SubgameSpec gamespec = findWorldSubgame(worldpath);
|
||||||
|
|
||||||
// Add all game mods and all world mods
|
// Add all game mods and all world mods
|
||||||
std::string game_virtual_path;
|
configuration.addGameMods(gamespec);
|
||||||
game_virtual_path.append("games/").append(gamespec.id).append("/mods");
|
configuration.addModsInPath(worldpath + DIR_DELIM + "worldmods", "worldmods");
|
||||||
addModsInPath(gamespec.gamemods_path, game_virtual_path);
|
|
||||||
addModsInPath(worldpath + DIR_DELIM + "worldmods", "worldmods");
|
|
||||||
|
|
||||||
// Load normal mods
|
// Load normal mods
|
||||||
std::string worldmt = worldpath + DIR_DELIM + "world.mt";
|
std::string worldmt = worldpath + DIR_DELIM + "world.mt";
|
||||||
addModsFromConfig(worldmt, gamespec.addon_mods_paths);
|
configuration.addModsFromConfig(worldmt, gamespec.addon_mods_paths);
|
||||||
|
configuration.checkConflictsAndDeps();
|
||||||
}
|
}
|
||||||
|
|
||||||
// clang-format off
|
// clang-format off
|
||||||
@ -57,12 +55,13 @@ void ServerModManager::loadMods(ServerScripting *script)
|
|||||||
{
|
{
|
||||||
// Print mods
|
// Print mods
|
||||||
infostream << "Server: Loading mods: ";
|
infostream << "Server: Loading mods: ";
|
||||||
for (const ModSpec &mod : m_sorted_mods) {
|
for (const ModSpec &mod : configuration.getMods()) {
|
||||||
infostream << mod.name << " ";
|
infostream << mod.name << " ";
|
||||||
}
|
}
|
||||||
|
|
||||||
infostream << std::endl;
|
infostream << std::endl;
|
||||||
// Load and run "mod" scripts
|
// Load and run "mod" scripts
|
||||||
for (const ModSpec &mod : m_sorted_mods) {
|
for (const ModSpec &mod : configuration.getMods()) {
|
||||||
mod.checkAndLog();
|
mod.checkAndLog();
|
||||||
|
|
||||||
std::string script_path = mod.path + DIR_DELIM + "init.lua";
|
std::string script_path = mod.path + DIR_DELIM + "init.lua";
|
||||||
@ -79,25 +78,23 @@ void ServerModManager::loadMods(ServerScripting *script)
|
|||||||
// clang-format on
|
// clang-format on
|
||||||
const ModSpec *ServerModManager::getModSpec(const std::string &modname) const
|
const ModSpec *ServerModManager::getModSpec(const std::string &modname) const
|
||||||
{
|
{
|
||||||
std::vector<ModSpec>::const_iterator it;
|
for (const auto &mod : configuration.getMods()) {
|
||||||
for (it = m_sorted_mods.begin(); it != m_sorted_mods.end(); ++it) {
|
|
||||||
const ModSpec &mod = *it;
|
|
||||||
if (mod.name == modname)
|
if (mod.name == modname)
|
||||||
return &mod;
|
return &mod;
|
||||||
}
|
}
|
||||||
return NULL;
|
|
||||||
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ServerModManager::getModNames(std::vector<std::string> &modlist) const
|
void ServerModManager::getModNames(std::vector<std::string> &modlist) const
|
||||||
{
|
{
|
||||||
for (const ModSpec &spec : m_sorted_mods)
|
for (const ModSpec &spec : configuration.getMods())
|
||||||
modlist.push_back(spec.name);
|
modlist.push_back(spec.name);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ServerModManager::getModsMediaPaths(std::vector<std::string> &paths) const
|
void ServerModManager::getModsMediaPaths(std::vector<std::string> &paths) const
|
||||||
{
|
{
|
||||||
for (auto it = m_sorted_mods.crbegin(); it != m_sorted_mods.crend(); it++) {
|
for (const auto &spec : configuration.getMods()) {
|
||||||
const ModSpec &spec = *it;
|
|
||||||
fs::GetRecursiveDirs(paths, spec.path + DIR_DELIM + "textures");
|
fs::GetRecursiveDirs(paths, spec.path + DIR_DELIM + "textures");
|
||||||
fs::GetRecursiveDirs(paths, spec.path + DIR_DELIM + "sounds");
|
fs::GetRecursiveDirs(paths, spec.path + DIR_DELIM + "sounds");
|
||||||
fs::GetRecursiveDirs(paths, spec.path + DIR_DELIM + "media");
|
fs::GetRecursiveDirs(paths, spec.path + DIR_DELIM + "media");
|
||||||
|
@ -19,7 +19,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "content/mods.h"
|
#include "content/mod_configuration.h"
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
class MetricsBackend;
|
class MetricsBackend;
|
||||||
@ -31,8 +31,10 @@ class ServerScripting;
|
|||||||
*
|
*
|
||||||
* All new calls to this class must be tested in test_servermodmanager.cpp
|
* All new calls to this class must be tested in test_servermodmanager.cpp
|
||||||
*/
|
*/
|
||||||
class ServerModManager : public ModConfiguration
|
class ServerModManager
|
||||||
{
|
{
|
||||||
|
ModConfiguration configuration;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
/**
|
/**
|
||||||
* Creates a ServerModManager which targets worldpath
|
* Creates a ServerModManager which targets worldpath
|
||||||
@ -42,6 +44,23 @@ public:
|
|||||||
void loadMods(ServerScripting *script);
|
void loadMods(ServerScripting *script);
|
||||||
const ModSpec *getModSpec(const std::string &modname) const;
|
const ModSpec *getModSpec(const std::string &modname) const;
|
||||||
void getModNames(std::vector<std::string> &modlist) const;
|
void getModNames(std::vector<std::string> &modlist) const;
|
||||||
|
|
||||||
|
inline const std::vector<ModSpec> &getMods() const {
|
||||||
|
return configuration.getMods();
|
||||||
|
}
|
||||||
|
|
||||||
|
inline const std::vector<ModSpec> &getUnsatisfiedMods() const {
|
||||||
|
return configuration.getUnsatisfiedMods();
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool isConsistent() const {
|
||||||
|
return configuration.isConsistent();
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void printUnsatisfiedModsError() const {
|
||||||
|
return configuration.printUnsatisfiedModsError();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Recursively gets all paths of mod folders that can contain media files.
|
* Recursively gets all paths of mod folders that can contain media files.
|
||||||
*
|
*
|
||||||
|
Loading…
Reference in New Issue
Block a user