Add support for statbar “off state” icons (#9462)
authorWuzzy <wuzzy2@mail.ru>
Mon, 11 May 2020 19:40:45 +0000 (21:40 +0200)
committerGitHub <noreply@github.com>
Mon, 11 May 2020 19:40:45 +0000 (21:40 +0200)
This adds support for optional “off state” icons for statbars. “off state icons” can be used to denote the lack of something, like missing hearts or bubbles.

Add "off state" textures to the builtin statbars.

Co-authored-by: SmallJoker <mk939@ymail.com>
15 files changed:
builtin/game/statbars.lua
doc/lua_api.txt
doc/texture_packs.txt
src/client/clientevent.h
src/client/game.cpp
src/client/hud.cpp
src/client/hud.h
src/hud.cpp
src/hud.h
src/network/clientpackethandler.cpp
src/network/networkprotocol.h
src/script/common/c_content.cpp
src/server.cpp
textures/base/pack/bubble_gone.png [new file with mode: 0644]
textures/base/pack/heart_gone.png [new file with mode: 0644]

index 6b5b5442833d90626146be78b9cac6e96d33c4d7..d192029c586dda6bf57c6da1c4b47a7587fedff2 100644 (file)
@@ -5,7 +5,9 @@ local health_bar_definition = {
        hud_elem_type = "statbar",
        position = {x = 0.5, y = 1},
        text = "heart.png",
+       text2 = "heart_gone.png",
        number = core.PLAYER_MAX_HP_DEFAULT,
+       item = core.PLAYER_MAX_HP_DEFAULT,
        direction = 0,
        size = {x = 24, y = 24},
        offset = {x = (-10 * 24) - 25, y = -(48 + 24 + 16)},
@@ -15,7 +17,9 @@ local breath_bar_definition = {
        hud_elem_type = "statbar",
        position = {x = 0.5, y = 1},
        text = "bubble.png",
+       text2 = "bubble_gone.png",
        number = core.PLAYER_MAX_BREATH_DEFAULT,
+       item = core.PLAYER_MAX_BREATH_DEFAULT * 2,
        direction = 0,
        size = {x = 24, y = 24},
        offset = {x = 25, y= -(48 + 24 + 16)},
index 961e1ff374735426cb211ed5055655f93df7c124..4078e21a13ea1284ea62b5b6a94da91c60d03b01 100644 (file)
@@ -1289,9 +1289,9 @@ To account for differing resolutions, the position coordinates are the
 percentage of the screen, ranging in value from `0` to `1`.
 
 The name field is not yet used, but should contain a description of what the
-HUD element represents. The direction field is the direction in which something
-is drawn.
+HUD element represents.
 
+The `direction` field is the direction in which something is drawn.
 `0` draws from left to right, `1` draws from right to left, `2` draws from
 top to bottom, and `3` draws from bottom to top.
 
@@ -1355,12 +1355,16 @@ Displays text on the HUD.
 
 ### `statbar`
 
-Displays a horizontal bar made up of half-images.
+Displays a horizontal bar made up of half-images with an optional background.
 
-* `text`: The name of the texture that is used.
+* `text`: The name of the texture to use.
+* `text2`: Optional texture name to enable a background / "off state"
+  texture (useful to visualize the maximal value). Both textures
+  must have the same size.
 * `number`: The number of half-textures that are displayed.
   If odd, will end with a vertically center-split texture.
-* `direction`
+* `item`: Same as `number` but for the "off state" texture
+* `direction`: To which direction the images will extend to
 * `offset`: offset in pixels from position.
 * `size`: If used, will force full-image size to this value (override texture
   pack image size)
@@ -7772,6 +7776,8 @@ Used by `Player:hud_add`. Returned by `Player:hud_get`.
 
         text = "<text>",
 
+        text2 = "<text>",
+
         number = 2,
 
         item = 3,
index 4e7bc93c4507266de9ababde7c00219c3d4651ad..94151f1a48d7a7149ca4a3064cc7ee03572501e9 100644 (file)
@@ -64,6 +64,8 @@ by texture packs. All existing fallback textures can be found in the directory
 
 * `bubble.png`: the bubble texture when the player is drowning
                 (default size: 12×12)
+* `bubble_gone.png`: like `bubble.png`, but denotes lack of breath
+                     (transparent by default, same size as bubble.png)
 
 * `crack_anylength.png`: node overlay texture when digging
 
@@ -76,6 +78,8 @@ by texture packs. All existing fallback textures can be found in the directory
 
 * `heart.png`: used to display the health points of the player
                (default size: 12×12)
+* `heart_gone.png`: like `heart.png`, but denotes lack of health points
+                    (transparent by default, same size as heart.png)
 
 * `minimap_mask_round.png`: round minimap mask, white gets replaced by the map
 * `minimap_mask_square.png`: mask used for the square minimap
index f5689c25b19786af13194923d72b50a0f42555c2..7f3984b03ff5c1bdb9738bc88b91ae2a57282987 100644 (file)
@@ -136,6 +136,7 @@ struct ClientEvent
                        v3f *world_pos;
                        v2s32 *size;
                        s16 z_index;
+                       std::string *text2;
                } hudadd;
                struct
                {
index 4d7a85526b466c9d1a11b23bc1ecdf1947ff523f..422e17d4f892b35d223038330fda1c8739a9075d 100644 (file)
@@ -2672,6 +2672,7 @@ void Game::handleClientEvent_HudAdd(ClientEvent *event, CameraOrientation *cam)
                delete event->hudadd.offset;
                delete event->hudadd.world_pos;
                delete event->hudadd.size;
+               delete event->hudadd.text2;
                return;
        }
 
@@ -2689,6 +2690,7 @@ void Game::handleClientEvent_HudAdd(ClientEvent *event, CameraOrientation *cam)
        e->world_pos = *event->hudadd.world_pos;
        e->size = *event->hudadd.size;
        e->z_index = event->hudadd.z_index;
+       e->text2  = *event->hudadd.text2;
        hud_server_to_client[server_id] = player->addHud(e);
 
        delete event->hudadd.pos;
@@ -2699,6 +2701,7 @@ void Game::handleClientEvent_HudAdd(ClientEvent *event, CameraOrientation *cam)
        delete event->hudadd.offset;
        delete event->hudadd.world_pos;
        delete event->hudadd.size;
+       delete event->hudadd.text2;
 }
 
 void Game::handleClientEvent_HudRemove(ClientEvent *event, CameraOrientation *cam)
@@ -2771,6 +2774,10 @@ void Game::handleClientEvent_HudChange(ClientEvent *event, CameraOrientation *ca
                case HUD_STAT_Z_INDEX:
                        e->z_index = event->hudchange.data;
                        break;
+
+               case HUD_STAT_TEXT2:
+                       e->text2 = *event->hudchange.sdata;
+                       break;
        }
 
        delete event->hudchange.v3fdata;
index 56763e7e48b709e65d8aa7832a881cf89dafee67..f8f712762ef0ed5643685bb94dec188c0ff2c78b 100644 (file)
@@ -332,7 +332,8 @@ void Hud::drawLuaElements(const v3s16 &camera_offset)
                                break; }
                        case HUD_ELEM_STATBAR: {
                                v2s32 offs(e->offset.X, e->offset.Y);
-                               drawStatbar(pos, HUD_CORNER_UPPER, e->dir, e->text, e->number, offs, e->size);
+                               drawStatbar(pos, HUD_CORNER_UPPER, e->dir, e->text, e->text2,
+                                       e->number, e->item, offs, e->size);
                                break; }
                        case HUD_ELEM_INVENTORY: {
                                InventoryList *inv = inventory->getList(e->text);
@@ -401,8 +402,9 @@ void Hud::drawLuaElements(const v3s16 &camera_offset)
 }
 
 
