#include "profiler.h"
#include "raycast.h"
#include "remoteplayer.h"
-#include "scripting_game.h"
+#include "scripting_server.h"
#include "server.h"
#include "voxelalgorithms.h"
#include "util/serialize.h"
#include "util/pointedthing.h"
#include "threading/mutex_auto_lock.h"
#include "filesys.h"
+#include "gameparams.h"
+#include "database-dummy.h"
+#include "database-files.h"
+#include "database-sqlite3.h"
+#if USE_POSTGRESQL
+#include "database-postgresql.h"
+#endif
#define LBM_NAME_ALLOWED_CHARS "abcdefghijklmnopqrstuvwxyz0123456789_:"
*/
ServerEnvironment::ServerEnvironment(ServerMap *map,
- GameScripting *scriptIface, IGameDef *gamedef,
- const std::string &path_world) :
+ ServerScripting *scriptIface, Server *server,
+ const std::string &path_world):
+ Environment(server),
m_map(map),
m_script(scriptIface),
- m_gamedef(gamedef),
+ m_server(server),
m_path_world(path_world),
m_send_recommended_timer(0),
m_active_block_interval_overload_skip(0),
m_game_time_fraction_counter(0),
m_last_clear_objects_time(0),
m_recommended_send_interval(0.1),
- m_max_lag_estimate(0.1)
+ m_max_lag_estimate(0.1),
+ m_player_database(NULL)
{
+ // Determine which database backend to use
+ std::string conf_path = path_world + DIR_DELIM + "world.mt";
+ Settings conf;
+ bool succeeded = conf.readConfigFile(conf_path.c_str());
+ if (!succeeded || !conf.exists("player_backend")) {
+ // fall back to files
+ conf.set("player_backend", "files");
+ warningstream << "/!\\ You are using old player file backend. "
+ << "This backend is deprecated and will be removed in next release /!\\"
+ << std::endl << "Switching to SQLite3 or PostgreSQL is advised, "
+ << "please read http://wiki.minetest.net/Database_backends." << std::endl;
+
+ if (!conf.updateConfigFile(conf_path.c_str())) {
+ errorstream << "ServerEnvironment::ServerEnvironment(): "
+ << "Failed to update world.mt!" << std::endl;
+ }
+ }
+
+ std::string name = "";
+ conf.getNoEx("player_backend", name);
+ m_player_database = openPlayerDatabase(name, path_world, conf);
}
ServerEnvironment::~ServerEnvironment()
i != m_players.end(); ++i) {
delete (*i);
}
+
+ delete m_player_database;
}
Map & ServerEnvironment::getMap()
}
}
+bool ServerEnvironment::removePlayerFromDatabase(const std::string &name)
+{
+ return m_player_database->removePlayer(name);
+}
+
bool ServerEnvironment::line_of_sight(v3f pos1, v3f pos2, float stepsize, v3s16 *p)
{
float distance = pos1.getDistanceFrom(pos2);
for (std::vector<RemotePlayer *>::iterator it = m_players.begin();
it != m_players.end(); ++it) {
RemotePlayer *player = dynamic_cast<RemotePlayer *>(*it);
- ((Server*)m_gamedef)->DenyAccessVerCompliant(player->peer_id,
+ m_server->DenyAccessVerCompliant(player->peer_id,
player->protocol_version, reason, str_reason, reconnect);
}
}
void ServerEnvironment::saveLoadedPlayers()
{
- std::string players_path = m_path_world + DIR_DELIM "players";
+ std::string players_path = m_path_world + DIR_DELIM + "players";
fs::CreateDir(players_path);
for (std::vector<RemotePlayer *>::iterator it = m_players.begin();
it != m_players.end();
++it) {
- if ((*it)->checkModified()) {
- (*it)->save(players_path, m_gamedef);
+ if ((*it)->checkModified() ||
+ ((*it)->getPlayerSAO() && (*it)->getPlayerSAO()->extendedAttributesModified())) {
+ try {
+ m_player_database->savePlayer(*it);
+ } catch (DatabaseException &e) {
+ errorstream << "Failed to save player " << (*it)->getName() << " exception: "
+ << e.what() << std::endl;
+ throw;
+ }
}
}
}
void ServerEnvironment::savePlayer(RemotePlayer *player)
{
- std::string players_path = m_path_world + DIR_DELIM "players";
- fs::CreateDir(players_path);
-
- player->save(players_path, m_gamedef);
+ try {
+ m_player_database->savePlayer(player);
+ } catch (DatabaseException &e) {
+ errorstream << "Failed to save player " << player->getName() << " exception: "
+ << e.what() << std::endl;
+ throw;
+ }
}
-RemotePlayer *ServerEnvironment::loadPlayer(const std::string &playername, PlayerSAO *sao)
+PlayerSAO *ServerEnvironment::loadPlayer(RemotePlayer *player, bool *new_player,
+ u16 peer_id, bool is_singleplayer)
{
- bool newplayer = false;
- bool found = false;
- std::string players_path = m_path_world + DIR_DELIM "players" DIR_DELIM;
- std::string path = players_path + playername;
-
- RemotePlayer *player = getPlayer(playername.c_str());
- if (!player) {
- player = new RemotePlayer("", m_gamedef->idef());
- newplayer = true;
- }
-
- for (u32 i = 0; i < PLAYER_FILE_ALTERNATE_TRIES; i++) {
- //// Open file and deserialize
- std::ifstream is(path.c_str(), std::ios_base::binary);
- if (!is.good())
- continue;
-
- player->deSerialize(is, path, sao);
- is.close();
-
- if (player->getName() == playername) {
- found = true;
- break;
+ PlayerSAO *playersao = new PlayerSAO(this, player, peer_id, is_singleplayer);
+ // Create player if it doesn't exist
+ if (!m_player_database->loadPlayer(player, playersao)) {
+ *new_player = true;
+ // Set player position
+ infostream << "Server: Finding spawn place for player \""
+ << player->getName() << "\"" << std::endl;
+ playersao->setBasePosition(m_server->findSpawnPos());
+
+ // Make sure the player is saved
+ player->setModified(true);
+ } else {
+ // If the player exists, ensure that they respawn inside legal bounds
+ // This fixes an assert crash when the player can't be added
+ // to the environment
+ if (objectpos_over_limit(playersao->getBasePosition())) {
+ actionstream << "Respawn position for player \""
+ << player->getName() << "\" outside limits, resetting" << std::endl;
+ playersao->setBasePosition(m_server->findSpawnPos());
}
-
- path = players_path + playername + itos(i);
}
- if (!found) {
- infostream << "Player file for player " << playername
- << " not found" << std::endl;
- if (newplayer)
- delete player;
+ // Add player to environment
+ addPlayer(player);
- return NULL;
- }
+ /* Clean up old HUD elements from previous sessions */
+ player->clearHud();
- if (newplayer) {
- addPlayer(player);
- }
- player->setModified(false);
- return player;
+ /* Add object to environment */
+ addActiveObject(playersao);
+
+ return playersao;
}
void ServerEnvironment::saveMeta()
} catch (SettingNotFoundException &e) {
// No problem, this is expected. Just continue with an empty string
}
- m_lbm_mgr.loadIntroductionTimes(lbm_introduction_times, m_gamedef, m_game_time);
+ m_lbm_mgr.loadIntroductionTimes(lbm_introduction_times, m_server, m_game_time);
m_day_count = args.exists("day_count") ?
args.getU64("day_count") : 0;
void ServerEnvironment::loadDefaultMeta()
{
- m_lbm_mgr.loadIntroductionTimes("", m_gamedef, m_game_time);
+ m_lbm_mgr.loadIntroductionTimes("", m_server, m_game_time);
}
struct ActiveABM
v3s16 p0;
for(p0.X=0; p0.X<MAP_BLOCKSIZE; p0.X++)
- for(p0.Y=0; p0.Y<MAP_BLOCKSIZE; p0.Y++)
- for(p0.Z=0; p0.Z<MAP_BLOCKSIZE; p0.Z++)
- {
- const MapNode &n = block->getNodeUnsafe(p0);
- content_t c = n.getContent();
+ for(p0.Y=0; p0.Y<MAP_BLOCKSIZE; p0.Y++)
+ for(p0.Z=0; p0.Z<MAP_BLOCKSIZE; p0.Z++)
+ {
+ const MapNode &n = block->getNodeUnsafe(p0);
+ content_t c = n.getContent();
- if (c >= m_aabms.size() || !m_aabms[c])
- continue;
+ if (c >= m_aabms.size() || !m_aabms[c])
+ continue;
- v3s16 p = p0 + block->getPosRelative();
- for(std::vector<ActiveABM>::iterator
- i = m_aabms[c]->begin(); i != m_aabms[c]->end(); ++i) {
- if(myrand() % i->chance != 0)
- continue;
+ v3s16 p = p0 + block->getPosRelative();
+ for(std::vector<ActiveABM>::iterator
+ i = m_aabms[c]->begin(); i != m_aabms[c]->end(); ++i) {
+ if(myrand() % i->chance != 0)
+ continue;
- // Check neighbors
- if(!i->required_neighbors.empty())
- {
- v3s16 p1;
- for(p1.X = p0.X-1; p1.X <= p0.X+1; p1.X++)
- for(p1.Y = p0.Y-1; p1.Y <= p0.Y+1; p1.Y++)
- for(p1.Z = p0.Z-1; p1.Z <= p0.Z+1; p1.Z++)
- {
- if(p1 == p0)
- continue;
- content_t c;
- if (block->isValidPosition(p1)) {
- // if the neighbor is found on the same map block
- // get it straight from there
- const MapNode &n = block->getNodeUnsafe(p1);
- c = n.getContent();
- } else {
- // otherwise consult the map
- MapNode n = map->getNodeNoEx(p1 + block->getPosRelative());
- c = n.getContent();
- }
- std::set<content_t>::const_iterator k;
- k = i->required_neighbors.find(c);
- if(k != i->required_neighbors.end()){
- goto neighbor_found;
- }
- }
- // No required neighbor found
+ // Check neighbors
+ if(!i->required_neighbors.empty())
+ {
+ v3s16 p1;
+ for(p1.X = p0.X-1; p1.X <= p0.X+1; p1.X++)
+ for(p1.Y = p0.Y-1; p1.Y <= p0.Y+1; p1.Y++)
+ for(p1.Z = p0.Z-1; p1.Z <= p0.Z+1; p1.Z++)
+ {
+ if(p1 == p0)
continue;
+ content_t c;
+ if (block->isValidPosition(p1)) {
+ // if the neighbor is found on the same map block
+ // get it straight from there
+ const MapNode &n = block->getNodeUnsafe(p1);
+ c = n.getContent();
+ } else {
+ // otherwise consult the map
+ MapNode n = map->getNodeNoEx(p1 + block->getPosRelative());
+ c = n.getContent();
}
- neighbor_found:
-
- // Call all the trigger variations
- i->abm->trigger(m_env, p, n);
- i->abm->trigger(m_env, p, n,
- active_object_count, active_object_count_wider);
-
- // Count surrounding objects again if the abms added any
- if(m_env->m_added_objects > 0) {
- active_object_count = countObjects(block, map, active_object_count_wider);
- m_env->m_added_objects = 0;
+ std::set<content_t>::const_iterator k;
+ k = i->required_neighbors.find(c);
+ if(k != i->required_neighbors.end()){
+ goto neighbor_found;
}
}
+ // No required neighbor found
+ continue;
}
+ neighbor_found:
+
+ // Call all the trigger variations
+ i->abm->trigger(m_env, p, n);
+ i->abm->trigger(m_env, p, n,
+ active_object_count, active_object_count_wider);
+
+ // Count surrounding objects again if the abms added any
+ if(m_env->m_added_objects > 0) {
+ active_object_count = countObjects(block, map, active_object_count_wider);
+ m_env->m_added_objects = 0;
+ }
+ }
+ }
}
};
bool ServerEnvironment::setNode(v3s16 p, const MapNode &n)
{
- INodeDefManager *ndef = m_gamedef->ndef();
+ INodeDefManager *ndef = m_server->ndef();
MapNode n_old = m_map->getNodeNoEx(p);
// Call destructor
bool ServerEnvironment::removeNode(v3s16 p)
{
- INodeDefManager *ndef = m_gamedef->ndef();
+ INodeDefManager *ndef = m_server->ndef();
MapNode n_old = m_map->getNodeNoEx(p);
// Call destructor
if (objectpos_over_limit(object->getBasePosition())) {
v3f p = object->getBasePosition();
- errorstream << "ServerEnvironment::addActiveObjectRaw(): "
+ warningstream << "ServerEnvironment::addActiveObjectRaw(): "
<< "object position (" << p.X << "," << p.Y << "," << p.Z
<< ") outside maximum range" << std::endl;
if (object->environmentDeletes())
{
// Add static object to active static list of the block
v3f objectpos = object->getBasePosition();
- std::string staticdata = object->getStaticData();
+ std::string staticdata = "";
+ object->getStaticData(&staticdata);
StaticObject s_obj(object->getType(), objectpos, staticdata);
// Add to the block where the object is located in
v3s16 blockpos = getNodeBlockPos(floatToInt(objectpos, BS));
If block wasn't generated (not in memory or on disk),
*/
-void ServerEnvironment::deactivateFarObjects(bool force_delete)
+void ServerEnvironment::deactivateFarObjects(bool _force_delete)
{
std::vector<u16> objects_to_remove;
for(ActiveObjectMap::iterator i = m_active_objects.begin();
i != m_active_objects.end(); ++i) {
+ // force_delete might be overriden per object
+ bool force_delete = _force_delete;
+
ServerActiveObject* obj = i->second;
assert(obj);
<<std::endl;
continue;
}
- std::string staticdata_new = obj->getStaticData();
+ std::string staticdata_new = "";
+ obj->getStaticData(&staticdata_new);
StaticObject s_obj(obj->getType(), objectpos, staticdata_new);
block->m_static_objects.insert(id, s_obj);
obj->m_static_block = blockpos_o;
if(obj->isStaticAllowed())
{
// Create new static object
- std::string staticdata_new = obj->getStaticData();
+ std::string staticdata_new = "";
+ obj->getStaticData(&staticdata_new);
StaticObject s_obj(obj->getType(), objectpos, staticdata_new);
bool stays_in_same_block = false;
m_active_objects.erase(*i);
}
}
+
+PlayerDatabase *ServerEnvironment::openPlayerDatabase(const std::string &name,
+ const std::string &savedir, const Settings &conf)
+{
+
+ if (name == "sqlite3")
+ return new PlayerDatabaseSQLite3(savedir);
+ else if (name == "dummy")
+ return new Database_Dummy();
+#if USE_POSTGRESQL
+ else if (name == "postgresql") {
+ std::string connect_string = "";
+ conf.getNoEx("pgsql_player_connection", connect_string);
+ return new PlayerDatabasePostgreSQL(connect_string);
+ }
+#endif
+ else if (name == "files")
+ return new PlayerDatabaseFiles(savedir + DIR_DELIM + "players");
+ else
+ throw BaseException(std::string("Database backend ") + name + " not supported.");
+}
+
+bool ServerEnvironment::migratePlayersDatabase(const GameParams &game_params,
+ const Settings &cmd_args)
+{
+ std::string migrate_to = cmd_args.get("migrate-players");
+ Settings world_mt;
+ std::string world_mt_path = game_params.world_path + DIR_DELIM + "world.mt";
+ if (!world_mt.readConfigFile(world_mt_path.c_str())) {
+ errorstream << "Cannot read world.mt!" << std::endl;
+ return false;
+ }
+
+ if (!world_mt.exists("player_backend")) {
+ errorstream << "Please specify your current backend in world.mt:"
+ << std::endl
+ << " player_backend = {files|sqlite3|postgresql}"
+ << std::endl;
+ return false;
+ }
+
+ std::string backend = world_mt.get("player_backend");
+ if (backend == migrate_to) {
+ errorstream << "Cannot migrate: new backend is same"
+ << " as the old one" << std::endl;
+ return false;
+ }
+
+ const std::string players_backup_path = game_params.world_path + DIR_DELIM
+ + "players.bak";
+
+ if (backend == "files") {
+ // Create backup directory
+ fs::CreateDir(players_backup_path);
+ }
+
+ try {
+ PlayerDatabase *srcdb = ServerEnvironment::openPlayerDatabase(backend,
+ game_params.world_path, world_mt);
+ PlayerDatabase *dstdb = ServerEnvironment::openPlayerDatabase(migrate_to,
+ game_params.world_path, world_mt);
+
+ std::vector<std::string> player_list;
+ srcdb->listPlayers(player_list);
+ for (std::vector<std::string>::const_iterator it = player_list.begin();
+ it != player_list.end(); ++it) {
+ actionstream << "Migrating player " << it->c_str() << std::endl;
+ RemotePlayer player(it->c_str(), NULL);
+ PlayerSAO playerSAO(NULL, &player, 15000, false);
+
+ srcdb->loadPlayer(&player, &playerSAO);
+
+ playerSAO.finalize(&player, std::set<std::string>());
+ player.setPlayerSAO(&playerSAO);
+
+ dstdb->savePlayer(&player);
+
+ // For files source, move player files to backup dir
+ if (backend == "files") {
+ fs::Rename(
+ game_params.world_path + DIR_DELIM + "players" + DIR_DELIM + (*it),
+ players_backup_path + DIR_DELIM + (*it));
+ }
+ }
+
+ actionstream << "Successfully migrated " << player_list.size() << " players"
+ << std::endl;
+ world_mt.set("player_backend", migrate_to);
+ if (!world_mt.updateConfigFile(world_mt_path.c_str()))
+ errorstream << "Failed to update world.mt!" << std::endl;
+ else
+ actionstream << "world.mt updated" << std::endl;
+
+ // When migration is finished from file backend, remove players directory if empty
+ if (backend == "files") {
+ fs::DeleteSingleFileOrEmptyDirectory(game_params.world_path + DIR_DELIM
+ + "players");
+ }
+
+ delete srcdb;
+ delete dstdb;
+
+ } catch (BaseException &e) {
+ errorstream << "An error occured during migration: " << e.what() << std::endl;
+ return false;
+ }
+ return true;
+}