mirror of
https://github.com/minetest/minetest.git
synced 2024-08-19 04:54:18 +02:00
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 empty
This commit is contained in:
parent
dda171d292
commit
29ab20c272
@ -134,6 +134,7 @@ LOCAL_SRC_FILES := \
|
||||
jni/src/convert_json.cpp \
|
||||
jni/src/craftdef.cpp \
|
||||
jni/src/database-dummy.cpp \
|
||||
jni/src/database-files.cpp \
|
||||
jni/src/database-sqlite3.cpp \
|
||||
jni/src/database.cpp \
|
||||
jni/src/debug.cpp \
|
||||
|
@ -279,6 +279,31 @@ core.register_chatcommand("auth_reload", {
|
||||
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", {
|
||||
params = "<X>,<Y>,<Z> | <to_name> | <name> <X>,<Y>,<Z> | <name> <to_name>",
|
||||
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.get_server_status()`: returns server status string
|
||||
* `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
|
||||
* `minetest.get_ban_list()`: returns the ban list (same as `minetest.get_ban_description("")`)
|
||||
|
@ -377,6 +377,7 @@ set(common_SRCS
|
||||
convert_json.cpp
|
||||
craftdef.cpp
|
||||
database-dummy.cpp
|
||||
database-files.cpp
|
||||
database-leveldb.cpp
|
||||
database-postgresql.cpp
|
||||
database-redis.cpp
|
||||
|
@ -770,7 +770,7 @@ void Client::initLocalMapSaving(const Address &address,
|
||||
|
||||
fs::CreateAllDirs(world_path);
|
||||
|
||||
m_localdb = new Database_SQLite3(world_path);
|
||||
m_localdb = new MapDatabaseSQLite3(world_path);
|
||||
m_localdb->beginSave();
|
||||
actionstream << "Local map saving started, map will be saved at '" << world_path << "'" << std::endl;
|
||||
}
|
||||
|
@ -49,7 +49,7 @@ class ClientMediaDownloader;
|
||||
struct MapDrawControl;
|
||||
class MtEventManager;
|
||||
struct PointedThing;
|
||||
class Database;
|
||||
class MapDatabase;
|
||||
class Minimap;
|
||||
struct MinimapMapblock;
|
||||
class Camera;
|
||||
@ -645,7 +645,7 @@ private:
|
||||
LocalClientState m_state;
|
||||
|
||||
// Used for saving server map to disk client-side
|
||||
Database *m_localdb;
|
||||
MapDatabase *m_localdb;
|
||||
IntervalLimiter m_localdb_save_interval;
|
||||
u16 m_cache_save_interval;
|
||||
|
||||
|
@ -764,9 +764,10 @@ bool LuaEntitySAO::collideWithObjects() const
|
||||
|
||||
// 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)),
|
||||
m_player(NULL),
|
||||
m_player(player_),
|
||||
m_peer_id(peer_id_),
|
||||
m_inventory(NULL),
|
||||
m_damage(0),
|
||||
@ -819,7 +820,7 @@ PlayerSAO::~PlayerSAO()
|
||||
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);
|
||||
m_player = player;
|
||||
|
@ -194,7 +194,7 @@ class RemotePlayer;
|
||||
class PlayerSAO : public UnitSAO
|
||||
{
|
||||
public:
|
||||
PlayerSAO(ServerEnvironment *env_, u16 peer_id_, bool is_singleplayer);
|
||||
PlayerSAO(ServerEnvironment *env_, RemotePlayer *player_, u16 peer_id_, bool is_singleplayer);
|
||||
~PlayerSAO();
|
||||
ActiveObjectType getType() const
|
||||
{ return ACTIVEOBJECT_TYPE_PLAYER; }
|
||||
@ -349,7 +349,7 @@ public:
|
||||
bool getCollisionBox(aabb3f *toset) const;
|
||||
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 getEyeOffset() const;
|
||||
|
@ -25,7 +25,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
#include "database.h"
|
||||
#include "irrlichttypes.h"
|
||||
|
||||
class Database_Dummy : public Database
|
||||
class Database_Dummy : public MapDatabase, public PlayerDatabase
|
||||
{
|
||||
public:
|
||||
bool saveBlock(const v3s16 &pos, const std::string &data);
|
||||
@ -33,6 +33,13 @@ public:
|
||||
bool deleteBlock(const v3s16 &pos);
|
||||
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:
|
||||
std::map<s64, std::string> m_database;
|
||||
};
|
||||
|
179
src/database-files.cpp
Normal file
179
src/database-files.cpp
Normal file
@ -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());
|
||||
}
|
||||
}
|
46
src/database-files.h
Normal file
46
src/database-files.h
Normal file
@ -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 "leveldb/db.h"
|
||||
|
||||
class Database_LevelDB : public Database
|
||||
class Database_LevelDB : public MapDatabase
|
||||
{
|
||||
public:
|
||||
Database_LevelDB(const std::string &savedir);
|
||||
@ -39,6 +39,8 @@ public:
|
||||
bool deleteBlock(const v3s16 &pos);
|
||||
void listAllLoadableBlocks(std::vector<v3s16> &dst);
|
||||
|
||||
void beginSave() {}
|
||||
void endSave() {}
|
||||
private:
|
||||
leveldb::DB *m_database;
|
||||
};
|
||||
|
@ -39,13 +39,15 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
#include "log.h"
|
||||
#include "exceptions.h"
|
||||
#include "settings.h"
|
||||
#include "content_sao.h"
|
||||
#include "remoteplayer.h"
|
||||
|
||||
Database_PostgreSQL::Database_PostgreSQL(const Settings &conf) :
|
||||
m_connect_string(""),
|
||||
Database_PostgreSQL::Database_PostgreSQL(const std::string &connect_string) :
|
||||
m_connect_string(connect_string),
|
||||
m_conn(NULL),
|
||||
m_pgversion(0)
|
||||
{
|
||||
if (!conf.getNoEx("pgsql_connection", m_connect_string)) {
|
||||
if (m_connect_string.empty()) {
|
||||
throw SettingNotFoundException(
|
||||
"Set pgsql_connection string in world.mt to "
|
||||
"use the postgresql backend\n"
|
||||
@ -57,8 +59,6 @@ Database_PostgreSQL::Database_PostgreSQL(const Settings &conf) :
|
||||
"DELETE rights on the database.\n"
|
||||
"Don't create mt_user as a SUPERUSER!");
|
||||
}
|
||||
|
||||
connectToDatabase();
|
||||
}
|
||||
|
||||
Database_PostgreSQL::~Database_PostgreSQL()
|
||||
@ -118,40 +118,6 @@ bool Database_PostgreSQL::initialized() const
|
||||
return (PQstatus(m_conn) == CONNECTION_OK);
|
||||
}
|
||||
|
||||
void Database_PostgreSQL::initStatements()
|
||||
{
|
||||
prepareStatement("read_block",
|
||||
"SELECT data FROM blocks "
|
||||
"WHERE posX = $1::int4 AND posY = $2::int4 AND "
|
||||
"posZ = $3::int4");
|
||||
|
||||
if (m_pgversion < 90500) {
|
||||
prepareStatement("write_block_insert",
|
||||
"INSERT INTO blocks (posX, posY, posZ, data) SELECT "
|
||||
"$1::int4, $2::int4, $3::int4, $4::bytea "
|
||||
"WHERE NOT EXISTS (SELECT true FROM blocks "
|
||||
"WHERE posX = $1::int4 AND posY = $2::int4 AND "
|
||||
"posZ = $3::int4)");
|
||||
|
||||
prepareStatement("write_block_update",
|
||||
"UPDATE blocks SET data = $4::bytea "
|
||||
"WHERE posX = $1::int4 AND posY = $2::int4 AND "
|
||||
"posZ = $3::int4");
|
||||
} else {
|
||||
prepareStatement("write_block",
|
||||
"INSERT INTO blocks (posX, posY, posZ, data) VALUES "
|
||||
"($1::int4, $2::int4, $3::int4, $4::bytea) "
|
||||
"ON CONFLICT ON CONSTRAINT blocks_pkey DO "
|
||||
"UPDATE SET data = $4::bytea");
|
||||
}
|
||||
|
||||
prepareStatement("delete_block", "DELETE FROM blocks WHERE "
|
||||
"posX = $1::int4 AND posY = $2::int4 AND posZ = $3::int4");
|
||||
|
||||
prepareStatement("list_all_loadable_blocks",
|
||||
"SELECT posX, posY, posZ FROM blocks");
|
||||
}
|
||||
|
||||
PGresult *Database_PostgreSQL::checkResults(PGresult *result, bool clear)
|
||||
{
|
||||
ExecStatusType statusType = PQresultStatus(result);
|
||||
@ -173,30 +139,21 @@ PGresult *Database_PostgreSQL::checkResults(PGresult *result, bool clear)
|
||||
return result;
|
||||
}
|
||||
|
||||
void Database_PostgreSQL::createDatabase()
|
||||
void Database_PostgreSQL::createTableIfNotExists(const std::string &table_name,
|
||||
const std::string &definition)
|
||||
{
|
||||
PGresult *result = checkResults(PQexec(m_conn,
|
||||
"SELECT relname FROM pg_class WHERE relname='blocks';"),
|
||||
false);
|
||||
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)) {
|
||||
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));
|
||||
checkResults(PQexec(m_conn, definition.c_str()));
|
||||
}
|
||||
|
||||
PQclear(result);
|
||||
|
||||
infostream << "PostgreSQL: Game Database was inited." << std::endl;
|
||||
}
|
||||
|
||||
|
||||
void Database_PostgreSQL::beginSave()
|
||||
{
|
||||
verifyDatabase();
|
||||
@ -208,14 +165,70 @@ void Database_PostgreSQL::endSave()
|
||||
checkResults(PQexec(m_conn, "COMMIT;"));
|
||||
}
|
||||
|
||||
bool Database_PostgreSQL::saveBlock(const v3s16 &pos,
|
||||
const std::string &data)
|
||||
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",
|
||||
"SELECT data FROM blocks "
|
||||
"WHERE posX = $1::int4 AND posY = $2::int4 AND "
|
||||
"posZ = $3::int4");
|
||||
|
||||
if (getPGVersion() < 90500) {
|
||||
prepareStatement("write_block_insert",
|
||||
"INSERT INTO blocks (posX, posY, posZ, data) SELECT "
|
||||
"$1::int4, $2::int4, $3::int4, $4::bytea "
|
||||
"WHERE NOT EXISTS (SELECT true FROM blocks "
|
||||
"WHERE posX = $1::int4 AND posY = $2::int4 AND "
|
||||
"posZ = $3::int4)");
|
||||
|
||||
prepareStatement("write_block_update",
|
||||
"UPDATE blocks SET data = $4::bytea "
|
||||
"WHERE posX = $1::int4 AND posY = $2::int4 AND "
|
||||
"posZ = $3::int4");
|
||||
} else {
|
||||
prepareStatement("write_block",
|
||||
"INSERT INTO blocks (posX, posY, posZ, data) VALUES "
|
||||
"($1::int4, $2::int4, $3::int4, $4::bytea) "
|
||||
"ON CONFLICT ON CONSTRAINT blocks_pkey DO "
|
||||
"UPDATE SET data = $4::bytea");
|
||||
}
|
||||
|
||||
prepareStatement("delete_block", "DELETE FROM blocks WHERE "
|
||||
"posX = $1::int4 AND posY = $2::int4 AND posZ = $3::int4");
|
||||
|
||||
prepareStatement("list_all_loadable_blocks",
|
||||
"SELECT posX, posY, posZ FROM blocks");
|
||||
}
|
||||
|
||||
bool MapDatabasePostgreSQL::saveBlock(const v3s16 &pos, const std::string &data)
|
||||
{
|
||||
// Verify if we don't overflow the platform integer with the mapblock size
|
||||
if (data.size() > INT_MAX) {
|
||||
errorstream << "Database_PostgreSQL::saveBlock: Data truncation! "
|
||||
<< "data.size() over 0xFFFF (== " << data.size()
|
||||
<< ")" << std::endl;
|
||||
<< "data.size() over 0xFFFFFFFF (== " << data.size()
|
||||
<< ")" << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -232,7 +245,7 @@ bool Database_PostgreSQL::saveBlock(const v3s16 &pos,
|
||||
};
|
||||
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_insert", ARRLEN(args), args, argLen, argFmt);
|
||||
} else {
|
||||
@ -241,8 +254,7 @@ bool Database_PostgreSQL::saveBlock(const v3s16 &pos,
|
||||
return true;
|
||||
}
|
||||
|
||||
void Database_PostgreSQL::loadBlock(const v3s16 &pos,
|
||||
std::string *block)
|
||||
void MapDatabasePostgreSQL::loadBlock(const v3s16 &pos, std::string *block)
|
||||
{
|
||||
verifyDatabase();
|
||||
|
||||
@ -256,19 +268,17 @@ void Database_PostgreSQL::loadBlock(const v3s16 &pos,
|
||||
const int argFmt[] = { 1, 1, 1 };
|
||||
|
||||
PGresult *results = execPrepared("read_block", ARRLEN(args), args,
|
||||
argLen, argFmt, false);
|
||||
argLen, argFmt, false);
|
||||
|
||||
*block = "";
|
||||
|
||||
if (PQntuples(results)) {
|
||||
*block = std::string(PQgetvalue(results, 0, 0),
|
||||
PQgetlength(results, 0, 0));
|
||||
}
|
||||
if (PQntuples(results))
|
||||
*block = std::string(PQgetvalue(results, 0, 0), PQgetlength(results, 0, 0));
|
||||
|
||||
PQclear(results);
|
||||
}
|
||||
|
||||
bool Database_PostgreSQL::deleteBlock(const v3s16 &pos)
|
||||
bool MapDatabasePostgreSQL::deleteBlock(const v3s16 &pos)
|
||||
{
|
||||
verifyDatabase();
|
||||
|
||||
@ -286,19 +296,339 @@ bool Database_PostgreSQL::deleteBlock(const v3s16 &pos)
|
||||
return true;
|
||||
}
|
||||
|
||||
void Database_PostgreSQL::listAllLoadableBlocks(std::vector<v3s16> &dst)
|
||||
void MapDatabasePostgreSQL::listAllLoadableBlocks(std::vector<v3s16> &dst)
|
||||
{
|
||||
verifyDatabase();
|
||||
|
||||
PGresult *results = execPrepared("list_all_loadable_blocks", 0,
|
||||
NULL, NULL, NULL, false, false);
|
||||
NULL, NULL, NULL, false, false);
|
||||
|
||||
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));
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
|
@ -27,55 +27,35 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
|
||||
class Settings;
|
||||
|
||||
class Database_PostgreSQL : public Database
|
||||
class Database_PostgreSQL: public Database
|
||||
{
|
||||
public:
|
||||
Database_PostgreSQL(const Settings &conf);
|
||||
Database_PostgreSQL(const std::string &connect_string);
|
||||
~Database_PostgreSQL();
|
||||
|
||||
void beginSave();
|
||||
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;
|
||||
|
||||
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
|
||||
inline int pg_to_int(PGresult *res, int row, int 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)
|
||||
{
|
||||
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
|
||||
std::string m_connect_string;
|
||||
PGconn *m_conn;
|
||||
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
|
||||
|
||||
|
@ -30,7 +30,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
|
||||
class Settings;
|
||||
|
||||
class Database_Redis : public Database
|
||||
class Database_Redis : public MapDatabase
|
||||
{
|
||||
public:
|
||||
Database_Redis(Settings &conf);
|
||||
|
@ -33,6 +33,8 @@ SQLite format specification:
|
||||
#include "settings.h"
|
||||
#include "porting.h"
|
||||
#include "util/string.h"
|
||||
#include "content_sao.h"
|
||||
#include "remoteplayer.h"
|
||||
|
||||
#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_savedir(savedir),
|
||||
m_database(NULL),
|
||||
m_stmt_read(NULL),
|
||||
m_stmt_write(NULL),
|
||||
m_stmt_list(NULL),
|
||||
m_stmt_delete(NULL),
|
||||
m_dbname(dbname),
|
||||
m_stmt_begin(NULL),
|
||||
m_stmt_end(NULL)
|
||||
{
|
||||
}
|
||||
|
||||
void Database_SQLite3::beginSave() {
|
||||
void Database_SQLite3::beginSave()
|
||||
{
|
||||
verifyDatabase();
|
||||
SQLRES(sqlite3_step(m_stmt_begin), SQLITE_DONE,
|
||||
"Failed to start SQLite3 transaction");
|
||||
sqlite3_reset(m_stmt_begin);
|
||||
}
|
||||
|
||||
void Database_SQLite3::endSave() {
|
||||
void Database_SQLite3::endSave()
|
||||
{
|
||||
verifyDatabase();
|
||||
SQLRES(sqlite3_step(m_stmt_end), SQLITE_DONE,
|
||||
"Failed to commit SQLite3 transaction");
|
||||
@ -142,7 +143,7 @@ void Database_SQLite3::openDatabase()
|
||||
{
|
||||
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
|
||||
|
||||
@ -170,6 +171,8 @@ void Database_SQLite3::openDatabase()
|
||||
+ itos(g_settings->getU16("sqlite_synchronous"));
|
||||
SQLOK(sqlite3_exec(m_database, query_str.c_str(), NULL, NULL, NULL),
|
||||
"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()
|
||||
@ -178,8 +181,61 @@ void Database_SQLite3::verifyDatabase()
|
||||
|
||||
openDatabase();
|
||||
|
||||
PREPARE_STATEMENT(begin, "BEGIN");
|
||||
PREPARE_STATEMENT(end, "COMMIT");
|
||||
PREPARE_STATEMENT(begin, "BEGIN;");
|
||||
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");
|
||||
#ifdef __ANDROID__
|
||||
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(list, "SELECT `pos` FROM `blocks`");
|
||||
|
||||
m_initialized = true;
|
||||
|
||||
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)),
|
||||
"Internal error: failed to bind query at " __FILE__ ":" TOSTRING(__LINE__));
|
||||
}
|
||||
|
||||
bool Database_SQLite3::deleteBlock(const v3s16 &pos)
|
||||
bool MapDatabaseSQLite3::deleteBlock(const v3s16 &pos)
|
||||
{
|
||||
verifyDatabase();
|
||||
|
||||
@ -216,7 +270,7 @@ bool Database_SQLite3::deleteBlock(const v3s16 &pos)
|
||||
return good;
|
||||
}
|
||||
|
||||
bool Database_SQLite3::saveBlock(const v3s16 &pos, const std::string &data)
|
||||
bool MapDatabaseSQLite3::saveBlock(const v3s16 &pos, const std::string &data)
|
||||
{
|
||||
verifyDatabase();
|
||||
|
||||
@ -243,7 +297,7 @@ bool Database_SQLite3::saveBlock(const v3s16 &pos, const std::string &data)
|
||||
return true;
|
||||
}
|
||||
|
||||
void Database_SQLite3::loadBlock(const v3s16 &pos, std::string *block)
|
||||
void MapDatabaseSQLite3::loadBlock(const v3s16 &pos, std::string *block)
|
||||
{
|
||||
verifyDatabase();
|
||||
|
||||
@ -264,37 +318,312 @@ void Database_SQLite3::loadBlock(const v3s16 &pos, std::string *block)
|
||||
sqlite3_reset(m_stmt_read);
|
||||
}
|
||||
|
||||
void Database_SQLite3::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 Database_SQLite3::listAllLoadableBlocks(std::vector<v3s16> &dst)
|
||||
void MapDatabaseSQLite3::listAllLoadableBlocks(std::vector<v3s16> &dst)
|
||||
{
|
||||
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)));
|
||||
}
|
||||
|
||||
sqlite3_reset(m_stmt_list);
|
||||
}
|
||||
|
||||
Database_SQLite3::~Database_SQLite3()
|
||||
{
|
||||
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)
|
||||
/*
|
||||
* Player Database
|
||||
*/
|
||||
|
||||
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
|
||||
#define DATABASE_SQLITE3_HEADER
|
||||
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
#include "database.h"
|
||||
#include "exceptions.h"
|
||||
|
||||
extern "C" {
|
||||
#include "sqlite3.h"
|
||||
@ -30,37 +32,97 @@ extern "C" {
|
||||
class Database_SQLite3 : public Database
|
||||
{
|
||||
public:
|
||||
Database_SQLite3(const std::string &savedir);
|
||||
~Database_SQLite3();
|
||||
virtual ~Database_SQLite3();
|
||||
|
||||
void beginSave();
|
||||
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; }
|
||||
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
|
||||
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;
|
||||
|
||||
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_end;
|
||||
|
||||
@ -69,4 +131,66 @@ private:
|
||||
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
|
||||
|
@ -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 +
|
||||
(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;
|
||||
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
|
||||
{
|
||||
public:
|
||||
virtual ~Database() {}
|
||||
virtual void beginSave() = 0;
|
||||
virtual void endSave() = 0;
|
||||
virtual bool initialized() const { return true; }
|
||||
};
|
||||
|
||||
virtual void beginSave() {}
|
||||
virtual void endSave() {}
|
||||
class MapDatabase : public Database
|
||||
{
|
||||
public:
|
||||
virtual ~MapDatabase() {}
|
||||
|
||||
virtual bool saveBlock(const v3s16 &pos, const std::string &data) = 0;
|
||||
virtual void loadBlock(const v3s16 &pos, std::string *block) = 0;
|
||||
@ -42,8 +47,19 @@ public:
|
||||
static v3s16 getIntegerAsBlock(s64 i);
|
||||
|
||||
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
|
||||
|
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
|
||||
#ifndef SERVER
|
||||
#include "client/clientlauncher.h"
|
||||
|
||||
#endif
|
||||
|
||||
#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 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)"))));
|
||||
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)"))));
|
||||
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,
|
||||
_("Feature an interactive terminal (Only works when using minetestserver or with --server)"))));
|
||||
#ifndef SERVER
|
||||
@ -332,7 +335,7 @@ static void print_allowed_options(const OptionList &allowed_options)
|
||||
if (i->second.type != VALUETYPE_FLAG)
|
||||
os1 << _(" <value>");
|
||||
|
||||
std::cout << padStringRight(os1.str(), 24);
|
||||
std::cout << padStringRight(os1.str(), 30);
|
||||
|
||||
if (i->second.help != NULL)
|
||||
std::cout << i->second.help;
|
||||
@ -828,7 +831,9 @@ static bool run_dedicated_server(const GameParams &game_params, const Settings &
|
||||
|
||||
// Database migration
|
||||
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 USE_CURSES
|
||||
@ -912,7 +917,7 @@ static bool run_dedicated_server(const GameParams &game_params, const Settings &
|
||||
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");
|
||||
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;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!world_mt.exists("backend")) {
|
||||
errorstream << "Please specify your current backend in world.mt:"
|
||||
<< std::endl
|
||||
<< " backend = {sqlite3|leveldb|redis|dummy}"
|
||||
<< " backend = {sqlite3|leveldb|redis|dummy|postgresql}"
|
||||
<< std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string backend = world_mt.get("backend");
|
||||
if (backend == migrate_to) {
|
||||
errorstream << "Cannot migrate: new backend is same"
|
||||
<< " as the old one" << std::endl;
|
||||
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);
|
||||
|
||||
u32 count = 0;
|
||||
@ -976,4 +984,3 @@ static bool migrate_database(const GameParams &game_params, const Settings &cmd_
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
13
src/map.cpp
13
src/map.cpp
@ -2286,13 +2286,13 @@ bool ServerMap::loadSectorFull(v2s16 p2d)
|
||||
}
|
||||
#endif
|
||||
|
||||
Database *ServerMap::createDatabase(
|
||||
MapDatabase *ServerMap::createDatabase(
|
||||
const std::string &name,
|
||||
const std::string &savedir,
|
||||
Settings &conf)
|
||||
{
|
||||
if (name == "sqlite3")
|
||||
return new Database_SQLite3(savedir);
|
||||
return new MapDatabaseSQLite3(savedir);
|
||||
if (name == "dummy")
|
||||
return new Database_Dummy();
|
||||
#if USE_LEVELDB
|
||||
@ -2304,8 +2304,11 @@ Database *ServerMap::createDatabase(
|
||||
return new Database_Redis(conf);
|
||||
#endif
|
||||
#if USE_POSTGRESQL
|
||||
else if (name == "postgresql")
|
||||
return new Database_PostgreSQL(conf);
|
||||
else if (name == "postgresql") {
|
||||
std::string connect_string = "";
|
||||
conf.getNoEx("pgsql_connection", connect_string);
|
||||
return new MapDatabasePostgreSQL(connect_string);
|
||||
}
|
||||
#endif
|
||||
else
|
||||
throw BaseException(std::string("Database backend ") + name + " not supported.");
|
||||
@ -2326,7 +2329,7 @@ bool ServerMap::saveBlock(MapBlock *block)
|
||||
return saveBlock(block, dbase);
|
||||
}
|
||||
|
||||
bool ServerMap::saveBlock(MapBlock *block, Database *db)
|
||||
bool ServerMap::saveBlock(MapBlock *block, MapDatabase *db)
|
||||
{
|
||||
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"
|
||||
|
||||
class Settings;
|
||||
class Database;
|
||||
class MapDatabase;
|
||||
class ClientMap;
|
||||
class MapSector;
|
||||
class ServerMapSector;
|
||||
@ -430,7 +430,7 @@ public:
|
||||
/*
|
||||
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
|
||||
bool loadFromFolders();
|
||||
@ -458,7 +458,7 @@ public:
|
||||
bool loadSectorMeta(v2s16 p2d);
|
||||
|
||||
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.
|
||||
void loadBlock(const std::string §ordir, const std::string &blockfile,
|
||||
MapSector *sector, bool save_after_load=false);
|
||||
@ -510,7 +510,7 @@ private:
|
||||
This is reset to false when written on disk.
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
assert(m_sao);
|
||||
|
@ -37,11 +37,11 @@ enum RemotePlayerChatResult
|
||||
*/
|
||||
class RemotePlayer : public Player
|
||||
{
|
||||
friend class PlayerDatabaseFiles;
|
||||
public:
|
||||
RemotePlayer(const char *name, IItemDefManager *idef);
|
||||
virtual ~RemotePlayer() {}
|
||||
|
||||
void save(std::string savedir, IGameDef *gamedef);
|
||||
void deSerialize(std::istream &is, const std::string &playername, PlayerSAO *sao);
|
||||
|
||||
PlayerSAO *getPlayerSAO() { return m_sao; }
|
||||
|
@ -334,6 +334,22 @@ int ModApiServer::l_kick_player(lua_State *L)
|
||||
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()
|
||||
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(ban_player);
|
||||
API_FCT(kick_player);
|
||||
API_FCT(remove_player);
|
||||
API_FCT(unban_player_or_ip);
|
||||
API_FCT(notify_authentication_modified);
|
||||
|
||||
|
@ -92,6 +92,9 @@ private:
|
||||
// kick_player(name, [message]) -> success
|
||||
static int l_kick_player(lua_State *L);
|
||||
|
||||
// remove_player(name)
|
||||
static int l_remove_player(lua_State *L);
|
||||
|
||||
// notify_authentication_modified(name)
|
||||
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/sha1.h"
|
||||
#include "util/hex.h"
|
||||
#include "database.h"
|
||||
|
||||
class ClientNotFoundException : public BaseException
|
||||
{
|
||||
@ -2618,9 +2619,8 @@ void Server::RespawnPlayer(u16 peer_id)
|
||||
|
||||
bool repositioned = m_script->on_respawnplayer(playersao);
|
||||
if (!repositioned) {
|
||||
v3f pos = findSpawnPos();
|
||||
// setPos will send the new position to client
|
||||
playersao->setPos(pos);
|
||||
playersao->setPos(findSpawnPos());
|
||||
}
|
||||
|
||||
SendPlayerHP(peer_id);
|
||||
@ -3442,8 +3442,8 @@ v3f Server::findSpawnPos()
|
||||
s32 range = 1 + i;
|
||||
// We're going to try to throw the player to this position
|
||||
v2s16 nodepos2d = v2s16(
|
||||
-range + (myrand() % (range * 2)),
|
||||
-range + (myrand() % (range * 2)));
|
||||
-range + (myrand() % (range * 2)),
|
||||
-range + (myrand() % (range * 2)));
|
||||
|
||||
// Get spawn level at point
|
||||
s16 spawn_level = m_emerge->getSpawnLevelAtPoint(nodepos2d);
|
||||
@ -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)
|
||||
{
|
||||
bool newplayer = false;
|
||||
|
||||
/*
|
||||
Try to get an existing player
|
||||
*/
|
||||
@ -3538,45 +3536,19 @@ PlayerSAO* Server::emergePlayer(const char *name, u16 peer_id, u16 proto_version
|
||||
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) {
|
||||
newplayer = true;
|
||||
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());
|
||||
}
|
||||
player = new RemotePlayer(name, idef());
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
/* Clean up old HUD elements from previous sessions */
|
||||
player->clearHud();
|
||||
|
||||
/* Add object to environment */
|
||||
m_env->addActiveObject(playersao);
|
||||
|
||||
/* Run scripts */
|
||||
if (newplayer) {
|
||||
m_script->on_newplayer(playersao);
|
||||
|
@ -306,6 +306,7 @@ public:
|
||||
bool showFormspec(const char *name, const std::string &formspec, const std::string &formname);
|
||||
Map & getMap() { return m_env->getMap(); }
|
||||
ServerEnvironment & getEnv() { return *m_env; }
|
||||
v3f findSpawnPos();
|
||||
|
||||
u32 hudAdd(RemotePlayer *player, HudElement *element);
|
||||
bool hudRemove(RemotePlayer *player, u32 id);
|
||||
@ -472,8 +473,6 @@ private:
|
||||
RemotePlayer *player = NULL);
|
||||
void handleAdminChat(const ChatEventChat *evt);
|
||||
|
||||
v3f findSpawnPos();
|
||||
|
||||
// When called, connection mutex should be locked
|
||||
RemoteClient* getClient(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 "threading/mutex_auto_lock.h"
|
||||
#include "filesys.h"
|
||||
#include "gameparams.h"
|
||||
#include "database-dummy.h"
|
||||
#include "database-files.h"
|
||||
#include "database-sqlite3.h"
|
||||
#if USE_POSTGRESQL
|
||||
#include "database-postgresql.h"
|
||||
#endif
|
||||
|
||||
#define LBM_NAME_ALLOWED_CHARS "abcdefghijklmnopqrstuvwxyz0123456789_:"
|
||||
|
||||
@ -365,8 +372,30 @@ ServerEnvironment::ServerEnvironment(ServerMap *map,
|
||||
m_game_time_fraction_counter(0),
|
||||
m_last_clear_objects_time(0),
|
||||
m_recommended_send_interval(0.1),
|
||||
m_max_lag_estimate(0.1)
|
||||
m_max_lag_estimate(0.1),
|
||||
m_player_database(NULL)
|
||||
{
|
||||
// Determine which database backend to use
|
||||
std::string conf_path = path_world + DIR_DELIM + "world.mt";
|
||||
Settings conf;
|
||||
bool succeeded = conf.readConfigFile(conf_path.c_str());
|
||||
if (!succeeded || !conf.exists("player_backend")) {
|
||||
// fall back to files
|
||||
conf.set("player_backend", "files");
|
||||
warningstream << "/!\\ You are using old player file backend. "
|
||||
<< "This backend is deprecated and will be removed in next release /!\\"
|
||||
<< std::endl << "Switching to SQLite3 or PostgreSQL is advised, "
|
||||
<< "please read http://wiki.minetest.net/Database_backends." << std::endl;
|
||||
|
||||
if (!conf.updateConfigFile(conf_path.c_str())) {
|
||||
errorstream << "ServerEnvironment::ServerEnvironment(): "
|
||||
<< "Failed to update world.mt!" << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
std::string name = "";
|
||||
conf.getNoEx("player_backend", name);
|
||||
m_player_database = openPlayerDatabase(name, path_world, conf);
|
||||
}
|
||||
|
||||
ServerEnvironment::~ServerEnvironment()
|
||||
@ -392,6 +421,8 @@ ServerEnvironment::~ServerEnvironment()
|
||||
i != m_players.end(); ++i) {
|
||||
delete (*i);
|
||||
}
|
||||
|
||||
delete m_player_database;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
float distance = pos1.getDistanceFrom(pos2);
|
||||
@ -495,7 +531,7 @@ void ServerEnvironment::kickAllPlayers(AccessDeniedCode reason,
|
||||
|
||||
void ServerEnvironment::saveLoadedPlayers()
|
||||
{
|
||||
std::string players_path = m_path_world + DIR_DELIM "players";
|
||||
std::string players_path = m_path_world + DIR_DELIM + "players";
|
||||
fs::CreateDir(players_path);
|
||||
|
||||
for (std::vector<RemotePlayer *>::iterator it = m_players.begin();
|
||||
@ -503,63 +539,63 @@ void ServerEnvironment::saveLoadedPlayers()
|
||||
++it) {
|
||||
if ((*it)->checkModified() ||
|
||||
((*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)
|
||||
{
|
||||
std::string players_path = m_path_world + DIR_DELIM "players";
|
||||
fs::CreateDir(players_path);
|
||||
|
||||
player->save(players_path, m_server);
|
||||
try {
|
||||
m_player_database->savePlayer(player);
|
||||
} catch (DatabaseException &e) {
|
||||
errorstream << "Failed to save player " << player->getName() << " exception: "
|
||||
<< e.what() << std::endl;
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
RemotePlayer *ServerEnvironment::loadPlayer(const std::string &playername, PlayerSAO *sao)
|
||||
PlayerSAO *ServerEnvironment::loadPlayer(RemotePlayer *player, bool *new_player,
|
||||
u16 peer_id, bool is_singleplayer)
|
||||
{
|
||||
bool newplayer = false;
|
||||
bool found = false;
|
||||
std::string players_path = m_path_world + DIR_DELIM "players" DIR_DELIM;
|
||||
std::string path = players_path + playername;
|
||||
PlayerSAO *playersao = new PlayerSAO(this, player, peer_id, is_singleplayer);
|
||||
// Create player if it doesn't exist
|
||||
if (!m_player_database->loadPlayer(player, playersao)) {
|
||||
*new_player = true;
|
||||
// Set player position
|
||||
infostream << "Server: Finding spawn place for player \""
|
||||
<< player->getName() << "\"" << std::endl;
|
||||
playersao->setBasePosition(m_server->findSpawnPos());
|
||||
|
||||
RemotePlayer *player = getPlayer(playername.c_str());
|
||||
if (!player) {
|
||||
player = new RemotePlayer("", m_server->idef());
|
||||
newplayer = true;
|
||||
}
|
||||
|
||||
for (u32 i = 0; i < PLAYER_FILE_ALTERNATE_TRIES; i++) {
|
||||
//// Open file and deserialize
|
||||
std::ifstream is(path.c_str(), std::ios_base::binary);
|
||||
if (!is.good())
|
||||
continue;
|
||||
|
||||
player->deSerialize(is, path, sao);
|
||||
is.close();
|
||||
|
||||
if (player->getName() == playername) {
|
||||
found = true;
|
||||
break;
|
||||
// Make sure the player is saved
|
||||
player->setModified(true);
|
||||
} else {
|
||||
// If the player exists, ensure that they respawn inside legal bounds
|
||||
// This fixes an assert crash when the player can't be added
|
||||
// to the environment
|
||||
if (objectpos_over_limit(playersao->getBasePosition())) {
|
||||
actionstream << "Respawn position for player \""
|
||||
<< player->getName() << "\" outside limits, resetting" << std::endl;
|
||||
playersao->setBasePosition(m_server->findSpawnPos());
|
||||
}
|
||||
|
||||
path = players_path + playername + itos(i);
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
infostream << "Player file for player " << playername
|
||||
<< " not found" << std::endl;
|
||||
if (newplayer)
|
||||
delete player;
|
||||
// Add player to environment
|
||||
addPlayer(player);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
/* Clean up old HUD elements from previous sessions */
|
||||
player->clearHud();
|
||||
|
||||
if (newplayer) {
|
||||
addPlayer(player);
|
||||
}
|
||||
player->setModified(false);
|
||||
return player;
|
||||
/* Add object to environment */
|
||||
addActiveObject(playersao);
|
||||
|
||||
return playersao;
|
||||
}
|
||||
|
||||
void ServerEnvironment::saveMeta()
|
||||
@ -2173,3 +2209,111 @@ void ServerEnvironment::deactivateFarObjects(bool _force_delete)
|
||||
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 ServerMap;
|
||||
struct GameParams;
|
||||
class RemotePlayer;
|
||||
class PlayerDatabase;
|
||||
class PlayerSAO;
|
||||
class ServerEnvironment;
|
||||
class ActiveBlockModifier;
|
||||
@ -217,9 +219,11 @@ public:
|
||||
// Save players
|
||||
void saveLoadedPlayers();
|
||||
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 removePlayer(RemotePlayer *player);
|
||||
bool removePlayerFromDatabase(const std::string &name);
|
||||
|
||||
/*
|
||||
Save and load time of day and game timer
|
||||
@ -334,8 +338,13 @@ public:
|
||||
|
||||
RemotePlayer *getPlayer(const u16 peer_id);
|
||||
RemotePlayer *getPlayer(const char* name);
|
||||
|
||||
static bool migratePlayersDatabase(const GameParams &game_params,
|
||||
const Settings &cmd_args);
|
||||
private:
|
||||
|
||||
static PlayerDatabase *openPlayerDatabase(const std::string &name,
|
||||
const std::string &savedir, const Settings &conf);
|
||||
/*
|
||||
Internal ActiveObject interface
|
||||
-------------------------------------------
|
||||
@ -419,6 +428,8 @@ private:
|
||||
// peer_ids in here should be unique, except that there may be many 0s
|
||||
std::vector<RemotePlayer*> m_players;
|
||||
|
||||
PlayerDatabase *m_player_database;
|
||||
|
||||
// Particles
|
||||
IntervalLimiter m_particle_management_interval;
|
||||
UNORDERED_MAP<u32, float> m_particle_spawners;
|
||||
|
@ -31,59 +31,10 @@ public:
|
||||
const char *getName() { return "TestPlayer"; }
|
||||
|
||||
void runTests(IGameDef *gamedef);
|
||||
|
||||
void testSave(IGameDef *gamedef);
|
||||
void testLoad(IGameDef *gamedef);
|
||||
};
|
||||
|
||||
static TestPlayer g_test_instance;
|
||||
|
||||
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
Block a user