diff --git a/doc/lua_api.txt b/doc/lua_api.txt index 65604a7ee..730e6de9a 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -2001,6 +2001,10 @@ supported. It is a string, with a somewhat strange format. Spaces and newlines can be inserted between the blocks, as is used in the examples. +WARNING: Minetest allows you to add elements to every single formspec instance +using player:set_formspec_prepend(), which may be the reason backgrounds are +appearing when you don't expect them to. See `no_prepend[]` + ### Examples #### Chest @@ -2053,6 +2057,10 @@ examples. * `position` and `anchor` elements need suitable values to avoid a formspec extending off the game window due to particular game window sizes. +#### `no_prepend[]` +* Must be used after the `size`, `position`, and `anchor` elements (if present). +* Disables player:set_formspec_prepend() from applying to this formspec. + #### `container[,]` * Start of a container block, moves all physical elements in the container by (X, Y). @@ -4046,6 +4054,13 @@ This is basically a reference to a C++ `ServerActiveObject` * Redefine player's inventory form * Should usually be called in `on_joinplayer` * `get_inventory_formspec()`: returns a formspec string +* `set_formspec_prepend(formspec)`: + * the formspec string will be added to every formspec shown to the user, + except for those with a no_prepend[] tag. + * This should be used to set style elements such as background[] and + bgcolor[], any non-style elements (eg: label) may result in weird behaviour. + * Only affects formspecs shown after this is called. +* `get_formspec_prepend(formspec)`: returns a formspec string. * `get_player_control()`: returns table with player pressed keys * The table consists of fields with boolean value representing the pressed keys, the fields are jump, right, left, LMB, RMB, sneak, aux1, down, up. diff --git a/src/client.h b/src/client.h index a468aa721..d5d656c2f 100644 --- a/src/client.h +++ b/src/client.h @@ -224,7 +224,8 @@ public: void handleCommand_UpdatePlayerList(NetworkPacket* pkt); void handleCommand_ModChannelMsg(NetworkPacket *pkt); void handleCommand_ModChannelSignal(NetworkPacket *pkt); - void handleCommand_SrpBytesSandB(NetworkPacket* pkt); + void handleCommand_SrpBytesSandB(NetworkPacket *pkt); + void handleCommand_FormspecPrepend(NetworkPacket *pkt); void handleCommand_CSMFlavourLimits(NetworkPacket *pkt); void ProcessData(NetworkPacket *pkt); @@ -432,6 +433,10 @@ public: bool sendModChannelMessage(const std::string &channel, const std::string &message); ModChannel *getModChannel(const std::string &channel); + const std::string &getFormspecPrepend() const + { + return m_env.getLocalPlayer()->formspec_prepend; + } private: void loadMods(); bool checkBuiltinIntegrity(); diff --git a/src/clientenvironment.h b/src/clientenvironment.h index 61ed687ca..12070afec 100644 --- a/src/clientenvironment.h +++ b/src/clientenvironment.h @@ -78,7 +78,7 @@ public: void step(f32 dtime); virtual void setLocalPlayer(LocalPlayer *player); - LocalPlayer *getLocalPlayer() { return m_local_player; } + LocalPlayer *getLocalPlayer() const { return m_local_player; } /* ClientSimpleObjects diff --git a/src/game.cpp b/src/game.cpp index 685fb0651..3103d1f2d 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -2028,7 +2028,7 @@ void Game::openInventory() || !client->getScript()->on_inventory_open(fs_src->m_client->getInventory(inventoryloc))) { TextDest *txt_dst = new TextDestPlayerInventory(client); GUIFormSpecMenu::create(current_formspec, client, &input->joystick, fs_src, - txt_dst); + txt_dst, client->getFormspecPrepend()); cur_formname = ""; current_formspec->setFormSpec(fs_src->getForm(), inventoryloc); } @@ -2535,7 +2535,7 @@ void Game::handleClientEvent_ShowFormSpec(ClientEvent *event, CameraOrientation new TextDestPlayerInventory(client, *(event->show_formspec.formname)); GUIFormSpecMenu::create(current_formspec, client, &input->joystick, - fs_src, txt_dst); + fs_src, txt_dst, client->getFormspecPrepend()); cur_formname = *(event->show_formspec.formname); } @@ -2548,7 +2548,8 @@ void Game::handleClientEvent_ShowLocalFormSpec(ClientEvent *event, CameraOrienta FormspecFormSource *fs_src = new FormspecFormSource(*event->show_formspec.formspec); LocalFormspecHandler *txt_dst = new LocalFormspecHandler(*event->show_formspec.formname, client); - GUIFormSpecMenu::create(current_formspec, client, &input->joystick, fs_src, txt_dst); + GUIFormSpecMenu::create(current_formspec, client, &input->joystick, + fs_src, txt_dst, client->getFormspecPrepend()); delete event->show_formspec.formspec; delete event->show_formspec.formname; @@ -3210,7 +3211,7 @@ void Game::handlePointingAtNode(const PointedThing &pointed, TextDest *txt_dst = new TextDestNodeMetadata(nodepos, client); GUIFormSpecMenu::create(current_formspec, client, &input->joystick, fs_src, - txt_dst); + txt_dst, client->getFormspecPrepend()); cur_formname.clear(); current_formspec->setFormSpec(meta->getString("formspec"), inventoryloc); @@ -4105,7 +4106,8 @@ void Game::showPauseMenu() FormspecFormSource *fs_src = new FormspecFormSource(os.str()); LocalFormspecHandler *txt_dst = new LocalFormspecHandler("MT_PAUSE_MENU"); - GUIFormSpecMenu::create(current_formspec, client, &input->joystick, fs_src, txt_dst); + GUIFormSpecMenu::create(current_formspec, client, &input->joystick, + fs_src, txt_dst, client->getFormspecPrepend()); current_formspec->setFocus("btn_continue"); current_formspec->doPause = true; } diff --git a/src/gui/guiEngine.cpp b/src/gui/guiEngine.cpp index 3a41df9a5..67ab6437d 100644 --- a/src/gui/guiEngine.cpp +++ b/src/gui/guiEngine.cpp @@ -165,6 +165,7 @@ GUIEngine::GUIEngine(JoystickController *joystick, m_texture_source, m_formspecgui, m_buttonhandler, + "", false); m_menu->allowClose(false); diff --git a/src/gui/guiFormSpecMenu.cpp b/src/gui/guiFormSpecMenu.cpp index eb80f6ed7..8eb74c97f 100644 --- a/src/gui/guiFormSpecMenu.cpp +++ b/src/gui/guiFormSpecMenu.cpp @@ -86,11 +86,13 @@ inline u32 clamp_u8(s32 value) GUIFormSpecMenu::GUIFormSpecMenu(JoystickController *joystick, gui::IGUIElement *parent, s32 id, IMenuManager *menumgr, Client *client, ISimpleTextureSource *tsrc, IFormSource *fsrc, TextDest *tdst, - bool remap_dbl_click) : + std::string formspecPrepend, + bool remap_dbl_click): GUIModalMenu(RenderingEngine::get_gui_env(), parent, id, menumgr), m_invmgr(client), m_tsrc(tsrc), m_client(client), + m_formspec_prepend(formspecPrepend), m_form_src(fsrc), m_text_dst(tdst), m_joystick(joystick), @@ -128,11 +130,12 @@ GUIFormSpecMenu::~GUIFormSpecMenu() } void GUIFormSpecMenu::create(GUIFormSpecMenu *&cur_formspec, Client *client, - JoystickController *joystick, IFormSource *fs_src, TextDest *txt_dest) + JoystickController *joystick, IFormSource *fs_src, TextDest *txt_dest, + const std::string &formspecPrepend) { if (cur_formspec == nullptr) { cur_formspec = new GUIFormSpecMenu(joystick, guiroot, -1, &g_menumgr, - client, client->getTextureSource(), fs_src, txt_dest); + client, client->getTextureSource(), fs_src, txt_dest, formspecPrepend); cur_formspec->doPause = false; /* @@ -144,6 +147,7 @@ void GUIFormSpecMenu::create(GUIFormSpecMenu *&cur_formspec, Client *client, */ } else { + cur_formspec->setFormspecPrepend(formspecPrepend); cur_formspec->setFormSource(fs_src); cur_formspec->setTextDest(txt_dest); } @@ -2008,7 +2012,6 @@ void GUIFormSpecMenu::regenerateGui(v2u32 screensize) ); } - m_slotbg_n = video::SColor(255,128,128,128); m_slotbg_h = video::SColor(255,192,192,192); @@ -2040,7 +2043,7 @@ void GUIFormSpecMenu::regenerateGui(v2u32 screensize) /* try to read version from first element only */ if (!elements.empty()) { - if ( parseVersionDirect(elements[0]) ) { + if (parseVersionDirect(elements[0])) { i++; } } @@ -2067,6 +2070,18 @@ void GUIFormSpecMenu::regenerateGui(v2u32 screensize) } } + /* "no_prepend" element is always after "position" (or "size" element) if it used */ + bool enable_prepends = true; + for (; i < elements.size(); i++) { + if (elements[i].empty()) + break; + + std::vector parts = split(elements[i], '['); + if (parts[0] == "no_prepend") + enable_prepends = false; + else + break; + } if (mydata.explicit_size) { // compute scaling for specified form size @@ -2120,7 +2135,7 @@ void GUIFormSpecMenu::regenerateGui(v2u32 screensize) // multiplied by gui_scaling, even if this means // the form doesn't fit the screen. double prefer_imgsize = mydata.screensize.Y / 15 * - gui_scaling; + gui_scaling; double fitx_imgsize = mydata.screensize.X / ((5.0/4.0) * (0.5 + mydata.invsize.X)); double fity_imgsize = mydata.screensize.Y / @@ -2173,12 +2188,19 @@ void GUIFormSpecMenu::regenerateGui(v2u32 screensize) mydata.basepos = getBasePos(); m_tooltip_element->setOverrideFont(m_font); - gui::IGUISkin* skin = Environment->getSkin(); + gui::IGUISkin *skin = Environment->getSkin(); sanity_check(skin); gui::IGUIFont *old_font = skin->getFont(); skin->setFont(m_font); pos_offset = v2s32(); + + if (enable_prepends) { + std::vector prepend_elements = split(m_formspec_prepend, ']'); + for (const auto &element : prepend_elements) + parseElement(&mydata, element); + } + for (; i< elements.size(); i++) { parseElement(&mydata, elements[i]); } diff --git a/src/gui/guiFormSpecMenu.h b/src/gui/guiFormSpecMenu.h index 736dd8ddb..7a096a1ea 100644 --- a/src/gui/guiFormSpecMenu.h +++ b/src/gui/guiFormSpecMenu.h @@ -287,12 +287,14 @@ public: ISimpleTextureSource *tsrc, IFormSource* fs_src, TextDest* txt_dst, + std::string formspecPrepend, bool remap_dbl_click = true); ~GUIFormSpecMenu(); static void create(GUIFormSpecMenu *&cur_formspec, Client *client, - JoystickController *joystick, IFormSource *fs_src, TextDest *txt_dest); + JoystickController *joystick, IFormSource *fs_src, TextDest *txt_dest, + const std::string &formspecPrepend); void setFormSpec(const std::string &formspec_string, const InventoryLocation ¤t_inventory_location) @@ -302,6 +304,11 @@ public: regenerateGui(m_screensize_old); } + void setFormspecPrepend(const std::string &formspecPrepend) + { + m_formspec_prepend = formspecPrepend; + } + // form_src is deleted by this GUIFormSpecMenu void setFormSource(IFormSource *form_src) { @@ -378,6 +385,7 @@ protected: Client *m_client; std::string m_formspec_string; + std::string m_formspec_prepend; InventoryLocation m_current_inventory_location; std::vector m_inventorylists; diff --git a/src/network/clientopcodes.cpp b/src/network/clientopcodes.cpp index 929827529..9962de46a 100644 --- a/src/network/clientopcodes.cpp +++ b/src/network/clientopcodes.cpp @@ -121,6 +121,7 @@ const ToClientCommandHandler toClientCommandTable[TOCLIENT_NUM_MSG_TYPES] = null_command_handler, 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, }; const static ServerCommandFactory null_command_factory = { "TOSERVER_NULL", 0, false }; diff --git a/src/network/clientpackethandler.cpp b/src/network/clientpackethandler.cpp index 37b649364..bc1f18ae8 100644 --- a/src/network/clientpackethandler.cpp +++ b/src/network/clientpackethandler.cpp @@ -1324,6 +1324,15 @@ void Client::handleCommand_SrpBytesSandB(NetworkPacket* pkt) Send(&resp_pkt); } +void Client::handleCommand_FormspecPrepend(NetworkPacket *pkt) +{ + LocalPlayer *player = m_env.getLocalPlayer(); + assert(player != NULL); + + // Store formspec in LocalPlayer + *pkt >> player->formspec_prepend; +} + void Client::handleCommand_CSMFlavourLimits(NetworkPacket *pkt) { *pkt >> m_csm_flavour_limits >> m_csm_noderange_limit; diff --git a/src/network/networkprotocol.h b/src/network/networkprotocol.h index 53d36e666..0a5701e59 100644 --- a/src/network/networkprotocol.h +++ b/src/network/networkprotocol.h @@ -187,6 +187,7 @@ with this program; if not, write to the Free Software Foundation, Inc., 'zoom_fov'. Nodebox version 5 Add disconnected nodeboxes + Add TOCLIENT_FORMSPEC_PREPEND */ #define LATEST_PROTOCOL_VERSION 36 @@ -644,7 +645,13 @@ enum ToClientCommand std::string bytes_B */ - TOCLIENT_NUM_MSG_TYPES = 0x61, + TOCLIENT_FORMSPEC_PREPEND = 0x61, + /* + u16 len + u8[len] formspec + */ + + TOCLIENT_NUM_MSG_TYPES = 0x62, }; enum ToServerCommand @@ -930,4 +937,3 @@ enum CSMFlavourLimit : u64 { CSM_FL_LOOKUP_NODES = 0x00000010, // Limit node lookups CSM_FL_ALL = 0xFFFFFFFF, }; - diff --git a/src/network/serveropcodes.cpp b/src/network/serveropcodes.cpp index 6dcf9c93a..883ff9d10 100644 --- a/src/network/serveropcodes.cpp +++ b/src/network/serveropcodes.cpp @@ -210,4 +210,5 @@ const ClientCommandFactory clientCommandFactoryTable[TOCLIENT_NUM_MSG_TYPES] = null_command_factory, null_command_factory, { "TOSERVER_SRP_BYTES_S_B", 0, true }, // 0x60 + { "TOCLIENT_FORMSPEC_PREPEND", 0, true }, // 0x61 }; diff --git a/src/player.h b/src/player.h index e343f55a5..2d4bfd839 100644 --- a/src/player.h +++ b/src/player.h @@ -148,6 +148,7 @@ public: float local_animation_speed; std::string inventory_formspec; + std::string formspec_prepend; PlayerControl control; const PlayerControl& getPlayerControl() { return control; } diff --git a/src/script/lua_api/l_object.cpp b/src/script/lua_api/l_object.cpp index f72a81d32..5fc6f9d3c 100644 --- a/src/script/lua_api/l_object.cpp +++ b/src/script/lua_api/l_object.cpp @@ -1244,6 +1244,37 @@ int ObjectRef::l_get_inventory_formspec(lua_State *L) return 1; } +// set_formspec_prepend(self, formspec) +int ObjectRef::l_set_formspec_prepend(lua_State *L) +{ + NO_MAP_LOCK_REQUIRED; + ObjectRef *ref = checkobject(L, 1); + RemotePlayer *player = getplayer(ref); + if (player == NULL) + return 0; + + std::string formspec = luaL_checkstring(L, 2); + + player->formspec_prepend = formspec; + getServer(L)->reportFormspecPrependModified(player->getName()); + lua_pushboolean(L, true); + return 1; +} + +// get_formspec_prepend(self) -> formspec +int ObjectRef::l_get_formspec_prepend(lua_State *L) +{ + NO_MAP_LOCK_REQUIRED; + ObjectRef *ref = checkobject(L, 1); + RemotePlayer *player = getplayer(ref); + if (player == NULL) + return 0; + + std::string formspec = player->formspec_prepend; + lua_pushlstring(L, formspec.c_str(), formspec.size()); + return 1; +} + // get_player_control(self) int ObjectRef::l_get_player_control(lua_State *L) { @@ -1817,6 +1848,8 @@ const luaL_Reg ObjectRef::methods[] = { luamethod(ObjectRef, set_attribute), luamethod(ObjectRef, set_inventory_formspec), luamethod(ObjectRef, get_inventory_formspec), + luamethod(ObjectRef, set_formspec_prepend), + luamethod(ObjectRef, get_formspec_prepend), luamethod(ObjectRef, get_player_control), luamethod(ObjectRef, get_player_control_bits), luamethod(ObjectRef, set_physics_override), diff --git a/src/script/lua_api/l_object.h b/src/script/lua_api/l_object.h index 2a76d8a70..58cfe7146 100644 --- a/src/script/lua_api/l_object.h +++ b/src/script/lua_api/l_object.h @@ -253,6 +253,12 @@ private: // get_inventory_formspec(self) -> formspec static int l_get_inventory_formspec(lua_State *L); + // set_formspec_prepend(self, formspec) + static int l_set_formspec_prepend(lua_State *L); + + // get_formspec_prepend(self) -> formspec + static int l_get_formspec_prepend(lua_State *L); + // get_player_control(self) static int l_get_player_control(lua_State *L); diff --git a/src/server.cpp b/src/server.cpp index 774f3ef12..8be223f74 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -1870,7 +1870,7 @@ void Server::SendPlayerInventoryFormspec(session_t peer_id) { RemotePlayer *player = m_env->getPlayer(peer_id); assert(player); - if(player->getPeerId() == PEER_ID_INEXISTENT) + if (player->getPeerId() == PEER_ID_INEXISTENT) return; NetworkPacket pkt(TOCLIENT_INVENTORY_FORMSPEC, 0, peer_id); @@ -1878,6 +1878,18 @@ void Server::SendPlayerInventoryFormspec(session_t peer_id) Send(&pkt); } +void Server::SendPlayerFormspecPrepend(session_t peer_id) +{ + RemotePlayer *player = m_env->getPlayer(peer_id); + assert(player); + if (player->getPeerId() == PEER_ID_INEXISTENT) + return; + + NetworkPacket pkt(TOCLIENT_FORMSPEC_PREPEND, 0, peer_id); + pkt << FORMSPEC_VERSION_STRING + player->formspec_prepend; + Send(&pkt); +} + u32 Server::SendActiveObjectRemoveAdd(session_t peer_id, const std::string &datas) { NetworkPacket pkt(TOCLIENT_ACTIVE_OBJECT_REMOVE_ADD, datas.size(), peer_id); @@ -2918,6 +2930,14 @@ void Server::reportInventoryFormspecModified(const std::string &name) SendPlayerInventoryFormspec(player->getPeerId()); } +void Server::reportFormspecPrependModified(const std::string &name) +{ + RemotePlayer *player = m_env->getPlayer(name.c_str()); + if (!player) + return; + SendPlayerFormspecPrepend(player->getPeerId()); +} + void Server::setIpBanned(const std::string &ip, const std::string &name) { m_banmanager->add(ip, name); diff --git a/src/server.h b/src/server.h index b0f65980d..0820753e0 100644 --- a/src/server.h +++ b/src/server.h @@ -216,6 +216,7 @@ public: bool checkPriv(const std::string &name, const std::string &priv); void reportPrivsModified(const std::string &name=""); // ""=all void reportInventoryFormspecModified(const std::string &name); + void reportFormspecPrependModified(const std::string &name); void setIpBanned(const std::string &ip, const std::string &name); void unsetIpBanned(const std::string &ip_or_name); @@ -376,6 +377,7 @@ private: void SendEyeOffset(session_t peer_id, v3f first, v3f third); void SendPlayerPrivileges(session_t peer_id); void SendPlayerInventoryFormspec(session_t peer_id); + void SendPlayerFormspecPrepend(session_t peer_id); void SendShowFormspecMessage(session_t peer_id, const std::string &formspec, const std::string &formname); void SendHUDAdd(session_t peer_id, u32 id, HudElement *form);