From c9fffbf642b73e610502a66afb594e44207aba32 Mon Sep 17 00:00:00 2001 From: luk3yx Date: Wed, 24 Mar 2021 08:30:33 +1300 Subject: [PATCH] Initial commit --- .luacheckrc | 11 + LICENSE.md | 22 ++ README.md | 108 ++++++++ colorstring_to_number.lua | 173 ++++++++++++ init.lua | 563 ++++++++++++++++++++++++++++++++++++++ mod.conf | 2 + textures/hud_fs_box.png | Bin 0 -> 565 bytes 7 files changed, 879 insertions(+) create mode 100644 .luacheckrc create mode 100644 LICENSE.md create mode 100644 README.md create mode 100644 colorstring_to_number.lua create mode 100644 init.lua create mode 100644 mod.conf create mode 100644 textures/hud_fs_box.png diff --git a/.luacheckrc b/.luacheckrc new file mode 100644 index 0000000..6658d21 --- /dev/null +++ b/.luacheckrc @@ -0,0 +1,11 @@ +max_line_length = 80 + +globals = { + 'formspec_ast', + 'minetest', +} + +read_globals = { + string = {fields = {'split', 'trim'}}, + table = {fields = {'copy'}} +} diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..410584d --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,22 @@ + +# The MIT License (MIT) + +Copyright © 2021 by luk3yx + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..dbef965 --- /dev/null +++ b/README.md @@ -0,0 +1,108 @@ +# hud_fs + +A Minetest mod library to make handling formspec-like HUDs easier. Depends on +[formspec_ast](https://content.minetest.net/packages/luk3yx/formspec_ast/). + +## API + + - `hud_fs.show_hud(player, formname, formspec)`: Displays or updates a HUD + with the specified formname and formspec. + - `formspec` can also be a formspec_ast tree for more advanced usage. + - `hud_fs.close_hud(player, formname)`: Closes `formname`. Equivalent to + `hud_fs.show_hud(player, formname, "")`. + +The player parameter in the above function can either be a player object or a +player name. + +*If you just want to manage HUDs, that's all you need to know!* Don't worry +about trying to get incremental updates or tracking HUD IDs, `hud_fs` does that +for you behind-the-scenes. + +### Supported formspec features + +All formspecs are assumed to be version 3 and the `formspec_version` element +(if any) is ignored. + +The following elements are supported: + + - `size` + - While there is no background for the HUD, this does change where the + co-ordinates start from. + - `position`, `anchor` + - **You need to use these to set the position of the HUD!** + - See [the Minetest API documentation](https://minetest.gitlab.io/minetest/formspec/#positionxy) for more info. + - You probably want `anchor` to have the same value as `position`. + - `container` + - `label` + - Because of HUD limitations, `minetest.colorize()` only works at the start + of the label. + - `image` + - `box` + - `textarea` + - If the name is non-empty a background is drawn behind the text. + - Text will overflow vertically outside the specified height. + - `item_image` + - Only works with some nodes, should work with all craftitems. + - `button`, `button_exit`, `image_button`, `image_button_exit`, + `item_image_button` + - Buttons become a grey box with a label in the middle. + - The label has the same limitations as the `label` element. + - The `noclip` option is ignored. + - Item image buttons have the same limitations as `item_image`. + +All valid formspec elements not listed above are ignored. + +### Advanced API + + - `hud_fs.set_scale(formname, scale)`: Sets the scale of the HUD. + - All future HUDs shown with `formname` will use this scale instead of the + default (64, subject to change). + - The scale is the amount of pixels per co-ordinate. For example, a 1x1 + image will have a size of 10x10 pixels if the scale is set to 10. + - `hud_fs.set_z_index(formname, z_index)`: Sets the base Z-Index of the HUD. + - All future HUDs shown with `formname` will use this z-index instead of + the default (0). + - The HUD will use z-index values from `z_index` to + `z_index + amount_of_hud_elements`. + - This won't work properly with Minetest clients older than 5.2.0. + + +## FAQ(?) + +#### Why not implement this mod in the Minetest engine? + +This mod (or anything similar) won't be implemented, a proposal to do so was +rejected in https://github.com/minetest/minetest/issues/10135. + +#### Why formspecs? + + - There isn't a complicated new API to learn, if you write MT mods you + probably know how to use formspecs already. + - I have a [web-based formspec editor] which can now be used to design HUDs + for use with this mod. + - You don't need any knowledge of Minetest's HUD API to use this mod. + - As this mod parses formspecs server-side, the lack of differential updates + is for the most part a non-issue. + +[web-based formspec editor]: https://git.minetest.land/luk3yx/formspec-editor + +#### But I hate formspecs and don't want to touch them + +Then don't use this mod. There are plenty of other HUD library mods around such +as [hudlib](https://github.com/octacian/hudlib) and +[panel_lib](https://gitlab.com/zughy-friends-minetest/panel_lib). + +## Performance + +If this mod becomes a performance bottleneck you can try the following things: + + - Move any formspec elements that are added or removed frequently to the end + of the formspec. This will allow them to be removed without touching other + elements internally. + - This mod is currently inefficient at updating HUDs when elements are + added or removed when they aren't at the end of the formspec. + - Using a formspec_ast tree instead of a formspec in show_hud. `formspec_ast` + is relatively slow at parsing formspecs at the time of writing. + - Don't call show_hud when you already know that nothing has changed. Doing so + will waste time parsing the formspec, converting it to HUD elements, then + figuring out what has changed. diff --git a/colorstring_to_number.lua b/colorstring_to_number.lua new file mode 100644 index 0000000..0e06ea1 --- /dev/null +++ b/colorstring_to_number.lua @@ -0,0 +1,173 @@ +-- +-- hud_fs: Render formspecs into HUDs +-- +-- This is the fallback parser for ColorStrings when +-- minetest.colorspec_to_colorstring doesn't exist. +-- + +-- From https://www.w3.org/TR/css-color-3/#svg-color +local lookup_table = { + aliceblue = 0xf0f8ff, + antiquewhite = 0xfaebd7, + aqua = 0x00ffff, + aquamarine = 0x7fffd4, + azure = 0xf0ffff, + beige = 0xf5f5dc, + bisque = 0xffe4c4, + black = 0x000000, + blanchedalmond = 0xffebcd, + blue = 0x0000ff, + blueviolet = 0x8a2be2, + brown = 0xa52a2a, + burlywood = 0xdeb887, + cadetblue = 0x5f9ea0, + chartreuse = 0x7fff00, + chocolate = 0xd2691e, + coral = 0xff7f50, + cornflowerblue = 0x6495ed, + cornsilk = 0xfff8dc, + crimson = 0xdc143c, + cyan = 0x00ffff, + darkblue = 0x00008b, + darkcyan = 0x008b8b, + darkgoldenrod = 0xb8860b, + darkgray = 0xa9a9a9, + darkgreen = 0x006400, + darkgrey = 0xa9a9a9, + darkkhaki = 0xbdb76b, + darkmagenta = 0x8b008b, + darkolivegreen = 0x556b2f, + darkorange = 0xff8c00, + darkorchid = 0x9932cc, + darkred = 0x8b0000, + darksalmon = 0xe9967a, + darkseagreen = 0x8fbc8f, + darkslateblue = 0x483d8b, + darkslategray = 0x2f4f4f, + darkslategrey = 0x2f4f4f, + darkturquoise = 0x00ced1, + darkviolet = 0x9400d3, + deeppink = 0xff1493, + deepskyblue = 0x00bfff, + dimgray = 0x696969, + dimgrey = 0x696969, + dodgerblue = 0x1e90ff, + firebrick = 0xb22222, + floralwhite = 0xfffaf0, + forestgreen = 0x228b22, + fuchsia = 0xff00ff, + gainsboro = 0xdcdcdc, + ghostwhite = 0xf8f8ff, + gold = 0xffd700, + goldenrod = 0xdaa520, + gray = 0x808080, + green = 0x008000, + greenyellow = 0xadff2f, + grey = 0x808080, + honeydew = 0xf0fff0, + hotpink = 0xff69b4, + indianred = 0xcd5c5c, + indigo = 0x4b0082, + ivory = 0xfffff0, + khaki = 0xf0e68c, + lavender = 0xe6e6fa, + lavenderblush = 0xfff0f5, + lawngreen = 0x7cfc00, + lemonchiffon = 0xfffacd, + lightblue = 0xadd8e6, + lightcoral = 0xf08080, + lightcyan = 0xe0ffff, + lightgoldenrodyellow = 0xfafad2, + lightgray = 0xd3d3d3, + lightgreen = 0x90ee90, + lightgrey = 0xd3d3d3, + lightpink = 0xffb6c1, + lightsalmon = 0xffa07a, + lightseagreen = 0x20b2aa, + lightskyblue = 0x87cefa, + lightslategray = 0x778899, + lightslategrey = 0x778899, + lightsteelblue = 0xb0c4de, + lightyellow = 0xffffe0, + lime = 0x00ff00, + limegreen = 0x32cd32, + linen = 0xfaf0e6, + magenta = 0xff00ff, + maroon = 0x800000, + mediumaquamarine = 0x66cdaa, + mediumblue = 0x0000cd, + mediumorchid = 0xba55d3, + mediumpurple = 0x9370db, + mediumseagreen = 0x3cb371, + mediumslateblue = 0x7b68ee, + mediumspringgreen = 0x00fa9a, + mediumturquoise = 0x48d1cc, + mediumvioletred = 0xc71585, + midnightblue = 0x191970, + mintcream = 0xf5fffa, + mistyrose = 0xffe4e1, + moccasin = 0xffe4b5, + navajowhite = 0xffdead, + navy = 0x000080, + oldlace = 0xfdf5e6, + olive = 0x808000, + olivedrab = 0x6b8e23, + orange = 0xffa500, + orangered = 0xff4500, + orchid = 0xda70d6, + palegoldenrod = 0xeee8aa, + palegreen = 0x98fb98, + paleturquoise = 0xafeeee, + palevioletred = 0xdb7093, + papayawhip = 0xffefd5, + peachpuff = 0xffdab9, + peru = 0xcd853f, + pink = 0xffc0cb, + plum = 0xdda0dd, + powderblue = 0xb0e0e6, + purple = 0x800080, + rebeccapurple = 0x663399, + red = 0xff0000, + rosybrown = 0xbc8f8f, + royalblue = 0x4169e1, + saddlebrown = 0x8b4513, + salmon = 0xfa8072, + sandybrown = 0xf4a460, + seagreen = 0x2e8b57, + seashell = 0xfff5ee, + sienna = 0xa0522d, + silver = 0xc0c0c0, + skyblue = 0x87ceeb, + slateblue = 0x6a5acd, + slategray = 0x708090, + slategrey = 0x708090, + snow = 0xfffafa, + springgreen = 0x00ff7f, + steelblue = 0x4682b4, + tan = 0xd2b48c, + teal = 0x008080, + thistle = 0xd8bfd8, + tomato = 0xff6347, + turquoise = 0x40e0d0, + violet = 0xee82ee, + wheat = 0xf5deb3, + white = 0xffffff, + whitesmoke = 0xf5f5f5, + yellow = 0xffff00, + yellowgreen = 0x9acd32, +} + +return function(col) + if col:byte(1) == 35 then + -- Convert #123 to #112233 + if #col < 7 then + col = col:gsub("[^#]", "%1%1") + end + + -- Remove the alpha channel and convert to a number + return tonumber(col:sub(2, 7), 16) + end + + -- Try the lookup table + return lookup_table[col:lower():match('^[^#]+')] +end diff --git a/init.lua b/init.lua new file mode 100644 index 0000000..9226f74 --- /dev/null +++ b/init.lua @@ -0,0 +1,563 @@ +-- +-- hud_fs: Render formspecs into HUDs +-- +-- Copyright © 2021 by luk3yx +-- + +local hud_fs = {} +local modname = minetest.get_current_modname() +_G[modname] = hud_fs + +local DEBUG = false +local DEFAULT_SCALE = 64 +local DEFAULT_Z_INDEX = 0 + +local floor, type, pairs, max = math.floor, type, pairs, math.max + +local colorstring_to_number +local function colorstring_to_number_fallback(col) + colorstring_to_number_fallback = dofile(minetest.get_modpath(modname) .. + "/colorstring_to_number.lua") + return colorstring_to_number_fallback(col) +end + +if minetest.colorspec_to_colorstring then + function colorstring_to_number(col) + local res = minetest.colorspec_to_colorstring(col) + if res and (res:byte(1) ~= 35 or #res < 7) then + -- Unexpected return value, go back to using the fallback parser. + minetest.log("warning", ("[hud_fs] Unexpected value returned by" .. + " minetest.colorspec_to_colorstring(%q): %q"):format(col, + res)) + colorstring_to_number = colorstring_to_number_fallback + return colorstring_to_number_fallback(col) + end + return res and tonumber(res:sub(2, 7), 16) + end +else + colorstring_to_number = colorstring_to_number_fallback +end + +-- Hacks to allow colorize() to work to some extent on labels +local function get_label_number(label) + local number, text = label:match("^\027%(c@([^%)]+)%)(.*)$") + + -- Remove trailing escape sequence added by minetest.colorize(). + if text then + text = text:gsub("\027%(c@[^%)]+%)$", "") + end + + return text or label, number and colorstring_to_number(number) or 0xFFFFFF +end + +local nodes = {} +function nodes.label(node, scale) + local text, number = get_label_number(node.label) + local elem = { + hud_elem_type = "text", + text = text, + alignment = {x = 1, y = 0}, + number = number + } + + -- Hack for newlines. This will unfortunately break if the font size is + -- changed from the default. + if elem.text:find('[\r\n]') then + elem.alignment.y = 1 + node.y = node.y - 10 / scale + end + return elem +end + +function nodes.image(node, scale, _, proto_ver) + local w = floor(node.w * scale) + local h = floor(node.h * scale) + local elem_scale = {x = 1, y = 1} + + -- Hacks to support MultiCraft + -- This is horrible and unfortunately is applied to MT clients as well + if proto_ver == 32 then + local true_w, true_h = w, h + w, h = 2 ^ math.ceil(math.log(w, 2)), 2 ^ math.ceil(math.log(h, 2)) + elem_scale.x, elem_scale.y = true_w / max(w, 1), true_h / max(h, 1) + end + + local texture = node.texture_name + if w > 0 and h > 0 and texture ~= "" then + texture = "(" .. texture .. ")^[resize:" .. w .. "x" .. h + else + -- Minetest throws an error with zero-width HUD images, use blank.png + -- to keep the image around (for future HUD update calls) while + -- silencing the error. + texture = "blank.png" + end + return { + hud_elem_type = "image", + text = texture, + alignment = {x = 1, y = 1}, + scale = elem_scale, + } +end + +-- Hack box[] into image[] +function nodes.box(node, scale, add_node, proto_ver) + local col = node.color + -- Add default transparency + if col:byte(1) == 35 then + if #col == 4 then + col = col:gsub("[^#]", "%1%1") .. "8C" + elseif #col == 7 then + col = col .. "8C" + end + end + node.texture_name = 'hud_fs_box.png^[colorize:' .. col + return nodes.image(node, scale, add_node, proto_ver) +end + +function nodes.textarea(node, scale, add_node) + -- Add in separate nodes for the label and background + if node.label ~= "" then + add_node("label", { + x = node.x, + y = node.y - 10 / scale, + label = node.label + }) + end + if node.name ~= "" then + add_node("box", { + x = node.x, + y = node.y, + w = node.w, + h = node.h, + color = "#757575FF" + }) + end + node.x = node.x + 5 / scale + node.y = node.y + 3 / scale + local lines = (node.default or ""):split("\n", true) + local max_line_length = node.w * scale / 8 + for i, line in ipairs(lines) do + lines[i] = minetest.wrap_text(line, max_line_length) + end + return { + hud_elem_type = "text", + text = table.concat(lines, "\n"), + alignment = {x = 1, y = 1}, + number = 0xFFFFFF, + scale = {x = node.w * scale, y = node.h * scale} + } +end + +local function get_tile_image(tiles, preferred_texture) + local tile + for i = preferred_texture, 1, -1 do + tile = tiles[i] + if tile then break end + end + if type(tile) == "table" then tile = tile.name end + if type(tile) ~= "string" then tile = "unknown_item.png" end + return tile +end + +function nodes.item_image(node, scale, add_node, proto_ver) + local def = minetest.registered_items[node.item_name] + if not def then + node.texture_name = "unknown_item.png" + elseif def.inventory_image and def.inventory_image ~= "" then + node.texture_name = def.inventory_image + elseif def.wield_image and def.wield_image ~= "" then + node.texture_name = def.wield_image + elseif def.tiles then + node.texture_name = minetest.inventorycube( + get_tile_image(def.tiles, 1), + get_tile_image(def.tiles, 6), + get_tile_image(def.tiles, 3) + ) + else + node.texture_name = "unknown_node.png" + end + return nodes.image(node, scale, add_node, proto_ver) +end + +function nodes.button(node, _, add_node) + -- This function is used by image_button and item_image_button as well + if node.drawborder == nil or node.drawborder then + add_node("box", { + x = node.x, + y = node.y, + w = node.w, + h = node.h, + color = "#515151FF" + }) + end + if node.texture_name and node.texture_name ~= "" then + add_node("image", node) + elseif node.item_name and node.item_name ~= "" then + add_node("item_image", node) + end + node.x = node.x + node.w / 2 + node.y = node.y + node.h / 2 + local text, number = get_label_number(node.label) + return { + hud_elem_type = "text", + text = text, + alignment = {x = 0, y = 0}, + number = number + } +end +nodes.button_exit = nodes.button +nodes.image_button = nodes.button +nodes.image_button_exit = nodes.button +nodes.item_image_button = nodes.button + +local render_error +local function render(tree, proto_ver, scale, z_index) + if type(tree) == "string" then + local err + tree, err = formspec_ast.parse(tree) + if not tree then + return render_error(err) + end + end + + tree = formspec_ast.flatten(tree) + -- if (tree.formspec_version or 1) < 2 then + -- return render_error("Formspec version 1 is not supported!") + -- end + + local hud_elems = {} + local size_w, size_h = 0, 0 + local pos = {x = 0.5, y = 0.5} + local offset_x, offset_y = 0, 0 + scale = scale or DEFAULT_SCALE + z_index = z_index or DEFAULT_Z_INDEX + + local function add_node(node_type, node) + local elem = nodes[node_type](node, scale, add_node, proto_ver) + elem.position = pos + elem.name = node_type + elem.z_index = z_index + elem.offset = { + x = (node.x + offset_x) * scale, + y = (node.y + offset_y) * scale + } + hud_elems[#hud_elems + 1] = elem + z_index = z_index + 1 + end + + for _, node in ipairs(tree) do + local node_type = node.type + if node_type == "size" then + size_w, size_h = node.w, node.h + offset_x, offset_y = -size_w / 2, -size_h / 2 + elseif node_type == "position" then + pos = {x = node.x, y = node.y} + elseif node_type == "anchor" then + if size_w == 0 or size_h == 0 then + return render_error("anchor[] without size[]") + end + offset_x = -node.x * size_w + offset_y = -node.y * size_h + -- return render_error('anchor[] not implemented') + elseif nodes[node_type] then + add_node(node_type, node) + end + end + + return hud_elems +end + +-- Defined as a local before render() +function render_error(err) + return render("formspec_version[3]size[8,1]label[0,0.5;" .. + minetest.formspec_escape(tostring(err)) .. "]") +end + +local hud_elems = {} +--[[ +hud_elems[player_name][formname] = { + {List of HUD IDs}, + {List of HUD definitions} +} +]] + +-- Returns needs_replacing, differences +local function compare_elems(old_elem, new_elem) + local differences = {} + for k, v in pairs(old_elem) do + local v2 = new_elem[k] + if type(v) == "table" and type(v2) == "table" then + -- Tables are guaranteed to be 2-dimensional vectors at the moment, + -- don't bother checking anything else to improve performance. + if v.x ~= v2.x or v.y ~= v2.y then + differences[#differences + 1] = k + end + elseif v ~= v2 then + -- Sometimes the HUD element will need to be deleted/re-added. + if k == "hud_elem_type" or v2 == nil then return true, nil end + differences[#differences + 1] = k + end + end + + -- Check for missing keys in old_elem + for k in pairs(new_elem) do + if old_elem[k] == nil then + differences[#differences + 1] = k + end + end + + return false, differences +end + +local scales = {} +local z_indexes = {} +function hud_fs.show_hud(player, formname, formspec) + if type(player) == "string" then + player = minetest.get_player_by_name(player) + end + + local name = player:get_player_name() + if not hud_elems[name] then + hud_elems[name] = {} + end + + -- Work around Minetest bug (should be fixed in 5.4) + local info = minetest.get_player_information(name) + if not info then return end + + local data = hud_elems[name][formname] + if not data then + data = {{}, {}} + hud_elems[name][formname] = data + end + local ids, elems = data[1], data[2] + local proto_ver = info.protocol_version + local new_elems = render(formspec, proto_ver, scales[formname], + z_indexes[formname]) + + -- Z-index was added to MT 5.2.0 (protocol version 39) and is ignored by + -- older clients. Because of the way HUDs work, sometimes it's safest to + -- just delete and re-add every single element for these older clients. + if proto_ver < 39 then + -- Maybe we can get away with just doing hud_change() and not resending + local diff_cache = {} + local can_update = true + local update_packets = max(#elems - #new_elems, 0) + for i, new_elem in ipairs(new_elems) do + local old_elem = elems[i] + if old_elem then + local needs_replacing, diff = compare_elems(old_elem, new_elem) + if needs_replacing then + can_update = false + break + end + if #diff > 0 then + diff_cache[i] = diff + update_packets = update_packets + 1 + end + else + -- If elements are added the whole HUD needs to be resent. + can_update = false + break + end + end + + local resend_packets = #elems + #new_elems + if can_update and resend_packets >= update_packets then + -- Send lots of hud_change packets + for i, new_elem in ipairs(new_elems) do + local diff = diff_cache[i] + if diff then + for _, stat in ipairs(diff) do + player:hud_change(ids[i], stat, new_elem[stat]) + end + elems[i] = new_elem + end + end + + -- Remove any extra elements + for i = #new_elems + 1, #ids do + player:hud_remove(ids[i]) + ids[i] = nil + elems[i] = nil + end + + if DEBUG then + minetest.chat_send_player(name, "[DEBUG] Sent " .. + update_packets .. " packet(s) using hud_change() and " .. + "hud_remove() to update HUD") + end + else + -- Or resend every single HUD element + for i = 1, #ids do + player:hud_remove(ids[i]) + ids[i] = nil + elems[i] = nil + end + for i, elem in ipairs(new_elems) do + ids[i] = player:hud_add(elem) + elems[i] = elem + end + + if DEBUG then + minetest.chat_send_player(name, "[DEBUG] Sent " .. + resend_packets .. " packet(s) resending entire HUD") + end + end + return + end + + -- As MT 5.2.0+ clients support z_index, the HUD IDs don't need to be + -- sequential and the update packets can therefore be more efficient. + local replaced, modified, modify_packets, added = 0, 0, 0, 0 + for i, new_elem in ipairs(new_elems) do + local old_elem = elems[i] + if old_elem then + local needs_replacing, diff = compare_elems(old_elem, new_elem) + if needs_replacing or #diff > 2 then + -- Resend the entire element if there are more than two + -- differences as this only sends two packets to the client. + player:hud_remove(ids[i]) + ids[i] = player:hud_add(new_elem) + replaced = replaced + 1 + else + -- Otherwise it's more efficient to use multiple hud_change() + -- calls (this is a no-op if #diff == 0). + for _, stat in ipairs(diff) do + player:hud_change(ids[i], stat, new_elem[stat]) + modify_packets = modify_packets + 1 + end + if #diff > 0 then modified = modified + 1 end + end + else + ids[i] = player:hud_add(new_elem) + added = added + 1 + end + elems[i] = new_elem + end + + -- Remove any extra elements + local removed = 0 + for i = #new_elems + 1, #ids do + player:hud_remove(ids[i]) + ids[i] = nil + elems[i] = nil + removed = removed + 1 + end + + if DEBUG then + local packets = modify_packets + replaced * 2 + added + removed + minetest.chat_send_player(name, "[DEBUG] Sent " .. packets .. + " network packet(s): Modified " .. modified .. + " elements in-place (for " .. modify_packets .. + " packet(s)), replaced " .. replaced .. ", added " .. added .. + " and removed " .. removed .. " element(s).") + end +end + +function hud_fs.close_hud(player, formname) + hud_fs.show_hud(player, formname, {}) +end + +minetest.register_on_leaveplayer(function(player) + hud_elems[player:get_player_name()] = nil +end) + +-- Sets the base z-index for formname. Should not be done when the formspec is +-- open. Note that this is not used for all elements, if the formspec contains +-- 10 HUD elements it will use a z-index ranging from z_index to z_index + 9. +-- This has no effect for clients older than 5.2.0. +function hud_fs.set_z_index(formname, z_index) + if z_index == DEFAULT_Z_INDEX then z_index = nil end + z_indexes[formname] = z_index +end + +-- Sets the scale for formname. This can be done when the HUD is open, however +-- the scale won't be changed for existing formspecs. +function hud_fs.set_scale(formname, scale) + if scale == DEFAULT_SCALE then scale = nil end + scales[formname] = scale +end + +-- Testing +--[=[ +if not DEBUG then return end + +local using_fs = {} +local function poll() + for _, player in ipairs(minetest.get_connected_players()) do + local name = player:get_player_name() + local fs = "formspec_version[3]" .. + "size[4,4.5] position[1,0] anchor[1,0] no_prepend[]" .. + "bgcolor[#00000022;true]" .. + "box[0,0;5,2;#380C2AFF]" .. + "label[0.25,0.5;This is a test HUD]" .. + "label[0.25,1;" .. minetest.colorize("#00ffff", "Server uptime: " .. + floor(minetest.get_server_uptime()) .. " seconds") .. + "]" .. + "label[0.25,1.5;Run /hud to interact!]" + if math.random(0, 1) == 0 then + fs = fs .. "image[3,1.5;1,1;default_dirt.png]" + end + -- if math.random(1, 5) > 1 then + -- fs = fs .. "label[0,1.5;Second label (test)\nNewline test\rabc]" + -- end + fs = fs .. + "box[0,2;5,2;#380C2ABF]" .. + "container[0,2.5]" .. + "item_image[0,0;1,1;carts:rail]" .. + "item_image[1,0;1,1;default:dirt_with_grass]" .. + "container[2,-0.5]" .. + "item_image[0,0.5;1,1;default:cactus]" .. + "item_image[1,0.5;1,1;default:tree]" .. + "container_end[]" .. + "container_end[]" .. + "button[0,4;2,0.5;mrkr;Waypoints]" .. + "button[2,4;2,0.5;close;Close]" + if using_fs[name] then + minetest.show_formspec(name, "hud_fs:uptime", fs) + hud_fs.close_hud(name, "hud_fs:uptime") + else + hud_fs.show_hud(name, "hud_fs:uptime", fs) + end + end +end + +local function poll_outer() + poll() + minetest.after(1, poll_outer) +end +minetest.register_on_mods_loaded(poll_outer) + +minetest.register_chatcommand("hud", { + func = function(name, _) + using_fs[name] = true + poll() + end, +}) + +minetest.register_chatcommand("hud2", { + func = function(name, _) + minetest.show_formspec(name, "hud_fs:uptime", "formspec_version[3]" .. + "size[4,4.5] position[1,0] anchor[1,0] no_prepend[]" .. + "bgcolor[#ff000000;neither;#00ff0000]" .. + "button[0,4;2,0.5;mrkr;Waypoints]" .. + "button[2,4;2,0.5;close;Close]") + end, +}) + +minetest.register_on_player_receive_fields(function(player, formname, fields) + if formname ~= "hud_fs:uptime" then return end + local name = player:get_player_name() + if fields.mrkr then + minetest.registered_chatcommands["mrkr"].func(name, "") + elseif fields.close then + minetest.close_formspec(name, "hud_fs:uptime") + elseif not fields.quit then + return + end + + using_fs[name] = nil + poll() +end) +]=] diff --git a/mod.conf b/mod.conf new file mode 100644 index 0000000..a212968 --- /dev/null +++ b/mod.conf @@ -0,0 +1,2 @@ +name = hud_fs +depends = formspec_ast diff --git a/textures/hud_fs_box.png b/textures/hud_fs_box.png new file mode 100644 index 0000000000000000000000000000000000000000..abdebe44efa033c9679d318fce8d90db8ebe7836 GIT binary patch literal 565 zcmV-50?Pe~P)EX>4Tx04R}tkv&MmP!xqvQ^g_`2Rn#31gTCIM2k3T6^c+H)C#RSn7s54nlvOS zE{=k0!NH%!s)LKOt`4q(Aov5~=H{g6A|>9J6k5c1;qgAsyXWxUeSpxYFwN?k05sh; z)5(OG&8><(uLvNB5JnJ}nPtpLQVPEHbx++?cQKyj-}h(rsdz@3D;k>@G%ynABNMaF7kRU=q4P{hdBSyPUiiI?tCw%-vu3sXTLas6x zITlcb2HEw4|H1FsT7{`eFDaY=x?ddUV-)Dw1)6oo`95}><_Qpd2CnqBzuEw1K1r{) zwdfJhzYSbmw>4!CxZD8-pA6ZQT`5RQC=`JAGy0|+FmMa>u6c87o#XTY$k41(H^9Lm zFjl1Ob)R>KI_LIpPiuZZVE%HTlAzu#00006VoOIv0EhsH0El#u0R{j7010qNS#tmY zE+YT{E+YYWr9XB6000McNlirueSad^gZEa<4bO1wgWnpw> zWFU8GbZ8()Nlj2!fese{000e1L_t&t*JEgCXkY{Y1cm{EzcWA}00000NkvXXu0mjf DzXj(1 literal 0 HcmV?d00001