minetest/src/content/subgames.cpp

424 lines
12 KiB
C++
Raw Permalink Normal View History

2012-03-11 05:54:23 -07:00
/*
2013-02-24 09:40:43 -08:00
Minetest
2013-02-24 10:38:45 -08:00
Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
2012-03-11 05:54:23 -07:00
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
2012-03-11 05:54:23 -07:00
(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.
2012-03-11 05:54:23 -07:00
You should have received a copy of the GNU Lesser General Public License along
2012-03-11 05:54:23 -07:00
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include <common/c_internal.h>
#include "content/subgames.h"
2012-03-11 05:54:23 -07:00
#include "porting.h"
#include "filesys.h"
#include "settings.h"
#include "log.h"
#include "util/strfnd.h"
#include "defaultsettings.h" // for set_default_settings
#include "map_settings_manager.h"
#include "util/string.h"
#ifndef SERVER
#include "client/tile.h" // getImagePath
#endif
2012-03-11 05:54:23 -07:00
// The maximum number of identical world names allowed
#define MAX_WORLD_NAMES 100
namespace
{
2013-03-21 12:42:23 -07:00
bool getGameMinetestConfig(const std::string &game_path, Settings &conf)
{
std::string conf_path = game_path + DIR_DELIM + "minetest.conf";
return conf.readConfigFile(conf_path.c_str());
}
}
void SubgameSpec::checkAndLog() const
{
// Log deprecation messages
auto handling_mode = get_deprecated_handling_mode();
if (!deprecation_msgs.empty() && handling_mode != DeprecatedHandlingMode::Ignore) {
std::ostringstream os;
os << "Game " << title << " at " << path << ":" << std::endl;
for (auto msg : deprecation_msgs)
os << "\t" << msg << std::endl;
if (handling_mode == DeprecatedHandlingMode::Error)
throw ModError(os.str());
else
warningstream << os.str();
}
}
struct GameFindPath
{
std::string path;
bool user_specific;
GameFindPath(const std::string &path, bool user_specific) :
path(path), user_specific(user_specific)
{
}
};
std::string getSubgamePathEnv()
{
2014-10-28 12:13:14 -07:00
char *subgame_path = getenv("MINETEST_SUBGAME_PATH");
return subgame_path ? std::string(subgame_path) : "";
}
2012-03-11 05:54:23 -07:00
SubgameSpec findSubgame(const std::string &id)
{
if (id.empty())
2012-03-11 05:54:23 -07:00
return SubgameSpec();
2012-03-19 11:44:07 -07:00
std::string share = porting::path_share;
std::string user = porting::path_user;
// Get games install locations
Strfnd search_paths(getSubgamePathEnv());
// Get all possible paths fo game
std::vector<GameFindPath> find_paths;
while (!search_paths.at_end()) {
std::string path = search_paths.next(PATH_DELIM);
path.append(DIR_DELIM).append(id);
find_paths.emplace_back(path, false);
path.append("_game");
find_paths.emplace_back(path, false);
2014-10-28 12:13:14 -07:00
}
std::string game_base = DIR_DELIM;
game_base = game_base.append("games").append(DIR_DELIM).append(id);
std::string game_suffixed = game_base + "_game";
find_paths.emplace_back(user + game_suffixed, true);
find_paths.emplace_back(user + game_base, true);
find_paths.emplace_back(share + game_suffixed, false);
find_paths.emplace_back(share + game_base, false);
2012-03-11 05:54:23 -07:00
// Find game directory
std::string game_path;
2012-03-11 05:54:23 -07:00
bool user_game = true; // Game is in user's directory
for (const GameFindPath &find_path : find_paths) {
const std::string &try_path = find_path.path;
if (fs::PathExists(try_path)) {
game_path = try_path;
user_game = find_path.user_specific;
break;
}
2012-03-11 05:54:23 -07:00
}
if (game_path.empty())
2012-03-11 05:54:23 -07:00
return SubgameSpec();
Basic support for configuring which mods to load for each world settings.h: added function to return all keys used in settings, and a function to remove a setting mods.{h,cpp}: added class ModConfiguration that represents a subset of the installed mods. server.{h,cpp}: server does not load add-on mods that are disabled in the world.mt file. mods are disabled by a setting of the form "load_mod_<modname> = false". if no load_mod_<modname> = ... setting is found, the mod is loaded anyways for backwards compatibilty. server also complains to errorstream about mods with unstatisfied dependencies and about mods that are not installed. guiConfigureWorld.{h,cpp}: shows a treeview of installed add-on mods and modpacks with little icons in front of their name indicating their status: a checkmark for enabled mods, a cross for disabled mods, a question mark for "new" mods Mods can be enabled/disabled by a checkbox. Mods also show a list of dependencies and reverse dependencies. double-click on a mod in dependency or reverse dependency listbox selects the corresponding mod. Enabling a mod also enables all its dependencies. Disabling a mod also disables all its reverse dependencies. For modpacks, show buttons to enable/disable all mods (recursively, including their dependencies) in it. Button "Save" saves the current settings to the world.mt file and returns to the main menu. Button "Cancel" returns to main menu without saving. basic keyboard controls (if the proper widget has keyboard focus): up/down: scroll through tree of mods left/right: collaps/expand a modpack space: enable/disable the selected mod
2012-12-08 09:10:54 -08:00
std::string gamemod_path = game_path + DIR_DELIM + "mods";
2012-03-19 16:06:44 -07:00
// Find mod directories
std::unordered_map<std::string, std::string> mods_paths;
mods_paths["mods"] = user + DIR_DELIM + "mods";
if (!user_game && user != share)
mods_paths["share"] = share + DIR_DELIM + "mods";
for (const std::string &mod_path : getEnvModPaths()) {
mods_paths[fs::AbsolutePath(mod_path)] = mod_path;
}
// Get meta
std::string conf_path = game_path + DIR_DELIM + "game.conf";
Settings conf;
conf.readConfigFile(conf_path.c_str());
std::string game_title;
if (conf.exists("title"))
game_title = conf.get("title");
else if (conf.exists("name"))
game_title = conf.get("name");
else
game_title = id;
std::string game_author;
if (conf.exists("author"))
game_author = conf.get("author");
2018-05-16 13:52:12 -07:00
int game_release = 0;
if (conf.exists("release"))
game_release = conf.getS32("release");
std::string menuicon_path;
#ifndef SERVER
menuicon_path = getImagePath(
game_path + DIR_DELIM + "menu" + DIR_DELIM + "icon.png");
#endif
SubgameSpec spec(id, game_path, gamemod_path, mods_paths, game_title,
2018-05-16 13:52:12 -07:00
menuicon_path, game_author, game_release);
if (conf.exists("name") && !conf.exists("title"))
spec.deprecation_msgs.push_back("\"name\" setting in game.conf is deprecated, please use \"title\" instead");
return spec;
2012-03-11 05:54:23 -07:00
}
SubgameSpec findWorldSubgame(const std::string &world_path)
{
std::string world_gameid = getWorldGameId(world_path, true);
// See if world contains an embedded game; if so, use it.
std::string world_gamepath = world_path + DIR_DELIM + "game";
if (fs::PathExists(world_gamepath)) {
SubgameSpec gamespec;
gamespec.id = world_gameid;
gamespec.path = world_gamepath;
gamespec.gamemods_path = world_gamepath + DIR_DELIM + "mods";
Settings conf;
std::string conf_path = world_gamepath + DIR_DELIM + "game.conf";
conf.readConfigFile(conf_path.c_str());
if (conf.exists("title"))
gamespec.title = conf.get("title");
else if (conf.exists("name"))
gamespec.title = conf.get("name");
else
gamespec.title = world_gameid;
return gamespec;
}
return findSubgame(world_gameid);
}
2012-03-11 05:54:23 -07:00
std::set<std::string> getAvailableGameIds()
{
std::set<std::string> gameids;
std::set<std::string> gamespaths;
2012-03-19 11:44:07 -07:00
gamespaths.insert(porting::path_share + DIR_DELIM + "games");
gamespaths.insert(porting::path_user + DIR_DELIM + "games");
Strfnd search_paths(getSubgamePathEnv());
while (!search_paths.at_end())
gamespaths.insert(search_paths.next(PATH_DELIM));
for (const std::string &gamespath : gamespaths) {
std::vector<fs::DirListNode> dirlist = fs::GetDirListing(gamespath);
for (const fs::DirListNode &dln : dirlist) {
if (!dln.dir)
2012-03-11 05:54:23 -07:00
continue;
// If configuration file is not found or broken, ignore game
Settings conf;
std::string conf_path = gamespath + DIR_DELIM + dln.name +
DIR_DELIM + "game.conf";
if (!conf.readConfigFile(conf_path.c_str()))
continue;
// Add it to result
const char *ends[] = {"_game", NULL};
std::string shorter = removeStringEnd(dln.name, ends);
if (!shorter.empty())
gameids.insert(shorter);
else
gameids.insert(dln.name);
2012-03-11 05:54:23 -07:00
}
}
return gameids;
}
std::vector<SubgameSpec> getAvailableGames()
{
std::vector<SubgameSpec> specs;
std::set<std::string> gameids = getAvailableGameIds();
specs.reserve(gameids.size());
for (const auto &gameid : gameids)
specs.push_back(findSubgame(gameid));
return specs;
}
#define LEGACY_GAMEID "minetest"
bool getWorldExists(const std::string &world_path)
{
return (fs::PathExists(world_path + DIR_DELIM + "map_meta.txt") ||
fs::PathExists(world_path + DIR_DELIM + "world.mt"));
}
//! Try to get the displayed name of a world
std::string getWorldName(const std::string &world_path, const std::string &default_name)
{
std::string conf_path = world_path + DIR_DELIM + "world.mt";
Settings conf;
bool succeeded = conf.readConfigFile(conf_path.c_str());
if (!succeeded) {
return default_name;
}
if (!conf.exists("world_name"))
return default_name;
return conf.get("world_name");
}
std::string getWorldGameId(const std::string &world_path, bool can_be_legacy)
2012-03-11 05:54:23 -07:00
{
std::string conf_path = world_path + DIR_DELIM + "world.mt";
Settings conf;
bool succeeded = conf.readConfigFile(conf_path.c_str());
if (!succeeded) {
if (can_be_legacy) {
// If map_meta.txt exists, it is probably an old minetest world
if (fs::PathExists(world_path + DIR_DELIM + "map_meta.txt"))
return LEGACY_GAMEID;
}
2012-03-11 05:54:23 -07:00
return "";
}
if (!conf.exists("gameid"))
2012-03-11 05:54:23 -07:00
return "";
// The "mesetint" gameid has been discarded
if (conf.get("gameid") == "mesetint")
return "minetest";
2012-03-11 05:54:23 -07:00
return conf.get("gameid");
}
std::string getWorldPathEnv()
{
char *world_path = getenv("MINETEST_WORLD_PATH");
return world_path ? std::string(world_path) : "";
}
std::vector<WorldSpec> getAvailableWorlds()
{
std::vector<WorldSpec> worlds;
std::set<std::string> worldspaths;
Strfnd search_paths(getWorldPathEnv());
while (!search_paths.at_end())
worldspaths.insert(search_paths.next(PATH_DELIM));
2012-03-19 11:44:07 -07:00
worldspaths.insert(porting::path_user + DIR_DELIM + "worlds");
infostream << "Searching worlds..." << std::endl;
for (const std::string &worldspath : worldspaths) {
infostream << " In " << worldspath << ": ";
std::vector<fs::DirListNode> dirvector = fs::GetDirListing(worldspath);
for (const fs::DirListNode &dln : dirvector) {
if (!dln.dir)
continue;
std::string fullpath = worldspath + DIR_DELIM + dln.name;
std::string name = getWorldName(fullpath, dln.name);
// Just allow filling in the gameid always for now
bool can_be_legacy = true;
std::string gameid = getWorldGameId(fullpath, can_be_legacy);
WorldSpec spec(fullpath, name, gameid);
if (!spec.isValid()) {
infostream << "(invalid: " << name << ") ";
} else {
infostream << name << " ";
worlds.push_back(spec);
}
}
infostream << std::endl;
}
// Check old world location
do {
std::string fullpath = porting::path_user + DIR_DELIM + "world";
if (!fs::PathExists(fullpath))
break;
std::string name = "Old World";
std::string gameid = getWorldGameId(fullpath, true);
WorldSpec spec(fullpath, name, gameid);
infostream << "Old world found." << std::endl;
worlds.push_back(spec);
} while (false);
infostream << worlds.size() << " found." << std::endl;
return worlds;
}
void loadGameConfAndInitWorld(const std::string &path, const std::string &name,
const SubgameSpec &gamespec, bool create_world)
{
std::string final_path = path;
// If we're creating a new world, ensure that the path isn't already taken
if (create_world) {
int counter = 1;
while (fs::PathExists(final_path) && counter < MAX_WORLD_NAMES) {
final_path = path + "_" + std::to_string(counter);
counter++;
}
if (fs::PathExists(final_path)) {
throw BaseException("Too many similar filenames");
}
}
Settings *game_settings = Settings::getLayer(SL_GAME);
const bool new_game_settings = (game_settings == nullptr);
if (new_game_settings) {
// Called by main-menu without a Server instance running
// -> create and free manually
game_settings = Settings::createLayer(SL_GAME);
}
getGameMinetestConfig(gamespec.path, *game_settings);
game_settings->removeSecureSettings();
infostream << "Initializing world at " << final_path << std::endl;
fs::CreateAllDirs(final_path);
// Create world.mt if does not already exist
std::string worldmt_path = final_path + DIR_DELIM "world.mt";
if (!fs::PathExists(worldmt_path)) {
Settings conf;
conf.set("world_name", name);
conf.set("gameid", gamespec.id);
conf.set("backend", "sqlite3");
conf.set("player_backend", "sqlite3");
conf.set("auth_backend", "sqlite3");
conf.set("mod_storage_backend", "sqlite3");
conf.setBool("creative_mode", g_settings->getBool("creative_mode"));
conf.setBool("enable_damage", g_settings->getBool("enable_damage"));
if (!conf.updateConfigFile(worldmt_path.c_str())) {
throw BaseException("Failed to update the config file");
}
}
// Create map_meta.txt if does not already exist
std::string map_meta_path = final_path + DIR_DELIM + "map_meta.txt";
if (!fs::PathExists(map_meta_path)) {
MapSettingsManager mgr(map_meta_path);
mgr.setMapSetting("seed", g_settings->get("fixed_map_seed"));
mgr.makeMapgenParams();
mgr.saveMapMeta();
}
// The Settings object is no longer needed for created worlds
if (new_game_settings)
delete game_settings;
}
std::vector<std::string> getEnvModPaths()
{
const char *c_mod_path = getenv("MINETEST_MOD_PATH");
std::vector<std::string> paths;
Strfnd search_paths(c_mod_path ? c_mod_path : "");
while (!search_paths.at_end())
paths.push_back(search_paths.next(PATH_DELIM));
return paths;
}