Add z-index to HUD elements
[minetest_hudbars.git] / init.lua
blob65d60c30a31c3ca10f952d282316a48736821388
1 local S = minetest.get_translator("hudbars")
2 local N = function(s) return s end
4 hb = {}
6 hb.hudtables = {}
8 -- number of registered HUD bars
9 hb.hudbars_count = 0
11 -- table which records which HUD bar slots have been “registered” so far; used for automatic positioning
12 hb.registered_slots = {}
14 hb.settings = {}
16 function hb.load_setting(sname, stype, defaultval, valid_values)
17 local sval
18 if stype == "string" then
19 sval = minetest.settings:get(sname)
20 elseif stype == "bool" then
21 sval = minetest.settings:get_bool(sname)
22 elseif stype == "number" then
23 sval = tonumber(minetest.settings:get(sname))
24 end
25 if sval ~= nil then
26 if valid_values ~= nil then
27 local valid = false
28 for i=1,#valid_values do
29 if sval == valid_values[i] then
30 valid = true
31 end
32 end
33 if not valid then
34 minetest.log("error", "[hudbars] Invalid value for "..sname.."! Using default value ("..tostring(defaultval)..").")
35 return defaultval
36 else
37 return sval
38 end
39 else
40 return sval
41 end
42 else
43 return defaultval
44 end
45 end
47 -- Load default settings
48 dofile(minetest.get_modpath("hudbars").."/default_settings.lua")
50 local function player_exists(player)
51 return player ~= nil and player:is_player()
52 end
54 local function make_label(format_string, format_string_config, label, start_value, max_value)
55 local params = {}
56 local order = format_string_config.order
57 for o=1, #order do
58 if order[o] == "label" then
59 table.insert(params, label)
60 elseif order[o] == "value" then
61 if format_string_config.format_value then
62 table.insert(params, string.format(format_string_config.format_value, start_value))
63 else
64 table.insert(params, start_value)
65 end
66 elseif order[o] == "max_value" then
67 if format_string_config.format_max_value then
68 table.insert(params, string.format(format_string_config.format_max_value, max_value))
69 else
70 table.insert(params, max_value)
71 end
72 end
73 end
74 local ret
75 if format_string_config.textdomain then
76 ret = minetest.translate(format_string_config.textdomain, format_string, unpack(params))
77 else
78 ret = S(format_string, unpack(params))
79 end
80 return ret
81 end
83 -- Table which contains all players with active default HUD bars (only for internal use)
84 hb.players = {}
86 function hb.value_to_barlength(value, max)
87 if max == 0 then
88 return 0
89 else
90 if hb.settings.bar_type == "progress_bar" then
91 local x
92 if value < 0 then x=-0.5 else x = 0.5 end
93 local ret = math.modf((value/max) * hb.settings.max_bar_length + x)
94 return ret
95 else
96 local x
97 if value < 0 then x=-0.5 else x = 0.5 end
98 local ret = math.modf((value/max) * hb.settings.statbar_length + x)
99 return ret
104 function hb.get_hudtable(identifier)
105 return hb.hudtables[identifier]
108 function hb.get_hudbar_position_index(identifier)
109 if hb.settings.sorting[identifier] ~= nil then
110 return hb.settings.sorting[identifier]
111 else
112 local i = 0
113 while true do
114 if hb.registered_slots[i] ~= true and hb.settings.sorting_reverse[i] == nil then
115 return i
117 i = i + 1
122 function hb.register_hudbar(identifier, text_color, label, textures, default_start_value, default_start_max, default_start_hidden, format_string, format_string_config)
123 minetest.log("action", "hb.register_hudbar: "..tostring(identifier))
124 local hudtable = {}
125 local pos, offset
126 local index = math.floor(hb.get_hudbar_position_index(identifier))
127 hb.registered_slots[index] = true
128 if hb.settings.alignment_pattern == "stack_up" then
129 pos = hb.settings.pos_left
130 offset = {
131 x = hb.settings.start_offset_left.x,
132 y = hb.settings.start_offset_left.y - hb.settings.vmargin * index
134 elseif hb.settings.alignment_pattern == "stack_down" then
135 pos = hb.settings.pos_left
136 offset = {
137 x = hb.settings.start_offset_left.x,
138 y = hb.settings.start_offset_left.y + hb.settings.vmargin * index
140 else
141 if index % 2 == 0 then
142 pos = hb.settings.pos_left
143 offset = {
144 x = hb.settings.start_offset_left.x,
145 y = hb.settings.start_offset_left.y - hb.settings.vmargin * (index/2)
147 else
148 pos = hb.settings.pos_right
149 offset = {
150 x = hb.settings.start_offset_right.x,
151 y = hb.settings.start_offset_right.y - hb.settings.vmargin * ((index-1)/2)
155 if format_string == nil then
156 format_string = N("@1: @2/@3")
158 if format_string_config == nil then
159 format_string_config = {}
161 if format_string_config.order == nil then
162 format_string_config.order = { "label", "value", "max_value" }
164 if format_string_config.format_value == nil then
165 format_string_config.format_value = "%d"
167 if format_string_config.format_max_value == nil then
168 format_string_config.format_max_value = "%d"
171 hudtable.add_all = function(player, hudtable, start_value, start_max, start_hidden)
172 if start_value == nil then start_value = hudtable.default_start_value end
173 if start_max == nil then start_max = hudtable.default_start_max end
174 if start_hidden == nil then start_hidden = hudtable.default_start_hidden end
175 local ids = {}
176 local state = {}
177 local name = player:get_player_name()
178 local bgscale, iconscale, text, barnumber, bgiconnumber
179 if start_max == 0 or start_hidden then
180 bgscale = { x=0, y=0 }
181 else
182 bgscale = { x=1, y=1 }
184 if start_hidden then
185 iconscale = { x=0, y=0 }
186 barnumber = 0
187 bgiconnumber = 0
188 text = ""
189 else
190 iconscale = { x=1, y=1 }
191 barnumber = hb.value_to_barlength(start_value, start_max)
192 bgiconnumber = hb.settings.statbar_length
193 text = make_label(format_string, format_string_config, label, start_value, start_max)
195 if hb.settings.bar_type == "progress_bar" then
196 ids.bg = player:hud_add({
197 hud_elem_type = "image",
198 position = pos,
199 scale = bgscale,
200 text = "hudbars_bar_background.png",
201 alignment = {x=1,y=1},
202 offset = { x = offset.x - 1, y = offset.y - 1 },
203 z_index = 0,
205 if textures.icon ~= nil then
206 ids.icon = player:hud_add({
207 hud_elem_type = "image",
208 position = pos,
209 scale = iconscale,
210 text = textures.icon,
211 alignment = {x=-1,y=1},
212 offset = { x = offset.x - 3, y = offset.y },
213 z_index = 1,
216 elseif hb.settings.bar_type == "statbar_modern" then
217 if textures.bgicon ~= nil then
218 ids.bg = player:hud_add({
219 hud_elem_type = "statbar",
220 position = pos,
221 text = textures.bgicon,
222 number = bgiconnumber,
223 alignment = {x=-1,y=-1},
224 offset = { x = offset.x, y = offset.y },
225 direction = 0,
226 size = {x=24, y=24},
227 z_index = 0,
231 local bar_image, bar_size
232 if hb.settings.bar_type == "progress_bar" then
233 bar_image = textures.bar
234 -- NOTE: Intentionally set to nil. For some reason, on some systems,
235 -- the progress bar is displaced when the bar_size is set explicitly here.
236 -- On the other hand, setting this to nil is deprecated in MT 5.0.0 due to
237 -- a debug log warning, but nothing is explained in lua_api.txt.
238 -- This section is a potential bug magnet, please watch with care!
239 -- The size of the bar image is expected to be exactly 2×16 pixels.
240 bar_size = nil
241 elseif hb.settings.bar_type == "statbar_classic" or hb.settings.bar_type == "statbar_modern" then
242 bar_image = textures.icon
243 bar_size = {x=24, y=24}
245 ids.bar = player:hud_add({
246 hud_elem_type = "statbar",
247 position = pos,
248 text = bar_image,
249 number = barnumber,
250 alignment = {x=-1,y=-1},
251 offset = offset,
252 direction = 0,
253 size = bar_size,
254 z_index = 1,
256 if hb.settings.bar_type == "progress_bar" then
257 ids.text = player:hud_add({
258 hud_elem_type = "text",
259 position = pos,
260 text = text,
261 alignment = {x=1,y=1},
262 number = text_color,
263 direction = 0,
264 offset = { x = offset.x + 2, y = offset.y - 1},
265 z_index = 2,
268 -- Do not forget to update hb.get_hudbar_state if you add new fields to the state table
269 state.hidden = start_hidden
270 state.value = start_value
271 state.max = start_max
272 state.text = text
273 state.barlength = hb.value_to_barlength(start_value, start_max)
275 local main_error_text =
276 "[hudbars] Bad initial values of HUD bar identifier “"..tostring(identifier).."” for player "..name..". "
278 if start_max < start_value then
279 minetest.log("error", main_error_text.."start_max ("..start_max..") is smaller than start_value ("..start_value..")!")
281 if start_max < 0 then
282 minetest.log("error", main_error_text.."start_max ("..start_max..") is smaller than 0!")
284 if start_value < 0 then
285 minetest.log("error", main_error_text.."start_value ("..start_value..") is smaller than 0!")
288 hb.hudtables[identifier].hudids[name] = ids
289 hb.hudtables[identifier].hudstate[name] = state
292 hudtable.identifier = identifier
293 hudtable.format_string = format_string
294 hudtable.format_string_config = format_string_config
295 hudtable.label = label
296 hudtable.hudids = {}
297 hudtable.hudstate = {}
298 hudtable.default_start_hidden = default_start_hidden
299 hudtable.default_start_value = default_start_value
300 hudtable.default_start_max = default_start_max
302 hb.hudbars_count= hb.hudbars_count + 1
304 hb.hudtables[identifier] = hudtable
307 function hb.init_hudbar(player, identifier, start_value, start_max, start_hidden)
308 if not player_exists(player) then return false end
309 local hudtable = hb.get_hudtable(identifier)
310 hb.hudtables[identifier].add_all(player, hudtable, start_value, start_max, start_hidden)
311 return true
314 function hb.change_hudbar(player, identifier, new_value, new_max_value, new_icon, new_bgicon, new_bar, new_label, new_text_color)
315 if new_value == nil and new_max_value == nil and new_icon == nil and new_bgicon == nil and new_bar == nil and new_label == nil and new_text_color == nil then
316 return true
318 if not player_exists(player) then
319 return false
322 local name = player:get_player_name()
323 local hudtable = hb.get_hudtable(identifier)
324 if not hudtable.hudstate[name] then
325 return false
327 local value_changed, max_changed = false, false
329 if new_value ~= nil then
330 if new_value ~= hudtable.hudstate[name].value then
331 hudtable.hudstate[name].value = new_value
332 value_changed = true
334 else
335 new_value = hudtable.hudstate[name].value
337 if new_max_value ~= nil then
338 if new_max_value ~= hudtable.hudstate[name].max then
339 hudtable.hudstate[name].max = new_max_value
340 max_changed = true
342 else
343 new_max_value = hudtable.hudstate[name].max
346 if hb.settings.bar_type == "progress_bar" then
347 if new_icon ~= nil and hudtable.hudids[name].icon ~= nil then
348 player:hud_change(hudtable.hudids[name].icon, "text", new_icon)
350 if new_bgicon ~= nil and hudtable.hudids[name].bgicon ~= nil then
351 player:hud_change(hudtable.hudids[name].bgicon, "text", new_bgicon)
353 if new_bar ~= nil then
354 player:hud_change(hudtable.hudids[name].bar , "text", new_bar)
356 if new_label ~= nil then
357 hudtable.label = new_label
358 local new_text = make_label(hudtable.format_string, hudtable.format_string_config, new_label, hudtable.hudstate[name].value, hudtable.hudstate[name].max)
359 player:hud_change(hudtable.hudids[name].text, "text", new_text)
361 if new_text_color ~= nil then
362 player:hud_change(hudtable.hudids[name].text, "number", new_text_color)
364 else
365 if new_icon ~= nil and hudtable.hudids[name].bar ~= nil then
366 player:hud_change(hudtable.hudids[name].bar, "text", new_icon)
368 if new_bgicon ~= nil and hudtable.hudids[name].bg ~= nil then
369 player:hud_change(hudtable.hudids[name].bg, "text", new_bgicon)
373 local main_error_text =
374 "[hudbars] Bad call to hb.change_hudbar, identifier: “"..tostring(identifier).."”, player name: “"..name.."”. "
375 if new_max_value < new_value then
376 minetest.log("error", main_error_text.."new_max_value ("..new_max_value..") is smaller than new_value ("..new_value..")!")
378 if new_max_value < 0 then
379 minetest.log("error", main_error_text.."new_max_value ("..new_max_value..") is smaller than 0!")
381 if new_value < 0 then
382 minetest.log("error", main_error_text.."new_value ("..new_value..") is smaller than 0!")
385 if hudtable.hudstate[name].hidden == false then
386 if max_changed and hb.settings.bar_type == "progress_bar" then
387 if hudtable.hudstate[name].max == 0 then
388 player:hud_change(hudtable.hudids[name].bg, "scale", {x=0,y=0})
389 else
390 player:hud_change(hudtable.hudids[name].bg, "scale", {x=1,y=1})
394 if value_changed or max_changed then
395 local new_barlength = hb.value_to_barlength(new_value, new_max_value)
396 if new_barlength ~= hudtable.hudstate[name].barlength then
397 player:hud_change(hudtable.hudids[name].bar, "number", hb.value_to_barlength(new_value, new_max_value))
398 hudtable.hudstate[name].barlength = new_barlength
401 if hb.settings.bar_type == "progress_bar" then
402 local new_text = make_label(hudtable.format_string, hudtable.format_string_config, hudtable.label, new_value, new_max_value)
403 if new_text ~= hudtable.hudstate[name].text then
404 player:hud_change(hudtable.hudids[name].text, "text", new_text)
405 hudtable.hudstate[name].text = new_text
410 return true
413 function hb.hide_hudbar(player, identifier)
414 if not player_exists(player) then return false end
415 local name = player:get_player_name()
416 local hudtable = hb.get_hudtable(identifier)
417 if hudtable == nil then return false end
418 if hb.settings.bar_type == "progress_bar" then
419 if hudtable.hudids[name].icon ~= nil then
420 player:hud_change(hudtable.hudids[name].icon, "scale", {x=0,y=0})
422 player:hud_change(hudtable.hudids[name].bg, "scale", {x=0,y=0})
423 player:hud_change(hudtable.hudids[name].text, "text", "")
424 elseif hb.settings.bar_type == "statbar_modern" then
425 player:hud_change(hudtable.hudids[name].bg, "number", 0)
427 player:hud_change(hudtable.hudids[name].bar, "number", 0)
428 hudtable.hudstate[name].hidden = true
429 return true
432 function hb.unhide_hudbar(player, identifier)
433 if not player_exists(player) then return false end
434 local name = player:get_player_name()
435 local hudtable = hb.get_hudtable(identifier)
436 if hudtable == nil then return false end
437 local value = hudtable.hudstate[name].value
438 local max = hudtable.hudstate[name].max
439 if hb.settings.bar_type == "progress_bar" then
440 if hudtable.hudids[name].icon ~= nil then
441 player:hud_change(hudtable.hudids[name].icon, "scale", {x=1,y=1})
443 if hudtable.hudstate[name].max ~= 0 then
444 player:hud_change(hudtable.hudids[name].bg, "scale", {x=1,y=1})
446 player:hud_change(hudtable.hudids[name].text, "text", make_label(hudtable.format_string, hudtable.format_string_config, hudtable.label, value, max))
447 elseif hb.settings.bar_type == "statbar_modern" then
448 player:hud_change(hudtable.hudids[name].bg, "number", hb.settings.statbar_length)
450 player:hud_change(hudtable.hudids[name].bar, "number", hb.value_to_barlength(value, max))
451 hudtable.hudstate[name].hidden = false
452 return true
455 function hb.get_hudbar_state(player, identifier)
456 if not player_exists(player) then return nil end
457 local ref = hb.get_hudtable(identifier).hudstate[player:get_player_name()]
458 -- Do not forget to update this chunk of code in case the state changes
459 local copy = {
460 hidden = ref.hidden,
461 value = ref.value,
462 max = ref.max,
463 text = ref.text,
464 barlength = ref.barlength,
466 return copy
469 function hb.get_hudbar_identifiers()
470 local ids = {}
471 for id, _ in pairs(hb.hudtables) do
472 table.insert(ids, id)
474 return ids
477 --register built-in HUD bars
478 if minetest.settings:get_bool("enable_damage") or hb.settings.forceload_default_hudbars then
479 hb.register_hudbar("health", 0xFFFFFF, S("Health"), { bar = "hudbars_bar_health.png", icon = "hudbars_icon_health.png", bgicon = "hudbars_bgicon_health.png" }, 20, 20, false)
480 hb.register_hudbar("breath", 0xFFFFFF, S("Breath"), { bar = "hudbars_bar_breath.png", icon = "hudbars_icon_breath.png", bgicon = "hudbars_bgicon_breath.png" }, 10, 10, true)
483 local function hide_builtin(player)
484 local flags = player:hud_get_flags()
485 flags.healthbar = false
486 flags.breathbar = false
487 player:hud_set_flags(flags)
491 local function custom_hud(player)
492 if minetest.settings:get_bool("enable_damage") or hb.settings.forceload_default_hudbars then
493 local hide
494 if minetest.settings:get_bool("enable_damage") then
495 hide = false
496 else
497 hide = true
499 local hp = player:get_hp()
500 local hp_max = player:get_properties().hp_max
501 hb.init_hudbar(player, "health", math.min(hp, hp_max), hp_max, hide)
502 local breath = player:get_breath()
503 local breath_max = player:get_properties().breath_max
504 local hide_breath
505 if breath >= breath_max and hb.settings.autohide_breath == true then hide_breath = true else hide_breath = false end
506 hb.init_hudbar(player, "breath", math.min(breath, breath_max-1), breath_max-1, hide_breath or hide)
510 local function update_health(player)
511 local hp_max = player:get_properties().hp_max
512 hb.change_hudbar(player, "health", player:get_hp(), hp_max)
515 -- update built-in HUD bars
516 local function update_hud(player)
517 if not player_exists(player) then return end
518 if minetest.settings:get_bool("enable_damage") then
519 if hb.settings.forceload_default_hudbars then
520 hb.unhide_hudbar(player, "health")
522 --air
523 local breath_max = player:get_properties().breath_max
524 local breath = player:get_breath()
526 if breath >= breath_max and hb.settings.autohide_breath == true then
527 hb.hide_hudbar(player, "breath")
528 else
529 hb.unhide_hudbar(player, "breath")
530 hb.change_hudbar(player, "breath", math.min(breath, breath_max-1), breath_max-1)
532 --health
533 update_health(player)
534 elseif hb.settings.forceload_default_hudbars then
535 hb.hide_hudbar(player, "health")
536 hb.hide_hudbar(player, "breath")
540 minetest.register_on_player_hpchange(function(player)
541 if hb.players[player:get_player_name()] ~= nil then
542 update_health(player)
544 end)
546 minetest.register_on_respawnplayer(function(player)
547 update_health(player)
548 hb.hide_hudbar(player, "breath")
549 end)
551 minetest.register_on_joinplayer(function(player)
552 hide_builtin(player)
553 custom_hud(player)
554 hb.players[player:get_player_name()] = player
555 end)
557 minetest.register_on_leaveplayer(function(player)
558 hb.players[player:get_player_name()] = nil
559 end)
561 local main_timer = 0
562 local timer = 0
563 minetest.register_globalstep(function(dtime)
564 main_timer = main_timer + dtime
565 timer = timer + dtime
566 if main_timer > hb.settings.tick or timer > 4 then
567 if main_timer > hb.settings.tick then main_timer = 0 end
568 -- only proceed if damage is enabled
569 if minetest.settings:get_bool("enable_damage") or hb.settings.forceload_default_hudbars then
570 for _, player in pairs(hb.players) do
571 -- update all hud elements
572 update_hud(player)
576 if timer > 4 then timer = 0 end
577 end)