-void Hud::drawStatbar(v2s32 pos, u16 corner, u16 drawdir, const std::string &texture,
-               s32 count, v2s32 offset, v2s32 size)
+void Hud::drawStatbar(v2s32 pos, u16 corner, u16 drawdir,
+               const std::string &texture, const std::string &bgtexture,
+               s32 count, s32 maxcount, v2s32 offset, v2s32 size)
 {
        const video::SColor color(255, 255, 255, 255);
        const video::SColor colors[] = {color, color, color, color};
@@ -411,6 +413,11 @@ void Hud::drawStatbar(v2s32 pos, u16 corner, u16 drawdir, const std::string &tex
        if (!stat_texture)
                return;
 
+       video::ITexture *stat_texture_bg = nullptr;
+       if (!bgtexture.empty()) {
+               stat_texture_bg = tsrc->getTexture(bgtexture);
+       }
+
        core::dimension2di srcd(stat_texture->getOriginalSize());
        core::dimension2di dstd;
        if (size == v2s32()) {
@@ -430,43 +437,100 @@ void Hud::drawStatbar(v2s32 pos, u16 corner, u16 drawdir, const std::string &tex
        p += offset;
 
        v2s32 steppos;
-       core::rect<s32> srchalfrect, dsthalfrect;
        switch (drawdir) {
                case HUD_DIR_RIGHT_LEFT:
                        steppos = v2s32(-1, 0);
-                       srchalfrect = core::rect<s32>(srcd.Width / 2, 0, srcd.Width, srcd.Height);
-                       dsthalfrect = core::rect<s32>(dstd.Width / 2, 0, dstd.Width, dstd.Height);
                        break;
                case HUD_DIR_TOP_BOTTOM:
                        steppos = v2s32(0, 1);
-                       srchalfrect = core::rect<s32>(0, 0, srcd.Width, srcd.Height / 2);
-                       dsthalfrect = core::rect<s32>(0, 0, dstd.Width, dstd.Height / 2);
                        break;
                case HUD_DIR_BOTTOM_TOP:
                        steppos = v2s32(0, -1);
-                       srchalfrect = core::rect<s32>(0, srcd.Height / 2, srcd.Width, srcd.Height);
-                       dsthalfrect = core::rect<s32>(0, dstd.Height / 2, dstd.Width, dstd.Height);
                        break;
                default:
+                       // From left to right
                        steppos = v2s32(1, 0);
-                       srchalfrect = core::rect<s32>(0, 0, srcd.Width / 2, srcd.Height);
-                       dsthalfrect = core::rect<s32>(0, 0, dstd.Width / 2, dstd.Height);
+                       break;
+       }
+
+       auto calculate_clipping_rect = [] (core::dimension2di src,
+                       v2s32 steppos) -> core::rect<s32> {
+
+               // Create basic rectangle
+               core::rect<s32> rect(0, 0,
+                       src.Width  - std::abs(steppos.X) * src.Width / 2,
+                       src.Height - std::abs(steppos.Y) * src.Height / 2
+               );
+               // Move rectangle left or down
+               if (steppos.X == -1)
+                       rect += v2s32(src.Width / 2, 0);
+               if (steppos.Y == -1)
+                       rect += v2s32(0, src.Height / 2);
+               return rect;
+       };
+       // Rectangles for 1/2 the actual value to display
+       core::rect<s32> srchalfrect, dsthalfrect;
+       // Rectangles for 1/2 the "off state" texture
+       core::rect<s32> srchalfrect2, dsthalfrect2;
+
+       if (count % 2 == 1) {
+               // Need to draw halves: Calculate rectangles
+               srchalfrect  = calculate_clipping_rect(srcd, steppos);
+               dsthalfrect  = calculate_clipping_rect(dstd, steppos);
+               srchalfrect2 = calculate_clipping_rect(srcd, steppos * -1);
+               dsthalfrect2 = calculate_clipping_rect(dstd, steppos * -1);
        }
+
        steppos.X *= dstd.Width;
        steppos.Y *= dstd.Height;
 
+       // Draw full textures
        for (s32 i = 0; i < count / 2; i++) {
                core::rect<s32> srcrect(0, 0, srcd.Width, srcd.Height);
-               core::rect<s32> dstrect(0,0, dstd.Width, dstd.Height);
+               core::rect<s32> dstrect(0, 0, dstd.Width, dstd.Height);
 
                dstrect += p;
-               draw2DImageFilterScaled(driver, stat_texture, dstrect, srcrect, NULL, colors, true);
+               draw2DImageFilterScaled(driver, stat_texture,
+                       dstrect, srcrect, NULL, colors, true);
                p += steppos;
        }
 
        if (count % 2 == 1) {
-               dsthalfrect += p;
-               draw2DImageFilterScaled(driver, stat_texture, dsthalfrect, srchalfrect, NULL, colors, true);
+               // Draw half a texture
+               draw2DImageFilterScaled(driver, stat_texture,
+                       dsthalfrect + p, srchalfrect, NULL, colors, true);
+
+               if (stat_texture_bg && maxcount > count) {
+                       draw2DImageFilterScaled(driver, stat_texture_bg,
+                                       dsthalfrect2 + p, srchalfrect2,
+                                       NULL, colors, true);
+                       p += steppos;
+               }
+       }
+
+       if (stat_texture_bg && maxcount > count / 2) {
+               // Draw "off state" textures
+               s32 start_offset;
+               if (count % 2 == 1)
+                       start_offset = count / 2 + 1;
+               else
+                       start_offset = count / 2;
+               for (s32 i = start_offset; i < maxcount / 2; i++) {
+                       core::rect<s32> srcrect(0, 0, srcd.Width, srcd.Height);
+                       core::rect<s32> dstrect(0, 0, dstd.Width, dstd.Height);
+
+                       dstrect += p;
+                       draw2DImageFilterScaled(driver, stat_texture_bg,
+                                       dstrect, srcrect,
+                                       NULL, colors, true);
+                       p += steppos;
+               }
+
+               if (maxcount % 2 == 1) {
+                       draw2DImageFilterScaled(driver, stat_texture_bg,
+                                       dsthalfrect + p, srchalfrect,
+                                       NULL, colors, true);
+               }
        }
 }
 
index cab115990d313a5ca6fa14ce2ab5d38ee313f88a..6274b1a83032cacd759b1e9051f6916d656303db 100644 (file)
@@ -82,8 +82,9 @@ public:
 
 private:
        bool calculateScreenPos(const v3s16 &camera_offset, HudElement *e, v2s32 *pos);
-       void drawStatbar(v2s32 pos, u16 corner, u16 drawdir, const std::string &texture,
-                       s32 count, v2s32 offset, v2s32 size = v2s32());
+       void drawStatbar(v2s32 pos, u16 corner, u16 drawdir,
+                       const std::string &texture, const std::string& bgtexture,
+                       s32 count, s32 maxcount, v2s32 offset, v2s32 size = v2s32());
 
        void drawItems(v2s32 upperleftpos, v2s32 screen_offset, s32 itemcount,
                        s32 inv_offset, InventoryList *mainlist, u16 selectitem,
index 39625b5fdb9801d186a3e9c3ce7687b961cfba10..3079b5cd81bfb2b6a435697be7be11048353b850 100644 (file)
@@ -46,6 +46,7 @@ const struct EnumString es_HudElementStat[] =
        {HUD_STAT_WORLD_POS, "world_pos"},
        {HUD_STAT_SIZE,    "size"},
        {HUD_STAT_Z_INDEX, "z_index"},
+       {HUD_STAT_TEXT2,   "text2"},
        {0, NULL},
 };
 
index b0977c6a426b9f1a5502cdbd12c36beea8ee1cac..bab420ed2064e903dec324ebd9bfa61efe3c9671 100644 (file)
--- a/src/hud.h
+++ b/src/hud.h
@@ -77,6 +77,7 @@ enum HudElementStat {
        HUD_STAT_WORLD_POS,
        HUD_STAT_SIZE,
        HUD_STAT_Z_INDEX,
+       HUD_STAT_TEXT2,
 };
 
 struct HudElement {
@@ -93,6 +94,7 @@ struct HudElement {
        v3f world_pos;
        v2s32 size;
        s16 z_index = 0;
+       std::string text2;
 };
 
 extern const EnumString es_HudElementType[];
index 8d0225a3dce73c50ec8fe8e4e923187217ce9505..7b1b1368cfb87f811b58ae38cd99a9f7ecfa4c4f 100644 (file)
@@ -1102,22 +1102,16 @@ void Client::handleCommand_HudAdd(NetworkPacket* pkt)
        v3f world_pos;
        v2s32 size;
        s16 z_index = 0;
+       std::string text2;
 
        *pkt >> server_id >> type >> pos >> name >> scale >> text >> number >> item
                >> dir >> align >> offset;
        try {
                *pkt >> world_pos;
-       }
-       catch(SerializationError &e) {};
-
-       try {
                *pkt >> size;
-       } catch(SerializationError &e) {};
-
-       try {
                *pkt >> z_index;
-       }
-       catch(PacketError &e) {}
+               *pkt >> text2;
+       } catch(PacketError &e) {};
 
        ClientEvent *event = new ClientEvent();
        event->type             = CE_HUDADD;
@@ -1135,6 +1129,7 @@ void Client::handleCommand_HudAdd(NetworkPacket* pkt)
        event->hudadd.world_pos = new v3f(world_pos);
        event->hudadd.size      = new v2s32(size);
        event->hudadd.z_index   = z_index;
+       event->hudadd.text2     = new std::string(text2);
        m_client_event_queue.push(event);
 }
 
@@ -1171,7 +1166,7 @@ void Client::handleCommand_HudChange(NetworkPacket* pkt)
        if (stat == HUD_STAT_POS || stat == HUD_STAT_SCALE ||
                stat == HUD_STAT_ALIGN || stat == HUD_STAT_OFFSET)
                *pkt >> v2fdata;
-       else if (stat == HUD_STAT_NAME || stat == HUD_STAT_TEXT)
+       else if (stat == HUD_STAT_NAME || stat == HUD_STAT_TEXT || stat == HUD_STAT_TEXT2)
                *pkt >> sdata;
        else if (stat == HUD_STAT_WORLD_POS)
                *pkt >> v3fdata;
index 527ebba7cd729d7f95dd9c0b5561f0dd03882e55..ab924f1dbd7dedb2fd512db94f7369b1f991529a 100644 (file)
@@ -560,10 +560,10 @@ enum ToClientCommand
                u32 id
                u8 type
                v2f1000 pos
-               u32 len
+               u16 len
                u8[len] name
                v2f1000 scale
-               u32 len2
+               u16 len2
                u8[len2] text
                u32 number
                u32 item
@@ -573,6 +573,8 @@ enum ToClientCommand
                v3f1000 world_pos
                v2s32 size
                s16 z_index
+               u16 len3
+               u8[len3] text2
        */
 
        TOCLIENT_HUDRM = 0x4a,
index dac828316552825eaffa32052e8e1e0947a4e77b..540b7222f378a556048489bd307fdb4782445ea6 100644 (file)
@@ -1871,6 +1871,7 @@ void read_hud_element(lua_State *L, HudElement *elem)
        elem->dir     = getintfield_default(L, 2, "direction", 0);
        elem->z_index = MYMAX(S16_MIN, MYMIN(S16_MAX,
                        getintfield_default(L, 2, "z_index", 0)));
+       elem->text2   = getstringfield_default(L, 2, "text2", "");
 
        // Deprecated, only for compatibility's sake
        if (elem->dir == 0)
@@ -1939,6 +1940,9 @@ void push_hud_element(lua_State *L, HudElement *elem)
 
        lua_pushnumber(L, elem->z_index);
        lua_setfield(L, -2, "z_index");
+
+       lua_pushstring(L, elem->text2.c_str());
+       lua_setfield(L, -2, "text2");
 }
 
 HudElementStat read_hud_change(lua_State *L, HudElement *elem, void **value)
@@ -2000,6 +2004,10 @@ HudElementStat read_hud_change(lua_State *L, HudElement *elem, void **value)
                        elem->z_index = MYMAX(S16_MIN, MYMIN(S16_MAX, luaL_checknumber(L, 4)));
                        *value = &elem->z_index;
                        break;
+               case HUD_STAT_TEXT2:
+                       elem->text2 = luaL_checkstring(L, 4);
+                       *value = &elem->text2;
+                       break;
        }
        return stat;
 }
