X-Git-Url: https://git.lizzy.rs/?a=blobdiff_plain;f=src%2Fmods.cpp;h=be6e1e5d30f06a1dba0b77053a346a944fc5e474;hb=24b312cc085b7fae6477c8a47fffb57484b20f6e;hp=db878d78b40b83634786a00215ed9d5df364c9a9;hpb=6b2023dc3eb1b483c92ba967f3335bb6880d2db1;p=dragonfireclient.git diff --git a/src/mods.cpp b/src/mods.cpp index db878d78b..be6e1e5d3 100644 --- a/src/mods.cpp +++ b/src/mods.cpp @@ -1,116 +1,358 @@ /* -Minetest-c55 -Copyright (C) 2011 celeron55, Perttu Ahola +Minetest +Copyright (C) 2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation; either version 2 of the License, or +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 General Public License for more details. +GNU Lesser General Public License for more details. -You should have received a copy of the GNU General Public License along +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 "mods.h" -#include +#include #include -#include -#include +#include "mods.h" #include "filesys.h" #include "strfnd.h" #include "log.h" +#include "subgame.h" +#include "settings.h" +#include "strfnd.h" +#include "convert_json.h" +#include "exceptions.h" -// Get a dependency-sorted list of ModSpecs -core::list getMods(core::list &modspaths) - throw(ModError) +static bool parseDependsLine(std::istream &is, + std::string &dep, std::set &symbols) { - std::queue mods_satisfied; - core::list mods_unsorted; - core::list mods_sorted; - // name, path: For detecting name conflicts - std::map mod_names; - for(core::list::Iterator i = modspaths.begin(); - i != modspaths.end(); i++){ - std::string modspath = *i; - std::vector dirlist = fs::GetDirListing(modspath); - for(u32 j=0; j::const_iterator i; - i = mod_names.find(modname); - if(i != mod_names.end()){ - std::string s; - infostream<<"WARNING: Mod name conflict detected: " - <second< 0 && !string_allowed(dep.substr(pos-1, 1), MODNAME_ALLOWED_CHARS)){ + // last character is a symbol, not part of the modname + symbols.insert(dep[pos-1]); + --pos; + } + dep = trim(dep.substr(0, pos)); + return dep != ""; +} + +void parseModContents(ModSpec &spec) +{ + // NOTE: this function works in mutual recursion with getModsInPath + Settings info; + info.readConfigFile((spec.path+DIR_DELIM+"mod.conf").c_str()); + + if (info.exists("name")) + spec.name = info.get("name"); + + spec.depends.clear(); + spec.optdepends.clear(); + spec.is_modpack = false; + spec.modpack_content.clear(); + + // Handle modpacks (defined by containing modpack.txt) + std::ifstream modpack_is((spec.path+DIR_DELIM+"modpack.txt").c_str()); + if(modpack_is.good()){ //a modpack, recursively get the mods in it + modpack_is.close(); // We don't actually need the file + spec.is_modpack = true; + spec.modpack_content = getModsInPath(spec.path, true); + + // modpacks have no dependencies; they are defined and + // tracked separately for each mod in the modpack + } + else{ // not a modpack, parse the dependencies + std::ifstream is((spec.path+DIR_DELIM+"depends.txt").c_str()); + while(is.good()){ + std::string dep; + std::set symbols; + if(parseDependsLine(is, dep, symbols)){ + if(symbols.count('?') != 0){ + spec.optdepends.insert(dep); + } + else{ + spec.depends.insert(dep); } } - std::set depends; - std::ifstream is((modpath+DIR_DELIM+"depends.txt").c_str(), - std::ios_base::binary); - while(is.good()){ - std::string dep; - std::getline(is, dep); - dep = trim(dep); - if(dep != "") - depends.insert(dep); - } - ModSpec spec(modname, modpath, depends); - mods_unsorted.push_back(spec); - if(depends.empty()) - mods_satisfied.push(spec); - mod_names[modname] = modpath; } } - // Sort by depencencies - while(!mods_satisfied.empty()){ - ModSpec mod = mods_satisfied.front(); - mods_satisfied.pop(); - mods_sorted.push_back(mod); - for(core::list::Iterator i = mods_unsorted.begin(); - i != mods_unsorted.end(); i++){ - ModSpec &mod2 = *i; - if(mod2.unsatisfied_depends.empty()) - continue; - mod2.unsatisfied_depends.erase(mod.name); - if(!mod2.unsatisfied_depends.empty()) +} + +std::map getModsInPath(std::string path, bool part_of_modpack) +{ + // NOTE: this function works in mutual recursion with parseModContents + + std::map result; + std::vector dirlist = fs::GetDirListing(path); + for(u32 j=0; j flattenModTree(std::map mods) +{ + std::map result; + for(std::map::iterator it = mods.begin(); + it != mods.end(); ++it) + { + ModSpec mod = (*it).second; + if(mod.is_modpack) + { + std::map content = + flattenModTree(mod.modpack_content); + result.insert(content.begin(),content.end()); + result.insert(std::make_pair(mod.name,mod)); + } + else //not a modpack + { + result.insert(std::make_pair(mod.name,mod)); + } + } + return result; +} + +std::vector flattenMods(std::map mods) +{ + std::vector result; + for(std::map::iterator it = mods.begin(); + it != mods.end(); ++it) + { + ModSpec mod = (*it).second; + if(mod.is_modpack) + { + std::vector content = flattenMods(mod.modpack_content); + result.reserve(result.size() + content.size()); + result.insert(result.end(),content.begin(),content.end()); + + } + else //not a modpack + { + result.push_back(mod); + } + } + return result; +} + +ModConfiguration::ModConfiguration(std::string worldpath) +{ + SubgameSpec gamespec = findWorldSubgame(worldpath); + + // Add all game mods and all world mods + addModsInPath(gamespec.gamemods_path); + addModsInPath(worldpath + DIR_DELIM + "worldmods"); + + // check world.mt file for mods explicitely declared to be + // loaded or not by a load_mod_ = ... line. + std::string worldmt = worldpath+DIR_DELIM+"world.mt"; + Settings worldmt_settings; + worldmt_settings.readConfigFile(worldmt.c_str()); + std::vector names = worldmt_settings.getNames(); + std::set include_mod_names; + for(std::vector::iterator it = names.begin(); + it != names.end(); ++it) + { + std::string name = *it; + // for backwards compatibility: exclude only mods which are + // explicitely excluded. if mod is not mentioned at all, it is + // enabled. So by default, all installed mods are enabled. + if (name.compare(0,9,"load_mod_") == 0 && + worldmt_settings.getBool(name)) + { + include_mod_names.insert(name.substr(9)); + } + } + + // Collect all mods that are also in include_mod_names + std::vector addon_mods; + for(std::set::const_iterator it_path = gamespec.addon_mods_paths.begin(); + it_path != gamespec.addon_mods_paths.end(); ++it_path) + { + std::vector addon_mods_in_path = flattenMods(getModsInPath(*it_path)); + for(std::vector::iterator it = addon_mods_in_path.begin(); + it != addon_mods_in_path.end(); ++it) + { + ModSpec& mod = *it; + if(include_mod_names.count(mod.name) != 0) + addon_mods.push_back(mod); + else + worldmt_settings.setBool("load_mod_" + mod.name, false); + } + } + worldmt_settings.updateConfigFile(worldmt.c_str()); + + addMods(addon_mods); + + // report on name conflicts + if(!m_name_conflicts.empty()){ + std::string s = "Unresolved name conflicts for mods "; + for(std::set::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::addModsInPath(std::string path) +{ + addMods(flattenMods(getModsInPath(path))); +} + +void ModConfiguration::addMods(std::vector 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 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 seen_this_iteration; + + for(std::vector::const_iterator it = new_mods.begin(); + it != new_mods.end(); ++it){ + const ModSpec &mod = *it; + if(mod.part_of_modpack != (bool)want_from_modpack) continue; - mods_satisfied.push(mod2); + 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: \"" + <::Iterator i = mods_unsorted.begin(); - i != mods_unsorted.end(); i++){ - ModSpec &mod = *i; - if(mod.unsatisfied_depends.empty()) - continue; - errs<<"mod \""<::iterator - i = mod.unsatisfied_depends.begin(); - i != mod.unsatisfied_depends.end(); i++){ - errs<<" \""<<(*i)<<"\""; +} + +void ModConfiguration::resolveDependencies() +{ + // Step 1: Compile a list of the mod names we're working with + std::set modnames; + for(std::vector::iterator it = m_unsatisfied_mods.begin(); + it != m_unsatisfied_mods.end(); ++it){ + modnames.insert((*it).name); + } + + // Step 2: get dependencies (including optional dependencies) + // of each mod, split mods into satisfied and unsatisfied + std::list satisfied; + std::list unsatisfied; + for(std::vector::iterator it = m_unsatisfied_mods.begin(); + it != m_unsatisfied_mods.end(); ++it){ + ModSpec mod = *it; + mod.unsatisfied_depends = mod.depends; + // check which optional dependencies actually exist + for(std::set::iterator it_optdep = mod.optdepends.begin(); + it_optdep != mod.optdepends.end(); ++it_optdep){ + std::string optdep = *it_optdep; + if(modnames.count(optdep) != 0) + mod.unsatisfied_depends.insert(optdep); } - errs<<"."<::iterator 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; + } + } } - return mods_sorted; + + // Step 4: write back list of unsatisfied mods + m_unsatisfied_mods.assign(unsatisfied.begin(), unsatisfied.end()); } +#if USE_CURL +Json::Value getModstoreUrl(std::string url) +{ + std::vector extra_headers; + + bool special_http_header = true; + + try { + special_http_header = g_settings->getBool("modstore_disable_special_http_header"); + } catch (SettingNotFoundException) {} + + if (special_http_header) { + extra_headers.push_back("Accept: application/vnd.minetest.mmdb-v1+json"); + } + return fetchJsonValue(url, special_http_header ? &extra_headers : NULL); +} +#endif