Fix another crash
[minetest_hudbars.git] / init.lua
blobbac79412ada2a995e032844c96dc5464d2e8fcae
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 },
204 if textures.icon ~= nil then
205 ids.icon = player:hud_add({
206 hud_elem_type = "image",
207 position = pos,
208 scale = iconscale,
209 text = textures.icon,
210 alignment = {x=-1,y=1},
211 offset = { x = offset.x - 3, y = offset.y },
214 elseif hb.settings.bar_type == "statbar_modern" then
215 if textures.bgicon ~= nil then
216 ids.bg = player:hud_add({
217 hud_elem_type = "statbar",
218 position = pos,
219 text = textures.bgicon,
220 number = bgiconnumber,
221 alignment = {x=-1,y=-1},
222 offset = { x = offset.x, y = offset.y },
223 direction = 0,
224 size = {x=24, y=24},
228 local bar_image, bar_size
229 if hb.settings.bar_type == "progress_bar" then
230 bar_image = textures.bar
231 -- NOTE: Intentionally set to nil. For some reason, on some systems,
232 -- the progress bar is displaced when the bar_size is set explicitly here.
233 -- On the other hand, setting this to nil is deprecated in MT 5.0.0 due to
234 -- a debug log warning, but nothing is explained in lua_api.txt.
235 -- This section is a potential bug magnet, please watch with care!
236 -- The size of the bar image is expected to be exactly 2×16 pixels.
237 bar_size = nil
238 elseif hb.settings.bar_type == "statbar_classic" or hb.settings.bar_type == "statbar_modern" then
239 bar_image = textures.icon
240 bar_size = {x=24, y=24}
242 ids.bar = player:hud_add({
243 hud_elem_type = "statbar",
244 position = pos,
245 text = bar_image,
246 number = barnumber,
247 alignment = {x=-1,y=-1},
248 offset = offset,
249 direction = 0,
250 size = bar_size,
252 if hb.settings.bar_type == "progress_bar" then
253 ids.text = player:hud_add({
254 hud_elem_type = "text",
255 position = pos,
256 text = text,
257 alignment = {x=1,y=1},
258 number = text_color,
259 direction = 0,
260 offset = { x = offset.x + 2, y = offset.y - 1},
263 -- Do not forget to update hb.get_hudbar_state if you add new fields to the state table
264 state.hidden = start_hidden
265 state.value = start_value
266 state.max = start_max
267 state.text = text
268 state.barlength = hb.value_to_barlength(start_value, start_max)
270 local main_error_text =
271 "[hudbars] Bad initial values of HUD bar identifier “"..tostring(identifier).."” for player "..name..". "
273 if start_max < start_value then
274 minetest.log("error", main_error_text.."start_max ("..start_max..") is smaller than start_value ("..start_value..")!")
276 if start_max < 0 then
277 minetest.log("error", main_error_text.."start_max ("..start_max..") is smaller than 0!")
279 if start_value < 0 then
280 minetest.log("error", main_error_text.."start_value ("..start_value..") is smaller than 0!")
283 hb.hudtables[identifier].hudids[name] = ids
284 hb.hudtables[identifier].hudstate[name] = state
287 hudtable.identifier = identifier
288 hudtable.format_string = format_string
289 hudtable.format_string_config = format_string_config
290 hudtable.label = label
291 hudtable.hudids = {}
292 hudtable.hudstate = {}
293 hudtable.default_start_hidden = default_start_hidden
294 hudtable.default_start_value = default_start_value
295 hudtable.default_start_max = default_start_max
297 hb.hudbars_count= hb.hudbars_count + 1
299 hb.hudtables[identifier] = hudtable
302 function hb.init_hudbar(player, identifier, start_value, start_max, start_hidden)
303 if not player_exists(player) then return false end
304 local hudtable = hb.get_hudtable(identifier)
305 hb.hudtables[identifier].add_all(player, hudtable, start_value, start_max, start_hidden)
306 return true
309 function hb.change_hudbar(player, identifier, new_value, new_max_value, new_icon, new_bgicon, new_bar, new_label, new_text_color)
310 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
311 return true
313 if not player_exists(player) then
314 return false
317 local name = player:get_player_name()
318 local hudtable = hb.get_hudtable(identifier)
319 if not hudtable.hudstate[name] then
320 return false
322 local value_changed, max_changed = false, false
324 if new_value ~= nil then
325 if new_value ~= hudtable.hudstate[name].value then
326 hudtable.hudstate[name].value = new_value
327 value_changed = true
329 else
330 new_value = hudtable.hudstate[name].value
332 if new_max_value ~= nil then
333 if new_max_value ~= hudtable.hudstate[name].max then
334 hudtable.hudstate[name].max = new_max_value
335 max_changed = true
337 else
338 new_max_value = hudtable.hudstate[name].max
341 if hb.settings.bar_type == "progress_bar" then
342 if new_icon ~= nil and hudtable.hudids[name].icon ~= nil then
343 player:hud_change(hudtable.hudids[name].icon, "text", new_icon)
345 if new_bgicon ~= nil and hudtable.hudids[name].bgicon ~= nil then
346 player:hud_change(hudtable.hudids[name].bgicon, "text", new_bgicon)
348 if new_bar ~= nil then
349 player:hud_change(hudtable.hudids[name].bar , "text", new_bar)
351 if new_label ~= nil then
352 hudtable.label = new_label
353 local new_text = make_label(hudtable.format_string, hudtable.format_string_config, new_label, hudtable.hudstate[name].value, hudtable.hudstate[name].max)
354 player:hud_change(hudtable.hudids[name].text, "text", new_text)
356 if new_text_color ~= nil then
357 player:hud_change(hudtable.hudids[name].text, "number", new_text_color)
359 else
360 if new_icon ~= nil and hudtable.hudids[name].bar ~= nil then
361 player:hud_change(hudtable.hudids[name].bar, "text", new_icon)
363 if new_bgicon ~= nil and hudtable.hudids[name].bg ~= nil then
364 player:hud_change(hudtable.hudids[name].bg, "text", new_bgicon)
368 local main_error_text =
369 "[hudbars] Bad call to hb.change_hudbar, identifier: “"..tostring(identifier).."”, player name: “"..name.."”. "
370 if new_max_value < new_value then
371 minetest.log("error", main_error_text.."new_max_value ("..new_max_value..") is smaller than new_value ("..new_value..")!")
373 if new_max_value < 0 then
374 minetest.log("error", main_error_text.."new_max_value ("..new_max_value..") is smaller than 0!")
376 if new_value < 0 then
377 minetest.log("error", main_error_text.."new_value ("..new_value..") is smaller than 0!")
380 if hudtable.hudstate[name].hidden == false then
381 if max_changed and hb.settings.bar_type == "progress_bar" then
382 if hudtable.hudstate[name].max == 0 then
383 player:hud_change(hudtable.hudids[name].bg, "scale", {x=0,y=0})
384 else
385 player:hud_change(hudtable.hudids[name].bg, "scale", {x=1,y=1})
389 if value_changed or max_changed then
390 local new_barlength = hb.value_to_barlength(new_value, new_max_value)
391 if new_barlength ~= hudtable.hudstate[name].barlength then
392 player:hud_change(hudtable.hudids[name].bar, "number", hb.value_to_barlength(new_value, new_max_value))
393 hudtable.hudstate[name].barlength = new_barlength
396 if hb.settings.bar_type == "progress_bar" then
397 local new_text = make_label(hudtable.format_string, hudtable.format_string_config, hudtable.label, new_value, new_max_value)
398 if new_text ~= hudtable.hudstate[name].text then
399 player:hud_change(hudtable.hudids[name].text, "text", new_text)
400 hudtable.hudstate[name].text = new_text
405 return true
408 function hb.hide_hudbar(player, identifier)
409 if not player_exists(player) then return false end
410 local name = player:get_player_name()
411 local hudtable = hb.get_hudtable(identifier)
412 if hudtable == nil then return false end
413 if hb.settings.bar_type == "progress_bar" then
414 if hudtable.hudids[name].icon ~= nil then
415 player:hud_change(hudtable.hudids[name].icon, "scale", {x=0,y=0})
417 player:hud_change(hudtable.hudids[name].bg, "scale", {x=0,y=0})
418 player:hud_change(hudtable.hudids[name].text, "text", "")
419 elseif hb.settings.bar_type == "statbar_modern" then
420 player:hud_change(hudtable.hudids[name].bg, "number", 0)
422 player:hud_change(hudtable.hudids[name].bar, "number", 0)
423 hudtable.hudstate[name].hidden = true
424 return true
427 function hb.unhide_hudbar(player, identifier)
428 if not player_exists(player) then return false end
429 local name = player:get_player_name()
430 local hudtable = hb.get_hudtable(identifier)
431 if hudtable == nil then return false end
432 local value = hudtable.hudstate[name].value
433 local max = hudtable.hudstate[name].max
434 if hb.settings.bar_type == "progress_bar" then
435 if hudtable.hudids[name].icon ~= nil then
436 player:hud_change(hudtable.hudids[name].icon, "scale", {x=1,y=1})
438 if hudtable.hudstate[name].max ~= 0 then
439 player:hud_change(hudtable.hudids[name].bg, "scale", {x=1,y=1})
441 player:hud_change(hudtable.hudids[name].text, "text", make_label(hudtable.format_string, hudtable.format_string_config, hudtable.label, value, max))
442 elseif hb.settings.bar_type == "statbar_modern" then
443 player:hud_change(hudtable.hudids[name].bg, "number", hb.settings.statbar_length)
445 player:hud_change(hudtable.hudids[name].bar, "number", hb.value_to_barlength(value, max))
446 hudtable.hudstate[name].hidden = false
447 return true
450 function hb.get_hudbar_state(player, identifier)
451 if not player_exists(player) then return nil end
452 local ref = hb.get_hudtable(identifier).hudstate[player:get_player_name()]
453 -- Do not forget to update this chunk of code in case the state changes
454 local copy = {
455 hidden = ref.hidden,
456 value = ref.value,
457 max = ref.max,
458 text = ref.text,
459 barlength = ref.barlength,
461 return copy
464 function hb.get_hudbar_identifiers()
465 local ids = {}
466 for id, _ in pairs(hb.hudtables) do
467 table.insert(ids, id)
469 return ids
472 --register built-in HUD bars
473 if minetest.settings:get_bool("enable_damage") or hb.settings.forceload_default_hudbars then
474 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)
475 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)
478 local function hide_builtin(player)
479 local flags = player:hud_get_flags()
480 flags.healthbar = false
481 flags.breathbar = false
482 player:hud_set_flags(flags)
486 local function custom_hud(player)
487 if minetest.settings:get_bool("enable_damage") or hb.settings.forceload_default_hudbars then
488 local hide
489 if minetest.settings:get_bool("enable_damage") then
490 hide = false
491 else
492 hide = true
494 local hp = player:get_hp()
495 local hp_max = player:get_properties().hp_max
496 hb.init_hudbar(player, "health", math.min(hp, hp_max), hp_max, hide)
497 local breath = player:get_breath()
498 local breath_max = player:get_properties().breath_max
499 local hide_breath
500 if breath >= breath_max and hb.settings.autohide_breath == true then hide_breath = true else hide_breath = false end
501 hb.init_hudbar(player, "breath", math.min(breath, breath_max-1), breath_max-1, hide_breath or hide)
505 local function update_health(player)
506 local hp_max = player:get_properties().hp_max
507 hb.change_hudbar(player, "health", player:get_hp(), hp_max)
510 -- update built-in HUD bars
511 local function update_hud(player)
512 if not player_exists(player) then return end
513 if minetest.settings:get_bool("enable_damage") then
514 if hb.settings.forceload_default_hudbars then
515 hb.unhide_hudbar(player, "health")
517 --air
518 local breath_max = player:get_properties().breath_max
519 local breath = player:get_breath()
521 if breath >= breath_max and hb.settings.autohide_breath == true then
522 hb.hide_hudbar(player, "breath")
523 else
524 hb.unhide_hudbar(player, "breath")
525 hb.change_hudbar(player, "breath", math.min(breath, breath_max-1), breath_max-1)
527 --health
528 update_health(player)
529 elseif hb.settings.forceload_default_hudbars then
530 hb.hide_hudbar(player, "health")
531 hb.hide_hudbar(player, "breath")
535 minetest.register_on_player_hpchange(function(player)
536 if hb.players[player:get_player_name()] ~= nil then
537 update_health(player)
539 end)
541 minetest.register_on_respawnplayer(function(player)
542 update_health(player)
543 hb.hide_hudbar(player, "breath")
544 end)
546 minetest.register_on_joinplayer(function(player)
547 hide_builtin(player)
548 custom_hud(player)
549 hb.players[player:get_player_name()] = player
550 end)
552 minetest.register_on_leaveplayer(function(player)
553 hb.players[player:get_player_name()] = nil
554 end)
556 local main_timer = 0
557 local timer = 0
558 minetest.register_globalstep(function(dtime)
559 main_timer = main_timer + dtime
560 timer = timer + dtime
561 if main_timer > hb.settings.tick or timer > 4 then
562 if main_timer > hb.settings.tick then main_timer = 0 end
563 -- only proceed if damage is enabled
564 if minetest.settings:get_bool("enable_damage") or hb.settings.forceload_default_hudbars then
565 for _, player in pairs(hb.players) do
566 -- update all hud elements
567 update_hud(player)
571 if timer > 4 then timer = 0 end
572 end)