diff --git a/doc/lua_api.md b/doc/lua_api.md index f60575044..6afbce1ac 100644 --- a/doc/lua_api.md +++ b/doc/lua_api.md @@ -8418,6 +8418,13 @@ child will follow movement and rotation of that bone. * `get_lighting()`: returns the current state of lighting for the player. * Result is a table with the same fields as `light_definition` in `set_lighting`. +* `set_node_visual(node_name, node_visual)`: sets `node_visual` of `node_name` for the player + * `node_name` is a name of registered node. + * `node_visual` is a table with the following optional fields: + * `variant_offset` this value is added to variant from node param2 value (default: `0`). + +* `get_node_visual(node_name)`: returns the current `node_visual` of `node_name` for the player. + * Result is a table with the same fields as `node_visual` in `set_node_visual`. * `respawn()`: Respawns the player using the same mechanism as the death screen, including calling `on_respawnplayer` callbacks. diff --git a/games/devtest/mods/testnodes/init.lua b/games/devtest/mods/testnodes/init.lua index 51adf58fb..79b557a58 100644 --- a/games/devtest/mods/testnodes/init.lua +++ b/games/devtest/mods/testnodes/init.lua @@ -12,3 +12,4 @@ dofile(path.."/textures.lua") dofile(path.."/overlays.lua") dofile(path.."/variants.lua") dofile(path.."/commands.lua") +dofile(path.."/node_visual.lua") diff --git a/games/devtest/mods/testnodes/node_visual.lua b/games/devtest/mods/testnodes/node_visual.lua new file mode 100644 index 000000000..55915eaa6 --- /dev/null +++ b/games/devtest/mods/testnodes/node_visual.lua @@ -0,0 +1,48 @@ +-- add command to change node_visual + +minetest.register_chatcommand("node_visual", { + params = "nodename field [value]", + description = "Change node_visual field of actual player to value or show value of field.", + func = function(name, param) + local player = minetest.get_player_by_name(name) + if not player then + return false, "No player." + end + + local splits = string.split(param, " ", false, 3) + + if #splits < 2 then + return false, "Expected node name and node_visual field as parameters." + end + + local node_name = splits[1] + local field_name = splits[2] + + if not minetest.registered_nodes[node_name] then + return false, "Unknown node "..node_name + end + + local node_visual = player:get_node_visual(node_name) + + if rawequal(node_visual[field_name], nil) then + return false, "Field "..field_name.." not found in node_visual." + end + + if #splits > 2 then + if type(node_visual[field_name]) == "number" then + node_visual[field_name] = tonumber(splits[3]) + elseif type(node_visual[field_name]) == "table" then + node_visual[field_name] = minetest.parse_json(splits[3]) + if type(node_visual[field_name]) ~= "table" then + return false, "Table in json format is expected as value." + end + else + node_visual[field_name] = splits[3] + end + player:set_node_visual(node_name, node_visual) + return true, "Node "..node_name.." node_visual field "..field_name.." set to value: "..dump(node_visual[field_name]) + else + return true, "Node "..node_name.." node_visual field "..field_name.." have value: "..dump(node_visual[field_name]) + end + end +}) diff --git a/src/client/client.h b/src/client/client.h index 42049791f..ef32a7c00 100644 --- a/src/client/client.h +++ b/src/client/client.h @@ -233,6 +233,7 @@ public: void handleCommand_MediaPush(NetworkPacket *pkt); void handleCommand_MinimapModes(NetworkPacket *pkt); void handleCommand_SetLighting(NetworkPacket *pkt); + void handleCommand_SetNodeVisual(NetworkPacket *pkt); void ProcessData(NetworkPacket *pkt); diff --git a/src/database/database-files.cpp b/src/database/database-files.cpp index 518c776ea..eb3c354e6 100644 --- a/src/database/database-files.cpp +++ b/src/database/database-files.cpp @@ -156,7 +156,7 @@ 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); + RemotePlayer testplayer("", NULL, NULL); for (u32 i = 0; i < PLAYER_FILE_ALTERNATE_TRIES && !path_found; i++) { if (!fs::PathExists(path)) { @@ -202,7 +202,7 @@ 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); + RemotePlayer temp_player("", NULL, 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); @@ -264,7 +264,7 @@ void PlayerDatabaseFiles::listPlayers(std::vector &res) if (!is.good()) continue; - RemotePlayer player(filename.c_str(), NULL); + RemotePlayer player(filename.c_str(), NULL, NULL); // Null env & dummy peer_id PlayerSAO playerSAO(NULL, &player, 15789, false); diff --git a/src/mapnode.cpp b/src/mapnode.cpp index 4c3f1a815..9f0ef201e 100644 --- a/src/mapnode.cpp +++ b/src/mapnode.cpp @@ -55,7 +55,9 @@ void MapNode::getColor(const ContentFeatures &f, video::SColor *color) const u16 MapNode::getVariant(const ContentFeatures &f) const { - return f.variant_count > 1 ? f.param2_variant.get(param2) % f.variant_count : 0; + if (f.variant_count > 1) + return (f.param2_variant.get(param2) + f.variant_offset) % f.variant_count; + return 0; } u8 MapNode::getFaceDir(const NodeDefManager *nodemgr, diff --git a/src/network/clientopcodes.cpp b/src/network/clientopcodes.cpp index d426d3fe7..9baa414c2 100644 --- a/src/network/clientopcodes.cpp +++ b/src/network/clientopcodes.cpp @@ -120,7 +120,7 @@ const ToClientCommandHandler toClientCommandTable[TOCLIENT_NUM_MSG_TYPES] = { "TOCLIENT_SET_MOON", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_HudSetMoon }, // 0x5b { "TOCLIENT_SET_STARS", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_HudSetStars }, // 0x5c { "TOCLIENT_MOVE_PLAYER_REL", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_MovePlayerRel }, // 0x5d, - null_command_handler, + { "TOCLIENT_SET_NODE_VISUAL", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_SetNodeVisual }, // 0x5e null_command_handler, { "TOCLIENT_SRP_BYTES_S_B", TOCLIENT_STATE_NOT_CONNECTED, &Client::handleCommand_SrpBytesSandB }, // 0x60 { "TOCLIENT_FORMSPEC_PREPEND", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_FormspecPrepend }, // 0x61, diff --git a/src/network/clientpackethandler.cpp b/src/network/clientpackethandler.cpp index 90f2bed5b..57258b27c 100644 --- a/src/network/clientpackethandler.cpp +++ b/src/network/clientpackethandler.cpp @@ -1816,3 +1816,16 @@ void Client::handleCommand_SetLighting(NetworkPacket *pkt) if (pkt->getRemainingBytes() >= 4) *pkt >> lighting.volumetric_light_strength; } + +void Client::handleCommand_SetNodeVisual(NetworkPacket *pkt) +{ + std::string node_name; + NodeVisual node_visual; + + *pkt >> node_name; + + if (pkt->getRemainingBytes() >= 2) + *pkt >> node_visual.variant_offset; + + m_nodedef->applyNodeVisual(node_name, node_visual); +} diff --git a/src/network/networkprotocol.h b/src/network/networkprotocol.h index add80b3b2..16d7bca51 100644 --- a/src/network/networkprotocol.h +++ b/src/network/networkprotocol.h @@ -811,6 +811,12 @@ enum ToClientCommand : u16 v3f added_pos */ + TOCLIENT_SET_NODE_VISUAL = 0x5e, + /* + std::string nodename + u16 variant_offset + */ + TOCLIENT_SRP_BYTES_S_B = 0x60, /* Belonging to AUTH_MECHANISM_SRP. diff --git a/src/nodedef.cpp b/src/nodedef.cpp index 464f5d54b..fd928c6b0 100644 --- a/src/nodedef.cpp +++ b/src/nodedef.cpp @@ -390,6 +390,7 @@ void ContentFeatures::reset() param_type = CPT_NONE; param_type_2 = CPT2_NONE; variant_count = 1; + variant_offset = 0; param2_variant = BitField(); is_ground_content = false; light_propagates = false; @@ -1098,6 +1099,22 @@ void ContentFeatures::updateTextures(ITextureSource *tsrc, IShaderSource *shdsrc } #endif +/* + NodeVisual +*/ + +NodeVisual::NodeVisual() : variant_offset(0) +{ +} +NodeVisual::NodeVisual(const ContentFeatures &f) +{ + from_contentFeature(f); +} +void NodeVisual::from_contentFeature(const ContentFeatures &f) +{ + variant_offset = f.variant_offset; +} + /* NodeDefManager */ @@ -1249,6 +1266,21 @@ const ContentFeatures& NodeDefManager::get(const std::string &name) const return get(id); } +void NodeDefManager::getNodeVisual(const std::string &name, NodeVisual &node_visual) const +{ + const ContentFeatures &f = get(name); + node_visual.from_contentFeature(f); +} + +void NodeDefManager::applyNodeVisual(const std::string &name, const NodeVisual &node_visual) +{ + content_t c = getId(name); + if (c < m_content_features.size() && !m_content_features[c].name.empty()) { + ContentFeatures& f = m_content_features[c]; + + f.variant_offset = node_visual.variant_offset; + } +} // returns CONTENT_IGNORE if no free ID found content_t NodeDefManager::allocateId() diff --git a/src/nodedef.h b/src/nodedef.h index 2d18f96fe..2fa2bb2ed 100644 --- a/src/nodedef.h +++ b/src/nodedef.h @@ -350,6 +350,8 @@ struct ContentFeatures ContentParamType2 param_type_2; // Number of node variants u16 variant_count = 1; + // Node variant offset + u16 variant_offset = 0; // Bit field for variant in param2 BitField param2_variant; @@ -547,6 +549,15 @@ private: u8 getAlphaForLegacy() const; }; +struct NodeVisual { + u16 variant_offset = 0; + + NodeVisual(); + NodeVisual(const ContentFeatures &f); + + void from_contentFeature(const ContentFeatures &f); +}; + /*! * @brief This class is for getting the actual properties of nodes from their * content ID. @@ -642,6 +653,20 @@ public: return m_selection_box_int_union; } + /*! + * Get NodeVisual object of node + * @param name a node name + * @param node_visual NodeVisual object to be set + */ + void getNodeVisual(const std::string &name, NodeVisual &node_visual) const; + + /*! + * Apply NodeVisual object to node + * @param name a node name + * @param node_visual NodeVisual object to be applied to node + */ + void applyNodeVisual(const std::string &name, const NodeVisual &node_visual); + /*! * Checks whether a node connects to an adjacent node. * @param from the node to be checked diff --git a/src/remoteplayer.cpp b/src/remoteplayer.cpp index 9658dca06..d9ecfbcd3 100644 --- a/src/remoteplayer.cpp +++ b/src/remoteplayer.cpp @@ -37,7 +37,7 @@ bool RemotePlayer::m_setting_cache_loaded = false; float RemotePlayer::m_setting_chat_message_limit_per_10sec = 0.0f; u16 RemotePlayer::m_setting_chat_message_limit_trigger_kick = 0; -RemotePlayer::RemotePlayer(const char *name, IItemDefManager *idef): +RemotePlayer::RemotePlayer(const char *name, IItemDefManager *idef, NodeDefManager *ndef): Player(name, idef) { if (!RemotePlayer::m_setting_cache_loaded) { @@ -67,6 +67,9 @@ RemotePlayer::RemotePlayer(const char *name, IItemDefManager *idef): m_sun_params = SkyboxDefaults::getSunDefaults(); m_moon_params = SkyboxDefaults::getMoonDefaults(); m_star_params = SkyboxDefaults::getStarDefaults(); + + // NodeDefManager forNodeDefManager for NodeVisual + m_ndef = ndef; } RemotePlayer::~RemotePlayer() @@ -114,6 +117,23 @@ RemotePlayerChatResult RemotePlayer::canSendChatMessage() return RPLAYER_CHATRESULT_OK; } +void RemotePlayer::setNodeVisual(const std::string &node_name, const NodeVisual &node_visual) +{ + content_t c = m_ndef->getId(node_name); + + m_node_visuals[c] = node_visual; +} + +void RemotePlayer::getNodeVisual(const std::string &node_name, NodeVisual &node_visual) +{ + content_t c = m_ndef->getId(node_name); + + if (m_node_visuals.find(c) != m_node_visuals.end()) + node_visual = m_node_visuals[c]; + else + node_visual.from_contentFeature(m_ndef->get(c)); +} + void RemotePlayer::onSuccessfulSave() { setModified(false); diff --git a/src/remoteplayer.h b/src/remoteplayer.h index a38f31731..d3b4ef966 100644 --- a/src/remoteplayer.h +++ b/src/remoteplayer.h @@ -23,6 +23,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "player.h" #include "skyparams.h" #include "lighting.h" +#include "nodedef.h" class PlayerSAO; @@ -41,7 +42,7 @@ class RemotePlayer : public Player friend class PlayerDatabaseFiles; public: - RemotePlayer(const char *name, IItemDefManager *idef); + RemotePlayer(const char *name, IItemDefManager *idef, NodeDefManager *ndef); virtual ~RemotePlayer(); PlayerSAO *getPlayerSAO() { return m_sao; } @@ -130,6 +131,10 @@ public: const Lighting& getLighting() const { return m_lighting; } + void setNodeVisual(const std::string &node_name, const NodeVisual &node_visual); + + void getNodeVisual(const std::string &node_name, NodeVisual &node_visual); + void setDirty(bool dirty) { m_dirty = true; } u16 protocol_version = 0; @@ -168,5 +173,8 @@ private: Lighting m_lighting; + NodeDefManager *m_ndef; + std::map m_node_visuals; + session_t m_peer_id = PEER_ID_INEXISTENT; }; diff --git a/src/script/lua_api/l_object.cpp b/src/script/lua_api/l_object.cpp index ad4b7af41..6767ab28a 100644 --- a/src/script/lua_api/l_object.cpp +++ b/src/script/lua_api/l_object.cpp @@ -30,6 +30,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "remoteplayer.h" #include "server.h" #include "hud.h" +#include "nodedef.h" #include "scripting_server.h" #include "server/luaentity_sao.h" #include "server/player_sao.h" @@ -2584,6 +2585,50 @@ int ObjectRef::l_get_lighting(lua_State *L) return 1; } +// set_node_visual(self, node_name, node_visual) +int ObjectRef::l_set_node_visual(lua_State *L) +{ + NO_MAP_LOCK_REQUIRED; + ObjectRef *ref = checkObject(L, 1); + RemotePlayer *player = getplayer(ref); + if (player == nullptr) + return 0; + + std::string node_name = readParam(L, 2); + + NodeVisual node_visual; + player->getNodeVisual(node_name, node_visual); + NodeVisual new_visual = node_visual; + + if (!lua_isnoneornil(L, 3)) { + luaL_checktype(L, 3, LUA_TTABLE); + new_visual.variant_offset = getfloatfield_default(L, -1, "variant_offset", node_visual.variant_offset); + } + + getServer(L)->setNodeVisual(player, node_name, new_visual); + return 0; +} + +// get_node_visual(self, node_name) +int ObjectRef::l_get_node_visual(lua_State *L) +{ + NO_MAP_LOCK_REQUIRED; + ObjectRef *ref = checkObject(L, 1); + RemotePlayer *player = getplayer(ref); + if (player == nullptr) + return 0; + + std::string node_name = readParam(L, 2); + + NodeVisual node_visual; + player->getNodeVisual(node_name, node_visual); + + lua_newtable(L); // result + lua_pushnumber(L, node_visual.variant_offset); + lua_setfield(L, -2, "variant_offset"); + return 1; +} + // respawn(self) int ObjectRef::l_respawn(lua_State *L) { @@ -2743,6 +2788,8 @@ luaL_Reg ObjectRef::methods[] = { luamethod(ObjectRef, set_minimap_modes), luamethod(ObjectRef, set_lighting), luamethod(ObjectRef, get_lighting), + luamethod(ObjectRef, set_node_visual), + luamethod(ObjectRef, get_node_visual), luamethod(ObjectRef, respawn), {0,0} diff --git a/src/script/lua_api/l_object.h b/src/script/lua_api/l_object.h index 73264db10..d619ddbe6 100644 --- a/src/script/lua_api/l_object.h +++ b/src/script/lua_api/l_object.h @@ -397,6 +397,12 @@ private: // get_lighting(self) static int l_get_lighting(lua_State *L); + // set_node_visual(self, node_name, node_visual) + static int l_set_node_visual(lua_State *L); + + // get_node_visual(self, node_name) + static int l_get_node_visual(lua_State *L); + // respawn(self) static int l_respawn(lua_State *L); }; diff --git a/src/server.cpp b/src/server.cpp index 5dd2d4691..d9221b382 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -1900,6 +1900,17 @@ void Server::SendSetLighting(session_t peer_id, const Lighting &lighting) Send(&pkt); } +void Server::SendSetNodeVisual(session_t peer_id, const std::string &node_name, const NodeVisual &node_visual) +{ + NetworkPacket pkt(TOCLIENT_SET_NODE_VISUAL, + 4, peer_id); + + pkt << node_name; + pkt << node_visual.variant_offset; + + Send(&pkt); +} + void Server::SendTimeOfDay(session_t peer_id, u16 time, f32 time_speed) { NetworkPacket pkt(TOCLIENT_TIME_OF_DAY, 0, peer_id); @@ -3542,6 +3553,13 @@ void Server::setLighting(RemotePlayer *player, const Lighting &lighting) SendSetLighting(player->getPeerId(), lighting); } +void Server::setNodeVisual(RemotePlayer *player, const std::string &node_name, const NodeVisual &node_visual) +{ + sanity_check(player); + player->setNodeVisual(node_name, node_visual); + SendSetNodeVisual(player->getPeerId(), node_name, node_visual); +} + void Server::notifyPlayers(const std::wstring &msg) { SendChatMessage(PEER_ID_INEXISTENT, ChatMessage(msg)); @@ -4047,7 +4065,7 @@ PlayerSAO* Server::emergePlayer(const char *name, session_t peer_id, u16 proto_v */ if (!player) { - player = new RemotePlayer(name, idef()); + player = new RemotePlayer(name, idef(), m_nodedef); } bool newplayer = false; diff --git a/src/server.h b/src/server.h index 16c1ea4cc..c44c4847d 100644 --- a/src/server.h +++ b/src/server.h @@ -38,6 +38,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "chatmessage.h" #include "sound.h" #include "translation.h" +#include "nodedef.h" #include #include #include @@ -359,6 +360,8 @@ public: void setLighting(RemotePlayer *player, const Lighting &lighting); + void setNodeVisual(RemotePlayer *player, const std::string &node_name, const NodeVisual &node_visual); + void RespawnPlayer(session_t peer_id); /* con::PeerHandler implementation. */ @@ -519,6 +522,7 @@ private: void SendCloudParams(session_t peer_id, const CloudParams ¶ms); void SendOverrideDayNightRatio(session_t peer_id, bool do_override, float ratio); void SendSetLighting(session_t peer_id, const Lighting &lighting); + void SendSetNodeVisual(session_t peer_id, const std::string &node_name, const NodeVisual &node_visual); void broadcastModChannelMessage(const std::string &channel, const std::string &message, session_t from_peer); diff --git a/src/serverenvironment.cpp b/src/serverenvironment.cpp index 8a7b568eb..daca69f04 100644 --- a/src/serverenvironment.cpp +++ b/src/serverenvironment.cpp @@ -2381,7 +2381,7 @@ bool ServerEnvironment::migratePlayersDatabase(const GameParams &game_params, 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); + RemotePlayer player(it->c_str(), NULL, NULL); PlayerSAO playerSAO(NULL, &player, 15000, false); srcdb->loadPlayer(&player, &playerSAO);