Player data to Database (#5475)
* Player data to Database Add player data into databases (SQLite3 & PG only) PostgreSQL & SQLite: better POO Design for databases Add --migrate-players argument to server + deprecation warning * Remove players directory if emptymaster
parent
dda171d292
commit
29ab20c272
|
@ -134,6 +134,7 @@ LOCAL_SRC_FILES := \
|
||||||
jni/src/convert_json.cpp \
|
jni/src/convert_json.cpp \
|
||||||
jni/src/craftdef.cpp \
|
jni/src/craftdef.cpp \
|
||||||
jni/src/database-dummy.cpp \
|
jni/src/database-dummy.cpp \
|
||||||
|
jni/src/database-files.cpp \
|
||||||
jni/src/database-sqlite3.cpp \
|
jni/src/database-sqlite3.cpp \
|
||||||
jni/src/database.cpp \
|
jni/src/database.cpp \
|
||||||
jni/src/debug.cpp \
|
jni/src/debug.cpp \
|
||||||
|
|
|
@ -279,6 +279,31 @@ core.register_chatcommand("auth_reload", {
|
||||||
end,
|
end,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
core.register_chatcommand("remove_player", {
|
||||||
|
params = "<name>",
|
||||||
|
description = "Remove player data",
|
||||||
|
privs = {server=true},
|
||||||
|
func = function(name, param)
|
||||||
|
local toname = param
|
||||||
|
if toname == "" then
|
||||||
|
return false, "Name field required"
|
||||||
|
end
|
||||||
|
|
||||||
|
local rc = core.remove_player(toname)
|
||||||
|
|
||||||
|
if rc == 0 then
|
||||||
|
core.log("action", name .. " removed player data of " .. toname .. ".")
|
||||||
|
return true, "Player \"" .. toname .. "\" removed."
|
||||||
|
elseif rc == 1 then
|
||||||
|
return true, "No such player \"" .. toname .. "\" to remove."
|
||||||
|
elseif rc == 2 then
|
||||||
|
return true, "Player \"" .. toname .. "\" is connected, cannot remove."
|
||||||
|
end
|
||||||
|
|
||||||
|
return false, "Unhandled remove_player return code " .. rc .. ""
|
||||||
|
end,
|
||||||
|
})
|
||||||
|
|
||||||
core.register_chatcommand("teleport", {
|
core.register_chatcommand("teleport", {
|
||||||
params = "<X>,<Y>,<Z> | <to_name> | <name> <X>,<Y>,<Z> | <name> <to_name>",
|
params = "<X>,<Y>,<Z> | <to_name> | <name> <X>,<Y>,<Z> | <name> <to_name>",
|
||||||
description = "Teleport to player or position",
|
description = "Teleport to player or position",
|
||||||
|
|
|
@ -2599,6 +2599,8 @@ These functions return the leftover itemstack.
|
||||||
* `minetest.cancel_shutdown_requests()`: cancel current delayed shutdown
|
* `minetest.cancel_shutdown_requests()`: cancel current delayed shutdown
|
||||||
* `minetest.get_server_status()`: returns server status string
|
* `minetest.get_server_status()`: returns server status string
|
||||||
* `minetest.get_server_uptime()`: returns the server uptime in seconds
|
* `minetest.get_server_uptime()`: returns the server uptime in seconds
|
||||||
|
* `minetest.remove_player(name)`: remove player from database (if he is not connected).
|
||||||
|
* Returns a code (0: successful, 1: no such player, 2: player is connected)
|
||||||
|
|
||||||
### Bans
|
### Bans
|
||||||
* `minetest.get_ban_list()`: returns the ban list (same as `minetest.get_ban_description("")`)
|
* `minetest.get_ban_list()`: returns the ban list (same as `minetest.get_ban_description("")`)
|
||||||
|
|
|
@ -377,6 +377,7 @@ set(common_SRCS
|
||||||
convert_json.cpp
|
convert_json.cpp
|
||||||
craftdef.cpp
|
craftdef.cpp
|
||||||
database-dummy.cpp
|
database-dummy.cpp
|
||||||
|
database-files.cpp
|
||||||
database-leveldb.cpp
|
database-leveldb.cpp
|
||||||
database-postgresql.cpp
|
database-postgresql.cpp
|
||||||
database-redis.cpp
|
database-redis.cpp
|
||||||
|
|
|
@ -770,7 +770,7 @@ void Client::initLocalMapSaving(const Address &address,
|
||||||
|
|
||||||
fs::CreateAllDirs(world_path);
|
fs::CreateAllDirs(world_path);
|
||||||
|
|
||||||
m_localdb = new Database_SQLite3(world_path);
|
m_localdb = new MapDatabaseSQLite3(world_path);
|
||||||
m_localdb->beginSave();
|
m_localdb->beginSave();
|
||||||
actionstream << "Local map saving started, map will be saved at '" << world_path << "'" << std::endl;
|
actionstream << "Local map saving started, map will be saved at '" << world_path << "'" << std::endl;
|
||||||
}
|
}
|
||||||
|
|
|
@ -49,7 +49,7 @@ class ClientMediaDownloader;
|
||||||
struct MapDrawControl;
|
struct MapDrawControl;
|
||||||
class MtEventManager;
|
class MtEventManager;
|
||||||
struct PointedThing;
|
struct PointedThing;
|
||||||
class Database;
|
class MapDatabase;
|
||||||
class Minimap;
|
class Minimap;
|
||||||
struct MinimapMapblock;
|
struct MinimapMapblock;
|
||||||
class Camera;
|
class Camera;
|
||||||
|
@ -645,7 +645,7 @@ private:
|
||||||
LocalClientState m_state;
|
LocalClientState m_state;
|
||||||
|
|
||||||
// Used for saving server map to disk client-side
|
// Used for saving server map to disk client-side
|
||||||
Database *m_localdb;
|
MapDatabase *m_localdb;
|
||||||
IntervalLimiter m_localdb_save_interval;
|
IntervalLimiter m_localdb_save_interval;
|
||||||
u16 m_cache_save_interval;
|
u16 m_cache_save_interval;
|
||||||
|
|
||||||
|
|
|
@ -764,9 +764,10 @@ bool LuaEntitySAO::collideWithObjects() const
|
||||||
|
|
||||||
// No prototype, PlayerSAO does not need to be deserialized
|
// No prototype, PlayerSAO does not need to be deserialized
|
||||||
|
|
||||||
PlayerSAO::PlayerSAO(ServerEnvironment *env_, u16 peer_id_, bool is_singleplayer):
|
PlayerSAO::PlayerSAO(ServerEnvironment *env_, RemotePlayer *player_, u16 peer_id_,
|
||||||
|
bool is_singleplayer):
|
||||||
UnitSAO(env_, v3f(0,0,0)),
|
UnitSAO(env_, v3f(0,0,0)),
|
||||||
m_player(NULL),
|
m_player(player_),
|
||||||
m_peer_id(peer_id_),
|
m_peer_id(peer_id_),
|
||||||
m_inventory(NULL),
|
m_inventory(NULL),
|
||||||
m_damage(0),
|
m_damage(0),
|
||||||
|
@ -819,7 +820,7 @@ PlayerSAO::~PlayerSAO()
|
||||||
delete m_inventory;
|
delete m_inventory;
|
||||||
}
|
}
|
||||||
|
|
||||||
void PlayerSAO::initialize(RemotePlayer *player, const std::set<std::string> &privs)
|
void PlayerSAO::finalize(RemotePlayer *player, const std::set<std::string> &privs)
|
||||||
{
|
{
|
||||||
assert(player);
|
assert(player);
|
||||||
m_player = player;
|
m_player = player;
|
||||||
|
|
|
@ -194,7 +194,7 @@ class RemotePlayer;
|
||||||
class PlayerSAO : public UnitSAO
|
class PlayerSAO : public UnitSAO
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
PlayerSAO(ServerEnvironment *env_, u16 peer_id_, bool is_singleplayer);
|
PlayerSAO(ServerEnvironment *env_, RemotePlayer *player_, u16 peer_id_, bool is_singleplayer);
|
||||||
~PlayerSAO();
|
~PlayerSAO();
|
||||||
ActiveObjectType getType() const
|
ActiveObjectType getType() const
|
||||||
{ return ACTIVEOBJECT_TYPE_PLAYER; }
|
{ return ACTIVEOBJECT_TYPE_PLAYER; }
|
||||||
|
@ -349,7 +349,7 @@ public:
|
||||||
bool getCollisionBox(aabb3f *toset) const;
|
bool getCollisionBox(aabb3f *toset) const;
|
||||||
bool collideWithObjects() const { return true; }
|
bool collideWithObjects() const { return true; }
|
||||||
|
|
||||||
void initialize(RemotePlayer *player, const std::set<std::string> &privs);
|
void finalize(RemotePlayer *player, const std::set<std::string> &privs);
|
||||||
|
|
||||||
v3f getEyePosition() const { return m_base_position + getEyeOffset(); }
|
v3f getEyePosition() const { return m_base_position + getEyeOffset(); }
|
||||||
v3f getEyeOffset() const;
|
v3f getEyeOffset() const;
|
||||||
|
|
|
@ -25,7 +25,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
#include "database.h"
|
#include "database.h"
|
||||||
#include "irrlichttypes.h"
|
#include "irrlichttypes.h"
|
||||||
|
|
||||||
class Database_Dummy : public Database
|
class Database_Dummy : public MapDatabase, public PlayerDatabase
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
bool saveBlock(const v3s16 &pos, const std::string &data);
|
bool saveBlock(const v3s16 &pos, const std::string &data);
|
||||||
|
@ -33,6 +33,13 @@ public:
|
||||||
bool deleteBlock(const v3s16 &pos);
|
bool deleteBlock(const v3s16 &pos);
|
||||||
void listAllLoadableBlocks(std::vector<v3s16> &dst);
|
void listAllLoadableBlocks(std::vector<v3s16> &dst);
|
||||||
|
|
||||||
|
void savePlayer(RemotePlayer *player) {}
|
||||||
|
bool loadPlayer(RemotePlayer *player, PlayerSAO *sao) { return true; }
|
||||||
|
bool removePlayer(const std::string &name) { return true; }
|
||||||
|
void listPlayers(std::vector<std::string> &) {}
|
||||||
|
|
||||||
|
void beginSave() {}
|
||||||
|
void endSave() {}
|
||||||
private:
|
private:
|
||||||
std::map<s64, std::string> m_database;
|
std::map<s64, std::string> m_database;
|
||||||
};
|
};
|
||||||
|
|
|
@ -0,0 +1,179 @@
|
||||||
|
/*
|
||||||
|
Minetest
|
||||||
|
Copyright (C) 2017 nerzhul, Loic Blot <loic.blot@unix-experience.fr>
|
||||||
|
|
||||||
|
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
|
||||||
|
(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.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Lesser General Public License along
|
||||||
|
with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
|
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <cassert>
|
||||||
|
#include <json/json.h>
|
||||||
|
#include "database-files.h"
|
||||||
|
#include "content_sao.h"
|
||||||
|
#include "remoteplayer.h"
|
||||||
|
#include "settings.h"
|
||||||
|
#include "porting.h"
|
||||||
|
#include "filesys.h"
|
||||||
|
|
||||||
|
// !!! WARNING !!!
|
||||||
|
// This backend is intended to be used on Minetest 0.4.16 only for the transition backend for
|
||||||
|
// player files
|
||||||
|
|
||||||
|
void PlayerDatabaseFiles::serialize(std::ostringstream &os, RemotePlayer *player)
|
||||||
|
{
|
||||||
|
// Utilize a Settings object for storing values
|
||||||
|
Settings args;
|
||||||
|
args.setS32("version", 1);
|
||||||
|
args.set("name", player->getName());
|
||||||
|
|
||||||
|
sanity_check(player->getPlayerSAO());
|
||||||
|
args.setS32("hp", player->getPlayerSAO()->getHP());
|
||||||
|
args.setV3F("position", player->getPlayerSAO()->getBasePosition());
|
||||||
|
args.setFloat("pitch", player->getPlayerSAO()->getPitch());
|
||||||
|
args.setFloat("yaw", player->getPlayerSAO()->getYaw());
|
||||||
|
args.setS32("breath", player->getPlayerSAO()->getBreath());
|
||||||
|
|
||||||
|
std::string extended_attrs = "";
|
||||||
|
player->serializeExtraAttributes(extended_attrs);
|
||||||
|
args.set("extended_attributes", extended_attrs);
|
||||||
|
|
||||||
|
args.writeLines(os);
|
||||||
|
|
||||||
|
os << "PlayerArgsEnd\n";
|
||||||
|
|
||||||
|
player->inventory.serialize(os);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlayerDatabaseFiles::savePlayer(RemotePlayer *player)
|
||||||
|
{
|
||||||
|
std::string savedir = m_savedir + DIR_DELIM;
|
||||||
|
std::string path = savedir + player->getName();
|
||||||
|
bool path_found = false;
|
||||||
|
RemotePlayer testplayer("", NULL);
|
||||||
|
|
||||||
|
for (u32 i = 0; i < PLAYER_FILE_ALTERNATE_TRIES && !path_found; i++) {
|
||||||
|
if (!fs::PathExists(path)) {
|
||||||
|
path_found = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open and deserialize file to check player name
|
||||||
|
std::ifstream is(path.c_str(), std::ios_base::binary);
|
||||||
|
if (!is.good()) {
|
||||||
|
errorstream << "Failed to open " << path << std::endl;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
testplayer.deSerialize(is, path, NULL);
|
||||||
|
is.close();
|
||||||
|
if (strcmp(testplayer.getName(), player->getName()) == 0) {
|
||||||
|
path_found = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
path = savedir + player->getName() + itos(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!path_found) {
|
||||||
|
errorstream << "Didn't find free file for player " << player->getName()
|
||||||
|
<< std::endl;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open and serialize file
|
||||||
|
std::ostringstream ss(std::ios_base::binary);
|
||||||
|
serialize(ss, player);
|
||||||
|
if (!fs::safeWriteToFile(path, ss.str())) {
|
||||||
|
infostream << "Failed to write " << path << std::endl;
|
||||||
|
}
|
||||||
|
player->setModified(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PlayerDatabaseFiles::removePlayer(const std::string &name)
|
||||||
|
{
|
||||||
|
std::string players_path = m_savedir + DIR_DELIM;
|
||||||
|
std::string path = players_path + name;
|
||||||
|
|
||||||
|
RemotePlayer temp_player("", NULL);
|
||||||
|
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;
|
||||||
|
|
||||||
|
temp_player.deSerialize(is, path, NULL);
|
||||||
|
is.close();
|
||||||
|
|
||||||
|
if (temp_player.getName() == name) {
|
||||||
|
fs::DeleteSingleFileOrEmptyDirectory(path);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
path = players_path + name + itos(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PlayerDatabaseFiles::loadPlayer(RemotePlayer *player, PlayerSAO *sao)
|
||||||
|
{
|
||||||
|
std::string players_path = m_savedir + DIR_DELIM;
|
||||||
|
std::string path = players_path + player->getName();
|
||||||
|
|
||||||
|
const std::string player_to_load = player->getName();
|
||||||
|
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() == player_to_load)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
path = players_path + player_to_load + itos(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
infostream << "Player file for player " << player_to_load << " not found" << std::endl;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlayerDatabaseFiles::listPlayers(std::vector<std::string> &res)
|
||||||
|
{
|
||||||
|
std::vector<fs::DirListNode> files = fs::GetDirListing(m_savedir);
|
||||||
|
// list files into players directory
|
||||||
|
for (std::vector<fs::DirListNode>::const_iterator it = files.begin(); it !=
|
||||||
|
files.end(); ++it) {
|
||||||
|
// Ignore directories
|
||||||
|
if (it->dir)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
const std::string &filename = it->name;
|
||||||
|
std::string full_path = m_savedir + DIR_DELIM + filename;
|
||||||
|
std::ifstream is(full_path.c_str(), std::ios_base::binary);
|
||||||
|
if (!is.good())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
RemotePlayer player(filename.c_str(), NULL);
|
||||||
|
// Null env & dummy peer_id
|
||||||
|
PlayerSAO playerSAO(NULL, &player, 15789, false);
|
||||||
|
|
||||||
|
player.deSerialize(is, "", &playerSAO);
|
||||||
|
is.close();
|
||||||
|
|
||||||
|
res.push_back(player.getName());
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,46 @@
|
||||||
|
/*
|
||||||
|
Minetest
|
||||||
|
Copyright (C) 2017 nerzhul, Loic Blot <loic.blot@unix-experience.fr>
|
||||||
|
|
||||||
|
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
|
||||||
|
(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.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Lesser General Public License along
|
||||||
|
with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
|
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef DATABASE_FILES_HEADER
|
||||||
|
#define DATABASE_FILES_HEADER
|
||||||
|
|
||||||
|
// !!! WARNING !!!
|
||||||
|
// This backend is intended to be used on Minetest 0.4.16 only for the transition backend for
|
||||||
|
// player files
|
||||||
|
|
||||||
|
#include "database.h"
|
||||||
|
|
||||||
|
class PlayerDatabaseFiles : public PlayerDatabase
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
PlayerDatabaseFiles(const std::string &savedir) : m_savedir(savedir) {}
|
||||||
|
virtual ~PlayerDatabaseFiles() {}
|
||||||
|
|
||||||
|
void savePlayer(RemotePlayer *player);
|
||||||
|
bool loadPlayer(RemotePlayer *player, PlayerSAO *sao);
|
||||||
|
bool removePlayer(const std::string &name);
|
||||||
|
void listPlayers(std::vector<std::string> &res);
|
||||||
|
|
||||||
|
private:
|
||||||
|
void serialize(std::ostringstream &os, RemotePlayer *player);
|
||||||
|
|
||||||
|
std::string m_savedir;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
|
@ -28,7 +28,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
#include "database.h"
|
#include "database.h"
|
||||||
#include "leveldb/db.h"
|
#include "leveldb/db.h"
|
||||||
|
|
||||||
class Database_LevelDB : public Database
|
class Database_LevelDB : public MapDatabase
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
Database_LevelDB(const std::string &savedir);
|
Database_LevelDB(const std::string &savedir);
|
||||||
|
@ -39,6 +39,8 @@ public:
|
||||||
bool deleteBlock(const v3s16 &pos);
|
bool deleteBlock(const v3s16 &pos);
|
||||||
void listAllLoadableBlocks(std::vector<v3s16> &dst);
|
void listAllLoadableBlocks(std::vector<v3s16> &dst);
|
||||||
|
|
||||||
|
void beginSave() {}
|
||||||
|
void endSave() {}
|
||||||
private:
|
private:
|
||||||
leveldb::DB *m_database;
|
leveldb::DB *m_database;
|
||||||
};
|
};
|
||||||
|
|
|
@ -39,13 +39,15 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
#include "log.h"
|
#include "log.h"
|
||||||
#include "exceptions.h"
|
#include "exceptions.h"
|
||||||
#include "settings.h"
|
#include "settings.h"
|
||||||
|
#include "content_sao.h"
|
||||||
|
#include "remoteplayer.h"
|
||||||
|
|
||||||
Database_PostgreSQL::Database_PostgreSQL(const Settings &conf) :
|
Database_PostgreSQL::Database_PostgreSQL(const std::string &connect_string) :
|
||||||
m_connect_string(""),
|
m_connect_string(connect_string),
|
||||||
m_conn(NULL),
|
m_conn(NULL),
|
||||||
m_pgversion(0)
|
m_pgversion(0)
|
||||||
{
|
{
|
||||||
if (!conf.getNoEx("pgsql_connection", m_connect_string)) {
|
if (m_connect_string.empty()) {
|
||||||
throw SettingNotFoundException(
|
throw SettingNotFoundException(
|
||||||
"Set pgsql_connection string in world.mt to "
|
"Set pgsql_connection string in world.mt to "
|
||||||
"use the postgresql backend\n"
|
"use the postgresql backend\n"
|
||||||
|
@ -57,8 +59,6 @@ Database_PostgreSQL::Database_PostgreSQL(const Settings &conf) :
|
||||||
"DELETE rights on the database.\n"
|
"DELETE rights on the database.\n"
|
||||||
"Don't create mt_user as a SUPERUSER!");
|
"Don't create mt_user as a SUPERUSER!");
|
||||||
}
|
}
|
||||||
|
|
||||||
connectToDatabase();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Database_PostgreSQL::~Database_PostgreSQL()
|
Database_PostgreSQL::~Database_PostgreSQL()
|
||||||
|
@ -118,14 +118,84 @@ bool Database_PostgreSQL::initialized() const
|
||||||
return (PQstatus(m_conn) == CONNECTION_OK);
|
return (PQstatus(m_conn) == CONNECTION_OK);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Database_PostgreSQL::initStatements()
|
PGresult *Database_PostgreSQL::checkResults(PGresult *result, bool clear)
|
||||||
|
{
|
||||||
|
ExecStatusType statusType = PQresultStatus(result);
|
||||||
|
|
||||||
|
switch (statusType) {
|
||||||
|
case PGRES_COMMAND_OK:
|
||||||
|
case PGRES_TUPLES_OK:
|
||||||
|
break;
|
||||||
|
case PGRES_FATAL_ERROR:
|
||||||
|
default:
|
||||||
|
throw DatabaseException(
|
||||||
|
std::string("PostgreSQL database error: ") +
|
||||||
|
PQresultErrorMessage(result));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (clear)
|
||||||
|
PQclear(result);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Database_PostgreSQL::createTableIfNotExists(const std::string &table_name,
|
||||||
|
const std::string &definition)
|
||||||
|
{
|
||||||
|
std::string sql_check_table = "SELECT relname FROM pg_class WHERE relname='" +
|
||||||
|
table_name + "';";
|
||||||
|
PGresult *result = checkResults(PQexec(m_conn, sql_check_table.c_str()), false);
|
||||||
|
|
||||||
|
// If table doesn't exist, create it
|
||||||
|
if (!PQntuples(result)) {
|
||||||
|
checkResults(PQexec(m_conn, definition.c_str()));
|
||||||
|
}
|
||||||
|
|
||||||
|
PQclear(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Database_PostgreSQL::beginSave()
|
||||||
|
{
|
||||||
|
verifyDatabase();
|
||||||
|
checkResults(PQexec(m_conn, "BEGIN;"));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Database_PostgreSQL::endSave()
|
||||||
|
{
|
||||||
|
checkResults(PQexec(m_conn, "COMMIT;"));
|
||||||
|
}
|
||||||
|
|
||||||
|
MapDatabasePostgreSQL::MapDatabasePostgreSQL(const std::string &connect_string):
|
||||||
|
Database_PostgreSQL(connect_string),
|
||||||
|
MapDatabase()
|
||||||
|
{
|
||||||
|
connectToDatabase();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void MapDatabasePostgreSQL::createDatabase()
|
||||||
|
{
|
||||||
|
createTableIfNotExists("blocks",
|
||||||
|
"CREATE TABLE blocks ("
|
||||||
|
"posX INT NOT NULL,"
|
||||||
|
"posY INT NOT NULL,"
|
||||||
|
"posZ INT NOT NULL,"
|
||||||
|
"data BYTEA,"
|
||||||
|
"PRIMARY KEY (posX,posY,posZ)"
|
||||||
|
");"
|
||||||
|
);
|
||||||
|
|
||||||
|
infostream << "PostgreSQL: Map Database was initialized." << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MapDatabasePostgreSQL::initStatements()
|
||||||
{
|
{
|
||||||
prepareStatement("read_block",
|
prepareStatement("read_block",
|
||||||
"SELECT data FROM blocks "
|
"SELECT data FROM blocks "
|
||||||
"WHERE posX = $1::int4 AND posY = $2::int4 AND "
|
"WHERE posX = $1::int4 AND posY = $2::int4 AND "
|
||||||
"posZ = $3::int4");
|
"posZ = $3::int4");
|
||||||
|
|
||||||
if (m_pgversion < 90500) {
|
if (getPGVersion() < 90500) {
|
||||||
prepareStatement("write_block_insert",
|
prepareStatement("write_block_insert",
|
||||||
"INSERT INTO blocks (posX, posY, posZ, data) SELECT "
|
"INSERT INTO blocks (posX, posY, posZ, data) SELECT "
|
||||||
"$1::int4, $2::int4, $3::int4, $4::bytea "
|
"$1::int4, $2::int4, $3::int4, $4::bytea "
|
||||||
|
@ -152,69 +222,12 @@ void Database_PostgreSQL::initStatements()
|
||||||
"SELECT posX, posY, posZ FROM blocks");
|
"SELECT posX, posY, posZ FROM blocks");
|
||||||
}
|
}
|
||||||
|
|
||||||
PGresult *Database_PostgreSQL::checkResults(PGresult *result, bool clear)
|
bool MapDatabasePostgreSQL::saveBlock(const v3s16 &pos, const std::string &data)
|
||||||
{
|
|
||||||
ExecStatusType statusType = PQresultStatus(result);
|
|
||||||
|
|
||||||
switch (statusType) {
|
|
||||||
case PGRES_COMMAND_OK:
|
|
||||||
case PGRES_TUPLES_OK:
|
|
||||||
break;
|
|
||||||
case PGRES_FATAL_ERROR:
|
|
||||||
default:
|
|
||||||
throw DatabaseException(
|
|
||||||
std::string("PostgreSQL database error: ") +
|
|
||||||
PQresultErrorMessage(result));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (clear)
|
|
||||||
PQclear(result);
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Database_PostgreSQL::createDatabase()
|
|
||||||
{
|
|
||||||
PGresult *result = checkResults(PQexec(m_conn,
|
|
||||||
"SELECT relname FROM pg_class WHERE relname='blocks';"),
|
|
||||||
false);
|
|
||||||
|
|
||||||
// If table doesn't exist, create it
|
|
||||||
if (!PQntuples(result)) {
|
|
||||||
static const char* dbcreate_sql = "CREATE TABLE blocks ("
|
|
||||||
"posX INT NOT NULL,"
|
|
||||||
"posY INT NOT NULL,"
|
|
||||||
"posZ INT NOT NULL,"
|
|
||||||
"data BYTEA,"
|
|
||||||
"PRIMARY KEY (posX,posY,posZ)"
|
|
||||||
");";
|
|
||||||
checkResults(PQexec(m_conn, dbcreate_sql));
|
|
||||||
}
|
|
||||||
|
|
||||||
PQclear(result);
|
|
||||||
|
|
||||||
infostream << "PostgreSQL: Game Database was inited." << std::endl;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void Database_PostgreSQL::beginSave()
|
|
||||||
{
|
|
||||||
verifyDatabase();
|
|
||||||
checkResults(PQexec(m_conn, "BEGIN;"));
|
|
||||||
}
|
|
||||||
|
|
||||||
void Database_PostgreSQL::endSave()
|
|
||||||
{
|
|
||||||
checkResults(PQexec(m_conn, "COMMIT;"));
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Database_PostgreSQL::saveBlock(const v3s16 &pos,
|
|
||||||
const std::string &data)
|
|
||||||
{
|
{
|
||||||
// Verify if we don't overflow the platform integer with the mapblock size
|
// Verify if we don't overflow the platform integer with the mapblock size
|
||||||
if (data.size() > INT_MAX) {
|
if (data.size() > INT_MAX) {
|
||||||
errorstream << "Database_PostgreSQL::saveBlock: Data truncation! "
|
errorstream << "Database_PostgreSQL::saveBlock: Data truncation! "
|
||||||
<< "data.size() over 0xFFFF (== " << data.size()
|
<< "data.size() over 0xFFFFFFFF (== " << data.size()
|
||||||
<< ")" << std::endl;
|
<< ")" << std::endl;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -232,7 +245,7 @@ bool Database_PostgreSQL::saveBlock(const v3s16 &pos,
|
||||||
};
|
};
|
||||||
const int argFmt[] = { 1, 1, 1, 1 };
|
const int argFmt[] = { 1, 1, 1, 1 };
|
||||||
|
|
||||||
if (m_pgversion < 90500) {
|
if (getPGVersion() < 90500) {
|
||||||
execPrepared("write_block_update", ARRLEN(args), args, argLen, argFmt);
|
execPrepared("write_block_update", ARRLEN(args), args, argLen, argFmt);
|
||||||
execPrepared("write_block_insert", ARRLEN(args), args, argLen, argFmt);
|
execPrepared("write_block_insert", ARRLEN(args), args, argLen, argFmt);
|
||||||
} else {
|
} else {
|
||||||
|
@ -241,8 +254,7 @@ bool Database_PostgreSQL::saveBlock(const v3s16 &pos,
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Database_PostgreSQL::loadBlock(const v3s16 &pos,
|
void MapDatabasePostgreSQL::loadBlock(const v3s16 &pos, std::string *block)
|
||||||
std::string *block)
|
|
||||||
{
|
{
|
||||||
verifyDatabase();
|
verifyDatabase();
|
||||||
|
|
||||||
|
@ -260,15 +272,13 @@ void Database_PostgreSQL::loadBlock(const v3s16 &pos,
|
||||||
|
|
||||||
*block = "";
|
*block = "";
|
||||||
|
|
||||||
if (PQntuples(results)) {
|
if (PQntuples(results))
|
||||||
*block = std::string(PQgetvalue(results, 0, 0),
|
*block = std::string(PQgetvalue(results, 0, 0), PQgetlength(results, 0, 0));
|
||||||
PQgetlength(results, 0, 0));
|
|
||||||
}
|
|
||||||
|
|
||||||
PQclear(results);
|
PQclear(results);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Database_PostgreSQL::deleteBlock(const v3s16 &pos)
|
bool MapDatabasePostgreSQL::deleteBlock(const v3s16 &pos)
|
||||||
{
|
{
|
||||||
verifyDatabase();
|
verifyDatabase();
|
||||||
|
|
||||||
|
@ -286,7 +296,7 @@ bool Database_PostgreSQL::deleteBlock(const v3s16 &pos)
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Database_PostgreSQL::listAllLoadableBlocks(std::vector<v3s16> &dst)
|
void MapDatabasePostgreSQL::listAllLoadableBlocks(std::vector<v3s16> &dst)
|
||||||
{
|
{
|
||||||
verifyDatabase();
|
verifyDatabase();
|
||||||
|
|
||||||
|
@ -295,10 +305,330 @@ void Database_PostgreSQL::listAllLoadableBlocks(std::vector<v3s16> &dst)
|
||||||
|
|
||||||
int numrows = PQntuples(results);
|
int numrows = PQntuples(results);
|
||||||
|
|
||||||
for (int row = 0; row < numrows; ++row) {
|
for (int row = 0; row < numrows; ++row)
|
||||||
dst.push_back(pg_to_v3s16(results, 0, 0));
|
dst.push_back(pg_to_v3s16(results, 0, 0));
|
||||||
|
|
||||||
|
PQclear(results);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Player Database
|
||||||
|
*/
|
||||||
|
PlayerDatabasePostgreSQL::PlayerDatabasePostgreSQL(const std::string &connect_string):
|
||||||
|
Database_PostgreSQL(connect_string),
|
||||||
|
PlayerDatabase()
|
||||||
|
{
|
||||||
|
connectToDatabase();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void PlayerDatabasePostgreSQL::createDatabase()
|
||||||
|
{
|
||||||
|
createTableIfNotExists("player",
|
||||||
|
"CREATE TABLE player ("
|
||||||
|
"name VARCHAR(60) NOT NULL,"
|
||||||
|
"pitch NUMERIC(15, 7) NOT NULL,"
|
||||||
|
"yaw NUMERIC(15, 7) NOT NULL,"
|
||||||
|
"posX NUMERIC(15, 7) NOT NULL,"
|
||||||
|
"posY NUMERIC(15, 7) NOT NULL,"
|
||||||
|
"posZ NUMERIC(15, 7) NOT NULL,"
|
||||||
|
"hp INT NOT NULL,"
|
||||||
|
"breath INT NOT NULL,"
|
||||||
|
"creation_date TIMESTAMP WITHOUT TIME ZONE NOT NULL DEFAULT NOW(),"
|
||||||
|
"modification_date TIMESTAMP WITHOUT TIME ZONE NOT NULL DEFAULT NOW(),"
|
||||||
|
"PRIMARY KEY (name)"
|
||||||
|
");"
|
||||||
|
);
|
||||||
|
|
||||||
|
createTableIfNotExists("player_inventories",
|
||||||
|
"CREATE TABLE player_inventories ("
|
||||||
|
"player VARCHAR(60) NOT NULL,"
|
||||||
|
"inv_id INT NOT NULL,"
|
||||||
|
"inv_width INT NOT NULL,"
|
||||||
|
"inv_name TEXT NOT NULL DEFAULT '',"
|
||||||
|
"inv_size INT NOT NULL,"
|
||||||
|
"PRIMARY KEY(player, inv_id),"
|
||||||
|
"CONSTRAINT player_inventories_fkey FOREIGN KEY (player) REFERENCES "
|
||||||
|
"player (name) ON DELETE CASCADE"
|
||||||
|
");"
|
||||||
|
);
|
||||||
|
|
||||||
|
createTableIfNotExists("player_inventory_items",
|
||||||
|
"CREATE TABLE player_inventory_items ("
|
||||||
|
"player VARCHAR(60) NOT NULL,"
|
||||||
|
"inv_id INT NOT NULL,"
|
||||||
|
"slot_id INT NOT NULL,"
|
||||||
|
"item TEXT NOT NULL DEFAULT '',"
|
||||||
|
"PRIMARY KEY(player, inv_id, slot_id),"
|
||||||
|
"CONSTRAINT player_inventory_items_fkey FOREIGN KEY (player) REFERENCES "
|
||||||
|
"player (name) ON DELETE CASCADE"
|
||||||
|
");"
|
||||||
|
);
|
||||||
|
|
||||||
|
createTableIfNotExists("player_metadata",
|
||||||
|
"CREATE TABLE player_metadata ("
|
||||||
|
"player VARCHAR(60) NOT NULL,"
|
||||||
|
"attr VARCHAR(256) NOT NULL,"
|
||||||
|
"value TEXT,"
|
||||||
|
"PRIMARY KEY(player, attr),"
|
||||||
|
"CONSTRAINT player_metadata_fkey FOREIGN KEY (player) REFERENCES "
|
||||||
|
"player (name) ON DELETE CASCADE"
|
||||||
|
");"
|
||||||
|
);
|
||||||
|
|
||||||
|
infostream << "PostgreSQL: Player Database was inited." << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlayerDatabasePostgreSQL::initStatements()
|
||||||
|
{
|
||||||
|
if (getPGVersion() < 90500) {
|
||||||
|
prepareStatement("create_player",
|
||||||
|
"INSERT INTO player(name, pitch, yaw, posX, posY, posZ, hp, breath) VALUES "
|
||||||
|
"($1, $2, $3, $4, $5, $6, $7::int, $8::int)");
|
||||||
|
|
||||||
|
prepareStatement("update_player",
|
||||||
|
"UPDATE SET pitch = $2, yaw = $3, posX = $4, posY = $5, posZ = $6, hp = $7::int, "
|
||||||
|
"breath = $8::int, modification_date = NOW() WHERE name = $1");
|
||||||
|
} else {
|
||||||
|
prepareStatement("save_player",
|
||||||
|
"INSERT INTO player(name, pitch, yaw, posX, posY, posZ, hp, breath) VALUES "
|
||||||
|
"($1, $2, $3, $4, $5, $6, $7::int, $8::int)"
|
||||||
|
"ON CONFLICT ON CONSTRAINT player_pkey DO UPDATE SET pitch = $2, yaw = $3, "
|
||||||
|
"posX = $4, posY = $5, posZ = $6, hp = $7::int, breath = $8::int, "
|
||||||
|
"modification_date = NOW()");
|
||||||
|
}
|
||||||
|
|
||||||
|
prepareStatement("remove_player", "DELETE FROM player WHERE name = $1");
|
||||||
|
|
||||||
|
prepareStatement("load_player_list", "SELECT name FROM player");
|
||||||
|
|
||||||
|
prepareStatement("remove_player_inventories",
|
||||||
|
"DELETE FROM player_inventories WHERE player = $1");
|
||||||
|
|
||||||
|
prepareStatement("remove_player_inventory_items",
|
||||||
|
"DELETE FROM player_inventory_items WHERE player = $1");
|
||||||
|
|
||||||
|
prepareStatement("add_player_inventory",
|
||||||
|
"INSERT INTO player_inventories (player, inv_id, inv_width, inv_name, inv_size) VALUES "
|
||||||
|
"($1, $2::int, $3::int, $4, $5::int)");
|
||||||
|
|
||||||
|
prepareStatement("add_player_inventory_item",
|
||||||
|
"INSERT INTO player_inventory_items (player, inv_id, slot_id, item) VALUES "
|
||||||
|
"($1, $2::int, $3::int, $4)");
|
||||||
|
|
||||||
|
prepareStatement("load_player_inventories",
|
||||||
|
"SELECT inv_id, inv_width, inv_name, inv_size FROM player_inventories "
|
||||||
|
"WHERE player = $1 ORDER BY inv_id");
|
||||||
|
|
||||||
|
prepareStatement("load_player_inventory_items",
|
||||||
|
"SELECT slot_id, item FROM player_inventory_items WHERE "
|
||||||
|
"player = $1 AND inv_id = $2::int");
|
||||||
|
|
||||||
|
prepareStatement("load_player",
|
||||||
|
"SELECT pitch, yaw, posX, posY, posZ, hp, breath FROM player WHERE name = $1");
|
||||||
|
|
||||||
|
prepareStatement("remove_player_metadata",
|
||||||
|
"DELETE FROM player_metadata WHERE player = $1");
|
||||||
|
|
||||||
|
prepareStatement("save_player_metadata",
|
||||||
|
"INSERT INTO player_metadata (player, attr, value) VALUES ($1, $2, $3)");
|
||||||
|
|
||||||
|
prepareStatement("load_player_metadata",
|
||||||
|
"SELECT attr, value FROM player_metadata WHERE player = $1");
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PlayerDatabasePostgreSQL::playerDataExists(const std::string &playername)
|
||||||
|
{
|
||||||
|
verifyDatabase();
|
||||||
|
|
||||||
|
const char *values[] = { playername.c_str() };
|
||||||
|
PGresult *results = execPrepared("load_player", 1, values, false);
|
||||||
|
|
||||||
|
bool res = (PQntuples(results) > 0);
|
||||||
|
PQclear(results);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlayerDatabasePostgreSQL::savePlayer(RemotePlayer *player)
|
||||||
|
{
|
||||||
|
PlayerSAO* sao = player->getPlayerSAO();
|
||||||
|
if (!sao)
|
||||||
|
return;
|
||||||
|
|
||||||
|
verifyDatabase();
|
||||||
|
|
||||||
|
v3f pos = sao->getBasePosition();
|
||||||
|
std::string pitch = ftos(sao->getPitch());
|
||||||
|
std::string yaw = ftos(sao->getYaw());
|
||||||
|
std::string posx = ftos(pos.X);
|
||||||
|
std::string posy = ftos(pos.Y);
|
||||||
|
std::string posz = ftos(pos.Z);
|
||||||
|
std::string hp = itos(sao->getHP());
|
||||||
|
std::string breath = itos(sao->getBreath());
|
||||||
|
const char *values[] = {
|
||||||
|
player->getName(),
|
||||||
|
pitch.c_str(),
|
||||||
|
yaw.c_str(),
|
||||||
|
posx.c_str(), posy.c_str(), posz.c_str(),
|
||||||
|
hp.c_str(),
|
||||||
|
breath.c_str()
|
||||||
|
};
|
||||||
|
|
||||||
|
const char* rmvalues[] = { player->getName() };
|
||||||
|
beginSave();
|
||||||
|
|
||||||
|
if (getPGVersion() < 90500) {
|
||||||
|
if (!playerDataExists(player->getName()))
|
||||||
|
execPrepared("create_player", 8, values, true, false);
|
||||||
|
else
|
||||||
|
execPrepared("update_player", 8, values, true, false);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
execPrepared("save_player", 8, values, true, false);
|
||||||
|
|
||||||
|
// Write player inventories
|
||||||
|
execPrepared("remove_player_inventories", 1, rmvalues);
|
||||||
|
execPrepared("remove_player_inventory_items", 1, rmvalues);
|
||||||
|
|
||||||
|
std::vector<const InventoryList*> inventory_lists = sao->getInventory()->getLists();
|
||||||
|
for (u16 i = 0; i < inventory_lists.size(); i++) {
|
||||||
|
const InventoryList* list = inventory_lists[i];
|
||||||
|
std::string name = list->getName(), width = itos(list->getWidth()),
|
||||||
|
inv_id = itos(i), lsize = itos(list->getSize());
|
||||||
|
|
||||||
|
const char* inv_values[] = {
|
||||||
|
player->getName(),
|
||||||
|
inv_id.c_str(),
|
||||||
|
width.c_str(),
|
||||||
|
name.c_str(),
|
||||||
|
lsize.c_str()
|
||||||
|
};
|
||||||
|
execPrepared("add_player_inventory", 5, inv_values);
|
||||||
|
|
||||||
|
for (u32 j = 0; j < list->getSize(); j++) {
|
||||||
|
std::ostringstream os;
|
||||||
|
list->getItem(j).serialize(os);
|
||||||
|
std::string itemStr = os.str(), slotId = itos(j);
|
||||||
|
|
||||||
|
const char* invitem_values[] = {
|
||||||
|
player->getName(),
|
||||||
|
inv_id.c_str(),
|
||||||
|
slotId.c_str(),
|
||||||
|
itemStr.c_str()
|
||||||
|
};
|
||||||
|
execPrepared("add_player_inventory_item", 4, invitem_values);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
execPrepared("remove_player_metadata", 1, rmvalues);
|
||||||
|
const PlayerAttributes &attrs = sao->getExtendedAttributes();
|
||||||
|
for (PlayerAttributes::const_iterator it = attrs.begin(); it != attrs.end(); ++it) {
|
||||||
|
const char *meta_values[] = {
|
||||||
|
player->getName(),
|
||||||
|
it->first.c_str(),
|
||||||
|
it->second.c_str()
|
||||||
|
};
|
||||||
|
execPrepared("save_player_metadata", 3, meta_values);
|
||||||
|
}
|
||||||
|
endSave();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PlayerDatabasePostgreSQL::loadPlayer(RemotePlayer *player, PlayerSAO *sao)
|
||||||
|
{
|
||||||
|
sanity_check(sao);
|
||||||
|
verifyDatabase();
|
||||||
|
|
||||||
|
const char *values[] = { player->getName() };
|
||||||
|
PGresult *results = execPrepared("load_player", 1, values, false, false);
|
||||||
|
|
||||||
|
// Player not found, return not found
|
||||||
|
if (!PQntuples(results)) {
|
||||||
|
PQclear(results);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
sao->setPitch(pg_to_float(results, 0, 0));
|
||||||
|
sao->setYaw(pg_to_float(results, 0, 1));
|
||||||
|
sao->setBasePosition(v3f(
|
||||||
|
pg_to_float(results, 0, 2),
|
||||||
|
pg_to_float(results, 0, 3),
|
||||||
|
pg_to_float(results, 0, 4))
|
||||||
|
);
|
||||||
|
sao->setHPRaw((s16) pg_to_int(results, 0, 5));
|
||||||
|
sao->setBreath((u16) pg_to_int(results, 0, 6), false);
|
||||||
|
|
||||||
|
PQclear(results);
|
||||||
|
|
||||||
|
// Load inventory
|
||||||
|
results = execPrepared("load_player_inventories", 1, values, false, false);
|
||||||
|
|
||||||
|
int resultCount = PQntuples(results);
|
||||||
|
|
||||||
|
for (int row = 0; row < resultCount; ++row) {
|
||||||
|
InventoryList* invList = player->inventory.
|
||||||
|
addList(PQgetvalue(results, row, 2), pg_to_uint(results, row, 3));
|
||||||
|
invList->setWidth(pg_to_uint(results, row, 1));
|
||||||
|
|
||||||
|
u32 invId = pg_to_uint(results, row, 0);
|
||||||
|
std::string invIdStr = itos(invId);
|
||||||
|
|
||||||
|
const char* values2[] = {
|
||||||
|
player->getName(),
|
||||||
|
invIdStr.c_str()
|
||||||
|
};
|
||||||
|
PGresult *results2 = execPrepared("load_player_inventory_items", 2,
|
||||||
|
values2, false, false);
|
||||||
|
|
||||||
|
int resultCount2 = PQntuples(results2);
|
||||||
|
for (int row2 = 0; row2 < resultCount2; row2++) {
|
||||||
|
const std::string itemStr = PQgetvalue(results2, row2, 1);
|
||||||
|
if (itemStr.length() > 0) {
|
||||||
|
ItemStack stack;
|
||||||
|
stack.deSerialize(itemStr);
|
||||||
|
invList->addItem(pg_to_uint(results2, row2, 0), stack);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
PQclear(results2);
|
||||||
|
}
|
||||||
|
|
||||||
|
PQclear(results);
|
||||||
|
|
||||||
|
results = execPrepared("load_player_metadata", 1, values, false);
|
||||||
|
|
||||||
|
int numrows = PQntuples(results);
|
||||||
|
for (int row = 0; row < numrows; row++) {
|
||||||
|
sao->setExtendedAttribute(PQgetvalue(results, row, 0),PQgetvalue(results, row, 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
PQclear(results);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PlayerDatabasePostgreSQL::removePlayer(const std::string &name)
|
||||||
|
{
|
||||||
|
if (!playerDataExists(name))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
verifyDatabase();
|
||||||
|
|
||||||
|
const char *values[] = { name.c_str() };
|
||||||
|
execPrepared("remove_player", 1, values);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlayerDatabasePostgreSQL::listPlayers(std::vector<std::string> &res)
|
||||||
|
{
|
||||||
|
verifyDatabase();
|
||||||
|
|
||||||
|
PGresult *results = execPrepared("load_player_list", 0, NULL, false);
|
||||||
|
|
||||||
|
int numrows = PQntuples(results);
|
||||||
|
for (int row = 0; row < numrows; row++)
|
||||||
|
res.push_back(PQgetvalue(results, row, 0));
|
||||||
|
|
||||||
PQclear(results);
|
PQclear(results);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -30,52 +30,32 @@ class Settings;
|
||||||
class Database_PostgreSQL: public Database
|
class Database_PostgreSQL: public Database
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
Database_PostgreSQL(const Settings &conf);
|
Database_PostgreSQL(const std::string &connect_string);
|
||||||
~Database_PostgreSQL();
|
~Database_PostgreSQL();
|
||||||
|
|
||||||
void beginSave();
|
void beginSave();
|
||||||
void endSave();
|
void endSave();
|
||||||
|
|
||||||
bool saveBlock(const v3s16 &pos, const std::string &data);
|
|
||||||
void loadBlock(const v3s16 &pos, std::string *block);
|
|
||||||
bool deleteBlock(const v3s16 &pos);
|
|
||||||
void listAllLoadableBlocks(std::vector<v3s16> &dst);
|
|
||||||
bool initialized() const;
|
bool initialized() const;
|
||||||
|
|
||||||
private:
|
|
||||||
// Database initialization
|
|
||||||
void connectToDatabase();
|
|
||||||
void initStatements();
|
|
||||||
void createDatabase();
|
|
||||||
|
|
||||||
inline void prepareStatement(const std::string &name, const std::string &sql)
|
|
||||||
{
|
|
||||||
checkResults(PQprepare(m_conn, name.c_str(), sql.c_str(), 0, NULL));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Database connectivity checks
|
|
||||||
void ping();
|
|
||||||
void verifyDatabase();
|
|
||||||
|
|
||||||
// Database usage
|
|
||||||
PGresult *checkResults(PGresult *res, bool clear = true);
|
|
||||||
|
|
||||||
inline PGresult *execPrepared(const char *stmtName, const int paramsNumber,
|
|
||||||
const void **params,
|
|
||||||
const int *paramsLengths = NULL, const int *paramsFormats = NULL,
|
|
||||||
bool clear = true, bool nobinary = true)
|
|
||||||
{
|
|
||||||
return checkResults(PQexecPrepared(m_conn, stmtName, paramsNumber,
|
|
||||||
(const char* const*) params, paramsLengths, paramsFormats,
|
|
||||||
nobinary ? 1 : 0), clear);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
protected:
|
||||||
// Conversion helpers
|
// Conversion helpers
|
||||||
inline int pg_to_int(PGresult *res, int row, int col)
|
inline int pg_to_int(PGresult *res, int row, int col)
|
||||||
{
|
{
|
||||||
return atoi(PQgetvalue(res, row, col));
|
return atoi(PQgetvalue(res, row, col));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline u32 pg_to_uint(PGresult *res, int row, int col)
|
||||||
|
{
|
||||||
|
return (u32) atoi(PQgetvalue(res, row, col));
|
||||||
|
}
|
||||||
|
|
||||||
|
inline float pg_to_float(PGresult *res, int row, int col)
|
||||||
|
{
|
||||||
|
return (float) atof(PQgetvalue(res, row, col));
|
||||||
|
}
|
||||||
|
|
||||||
inline v3s16 pg_to_v3s16(PGresult *res, int row, int col)
|
inline v3s16 pg_to_v3s16(PGresult *res, int row, int col)
|
||||||
{
|
{
|
||||||
return v3s16(
|
return v3s16(
|
||||||
|
@ -85,11 +65,86 @@ private:
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline PGresult *execPrepared(const char *stmtName, const int paramsNumber,
|
||||||
|
const void **params,
|
||||||
|
const int *paramsLengths = NULL, const int *paramsFormats = NULL,
|
||||||
|
bool clear = true, bool nobinary = true)
|
||||||
|
{
|
||||||
|
return checkResults(PQexecPrepared(m_conn, stmtName, paramsNumber,
|
||||||
|
(const char* const*) params, paramsLengths, paramsFormats,
|
||||||
|
nobinary ? 1 : 0), clear);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline PGresult *execPrepared(const char *stmtName, const int paramsNumber,
|
||||||
|
const char **params, bool clear = true, bool nobinary = true)
|
||||||
|
{
|
||||||
|
return execPrepared(stmtName, paramsNumber,
|
||||||
|
(const void **)params, NULL, NULL, clear, nobinary);
|
||||||
|
}
|
||||||
|
|
||||||
|
void createTableIfNotExists(const std::string &table_name, const std::string &definition);
|
||||||
|
void verifyDatabase();
|
||||||
|
|
||||||
|
// Database initialization
|
||||||
|
void connectToDatabase();
|
||||||
|
virtual void createDatabase() = 0;
|
||||||
|
virtual void initStatements() = 0;
|
||||||
|
inline void prepareStatement(const std::string &name, const std::string &sql)
|
||||||
|
{
|
||||||
|
checkResults(PQprepare(m_conn, name.c_str(), sql.c_str(), 0, NULL));
|
||||||
|
}
|
||||||
|
|
||||||
|
const int getPGVersion() const { return m_pgversion; }
|
||||||
|
private:
|
||||||
|
// Database connectivity checks
|
||||||
|
void ping();
|
||||||
|
|
||||||
|
// Database usage
|
||||||
|
PGresult *checkResults(PGresult *res, bool clear = true);
|
||||||
|
|
||||||
// Attributes
|
// Attributes
|
||||||
std::string m_connect_string;
|
std::string m_connect_string;
|
||||||
PGconn *m_conn;
|
PGconn *m_conn;
|
||||||
int m_pgversion;
|
int m_pgversion;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class MapDatabasePostgreSQL : private Database_PostgreSQL, public MapDatabase
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
MapDatabasePostgreSQL(const std::string &connect_string);
|
||||||
|
virtual ~MapDatabasePostgreSQL() {}
|
||||||
|
|
||||||
|
bool saveBlock(const v3s16 &pos, const std::string &data);
|
||||||
|
void loadBlock(const v3s16 &pos, std::string *block);
|
||||||
|
bool deleteBlock(const v3s16 &pos);
|
||||||
|
void listAllLoadableBlocks(std::vector<v3s16> &dst);
|
||||||
|
|
||||||
|
void beginSave() { Database_PostgreSQL::beginSave(); }
|
||||||
|
void endSave() { Database_PostgreSQL::endSave(); }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
virtual void createDatabase();
|
||||||
|
virtual void initStatements();
|
||||||
|
};
|
||||||
|
|
||||||
|
class PlayerDatabasePostgreSQL : private Database_PostgreSQL, public PlayerDatabase
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
PlayerDatabasePostgreSQL(const std::string &connect_string);
|
||||||
|
virtual ~PlayerDatabasePostgreSQL() {}
|
||||||
|
|
||||||
|
void savePlayer(RemotePlayer *player);
|
||||||
|
bool loadPlayer(RemotePlayer *player, PlayerSAO *sao);
|
||||||
|
bool removePlayer(const std::string &name);
|
||||||
|
void listPlayers(std::vector<std::string> &res);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
virtual void createDatabase();
|
||||||
|
virtual void initStatements();
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool playerDataExists(const std::string &playername);
|
||||||
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
|
@ -30,7 +30,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
|
|
||||||
class Settings;
|
class Settings;
|
||||||
|
|
||||||
class Database_Redis : public Database
|
class Database_Redis : public MapDatabase
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
Database_Redis(Settings &conf);
|
Database_Redis(Settings &conf);
|
||||||
|
|
|
@ -33,6 +33,8 @@ SQLite format specification:
|
||||||
#include "settings.h"
|
#include "settings.h"
|
||||||
#include "porting.h"
|
#include "porting.h"
|
||||||
#include "util/string.h"
|
#include "util/string.h"
|
||||||
|
#include "content_sao.h"
|
||||||
|
#include "remoteplayer.h"
|
||||||
|
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
|
|
||||||
|
@ -111,27 +113,26 @@ int Database_SQLite3::busyHandler(void *data, int count)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Database_SQLite3::Database_SQLite3(const std::string &savedir) :
|
Database_SQLite3::Database_SQLite3(const std::string &savedir, const std::string &dbname) :
|
||||||
|
m_database(NULL),
|
||||||
m_initialized(false),
|
m_initialized(false),
|
||||||
m_savedir(savedir),
|
m_savedir(savedir),
|
||||||
m_database(NULL),
|
m_dbname(dbname),
|
||||||
m_stmt_read(NULL),
|
|
||||||
m_stmt_write(NULL),
|
|
||||||
m_stmt_list(NULL),
|
|
||||||
m_stmt_delete(NULL),
|
|
||||||
m_stmt_begin(NULL),
|
m_stmt_begin(NULL),
|
||||||
m_stmt_end(NULL)
|
m_stmt_end(NULL)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
void Database_SQLite3::beginSave() {
|
void Database_SQLite3::beginSave()
|
||||||
|
{
|
||||||
verifyDatabase();
|
verifyDatabase();
|
||||||
SQLRES(sqlite3_step(m_stmt_begin), SQLITE_DONE,
|
SQLRES(sqlite3_step(m_stmt_begin), SQLITE_DONE,
|
||||||
"Failed to start SQLite3 transaction");
|
"Failed to start SQLite3 transaction");
|
||||||
sqlite3_reset(m_stmt_begin);
|
sqlite3_reset(m_stmt_begin);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Database_SQLite3::endSave() {
|
void Database_SQLite3::endSave()
|
||||||
|
{
|
||||||
verifyDatabase();
|
verifyDatabase();
|
||||||
SQLRES(sqlite3_step(m_stmt_end), SQLITE_DONE,
|
SQLRES(sqlite3_step(m_stmt_end), SQLITE_DONE,
|
||||||
"Failed to commit SQLite3 transaction");
|
"Failed to commit SQLite3 transaction");
|
||||||
|
@ -142,7 +143,7 @@ void Database_SQLite3::openDatabase()
|
||||||
{
|
{
|
||||||
if (m_database) return;
|
if (m_database) return;
|
||||||
|
|
||||||
std::string dbp = m_savedir + DIR_DELIM + "map.sqlite";
|
std::string dbp = m_savedir + DIR_DELIM + m_dbname + ".sqlite";
|
||||||
|
|
||||||
// Open the database connection
|
// Open the database connection
|
||||||
|
|
||||||
|
@ -170,6 +171,8 @@ void Database_SQLite3::openDatabase()
|
||||||
+ itos(g_settings->getU16("sqlite_synchronous"));
|
+ itos(g_settings->getU16("sqlite_synchronous"));
|
||||||
SQLOK(sqlite3_exec(m_database, query_str.c_str(), NULL, NULL, NULL),
|
SQLOK(sqlite3_exec(m_database, query_str.c_str(), NULL, NULL, NULL),
|
||||||
"Failed to modify sqlite3 synchronous mode");
|
"Failed to modify sqlite3 synchronous mode");
|
||||||
|
SQLOK(sqlite3_exec(m_database, "PRAGMA foreign_keys = ON", NULL, NULL, NULL),
|
||||||
|
"Failed to enable sqlite3 foreign key support");
|
||||||
}
|
}
|
||||||
|
|
||||||
void Database_SQLite3::verifyDatabase()
|
void Database_SQLite3::verifyDatabase()
|
||||||
|
@ -178,8 +181,61 @@ void Database_SQLite3::verifyDatabase()
|
||||||
|
|
||||||
openDatabase();
|
openDatabase();
|
||||||
|
|
||||||
PREPARE_STATEMENT(begin, "BEGIN");
|
PREPARE_STATEMENT(begin, "BEGIN;");
|
||||||
PREPARE_STATEMENT(end, "COMMIT");
|
PREPARE_STATEMENT(end, "COMMIT;");
|
||||||
|
|
||||||
|
initStatements();
|
||||||
|
|
||||||
|
m_initialized = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Database_SQLite3::~Database_SQLite3()
|
||||||
|
{
|
||||||
|
FINALIZE_STATEMENT(m_stmt_begin)
|
||||||
|
FINALIZE_STATEMENT(m_stmt_end)
|
||||||
|
|
||||||
|
SQLOK_ERRSTREAM(sqlite3_close(m_database), "Failed to close database");
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Map database
|
||||||
|
*/
|
||||||
|
|
||||||
|
MapDatabaseSQLite3::MapDatabaseSQLite3(const std::string &savedir):
|
||||||
|
Database_SQLite3(savedir, "map"),
|
||||||
|
MapDatabase(),
|
||||||
|
m_stmt_read(NULL),
|
||||||
|
m_stmt_write(NULL),
|
||||||
|
m_stmt_list(NULL),
|
||||||
|
m_stmt_delete(NULL)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
MapDatabaseSQLite3::~MapDatabaseSQLite3()
|
||||||
|
{
|
||||||
|
FINALIZE_STATEMENT(m_stmt_read)
|
||||||
|
FINALIZE_STATEMENT(m_stmt_write)
|
||||||
|
FINALIZE_STATEMENT(m_stmt_list)
|
||||||
|
FINALIZE_STATEMENT(m_stmt_delete)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void MapDatabaseSQLite3::createDatabase()
|
||||||
|
{
|
||||||
|
assert(m_database); // Pre-condition
|
||||||
|
|
||||||
|
SQLOK(sqlite3_exec(m_database,
|
||||||
|
"CREATE TABLE IF NOT EXISTS `blocks` (\n"
|
||||||
|
" `pos` INT PRIMARY KEY,\n"
|
||||||
|
" `data` BLOB\n"
|
||||||
|
");\n",
|
||||||
|
NULL, NULL, NULL),
|
||||||
|
"Failed to create database table");
|
||||||
|
}
|
||||||
|
|
||||||
|
void MapDatabaseSQLite3::initStatements()
|
||||||
|
{
|
||||||
PREPARE_STATEMENT(read, "SELECT `data` FROM `blocks` WHERE `pos` = ? LIMIT 1");
|
PREPARE_STATEMENT(read, "SELECT `data` FROM `blocks` WHERE `pos` = ? LIMIT 1");
|
||||||
#ifdef __ANDROID__
|
#ifdef __ANDROID__
|
||||||
PREPARE_STATEMENT(write, "INSERT INTO `blocks` (`pos`, `data`) VALUES (?, ?)");
|
PREPARE_STATEMENT(write, "INSERT INTO `blocks` (`pos`, `data`) VALUES (?, ?)");
|
||||||
|
@ -189,18 +245,16 @@ void Database_SQLite3::verifyDatabase()
|
||||||
PREPARE_STATEMENT(delete, "DELETE FROM `blocks` WHERE `pos` = ?");
|
PREPARE_STATEMENT(delete, "DELETE FROM `blocks` WHERE `pos` = ?");
|
||||||
PREPARE_STATEMENT(list, "SELECT `pos` FROM `blocks`");
|
PREPARE_STATEMENT(list, "SELECT `pos` FROM `blocks`");
|
||||||
|
|
||||||
m_initialized = true;
|
|
||||||
|
|
||||||
verbosestream << "ServerMap: SQLite3 database opened." << std::endl;
|
verbosestream << "ServerMap: SQLite3 database opened." << std::endl;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void Database_SQLite3::bindPos(sqlite3_stmt *stmt, const v3s16 &pos, int index)
|
inline void MapDatabaseSQLite3::bindPos(sqlite3_stmt *stmt, const v3s16 &pos, int index)
|
||||||
{
|
{
|
||||||
SQLOK(sqlite3_bind_int64(stmt, index, getBlockAsInteger(pos)),
|
SQLOK(sqlite3_bind_int64(stmt, index, getBlockAsInteger(pos)),
|
||||||
"Internal error: failed to bind query at " __FILE__ ":" TOSTRING(__LINE__));
|
"Internal error: failed to bind query at " __FILE__ ":" TOSTRING(__LINE__));
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Database_SQLite3::deleteBlock(const v3s16 &pos)
|
bool MapDatabaseSQLite3::deleteBlock(const v3s16 &pos)
|
||||||
{
|
{
|
||||||
verifyDatabase();
|
verifyDatabase();
|
||||||
|
|
||||||
|
@ -216,7 +270,7 @@ bool Database_SQLite3::deleteBlock(const v3s16 &pos)
|
||||||
return good;
|
return good;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Database_SQLite3::saveBlock(const v3s16 &pos, const std::string &data)
|
bool MapDatabaseSQLite3::saveBlock(const v3s16 &pos, const std::string &data)
|
||||||
{
|
{
|
||||||
verifyDatabase();
|
verifyDatabase();
|
||||||
|
|
||||||
|
@ -243,7 +297,7 @@ bool Database_SQLite3::saveBlock(const v3s16 &pos, const std::string &data)
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Database_SQLite3::loadBlock(const v3s16 &pos, std::string *block)
|
void MapDatabaseSQLite3::loadBlock(const v3s16 &pos, std::string *block)
|
||||||
{
|
{
|
||||||
verifyDatabase();
|
verifyDatabase();
|
||||||
|
|
||||||
|
@ -264,37 +318,312 @@ void Database_SQLite3::loadBlock(const v3s16 &pos, std::string *block)
|
||||||
sqlite3_reset(m_stmt_read);
|
sqlite3_reset(m_stmt_read);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Database_SQLite3::createDatabase()
|
void MapDatabaseSQLite3::listAllLoadableBlocks(std::vector<v3s16> &dst)
|
||||||
{
|
|
||||||
assert(m_database); // Pre-condition
|
|
||||||
SQLOK(sqlite3_exec(m_database,
|
|
||||||
"CREATE TABLE IF NOT EXISTS `blocks` (\n"
|
|
||||||
" `pos` INT PRIMARY KEY,\n"
|
|
||||||
" `data` BLOB\n"
|
|
||||||
");\n",
|
|
||||||
NULL, NULL, NULL),
|
|
||||||
"Failed to create database table");
|
|
||||||
}
|
|
||||||
|
|
||||||
void Database_SQLite3::listAllLoadableBlocks(std::vector<v3s16> &dst)
|
|
||||||
{
|
{
|
||||||
verifyDatabase();
|
verifyDatabase();
|
||||||
|
|
||||||
while (sqlite3_step(m_stmt_list) == SQLITE_ROW) {
|
while (sqlite3_step(m_stmt_list) == SQLITE_ROW)
|
||||||
dst.push_back(getIntegerAsBlock(sqlite3_column_int64(m_stmt_list, 0)));
|
dst.push_back(getIntegerAsBlock(sqlite3_column_int64(m_stmt_list, 0)));
|
||||||
}
|
|
||||||
sqlite3_reset(m_stmt_list);
|
sqlite3_reset(m_stmt_list);
|
||||||
}
|
}
|
||||||
|
|
||||||
Database_SQLite3::~Database_SQLite3()
|
/*
|
||||||
{
|
* Player Database
|
||||||
FINALIZE_STATEMENT(m_stmt_read)
|
*/
|
||||||
FINALIZE_STATEMENT(m_stmt_write)
|
|
||||||
FINALIZE_STATEMENT(m_stmt_list)
|
|
||||||
FINALIZE_STATEMENT(m_stmt_begin)
|
|
||||||
FINALIZE_STATEMENT(m_stmt_end)
|
|
||||||
FINALIZE_STATEMENT(m_stmt_delete)
|
|
||||||
|
|
||||||
SQLOK_ERRSTREAM(sqlite3_close(m_database), "Failed to close database");
|
PlayerDatabaseSQLite3::PlayerDatabaseSQLite3(const std::string &savedir):
|
||||||
|
Database_SQLite3(savedir, "players"),
|
||||||
|
PlayerDatabase(),
|
||||||
|
m_stmt_player_load(NULL),
|
||||||
|
m_stmt_player_add(NULL),
|
||||||
|
m_stmt_player_update(NULL),
|
||||||
|
m_stmt_player_remove(NULL),
|
||||||
|
m_stmt_player_list(NULL),
|
||||||
|
m_stmt_player_load_inventory(NULL),
|
||||||
|
m_stmt_player_load_inventory_items(NULL),
|
||||||
|
m_stmt_player_add_inventory(NULL),
|
||||||
|
m_stmt_player_add_inventory_items(NULL),
|
||||||
|
m_stmt_player_remove_inventory(NULL),
|
||||||
|
m_stmt_player_remove_inventory_items(NULL),
|
||||||
|
m_stmt_player_metadata_load(NULL),
|
||||||
|
m_stmt_player_metadata_remove(NULL),
|
||||||
|
m_stmt_player_metadata_add(NULL)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
PlayerDatabaseSQLite3::~PlayerDatabaseSQLite3()
|
||||||
|
{
|
||||||
|
FINALIZE_STATEMENT(m_stmt_player_load)
|
||||||
|
FINALIZE_STATEMENT(m_stmt_player_add)
|
||||||
|
FINALIZE_STATEMENT(m_stmt_player_update)
|
||||||
|
FINALIZE_STATEMENT(m_stmt_player_remove)
|
||||||
|
FINALIZE_STATEMENT(m_stmt_player_list)
|
||||||
|
FINALIZE_STATEMENT(m_stmt_player_add_inventory)
|
||||||
|
FINALIZE_STATEMENT(m_stmt_player_add_inventory_items)
|
||||||
|
FINALIZE_STATEMENT(m_stmt_player_remove_inventory)
|
||||||
|
FINALIZE_STATEMENT(m_stmt_player_remove_inventory_items)
|
||||||
|
FINALIZE_STATEMENT(m_stmt_player_load_inventory)
|
||||||
|
FINALIZE_STATEMENT(m_stmt_player_load_inventory_items)
|
||||||
|
FINALIZE_STATEMENT(m_stmt_player_metadata_load)
|
||||||
|
FINALIZE_STATEMENT(m_stmt_player_metadata_add)
|
||||||
|
FINALIZE_STATEMENT(m_stmt_player_metadata_remove)
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
void PlayerDatabaseSQLite3::createDatabase()
|
||||||
|
{
|
||||||
|
assert(m_database); // Pre-condition
|
||||||
|
|
||||||
|
SQLOK(sqlite3_exec(m_database,
|
||||||
|
"CREATE TABLE IF NOT EXISTS `player` ("
|
||||||
|
"`name` VARCHAR(50) NOT NULL,"
|
||||||
|
"`pitch` NUMERIC(11, 4) NOT NULL,"
|
||||||
|
"`yaw` NUMERIC(11, 4) NOT NULL,"
|
||||||
|
"`posX` NUMERIC(11, 4) NOT NULL,"
|
||||||
|
"`posY` NUMERIC(11, 4) NOT NULL,"
|
||||||
|
"`posZ` NUMERIC(11, 4) NOT NULL,"
|
||||||
|
"`hp` INT NOT NULL,"
|
||||||
|
"`breath` INT NOT NULL,"
|
||||||
|
"`creation_date` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,"
|
||||||
|
"`modification_date` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,"
|
||||||
|
"PRIMARY KEY (`name`));",
|
||||||
|
NULL, NULL, NULL),
|
||||||
|
"Failed to create player table");
|
||||||
|
|
||||||
|
SQLOK(sqlite3_exec(m_database,
|
||||||
|
"CREATE TABLE IF NOT EXISTS `player_metadata` ("
|
||||||
|
" `player` VARCHAR(50) NOT NULL,"
|
||||||
|
" `metadata` VARCHAR(256) NOT NULL,"
|
||||||
|
" `value` TEXT,"
|
||||||
|
" PRIMARY KEY(`player`, `metadata`),"
|
||||||
|
" FOREIGN KEY (`player`) REFERENCES player (`name`) ON DELETE CASCADE );",
|
||||||
|
NULL, NULL, NULL),
|
||||||
|
"Failed to create player metadata table");
|
||||||
|
|
||||||
|
SQLOK(sqlite3_exec(m_database,
|
||||||
|
"CREATE TABLE IF NOT EXISTS `player_inventories` ("
|
||||||
|
" `player` VARCHAR(50) NOT NULL,"
|
||||||
|
" `inv_id` INT NOT NULL,"
|
||||||
|
" `inv_width` INT NOT NULL,"
|
||||||
|
" `inv_name` TEXT NOT NULL DEFAULT '',"
|
||||||
|
" `inv_size` INT NOT NULL,"
|
||||||
|
" PRIMARY KEY(player, inv_id),"
|
||||||
|
" FOREIGN KEY (`player`) REFERENCES player (`name`) ON DELETE CASCADE );",
|
||||||
|
NULL, NULL, NULL),
|
||||||
|
"Failed to create player inventory table");
|
||||||
|
|
||||||
|
SQLOK(sqlite3_exec(m_database,
|
||||||
|
"CREATE TABLE `player_inventory_items` ("
|
||||||
|
" `player` VARCHAR(50) NOT NULL,"
|
||||||
|
" `inv_id` INT NOT NULL,"
|
||||||
|
" `slot_id` INT NOT NULL,"
|
||||||
|
" `item` TEXT NOT NULL DEFAULT '',"
|
||||||
|
" PRIMARY KEY(player, inv_id, slot_id),"
|
||||||
|
" FOREIGN KEY (`player`) REFERENCES player (`name`) ON DELETE CASCADE );",
|
||||||
|
NULL, NULL, NULL),
|
||||||
|
"Failed to create player inventory items table");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void PlayerDatabaseSQLite3::initStatements()
|
||||||
|
{
|
||||||
|
PREPARE_STATEMENT(player_load, "SELECT `pitch`, `yaw`, `posX`, `posY`, `posZ`, `hp`, "
|
||||||
|
"`breath`"
|
||||||
|
"FROM `player` WHERE `name` = ?")
|
||||||
|
PREPARE_STATEMENT(player_add, "INSERT INTO `player` (`name`, `pitch`, `yaw`, `posX`, "
|
||||||
|
"`posY`, `posZ`, `hp`, `breath`) VALUES (?, ?, ?, ?, ?, ?, ?, ?)")
|
||||||
|
PREPARE_STATEMENT(player_update, "UPDATE `player` SET `pitch` = ?, `yaw` = ?, "
|
||||||
|
"`posX` = ?, `posY` = ?, `posZ` = ?, `hp` = ?, `breath` = ?, "
|
||||||
|
"`modification_date` = CURRENT_TIMESTAMP WHERE `name` = ?")
|
||||||
|
PREPARE_STATEMENT(player_remove, "DELETE FROM `player` WHERE `name` = ?")
|
||||||
|
PREPARE_STATEMENT(player_list, "SELECT `name` FROM `player`")
|
||||||
|
|
||||||
|
PREPARE_STATEMENT(player_add_inventory, "INSERT INTO `player_inventories` "
|
||||||
|
"(`player`, `inv_id`, `inv_width`, `inv_name`, `inv_size`) VALUES (?, ?, ?, ?, ?)")
|
||||||
|
PREPARE_STATEMENT(player_add_inventory_items, "INSERT INTO `player_inventory_items` "
|
||||||
|
"(`player`, `inv_id`, `slot_id`, `item`) VALUES (?, ?, ?, ?)")
|
||||||
|
PREPARE_STATEMENT(player_remove_inventory, "DELETE FROM `player_inventories` "
|
||||||
|
"WHERE `player` = ?")
|
||||||
|
PREPARE_STATEMENT(player_remove_inventory_items, "DELETE FROM `player_inventory_items` "
|
||||||
|
"WHERE `player` = ?")
|
||||||
|
PREPARE_STATEMENT(player_load_inventory, "SELECT `inv_id`, `inv_width`, `inv_name`, "
|
||||||
|
"`inv_size` FROM `player_inventories` WHERE `player` = ? ORDER BY inv_id")
|
||||||
|
PREPARE_STATEMENT(player_load_inventory_items, "SELECT `slot_id`, `item` "
|
||||||
|
"FROM `player_inventory_items` WHERE `player` = ? AND `inv_id` = ?")
|
||||||
|
|
||||||
|
PREPARE_STATEMENT(player_metadata_load, "SELECT `metadata`, `value` FROM "
|
||||||
|
"`player_metadata` WHERE `player` = ?")
|
||||||
|
PREPARE_STATEMENT(player_metadata_add, "INSERT INTO `player_metadata` "
|
||||||
|
"(`player`, `metadata`, `value`) VALUES (?, ?, ?)")
|
||||||
|
PREPARE_STATEMENT(player_metadata_remove, "DELETE FROM `player_metadata` "
|
||||||
|
"WHERE `player` = ?")
|
||||||
|
verbosestream << "ServerEnvironment: SQLite3 database opened (players)." << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PlayerDatabaseSQLite3::playerDataExists(const std::string &name)
|
||||||
|
{
|
||||||
|
verifyDatabase();
|
||||||
|
str_to_sqlite(m_stmt_player_load, 1, name);
|
||||||
|
bool res = (sqlite3_step(m_stmt_player_load) == SQLITE_ROW);
|
||||||
|
sqlite3_reset(m_stmt_player_load);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlayerDatabaseSQLite3::savePlayer(RemotePlayer *player)
|
||||||
|
{
|
||||||
|
PlayerSAO* sao = player->getPlayerSAO();
|
||||||
|
sanity_check(sao);
|
||||||
|
|
||||||
|
const v3f &pos = sao->getBasePosition();
|
||||||
|
// Begin save in brace is mandatory
|
||||||
|
if (!playerDataExists(player->getName())) {
|
||||||
|
beginSave();
|
||||||
|
str_to_sqlite(m_stmt_player_add, 1, player->getName());
|
||||||
|
double_to_sqlite(m_stmt_player_add, 2, sao->getPitch());
|
||||||
|
double_to_sqlite(m_stmt_player_add, 3, sao->getYaw());
|
||||||
|
double_to_sqlite(m_stmt_player_add, 4, pos.X);
|
||||||
|
double_to_sqlite(m_stmt_player_add, 5, pos.Y);
|
||||||
|
double_to_sqlite(m_stmt_player_add, 6, pos.Z);
|
||||||
|
int64_to_sqlite(m_stmt_player_add, 7, sao->getHP());
|
||||||
|
int64_to_sqlite(m_stmt_player_add, 8, sao->getBreath());
|
||||||
|
|
||||||
|
sqlite3_vrfy(sqlite3_step(m_stmt_player_add), SQLITE_DONE);
|
||||||
|
sqlite3_reset(m_stmt_player_add);
|
||||||
|
} else {
|
||||||
|
beginSave();
|
||||||
|
double_to_sqlite(m_stmt_player_update, 1, sao->getPitch());
|
||||||
|
double_to_sqlite(m_stmt_player_update, 2, sao->getYaw());
|
||||||
|
double_to_sqlite(m_stmt_player_update, 3, pos.X);
|
||||||
|
double_to_sqlite(m_stmt_player_update, 4, pos.Y);
|
||||||
|
double_to_sqlite(m_stmt_player_update, 5, pos.Z);
|
||||||
|
int64_to_sqlite(m_stmt_player_update, 6, sao->getHP());
|
||||||
|
int64_to_sqlite(m_stmt_player_update, 7, sao->getBreath());
|
||||||
|
str_to_sqlite(m_stmt_player_update, 8, player->getName());
|
||||||
|
|
||||||
|
sqlite3_vrfy(sqlite3_step(m_stmt_player_update), SQLITE_DONE);
|
||||||
|
sqlite3_reset(m_stmt_player_update);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write player inventories
|
||||||
|
str_to_sqlite(m_stmt_player_remove_inventory, 1, player->getName());
|
||||||
|
sqlite3_vrfy(sqlite3_step(m_stmt_player_remove_inventory), SQLITE_DONE);
|
||||||
|
sqlite3_reset(m_stmt_player_remove_inventory);
|
||||||
|
|
||||||
|
str_to_sqlite(m_stmt_player_remove_inventory_items, 1, player->getName());
|
||||||
|
sqlite3_vrfy(sqlite3_step(m_stmt_player_remove_inventory_items), SQLITE_DONE);
|
||||||
|
sqlite3_reset(m_stmt_player_remove_inventory_items);
|
||||||
|
|
||||||
|
std::vector<const InventoryList*> inventory_lists = sao->getInventory()->getLists();
|
||||||
|
for (u16 i = 0; i < inventory_lists.size(); i++) {
|
||||||
|
const InventoryList* list = inventory_lists[i];
|
||||||
|
|
||||||
|
str_to_sqlite(m_stmt_player_add_inventory, 1, player->getName());
|
||||||
|
int_to_sqlite(m_stmt_player_add_inventory, 2, i);
|
||||||
|
int_to_sqlite(m_stmt_player_add_inventory, 3, list->getWidth());
|
||||||
|
str_to_sqlite(m_stmt_player_add_inventory, 4, list->getName());
|
||||||
|
int_to_sqlite(m_stmt_player_add_inventory, 5, list->getSize());
|
||||||
|
sqlite3_vrfy(sqlite3_step(m_stmt_player_add_inventory), SQLITE_DONE);
|
||||||
|
sqlite3_reset(m_stmt_player_add_inventory);
|
||||||
|
|
||||||
|
for (u32 j = 0; j < list->getSize(); j++) {
|
||||||
|
std::ostringstream os;
|
||||||
|
list->getItem(j).serialize(os);
|
||||||
|
std::string itemStr = os.str();
|
||||||
|
|
||||||
|
str_to_sqlite(m_stmt_player_add_inventory_items, 1, player->getName());
|
||||||
|
int_to_sqlite(m_stmt_player_add_inventory_items, 2, i);
|
||||||
|
int_to_sqlite(m_stmt_player_add_inventory_items, 3, j);
|
||||||
|
str_to_sqlite(m_stmt_player_add_inventory_items, 4, itemStr);
|
||||||
|
sqlite3_vrfy(sqlite3_step(m_stmt_player_add_inventory_items), SQLITE_DONE);
|
||||||
|
sqlite3_reset(m_stmt_player_add_inventory_items);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
str_to_sqlite(m_stmt_player_metadata_remove, 1, player->getName());
|
||||||
|
sqlite3_vrfy(sqlite3_step(m_stmt_player_metadata_remove), SQLITE_DONE);
|
||||||
|
sqlite3_reset(m_stmt_player_metadata_remove);
|
||||||
|
|
||||||
|
const PlayerAttributes &attrs = sao->getExtendedAttributes();
|
||||||
|
for (PlayerAttributes::const_iterator it = attrs.begin(); it != attrs.end(); ++it) {
|
||||||
|
str_to_sqlite(m_stmt_player_metadata_add, 1, player->getName());
|
||||||
|
str_to_sqlite(m_stmt_player_metadata_add, 2, it->first);
|
||||||
|
str_to_sqlite(m_stmt_player_metadata_add, 3, it->second);
|
||||||
|
sqlite3_vrfy(sqlite3_step(m_stmt_player_metadata_add), SQLITE_DONE);
|
||||||
|
sqlite3_reset(m_stmt_player_metadata_add);
|
||||||
|
}
|
||||||
|
|
||||||
|
endSave();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PlayerDatabaseSQLite3::loadPlayer(RemotePlayer *player, PlayerSAO *sao)
|
||||||
|
{
|
||||||
|
verifyDatabase();
|
||||||
|
|
||||||
|
str_to_sqlite(m_stmt_player_load, 1, player->getName());
|
||||||
|
if (sqlite3_step(m_stmt_player_load) != SQLITE_ROW) {
|
||||||
|
sqlite3_reset(m_stmt_player_load);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
sao->setPitch(sqlite_to_float(m_stmt_player_load, 0));
|
||||||
|
sao->setYaw(sqlite_to_float(m_stmt_player_load, 1));
|
||||||
|
sao->setBasePosition(sqlite_to_v3f(m_stmt_player_load, 2));
|
||||||
|
sao->setHPRaw((s16) MYMIN(sqlite_to_int(m_stmt_player_load, 5), S16_MAX));
|
||||||
|
sao->setBreath((u16) MYMIN(sqlite_to_int(m_stmt_player_load, 6), U16_MAX), false);
|
||||||
|
sqlite3_reset(m_stmt_player_load);
|
||||||
|
|
||||||
|
// Load inventory
|
||||||
|
str_to_sqlite(m_stmt_player_load_inventory, 1, player->getName());
|
||||||
|
while (sqlite3_step(m_stmt_player_load_inventory) == SQLITE_ROW) {
|
||||||
|
InventoryList *invList = player->inventory.addList(
|
||||||
|
sqlite_to_string(m_stmt_player_load_inventory, 2),
|
||||||
|
sqlite_to_uint(m_stmt_player_load_inventory, 3));
|
||||||
|
invList->setWidth(sqlite_to_uint(m_stmt_player_load_inventory, 1));
|
||||||
|
|
||||||
|
u32 invId = sqlite_to_uint(m_stmt_player_load_inventory, 0);
|
||||||
|
|
||||||
|
str_to_sqlite(m_stmt_player_load_inventory_items, 1, player->getName());
|
||||||
|
int_to_sqlite(m_stmt_player_load_inventory_items, 2, invId);
|
||||||
|
while (sqlite3_step(m_stmt_player_load_inventory_items) == SQLITE_ROW) {
|
||||||
|
const std::string itemStr = sqlite_to_string(m_stmt_player_load_inventory_items, 1);
|
||||||
|
if (itemStr.length() > 0) {
|
||||||
|
ItemStack stack;
|
||||||
|
stack.deSerialize(itemStr);
|
||||||
|
invList->addItem(sqlite_to_uint(m_stmt_player_load_inventory_items, 0), stack);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sqlite3_reset(m_stmt_player_load_inventory_items);
|
||||||
|
}
|
||||||
|
|
||||||
|
sqlite3_reset(m_stmt_player_load_inventory);
|
||||||
|
|
||||||
|
str_to_sqlite(m_stmt_player_metadata_load, 1, sao->getPlayer()->getName());
|
||||||
|
while (sqlite3_step(m_stmt_player_metadata_load) == SQLITE_ROW) {
|
||||||
|
std::string attr = sqlite_to_string(m_stmt_player_metadata_load, 0);
|
||||||
|
std::string value = sqlite_to_string(m_stmt_player_metadata_load, 1);
|
||||||
|
|
||||||
|
sao->setExtendedAttribute(attr, value);
|
||||||
|
}
|
||||||
|
sqlite3_reset(m_stmt_player_metadata_load);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PlayerDatabaseSQLite3::removePlayer(const std::string &name)
|
||||||
|
{
|
||||||
|
if (!playerDataExists(name))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
str_to_sqlite(m_stmt_player_remove, 1, name);
|
||||||
|
sqlite3_vrfy(sqlite3_step(m_stmt_player_remove), SQLITE_DONE);
|
||||||
|
sqlite3_reset(m_stmt_player_remove);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlayerDatabaseSQLite3::listPlayers(std::vector<std::string> &res)
|
||||||
|
{
|
||||||
|
verifyDatabase();
|
||||||
|
|
||||||
|
while (sqlite3_step(m_stmt_player_list) == SQLITE_ROW)
|
||||||
|
res.push_back(sqlite_to_string(m_stmt_player_list, 0));
|
||||||
|
|
||||||
|
sqlite3_reset(m_stmt_player_list);
|
||||||
|
}
|
||||||
|
|
|
@ -20,8 +20,10 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
#ifndef DATABASE_SQLITE3_HEADER
|
#ifndef DATABASE_SQLITE3_HEADER
|
||||||
#define DATABASE_SQLITE3_HEADER
|
#define DATABASE_SQLITE3_HEADER
|
||||||
|
|
||||||
|
#include <cstring>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include "database.h"
|
#include "database.h"
|
||||||
|
#include "exceptions.h"
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#include "sqlite3.h"
|
#include "sqlite3.h"
|
||||||
|
@ -30,37 +32,97 @@ extern "C" {
|
||||||
class Database_SQLite3 : public Database
|
class Database_SQLite3 : public Database
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
Database_SQLite3(const std::string &savedir);
|
virtual ~Database_SQLite3();
|
||||||
~Database_SQLite3();
|
|
||||||
|
|
||||||
void beginSave();
|
void beginSave();
|
||||||
void endSave();
|
void endSave();
|
||||||
|
|
||||||
bool saveBlock(const v3s16 &pos, const std::string &data);
|
|
||||||
void loadBlock(const v3s16 &pos, std::string *block);
|
|
||||||
bool deleteBlock(const v3s16 &pos);
|
|
||||||
void listAllLoadableBlocks(std::vector<v3s16> &dst);
|
|
||||||
bool initialized() const { return m_initialized; }
|
bool initialized() const { return m_initialized; }
|
||||||
|
protected:
|
||||||
|
Database_SQLite3(const std::string &savedir, const std::string &dbname);
|
||||||
|
|
||||||
private:
|
|
||||||
// Open the database
|
|
||||||
void openDatabase();
|
|
||||||
// Create the database structure
|
|
||||||
void createDatabase();
|
|
||||||
// Open and initialize the database if needed
|
// Open and initialize the database if needed
|
||||||
void verifyDatabase();
|
void verifyDatabase();
|
||||||
|
|
||||||
void bindPos(sqlite3_stmt *stmt, const v3s16 &pos, int index = 1);
|
// Convertors
|
||||||
|
inline void str_to_sqlite(sqlite3_stmt *s, int iCol, const std::string &str) const
|
||||||
|
{
|
||||||
|
sqlite3_vrfy(sqlite3_bind_text(s, iCol, str.c_str(), str.size(), NULL));
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void str_to_sqlite(sqlite3_stmt *s, int iCol, const char *str) const
|
||||||
|
{
|
||||||
|
sqlite3_vrfy(sqlite3_bind_text(s, iCol, str, strlen(str), NULL));
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void int_to_sqlite(sqlite3_stmt *s, int iCol, int val) const
|
||||||
|
{
|
||||||
|
sqlite3_vrfy(sqlite3_bind_int(s, iCol, val));
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void int64_to_sqlite(sqlite3_stmt *s, int iCol, s64 val) const
|
||||||
|
{
|
||||||
|
sqlite3_vrfy(sqlite3_bind_int64(s, iCol, (sqlite3_int64) val));
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void double_to_sqlite(sqlite3_stmt *s, int iCol, double val) const
|
||||||
|
{
|
||||||
|
sqlite3_vrfy(sqlite3_bind_double(s, iCol, val));
|
||||||
|
}
|
||||||
|
|
||||||
|
inline std::string sqlite_to_string(sqlite3_stmt *s, int iCol)
|
||||||
|
{
|
||||||
|
const char* text = reinterpret_cast<const char*>(sqlite3_column_text(s, iCol));
|
||||||
|
return std::string(text ? text : "");
|
||||||
|
}
|
||||||
|
|
||||||
|
inline s32 sqlite_to_int(sqlite3_stmt *s, int iCol)
|
||||||
|
{
|
||||||
|
return sqlite3_column_int(s, iCol);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline u32 sqlite_to_uint(sqlite3_stmt *s, int iCol)
|
||||||
|
{
|
||||||
|
return (u32) sqlite3_column_int(s, iCol);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline float sqlite_to_float(sqlite3_stmt *s, int iCol)
|
||||||
|
{
|
||||||
|
return (float) sqlite3_column_double(s, iCol);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline const v3f sqlite_to_v3f(sqlite3_stmt *s, int iCol)
|
||||||
|
{
|
||||||
|
return v3f(sqlite_to_float(s, iCol), sqlite_to_float(s, iCol + 1),
|
||||||
|
sqlite_to_float(s, iCol + 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Query verifiers helpers
|
||||||
|
inline void sqlite3_vrfy(int s, const std::string &m = "", int r = SQLITE_OK) const
|
||||||
|
{
|
||||||
|
if (s != r)
|
||||||
|
throw DatabaseException(m + ": " + sqlite3_errmsg(m_database));
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void sqlite3_vrfy(const int s, const int r, const std::string &m = "") const
|
||||||
|
{
|
||||||
|
sqlite3_vrfy(s, m, r);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the database structure
|
||||||
|
virtual void createDatabase() = 0;
|
||||||
|
virtual void initStatements() = 0;
|
||||||
|
|
||||||
|
sqlite3 *m_database;
|
||||||
|
private:
|
||||||
|
// Open the database
|
||||||
|
void openDatabase();
|
||||||
|
|
||||||
bool m_initialized;
|
bool m_initialized;
|
||||||
|
|
||||||
std::string m_savedir;
|
std::string m_savedir;
|
||||||
|
std::string m_dbname;
|
||||||
|
|
||||||
sqlite3 *m_database;
|
|
||||||
sqlite3_stmt *m_stmt_read;
|
|
||||||
sqlite3_stmt *m_stmt_write;
|
|
||||||
sqlite3_stmt *m_stmt_list;
|
|
||||||
sqlite3_stmt *m_stmt_delete;
|
|
||||||
sqlite3_stmt *m_stmt_begin;
|
sqlite3_stmt *m_stmt_begin;
|
||||||
sqlite3_stmt *m_stmt_end;
|
sqlite3_stmt *m_stmt_end;
|
||||||
|
|
||||||
|
@ -69,4 +131,66 @@ private:
|
||||||
static int busyHandler(void *data, int count);
|
static int busyHandler(void *data, int count);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class MapDatabaseSQLite3 : private Database_SQLite3, public MapDatabase
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
MapDatabaseSQLite3(const std::string &savedir);
|
||||||
|
virtual ~MapDatabaseSQLite3();
|
||||||
|
|
||||||
|
bool saveBlock(const v3s16 &pos, const std::string &data);
|
||||||
|
void loadBlock(const v3s16 &pos, std::string *block);
|
||||||
|
bool deleteBlock(const v3s16 &pos);
|
||||||
|
void listAllLoadableBlocks(std::vector<v3s16> &dst);
|
||||||
|
|
||||||
|
void beginSave() { Database_SQLite3::beginSave(); }
|
||||||
|
void endSave() { Database_SQLite3::endSave(); }
|
||||||
|
protected:
|
||||||
|
virtual void createDatabase();
|
||||||
|
virtual void initStatements();
|
||||||
|
|
||||||
|
private:
|
||||||
|
void bindPos(sqlite3_stmt *stmt, const v3s16 &pos, int index = 1);
|
||||||
|
|
||||||
|
// Map
|
||||||
|
sqlite3_stmt *m_stmt_read;
|
||||||
|
sqlite3_stmt *m_stmt_write;
|
||||||
|
sqlite3_stmt *m_stmt_list;
|
||||||
|
sqlite3_stmt *m_stmt_delete;
|
||||||
|
};
|
||||||
|
|
||||||
|
class PlayerDatabaseSQLite3 : private Database_SQLite3, public PlayerDatabase
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
PlayerDatabaseSQLite3(const std::string &savedir);
|
||||||
|
virtual ~PlayerDatabaseSQLite3();
|
||||||
|
|
||||||
|
void savePlayer(RemotePlayer *player);
|
||||||
|
bool loadPlayer(RemotePlayer *player, PlayerSAO *sao);
|
||||||
|
bool removePlayer(const std::string &name);
|
||||||
|
void listPlayers(std::vector<std::string> &res);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
virtual void createDatabase();
|
||||||
|
virtual void initStatements();
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool playerDataExists(const std::string &name);
|
||||||
|
|
||||||
|
// Players
|
||||||
|
sqlite3_stmt *m_stmt_player_load;
|
||||||
|
sqlite3_stmt *m_stmt_player_add;
|
||||||
|
sqlite3_stmt *m_stmt_player_update;
|
||||||
|
sqlite3_stmt *m_stmt_player_remove;
|
||||||
|
sqlite3_stmt *m_stmt_player_list;
|
||||||
|
sqlite3_stmt *m_stmt_player_load_inventory;
|
||||||
|
sqlite3_stmt *m_stmt_player_load_inventory_items;
|
||||||
|
sqlite3_stmt *m_stmt_player_add_inventory;
|
||||||
|
sqlite3_stmt *m_stmt_player_add_inventory_items;
|
||||||
|
sqlite3_stmt *m_stmt_player_remove_inventory;
|
||||||
|
sqlite3_stmt *m_stmt_player_remove_inventory_items;
|
||||||
|
sqlite3_stmt *m_stmt_player_metadata_load;
|
||||||
|
sqlite3_stmt *m_stmt_player_metadata_remove;
|
||||||
|
sqlite3_stmt *m_stmt_player_metadata_add;
|
||||||
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -48,7 +48,7 @@ static inline s64 pythonmodulo(s64 i, s16 mod)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
s64 Database::getBlockAsInteger(const v3s16 &pos)
|
s64 MapDatabase::getBlockAsInteger(const v3s16 &pos)
|
||||||
{
|
{
|
||||||
return (u64) pos.Z * 0x1000000 +
|
return (u64) pos.Z * 0x1000000 +
|
||||||
(u64) pos.Y * 0x1000 +
|
(u64) pos.Y * 0x1000 +
|
||||||
|
@ -56,7 +56,7 @@ s64 Database::getBlockAsInteger(const v3s16 &pos)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
v3s16 Database::getIntegerAsBlock(s64 i)
|
v3s16 MapDatabase::getIntegerAsBlock(s64 i)
|
||||||
{
|
{
|
||||||
v3s16 pos;
|
v3s16 pos;
|
||||||
pos.X = unsigned_to_signed(pythonmodulo(i, 4096), 2048);
|
pos.X = unsigned_to_signed(pythonmodulo(i, 4096), 2048);
|
||||||
|
|
|
@ -29,10 +29,15 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
class Database
|
class Database
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
virtual ~Database() {}
|
virtual void beginSave() = 0;
|
||||||
|
virtual void endSave() = 0;
|
||||||
|
virtual bool initialized() const { return true; }
|
||||||
|
};
|
||||||
|
|
||||||
virtual void beginSave() {}
|
class MapDatabase : public Database
|
||||||
virtual void endSave() {}
|
{
|
||||||
|
public:
|
||||||
|
virtual ~MapDatabase() {}
|
||||||
|
|
||||||
virtual bool saveBlock(const v3s16 &pos, const std::string &data) = 0;
|
virtual bool saveBlock(const v3s16 &pos, const std::string &data) = 0;
|
||||||
virtual void loadBlock(const v3s16 &pos, std::string *block) = 0;
|
virtual void loadBlock(const v3s16 &pos, std::string *block) = 0;
|
||||||
|
@ -42,8 +47,19 @@ public:
|
||||||
static v3s16 getIntegerAsBlock(s64 i);
|
static v3s16 getIntegerAsBlock(s64 i);
|
||||||
|
|
||||||
virtual void listAllLoadableBlocks(std::vector<v3s16> &dst) = 0;
|
virtual void listAllLoadableBlocks(std::vector<v3s16> &dst) = 0;
|
||||||
|
};
|
||||||
|
|
||||||
virtual bool initialized() const { return true; }
|
class PlayerSAO;
|
||||||
|
class RemotePlayer;
|
||||||
|
|
||||||
|
class PlayerDatabase
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
virtual ~PlayerDatabase() {}
|
||||||
|
virtual void savePlayer(RemotePlayer *player) = 0;
|
||||||
|
virtual bool loadPlayer(RemotePlayer *player, PlayerSAO *sao) = 0;
|
||||||
|
virtual bool removePlayer(const std::string &name) = 0;
|
||||||
|
virtual void listPlayers(std::vector<std::string> &res) = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
21
src/main.cpp
21
src/main.cpp
|
@ -47,6 +47,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
#endif
|
#endif
|
||||||
#ifndef SERVER
|
#ifndef SERVER
|
||||||
#include "client/clientlauncher.h"
|
#include "client/clientlauncher.h"
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef HAVE_TOUCHSCREENGUI
|
#ifdef HAVE_TOUCHSCREENGUI
|
||||||
|
@ -102,7 +103,7 @@ static bool get_game_from_cmdline(GameParams *game_params, const Settings &cmd_a
|
||||||
static bool determine_subgame(GameParams *game_params);
|
static bool determine_subgame(GameParams *game_params);
|
||||||
|
|
||||||
static bool run_dedicated_server(const GameParams &game_params, const Settings &cmd_args);
|
static bool run_dedicated_server(const GameParams &game_params, const Settings &cmd_args);
|
||||||
static bool migrate_database(const GameParams &game_params, const Settings &cmd_args);
|
static bool migrate_map_database(const GameParams &game_params, const Settings &cmd_args);
|
||||||
|
|
||||||
/**********************************************************************/
|
/**********************************************************************/
|
||||||
|
|
||||||
|
@ -292,6 +293,8 @@ static void set_allowed_options(OptionList *allowed_options)
|
||||||
_("Set gameid (\"--gameid list\" prints available ones)"))));
|
_("Set gameid (\"--gameid list\" prints available ones)"))));
|
||||||
allowed_options->insert(std::make_pair("migrate", ValueSpec(VALUETYPE_STRING,
|
allowed_options->insert(std::make_pair("migrate", ValueSpec(VALUETYPE_STRING,
|
||||||
_("Migrate from current map backend to another (Only works when using minetestserver or with --server)"))));
|
_("Migrate from current map backend to another (Only works when using minetestserver or with --server)"))));
|
||||||
|
allowed_options->insert(std::make_pair("migrate-players", ValueSpec(VALUETYPE_STRING,
|
||||||
|
_("Migrate from current players backend to another (Only works when using minetestserver or with --server)"))));
|
||||||
allowed_options->insert(std::make_pair("terminal", ValueSpec(VALUETYPE_FLAG,
|
allowed_options->insert(std::make_pair("terminal", ValueSpec(VALUETYPE_FLAG,
|
||||||
_("Feature an interactive terminal (Only works when using minetestserver or with --server)"))));
|
_("Feature an interactive terminal (Only works when using minetestserver or with --server)"))));
|
||||||
#ifndef SERVER
|
#ifndef SERVER
|
||||||
|
@ -332,7 +335,7 @@ static void print_allowed_options(const OptionList &allowed_options)
|
||||||
if (i->second.type != VALUETYPE_FLAG)
|
if (i->second.type != VALUETYPE_FLAG)
|
||||||
os1 << _(" <value>");
|
os1 << _(" <value>");
|
||||||
|
|
||||||
std::cout << padStringRight(os1.str(), 24);
|
std::cout << padStringRight(os1.str(), 30);
|
||||||
|
|
||||||
if (i->second.help != NULL)
|
if (i->second.help != NULL)
|
||||||
std::cout << i->second.help;
|
std::cout << i->second.help;
|
||||||
|
@ -828,7 +831,9 @@ static bool run_dedicated_server(const GameParams &game_params, const Settings &
|
||||||
|
|
||||||
// Database migration
|
// Database migration
|
||||||
if (cmd_args.exists("migrate"))
|
if (cmd_args.exists("migrate"))
|
||||||
return migrate_database(game_params, cmd_args);
|
return migrate_map_database(game_params, cmd_args);
|
||||||
|
else if (cmd_args.exists("migrate-players"))
|
||||||
|
return ServerEnvironment::migratePlayersDatabase(game_params, cmd_args);
|
||||||
|
|
||||||
if (cmd_args.exists("terminal")) {
|
if (cmd_args.exists("terminal")) {
|
||||||
#if USE_CURSES
|
#if USE_CURSES
|
||||||
|
@ -912,7 +917,7 @@ static bool run_dedicated_server(const GameParams &game_params, const Settings &
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool migrate_database(const GameParams &game_params, const Settings &cmd_args)
|
static bool migrate_map_database(const GameParams &game_params, const Settings &cmd_args)
|
||||||
{
|
{
|
||||||
std::string migrate_to = cmd_args.get("migrate");
|
std::string migrate_to = cmd_args.get("migrate");
|
||||||
Settings world_mt;
|
Settings world_mt;
|
||||||
|
@ -921,20 +926,23 @@ static bool migrate_database(const GameParams &game_params, const Settings &cmd_
|
||||||
errorstream << "Cannot read world.mt!" << std::endl;
|
errorstream << "Cannot read world.mt!" << std::endl;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!world_mt.exists("backend")) {
|
if (!world_mt.exists("backend")) {
|
||||||
errorstream << "Please specify your current backend in world.mt:"
|
errorstream << "Please specify your current backend in world.mt:"
|
||||||
<< std::endl
|
<< std::endl
|
||||||
<< " backend = {sqlite3|leveldb|redis|dummy}"
|
<< " backend = {sqlite3|leveldb|redis|dummy|postgresql}"
|
||||||
<< std::endl;
|
<< std::endl;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string backend = world_mt.get("backend");
|
std::string backend = world_mt.get("backend");
|
||||||
if (backend == migrate_to) {
|
if (backend == migrate_to) {
|
||||||
errorstream << "Cannot migrate: new backend is same"
|
errorstream << "Cannot migrate: new backend is same"
|
||||||
<< " as the old one" << std::endl;
|
<< " as the old one" << std::endl;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
Database *old_db = ServerMap::createDatabase(backend, game_params.world_path, world_mt),
|
|
||||||
|
MapDatabase *old_db = ServerMap::createDatabase(backend, game_params.world_path, world_mt),
|
||||||
*new_db = ServerMap::createDatabase(migrate_to, game_params.world_path, world_mt);
|
*new_db = ServerMap::createDatabase(migrate_to, game_params.world_path, world_mt);
|
||||||
|
|
||||||
u32 count = 0;
|
u32 count = 0;
|
||||||
|
@ -976,4 +984,3 @@ static bool migrate_database(const GameParams &game_params, const Settings &cmd_
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
13
src/map.cpp
13
src/map.cpp
|
@ -2286,13 +2286,13 @@ bool ServerMap::loadSectorFull(v2s16 p2d)
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
Database *ServerMap::createDatabase(
|
MapDatabase *ServerMap::createDatabase(
|
||||||
const std::string &name,
|
const std::string &name,
|
||||||
const std::string &savedir,
|
const std::string &savedir,
|
||||||
Settings &conf)
|
Settings &conf)
|
||||||
{
|
{
|
||||||
if (name == "sqlite3")
|
if (name == "sqlite3")
|
||||||
return new Database_SQLite3(savedir);
|
return new MapDatabaseSQLite3(savedir);
|
||||||
if (name == "dummy")
|
if (name == "dummy")
|
||||||
return new Database_Dummy();
|
return new Database_Dummy();
|
||||||
#if USE_LEVELDB
|
#if USE_LEVELDB
|
||||||
|
@ -2304,8 +2304,11 @@ Database *ServerMap::createDatabase(
|
||||||
return new Database_Redis(conf);
|
return new Database_Redis(conf);
|
||||||
#endif
|
#endif
|
||||||
#if USE_POSTGRESQL
|
#if USE_POSTGRESQL
|
||||||
else if (name == "postgresql")
|
else if (name == "postgresql") {
|
||||||
return new Database_PostgreSQL(conf);
|
std::string connect_string = "";
|
||||||
|
conf.getNoEx("pgsql_connection", connect_string);
|
||||||
|
return new MapDatabasePostgreSQL(connect_string);
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
else
|
else
|
||||||
throw BaseException(std::string("Database backend ") + name + " not supported.");
|
throw BaseException(std::string("Database backend ") + name + " not supported.");
|
||||||
|
@ -2326,7 +2329,7 @@ bool ServerMap::saveBlock(MapBlock *block)
|
||||||
return saveBlock(block, dbase);
|
return saveBlock(block, dbase);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ServerMap::saveBlock(MapBlock *block, Database *db)
|
bool ServerMap::saveBlock(MapBlock *block, MapDatabase *db)
|
||||||
{
|
{
|
||||||
v3s16 p3d = block->getPos();
|
v3s16 p3d = block->getPos();
|
||||||
|
|
||||||
|
|
|
@ -37,7 +37,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
#include "map_settings_manager.h"
|
#include "map_settings_manager.h"
|
||||||
|
|
||||||
class Settings;
|
class Settings;
|
||||||
class Database;
|
class MapDatabase;
|
||||||
class ClientMap;
|
class ClientMap;
|
||||||
class MapSector;
|
class MapSector;
|
||||||
class ServerMapSector;
|
class ServerMapSector;
|
||||||
|
@ -430,7 +430,7 @@ public:
|
||||||
/*
|
/*
|
||||||
Database functions
|
Database functions
|
||||||
*/
|
*/
|
||||||
static Database *createDatabase(const std::string &name, const std::string &savedir, Settings &conf);
|
static MapDatabase *createDatabase(const std::string &name, const std::string &savedir, Settings &conf);
|
||||||
|
|
||||||
// Returns true if the database file does not exist
|
// Returns true if the database file does not exist
|
||||||
bool loadFromFolders();
|
bool loadFromFolders();
|
||||||
|
@ -458,7 +458,7 @@ public:
|
||||||
bool loadSectorMeta(v2s16 p2d);
|
bool loadSectorMeta(v2s16 p2d);
|
||||||
|
|
||||||
bool saveBlock(MapBlock *block);
|
bool saveBlock(MapBlock *block);
|
||||||
static bool saveBlock(MapBlock *block, Database *db);
|
static bool saveBlock(MapBlock *block, MapDatabase *db);
|
||||||
// This will generate a sector with getSector if not found.
|
// This will generate a sector with getSector if not found.
|
||||||
void loadBlock(const std::string §ordir, const std::string &blockfile,
|
void loadBlock(const std::string §ordir, const std::string &blockfile,
|
||||||
MapSector *sector, bool save_after_load=false);
|
MapSector *sector, bool save_after_load=false);
|
||||||
|
@ -510,7 +510,7 @@ private:
|
||||||
This is reset to false when written on disk.
|
This is reset to false when written on disk.
|
||||||
*/
|
*/
|
||||||
bool m_map_metadata_changed;
|
bool m_map_metadata_changed;
|
||||||
Database *dbase;
|
MapDatabase *dbase;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -67,54 +67,6 @@ RemotePlayer::RemotePlayer(const char *name, IItemDefManager *idef):
|
||||||
movement_gravity = g_settings->getFloat("movement_gravity") * BS;
|
movement_gravity = g_settings->getFloat("movement_gravity") * BS;
|
||||||
}
|
}
|
||||||
|
|
||||||
void RemotePlayer::save(std::string savedir, IGameDef *gamedef)
|
|
||||||
{
|
|
||||||
/*
|
|
||||||
* We have to open all possible player files in the players directory
|
|
||||||
* and check their player names because some file systems are not
|
|
||||||
* case-sensitive and player names are case-sensitive.
|
|
||||||
*/
|
|
||||||
|
|
||||||
// A player to deserialize files into to check their names
|
|
||||||
RemotePlayer testplayer("", gamedef->idef());
|
|
||||||
|
|
||||||
savedir += DIR_DELIM;
|
|
||||||
std::string path = savedir + m_name;
|
|
||||||
for (u32 i = 0; i < PLAYER_FILE_ALTERNATE_TRIES; i++) {
|
|
||||||
if (!fs::PathExists(path)) {
|
|
||||||
// Open file and serialize
|
|
||||||
std::ostringstream ss(std::ios_base::binary);
|
|
||||||
serialize(ss);
|
|
||||||
if (!fs::safeWriteToFile(path, ss.str())) {
|
|
||||||
infostream << "Failed to write " << path << std::endl;
|
|
||||||
}
|
|
||||||
setModified(false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Open file and deserialize
|
|
||||||
std::ifstream is(path.c_str(), std::ios_base::binary);
|
|
||||||
if (!is.good()) {
|
|
||||||
infostream << "Failed to open " << path << std::endl;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
testplayer.deSerialize(is, path, NULL);
|
|
||||||
is.close();
|
|
||||||
if (strcmp(testplayer.getName(), m_name) == 0) {
|
|
||||||
// Open file and serialize
|
|
||||||
std::ostringstream ss(std::ios_base::binary);
|
|
||||||
serialize(ss);
|
|
||||||
if (!fs::safeWriteToFile(path, ss.str())) {
|
|
||||||
infostream << "Failed to write " << path << std::endl;
|
|
||||||
}
|
|
||||||
setModified(false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
path = savedir + m_name + itos(i);
|
|
||||||
}
|
|
||||||
|
|
||||||
infostream << "Didn't find free file for player " << m_name << std::endl;
|
|
||||||
}
|
|
||||||
|
|
||||||
void RemotePlayer::serializeExtraAttributes(std::string &output)
|
void RemotePlayer::serializeExtraAttributes(std::string &output)
|
||||||
{
|
{
|
||||||
assert(m_sao);
|
assert(m_sao);
|
||||||
|
|
|
@ -37,11 +37,11 @@ enum RemotePlayerChatResult
|
||||||
*/
|
*/
|
||||||
class RemotePlayer : public Player
|
class RemotePlayer : public Player
|
||||||
{
|
{
|
||||||
|
friend class PlayerDatabaseFiles;
|
||||||
public:
|
public:
|
||||||
RemotePlayer(const char *name, IItemDefManager *idef);
|
RemotePlayer(const char *name, IItemDefManager *idef);
|
||||||
virtual ~RemotePlayer() {}
|
virtual ~RemotePlayer() {}
|
||||||
|
|
||||||
void save(std::string savedir, IGameDef *gamedef);
|
|
||||||
void deSerialize(std::istream &is, const std::string &playername, PlayerSAO *sao);
|
void deSerialize(std::istream &is, const std::string &playername, PlayerSAO *sao);
|
||||||
|
|
||||||
PlayerSAO *getPlayerSAO() { return m_sao; }
|
PlayerSAO *getPlayerSAO() { return m_sao; }
|
||||||
|
|
|
@ -334,6 +334,22 @@ int ModApiServer::l_kick_player(lua_State *L)
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int ModApiServer::l_remove_player(lua_State *L)
|
||||||
|
{
|
||||||
|
NO_MAP_LOCK_REQUIRED;
|
||||||
|
std::string name = luaL_checkstring(L, 1);
|
||||||
|
ServerEnvironment *s_env = dynamic_cast<ServerEnvironment *>(getEnv(L));
|
||||||
|
assert(s_env);
|
||||||
|
|
||||||
|
RemotePlayer *player = s_env->getPlayer(name.c_str());
|
||||||
|
if (!player)
|
||||||
|
lua_pushinteger(L, s_env->removePlayerFromDatabase(name) ? 0 : 1);
|
||||||
|
else
|
||||||
|
lua_pushinteger(L, 2);
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
// unban_player_or_ip()
|
// unban_player_or_ip()
|
||||||
int ModApiServer::l_unban_player_or_ip(lua_State *L)
|
int ModApiServer::l_unban_player_or_ip(lua_State *L)
|
||||||
{
|
{
|
||||||
|
@ -510,6 +526,7 @@ void ModApiServer::Initialize(lua_State *L, int top)
|
||||||
API_FCT(get_ban_description);
|
API_FCT(get_ban_description);
|
||||||
API_FCT(ban_player);
|
API_FCT(ban_player);
|
||||||
API_FCT(kick_player);
|
API_FCT(kick_player);
|
||||||
|
API_FCT(remove_player);
|
||||||
API_FCT(unban_player_or_ip);
|
API_FCT(unban_player_or_ip);
|
||||||
API_FCT(notify_authentication_modified);
|
API_FCT(notify_authentication_modified);
|
||||||
|
|
||||||
|
|
|
@ -92,6 +92,9 @@ private:
|
||||||
// kick_player(name, [message]) -> success
|
// kick_player(name, [message]) -> success
|
||||||
static int l_kick_player(lua_State *L);
|
static int l_kick_player(lua_State *L);
|
||||||
|
|
||||||
|
// remove_player(name)
|
||||||
|
static int l_remove_player(lua_State *L);
|
||||||
|
|
||||||
// notify_authentication_modified(name)
|
// notify_authentication_modified(name)
|
||||||
static int l_notify_authentication_modified(lua_State *L);
|
static int l_notify_authentication_modified(lua_State *L);
|
||||||
|
|
||||||
|
|
|
@ -60,6 +60,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
#include "util/base64.h"
|
#include "util/base64.h"
|
||||||
#include "util/sha1.h"
|
#include "util/sha1.h"
|
||||||
#include "util/hex.h"
|
#include "util/hex.h"
|
||||||
|
#include "database.h"
|
||||||
|
|
||||||
class ClientNotFoundException : public BaseException
|
class ClientNotFoundException : public BaseException
|
||||||
{
|
{
|
||||||
|
@ -2618,9 +2619,8 @@ void Server::RespawnPlayer(u16 peer_id)
|
||||||
|
|
||||||
bool repositioned = m_script->on_respawnplayer(playersao);
|
bool repositioned = m_script->on_respawnplayer(playersao);
|
||||||
if (!repositioned) {
|
if (!repositioned) {
|
||||||
v3f pos = findSpawnPos();
|
|
||||||
// setPos will send the new position to client
|
// setPos will send the new position to client
|
||||||
playersao->setPos(pos);
|
playersao->setPos(findSpawnPos());
|
||||||
}
|
}
|
||||||
|
|
||||||
SendPlayerHP(peer_id);
|
SendPlayerHP(peer_id);
|
||||||
|
@ -3516,8 +3516,6 @@ void Server::requestShutdown(const std::string &msg, bool reconnect, float delay
|
||||||
|
|
||||||
PlayerSAO* Server::emergePlayer(const char *name, u16 peer_id, u16 proto_version)
|
PlayerSAO* Server::emergePlayer(const char *name, u16 peer_id, u16 proto_version)
|
||||||
{
|
{
|
||||||
bool newplayer = false;
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Try to get an existing player
|
Try to get an existing player
|
||||||
*/
|
*/
|
||||||
|
@ -3538,45 +3536,19 @@ PlayerSAO* Server::emergePlayer(const char *name, u16 peer_id, u16 proto_version
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a new player active object
|
|
||||||
PlayerSAO *playersao = new PlayerSAO(m_env, peer_id, isSingleplayer());
|
|
||||||
player = m_env->loadPlayer(name, playersao);
|
|
||||||
|
|
||||||
// Create player if it doesn't exist
|
|
||||||
if (!player) {
|
if (!player) {
|
||||||
newplayer = true;
|
player = new RemotePlayer(name, idef());
|
||||||
player = new RemotePlayer(name, this->idef());
|
|
||||||
// Set player position
|
|
||||||
infostream<<"Server: Finding spawn place for player \""
|
|
||||||
<<name<<"\""<<std::endl;
|
|
||||||
playersao->setBasePosition(findSpawnPos());
|
|
||||||
|
|
||||||
// Make sure the player is saved
|
|
||||||
player->setModified(true);
|
|
||||||
|
|
||||||
// Add player to environment
|
|
||||||
m_env->addPlayer(player);
|
|
||||||
} 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 \""
|
|
||||||
<< name << "\" outside limits, resetting" << std::endl;
|
|
||||||
playersao->setBasePosition(findSpawnPos());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
playersao->initialize(player, getPlayerEffectivePrivs(player->getName()));
|
bool newplayer = false;
|
||||||
|
|
||||||
|
// Load player
|
||||||
|
PlayerSAO *playersao = m_env->loadPlayer(player, &newplayer, peer_id, isSingleplayer());
|
||||||
|
|
||||||
|
// Complete init with server parts
|
||||||
|
playersao->finalize(player, getPlayerEffectivePrivs(player->getName()));
|
||||||
player->protocol_version = proto_version;
|
player->protocol_version = proto_version;
|
||||||
|
|
||||||
/* Clean up old HUD elements from previous sessions */
|
|
||||||
player->clearHud();
|
|
||||||
|
|
||||||
/* Add object to environment */
|
|
||||||
m_env->addActiveObject(playersao);
|
|
||||||
|
|
||||||
/* Run scripts */
|
/* Run scripts */
|
||||||
if (newplayer) {
|
if (newplayer) {
|
||||||
m_script->on_newplayer(playersao);
|
m_script->on_newplayer(playersao);
|
||||||
|
|
|
@ -306,6 +306,7 @@ public:
|
||||||
bool showFormspec(const char *name, const std::string &formspec, const std::string &formname);
|
bool showFormspec(const char *name, const std::string &formspec, const std::string &formname);
|
||||||
Map & getMap() { return m_env->getMap(); }
|
Map & getMap() { return m_env->getMap(); }
|
||||||
ServerEnvironment & getEnv() { return *m_env; }
|
ServerEnvironment & getEnv() { return *m_env; }
|
||||||
|
v3f findSpawnPos();
|
||||||
|
|
||||||
u32 hudAdd(RemotePlayer *player, HudElement *element);
|
u32 hudAdd(RemotePlayer *player, HudElement *element);
|
||||||
bool hudRemove(RemotePlayer *player, u32 id);
|
bool hudRemove(RemotePlayer *player, u32 id);
|
||||||
|
@ -472,8 +473,6 @@ private:
|
||||||
RemotePlayer *player = NULL);
|
RemotePlayer *player = NULL);
|
||||||
void handleAdminChat(const ChatEventChat *evt);
|
void handleAdminChat(const ChatEventChat *evt);
|
||||||
|
|
||||||
v3f findSpawnPos();
|
|
||||||
|
|
||||||
// When called, connection mutex should be locked
|
// When called, connection mutex should be locked
|
||||||
RemoteClient* getClient(u16 peer_id,ClientState state_min=CS_Active);
|
RemoteClient* getClient(u16 peer_id,ClientState state_min=CS_Active);
|
||||||
RemoteClient* getClientNoEx(u16 peer_id,ClientState state_min=CS_Active);
|
RemoteClient* getClientNoEx(u16 peer_id,ClientState state_min=CS_Active);
|
||||||
|
|
|
@ -36,6 +36,13 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
#include "util/pointedthing.h"
|
#include "util/pointedthing.h"
|
||||||
#include "threading/mutex_auto_lock.h"
|
#include "threading/mutex_auto_lock.h"
|
||||||
#include "filesys.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_:"
|
#define LBM_NAME_ALLOWED_CHARS "abcdefghijklmnopqrstuvwxyz0123456789_:"
|
||||||
|
|
||||||
|
@ -365,8 +372,30 @@ ServerEnvironment::ServerEnvironment(ServerMap *map,
|
||||||
m_game_time_fraction_counter(0),
|
m_game_time_fraction_counter(0),
|
||||||
m_last_clear_objects_time(0),
|
m_last_clear_objects_time(0),
|
||||||
m_recommended_send_interval(0.1),
|
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()
|
ServerEnvironment::~ServerEnvironment()
|
||||||
|
@ -392,6 +421,8 @@ ServerEnvironment::~ServerEnvironment()
|
||||||
i != m_players.end(); ++i) {
|
i != m_players.end(); ++i) {
|
||||||
delete (*i);
|
delete (*i);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
delete m_player_database;
|
||||||
}
|
}
|
||||||
|
|
||||||
Map & ServerEnvironment::getMap()
|
Map & ServerEnvironment::getMap()
|
||||||
|
@ -455,6 +486,11 @@ void ServerEnvironment::removePlayer(RemotePlayer *player)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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)
|
bool ServerEnvironment::line_of_sight(v3f pos1, v3f pos2, float stepsize, v3s16 *p)
|
||||||
{
|
{
|
||||||
float distance = pos1.getDistanceFrom(pos2);
|
float distance = pos1.getDistanceFrom(pos2);
|
||||||
|
@ -495,7 +531,7 @@ void ServerEnvironment::kickAllPlayers(AccessDeniedCode reason,
|
||||||
|
|
||||||
void ServerEnvironment::saveLoadedPlayers()
|
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);
|
fs::CreateDir(players_path);
|
||||||
|
|
||||||
for (std::vector<RemotePlayer *>::iterator it = m_players.begin();
|
for (std::vector<RemotePlayer *>::iterator it = m_players.begin();
|
||||||
|
@ -503,63 +539,63 @@ void ServerEnvironment::saveLoadedPlayers()
|
||||||
++it) {
|
++it) {
|
||||||
if ((*it)->checkModified() ||
|
if ((*it)->checkModified() ||
|
||||||
((*it)->getPlayerSAO() && (*it)->getPlayerSAO()->extendedAttributesModified())) {
|
((*it)->getPlayerSAO() && (*it)->getPlayerSAO()->extendedAttributesModified())) {
|
||||||
(*it)->save(players_path, m_server);
|
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)
|
void ServerEnvironment::savePlayer(RemotePlayer *player)
|
||||||
{
|
{
|
||||||
std::string players_path = m_path_world + DIR_DELIM "players";
|
try {
|
||||||
fs::CreateDir(players_path);
|
m_player_database->savePlayer(player);
|
||||||
|
} catch (DatabaseException &e) {
|
||||||
player->save(players_path, m_server);
|
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;
|
PlayerSAO *playersao = new PlayerSAO(this, player, peer_id, is_singleplayer);
|
||||||
bool found = false;
|
// Create player if it doesn't exist
|
||||||
std::string players_path = m_path_world + DIR_DELIM "players" DIR_DELIM;
|
if (!m_player_database->loadPlayer(player, playersao)) {
|
||||||
std::string path = players_path + playername;
|
*new_player = true;
|
||||||
|
// Set player position
|
||||||
|
infostream << "Server: Finding spawn place for player \""
|
||||||
|
<< player->getName() << "\"" << std::endl;
|
||||||
|
playersao->setBasePosition(m_server->findSpawnPos());
|
||||||
|
|
||||||
RemotePlayer *player = getPlayer(playername.c_str());
|
// Make sure the player is saved
|
||||||
if (!player) {
|
player->setModified(true);
|
||||||
player = new RemotePlayer("", m_server->idef());
|
} else {
|
||||||
newplayer = true;
|
// 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());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (u32 i = 0; i < PLAYER_FILE_ALTERNATE_TRIES; i++) {
|
// Add player to environment
|
||||||
//// 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
path = players_path + playername + itos(i);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!found) {
|
|
||||||
infostream << "Player file for player " << playername
|
|
||||||
<< " not found" << std::endl;
|
|
||||||
if (newplayer)
|
|
||||||
delete player;
|
|
||||||
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (newplayer) {
|
|
||||||
addPlayer(player);
|
addPlayer(player);
|
||||||
}
|
|
||||||
player->setModified(false);
|
/* Clean up old HUD elements from previous sessions */
|
||||||
return player;
|
player->clearHud();
|
||||||
|
|
||||||
|
/* Add object to environment */
|
||||||
|
addActiveObject(playersao);
|
||||||
|
|
||||||
|
return playersao;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ServerEnvironment::saveMeta()
|
void ServerEnvironment::saveMeta()
|
||||||
|
@ -2173,3 +2209,111 @@ void ServerEnvironment::deactivateFarObjects(bool _force_delete)
|
||||||
m_active_objects.erase(*i);
|
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;
|
||||||
|
}
|
||||||
|
|
|
@ -27,7 +27,9 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
|
|
||||||
class IGameDef;
|
class IGameDef;
|
||||||
class ServerMap;
|
class ServerMap;
|
||||||
|
struct GameParams;
|
||||||
class RemotePlayer;
|
class RemotePlayer;
|
||||||
|
class PlayerDatabase;
|
||||||
class PlayerSAO;
|
class PlayerSAO;
|
||||||
class ServerEnvironment;
|
class ServerEnvironment;
|
||||||
class ActiveBlockModifier;
|
class ActiveBlockModifier;
|
||||||
|
@ -217,9 +219,11 @@ public:
|
||||||
// Save players
|
// Save players
|
||||||
void saveLoadedPlayers();
|
void saveLoadedPlayers();
|
||||||
void savePlayer(RemotePlayer *player);
|
void savePlayer(RemotePlayer *player);
|
||||||
RemotePlayer *loadPlayer(const std::string &playername, PlayerSAO *sao);
|
PlayerSAO *loadPlayer(RemotePlayer *player, bool *new_player, u16 peer_id,
|
||||||
|
bool is_singleplayer);
|
||||||
void addPlayer(RemotePlayer *player);
|
void addPlayer(RemotePlayer *player);
|
||||||
void removePlayer(RemotePlayer *player);
|
void removePlayer(RemotePlayer *player);
|
||||||
|
bool removePlayerFromDatabase(const std::string &name);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Save and load time of day and game timer
|
Save and load time of day and game timer
|
||||||
|
@ -334,8 +338,13 @@ public:
|
||||||
|
|
||||||
RemotePlayer *getPlayer(const u16 peer_id);
|
RemotePlayer *getPlayer(const u16 peer_id);
|
||||||
RemotePlayer *getPlayer(const char* name);
|
RemotePlayer *getPlayer(const char* name);
|
||||||
|
|
||||||
|
static bool migratePlayersDatabase(const GameParams &game_params,
|
||||||
|
const Settings &cmd_args);
|
||||||
private:
|
private:
|
||||||
|
|
||||||
|
static PlayerDatabase *openPlayerDatabase(const std::string &name,
|
||||||
|
const std::string &savedir, const Settings &conf);
|
||||||
/*
|
/*
|
||||||
Internal ActiveObject interface
|
Internal ActiveObject interface
|
||||||
-------------------------------------------
|
-------------------------------------------
|
||||||
|
@ -419,6 +428,8 @@ private:
|
||||||
// peer_ids in here should be unique, except that there may be many 0s
|
// peer_ids in here should be unique, except that there may be many 0s
|
||||||
std::vector<RemotePlayer*> m_players;
|
std::vector<RemotePlayer*> m_players;
|
||||||
|
|
||||||
|
PlayerDatabase *m_player_database;
|
||||||
|
|
||||||
// Particles
|
// Particles
|
||||||
IntervalLimiter m_particle_management_interval;
|
IntervalLimiter m_particle_management_interval;
|
||||||
UNORDERED_MAP<u32, float> m_particle_spawners;
|
UNORDERED_MAP<u32, float> m_particle_spawners;
|
||||||
|
|
|
@ -31,59 +31,10 @@ public:
|
||||||
const char *getName() { return "TestPlayer"; }
|
const char *getName() { return "TestPlayer"; }
|
||||||
|
|
||||||
void runTests(IGameDef *gamedef);
|
void runTests(IGameDef *gamedef);
|
||||||
|
|
||||||
void testSave(IGameDef *gamedef);
|
|
||||||
void testLoad(IGameDef *gamedef);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
static TestPlayer g_test_instance;
|
static TestPlayer g_test_instance;
|
||||||
|
|
||||||
void TestPlayer::runTests(IGameDef *gamedef)
|
void TestPlayer::runTests(IGameDef *gamedef)
|
||||||
{
|
{
|
||||||
TEST(testSave, gamedef);
|
|
||||||
TEST(testLoad, gamedef);
|
|
||||||
}
|
|
||||||
|
|
||||||
void TestPlayer::testSave(IGameDef *gamedef)
|
|
||||||
{
|
|
||||||
RemotePlayer rplayer("testplayer_save", gamedef->idef());
|
|
||||||
PlayerSAO sao(NULL, 1, false);
|
|
||||||
sao.initialize(&rplayer, std::set<std::string>());
|
|
||||||
rplayer.setPlayerSAO(&sao);
|
|
||||||
sao.setBreath(10, false);
|
|
||||||
sao.setHPRaw(8);
|
|
||||||
sao.setYaw(0.1f);
|
|
||||||
sao.setPitch(0.6f);
|
|
||||||
sao.setBasePosition(v3f(450.2f, -15.7f, 68.1f));
|
|
||||||
rplayer.save(".", gamedef);
|
|
||||||
UASSERT(fs::PathExists("testplayer_save"));
|
|
||||||
}
|
|
||||||
|
|
||||||
void TestPlayer::testLoad(IGameDef *gamedef)
|
|
||||||
{
|
|
||||||
RemotePlayer rplayer("testplayer_load", gamedef->idef());
|
|
||||||
PlayerSAO sao(NULL, 1, false);
|
|
||||||
sao.initialize(&rplayer, std::set<std::string>());
|
|
||||||
rplayer.setPlayerSAO(&sao);
|
|
||||||
sao.setBreath(10, false);
|
|
||||||
sao.setHPRaw(8);
|
|
||||||
sao.setYaw(0.1f);
|
|
||||||
sao.setPitch(0.6f);
|
|
||||||
sao.setBasePosition(v3f(450.2f, -15.7f, 68.1f));
|
|
||||||
rplayer.save(".", gamedef);
|
|
||||||
UASSERT(fs::PathExists("testplayer_load"));
|
|
||||||
|
|
||||||
RemotePlayer rplayer_load("testplayer_load", gamedef->idef());
|
|
||||||
PlayerSAO sao_load(NULL, 2, false);
|
|
||||||
std::ifstream is("testplayer_load", std::ios_base::binary);
|
|
||||||
UASSERT(is.good());
|
|
||||||
rplayer_load.deSerialize(is, "testplayer_load", &sao_load);
|
|
||||||
is.close();
|
|
||||||
|
|
||||||
UASSERT(strcmp(rplayer_load.getName(), "testplayer_load") == 0);
|
|
||||||
UASSERT(sao_load.getBreath() == 10);
|
|
||||||
UASSERT(sao_load.getHP() == 8);
|
|
||||||
UASSERT(sao_load.getYaw() == 0.1f);
|
|
||||||
UASSERT(sao_load.getPitch() == 0.6f);
|
|
||||||
UASSERT(sao_load.getBasePosition() == v3f(450.2f, -15.7f, 68.1f));
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue