Merge pull request #431 from sapier/dtime_clamping
[oweals/minetest.git] / src / mods.cpp
1 /*
2 Minetest-c55
3 Copyright (C) 2011 celeron55, Perttu Ahola <celeron55@gmail.com>
4
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.
9
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.
14
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.
18 */
19
20 #include "mods.h"
21 #include "filesys.h"
22 #include "strfnd.h"
23 #include "log.h"
24 #include "subgame.h"
25 #include "settings.h"
26
27 std::map<std::string, ModSpec> getModsInPath(std::string path)
28 {
29         std::map<std::string, ModSpec> result;
30         std::vector<fs::DirListNode> dirlist = fs::GetDirListing(path);
31         for(u32 j=0; j<dirlist.size(); j++){
32                 if(!dirlist[j].dir)
33                         continue;
34                 std::string modname = dirlist[j].name;
35                 // Ignore all directories beginning with a ".", especially
36                 // VCS directories like ".git" or ".svn"
37                 if(modname[0] == '.')
38                         continue;
39                 std::string modpath = path + DIR_DELIM + modname;
40
41                 // Handle modpacks (defined by containing modpack.txt)
42                 std::ifstream modpack_is((modpath+DIR_DELIM+"modpack.txt").c_str(),
43                                         std::ios_base::binary);
44                 if(modpack_is.good()) //a modpack, recursively get the mods in it
45                 {
46                         modpack_is.close(); // We don't actually need the file
47                         ModSpec spec(modname,modpath);
48                         spec.modpack_content = getModsInPath(modpath);
49                         spec.is_modpack = true;
50                         result.insert(std::make_pair(modname,spec));
51                 }
52                 else // not a modpack, add the modspec
53                 {
54                         std::set<std::string> depends;
55                         std::ifstream is((modpath+DIR_DELIM+"depends.txt").c_str(),
56                                 std::ios_base::binary);
57                         while(is.good())
58                         {
59                                 std::string dep;
60                                 std::getline(is, dep);
61                                 dep = trim(dep);        
62                                 if(dep != "")
63                                         depends.insert(dep);
64                         }
65
66                         ModSpec spec(modname, modpath, depends);
67                         result.insert(std::make_pair(modname,spec));
68                 }
69         }
70         return result;
71 }
72
73 std::map<std::string, ModSpec> flattenModTree(std::map<std::string, ModSpec> mods)
74 {
75         std::map<std::string, ModSpec> result;
76         for(std::map<std::string,ModSpec>::iterator it = mods.begin();
77                 it != mods.end(); ++it)
78         {
79                 ModSpec mod = (*it).second;
80                 if(mod.is_modpack)
81                 {
82                         std::map<std::string, ModSpec> content = 
83                                 flattenModTree(mod.modpack_content);
84                         result.insert(content.begin(),content.end());
85                         result.insert(std::make_pair(mod.name,mod));
86                 } 
87                 else //not a modpack
88                 {
89                         result.insert(std::make_pair(mod.name,mod));
90                 }
91         }
92         return result;
93 }
94
95 std::vector<ModSpec> flattenMods(std::map<std::string, ModSpec> mods)
96 {
97         std::vector<ModSpec> result;
98         for(std::map<std::string,ModSpec>::iterator it = mods.begin();
99                 it != mods.end(); ++it)
100         {
101                 ModSpec mod = (*it).second;
102                 if(mod.is_modpack)
103                 {
104                         std::vector<ModSpec> content = flattenMods(mod.modpack_content);
105                         result.reserve(result.size() + content.size());
106                         result.insert(result.end(),content.begin(),content.end());
107                         
108                 } 
109                 else //not a modpack
110                 {
111                         // infostream << "inserting mod " << mod.name << std::endl;
112                         result.push_back(mod);
113                 }
114         }
115         return result;
116 }
117
118 std::vector<ModSpec> filterMods(std::vector<ModSpec> mods,
119                                                                 std::set<std::string> exclude_mod_names)
120 {
121         std::vector<ModSpec> result;
122         for(std::vector<ModSpec>::iterator it = mods.begin();
123                 it != mods.end(); ++it)
124         {
125                 ModSpec& mod = *it;
126                 if(exclude_mod_names.count(mod.name) == 0)
127                         result.push_back(mod);
128         }       
129         return result;
130 }
131
132 void ModConfiguration::addModsInPathFiltered(std::string path, std::set<std::string> exclude_mods)
133 {
134         addMods(filterMods(flattenMods(getModsInPath(path)),exclude_mods));
135 }
136
137
138 void ModConfiguration::addMods(std::vector<ModSpec> new_mods)
139 {
140         // Step 1: remove mods in sorted_mods from unmet dependencies
141         // of new_mods. new mods without unmet dependencies are
142         // temporarily stored in satisfied_mods
143         std::vector<ModSpec> satisfied_mods;
144         for(std::vector<ModSpec>::iterator it = m_sorted_mods.begin();
145                 it != m_sorted_mods.end(); ++it)
146         {
147                 ModSpec mod = *it;
148                 for(std::vector<ModSpec>::iterator it_new = new_mods.begin();
149                         it_new != new_mods.end(); ++it_new)
150                 {
151                         ModSpec& mod_new = *it_new;
152                         //infostream << "erasing dependency " << mod.name << " from " << mod_new.name << std::endl;
153                         mod_new.unsatisfied_depends.erase(mod.name);
154                 }
155         }
156
157         // split new mods into satisfied and unsatisfied
158         for(std::vector<ModSpec>::iterator it = new_mods.begin();
159                 it != new_mods.end(); ++it)
160         {
161                 ModSpec mod_new = *it;
162                 if(mod_new.unsatisfied_depends.empty())
163                         satisfied_mods.push_back(mod_new);
164                 else
165                         m_unsatisfied_mods.push_back(mod_new);
166         }
167
168         // Step 2: mods without unmet dependencies can be appended to
169         // the sorted list.
170         while(!satisfied_mods.empty())
171         {
172                 ModSpec mod = satisfied_mods.back();
173                 m_sorted_mods.push_back(mod);
174                 satisfied_mods.pop_back();
175                 for(std::list<ModSpec>::iterator it = m_unsatisfied_mods.begin();
176                         it != m_unsatisfied_mods.end(); )
177                 {
178                         ModSpec& mod2 = *it;
179                         mod2.unsatisfied_depends.erase(mod.name);
180                         if(mod2.unsatisfied_depends.empty())
181                         {
182                                 satisfied_mods.push_back(mod2);
183                                 it = m_unsatisfied_mods.erase(it);
184                         }
185                         else
186                                 ++it;
187                 }       
188         }
189 }
190
191 ModConfiguration::ModConfiguration(std::string worldpath)
192 {
193         // Add all world mods and all game mods
194         addModsInPath(worldpath + DIR_DELIM + "worldmods");
195         SubgameSpec gamespec = findWorldSubgame(worldpath);
196         addModsInPath(gamespec.gamemods_path);
197
198         // check world.mt file for mods explicitely declared to be
199         // loaded or not by a load_mod_<modname> = ... line.
200         std::string worldmt = worldpath+DIR_DELIM+"world.mt";
201         Settings worldmt_settings;
202         worldmt_settings.readConfigFile(worldmt.c_str());
203         std::vector<std::string> names = worldmt_settings.getNames();
204         std::set<std::string> exclude_mod_names;
205         for(std::vector<std::string>::iterator it = names.begin(); 
206                 it != names.end(); ++it)
207         {       
208                 std::string name = *it;  
209                 // for backwards compatibility: exclude only mods which are
210                 // explicitely excluded. if mod is not mentioned at all, it is
211                 // enabled. So by default, all installed mods are enabled.
212                 if (name.compare(0,9,"load_mod_") == 0 &&
213                         !worldmt_settings.getBool(name))
214                 {
215                         exclude_mod_names.insert(name.substr(9));
216                 }
217         }
218
219         for(std::set<std::string>::const_iterator i = gamespec.addon_mods_paths.begin();
220                 i != gamespec.addon_mods_paths.end(); ++i)
221                 addModsInPathFiltered((*i),exclude_mod_names);
222 }