mirror of
				https://github.com/luanti-org/luanti.git
				synced 2025-11-04 09:15:29 +01:00 
			
		
		
		
	Refactor ModConfiguration
This commit is contained in:
		@@ -58,6 +58,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 | 
			
		||||
#include "game.h"
 | 
			
		||||
#include "chatmessage.h"
 | 
			
		||||
#include "translation.h"
 | 
			
		||||
#include "content/mod_configuration.h"
 | 
			
		||||
 | 
			
		||||
extern gui::IGUIEnvironment* guienv;
 | 
			
		||||
 | 
			
		||||
@@ -196,7 +197,21 @@ void Client::loadMods()
 | 
			
		||||
	scanModIntoMemory(BUILTIN_MOD_NAME, getBuiltinLuaPath());
 | 
			
		||||
	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();
 | 
			
		||||
	// complain about mods with unsatisfied dependencies
 | 
			
		||||
	if (!modconf.isConsistent()) {
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										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();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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)
 | 
			
		||||
 
 | 
			
		||||
@@ -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;
 | 
			
		||||
 | 
			
		||||
@@ -87,8 +88,12 @@ struct ModSpec
 | 
			
		||||
	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
 | 
			
		||||
@@ -104,85 +109,6 @@ std::map<std::string, ModSpec> getModsInPath(const std::string &path,
 | 
			
		||||
// 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)
 | 
			
		||||
	 *
 | 
			
		||||
	 * @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
 | 
			
		||||
{
 | 
			
		||||
 
 | 
			
		||||
@@ -877,4 +877,3 @@ bool Rename(const std::string &from, const std::string &to)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
} // namespace fs
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -60,6 +60,11 @@ bool IsPathAbsolute(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);
 | 
			
		||||
 | 
			
		||||
// 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 "content/subgames.h"
 | 
			
		||||
#include "porting.h"
 | 
			
		||||
#include "util/metricsbackend.h"
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 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
 | 
			
		||||
 * @param worldpath
 | 
			
		||||
 */
 | 
			
		||||
ServerModManager::ServerModManager(const std::string &worldpath) :
 | 
			
		||||
		ModConfiguration(worldpath)
 | 
			
		||||
ServerModManager::ServerModManager(const std::string &worldpath):
 | 
			
		||||
	configuration()
 | 
			
		||||
{
 | 
			
		||||
	SubgameSpec gamespec = findWorldSubgame(worldpath);
 | 
			
		||||
 | 
			
		||||
	// Add all game mods and all world mods
 | 
			
		||||
	std::string game_virtual_path;
 | 
			
		||||
	game_virtual_path.append("games/").append(gamespec.id).append("/mods");
 | 
			
		||||
	addModsInPath(gamespec.gamemods_path, game_virtual_path);
 | 
			
		||||
	addModsInPath(worldpath + DIR_DELIM + "worldmods", "worldmods");
 | 
			
		||||
	configuration.addGameMods(gamespec);
 | 
			
		||||
	configuration.addModsInPath(worldpath + DIR_DELIM + "worldmods", "worldmods");
 | 
			
		||||
 | 
			
		||||
	// Load normal mods
 | 
			
		||||
	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
 | 
			
		||||
@@ -57,12 +55,13 @@ void ServerModManager::loadMods(ServerScripting *script)
 | 
			
		||||
{
 | 
			
		||||
	// Print mods
 | 
			
		||||
	infostream << "Server: Loading mods: ";
 | 
			
		||||
	for (const ModSpec &mod : m_sorted_mods) {
 | 
			
		||||
	for (const ModSpec &mod : configuration.getMods()) {
 | 
			
		||||
		infostream << mod.name << " ";
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	infostream << std::endl;
 | 
			
		||||
	// Load and run "mod" scripts
 | 
			
		||||
	for (const ModSpec &mod : m_sorted_mods) {
 | 
			
		||||
	for (const ModSpec &mod : configuration.getMods()) {
 | 
			
		||||
		mod.checkAndLog();
 | 
			
		||||
 | 
			
		||||
		std::string script_path = mod.path + DIR_DELIM + "init.lua";
 | 
			
		||||
@@ -79,25 +78,23 @@ void ServerModManager::loadMods(ServerScripting *script)
 | 
			
		||||
// clang-format on
 | 
			
		||||
const ModSpec *ServerModManager::getModSpec(const std::string &modname) const
 | 
			
		||||
{
 | 
			
		||||
	std::vector<ModSpec>::const_iterator it;
 | 
			
		||||
	for (it = m_sorted_mods.begin(); it != m_sorted_mods.end(); ++it) {
 | 
			
		||||
		const ModSpec &mod = *it;
 | 
			
		||||
	for (const auto &mod : configuration.getMods()) {
 | 
			
		||||
		if (mod.name == modname)
 | 
			
		||||
			return &mod;
 | 
			
		||||
	}
 | 
			
		||||
	return NULL;
 | 
			
		||||
 | 
			
		||||
	return nullptr;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ServerModManager::getModsMediaPaths(std::vector<std::string> &paths) const
 | 
			
		||||
{
 | 
			
		||||
	for (auto it = m_sorted_mods.crbegin(); it != m_sorted_mods.crend(); it++) {
 | 
			
		||||
		const ModSpec &spec = *it;
 | 
			
		||||
	for (const auto &spec : configuration.getMods()) {
 | 
			
		||||
		fs::GetRecursiveDirs(paths, spec.path + DIR_DELIM + "textures");
 | 
			
		||||
		fs::GetRecursiveDirs(paths, spec.path + DIR_DELIM + "sounds");
 | 
			
		||||
		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
 | 
			
		||||
 | 
			
		||||
#include "content/mods.h"
 | 
			
		||||
#include "content/mod_configuration.h"
 | 
			
		||||
#include <memory>
 | 
			
		||||
 | 
			
		||||
class MetricsBackend;
 | 
			
		||||
@@ -31,8 +31,10 @@ class ServerScripting;
 | 
			
		||||
 *
 | 
			
		||||
 * All new calls to this class must be tested in test_servermodmanager.cpp
 | 
			
		||||
 */
 | 
			
		||||
class ServerModManager : public ModConfiguration
 | 
			
		||||
class ServerModManager
 | 
			
		||||
{
 | 
			
		||||
	ModConfiguration configuration;
 | 
			
		||||
 | 
			
		||||
public:
 | 
			
		||||
	/**
 | 
			
		||||
	 * Creates a ServerModManager which targets worldpath
 | 
			
		||||
@@ -42,6 +44,23 @@ public:
 | 
			
		||||
	void loadMods(ServerScripting *script);
 | 
			
		||||
	const ModSpec *getModSpec(const std::string &modname) 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.
 | 
			
		||||
	 *
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user