Fix possible crash after player left
[minetest_hudbars.git] / init.lua
blob651a22d13b7bd1dbab0d22b35b2409092780a094
1 local S
2 if (minetest.get_modpath("intllib")) then
3 S = intllib.Getter()
4 else
5 S = function ( s ) return s end
6 end
8 hb = {}
10 hb.hudtables = {}
12 -- number of registered HUD bars
13 hb.hudbars_count = 0
15 -- table which records which HUD bar slots have been “registered” so far; used for automatic positioning
16 hb.registered_slots = {}
18 hb.settings = {}
20 function hb.load_setting(sname, stype, defaultval, valid_values)
21 local sval
22 if stype == "string" then
23 sval = minetest.setting_get(sname)
24 elseif stype == "bool" then
25 sval = minetest.setting_getbool(sname)
26 elseif stype == "number" then
27 sval = tonumber(minetest.setting_get(sname))
28 end
29 if sval ~= nil then
30 if valid_values ~= nil then
31 local valid = false
32 for i=1,#valid_values do
33 if sval == valid_values[i] then
34 valid = true
35 end
36 end
37 if not valid then
38 minetest.log("error", "[hudbars] Invalid value for "..sname.."! Using default value ("..tostring(defaultval)..").")
39 return defaultval
40 else
41 return sval
42 end
43 else
44 return sval
45 end
46 else
47 return defaultval
48 end
49 end
51 -- (hardcoded) default settings
52 hb.settings.max_bar_length = 160
53 hb.settings.statbar_length = 20
55 -- statbar positions
56 hb.settings.pos_left = {}
57 hb.settings.pos_right = {}
58 hb.settings.start_offset_left = {}
59 hb.settings.start_offset_right= {}
60 hb.settings.pos_left.x = hb.load_setting("hudbars_pos_left_x", "number", 0.5)
61 hb.settings.pos_left.y = hb.load_setting("hudbars_pos_left_y", "number", 1)
62 hb.settings.pos_right.x = hb.load_setting("hudbars_pos_right_x", "number", 0.5)
63 hb.settings.pos_right.y = hb.load_setting("hudbars_pos_right_y", "number", 1)
64 hb.settings.start_offset_left.x = hb.load_setting("hudbars_start_offset_left_x", "number", -175)
65 hb.settings.start_offset_left.y = hb.load_setting("hudbars_start_offset_left_y", "number", -86)
66 hb.settings.start_offset_right.x = hb.load_setting("hudbars_start_offset_right_x", "number", 15)
67 hb.settings.start_offset_right.y = hb.load_setting("hudbars_start_offset_right_y", "number", -86)
69 hb.settings.vmargin = hb.load_setting("hudbars_vmargin", "number", 24)
70 hb.settings.tick = hb.load_setting("hudbars_tick", "number", 0.1)
72 -- experimental setting: Changing this setting is not officially supported, do NOT rely on it!
73 hb.settings.forceload_default_hudbars = hb.load_setting("hudbars_forceload_default_hudbars", "bool", true)
75 -- Misc. settings
76 hb.settings.alignment_pattern = hb.load_setting("hudbars_alignment_pattern", "string", "zigzag", {"zigzag", "stack_up", "stack_down"})
77 hb.settings.bar_type = hb.load_setting("hudbars_bar_type", "string", "progress_bar", {"progress_bar", "statbar_classic", "statbar_modern"})
78 hb.settings.autohide_breath = hb.load_setting("hudbars_autohide_breath", "bool", true)
80 local sorting = minetest.setting_get("hudbars_sorting")
81 if sorting ~= nil then
82 hb.settings.sorting = {}
83 hb.settings.sorting_reverse = {}
84 for k,v in string.gmatch(sorting, "(%w+)=(%w+)") do
85 hb.settings.sorting[k] = tonumber(v)
86 hb.settings.sorting_reverse[tonumber(v)] = k
87 end
88 else
89 hb.settings.sorting = { ["health"] = 0, ["breath"] = 1 }
90 hb.settings.sorting_reverse = { [0] = "health", [1] = "breath" }
91 end
93 local function player_exists(player)
94 return player ~= nil and player:is_player()
95 end
97 -- Table which contains all players with active default HUD bars (only for internal use)
98 hb.players = {}
100 function hb.value_to_barlength(value, max)
101 if max == 0 then
102 return 0
103 else
104 if hb.settings.bar_type == "progress_bar" then
105 local x
106 if value < 0 then x=-0.5 else x = 0.5 end
107 local ret = math.modf((value/max) * hb.settings.max_bar_length + x)
108 return ret
109 else
110 local x
111 if value < 0 then x=-0.5 else x = 0.5 end
112 local ret = math.modf((value/max) * hb.settings.statbar_length + x)
113 return ret
118 function hb.get_hudtable(identifier)
119 return hb.hudtables[identifier]
122 function hb.get_hudbar_position_index(identifier)
123 if hb.settings.sorting[identifier] ~= nil then
124 return hb.settings.sorting[identifier]
125 else
126 local i = 0
127 while true do
128 if hb.registered_slots[i] ~= true and hb.settings.sorting_reverse[i] == nil then
129 return i
131 i = i + 1
136 function hb.register_hudbar(identifier, text_color, label, textures, default_start_value, default_start_max, default_start_hidden, format_string)
137 minetest.log("action", "hb.register_hudbar: "..tostring(identifier))
138 local hudtable = {}
139 local pos, offset
140 local index = math.floor(hb.get_hudbar_position_index(identifier))
141 hb.registered_slots[index] = true
142 if hb.settings.alignment_pattern == "stack_up" then
143 pos = hb.settings.pos_left
144 offset = {
145 x = hb.settings.start_offset_left.x,
146 y = hb.settings.start_offset_left.y - hb.settings.vmargin * index
148 elseif hb.settings.alignment_pattern == "stack_down" then
149 pos = hb.settings.pos_left
150 offset = {
151 x = hb.settings.start_offset_left.x,
152 y = hb.settings.start_offset_left.y + hb.settings.vmargin * index
154 else
155 if index % 2 == 0 then
156 pos = hb.settings.pos_left
157 offset = {
158 x = hb.settings.start_offset_left.x,
159 y = hb.settings.start_offset_left.y - hb.settings.vmargin * (index/2)
161 else
162 pos = hb.settings.pos_right
163 offset = {
164 x = hb.settings.start_offset_right.x,
165 y = hb.settings.start_offset_right.y - hb.settings.vmargin * ((index-1)/2)
169 if format_string == nil then
170 format_string = S("%s: %d/%d")
173 hudtable.add_all = function(player, hudtable, start_value, start_max, start_hidden)
174 if start_value == nil then start_value = hudtable.default_start_value end
175 if start_max == nil then start_max = hudtable.default_start_max end
176 if start_hidden == nil then start_hidden = hudtable.default_start_hidden end
177 local ids = {}
178 local state = {}
179 local name = player:get_player_name()
180 local bgscale, iconscale, text, barnumber, bgiconnumber
181 if start_max == 0 or start_hidden then
182 bgscale = { x=0, y=0 }
183 else
184 bgscale = { x=1, y=1 }
186 if start_hidden then
187 iconscale = { x=0, y=0 }
188 barnumber = 0
189 bgiconnumber = 0
190 text = ""
191 else
192 iconscale = { x=1, y=1 }
193 barnumber = hb.value_to_barlength(start_value, start_max)
194 bgiconnumber = hb.settings.statbar_length
195 text = string.format(format_string, label, start_value, start_max)
197 if hb.settings.bar_type == "progress_bar" then
198 ids.bg = player:hud_add({
199 hud_elem_type = "image",
200 position = pos,
201 scale = bgscale,
202 text = "hudbars_bar_background.png",
203 alignment = {x=1,y=1},
204 offset = { x = offset.x - 1, y = offset.y - 1 },
206 if textures.icon ~= nil then
207 ids.icon = player:hud_add({
208 hud_elem_type = "image",
209 position = pos,
210 scale = iconscale,
211 text = textures.icon,
212 alignment = {x=-1,y=1},
213 offset = { x = offset.x - 3, y = offset.y },
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 },
228 local bar_image
229 if hb.settings.bar_type == "progress_bar" then
230 bar_image = textures.bar
231 elseif hb.settings.bar_type == "statbar_classic" or hb.settings.bar_type == "statbar_modern" then
232 bar_image = textures.icon
234 ids.bar = player:hud_add({
235 hud_elem_type = "statbar",
236 position = pos,
237 text = bar_image,
238 number = barnumber,
239 alignment = {x=-1,y=-1},
240 offset = offset,
242 if hb.settings.bar_type == "progress_bar" then
243 ids.text = player:hud_add({
244 hud_elem_type = "text",
245 position = pos,
246 text = text,
247 alignment = {x=1,y=1},
248 number = text_color,
249 direction = 0,
250 offset = { x = offset.x + 2, y = offset.y - 1},
253 -- Do not forget to update hb.get_hudbar_state if you add new fields to the state table
254 state.hidden = start_hidden
255 state.value = start_value
256 state.max = start_max
257 state.text = text
258 state.barlength = hb.value_to_barlength(start_value, start_max)
260 local main_error_text =
261 "[hudbars] Bad initial values of HUD bar identifier “"..tostring(identifier).."” for player "..name..". "
263 if start_max < start_value then
264 minetest.log("error", main_error_text.."start_max ("..start_max..") is smaller than start_value ("..start_value..")!")
266 if start_max < 0 then
267 minetest.log("error", main_error_text.."start_max ("..start_max..") is smaller than 0!")
269 if start_value < 0 then
270 minetest.log("error", main_error_text.."start_value ("..start_value..") is smaller than 0!")
273 hb.hudtables[identifier].hudids[name] = ids
274 hb.hudtables[identifier].hudstate[name] = state
277 hudtable.identifier = identifier
278 hudtable.format_string = format_string
279 hudtable.label = label
280 hudtable.hudids = {}
281 hudtable.hudstate = {}
282 hudtable.default_start_hidden = default_start_hidden
283 hudtable.default_start_value = default_start_value
284 hudtable.default_start_max = default_start_max
286 hb.hudbars_count= hb.hudbars_count + 1
288 hb.hudtables[identifier] = hudtable
291 function hb.init_hudbar(player, identifier, start_value, start_max, start_hidden)
292 if not player_exists(player) then return false end
293 local hudtable = hb.get_hudtable(identifier)
294 hb.hudtables[identifier].add_all(player, hudtable, start_value, start_max, start_hidden)
295 return true
298 function hb.change_hudbar(player, identifier, new_value, new_max_value, new_icon, new_bgicon, new_bar, new_label, new_text_color)
299 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
300 return true
302 if not player_exists(player) then
303 return false
306 local name = player:get_player_name()
307 local hudtable = hb.get_hudtable(identifier)
308 local value_changed, max_changed = false, false
310 if new_value ~= nil then
311 if new_value ~= hudtable.hudstate[name].value then
312 hudtable.hudstate[name].value = new_value
313 value_changed = true
315 else
316 new_value = hudtable.hudstate[name].value
318 if new_max_value ~= nil then
319 if new_max_value ~= hudtable.hudstate[name].max then
320 hudtable.hudstate[name].max = new_max_value
321 max_changed = true
323 else
324 new_max_value = hudtable.hudstate[name].max
327 if hb.settings.bar_type == "progress_bar" then
328 if new_icon ~= nil and hudtable.hudids[name].icon ~= nil then
329 player:hud_change(hudtable.hudids[name].icon, "text", new_icon)
331 if new_bgicon ~= nil and hudtable.hudids[name].bgicon ~= nil then
332 player:hud_change(hudtable.hudids[name].bgicon, "text", new_bgicon)
334 if new_bar ~= nil then
335 player:hud_change(hudtable.hudids[name].bar , "text", new_bar)
337 if new_label ~= nil then
338 hudtable.label = new_label
339 local new_text = string.format(hudtable.format_string, new_label, hudtable.hudstate[name].value, hudtable.hudstate[name].max)
340 player:hud_change(hudtable.hudids[name].text, "text", new_text)
342 if new_text_color ~= nil then
343 player:hud_change(hudtable.hudids[name].text, "number", new_text_color)
345 else
346 if new_icon ~= nil and hudtable.hudids[name].bar ~= nil then
347 player:hud_change(hudtable.hudids[name].bar, "text", new_icon)
349 if new_bgicon ~= nil and hudtable.hudids[name].bg ~= nil then
350 player:hud_change(hudtable.hudids[name].bg, "text", new_bgicon)
354 local main_error_text =
355 "[hudbars] Bad call to hb.change_hudbar, identifier: “"..tostring(identifier).."”, player name: “"..name.."”. "
356 if new_max_value < new_value then
357 minetest.log("error", main_error_text.."new_max_value ("..new_max_value..") is smaller than new_value ("..new_value..")!")
359 if new_max_value < 0 then
360 minetest.log("error", main_error_text.."new_max_value ("..new_max_value..") is smaller than 0!")
362 if new_value < 0 then
363 minetest.log("error", main_error_text.."new_value ("..new_value..") is smaller than 0!")
366 if hudtable.hudstate[name].hidden == false then
367 if max_changed and hb.settings.bar_type == "progress_bar" then
368 if hudtable.hudstate[name].max == 0 then
369 player:hud_change(hudtable.hudids[name].bg, "scale", {x=0,y=0})
370 else
371 player:hud_change(hudtable.hudids[name].bg, "scale", {x=1,y=1})
375 if value_changed or max_changed then
376 local new_barlength = hb.value_to_barlength(new_value, new_max_value)
377 if new_barlength ~= hudtable.hudstate[name].barlength then
378 player:hud_change(hudtable.hudids[name].bar, "number", hb.value_to_barlength(new_value, new_max_value))
379 hudtable.hudstate[name].barlength = new_barlength
382 if hb.settings.bar_type == "progress_bar" then
383 local new_text = string.format(hudtable.format_string, hudtable.label, new_value, new_max_value)
384 if new_text ~= hudtable.hudstate[name].text then
385 player:hud_change(hudtable.hudids[name].text, "text", new_text)
386 hudtable.hudstate[name].text = new_text
391 return true
394 function hb.hide_hudbar(player, identifier)
395 if not player_exists(player) then return false end
396 local name = player:get_player_name()
397 local hudtable = hb.get_hudtable(identifier)
398 if hudtable == nil then return false end
399 if(hudtable.hudstate[name].hidden == false) then
400 if hb.settings.bar_type == "progress_bar" then
401 if hudtable.hudids[name].icon ~= nil then
402 player:hud_change(hudtable.hudids[name].icon, "scale", {x=0,y=0})
404 player:hud_change(hudtable.hudids[name].bg, "scale", {x=0,y=0})
405 player:hud_change(hudtable.hudids[name].text, "text", "")
406 elseif hb.settings.bar_type == "statbar_modern" then
407 player:hud_change(hudtable.hudids[name].bg, "number", 0)
409 player:hud_change(hudtable.hudids[name].bar, "number", 0)
410 hudtable.hudstate[name].hidden = true
412 return true
415 function hb.unhide_hudbar(player, identifier)
416 if not player_exists(player) then return false end
417 local name = player:get_player_name()
418 local hudtable = hb.get_hudtable(identifier)
419 if hudtable == nil then return false end
420 if(hudtable.hudstate[name].hidden) then
421 local value = hudtable.hudstate[name].value
422 local max = hudtable.hudstate[name].max
423 if hb.settings.bar_type == "progress_bar" then
424 if hudtable.hudids[name].icon ~= nil then
425 player:hud_change(hudtable.hudids[name].icon, "scale", {x=1,y=1})
427 if hudtable.hudstate[name].max ~= 0 then
428 player:hud_change(hudtable.hudids[name].bg, "scale", {x=1,y=1})
430 player:hud_change(hudtable.hudids[name].text, "text", tostring(string.format(hudtable.format_string, hudtable.label, value, max)))
431 elseif hb.settings.bar_type == "statbar_modern" then
432 player:hud_change(hudtable.hudids[name].bg, "number", hb.settings.statbar_length)
434 player:hud_change(hudtable.hudids[name].bar, "number", hb.value_to_barlength(value, max))
435 hudtable.hudstate[name].hidden = false
437 return true
440 function hb.get_hudbar_state(player, identifier)
441 if not player_exists(player) then return nil end
442 local ref = hb.get_hudtable(identifier).hudstate[player:get_player_name()]
443 -- Do not forget to update this chunk of code in case the state changes
444 local copy = {
445 hidden = ref.hidden,
446 value = ref.value,
447 max = ref.max,
448 text = ref.text,
449 barlength = ref.barlength,
451 return copy
454 --register built-in HUD bars
455 if minetest.setting_getbool("enable_damage") or hb.settings.forceload_default_hudbars then
456 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)
457 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)
460 local function hide_builtin(player)
461 local flags = player:hud_get_flags()
462 flags.healthbar = false
463 flags.breathbar = false
464 player:hud_set_flags(flags)
468 local function custom_hud(player)
469 if minetest.setting_getbool("enable_damage") or hb.settings.forceload_default_hudbars then
470 local hide
471 if minetest.setting_getbool("enable_damage") then
472 hide = false
473 else
474 hide = true
476 hb.init_hudbar(player, "health", player:get_hp(), nil, hide)
477 local breath = player:get_breath()
478 local hide_breath
479 if breath == 11 and hb.settings.autohide_breath == true then hide_breath = true else hide_breath = false end
480 hb.init_hudbar(player, "breath", math.min(breath, 10), nil, hide_breath or hide)
485 -- update built-in HUD bars
486 local function update_hud(player)
487 if not player_exists() then return end
488 if minetest.setting_getbool("enable_damage") then
489 if hb.settings.forceload_default_hudbars then
490 hb.unhide_hudbar(player, "health")
492 --air
493 local breath = player:get_breath()
495 if breath == 11 and hb.settings.autohide_breath == true then
496 hb.hide_hudbar(player, "breath")
497 else
498 hb.unhide_hudbar(player, "breath")
499 hb.change_hudbar(player, "breath", math.min(breath, 10))
502 --health
503 hb.change_hudbar(player, "health", player:get_hp())
504 elseif hb.settings.forceload_default_hudbars then
505 hb.hide_hudbar(player, "health")
506 hb.hide_hudbar(player, "breath")
510 minetest.register_on_joinplayer(function(player)
511 hide_builtin(player)
512 custom_hud(player)
513 hb.players[player:get_player_name()] = player
514 end)
516 minetest.register_on_leaveplayer(function(player)
517 hb.players[player:get_player_name()] = nil
518 end)
520 local main_timer = 0
521 local timer = 0
522 minetest.register_globalstep(function(dtime)
523 main_timer = main_timer + dtime
524 timer = timer + dtime
525 if main_timer > hb.settings.tick or timer > 4 then
526 if main_timer > hb.settings.tick then main_timer = 0 end
527 -- only proceed if damage is enabled
528 if minetest.setting_getbool("enable_damage") or hb.settings.forceload_default_hudbars then
529 for _, player in pairs(hb.players) do
530 -- update all hud elements
531 minetest.after(1,function(player) update_hud(player) end, player)
532 -- update_hud(player)
536 if timer > 4 then timer = 0 end
537 end)