]> git.lizzy.rs Git - dragonfireclient.git/blob - src/content/subgames.cpp
Deprecate game.conf name, use title instead (#12030)
[dragonfireclient.git] / src / content / subgames.cpp
1 /*
2 Minetest
3 Copyright (C) 2013 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 <common/c_internal.h>
21 #include "content/subgames.h"
22 #include "porting.h"
23 #include "filesys.h"
24 #include "settings.h"
25 #include "log.h"
26 #include "util/strfnd.h"
27 #include "defaultsettings.h" // for set_default_settings
28 #include "map_settings_manager.h"
29 #include "util/string.h"
30
31 #ifndef SERVER
32 #include "client/tile.h" // getImagePath
33 #endif
34
35 // The maximum number of identical world names allowed
36 #define MAX_WORLD_NAMES 100
37
38 namespace
39 {
40
41 bool getGameMinetestConfig(const std::string &game_path, Settings &conf)
42 {
43         std::string conf_path = game_path + DIR_DELIM + "minetest.conf";
44         return conf.readConfigFile(conf_path.c_str());
45 }
46
47 }
48
49
50 void SubgameSpec::checkAndLog() const
51 {
52         // Log deprecation messages
53         auto handling_mode = get_deprecated_handling_mode();
54         if (!deprecation_msgs.empty() && handling_mode != DeprecatedHandlingMode::Ignore) {
55                 std::ostringstream os;
56                 os << "Game " << title << " at " << path << ":" << std::endl;
57                 for (auto msg : deprecation_msgs)
58                         os << "\t" << msg << std::endl;
59
60                 if (handling_mode == DeprecatedHandlingMode::Error)
61                         throw ModError(os.str());
62                 else
63                         warningstream << os.str();
64         }
65 }
66
67
68 struct GameFindPath
69 {
70         std::string path;
71         bool user_specific;
72         GameFindPath(const std::string &path, bool user_specific) :
73                         path(path), user_specific(user_specific)
74         {
75         }
76 };
77
78 std::string getSubgamePathEnv()
79 {
80         char *subgame_path = getenv("MINETEST_SUBGAME_PATH");
81         return subgame_path ? std::string(subgame_path) : "";
82 }
83
84 SubgameSpec findSubgame(const std::string &id)
85 {
86         if (id.empty())
87                 return SubgameSpec();
88         std::string share = porting::path_share;
89         std::string user = porting::path_user;
90
91         // Get games install locations
92         Strfnd search_paths(getSubgamePathEnv());
93
94         // Get all possible paths fo game
95         std::vector<GameFindPath> find_paths;
96         while (!search_paths.at_end()) {
97                 std::string path = search_paths.next(PATH_DELIM);
98                 path.append(DIR_DELIM).append(id);
99                 find_paths.emplace_back(path, false);
100                 path.append("_game");
101                 find_paths.emplace_back(path, false);
102         }
103
104         std::string game_base = DIR_DELIM;
105         game_base = game_base.append("games").append(DIR_DELIM).append(id);
106         std::string game_suffixed = game_base + "_game";
107         find_paths.emplace_back(user + game_suffixed, true);
108         find_paths.emplace_back(user + game_base, true);
109         find_paths.emplace_back(share + game_suffixed, false);
110         find_paths.emplace_back(share + game_base, false);
111
112         // Find game directory
113         std::string game_path;
114         bool user_game = true; // Game is in user's directory
115         for (const GameFindPath &find_path : find_paths) {
116                 const std::string &try_path = find_path.path;
117                 if (fs::PathExists(try_path)) {
118                         game_path = try_path;
119                         user_game = find_path.user_specific;
120                         break;
121                 }
122         }
123
124         if (game_path.empty())
125                 return SubgameSpec();
126
127         std::string gamemod_path = game_path + DIR_DELIM + "mods";
128
129         // Find mod directories
130         std::unordered_map<std::string, std::string> mods_paths;
131         mods_paths["mods"] = user + DIR_DELIM + "mods";
132         if (!user_game && user != share)
133                 mods_paths["share"] = share + DIR_DELIM + "mods";
134
135         for (const std::string &mod_path : getEnvModPaths()) {
136                 mods_paths[fs::AbsolutePath(mod_path)] = mod_path;
137         }
138
139         // Get meta
140         std::string conf_path = game_path + DIR_DELIM + "game.conf";
141         Settings conf;
142         conf.readConfigFile(conf_path.c_str());
143
144         std::string game_title;
145         if (conf.exists("title"))
146                 game_title = conf.get("title");
147         else if (conf.exists("name"))
148                 game_title = conf.get("name");
149         else
150                 game_title = id;
151
152         std::string game_author;
153         if (conf.exists("author"))
154                 game_author = conf.get("author");
155
156         int game_release = 0;
157         if (conf.exists("release"))
158                 game_release = conf.getS32("release");
159
160         std::string menuicon_path;
161 #ifndef SERVER
162         menuicon_path = getImagePath(
163                         game_path + DIR_DELIM + "menu" + DIR_DELIM + "icon.png");
164 #endif
165
166         SubgameSpec spec(id, game_path, gamemod_path, mods_paths, game_title,
167                         menuicon_path, game_author, game_release);
168
169         if (conf.exists("name") && !conf.exists("title"))
170                 spec.deprecation_msgs.push_back("\"name\" setting in game.conf is deprecated, please use \"title\" instead");
171
172         return spec;
173 }
174
175 SubgameSpec findWorldSubgame(const std::string &world_path)
176 {
177         std::string world_gameid = getWorldGameId(world_path, true);
178         // See if world contains an embedded game; if so, use it.
179         std::string world_gamepath = world_path + DIR_DELIM + "game";
180         if (fs::PathExists(world_gamepath)) {
181                 SubgameSpec gamespec;
182                 gamespec.id = world_gameid;
183                 gamespec.path = world_gamepath;
184                 gamespec.gamemods_path = world_gamepath + DIR_DELIM + "mods";
185
186                 Settings conf;
187                 std::string conf_path = world_gamepath + DIR_DELIM + "game.conf";
188                 conf.readConfigFile(conf_path.c_str());
189
190                 if (conf.exists("title"))
191                         gamespec.title = conf.get("title");
192                 else if (conf.exists("name"))
193                         gamespec.title = conf.get("name");
194                 else
195                         gamespec.title = world_gameid;
196
197                 return gamespec;
198         }
199         return findSubgame(world_gameid);
200 }
201
202 std::set<std::string> getAvailableGameIds()
203 {
204         std::set<std::string> gameids;
205         std::set<std::string> gamespaths;
206         gamespaths.insert(porting::path_share + DIR_DELIM + "games");
207         gamespaths.insert(porting::path_user + DIR_DELIM + "games");
208
209         Strfnd search_paths(getSubgamePathEnv());
210
211         while (!search_paths.at_end())
212                 gamespaths.insert(search_paths.next(PATH_DELIM));
213
214         for (const std::string &gamespath : gamespaths) {
215                 std::vector<fs::DirListNode> dirlist = fs::GetDirListing(gamespath);
216                 for (const fs::DirListNode &dln : dirlist) {
217                         if (!dln.dir)
218                                 continue;
219
220                         // If configuration file is not found or broken, ignore game
221                         Settings conf;
222                         std::string conf_path = gamespath + DIR_DELIM + dln.name +
223                                                 DIR_DELIM + "game.conf";
224                         if (!conf.readConfigFile(conf_path.c_str()))
225                                 continue;
226
227                         // Add it to result
228                         const char *ends[] = {"_game", NULL};
229                         std::string shorter = removeStringEnd(dln.name, ends);
230                         if (!shorter.empty())
231                                 gameids.insert(shorter);
232                         else
233                                 gameids.insert(dln.name);
234                 }
235         }
236         return gameids;
237 }
238
239 std::vector<SubgameSpec> getAvailableGames()
240 {
241         std::vector<SubgameSpec> specs;
242         std::set<std::string> gameids = getAvailableGameIds();
243         specs.reserve(gameids.size());
244         for (const auto &gameid : gameids)
245                 specs.push_back(findSubgame(gameid));
246         return specs;
247 }
248
249 #define LEGACY_GAMEID "minetest"
250
251 bool getWorldExists(const std::string &world_path)
252 {
253         return (fs::PathExists(world_path + DIR_DELIM + "map_meta.txt") ||
254                         fs::PathExists(world_path + DIR_DELIM + "world.mt"));
255 }
256
257 //! Try to get the displayed name of a world
258 std::string getWorldName(const std::string &world_path, const std::string &default_name)
259 {
260         std::string conf_path = world_path + DIR_DELIM + "world.mt";
261         Settings conf;
262         bool succeeded = conf.readConfigFile(conf_path.c_str());
263         if (!succeeded) {
264                 return default_name;
265         }
266
267         if (!conf.exists("world_name"))
268                 return default_name;
269         return conf.get("world_name");
270 }
271
272 std::string getWorldGameId(const std::string &world_path, bool can_be_legacy)
273 {
274         std::string conf_path = world_path + DIR_DELIM + "world.mt";
275         Settings conf;
276         bool succeeded = conf.readConfigFile(conf_path.c_str());
277         if (!succeeded) {
278                 if (can_be_legacy) {
279                         // If map_meta.txt exists, it is probably an old minetest world
280                         if (fs::PathExists(world_path + DIR_DELIM + "map_meta.txt"))
281                                 return LEGACY_GAMEID;
282                 }
283                 return "";
284         }
285         if (!conf.exists("gameid"))
286                 return "";
287         // The "mesetint" gameid has been discarded
288         if (conf.get("gameid") == "mesetint")
289                 return "minetest";
290         return conf.get("gameid");
291 }
292
293 std::string getWorldPathEnv()
294 {
295         char *world_path = getenv("MINETEST_WORLD_PATH");
296         return world_path ? std::string(world_path) : "";
297 }
298
299 std::vector<WorldSpec> getAvailableWorlds()
300 {
301         std::vector<WorldSpec> worlds;
302         std::set<std::string> worldspaths;
303
304         Strfnd search_paths(getWorldPathEnv());
305
306         while (!search_paths.at_end())
307                 worldspaths.insert(search_paths.next(PATH_DELIM));
308
309         worldspaths.insert(porting::path_user + DIR_DELIM + "worlds");
310         infostream << "Searching worlds..." << std::endl;
311         for (const std::string &worldspath : worldspaths) {
312                 infostream << "  In " << worldspath << ": ";
313                 std::vector<fs::DirListNode> dirvector = fs::GetDirListing(worldspath);
314                 for (const fs::DirListNode &dln : dirvector) {
315                         if (!dln.dir)
316                                 continue;
317                         std::string fullpath = worldspath + DIR_DELIM + dln.name;
318                         std::string name = getWorldName(fullpath, dln.name);
319                         // Just allow filling in the gameid always for now
320                         bool can_be_legacy = true;
321                         std::string gameid = getWorldGameId(fullpath, can_be_legacy);
322                         WorldSpec spec(fullpath, name, gameid);
323                         if (!spec.isValid()) {
324                                 infostream << "(invalid: " << name << ") ";
325                         } else {
326                                 infostream << name << " ";
327                                 worlds.push_back(spec);
328                         }
329                 }
330                 infostream << std::endl;
331         }
332         // Check old world location
333         do {
334                 std::string fullpath = porting::path_user + DIR_DELIM + "world";
335                 if (!fs::PathExists(fullpath))
336                         break;
337                 std::string name = "Old World";
338                 std::string gameid = getWorldGameId(fullpath, true);
339                 WorldSpec spec(fullpath, name, gameid);
340                 infostream << "Old world found." << std::endl;
341                 worlds.push_back(spec);
342         } while (false);
343         infostream << worlds.size() << " found." << std::endl;
344         return worlds;
345 }
346
347 void loadGameConfAndInitWorld(const std::string &path, const std::string &name,
348                 const SubgameSpec &gamespec, bool create_world)
349 {
350         std::string final_path = path;
351
352         // If we're creating a new world, ensure that the path isn't already taken
353         if (create_world) {
354                 int counter = 1;
355                 while (fs::PathExists(final_path) && counter < MAX_WORLD_NAMES) {
356                         final_path = path + "_" + std::to_string(counter);
357                         counter++;
358                 }
359
360                 if (fs::PathExists(final_path)) {
361                         throw BaseException("Too many similar filenames");
362                 }
363         }
364
365         Settings *game_settings = Settings::getLayer(SL_GAME);
366         const bool new_game_settings = (game_settings == nullptr);
367         if (new_game_settings) {
368                 // Called by main-menu without a Server instance running
369                 // -> create and free manually
370                 game_settings = Settings::createLayer(SL_GAME);
371         }
372
373         getGameMinetestConfig(gamespec.path, *game_settings);
374         game_settings->removeSecureSettings();
375
376         infostream << "Initializing world at " << final_path << std::endl;
377
378         fs::CreateAllDirs(final_path);
379
380         // Create world.mt if does not already exist
381         std::string worldmt_path = final_path + DIR_DELIM "world.mt";
382         if (!fs::PathExists(worldmt_path)) {
383                 Settings conf;
384
385                 conf.set("world_name", name);
386                 conf.set("gameid", gamespec.id);
387                 conf.set("backend", "sqlite3");
388                 conf.set("player_backend", "sqlite3");
389                 conf.set("auth_backend", "sqlite3");
390                 conf.set("mod_storage_backend", "sqlite3");
391                 conf.setBool("creative_mode", g_settings->getBool("creative_mode"));
392                 conf.setBool("enable_damage", g_settings->getBool("enable_damage"));
393
394                 if (!conf.updateConfigFile(worldmt_path.c_str())) {
395                         throw BaseException("Failed to update the config file");
396                 }
397         }
398
399         // Create map_meta.txt if does not already exist
400         std::string map_meta_path = final_path + DIR_DELIM + "map_meta.txt";
401         if (!fs::PathExists(map_meta_path)) {
402                 MapSettingsManager mgr(map_meta_path);
403
404                 mgr.setMapSetting("seed", g_settings->get("fixed_map_seed"));
405
406                 mgr.makeMapgenParams();
407                 mgr.saveMapMeta();
408         }
409
410         // The Settings object is no longer needed for created worlds
411         if (new_game_settings)
412                 delete game_settings;
413 }
414
415 std::vector<std::string> getEnvModPaths()
416 {
417         const char *c_mod_path = getenv("MINETEST_MOD_PATH");
418         std::vector<std::string> paths;
419         Strfnd search_paths(c_mod_path ? c_mod_path : "");
420         while (!search_paths.at_end())
421                 paths.push_back(search_paths.next(PATH_DELIM));
422         return paths;
423 }