3 Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU Lesser General Public License as published by
7 the Free Software Foundation; either version 2.1 of the License, or
8 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU Lesser General Public License for more details.
15 You should have received a copy of the GNU Lesser General Public License along
16 with this program; if not, write to the Free Software Foundation, Inc.,
17 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
28 std::map<std::string, ModSpec> getModsInPath(std::string path)
30 std::map<std::string, ModSpec> result;
31 std::vector<fs::DirListNode> dirlist = fs::GetDirListing(path);
32 for(u32 j=0; j<dirlist.size(); j++){
35 std::string modname = dirlist[j].name;
36 // Ignore all directories beginning with a ".", especially
37 // VCS directories like ".git" or ".svn"
40 std::string modpath = path + DIR_DELIM + modname;
42 // Handle modpacks (defined by containing modpack.txt)
43 std::ifstream modpack_is((modpath+DIR_DELIM+"modpack.txt").c_str(),
44 std::ios_base::binary);
45 if(modpack_is.good()) //a modpack, recursively get the mods in it
47 modpack_is.close(); // We don't actually need the file
48 ModSpec spec(modname,modpath);
49 spec.modpack_content = getModsInPath(modpath);
50 spec.is_modpack = true;
51 result.insert(std::make_pair(modname,spec));
53 else // not a modpack, add the modspec
55 std::set<std::string> depends;
56 std::ifstream is((modpath+DIR_DELIM+"depends.txt").c_str(),
57 std::ios_base::binary);
61 std::getline(is, dep);
67 ModSpec spec(modname, modpath, depends);
68 result.insert(std::make_pair(modname,spec));
74 std::map<std::string, ModSpec> flattenModTree(std::map<std::string, ModSpec> mods)
76 std::map<std::string, ModSpec> result;
77 for(std::map<std::string,ModSpec>::iterator it = mods.begin();
78 it != mods.end(); ++it)
80 ModSpec mod = (*it).second;
83 std::map<std::string, ModSpec> content =
84 flattenModTree(mod.modpack_content);
85 result.insert(content.begin(),content.end());
86 result.insert(std::make_pair(mod.name,mod));
90 result.insert(std::make_pair(mod.name,mod));
96 std::vector<ModSpec> flattenMods(std::map<std::string, ModSpec> mods)
98 std::vector<ModSpec> result;
99 for(std::map<std::string,ModSpec>::iterator it = mods.begin();
100 it != mods.end(); ++it)
102 ModSpec mod = (*it).second;
105 std::vector<ModSpec> content = flattenMods(mod.modpack_content);
106 result.reserve(result.size() + content.size());
107 result.insert(result.end(),content.begin(),content.end());
112 // infostream << "inserting mod " << mod.name << std::endl;
113 result.push_back(mod);
119 std::vector<ModSpec> filterMods(std::vector<ModSpec> mods,
120 std::set<std::string> exclude_mod_names)
122 std::vector<ModSpec> result;
123 for(std::vector<ModSpec>::iterator it = mods.begin();
124 it != mods.end(); ++it)
127 if(exclude_mod_names.count(mod.name) == 0)
128 result.push_back(mod);
133 void ModConfiguration::addModsInPathFiltered(std::string path, std::set<std::string> exclude_mods)
135 addMods(filterMods(flattenMods(getModsInPath(path)),exclude_mods));
139 void ModConfiguration::addMods(std::vector<ModSpec> new_mods)
141 // Step 1: remove mods in sorted_mods from unmet dependencies
142 // of new_mods. new mods without unmet dependencies are
143 // temporarily stored in satisfied_mods
144 std::vector<ModSpec> satisfied_mods;
145 for(std::vector<ModSpec>::iterator it = m_sorted_mods.begin();
146 it != m_sorted_mods.end(); ++it)
149 for(std::vector<ModSpec>::iterator it_new = new_mods.begin();
150 it_new != new_mods.end(); ++it_new)
152 ModSpec& mod_new = *it_new;
153 //infostream << "erasing dependency " << mod.name << " from " << mod_new.name << std::endl;
154 mod_new.unsatisfied_depends.erase(mod.name);
158 // split new mods into satisfied and unsatisfied
159 for(std::vector<ModSpec>::iterator it = new_mods.begin();
160 it != new_mods.end(); ++it)
162 ModSpec mod_new = *it;
163 if(mod_new.unsatisfied_depends.empty())
164 satisfied_mods.push_back(mod_new);
166 m_unsatisfied_mods.push_back(mod_new);
169 // Step 2: mods without unmet dependencies can be appended to
171 while(!satisfied_mods.empty())
173 ModSpec mod = satisfied_mods.back();
174 m_sorted_mods.push_back(mod);
175 satisfied_mods.pop_back();
176 for(std::list<ModSpec>::iterator it = m_unsatisfied_mods.begin();
177 it != m_unsatisfied_mods.end(); )
180 mod2.unsatisfied_depends.erase(mod.name);
181 if(mod2.unsatisfied_depends.empty())
183 satisfied_mods.push_back(mod2);
184 it = m_unsatisfied_mods.erase(it);
192 // If failed, returned modspec has name==""
193 static ModSpec findCommonMod(const std::string &modname)
195 // Try to find in {$user,$share}/games/common/$modname
196 std::vector<std::string> find_paths;
197 find_paths.push_back(porting::path_user + DIR_DELIM + "games" +
198 DIR_DELIM + "common" + DIR_DELIM + "mods" + DIR_DELIM + modname);
199 find_paths.push_back(porting::path_share + DIR_DELIM + "games" +
200 DIR_DELIM + "common" + DIR_DELIM + "mods" + DIR_DELIM + modname);
201 for(u32 i=0; i<find_paths.size(); i++){
202 const std::string &try_path = find_paths[i];
203 if(fs::PathExists(try_path))
204 return ModSpec(modname, try_path);
206 // Failed to find mod
210 ModConfiguration::ModConfiguration(std::string worldpath)
212 SubgameSpec gamespec = findWorldSubgame(worldpath);
214 // Add common mods without dependency handling
215 std::vector<std::string> inexistent_common_mods;
217 if(getGameConfig(gamespec.path, gameconf)){
218 if(gameconf.exists("common_mods")){
219 Strfnd f(gameconf.get("common_mods"));
221 std::string modname = trim(f.next(","));
224 ModSpec spec = findCommonMod(modname);
225 if(spec.name.empty())
226 inexistent_common_mods.push_back(modname);
228 m_sorted_mods.push_back(spec);
232 if(!inexistent_common_mods.empty()){
233 std::string s = "Required common mods ";
234 for(u32 i=0; i<inexistent_common_mods.size(); i++){
235 if(i != 0) s += ", ";
236 s += std::string("\"") + inexistent_common_mods[i] + "\"";
238 s += " could not be found.";
242 // Add all world mods and all game mods
243 addModsInPath(worldpath + DIR_DELIM + "worldmods");
244 addModsInPath(gamespec.gamemods_path);
246 // check world.mt file for mods explicitely declared to be
247 // loaded or not by a load_mod_<modname> = ... line.
248 std::string worldmt = worldpath+DIR_DELIM+"world.mt";
249 Settings worldmt_settings;
250 worldmt_settings.readConfigFile(worldmt.c_str());
251 std::vector<std::string> names = worldmt_settings.getNames();
252 std::set<std::string> exclude_mod_names;
253 for(std::vector<std::string>::iterator it = names.begin();
254 it != names.end(); ++it)
256 std::string name = *it;
257 // for backwards compatibility: exclude only mods which are
258 // explicitely excluded. if mod is not mentioned at all, it is
259 // enabled. So by default, all installed mods are enabled.
260 if (name.compare(0,9,"load_mod_") == 0 &&
261 !worldmt_settings.getBool(name))
263 exclude_mod_names.insert(name.substr(9));
267 for(std::set<std::string>::const_iterator i = gamespec.addon_mods_paths.begin();
268 i != gamespec.addon_mods_paths.end(); ++i)
269 addModsInPathFiltered((*i),exclude_mod_names);