Add formspec theming using prepended strings
authorAndrew Ward <rw@rubenwardy.com>
Wed, 28 Mar 2018 15:04:41 +0000 (16:04 +0100)
committerGitHub <noreply@github.com>
Wed, 28 Mar 2018 15:04:41 +0000 (16:04 +0100)
16 files changed:
doc/lua_api.txt
src/client.h
src/clientenvironment.h
src/game.cpp
src/gui/guiEngine.cpp
src/gui/guiFormSpecMenu.cpp
src/gui/guiFormSpecMenu.h
src/network/clientopcodes.cpp
src/network/clientpackethandler.cpp
src/network/networkprotocol.h
src/network/serveropcodes.cpp
src/player.h
src/script/lua_api/l_object.cpp
src/script/lua_api/l_object.h
src/server.cpp
src/server.h

index 65604a7ee356053fadb143d574bc025a29d2c969..730e6de9a8f90792de5d592d01d8093e610ac816 100644 (file)
@@ -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[<X>,<Y>]`
 * 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.
index a468aa721e153459ba1281b947d1b5e72c837548..d5d656c2f2c22c1e48435485858e233967402ac6 100644 (file)
@@ -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();
index 61ed687ca2775946094346f41213b43255344866..12070afec723c275f51b3b61415b816b3fabdbab 100644 (file)
@@ -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
index 685fb06517bec3436574fc96946c3ee8662d5d9a..3103d1f2dc34d0ded3d0a8ed4d84a883d2d8e623 100644 (file)
@@ -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;
 }
index 3a41df9a5c335dfc7e0b843b83d7905bbe6f854f..67ab6437d27ba64bf5a56199b675ef44dcefb4d4 100644 (file)
@@ -165,6 +165,7 @@ GUIEngine::GUIEngine(JoystickController *joystick,
                        m_texture_source,
                        m_formspecgui,
                        m_buttonhandler,
+                       "",
                        false);
 
        m_menu->allowClose(false);
index eb80f6ed7bf3c10f554b9f26457c114bc802b15a..8eb74c97f2155698f10ee8ccdbf244532dca537f 100644 (file)
@@ -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<std::string> 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::IGUISkinskin = 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<std::string> prepend_elements = split(m_formspec_prepend, ']');
+               for (const auto &element : prepend_elements)
+                       parseElement(&mydata, element);
+       }
+
        for (; i< elements.size(); i++) {
                parseElement(&mydata, elements[i]);
        }
index 736dd8ddb304227256fdaff51ea8ff5a54e65ec3..7a096a1ea14ff185fd057fcce1bc2aaf58f269bc 100644 (file)
@@ -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 &current_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<ListDrawSpec> m_inventorylists;
index 929827529925b02f204a63da7282e4dab9d9efec..9962de46a2935d79a6f5c089d5186704f51a0507 100644 (file)
@@ -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 };
index 37b6493647ba626f615291281a49a6658cad1098..bc1f18ae85c382251b3813e2bdd8a34a3d789884 100644 (file)
@@ -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;
index 53d36e6669cd2571afc748ff0e00e863aadaf85d..0a5701e59dcfcb417f5749f83038a3600189fae8 100644 (file)
@@ -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,
 };
-
index 6dcf9c93a21dca17baf01455a07470f6faf94af6..883ff9d106cb7f22908fb8f6c4e114b2019883f0 100644 (file)
@@ -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
 };
index e343f55a5432fa698b127d6ba00b9f18523c1646..2d4bfd8390ac0cb69caa59a5b2d1cb5fc033f408 100644 (file)
@@ -148,6 +148,7 @@ public:
        float local_animation_speed;
 
        std::string inventory_formspec;
+       std::string formspec_prepend;
 
        PlayerControl control;
        const PlayerControl& getPlayerControl() { return control; }
index f72a81d322a28d1864d9e10ed88bcf765e8d406b..5fc6f9d3c89823e7263d2171dd911e936d3a4944 100644 (file)
@@ -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),
index 2a76d8a706c32d1929b40c11a31f5187fa3edb97..58cfe7146c2b79222e2abbc8230642fffab9045c 100644 (file)
@@ -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);
 
index 774f3ef124821fd0cf652fa29f71c1adf5519e43..8be223f746715a07a96d9a1781aadefd4b7f8375 100644 (file)
@@ -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);
index b0f65980d670ae69e747db2299395dfb5a09a3a2..0820753e099abc12dfee6ac0846536fbfb1a0fc3 100644 (file)
@@ -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);