Skip to content

Commit

Permalink
Add support for statbar “off state” icons (#9462)
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
Wuzzy2 and SmallJoker committed May 11, 2020
1 parent 88bb8e5 commit 6e1372b
Show file tree
Hide file tree
Showing 15 changed files with 132 additions and 36 deletions.
4 changes: 4 additions & 0 deletions builtin/game/statbars.lua
Expand Up @@ -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)},
Expand All @@ -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)},
Expand Down
16 changes: 11 additions & 5 deletions doc/lua_api.txt
Expand Up @@ -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.

Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -7772,6 +7776,8 @@ Used by `Player:hud_add`. Returned by `Player:hud_get`.

text = "<text>",

text2 = "<text>",

number = 2,

item = 3,
Expand Down
4 changes: 4 additions & 0 deletions doc/texture_packs.txt
Expand Up @@ -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

Expand All @@ -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
Expand Down
1 change: 1 addition & 0 deletions src/client/clientevent.h
Expand Up @@ -136,6 +136,7 @@ struct ClientEvent
v3f *world_pos;
v2s32 *size;
s16 z_index;
std::string *text2;
} hudadd;
struct
{
Expand Down
7 changes: 7 additions & 0 deletions src/client/game.cpp
Expand Up @@ -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;
}

Expand All @@ -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;
Expand All @@ -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)
Expand Down Expand Up @@ -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;
Expand Down
96 changes: 80 additions & 16 deletions src/client/hud.cpp
Expand Up @@ -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);
Expand Down Expand Up @@ -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};
Expand All @@ -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()) {
Expand All @@ -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);
}
}
}

Expand Down
5 changes: 3 additions & 2 deletions src/client/hud.h
Expand Up @@ -82,8 +82,9 @@ class Hud

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,
Expand Down
1 change: 1 addition & 0 deletions src/hud.cpp
Expand Up @@ -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},
};

Expand Down
2 changes: 2 additions & 0 deletions src/hud.h
Expand Up @@ -77,6 +77,7 @@ enum HudElementStat {
HUD_STAT_WORLD_POS,
HUD_STAT_SIZE,
HUD_STAT_Z_INDEX,
HUD_STAT_TEXT2,
};

struct HudElement {
Expand All @@ -93,6 +94,7 @@ struct HudElement {
v3f world_pos;
v2s32 size;
s16 z_index = 0;
std::string text2;
};

extern const EnumString es_HudElementType[];
Expand Down
15 changes: 5 additions & 10 deletions src/network/clientpackethandler.cpp
Expand Up @@ -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;
Expand All @@ -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);
}

Expand Down Expand Up @@ -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;
Expand Down
6 changes: 4 additions & 2 deletions src/network/networkprotocol.h
Expand Up @@ -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
Expand All @@ -573,6 +573,8 @@ enum ToClientCommand
v3f1000 world_pos
v2s32 size
s16 z_index
u16 len3
u8[len3] text2
*/

TOCLIENT_HUDRM = 0x4a,
Expand Down
8 changes: 8 additions & 0 deletions src/script/common/c_content.cpp
Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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;
}
Expand Down

0 comments on commit 6e1372b

Please sign in to comment.