From 52ba1f867e5edb579a59a44fbb8286d4f1e54931 Mon Sep 17 00:00:00 2001 From: Loic Blot Date: Sun, 1 Jan 2017 16:13:01 +0100 Subject: Breath cheat fix: server side Breath is now handled server side. Changing this behaviour required some modifications to core: * Ignore TOSERVER_BREATH package, marking it as obsolete * Clients doesn't send the breath to server anymore * Use PlayerSAO pointer instead of peer_id in Server::SendPlayerBreath to prevent a useless lookup (little perf gain) * drop a useless static_cast in emergePlayer --- src/remoteplayer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/remoteplayer.cpp') diff --git a/src/remoteplayer.cpp b/src/remoteplayer.cpp index 67ab89113..18bfa1030 100644 --- a/src/remoteplayer.cpp +++ b/src/remoteplayer.cpp @@ -148,7 +148,7 @@ void RemotePlayer::deSerialize(std::istream &is, const std::string &playername, } catch (SettingNotFoundException &e) {} try { - sao->setBreath(args.getS32("breath")); + sao->setBreath(args.getS32("breath"), false); } catch (SettingNotFoundException &e) {} } -- cgit v1.2.3 From b7a98e98500402c3bbdb6d56d0fe42b4f5b3cedb Mon Sep 17 00:00:00 2001 From: Loïc Blot Date: Fri, 27 Jan 2017 08:59:30 +0100 Subject: Implement player attribute backend (#4155) * This backend permit mods to store extra players attributes to a common interface. * Add the obj:set_attribute(attr, value) Lua call * Add the obj:get_attribute(attr) Lua call Examples: * player:set_attribute("home:home", "10,25,-78") * player:get_attribute("default:mana") Attributes are saved as a json in the player file in extended_attributes key They are saved only if a modification on the attributes occurs and loaded when emergePlayer is called (they are attached to PlayerSAO). --- doc/lua_api.txt | 2 ++ src/content_sao.cpp | 1 + src/content_sao.h | 37 +++++++++++++++++++++++++++++++++++++ src/remoteplayer.cpp | 38 +++++++++++++++++++++++++++++++++++--- src/remoteplayer.h | 5 ++++- src/script/lua_api/l_object.cpp | 41 +++++++++++++++++++++++++++++++++++++++++ src/script/lua_api/l_object.h | 6 ++++++ src/server.h | 1 - src/serverenvironment.cpp | 3 ++- 9 files changed, 128 insertions(+), 6 deletions(-) (limited to 'src/remoteplayer.cpp') diff --git a/doc/lua_api.txt b/doc/lua_api.txt index 62a7b81f7..ee7d57c2f 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -2899,6 +2899,8 @@ This is basically a reference to a C++ `ServerActiveObject` * `0`: player is drowning, * `1`-`10`: remaining number of bubbles * `11`: bubbles bar is not shown +* `set_attribute(attribute, value)`: sets an extra attribute with value on player +* `get_attribute(attribute)`: returns value for extra attribute. Returns nil if no attribute found. * `set_inventory_formspec(formspec)` * Redefine player's inventory form * Should usually be called in on_joinplayer diff --git a/src/content_sao.cpp b/src/content_sao.cpp index bb62aea7d..35133490e 100644 --- a/src/content_sao.cpp +++ b/src/content_sao.cpp @@ -791,6 +791,7 @@ PlayerSAO::PlayerSAO(ServerEnvironment *env_, u16 peer_id_, bool is_singleplayer m_pitch(0), m_fov(0), m_wanted_range(0), + m_extended_attributes_modified(false), // public m_physics_override_speed(1), m_physics_override_jump(1), diff --git a/src/content_sao.h b/src/content_sao.h index c3674fa2d..bbf244742 100644 --- a/src/content_sao.h +++ b/src/content_sao.h @@ -180,6 +180,7 @@ public: } }; +typedef UNORDERED_MAP PlayerAttributes; class RemotePlayer; class PlayerSAO : public UnitSAO @@ -249,6 +250,39 @@ public: int getWieldIndex() const; void setWieldIndex(int i); + /* + Modding interface + */ + inline void setExtendedAttribute(const std::string &attr, const std::string &value) + { + m_extra_attributes[attr] = value; + m_extended_attributes_modified = true; + } + + inline bool getExtendedAttribute(const std::string &attr, std::string *value) + { + if (m_extra_attributes.find(attr) == m_extra_attributes.end()) + return false; + + *value = m_extra_attributes[attr]; + return true; + } + + inline const PlayerAttributes &getExtendedAttributes() + { + return m_extra_attributes; + } + + inline bool extendedAttributesModified() const + { + return m_extended_attributes_modified; + } + + inline void setExtendedAttributeModified(bool v) + { + m_extended_attributes_modified = v; + } + /* PlayerSAO-specific */ @@ -343,6 +377,9 @@ private: f32 m_pitch; f32 m_fov; s16 m_wanted_range; + + PlayerAttributes m_extra_attributes; + bool m_extended_attributes_modified; public: float m_physics_override_speed; float m_physics_override_jump; diff --git a/src/remoteplayer.cpp b/src/remoteplayer.cpp index 18bfa1030..6853ad6d9 100644 --- a/src/remoteplayer.cpp +++ b/src/remoteplayer.cpp @@ -19,13 +19,14 @@ with this program; if not, write to the Free Software Foundation, Inc., */ #include "remoteplayer.h" +#include #include "content_sao.h" #include "filesys.h" #include "gamedef.h" #include "porting.h" // strlcpy +#include "server.h" #include "settings.h" - /* RemotePlayer */ @@ -112,9 +113,23 @@ void RemotePlayer::save(std::string savedir, IGameDef *gamedef) } infostream << "Didn't find free file for player " << m_name << std::endl; - return; } +void RemotePlayer::serializeExtraAttributes(std::string &output) +{ + assert(m_sao); + Json::Value json_root; + const PlayerAttributes &attrs = m_sao->getExtendedAttributes(); + for (PlayerAttributes::const_iterator it = attrs.begin(); it != attrs.end(); ++it) { + json_root[(*it).first] = (*it).second; + } + + Json::FastWriter writer; + output = writer.write(json_root); + m_sao->setExtendedAttributeModified(false); +} + + void RemotePlayer::deSerialize(std::istream &is, const std::string &playername, PlayerSAO *sao) { @@ -150,6 +165,20 @@ void RemotePlayer::deSerialize(std::istream &is, const std::string &playername, try { sao->setBreath(args.getS32("breath"), false); } catch (SettingNotFoundException &e) {} + + try { + std::string extended_attributes = args.get("extended_attributes"); + Json::Reader reader; + Json::Value attr_root; + reader.parse(extended_attributes, attr_root); + + const Json::Value::Members attr_list = attr_root.getMemberNames(); + for (Json::Value::Members::const_iterator it = attr_list.begin(); + it != attr_list.end(); ++it) { + Json::Value attr_value = attr_root[*it]; + sao->setExtendedAttribute(*it, attr_value.asString()); + } + } catch (SettingNotFoundException &e) {} } inventory.deSerialize(is); @@ -175,7 +204,6 @@ void RemotePlayer::serialize(std::ostream &os) Settings args; args.setS32("version", 1); args.set("name", m_name); - //args.set("password", m_password); // This should not happen assert(m_sao); @@ -185,6 +213,10 @@ void RemotePlayer::serialize(std::ostream &os) args.setFloat("yaw", m_sao->getYaw()); args.setS32("breath", m_sao->getBreath()); + std::string extended_attrs = ""; + serializeExtraAttributes(extended_attrs); + args.set("extended_attributes", extended_attrs); + args.writeLines(os); os<<"PlayerArgsEnd\n"; diff --git a/src/remoteplayer.h b/src/remoteplayer.h index 61b5a23de..f44fb9332 100644 --- a/src/remoteplayer.h +++ b/src/remoteplayer.h @@ -25,11 +25,13 @@ with this program; if not, write to the Free Software Foundation, Inc., class PlayerSAO; -enum RemotePlayerChatResult { +enum RemotePlayerChatResult +{ RPLAYER_CHATRESULT_OK, RPLAYER_CHATRESULT_FLOODING, RPLAYER_CHATRESULT_KICK, }; + /* Player on the server */ @@ -135,6 +137,7 @@ private: deSerialize stops reading exactly at the right point. */ void serialize(std::ostream &os); + void serializeExtraAttributes(std::string &output); PlayerSAO *m_sao; bool m_dirty; diff --git a/src/script/lua_api/l_object.cpp b/src/script/lua_api/l_object.cpp index be4451704..9352812ab 100644 --- a/src/script/lua_api/l_object.cpp +++ b/src/script/lua_api/l_object.cpp @@ -1181,6 +1181,45 @@ int ObjectRef::l_get_breath(lua_State *L) return 1; } +// set_attribute(self, attribute, value) +int ObjectRef::l_set_attribute(lua_State *L) +{ + ObjectRef *ref = checkobject(L, 1); + PlayerSAO* co = getplayersao(ref); + if (co == NULL) { + return 0; + } + + std::string attr = luaL_checkstring(L, 2); + std::string value = luaL_checkstring(L, 3); + + if (co->getType() == ACTIVEOBJECT_TYPE_PLAYER) { + co->setExtendedAttribute(attr, value); + } + return 1; +} + +// get_attribute(self, attribute) +int ObjectRef::l_get_attribute(lua_State *L) +{ + ObjectRef *ref = checkobject(L, 1); + PlayerSAO* co = getplayersao(ref); + if (co == NULL) { + return 0; + } + + std::string attr = luaL_checkstring(L, 2); + + std::string value = ""; + if (co->getExtendedAttribute(attr, &value)) { + lua_pushstring(L, value.c_str()); + return 1; + } + + return 0; +} + + // set_inventory_formspec(self, formspec) int ObjectRef::l_set_inventory_formspec(lua_State *L) { @@ -1839,6 +1878,8 @@ const luaL_reg ObjectRef::methods[] = { luamethod(ObjectRef, set_look_pitch), luamethod(ObjectRef, get_breath), luamethod(ObjectRef, set_breath), + luamethod(ObjectRef, get_attribute), + luamethod(ObjectRef, set_attribute), luamethod(ObjectRef, set_inventory_formspec), luamethod(ObjectRef, get_inventory_formspec), luamethod(ObjectRef, get_player_control), diff --git a/src/script/lua_api/l_object.h b/src/script/lua_api/l_object.h index 96d0abae8..2c9aa559a 100644 --- a/src/script/lua_api/l_object.h +++ b/src/script/lua_api/l_object.h @@ -226,6 +226,12 @@ private: // get_breath(self, breath) static int l_get_breath(lua_State *L); + // set_attribute(self, attribute, value) + static int l_set_attribute(lua_State *L); + + // get_attribute(self, attribute) + static int l_get_attribute(lua_State *L); + // set_inventory_formspec(self, formspec) static int l_set_inventory_formspec(lua_State *L); diff --git a/src/server.h b/src/server.h index e5121bdc3..8f553ce38 100644 --- a/src/server.h +++ b/src/server.h @@ -576,7 +576,6 @@ private: float m_time_of_day_send_timer; // Uptime of server in seconds MutexedVariable m_uptime; - /* Client interface */ diff --git a/src/serverenvironment.cpp b/src/serverenvironment.cpp index 01dc3ff10..7a5cfafd6 100644 --- a/src/serverenvironment.cpp +++ b/src/serverenvironment.cpp @@ -500,7 +500,8 @@ void ServerEnvironment::saveLoadedPlayers() for (std::vector::iterator it = m_players.begin(); it != m_players.end(); ++it) { - if ((*it)->checkModified()) { + if ((*it)->checkModified() || + ((*it)->getPlayerSAO() && (*it)->getPlayerSAO()->extendedAttributesModified())) { (*it)->save(players_path, m_server); } } -- cgit v1.2.3 From ef6feca501fcf0d5a1fd2021f1d4df96a4533f65 Mon Sep 17 00:00:00 2001 From: Loïc Blot Date: Wed, 8 Feb 2017 00:15:55 +0100 Subject: Add ModMetadata API (#5131) * mod can create a ModMetadata object where store its values and retrieve it. * Modmetadata object can only be fetched at mod loading * Save when modified using same time as map interval or at server stop * add helper function to get mod storage path * ModMetadata has exactly same calls than all every other Metadata --- doc/lua_api.txt | 10 ++- src/metadata.cpp | 20 +++++- src/metadata.h | 2 +- src/mods.cpp | 80 ++++++++++++++++++++- src/mods.h | 21 ++++++ src/remoteplayer.cpp | 2 +- src/script/lua_api/CMakeLists.txt | 1 + src/script/lua_api/l_storage.cpp | 143 ++++++++++++++++++++++++++++++++++++++ src/script/lua_api/l_storage.h | 62 +++++++++++++++++ src/script/scripting_game.cpp | 3 + src/server.cpp | 43 +++++++++++- src/server.h | 9 ++- 12 files changed, 384 insertions(+), 12 deletions(-) create mode 100644 src/script/lua_api/l_storage.cpp create mode 100644 src/script/lua_api/l_storage.h (limited to 'src/remoteplayer.cpp') diff --git a/doc/lua_api.txt b/doc/lua_api.txt index dd20ae904..4774e8a5a 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -2629,6 +2629,11 @@ These functions return the leftover itemstack. * `HTTPApiTable.fetch_async_get(handle)`: returns HTTPRequestResult * Return response data for given asynchronous HTTP request +### Storage API: +* `minetest.get_mod_storage()`: + * returns reference to mod private `StorageRef` + * must be called during mod load time + ### Misc. * `minetest.get_connected_players()`: returns list of `ObjectRefs` * `minetest.player_exists(name)`: boolean, whether player exists (regardless of online status) @@ -2791,7 +2796,7 @@ Class reference --------------- ### `MetaDataRef` -See `NodeMetaRef` and `ItemStackMetaRef`. +See `StorageRef`, `NodeMetaRef` and `ItemStackMetaRef`. #### Methods * `set_string(name, value)` @@ -2845,6 +2850,9 @@ Can be gotten via `minetest.get_node_timer(pos)`. * `is_started()`: returns boolean state of timer * returns `true` if timer is started, otherwise `false` +### `StorageRef` +This is basically a reference to a C++ `ModMetadata` + ### `ObjectRef` Moving things in the game are generally these. diff --git a/src/metadata.cpp b/src/metadata.cpp index 3cc45f919..2ce9af5af 100644 --- a/src/metadata.cpp +++ b/src/metadata.cpp @@ -76,13 +76,27 @@ const std::string &Metadata::getString(const std::string &name, return resolveString(it->second, recursion); } -void Metadata::setString(const std::string &name, const std::string &var) +/** + * Sets var to name key in the metadata storage + * + * @param name + * @param var + * @return true if key-value pair is created or changed + */ +bool Metadata::setString(const std::string &name, const std::string &var) { if (var.empty()) { m_stringvars.erase(name); - } else { - m_stringvars[name] = var; + return true; + } + + StringMap::iterator it = m_stringvars.find(name); + if (it != m_stringvars.end() && it->second == var) { + return false; } + + m_stringvars[name] = var; + return true; } const std::string &Metadata::resolveString(const std::string &str, diff --git a/src/metadata.h b/src/metadata.h index 4bb3c2ee7..a8270b4c4 100644 --- a/src/metadata.h +++ b/src/metadata.h @@ -46,7 +46,7 @@ public: size_t size() const; bool contains(const std::string &name) const; const std::string &getString(const std::string &name, u16 recursion = 0) const; - void setString(const std::string &name, const std::string &var); + virtual bool setString(const std::string &name, const std::string &var); const StringMap &getStrings() const { return m_stringvars; diff --git a/src/mods.cpp b/src/mods.cpp index 1b1bdb07b..bae9a42d3 100644 --- a/src/mods.cpp +++ b/src/mods.cpp @@ -21,13 +21,10 @@ with this program; if not, write to the Free Software Foundation, Inc., #include #include "mods.h" #include "filesys.h" -#include "util/strfnd.h" #include "log.h" #include "subgame.h" #include "settings.h" -#include "util/strfnd.h" #include "convert_json.h" -#include "exceptions.h" static bool parseDependsLine(std::istream &is, std::string &dep, std::set &symbols) @@ -356,3 +353,80 @@ Json::Value getModstoreUrl(std::string url) } #endif + +ModMetadata::ModMetadata(const std::string &mod_name): + m_mod_name(mod_name), + m_modified(false) +{ + m_stringvars.clear(); +} + +void ModMetadata::clear() +{ + Metadata::clear(); + m_modified = true; +} + +bool ModMetadata::save(const std::string &root_path) +{ + Json::Value json; + for (StringMap::const_iterator it = m_stringvars.begin(); + it != m_stringvars.end(); ++it) { + json[it->first] = it->second; + } + + if (!fs::PathExists(root_path)) { + if (!fs::CreateAllDirs(root_path)) { + errorstream << "ModMetadata[" << m_mod_name << "]: Unable to save. '" + << root_path << "' tree cannot be created." << std::endl; + return false; + } + } else if (!fs::IsDir(root_path)) { + errorstream << "ModMetadata[" << m_mod_name << "]: Unable to save. '" + << root_path << "' is not a directory." << std::endl; + return false; + } + + bool w_ok = fs::safeWriteToFile(root_path + DIR_DELIM + m_mod_name, + Json::FastWriter().write(json)); + + if (w_ok) { + m_modified = false; + } else { + errorstream << "ModMetadata[" << m_mod_name << "]: failed write file." << std::endl; + } + return w_ok; +} + +bool ModMetadata::load(const std::string &root_path) +{ + m_stringvars.clear(); + + std::ifstream is((root_path + DIR_DELIM + m_mod_name).c_str(), std::ios_base::binary); + if (!is.good()) { + return false; + } + + Json::Reader reader; + Json::Value root; + if (!reader.parse(is, root)) { + errorstream << "ModMetadata[" << m_mod_name << "]: failed read data " + "(Json decoding failure)." << std::endl; + return false; + } + + const Json::Value::Members attr_list = root.getMemberNames(); + for (Json::Value::Members::const_iterator it = attr_list.begin(); + it != attr_list.end(); ++it) { + Json::Value attr_value = root[*it]; + m_stringvars[*it] = attr_value.asString(); + } + + return true; +} + +bool ModMetadata::setString(const std::string &name, const std::string &var) +{ + m_modified = Metadata::setString(name, var); + return m_modified; +} diff --git a/src/mods.h b/src/mods.h index af7777d18..61af5e5d1 100644 --- a/src/mods.h +++ b/src/mods.h @@ -28,6 +28,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include #include #include "config.h" +#include "metadata.h" #define MODNAME_ALLOWED_CHARS "abcdefghijklmnopqrstuvwxyz0123456789_" @@ -205,4 +206,24 @@ struct ModStoreModDetails { bool valid; }; +class ModMetadata: public Metadata +{ +public: + ModMetadata(const std::string &mod_name); + ~ModMetadata() {} + + virtual void clear(); + + bool save(const std::string &root_path); + bool load(const std::string &root_path); + + bool isModified() const { return m_modified; } + const std::string &getModName() const { return m_mod_name; } + + virtual bool setString(const std::string &name, const std::string &var); +private: + std::string m_mod_name; + bool m_modified; +}; + #endif diff --git a/src/remoteplayer.cpp b/src/remoteplayer.cpp index 6853ad6d9..0a4591410 100644 --- a/src/remoteplayer.cpp +++ b/src/remoteplayer.cpp @@ -174,7 +174,7 @@ void RemotePlayer::deSerialize(std::istream &is, const std::string &playername, const Json::Value::Members attr_list = attr_root.getMemberNames(); for (Json::Value::Members::const_iterator it = attr_list.begin(); - it != attr_list.end(); ++it) { + it != attr_list.end(); ++it) { Json::Value attr_value = attr_root[*it]; sao->setExtendedAttribute(*it, attr_value.asString()); } diff --git a/src/script/lua_api/CMakeLists.txt b/src/script/lua_api/CMakeLists.txt index 070234eba..e82560696 100644 --- a/src/script/lua_api/CMakeLists.txt +++ b/src/script/lua_api/CMakeLists.txt @@ -15,6 +15,7 @@ set(common_SCRIPT_LUA_API_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/l_particles.cpp ${CMAKE_CURRENT_SOURCE_DIR}/l_rollback.cpp ${CMAKE_CURRENT_SOURCE_DIR}/l_server.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/l_storage.cpp ${CMAKE_CURRENT_SOURCE_DIR}/l_util.cpp ${CMAKE_CURRENT_SOURCE_DIR}/l_vmanip.cpp ${CMAKE_CURRENT_SOURCE_DIR}/l_settings.cpp diff --git a/src/script/lua_api/l_storage.cpp b/src/script/lua_api/l_storage.cpp new file mode 100644 index 000000000..42928255f --- /dev/null +++ b/src/script/lua_api/l_storage.cpp @@ -0,0 +1,143 @@ +/* +Minetest +Copyright (C) 2013 celeron55, Perttu Ahola +Copyright (C) 2017 nerzhul, Loic Blot + +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 "lua_api/l_storage.h" +#include "l_internal.h" +#include "mods.h" +#include "server.h" + +int ModApiStorage::l_get_mod_storage(lua_State *L) +{ + lua_rawgeti(L, LUA_REGISTRYINDEX, CUSTOM_RIDX_CURRENT_MOD_NAME); + if (!lua_isstring(L, -1)) { + return 0; + } + + std::string mod_name = lua_tostring(L, -1); + + ModMetadata *store = new ModMetadata(mod_name); + // For server side + if (Server *server = getServer(L)) { + store->load(server->getModStoragePath()); + server->registerModStorage(store); + } else { + assert(false); // this should not happen + } + + StorageRef::create(L, store); + int object = lua_gettop(L); + + lua_pushvalue(L, object); + return 1; +} + +void ModApiStorage::Initialize(lua_State *L, int top) +{ + API_FCT(get_mod_storage); +} + +StorageRef::StorageRef(ModMetadata *object): + m_object(object) +{ +} + +void StorageRef::create(lua_State *L, ModMetadata *object) +{ + StorageRef *o = new StorageRef(object); + *(void **)(lua_newuserdata(L, sizeof(void *))) = o; + luaL_getmetatable(L, className); + lua_setmetatable(L, -2); +} + +int StorageRef::gc_object(lua_State *L) +{ + StorageRef *o = *(StorageRef **)(lua_touserdata(L, 1)); + // Server side + if (Server *server = getServer(L)) + server->unregisterModStorage(getobject(o)->getModName()); + delete o; + return 0; +} + +void StorageRef::Register(lua_State *L) +{ + lua_newtable(L); + int methodtable = lua_gettop(L); + luaL_newmetatable(L, className); + int metatable = lua_gettop(L); + + lua_pushliteral(L, "__metatable"); + lua_pushvalue(L, methodtable); + lua_settable(L, metatable); // hide metatable from Lua getmetatable() + + lua_pushliteral(L, "metadata_class"); + lua_pushlstring(L, className, strlen(className)); + lua_settable(L, metatable); + + lua_pushliteral(L, "__index"); + lua_pushvalue(L, methodtable); + lua_settable(L, metatable); + + lua_pushliteral(L, "__gc"); + lua_pushcfunction(L, gc_object); + lua_settable(L, metatable); + + lua_pop(L, 1); // drop metatable + + luaL_openlib(L, 0, methods, 0); // fill methodtable + lua_pop(L, 1); // drop methodtable +} + +StorageRef* StorageRef::checkobject(lua_State *L, int narg) +{ + luaL_checktype(L, narg, LUA_TUSERDATA); + void *ud = luaL_checkudata(L, narg, className); + if (!ud) luaL_typerror(L, narg, className); + return *(StorageRef**)ud; // unbox pointer +} + +ModMetadata* StorageRef::getobject(StorageRef *ref) +{ + ModMetadata *co = ref->m_object; + return co; +} + +Metadata* StorageRef::getmeta(bool auto_create) +{ + return m_object; +} + +void StorageRef::clearMeta() +{ + m_object->clear(); +} + +const char StorageRef::className[] = "StorageRef"; +const luaL_reg StorageRef::methods[] = { + luamethod(MetaDataRef, get_string), + luamethod(MetaDataRef, set_string), + luamethod(MetaDataRef, get_int), + luamethod(MetaDataRef, set_int), + luamethod(MetaDataRef, get_float), + luamethod(MetaDataRef, set_float), + luamethod(MetaDataRef, to_table), + luamethod(MetaDataRef, from_table), + {0,0} +}; diff --git a/src/script/lua_api/l_storage.h b/src/script/lua_api/l_storage.h new file mode 100644 index 000000000..fde2828ad --- /dev/null +++ b/src/script/lua_api/l_storage.h @@ -0,0 +1,62 @@ +/* +Minetest +Copyright (C) 2013 celeron55, Perttu Ahola +Copyright (C) 2017 nerzhul, Loic Blot + +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 __L_STORAGE_H__ +#define __L_STORAGE_H__ + +#include "lua_api/l_base.h" +#include "l_metadata.h" + +class ModMetadata; + +class ModApiStorage: public ModApiBase +{ +protected: + static int l_get_mod_storage(lua_State *L); +public: + static void Initialize(lua_State *L, int top); + +}; + +class StorageRef: public MetaDataRef +{ +private: + ModMetadata *m_object; + + static const char className[]; + static const luaL_reg methods[]; + + virtual Metadata* getmeta(bool auto_create); + virtual void clearMeta(); + + // garbage collector + static int gc_object(lua_State *L); +public: + StorageRef(ModMetadata *object); + ~StorageRef() {} + + static void Register(lua_State *L); + static void create(lua_State *L, ModMetadata *object); + + static StorageRef *checkobject(lua_State *L, int narg); + static ModMetadata* getobject(StorageRef *ref); +}; + +#endif /* __L_STORAGE_H__ */ diff --git a/src/script/scripting_game.cpp b/src/script/scripting_game.cpp index 7becef6dc..4da752263 100644 --- a/src/script/scripting_game.cpp +++ b/src/script/scripting_game.cpp @@ -41,6 +41,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "lua_api/l_vmanip.h" #include "lua_api/l_settings.h" #include "lua_api/l_http.h" +#include "lua_api/l_storage.h" extern "C" { #include "lualib.h" @@ -92,6 +93,7 @@ void GameScripting::InitializeModApi(lua_State *L, int top) ModApiServer::Initialize(L, top); ModApiUtil::Initialize(L, top); ModApiHttp::Initialize(L, top); + ModApiStorage::Initialize(L, top); // Register reference classes (userdata) InvRef::Register(L); @@ -108,6 +110,7 @@ void GameScripting::InitializeModApi(lua_State *L, int top) NodeTimerRef::Register(L); ObjectRef::Register(L); LuaSettings::Register(L); + StorageRef::Register(L); } void log_deprecated(const std::string &message) diff --git a/src/server.cpp b/src/server.cpp index e5714ac03..8b9f46f85 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -178,8 +178,8 @@ Server::Server( m_admin_chat(iface), m_ignore_map_edit_events(false), m_ignore_map_edit_events_peer_id(0), - m_next_sound_id(0) - + m_next_sound_id(0), + m_mod_storage_save_timer(10.0f) { m_liquid_transform_timer = 0.0; m_liquid_transform_every = 1.0; @@ -788,6 +788,18 @@ void Server::AsyncRunStep(bool initial_step) << "packet size is " << pktSize << std::endl; } m_clients.unlock(); + + m_mod_storage_save_timer -= dtime; + if (m_mod_storage_save_timer <= 0.0f) { + infostream << "Saving registered mod storages." << std::endl; + m_mod_storage_save_timer = g_settings->getFloat("server_map_save_interval"); + for (UNORDERED_MAP::const_iterator + it = m_mod_storages.begin(); it != m_mod_storages.end(); ++it) { + if (it->second->isModified()) { + it->second->save(getModStoragePath()); + } + } + } } /* @@ -3404,6 +3416,11 @@ std::string Server::getBuiltinLuaPath() return porting::path_share + DIR_DELIM + "builtin"; } +std::string Server::getModStoragePath() const +{ + return m_path_world + DIR_DELIM + "mod_storage"; +} + v3f Server::findSpawnPos() { ServerMap &map = m_env->getServerMap(); @@ -3525,6 +3542,28 @@ PlayerSAO* Server::emergePlayer(const char *name, u16 peer_id, u16 proto_version return playersao; } +bool Server::registerModStorage(ModMetadata *storage) +{ + if (m_mod_storages.find(storage->getModName()) != m_mod_storages.end()) { + errorstream << "Unable to register same mod storage twice. Storage name: " + << storage->getModName() << std::endl; + return false; + } + + m_mod_storages[storage->getModName()] = storage; + return true; +} + +void Server::unregisterModStorage(const std::string &name) +{ + UNORDERED_MAP::const_iterator it = m_mod_storages.find(name); + if (it != m_mod_storages.end()) { + // Save unconditionaly on unregistration + it->second->save(getModStoragePath()); + m_mod_storages.erase(name); + } +} + void dedicated_server_loop(Server &server, bool &kill) { DSTACK(FUNCTION_NAME); diff --git a/src/server.h b/src/server.h index 8f553ce38..3eee67b78 100644 --- a/src/server.h +++ b/src/server.h @@ -299,7 +299,8 @@ public: const ModSpec* getModSpec(const std::string &modname) const; void getModNames(std::vector &modlist); std::string getBuiltinLuaPath(); - inline std::string getWorldPath() const { return m_path_world; } + inline const std::string &getWorldPath() const { return m_path_world; } + std::string getModStoragePath() const; inline bool isSingleplayer() { return m_simple_singleplayer_mode; } @@ -360,6 +361,9 @@ public: void SendInventory(PlayerSAO* playerSAO); void SendMovePlayer(u16 peer_id); + bool registerModStorage(ModMetadata *storage); + void unregisterModStorage(const std::string &name); + // Bind address Address m_bind_addr; @@ -650,6 +654,9 @@ private: // value = "" (visible to all players) or player name std::map m_detached_inventories_player; + UNORDERED_MAP m_mod_storages; + float m_mod_storage_save_timer; + DISABLE_CLASS_COPY(Server); }; -- cgit v1.2.3 From 072bbba69aa2528c309b76aaec288bdba52e119c Mon Sep 17 00:00:00 2001 From: Loïc Blot Date: Wed, 22 Mar 2017 21:41:02 +0100 Subject: Some performance optimizations (#5424) * Some performance optimizations This is globally removing some memory useless copy * use a const ref return on std::string Settings::get to prevent data copy on getters which doesn't need to copy it * pass some stack created strings to static const as they are not modified anywhere * Camera: return nametags per const ref instead of a list pointer, we only need to read it * INodeDefManager: getAll should be a result ref writer instead of a return copy * INodeDefManager: getAlias should return a const std::string ref * Minimap: unroll a Scolor creation in blitMinimapPixersToImageRadar to prvent many variable construct/destruct which are unneeded (we rewrite the content in the loop) * CNodeDefManager::updateAliases: prevent a idef getall copy * Profiler: constness * rollback_interface: create real_name later, and use const ref * MapBlockMesh updateFastFaceRow: unroll TileSpec next_tile, which has a cost of 1.8% CPU due to variable allocation/destruction, * MapBlockMesh updateFastFaceRow: copy next_tile to tile only if it's a different tilespec * MapBlockMesh updateFastFaceRow: use memcpy to copy next_lights to lights to do it in a single cpu operation --- src/camera.h | 3 +-- src/client/clientlauncher.cpp | 2 +- src/client/tile.cpp | 12 +++++------ src/drawscene.cpp | 2 +- src/game.cpp | 2 +- src/itemdef.cpp | 7 +++---- src/itemdef.h | 8 ++++---- src/mapblock_mesh.cpp | 48 ++++++++++--------------------------------- src/minimap.cpp | 30 ++++++++++++++------------- src/nodedef.cpp | 9 ++++---- src/profiler.h | 33 +++++++++++++---------------- src/remoteplayer.cpp | 4 ++-- src/rollback_interface.cpp | 5 +++-- src/settings.cpp | 29 +------------------------- src/settings.h | 4 +--- src/sky.h | 15 ++++++++------ 16 files changed, 79 insertions(+), 134 deletions(-) (limited to 'src/remoteplayer.cpp') diff --git a/src/camera.h b/src/camera.h index b5be26718..f57efdf10 100644 --- a/src/camera.h +++ b/src/camera.h @@ -172,8 +172,7 @@ public: void removeNametag(Nametag *nametag); - std::list *getNametags() - { return &m_nametags; } + const std::list &getNametags() { return m_nametags; } void drawNametags(); diff --git a/src/client/clientlauncher.cpp b/src/client/clientlauncher.cpp index 2adac53c2..bdd16205f 100644 --- a/src/client/clientlauncher.cpp +++ b/src/client/clientlauncher.cpp @@ -530,7 +530,7 @@ bool ClientLauncher::create_engine_device() // Determine driver video::E_DRIVER_TYPE driverType = video::EDT_OPENGL; - std::string driverstring = g_settings->get("video_driver"); + const std::string &driverstring = g_settings->get("video_driver"); std::vector drivers = porting::getSupportedVideoDrivers(); u32 i; diff --git a/src/client/tile.cpp b/src/client/tile.cpp index 000c766d0..85d388d6e 100644 --- a/src/client/tile.cpp +++ b/src/client/tile.cpp @@ -134,9 +134,8 @@ std::string getTexturePath(const std::string &filename) /* Check from texture_path */ - std::string texture_path = g_settings->get("texture_path"); - if (texture_path != "") - { + const std::string &texture_path = g_settings->get("texture_path"); + if (texture_path != "") { std::string testpath = texture_path + DIR_DELIM + filename; // Check all filename extensions. Returns "" if not found. fullpath = getImagePath(testpath); @@ -1854,7 +1853,7 @@ bool TextureSource::generateImagePart(std::string part_of_name, for (u32 x = 0; x < dim.Width; x++) { video::SColor c = baseimg->getPixel(x, y); - c.color ^= mask; + c.color ^= mask; baseimg->setPixel(x, y, c); } } @@ -2266,7 +2265,8 @@ video::ITexture* TextureSource::getNormalTexture(const std::string &name) if (isKnownSourceImage("override_normal.png")) return getTexture("override_normal.png"); std::string fname_base = name; - std::string normal_ext = "_normal.png"; + static const char *normal_ext = "_normal.png"; + static const uint32_t normal_ext_size = strlen(normal_ext); size_t pos = fname_base.find("."); std::string fname_normal = fname_base.substr(0, pos) + normal_ext; if (isKnownSourceImage(fname_normal)) { @@ -2274,7 +2274,7 @@ video::ITexture* TextureSource::getNormalTexture(const std::string &name) size_t i = 0; while ((i = fname_base.find(".", i)) != std::string::npos) { fname_base.replace(i, 4, normal_ext); - i += normal_ext.length(); + i += normal_ext_size; } return getTexture(fname_base); } diff --git a/src/drawscene.cpp b/src/drawscene.cpp index 3a03743ee..663c8828c 100644 --- a/src/drawscene.cpp +++ b/src/drawscene.cpp @@ -494,7 +494,7 @@ void draw_scene(video::IVideoDriver *driver, scene::ISceneManager *smgr, catch(SettingNotFoundException) {} #endif - std::string draw_mode = g_settings->get("3d_mode"); + const std::string &draw_mode = g_settings->get("3d_mode"); smgr->drawAll(); diff --git a/src/game.cpp b/src/game.cpp index 5b49d34c0..9d52e4326 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -4094,7 +4094,7 @@ void Game::updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime, Drawing begins */ - video::SColor skycolor = sky->getSkyColor(); + const video::SColor &skycolor = sky->getSkyColor(); TimeTaker tt_draw("mainloop: draw"); driver->beginScene(true, true, skycolor); diff --git a/src/itemdef.cpp b/src/itemdef.cpp index f43e5c970..627ad1b6c 100644 --- a/src/itemdef.cpp +++ b/src/itemdef.cpp @@ -275,16 +275,16 @@ public: assert(i != m_item_definitions.end()); return *(i->second); } - virtual std::string getAlias(const std::string &name) const + virtual const std::string &getAlias(const std::string &name) const { StringMap::const_iterator it = m_aliases.find(name); if (it != m_aliases.end()) return it->second; return name; } - virtual std::set getAll() const + virtual void getAll(std::set &result) const { - std::set result; + result.clear(); for(std::map::const_iterator it = m_item_definitions.begin(); it != m_item_definitions.end(); ++it) { @@ -295,7 +295,6 @@ public: it != m_aliases.end(); ++it) { result.insert(it->first); } - return result; } virtual bool isKnown(const std::string &name_) const { diff --git a/src/itemdef.h b/src/itemdef.h index 2ade6116a..01ec4fa2f 100644 --- a/src/itemdef.h +++ b/src/itemdef.h @@ -100,9 +100,9 @@ public: // Get item definition virtual const ItemDefinition& get(const std::string &name) const=0; // Get alias definition - virtual std::string getAlias(const std::string &name) const=0; + virtual const std::string &getAlias(const std::string &name) const=0; // Get set of all defined item names and aliases - virtual std::set getAll() const=0; + virtual void getAll(std::set &result) const=0; // Check if item is known virtual bool isKnown(const std::string &name) const=0; #ifndef SERVER @@ -126,9 +126,9 @@ public: // Get item definition virtual const ItemDefinition& get(const std::string &name) const=0; // Get alias definition - virtual std::string getAlias(const std::string &name) const=0; + virtual const std::string &getAlias(const std::string &name) const=0; // Get set of all defined item names and aliases - virtual std::set getAll() const=0; + virtual void getAll(std::set &result) const=0; // Check if item is known virtual bool isKnown(const std::string &name) const=0; #ifndef SERVER diff --git a/src/mapblock_mesh.cpp b/src/mapblock_mesh.cpp index f76033ea8..eddb061b4 100644 --- a/src/mapblock_mesh.cpp +++ b/src/mapblock_mesh.cpp @@ -855,8 +855,9 @@ static void updateFastFaceRow( makes_face, p_corrected, face_dir_corrected, lights, tile); - for(u16 j=0; jadd("Meshgen: diff: next_makes_face != makes_face", - next_makes_face != makes_face ? 1 : 0); - g_profiler->add("Meshgen: diff: n_p_corr != p_corr + t_dir", - (next_p_corrected != p_corrected + translate_dir) ? 1 : 0); - g_profiler->add("Meshgen: diff: next_f_dir_corr != f_dir_corr", - next_face_dir_corrected != face_dir_corrected ? 1 : 0); - g_profiler->add("Meshgen: diff: next_lights[] != lights[]", - (next_lights[0] != lights[0] || - next_lights[0] != lights[0] || - next_lights[0] != lights[0] || - next_lights[0] != lights[0]) ? 1 : 0); - g_profiler->add("Meshgen: diff: !(next_tile == tile)", - !(next_tile == tile) ? 1 : 0); - }*/ } - /*g_profiler->add("Meshgen: Total faces checked", 1); - if(makes_face) - g_profiler->add("Meshgen: Total makes_face checked", 1);*/ - } else { - /*if(makes_face) - g_profiler->add("Meshgen: diff: last position", 1);*/ } - if(next_is_different) - { + if (next_is_different) { /* Create a face if there should be one */ - if(makes_face) - { + if (makes_face) { // Floating point conversion of the position vector v3f pf(p_corrected.X, p_corrected.Y, p_corrected.Z); // Center point of face (kind of) @@ -957,11 +933,9 @@ static void updateFastFaceRow( makes_face = next_makes_face; p_corrected = next_p_corrected; face_dir_corrected = next_face_dir_corrected; - lights[0] = next_lights[0]; - lights[1] = next_lights[1]; - lights[2] = next_lights[2]; - lights[3] = next_lights[3]; - tile = next_tile; + std::memcpy(lights, next_lights, ARRLEN(lights) * sizeof(u16)); + if (next_is_different) + tile = next_tile; p = p_next; } } diff --git a/src/minimap.cpp b/src/minimap.cpp index ee29c58ba..a7f4822c9 100644 --- a/src/minimap.cpp +++ b/src/minimap.cpp @@ -105,7 +105,7 @@ void MinimapUpdateThread::doUpdate() // Swap two values in the map using single lookup std::pair::iterator, bool> result = m_blocks_cache.insert(std::make_pair(update.pos, update.data)); - if (result.second == false) { + if (!result.second) { delete result.first->second; result.first->second = update.data; } @@ -322,13 +322,15 @@ void Minimap::setAngle(f32 angle) void Minimap::blitMinimapPixelsToImageRadar(video::IImage *map_image) { + video::SColor c(240, 0, 0, 0); for (s16 x = 0; x < data->map_size; x++) for (s16 z = 0; z < data->map_size; z++) { MinimapPixel *mmpixel = &data->minimap_scan[x + z * data->map_size]; - video::SColor c(240, 0, 0, 0); if (mmpixel->air_count > 0) c.setGreen(core::clamp(core::round32(32 + mmpixel->air_count * 8), 0, 255)); + else + c.setGreen(0); map_image->setPixel(x, data->map_size - z - 1, c); } @@ -337,21 +339,23 @@ void Minimap::blitMinimapPixelsToImageRadar(video::IImage *map_image) void Minimap::blitMinimapPixelsToImageSurface( video::IImage *map_image, video::IImage *heightmap_image) { + // This variable creation/destruction has a 1% cost on rendering minimap + video::SColor tilecolor; for (s16 x = 0; x < data->map_size; x++) for (s16 z = 0; z < data->map_size; z++) { MinimapPixel *mmpixel = &data->minimap_scan[x + z * data->map_size]; const ContentFeatures &f = m_ndef->get(mmpixel->n); const TileDef *tile = &f.tiledef[0]; + // Color of the 0th tile (mostly this is the topmost) - video::SColor tilecolor; if(tile->has_color) tilecolor = tile->color; else mmpixel->n.getColor(f, &tilecolor); + tilecolor.setRed(tilecolor.getRed() * f.minimap_color.getRed() / 255); - tilecolor.setGreen(tilecolor.getGreen() * f.minimap_color.getGreen() - / 255); + tilecolor.setGreen(tilecolor.getGreen() * f.minimap_color.getGreen() / 255); tilecolor.setBlue(tilecolor.getBlue() * f.minimap_color.getBlue() / 255); tilecolor.setAlpha(240); @@ -391,7 +395,7 @@ video::ITexture *Minimap::getMinimapTexture() if (minimap_mask) { for (s16 y = 0; y < MINIMAP_MAX_SY; y++) for (s16 x = 0; x < MINIMAP_MAX_SX; x++) { - video::SColor mask_col = minimap_mask->getPixel(x, y); + const video::SColor &mask_col = minimap_mask->getPixel(x, y); if (!mask_col.getAlpha()) minimap_image->setPixel(x, y, video::SColor(0,0,0,0)); } @@ -430,7 +434,7 @@ scene::SMeshBuffer *Minimap::getMinimapMeshBuffer() scene::SMeshBuffer *buf = new scene::SMeshBuffer(); buf->Vertices.set_used(4); buf->Indices.set_used(6); - video::SColor c(255, 255, 255, 255); + static const video::SColor c(255, 255, 255, 255); buf->Vertices[0] = video::S3DVertex(-1, -1, 0, 0, 0, 1, c, 0, 1); buf->Vertices[1] = video::S3DVertex(-1, 1, 0, 0, 0, 1, c, 0, 0); @@ -550,15 +554,13 @@ void Minimap::updateActiveMarkers() video::IImage *minimap_mask = data->minimap_shape_round ? data->minimap_mask_round : data->minimap_mask_square; - std::list *nametags = client->getCamera()->getNametags(); + const std::list &nametags = client->getCamera()->getNametags(); m_active_markers.clear(); - for (std::list::const_iterator - i = nametags->begin(); - i != nametags->end(); ++i) { - Nametag *nametag = *i; - v3s16 pos = floatToInt(nametag->parent_node->getPosition() + + for (std::list::const_iterator i = nametags.begin(); + i != nametags.end(); ++i) { + v3s16 pos = floatToInt((*i)->parent_node->getPosition() + intToFloat(client->getCamera()->getOffset(), BS), BS); pos -= data->pos - v3s16(data->map_size / 2, data->scan_height / 2, @@ -570,7 +572,7 @@ void Minimap::updateActiveMarkers() } pos.X = ((float)pos.X / data->map_size) * MINIMAP_MAX_SX; pos.Z = ((float)pos.Z / data->map_size) * MINIMAP_MAX_SY; - video::SColor mask_col = minimap_mask->getPixel(pos.X, pos.Z); + const video::SColor &mask_col = minimap_mask->getPixel(pos.X, pos.Z); if (!mask_col.getAlpha()) { continue; } diff --git a/src/nodedef.cpp b/src/nodedef.cpp index 3532eea1e..2745a45e8 100644 --- a/src/nodedef.cpp +++ b/src/nodedef.cpp @@ -1332,12 +1332,13 @@ void CNodeDefManager::removeNode(const std::string &name) void CNodeDefManager::updateAliases(IItemDefManager *idef) { - std::set all = idef->getAll(); + std::set all; + idef->getAll(all); m_name_id_mapping_with_aliases.clear(); - for (std::set::iterator + for (std::set::const_iterator i = all.begin(); i != all.end(); ++i) { - std::string name = *i; - std::string convert_to = idef->getAlias(name); + const std::string &name = *i; + const std::string &convert_to = idef->getAlias(name); content_t id; if (m_name_id_mapping.getId(convert_to, id)) { m_name_id_mapping_with_aliases.insert( diff --git a/src/profiler.h b/src/profiler.h index e8eac86b1..6da115972 100644 --- a/src/profiler.h +++ b/src/profiler.h @@ -119,39 +119,34 @@ public: u32 minindex, maxindex; paging(m_data.size(), page, pagecount, minindex, maxindex); - for(std::map::iterator - i = m_data.begin(); - i != m_data.end(); ++i) - { - if(maxindex == 0) + for (std::map::const_iterator i = m_data.begin(); + i != m_data.end(); ++i) { + if (maxindex == 0) break; maxindex--; - if(minindex != 0) - { + if (minindex != 0) { minindex--; continue; } - std::string name = i->first; int avgcount = 1; - std::map::iterator n = m_avgcounts.find(name); - if(n != m_avgcounts.end()){ + std::map::const_iterator n = m_avgcounts.find(i->first); + if (n != m_avgcounts.end()) { if(n->second >= 1) avgcount = n->second; } - o<<" "<first << ": "; s32 clampsize = 40; - s32 space = clampsize - name.size(); - for(s32 j=0; jfirst.size(); + for(s32 j = 0; j < space; j++) { + if (j % 2 == 0 && j < space - 1) + o << "-"; else - o<<" "; + o << " "; } - o<<(i->second / avgcount); - o<second / avgcount); + o << std::endl; } } diff --git a/src/remoteplayer.cpp b/src/remoteplayer.cpp index 0a4591410..c8e5b9132 100644 --- a/src/remoteplayer.cpp +++ b/src/remoteplayer.cpp @@ -141,7 +141,7 @@ void RemotePlayer::deSerialize(std::istream &is, const std::string &playername, m_dirty = true; //args.getS32("version"); // Version field value not used - std::string name = args.get("name"); + const std::string &name = args.get("name"); strlcpy(m_name, name.c_str(), PLAYERNAME_SIZE); if (sao) { @@ -167,7 +167,7 @@ void RemotePlayer::deSerialize(std::istream &is, const std::string &playername, } catch (SettingNotFoundException &e) {} try { - std::string extended_attributes = args.get("extended_attributes"); + const std::string &extended_attributes = args.get("extended_attributes"); Json::Reader reader; Json::Value attr_root; reader.parse(extended_attributes, attr_root); diff --git a/src/rollback_interface.cpp b/src/rollback_interface.cpp index 7345c4a7d..40a33a51d 100644 --- a/src/rollback_interface.cpp +++ b/src/rollback_interface.cpp @@ -190,7 +190,6 @@ bool RollbackAction::applyRevert(Map *map, InventoryManager *imgr, IGameDef *gam case TYPE_MODIFY_INVENTORY_STACK: { InventoryLocation loc; loc.deSerialize(inventory_location); - std::string real_name = gamedef->idef()->getAlias(inventory_stack.name); Inventory *inv = imgr->getInventory(loc); if (!inv) { infostream << "RollbackAction::applyRevert(): Could not get " @@ -211,10 +210,12 @@ bool RollbackAction::applyRevert(Map *map, InventoryManager *imgr, IGameDef *gam << inventory_location << std::endl; return false; } + // If item was added, take away item, otherwise add removed item if (inventory_add) { // Silently ignore different current item - if (list->getItem(inventory_index).name != real_name) + if (list->getItem(inventory_index).name != + gamedef->idef()->getAlias(inventory_stack.name)) return false; list->takeItem(inventory_index, inventory_stack.count); } else { diff --git a/src/settings.cpp b/src/settings.cpp index c4c3c9073..b4083264e 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -90,33 +90,6 @@ bool Settings::checkValueValid(const std::string &value) return true; } - -std::string Settings::sanitizeName(const std::string &name) -{ - std::string n = trim(name); - - for (const char *s = "=\"{}#"; *s; s++) - n.erase(std::remove(n.begin(), n.end(), *s), n.end()); - - return n; -} - - -std::string Settings::sanitizeValue(const std::string &value) -{ - std::string v(value); - size_t p = 0; - - if (v.substr(0, 3) == "\"\"\"") - v.erase(0, 3); - - while ((p = v.find("\n\"\"\"")) != std::string::npos) - v.erase(p, 4); - - return v; -} - - std::string Settings::getMultiline(std::istream &is, size_t *num_lines) { size_t lines = 1; @@ -398,7 +371,7 @@ Settings *Settings::getGroup(const std::string &name) const } -std::string Settings::get(const std::string &name) const +const std::string &Settings::get(const std::string &name) const { const SettingsEntry &entry = getEntry(name); if (entry.is_group) diff --git a/src/settings.h b/src/settings.h index b19733514..777d0eff5 100644 --- a/src/settings.h +++ b/src/settings.h @@ -129,8 +129,6 @@ public: static bool checkNameValid(const std::string &name); static bool checkValueValid(const std::string &value); - static std::string sanitizeName(const std::string &name); - static std::string sanitizeValue(const std::string &value); static std::string getMultiline(std::istream &is, size_t *num_lines=NULL); static void printEntry(std::ostream &os, const std::string &name, const SettingsEntry &entry, u32 tab_depth=0); @@ -141,7 +139,7 @@ public: const SettingsEntry &getEntry(const std::string &name) const; Settings *getGroup(const std::string &name) const; - std::string get(const std::string &name) const; + const std::string &get(const std::string &name) const; bool getBool(const std::string &name) const; u16 getU16(const std::string &name) const; s16 getS16(const std::string &name) const; diff --git a/src/sky.h b/src/sky.h index f19891773..17d6c5f73 100644 --- a/src/sky.h +++ b/src/sky.h @@ -56,18 +56,21 @@ public: void update(float m_time_of_day, float time_brightness, float direct_brightness, bool sunlight_seen, CameraMode cam_mode, float yaw, float pitch); - + float getBrightness(){ return m_brightness; } - video::SColor getBgColor(){ + const video::SColor &getBgColor() const + { return m_visible ? m_bgcolor : m_fallback_bg_color; } - video::SColor getSkyColor(){ + + const video::SColor &getSkyColor() const + { return m_visible ? m_skycolor : m_fallback_bg_color; } - - bool getCloudsVisible(){ return m_clouds_visible && m_visible; } - video::SColorf getCloudColor(){ return m_cloudcolor_f; } + + bool getCloudsVisible() { return m_clouds_visible && m_visible; } + const video::SColorf &getCloudColor() { return m_cloudcolor_f; } void setVisible(bool visible){ m_visible = visible; } void setFallbackBgColor(const video::SColor &fallback_bg_color){ -- cgit v1.2.3 From 29ab20c27229672c24a7699afbcd54caad903331 Mon Sep 17 00:00:00 2001 From: Loïc Blot Date: Sun, 23 Apr 2017 14:35:08 +0200 Subject: 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 --- build/android/jni/Android.mk | 1 + builtin/game/chatcommands.lua | 25 +++ doc/lua_api.txt | 2 + src/CMakeLists.txt | 1 + src/client.cpp | 2 +- src/client.h | 4 +- src/content_sao.cpp | 7 +- src/content_sao.h | 4 +- src/database-dummy.h | 9 +- src/database-files.cpp | 179 +++++++++++++++ src/database-files.h | 46 ++++ src/database-leveldb.h | 4 +- src/database-postgresql.cpp | 470 ++++++++++++++++++++++++++++++++++------ src/database-postgresql.h | 115 +++++++--- src/database-redis.h | 2 +- src/database-sqlite3.cpp | 399 +++++++++++++++++++++++++++++++--- src/database-sqlite3.h | 158 ++++++++++++-- src/database.cpp | 4 +- src/database.h | 24 +- src/main.cpp | 21 +- src/map.cpp | 13 +- src/map.h | 8 +- src/remoteplayer.cpp | 48 ---- src/remoteplayer.h | 2 +- src/script/lua_api/l_server.cpp | 17 ++ src/script/lua_api/l_server.h | 3 + src/server.cpp | 50 +---- src/server.h | 3 +- src/serverenvironment.cpp | 234 ++++++++++++++++---- src/serverenvironment.h | 13 +- src/unittest/test_player.cpp | 49 ----- 31 files changed, 1547 insertions(+), 370 deletions(-) create mode 100644 src/database-files.cpp create mode 100644 src/database-files.h (limited to 'src/remoteplayer.cpp') diff --git a/build/android/jni/Android.mk b/build/android/jni/Android.mk index 2929eaba1..b652c6b5e 100644 --- a/build/android/jni/Android.mk +++ b/build/android/jni/Android.mk @@ -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 \ diff --git a/builtin/game/chatcommands.lua b/builtin/game/chatcommands.lua index 84f2c3fed..cbf75c1bc 100644 --- a/builtin/game/chatcommands.lua +++ b/builtin/game/chatcommands.lua @@ -279,6 +279,31 @@ core.register_chatcommand("auth_reload", { end, }) +core.register_chatcommand("remove_player", { + params = "", + 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 = ",, | | ,, | ", description = "Teleport to player or position", diff --git a/doc/lua_api.txt b/doc/lua_api.txt index 33254fb2a..c3d8d2bf6 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -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("")`) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 37f72a44d..7f779db10 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -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 diff --git a/src/client.cpp b/src/client.cpp index ce42d025e..94c808a57 100644 --- a/src/client.cpp +++ b/src/client.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; } diff --git a/src/client.h b/src/client.h index 5dc3f9bc8..328a24f90 100644 --- a/src/client.h +++ b/src/client.h @@ -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; diff --git a/src/content_sao.cpp b/src/content_sao.cpp index 355453fc9..caf6dcbab 100644 --- a/src/content_sao.cpp +++ b/src/content_sao.cpp @@ -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 &privs) +void PlayerSAO::finalize(RemotePlayer *player, const std::set &privs) { assert(player); m_player = player; diff --git a/src/content_sao.h b/src/content_sao.h index e53e8ecce..e08795579 100644 --- a/src/content_sao.h +++ b/src/content_sao.h @@ -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 &privs); + void finalize(RemotePlayer *player, const std::set &privs); v3f getEyePosition() const { return m_base_position + getEyeOffset(); } v3f getEyeOffset() const; diff --git a/src/database-dummy.h b/src/database-dummy.h index 9083850cb..7d1cb2279 100644 --- a/src/database-dummy.h +++ b/src/database-dummy.h @@ -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 &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 &) {} + + void beginSave() {} + void endSave() {} private: std::map m_database; }; diff --git a/src/database-files.cpp b/src/database-files.cpp new file mode 100644 index 000000000..08a1f2d03 --- /dev/null +++ b/src/database-files.cpp @@ -0,0 +1,179 @@ +/* +Minetest +Copyright (C) 2017 nerzhul, Loic Blot + +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 +#include +#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 &res) +{ + std::vector files = fs::GetDirListing(m_savedir); + // list files into players directory + for (std::vector::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()); + } +} diff --git a/src/database-files.h b/src/database-files.h new file mode 100644 index 000000000..d23069c2a --- /dev/null +++ b/src/database-files.h @@ -0,0 +1,46 @@ +/* +Minetest +Copyright (C) 2017 nerzhul, Loic Blot + +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 &res); + +private: + void serialize(std::ostringstream &os, RemotePlayer *player); + + std::string m_savedir; +}; + +#endif diff --git a/src/database-leveldb.h b/src/database-leveldb.h index 171946741..52ccebe70 100644 --- a/src/database-leveldb.h +++ b/src/database-leveldb.h @@ -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 &dst); + void beginSave() {} + void endSave() {} private: leveldb::DB *m_database; }; diff --git a/src/database-postgresql.cpp b/src/database-postgresql.cpp index 83678fd52..a6b62bad5 100644 --- a/src/database-postgresql.cpp +++ b/src/database-postgresql.cpp @@ -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,18 +296,338 @@ bool Database_PostgreSQL::deleteBlock(const v3s16 &pos) return true; } -void Database_PostgreSQL::listAllLoadableBlocks(std::vector &dst) +void MapDatabasePostgreSQL::listAllLoadableBlocks(std::vector &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 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 &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); } diff --git a/src/database-postgresql.h b/src/database-postgresql.h index 1cfa544e3..d6f208fd9 100644 --- a/src/database-postgresql.h +++ b/src/database-postgresql.h @@ -27,53 +27,33 @@ 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 &dst); bool initialized() const; -private: - // Database initialization - void connectToDatabase(); - void initStatements(); - void createDatabase(); - inline void prepareStatement(const std::string &name, const std::string &sql) +protected: + // Conversion helpers + inline int pg_to_int(PGresult *res, int row, int col) { - checkResults(PQprepare(m_conn, name.c_str(), sql.c_str(), 0, NULL)); + return atoi(PQgetvalue(res, row, col)); } - // 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) + inline u32 pg_to_uint(PGresult *res, int row, int col) { - return checkResults(PQexecPrepared(m_conn, stmtName, paramsNumber, - (const char* const*) params, paramsLengths, paramsFormats, - nobinary ? 1 : 0), clear); + return (u32) atoi(PQgetvalue(res, row, col)); } - // Conversion helpers - inline int pg_to_int(PGresult *res, int row, int col) + inline float pg_to_float(PGresult *res, int row, int col) { - return atoi(PQgetvalue(res, row, col)); + return (float) atof(PQgetvalue(res, row, col)); } inline v3s16 pg_to_v3s16(PGresult *res, int row, int col) @@ -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 &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 &res); + +protected: + virtual void createDatabase(); + virtual void initStatements(); + +private: + bool playerDataExists(const std::string &playername); +}; + #endif diff --git a/src/database-redis.h b/src/database-redis.h index 214bc8dd6..fa15dd8a7 100644 --- a/src/database-redis.h +++ b/src/database-redis.h @@ -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); diff --git a/src/database-sqlite3.cpp b/src/database-sqlite3.cpp index 095d485c0..714f56c39 100644 --- a/src/database-sqlite3.cpp +++ b/src/database-sqlite3.cpp @@ -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 @@ -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() +void MapDatabaseSQLite3::listAllLoadableBlocks(std::vector &dst) +{ + verifyDatabase(); + + while (sqlite3_step(m_stmt_list) == SQLITE_ROW) + dst.push_back(getIntegerAsBlock(sqlite3_column_int64(m_stmt_list, 0))); + + sqlite3_reset(m_stmt_list); +} + +/* + * Player 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 `blocks` (\n" - " `pos` INT PRIMARY KEY,\n" - " `data` BLOB\n" - ");\n", + "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 database table"); + "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 Database_SQLite3::listAllLoadableBlocks(std::vector &dst) +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; +} - while (sqlite3_step(m_stmt_list) == SQLITE_ROW) { - dst.push_back(getIntegerAsBlock(sqlite3_column_int64(m_stmt_list, 0))); +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); } - sqlite3_reset(m_stmt_list); + + // 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 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(); } -Database_SQLite3::~Database_SQLite3() +bool PlayerDatabaseSQLite3::loadPlayer(RemotePlayer *player, PlayerSAO *sao) { - 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) + verifyDatabase(); - SQLOK_ERRSTREAM(sqlite3_close(m_database), "Failed to close database"); + 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 &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); +} diff --git a/src/database-sqlite3.h b/src/database-sqlite3.h index 2ab4c8ee9..3244facc9 100644 --- a/src/database-sqlite3.h +++ b/src/database-sqlite3.h @@ -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 #include #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 &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(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 &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 &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 diff --git a/src/database.cpp b/src/database.cpp index 262d475ec..8e1483893 100644 --- a/src/database.cpp +++ b/src/database.cpp @@ -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); diff --git a/src/database.h b/src/database.h index 7213f088a..5a2b844fd 100644 --- a/src/database.h +++ b/src/database.h @@ -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 &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 &res) = 0; }; #endif diff --git a/src/main.cpp b/src/main.cpp index 1ec278981..2ad4e2780 100644 --- a/src/main.cpp +++ b/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 << _(" "); - 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; } - diff --git a/src/map.cpp b/src/map.cpp index 75dcee350..c148c51f1 100644 --- a/src/map.cpp +++ b/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(); diff --git a/src/map.h b/src/map.h index 4d7079823..7e597bef6 100644 --- a/src/map.h +++ b/src/map.h @@ -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; }; diff --git a/src/remoteplayer.cpp b/src/remoteplayer.cpp index c8e5b9132..2dbfe9d9d 100644 --- a/src/remoteplayer.cpp +++ b/src/remoteplayer.cpp @@ -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); diff --git a/src/remoteplayer.h b/src/remoteplayer.h index 9d123393f..ce7db5608 100644 --- a/src/remoteplayer.h +++ b/src/remoteplayer.h @@ -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; } diff --git a/src/script/lua_api/l_server.cpp b/src/script/lua_api/l_server.cpp index 6ac4bb653..d94f3e31d 100644 --- a/src/script/lua_api/l_server.cpp +++ b/src/script/lua_api/l_server.cpp @@ -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(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); diff --git a/src/script/lua_api/l_server.h b/src/script/lua_api/l_server.h index 008810784..e6c0df978 100644 --- a/src/script/lua_api/l_server.h +++ b/src/script/lua_api/l_server.h @@ -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); diff --git a/src/server.cpp b/src/server.cpp index ac6265d09..4c7e60286 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -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,44 +3536,18 @@ 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 \"" - <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())); - - player->protocol_version = proto_version; + bool newplayer = false; - /* Clean up old HUD elements from previous sessions */ - player->clearHud(); + // Load player + PlayerSAO *playersao = m_env->loadPlayer(player, &newplayer, peer_id, isSingleplayer()); - /* Add object to environment */ - m_env->addActiveObject(playersao); + // Complete init with server parts + playersao->finalize(player, getPlayerEffectivePrivs(player->getName())); + player->protocol_version = proto_version; /* Run scripts */ if (newplayer) { diff --git a/src/server.h b/src/server.h index 4183bcda1..948fb8fc2 100644 --- a/src/server.h +++ b/src/server.h @@ -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); diff --git a/src/serverenvironment.cpp b/src/serverenvironment.cpp index e09c7da16..c0dc0e0ea 100644 --- a/src/serverenvironment.cpp +++ b/src/serverenvironment.cpp @@ -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::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; - - 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; + PlayerSAO *playersao = new PlayerSAO(this, player, peer_id, is_singleplayer); + // Create player if it doesn't exist + if (!m_player_database->loadPlayer(player, playersao)) { + *new_player = true; + // Set player position + infostream << "Server: Finding spawn place for player \"" + << player->getName() << "\"" << std::endl; + playersao->setBasePosition(m_server->findSpawnPos()); + + // Make sure the player is saved + player->setModified(true); + } else { + // If the player exists, ensure that they respawn inside legal bounds + // This fixes an assert crash when the player can't be added + // to the environment + if (objectpos_over_limit(playersao->getBasePosition())) { + actionstream << "Respawn position for player \"" + << player->getName() << "\" outside limits, resetting" << std::endl; + playersao->setBasePosition(m_server->findSpawnPos()); } - - path = players_path + playername + itos(i); } - if (!found) { - infostream << "Player file for player " << playername - << " not found" << std::endl; - if (newplayer) - delete player; + // Add player to environment + addPlayer(player); - return NULL; - } + /* Clean up old HUD elements from previous sessions */ + player->clearHud(); - if (newplayer) { - addPlayer(player); - } - player->setModified(false); - return player; + /* Add object to environment */ + addActiveObject(playersao); + + return playersao; } void ServerEnvironment::saveMeta() @@ -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 player_list; + srcdb->listPlayers(player_list); + for (std::vector::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()); + 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; +} diff --git a/src/serverenvironment.h b/src/serverenvironment.h index 99110542a..0e31aa41a 100644 --- a/src/serverenvironment.h +++ b/src/serverenvironment.h @@ -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 m_players; + PlayerDatabase *m_player_database; + // Particles IntervalLimiter m_particle_management_interval; UNORDERED_MAP m_particle_spawners; diff --git a/src/unittest/test_player.cpp b/src/unittest/test_player.cpp index b639878b2..e2b1cd855 100644 --- a/src/unittest/test_player.cpp +++ b/src/unittest/test_player.cpp @@ -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()); - 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()); - 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)); } -- cgit v1.2.3 From f1d7a26b7c341b468f34325cec5c3d495f175a8f Mon Sep 17 00:00:00 2001 From: Ben Deutsch Date: Fri, 17 Mar 2017 10:39:47 +0100 Subject: Add clouds API --- doc/lua_api.txt | 9 ++++ src/client.h | 11 +++++ src/cloudparams.h | 33 ++++++++++++++ src/clouds.cpp | 90 ++++++++++++++++++++----------------- src/clouds.h | 54 +++++++++++++++++++--- src/game.cpp | 13 ++++++ src/network/clientopcodes.cpp | 2 +- src/network/clientpackethandler.cpp | 28 ++++++++++++ src/network/networkprotocol.h | 10 +++++ src/network/serveropcodes.cpp | 2 +- src/remoteplayer.cpp | 8 ++++ src/remoteplayer.h | 12 +++++ src/script/lua_api/l_object.cpp | 81 +++++++++++++++++++++++++++++++++ src/script/lua_api/l_object.h | 6 +++ src/server.cpp | 30 +++++++++++++ src/server.h | 12 +++++ src/sky.cpp | 6 ++- 17 files changed, 357 insertions(+), 50 deletions(-) create mode 100644 src/cloudparams.h (limited to 'src/remoteplayer.cpp') diff --git a/doc/lua_api.txt b/doc/lua_api.txt index eba8a5fef..479e38a2e 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -3077,6 +3077,15 @@ This is basically a reference to a C++ `ServerActiveObject` * `"skybox"`: Uses 6 textures, `bgcolor` used * `"plain"`: Uses 0 textures, `bgcolor` used * `get_sky()`: returns bgcolor, type and a table with the textures +* `set_clouds(parameters)`: set cloud parameters + * `parameters` is a table with the following optional fields: + * `density`: from `0` (no clouds) to `1` (full clouds) (default `0.4`) + * `color`: basic cloud color, with alpha channel (default `#fff0f0e5`) + * `ambient`: cloud color lower bound, use for a "glow at night" effect (default `#000000`) + * `height`: cloud height, i.e. y of cloud base (default per conf, usually `120`) + * `thickness`: cloud thickness in nodes (default `16`) + * `speed`: 2D cloud speed + direction in nodes per second (default `{x=0, y=-2}`) +* `get_clouds()`: returns a table with the current cloud parameters as in `set_clouds` * `override_day_night_ratio(ratio or nil)` * `0`...`1`: Overrides day-night ratio, controlling sunlight to a specific amount * `nil`: Disables override, defaulting to sunlight based on day-night cycle diff --git a/src/client.h b/src/client.h index f5b03f19d..7cbfadd50 100644 --- a/src/client.h +++ b/src/client.h @@ -77,6 +77,7 @@ enum ClientEventType CE_HUDCHANGE, CE_SET_SKY, CE_OVERRIDE_DAY_NIGHT_RATIO, + CE_CLOUD_PARAMS, }; struct ClientEvent @@ -178,6 +179,15 @@ struct ClientEvent bool do_override; float ratio_f; } override_day_night_ratio; + struct { + f32 density; + u32 color_bright; + u32 color_ambient; + f32 height; + f32 thickness; + f32 speed_x; + f32 speed_y; + } cloud_params; }; }; @@ -331,6 +341,7 @@ public: void handleCommand_HudSetFlags(NetworkPacket* pkt); void handleCommand_HudSetParam(NetworkPacket* pkt); void handleCommand_HudSetSky(NetworkPacket* pkt); + void handleCommand_CloudParams(NetworkPacket* pkt); void handleCommand_OverrideDayNightRatio(NetworkPacket* pkt); void handleCommand_LocalPlayerAnimations(NetworkPacket* pkt); void handleCommand_EyeOffset(NetworkPacket* pkt); diff --git a/src/cloudparams.h b/src/cloudparams.h new file mode 100644 index 000000000..dafec4b27 --- /dev/null +++ b/src/cloudparams.h @@ -0,0 +1,33 @@ +/* +Minetest +Copyright (C) 2017 bendeutsch, Ben Deutsch + +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 CLOUDPARAMS_HEADER +#define CLOUDPARAMS_HEADER + +struct CloudParams +{ + float density; + video::SColor color_bright; + video::SColor color_ambient; + float thickness; + float height; + v2f speed; +}; + +#endif diff --git a/src/clouds.cpp b/src/clouds.cpp index 82b63b6b3..627fac47a 100644 --- a/src/clouds.cpp +++ b/src/clouds.cpp @@ -32,6 +32,7 @@ irr::scene::ISceneManager *g_menucloudsmgr = NULL; static void cloud_3d_setting_changed(const std::string &settingname, void *data) { + // TODO: only re-read cloud settings, not height or radius ((Clouds *)data)->readSettings(); } @@ -44,9 +45,10 @@ Clouds::Clouds( ): scene::ISceneNode(parent, mgr, id), m_seed(seed), - m_camera_pos(0,0), - m_time(0), - m_camera_offset(0,0,0) + m_camera_pos(0.0f, 0.0f), + m_origin(0.0f, 0.0f), + m_camera_offset(0.0f, 0.0f, 0.0f), + m_color(1.0f, 1.0f, 1.0f, 1.0f) { m_material.setFlag(video::EMF_LIGHTING, false); //m_material.setFlag(video::EMF_BACK_FACE_CULLING, false); @@ -57,14 +59,18 @@ Clouds::Clouds( //m_material.MaterialType = video::EMT_TRANSPARENT_VERTEX_ALPHA; m_material.MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL; + m_params.density = 0.4f; + m_params.thickness = 16.0f; + m_params.color_bright = video::SColor(229, 255, 240, 240); + m_params.color_ambient = video::SColor(255, 0, 0, 0); + m_params.speed = v2f(0.0f, -2.0f); + m_passed_cloud_y = cloudheight; readSettings(); g_settings->registerChangedCallback("enable_3d_clouds", &cloud_3d_setting_changed, this); - m_box = aabb3f(-BS*1000000,m_cloud_y-BS,-BS*1000000, - BS*1000000,m_cloud_y+BS,BS*1000000); - + updateBox(); } Clouds::~Clouds() @@ -88,6 +94,10 @@ void Clouds::OnRegisterSceneNode() void Clouds::render() { + + if (m_params.density <= 0.0f) + return; // no need to do anything + video::IVideoDriver* driver = SceneManager->getVideoDriver(); if(SceneManager->getSceneNodeRenderPass() != scene::ESNRP_TRANSPARENT) @@ -107,15 +117,12 @@ void Clouds::render() Clouds move from Z+ towards Z- */ - const float cloud_size = BS * 64; - const v2f cloud_speed(0, -BS * 2); + static const float cloud_size = BS * 64.0f; const float cloud_full_radius = cloud_size * m_cloud_radius_i; - // Position of cloud noise origin in world coordinates - v2f world_cloud_origin_pos_f = m_time * cloud_speed; // Position of cloud noise origin from the camera - v2f cloud_origin_from_camera_f = world_cloud_origin_pos_f - m_camera_pos; + v2f cloud_origin_from_camera_f = m_origin - m_camera_pos; // The center point of drawing in the noise v2f center_of_drawing_in_noise_f = -cloud_origin_from_camera_f; // The integer center point of drawing in the noise @@ -127,7 +134,7 @@ void Clouds::render() v2f world_center_of_drawing_in_noise_f = v2f( center_of_drawing_in_noise_i.X * cloud_size, center_of_drawing_in_noise_i.Y * cloud_size - ) + world_cloud_origin_pos_f; + ) + m_origin; /*video::SColor c_top(128,b*240,b*240,b*255); video::SColor c_side_1(128,b*230,b*230,b*255); @@ -146,10 +153,6 @@ void Clouds::render() c_bottom_f.r *= 0.80; c_bottom_f.g *= 0.80; c_bottom_f.b *= 0.80; - c_top_f.a = 0.9; - c_side_1_f.a = 0.9; - c_side_2_f.a = 0.9; - c_bottom_f.a = 0.9; video::SColor c_top = c_top_f.toSColor(); video::SColor c_side_1 = c_side_1_f.toSColor(); video::SColor c_side_2 = c_side_2_f.toSColor(); @@ -187,11 +190,14 @@ void Clouds::render() zi + center_of_drawing_in_noise_i.Y ); - double noise = noise2d_perlin( + float noise = noise2d_perlin( (float)p_in_noise_i.X * cloud_size_noise, (float)p_in_noise_i.Y * cloud_size_noise, m_seed, 3, 0.5); - grid[i] = (noise >= 0.4); + // normalize to 0..1 (given 3 octaves) + static const float noise_bound = 1.0f + 0.5f + 0.25f; + float density = noise / noise_bound * 0.5f + 0.5f; + grid[i] = (density < m_params.density); } } @@ -236,8 +242,9 @@ void Clouds::render() v[3].Color.setBlue(255); }*/ - f32 rx = cloud_size/2; - f32 ry = 8 * BS; + f32 rx = cloud_size / 2.0f; + // if clouds are flat, the top layer should be at the given height + f32 ry = m_enable_3d ? m_params.thickness * BS : 0.0f; f32 rz = cloud_size / 2; for(int i=0; igetU16("cloud_radius"); m_enable_3d = g_settings->getBool("enable_3d_clouds"); } - diff --git a/src/clouds.h b/src/clouds.h index 9c6b41786..a0bda28df 100644 --- a/src/clouds.h +++ b/src/clouds.h @@ -23,6 +23,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "irrlichttypes_extrabloated.h" #include #include "constants.h" +#include "cloudparams.h" // Menu clouds class Clouds; @@ -79,27 +80,68 @@ public: void updateCameraOffset(v3s16 camera_offset) { m_camera_offset = camera_offset; - m_box = aabb3f(-BS * 1000000, m_cloud_y - BS - BS * camera_offset.Y, -BS * 1000000, - BS * 1000000, m_cloud_y + BS - BS * camera_offset.Y, BS * 1000000); + updateBox(); } void readSettings(); + void setDensity(float density) + { + m_params.density = density; + // currently does not need bounding + } + + void setColorBright(const video::SColor &color_bright) + { + m_params.color_bright = color_bright; + } + + void setColorAmbient(const video::SColor &color_ambient) + { + m_params.color_ambient = color_ambient; + } + + void setHeight(float height) + { + m_params.height = height; // add bounding when necessary + updateBox(); + } + + void setSpeed(v2f speed) + { + m_params.speed = speed; + } + + void setThickness(float thickness) + { + m_params.thickness = thickness; + updateBox(); + } + private: + void updateBox() + { + float height_bs = m_params.height * BS; + float thickness_bs = m_params.thickness * BS; + m_box = aabb3f(-BS * 1000000.0f, height_bs - BS * m_camera_offset.Y, -BS * 1000000.0f, + BS * 1000000.0f, height_bs + thickness_bs - BS * m_camera_offset.Y, BS * 1000000.0f); + } + video::SMaterial m_material; aabb3f m_box; s16 m_passed_cloud_y; - float m_cloud_y; u16 m_cloud_radius_i; bool m_enable_3d; - video::SColorf m_color; u32 m_seed; v2f m_camera_pos; - float m_time; + v2f m_origin; + v2f m_speed; v3s16 m_camera_offset; + video::SColorf m_color; + CloudParams m_params; + }; #endif - diff --git a/src/game.cpp b/src/game.cpp index a1cc1ab15..ba6530d80 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -3295,6 +3295,19 @@ void Game::processClientEvents(CameraOrientation *cam) event.override_day_night_ratio.ratio_f * 1000); break; + case CE_CLOUD_PARAMS: + if (clouds) { + clouds->setDensity(event.cloud_params.density); + clouds->setColorBright(video::SColor(event.cloud_params.color_bright)); + clouds->setColorAmbient(video::SColor(event.cloud_params.color_ambient)); + clouds->setHeight(event.cloud_params.height); + clouds->setThickness(event.cloud_params.thickness); + clouds->setSpeed(v2f( + event.cloud_params.speed_x, + event.cloud_params.speed_y)); + } + break; + default: // unknown or unhandled type break; diff --git a/src/network/clientopcodes.cpp b/src/network/clientopcodes.cpp index 563baf77b..1be6e5522 100644 --- a/src/network/clientopcodes.cpp +++ b/src/network/clientopcodes.cpp @@ -108,7 +108,7 @@ const ToClientCommandHandler toClientCommandTable[TOCLIENT_NUM_MSG_TYPES] = { "TOCLIENT_LOCAL_PLAYER_ANIMATIONS", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_LocalPlayerAnimations }, // 0x51 { "TOCLIENT_EYE_OFFSET", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_EyeOffset }, // 0x52 { "TOCLIENT_DELETE_PARTICLESPAWNER", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_DeleteParticleSpawner }, // 0x53 - null_command_handler, + { "TOCLIENT_CLOUD_PARAMS", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_CloudParams }, // 0x54 null_command_handler, null_command_handler, null_command_handler, diff --git a/src/network/clientpackethandler.cpp b/src/network/clientpackethandler.cpp index 772ffe905..defc83f31 100644 --- a/src/network/clientpackethandler.cpp +++ b/src/network/clientpackethandler.cpp @@ -1168,6 +1168,34 @@ void Client::handleCommand_HudSetSky(NetworkPacket* pkt) m_client_event_queue.push(event); } +void Client::handleCommand_CloudParams(NetworkPacket* pkt) +{ + f32 density; + video::SColor color_bright; + video::SColor color_ambient; + f32 height; + f32 thickness; + v2f speed; + + *pkt >> density >> color_bright >> color_ambient + >> height >> thickness >> speed; + + ClientEvent event; + event.type = CE_CLOUD_PARAMS; + event.cloud_params.density = density; + // use the underlying u32 representation, because we can't + // use struct members with constructors here, and this way + // we avoid using new() and delete() for no good reason + event.cloud_params.color_bright = color_bright.color; + event.cloud_params.color_ambient = color_ambient.color; + event.cloud_params.height = height; + event.cloud_params.thickness = thickness; + // same here: deconstruct to skip constructor + event.cloud_params.speed_x = speed.X; + event.cloud_params.speed_y = speed.Y; + m_client_event_queue.push(event); +} + void Client::handleCommand_OverrideDayNightRatio(NetworkPacket* pkt) { bool do_override; diff --git a/src/network/networkprotocol.h b/src/network/networkprotocol.h index e7a3469b7..a1a4f5bfa 100644 --- a/src/network/networkprotocol.h +++ b/src/network/networkprotocol.h @@ -610,6 +610,16 @@ enum ToClientCommand u32 id */ + TOCLIENT_CLOUD_PARAMS = 0x54, + /* + f1000 density + u8[4] color_diffuse (ARGB) + u8[4] color_ambient (ARGB) + f1000 height + f1000 thickness + v2f1000 speed + */ + TOCLIENT_SRP_BYTES_S_B = 0x60, /* Belonging to AUTH_MECHANISM_LEGACY_PASSWORD and AUTH_MECHANISM_SRP. diff --git a/src/network/serveropcodes.cpp b/src/network/serveropcodes.cpp index 31b571ff0..450730ca2 100644 --- a/src/network/serveropcodes.cpp +++ b/src/network/serveropcodes.cpp @@ -197,7 +197,7 @@ const ClientCommandFactory clientCommandFactoryTable[TOCLIENT_NUM_MSG_TYPES] = { "TOCLIENT_LOCAL_PLAYER_ANIMATIONS", 0, true }, // 0x51 { "TOCLIENT_EYE_OFFSET", 0, true }, // 0x52 { "TOCLIENT_DELETE_PARTICLESPAWNER", 0, true }, // 0x53 - null_command_factory, + { "TOCLIENT_CLOUD_PARAMS", 0, true }, // 0x54 null_command_factory, null_command_factory, null_command_factory, diff --git a/src/remoteplayer.cpp b/src/remoteplayer.cpp index 2dbfe9d9d..2b4db62f5 100644 --- a/src/remoteplayer.cpp +++ b/src/remoteplayer.cpp @@ -65,6 +65,14 @@ RemotePlayer::RemotePlayer(const char *name, IItemDefManager *idef): movement_liquid_fluidity_smooth = g_settings->getFloat("movement_liquid_fluidity_smooth") * BS; movement_liquid_sink = g_settings->getFloat("movement_liquid_sink") * BS; movement_gravity = g_settings->getFloat("movement_gravity") * BS; + + // copy defaults + m_cloud_params.density = 0.4f; + m_cloud_params.color_bright = video::SColor(255, 255, 240, 240); + m_cloud_params.color_ambient = video::SColor(255, 0, 0, 0); + m_cloud_params.height = 120.0f; + m_cloud_params.thickness = 16.0f; + m_cloud_params.speed = v2f(0.0f, -2.0f); } void RemotePlayer::serializeExtraAttributes(std::string &output) diff --git a/src/remoteplayer.h b/src/remoteplayer.h index 4b96835fc..b9d9c74f5 100644 --- a/src/remoteplayer.h +++ b/src/remoteplayer.h @@ -22,6 +22,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #define REMOTEPLAYER_HEADER #include "player.h" +#include "cloudparams.h" class PlayerSAO; @@ -99,6 +100,16 @@ public: *params = m_sky_params; } + void setCloudParams(const CloudParams &cloud_params) + { + m_cloud_params = cloud_params; + } + + const CloudParams &getCloudParams() const + { + return m_cloud_params; + } + bool checkModified() const { return m_dirty || inventory.checkModified(); } void setModified(const bool x) @@ -154,6 +165,7 @@ private: std::string m_sky_type; video::SColor m_sky_bgcolor; std::vector m_sky_params; + CloudParams m_cloud_params; }; #endif diff --git a/src/script/lua_api/l_object.cpp b/src/script/lua_api/l_object.cpp index a5b6e3941..6cd852299 100644 --- a/src/script/lua_api/l_object.cpp +++ b/src/script/lua_api/l_object.cpp @@ -1729,6 +1729,85 @@ int ObjectRef::l_get_sky(lua_State *L) return 3; } +// set_clouds(self, {density=, color=, ambient=, height=, thickness=, speed=}) +int ObjectRef::l_set_clouds(lua_State *L) +{ + NO_MAP_LOCK_REQUIRED; + ObjectRef *ref = checkobject(L, 1); + RemotePlayer *player = getplayer(ref); + if (!player) + return 0; + if (!lua_istable(L, 2)) + return 0; + + CloudParams cloud_params = player->getCloudParams(); + + cloud_params.density = getfloatfield_default(L, 2, "density", cloud_params.density); + + lua_getfield(L, 2, "color"); + if (!lua_isnil(L, -1)) + read_color(L, -1, &cloud_params.color_bright); + lua_pop(L, 1); + lua_getfield(L, 2, "ambient"); + if (!lua_isnil(L, -1)) + read_color(L, -1, &cloud_params.color_ambient); + lua_pop(L, 1); + + cloud_params.height = getfloatfield_default(L, 2, "height", cloud_params.height ); + cloud_params.thickness = getfloatfield_default(L, 2, "thickness", cloud_params.thickness); + + lua_getfield(L, 2, "speed"); + if (lua_istable(L, -1)) { + v2f new_speed; + new_speed.X = getfloatfield_default(L, -1, "x", 0); + new_speed.Y = getfloatfield_default(L, -1, "y", 0); + cloud_params.speed = new_speed; + } + lua_pop(L, 1); + + if (!getServer(L)->setClouds(player, cloud_params.density, + cloud_params.color_bright, cloud_params.color_ambient, + cloud_params.height, cloud_params.thickness, + cloud_params.speed)) + return 0; + + player->setCloudParams(cloud_params); + + lua_pushboolean(L, true); + return 1; +} + +int ObjectRef::l_get_clouds(lua_State *L) +{ + NO_MAP_LOCK_REQUIRED; + ObjectRef *ref = checkobject(L, 1); + RemotePlayer *player = getplayer(ref); + if (!player) + return 0; + const CloudParams &cloud_params = player->getCloudParams(); + + lua_newtable(L); + lua_pushnumber(L, cloud_params.density); + lua_setfield(L, -2, "density"); + push_ARGB8(L, cloud_params.color_bright); + lua_setfield(L, -2, "color"); + push_ARGB8(L, cloud_params.color_ambient); + lua_setfield(L, -2, "ambient"); + lua_pushnumber(L, cloud_params.height); + lua_setfield(L, -2, "height"); + lua_pushnumber(L, cloud_params.thickness); + lua_setfield(L, -2, "thickness"); + lua_newtable(L); + lua_pushnumber(L, cloud_params.speed.X); + lua_setfield(L, -2, "x"); + lua_pushnumber(L, cloud_params.speed.Y); + lua_setfield(L, -2, "y"); + lua_setfield(L, -2, "speed"); + + return 1; +} + + // override_day_night_ratio(self, brightness=0...1) int ObjectRef::l_override_day_night_ratio(lua_State *L) { @@ -1911,6 +1990,8 @@ const luaL_Reg ObjectRef::methods[] = { luamethod(ObjectRef, hud_get_hotbar_selected_image), luamethod(ObjectRef, set_sky), luamethod(ObjectRef, get_sky), + luamethod(ObjectRef, set_clouds), + luamethod(ObjectRef, get_clouds), luamethod(ObjectRef, override_day_night_ratio), luamethod(ObjectRef, get_day_night_ratio), luamethod(ObjectRef, set_local_animation), diff --git a/src/script/lua_api/l_object.h b/src/script/lua_api/l_object.h index 98f5c2b11..0912a1c49 100644 --- a/src/script/lua_api/l_object.h +++ b/src/script/lua_api/l_object.h @@ -289,6 +289,12 @@ private: // get_sky(self, type, list) static int l_get_sky(lua_State *L); + // set_clouds(self, {density=, color=, ambient=, height=, thickness=, speed=}) + static int l_set_clouds(lua_State *L); + + // get_clouds(self) + static int l_get_clouds(lua_State *L); + // override_day_night_ratio(self, type) static int l_override_day_night_ratio(lua_State *L); diff --git a/src/server.cpp b/src/server.cpp index 2edf83947..9ef69cb37 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -1882,6 +1882,20 @@ void Server::SendSetSky(u16 peer_id, const video::SColor &bgcolor, Send(&pkt); } +void Server::SendCloudParams(u16 peer_id, float density, + const video::SColor &color_bright, + const video::SColor &color_ambient, + float height, + float thickness, + const v2f &speed) +{ + NetworkPacket pkt(TOCLIENT_CLOUD_PARAMS, 0, peer_id); + pkt << density << color_bright << color_ambient + << height << thickness << speed; + + Send(&pkt); +} + void Server::SendOverrideDayNightRatio(u16 peer_id, bool do_override, float ratio) { @@ -3196,6 +3210,22 @@ bool Server::setSky(RemotePlayer *player, const video::SColor &bgcolor, return true; } +bool Server::setClouds(RemotePlayer *player, float density, + const video::SColor &color_bright, + const video::SColor &color_ambient, + float height, + float thickness, + const v2f &speed) +{ + if (!player) + return false; + + SendCloudParams(player->peer_id, density, + color_bright, color_ambient, height, + thickness, speed); + return true; +} + bool Server::overrideDayNightRatio(RemotePlayer *player, bool do_override, float ratio) { diff --git a/src/server.h b/src/server.h index 948fb8fc2..3a082b9a4 100644 --- a/src/server.h +++ b/src/server.h @@ -332,6 +332,12 @@ public: bool setSky(RemotePlayer *player, const video::SColor &bgcolor, const std::string &type, const std::vector ¶ms); + bool setClouds(RemotePlayer *player, float density, + const video::SColor &color_bright, + const video::SColor &color_ambient, + float height, + float thickness, + const v2f &speed); bool overrideDayNightRatio(RemotePlayer *player, bool do_override, float brightness); @@ -401,6 +407,12 @@ private: void SendHUDSetParam(u16 peer_id, u16 param, const std::string &value); void SendSetSky(u16 peer_id, const video::SColor &bgcolor, const std::string &type, const std::vector ¶ms); + void SendCloudParams(u16 peer_id, float density, + const video::SColor &color_bright, + const video::SColor &color_ambient, + float height, + float thickness, + const v2f &speed); void SendOverrideDayNightRatio(u16 peer_id, bool do_override, float ratio); /* diff --git a/src/sky.cpp b/src/sky.cpp index 211a2dcdc..7f999feb0 100644 --- a/src/sky.cpp +++ b/src/sky.cpp @@ -534,8 +534,10 @@ void Sky::update(float time_of_day, float time_brightness, video::SColorf skycolor_bright_dawn_f = video::SColor(255, 180, 186, 250); video::SColorf skycolor_bright_night_f = video::SColor(255, 0, 107, 255); - video::SColorf cloudcolor_bright_normal_f = video::SColor(255, 240, 240, 255); - video::SColorf cloudcolor_bright_dawn_f = video::SColor(255, 255, 223, 191); + // pure white: becomes "diffuse light component" for clouds + video::SColorf cloudcolor_bright_normal_f = video::SColor(255, 255, 255, 255); + // dawn-factoring version of pure white (note: R is above 1.0) + video::SColorf cloudcolor_bright_dawn_f(255.0f/240.0f, 223.0f/240.0f, 191.0f/255.0f); float cloud_color_change_fraction = 0.95; if (sunlight_seen) { -- cgit v1.2.3 From 76ec6b83133f286581f051dcb1b3da5a52e30b5b Mon Sep 17 00:00:00 2001 From: paramat Date: Tue, 2 May 2017 01:37:23 +0100 Subject: Clouds: Fix reddish clouds. Add missing alpha update Fix accidental swap of red and blue components that caused reddish clouds Add missing update of alpha in remoteplayer.cpp --- src/clouds.cpp | 2 +- src/remoteplayer.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'src/remoteplayer.cpp') diff --git a/src/clouds.cpp b/src/clouds.cpp index 627fac47a..cb8adb37f 100644 --- a/src/clouds.cpp +++ b/src/clouds.cpp @@ -61,7 +61,7 @@ Clouds::Clouds( m_params.density = 0.4f; m_params.thickness = 16.0f; - m_params.color_bright = video::SColor(229, 255, 240, 240); + m_params.color_bright = video::SColor(229, 240, 240, 255); m_params.color_ambient = video::SColor(255, 0, 0, 0); m_params.speed = v2f(0.0f, -2.0f); diff --git a/src/remoteplayer.cpp b/src/remoteplayer.cpp index 2b4db62f5..540132978 100644 --- a/src/remoteplayer.cpp +++ b/src/remoteplayer.cpp @@ -68,7 +68,7 @@ RemotePlayer::RemotePlayer(const char *name, IItemDefManager *idef): // copy defaults m_cloud_params.density = 0.4f; - m_cloud_params.color_bright = video::SColor(255, 255, 240, 240); + m_cloud_params.color_bright = video::SColor(229, 240, 240, 255); m_cloud_params.color_ambient = video::SColor(255, 0, 0, 0); m_cloud_params.height = 120.0f; m_cloud_params.thickness = 16.0f; -- cgit v1.2.3