Initial commit
This commit is contained in:
commit
c9fffbf642
|
@ -0,0 +1,11 @@
|
||||||
|
max_line_length = 80
|
||||||
|
|
||||||
|
globals = {
|
||||||
|
'formspec_ast',
|
||||||
|
'minetest',
|
||||||
|
}
|
||||||
|
|
||||||
|
read_globals = {
|
||||||
|
string = {fields = {'split', 'trim'}},
|
||||||
|
table = {fields = {'copy'}}
|
||||||
|
}
|
|
@ -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.
|
|
@ -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.
|
|
@ -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
|
|
@ -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)
|
||||||
|
]=]
|
Binary file not shown.
After Width: | Height: | Size: 565 B |
Loading…
Reference in New Issue