index 16e026ce25fea65b8f84463ea6e7fcf40f1cc121..b28c30e1e2840b69e9aa08130b0a06d83aa55954 100644 (file)
@@ -1621,7 +1621,7 @@ void Server::SendHUDAdd(session_t peer_id, u32 id, HudElement *form)
        pkt << id << (u8) form->type << form->pos << form->name << form->scale
                        << form->text << form->number << form->item << form->dir
                        << form->align << form->offset << form->world_pos << form->size
-                       << form->z_index;
+                       << form->z_index << form->text2;
 
        Send(&pkt);
 }
@@ -1647,6 +1647,7 @@ void Server::SendHUDChange(session_t peer_id, u32 id, HudElementStat stat, void
                        break;
                case HUD_STAT_NAME:
                case HUD_STAT_TEXT:
+               case HUD_STAT_TEXT2:
                        pkt << *(std::string *) value;
                        break;
                case HUD_STAT_WORLD_POS:
diff --git a/textures/base/pack/bubble_gone.png b/textures/base/pack/bubble_gone.png
new file mode 100644 (file)
index 0000000..240ca4f
Binary files /dev/null and b/textures/base/pack/bubble_gone.png differ
diff --git a/textures/base/pack/heart_gone.png b/textures/base/pack/heart_gone.png
new file mode 100644 (file)
index 0000000..240ca4f
Binary files /dev/null and b/textures/base/pack/heart_gone.png differ