mirror of
https://github.com/luanti-org/luanti.git
synced 2025-06-27 16:36:03 +00:00
Refactor ModConfiguration
This commit is contained in:
parent
1d512ef7f4
commit
06de82fd86
10 changed files with 501 additions and 409 deletions
|
@ -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,91 +79,89 @@ 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 (modpack2_is.good())
|
||||
modpack2_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;
|
||||
}
|
||||
|
||||
} 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.");
|
||||
Settings info;
|
||||
info.readConfigFile((spec.path + DIR_DELIM + "mod.conf").c_str());
|
||||
|
||||
if (info.exists("author"))
|
||||
spec.author = info.get("author");
|
||||
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("release"))
|
||||
spec.release = info.getS32("release");
|
||||
if (info.exists("author"))
|
||||
spec.author = info.get("author");
|
||||
|
||||
// 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("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);
|
||||
}
|
||||
}
|
||||
|
||||
// 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")) {
|
||||
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);
|
||||
}
|
||||
|
||||
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(
|
||||
|
@ -218,240 +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, 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):
|
||||
m_mod_name(mod_name), m_database(database)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue