minetest/src/subgame.cpp

320 lines
9.4 KiB
C++
Raw Normal View History

2012-03-11 13:54:23 +01:00
/*
2013-02-24 18:40:43 +01:00
Minetest
2013-02-24 19:38:45 +01:00
Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
2012-03-11 13:54:23 +01: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 13:54:23 +01: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 13:54:23 +01:00
You should have received a copy of the GNU Lesser General Public License along
2012-03-11 13:54:23 +01:00
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "subgame.h"
#include "porting.h"
#include "filesys.h"
#include "settings.h"
#include "log.h"
#include "strfnd.h"
#include "defaultsettings.h" // for override_default_settings
#include "mapgen.h" // for MapgenParams
#include "util/string.h"
#ifndef SERVER
#include "client/tile.h" // getImagePath
#endif
2012-03-11 13:54:23 +01:00
2013-03-21 20:42:23 +01: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());
}
bool getGameConfig(const std::string &game_path, Settings &conf)
2012-03-26 22:02:12 +02:00
{
std::string conf_path = game_path + DIR_DELIM + "game.conf";
return conf.readConfigFile(conf_path.c_str());
}
std::string getGameName(const std::string &game_path)
{
2012-03-26 22:02:12 +02:00
Settings conf;
if(!getGameConfig(game_path, conf))
2012-03-26 22:02:12 +02:00
return "";
if(!conf.exists("name"))
return "";
return conf.get("name");
}
struct GameFindPath
{
std::string path;
bool user_specific;
GameFindPath(const std::string &path, bool user_specific):
path(path),
user_specific(user_specific)
{}
};
Strfnd getSubgamePathEnv() {
2014-10-28 20:13:14 +01:00
std::string sp;
char *subgame_path = getenv("MINETEST_SUBGAME_PATH");
2014-10-28 20:13:14 +01:00
if(subgame_path) {
sp = std::string(subgame_path);
}
2014-10-28 20:13:14 +01:00
return Strfnd(sp);
}
2012-03-11 13:54:23 +01:00
SubgameSpec findSubgame(const std::string &id)
{
if(id == "")
return SubgameSpec();
2012-03-19 19:44:07 +01:00
std::string share = porting::path_share;
std::string user = porting::path_user;
std::vector<GameFindPath> find_paths;
2014-10-28 20:13:14 +01:00
Strfnd search_paths = getSubgamePathEnv();
2014-10-28 20:13:14 +01:00
while(!search_paths.atend()) {
std::string path = search_paths.next(":");
find_paths.push_back(GameFindPath(
path + DIR_DELIM + id, false));
find_paths.push_back(GameFindPath(
path + DIR_DELIM + id + "_game", false));
}
find_paths.push_back(GameFindPath(
user + DIR_DELIM + "games" + DIR_DELIM + id + "_game", true));
find_paths.push_back(GameFindPath(
user + DIR_DELIM + "games" + DIR_DELIM + id, true));
find_paths.push_back(GameFindPath(
share + DIR_DELIM + "games" + DIR_DELIM + id + "_game", false));
find_paths.push_back(GameFindPath(
share + DIR_DELIM + "games" + DIR_DELIM + id, false));
2012-03-11 13:54:23 +01:00
// Find game directory
std::string game_path;
2012-03-11 13:54:23 +01:00
bool user_game = true; // Game is in user's directory
for(u32 i=0; i<find_paths.size(); i++){
const std::string &try_path = find_paths[i].path;
if(fs::PathExists(try_path)){
game_path = try_path;
user_game = find_paths[i].user_specific;
break;
}
2012-03-11 13:54:23 +01:00
}
if(game_path == "")
2012-03-11 13:54:23 +01: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 18:10:54 +01:00
std::string gamemod_path = game_path + DIR_DELIM + "mods";
2012-03-20 00:06:44 +01:00
// Find mod directories
std::set<std::string> mods_paths;
2012-03-11 13:54:23 +01:00
if(!user_game)
mods_paths.insert(share + DIR_DELIM + "mods");
2012-03-20 00:06:44 +01:00
if(user != share || user_game)
mods_paths.insert(user + DIR_DELIM + "mods");
2012-03-26 22:02:12 +02:00
std::string game_name = getGameName(game_path);
if(game_name == "")
game_name = id;
std::string menuicon_path;
#ifndef SERVER
menuicon_path = getImagePath(game_path + DIR_DELIM + "menu" + DIR_DELIM + "icon.png");
#endif
return SubgameSpec(id, game_path, gamemod_path, mods_paths, game_name,
menuicon_path);
2012-03-11 13:54:23 +01: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;
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 18:10:54 +01:00
gamespec.gamemods_path= world_gamepath + DIR_DELIM + "mods";
gamespec.name = getGameName(world_gamepath);
if(gamespec.name == "")
gamespec.name = "unknown";
return gamespec;
}
return findSubgame(world_gameid);
}
2012-03-11 13:54:23 +01:00
std::set<std::string> getAvailableGameIds()
{
std::set<std::string> gameids;
std::set<std::string> gamespaths;
2012-03-19 19:44:07 +01:00
gamespaths.insert(porting::path_share + DIR_DELIM + "games");
gamespaths.insert(porting::path_user + DIR_DELIM + "games");
2014-10-28 20:13:14 +01:00
Strfnd search_paths = getSubgamePathEnv();
2014-10-28 20:13:14 +01:00
while(!search_paths.atend()) {
gamespaths.insert(search_paths.next(":"));
}
2012-03-11 13:54:23 +01:00
for(std::set<std::string>::const_iterator i = gamespaths.begin();
2015-08-25 22:23:05 +02:00
i != gamespaths.end(); ++i){
2012-03-11 13:54:23 +01:00
std::vector<fs::DirListNode> dirlist = fs::GetDirListing(*i);
for(u32 j=0; j<dirlist.size(); j++){
if(!dirlist[j].dir)
continue;
// If configuration file is not found or broken, ignore game
Settings conf;
if(!getGameConfig(*i + DIR_DELIM + dirlist[j].name, conf))
continue;
// Add it to result
const char *ends[] = {"_game", NULL};
std::string shorter = removeStringEnd(dirlist[j].name, ends);
if(shorter != "")
gameids.insert(shorter);
else
gameids.insert(dirlist[j].name);
2012-03-11 13:54:23 +01:00
}
}
return gameids;
}
std::vector<SubgameSpec> getAvailableGames()
{
std::vector<SubgameSpec> specs;
std::set<std::string> gameids = getAvailableGameIds();
for(std::set<std::string>::const_iterator i = gameids.begin();
2015-08-25 22:23:05 +02:00
i != gameids.end(); ++i)
specs.push_back(findSubgame(*i));
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"));
}
std::string getWorldGameId(const std::string &world_path, bool can_be_legacy)
2012-03-11 13:54:23 +01: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 13:54:23 +01:00
return "";
}
2012-03-11 13:54:23 +01:00
if(!conf.exists("gameid"))
return "";
// The "mesetint" gameid has been discarded
if(conf.get("gameid") == "mesetint")
return "minetest";
2012-03-11 13:54:23 +01:00
return conf.get("gameid");
}
std::vector<WorldSpec> getAvailableWorlds()
{
std::vector<WorldSpec> worlds;
std::set<std::string> worldspaths;
2012-03-19 19:44:07 +01:00
worldspaths.insert(porting::path_user + DIR_DELIM + "worlds");
infostream<<"Searching worlds..."<<std::endl;
for(std::set<std::string>::const_iterator i = worldspaths.begin();
2015-08-25 22:23:05 +02:00
i != worldspaths.end(); ++i){
infostream<<" In "<<(*i)<<": "<<std::endl;
std::vector<fs::DirListNode> dirvector = fs::GetDirListing(*i);
for(u32 j=0; j<dirvector.size(); j++){
if(!dirvector[j].dir)
continue;
std::string fullpath = *i + DIR_DELIM + dirvector[j].name;
std::string name = dirvector[j].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(0);
infostream<<worlds.size()<<" found."<<std::endl;
return worlds;
}
bool loadGameConfAndInitWorld(const std::string &path, const SubgameSpec &gamespec)
{
// Override defaults with those provided by the game.
// We clear and reload the defaults because the defaults
// might have been overridden by other subgame config
// files that were loaded before.
g_settings->clearDefaults();
set_default_settings(g_settings);
Settings game_defaults;
getGameMinetestConfig(gamespec.path, game_defaults);
override_default_settings(g_settings, &game_defaults);
infostream << "Initializing world at " << path << std::endl;
fs::CreateAllDirs(path);
// Create world.mt if does not already exist
std::string worldmt_path = path + DIR_DELIM "world.mt";
if (!fs::PathExists(worldmt_path)) {
std::ostringstream ss(std::ios_base::binary);
ss << "gameid = " << gamespec.id
<< "\nbackend = sqlite3"
<< "\ncreative_mode = " << g_settings->get("creative_mode")
<< "\nenable_damage = " << g_settings->get("enable_damage")
<< "\n";
if (!fs::safeWriteToFile(worldmt_path, ss.str()))
return false;
infostream << "Wrote world.mt (" << worldmt_path << ")" << std::endl;
}
// Create map_meta.txt if does not already exist
std::string map_meta_path = path + DIR_DELIM + "map_meta.txt";
if (!fs::PathExists(map_meta_path)){
verbosestream << "Creating map_meta.txt (" << map_meta_path << ")" << std::endl;
fs::CreateAllDirs(path);
std::ostringstream oss(std::ios_base::binary);
Settings conf;
MapgenParams params;
params.load(*g_settings);
params.save(conf);
conf.writeLines(oss);
oss << "[end_of_params]\n";
fs::safeWriteToFile(map_meta_path, oss.str());
}
return true;
}