Fix crash HP changed before player was registered
[minetest_hudbars.git] / init.lua
blob89d8b1cc15d062a46a40d46b21737fb24ecfd415
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.bar_type = hb.load_setting("hudbars_bar_type", "string", "progress_bar", {"progress_bar", "statbar_classic", "statbar_modern"})
65 if hb.settings.bar_type == "progress_bar" then
66 hb.settings.start_offset_left.x = hb.load_setting("hudbars_start_offset_left_x", "number", -175)
67 hb.settings.start_offset_left.y = hb.load_setting("hudbars_start_offset_left_y", "number", -86)
68 hb.settings.start_offset_right.x = hb.load_setting("hudbars_start_offset_right_x", "number", 15)
69 hb.settings.start_offset_right.y = hb.load_setting("hudbars_start_offset_right_y", "number", -86)
70 else
71 hb.settings.start_offset_left.x = hb.load_setting("hudbars_start_statbar_offset_left_x", "number", -265)
72 hb.settings.start_offset_left.y = hb.load_setting("hudbars_start_statbar_offset_left_y", "number", -90)
73 hb.settings.start_offset_right.x = hb.load_setting("hudbars_start_statbar_offset_right_x", "number", 25)
74 hb.settings.start_offset_right.y = hb.load_setting("hudbars_start_statbar_offset_right_y", "number", -90)
75 end
76 hb.settings.vmargin = hb.load_setting("hudbars_vmargin", "number", 24)
77 hb.settings.tick = hb.load_setting("hudbars_tick", "number", 0.1)
79 -- experimental setting: Changing this setting is not officially supported, do NOT rely on it!
80 hb.settings.forceload_default_hudbars = hb.load_setting("hudbars_forceload_default_hudbars", "bool", true)
82 -- Misc. settings
83 hb.settings.alignment_pattern = hb.load_setting("hudbars_alignment_pattern", "string", "zigzag", {"zigzag", "stack_up", "stack_down"})
84 hb.settings.autohide_breath = hb.load_setting("hudbars_autohide_breath", "bool", true)
86 local sorting = minetest.setting_get("hudbars_sorting")
87 if sorting ~= nil then
88 hb.settings.sorting = {}
89 hb.settings.sorting_reverse = {}
90 for k,v in string.gmatch(sorting, "(%w+)=(%w+)") do
91 hb.settings.sorting[k] = tonumber(v)
92 hb.settings.sorting_reverse[tonumber(v)] = k
93 end
94 else
95 hb.settings.sorting = { ["health"] = 0, ["breath"] = 1 }
96 hb.settings.sorting_reverse = { [0] = "health", [1] = "breath" }
97 end
99 local function player_exists(player)
100 return player ~= nil and player:is_player()
103 -- Table which contains all players with active default HUD bars (only for internal use)
104 hb.players = {}
106 function hb.value_to_barlength(value, max)
107 if max == 0 then
108 return 0
109 else
110 if hb.settings.bar_type == "progress_bar" then
111 local x
112 if value < 0 then x=-0.5 else x = 0.5 end
113 local ret = math.modf((value/max) * hb.settings.max_bar_length + x)
114 return ret
115 else
116 local x
117 if value < 0 then x=-0.5 else x = 0.5 end
118 local ret = math.modf((value/max) * hb.settings.statbar_length + x)
119 return ret
124 function hb.get_hudtable(identifier)
125 return hb.hudtables[identifier]
128 function hb.get_hudbar_position_index(identifier)
129 if hb.settings.sorting[identifier] ~= nil then
130 return hb.settings.sorting[identifier]
131 else
132 local i = 0
133 while true do
134 if hb.registered_slots[i] ~= true and hb.settings.sorting_reverse[i] == nil then
135 return i
137 i = i + 1
142 function hb.register_hudbar(identifier, text_color, label, textures, default_start_value, default_start_max, default_start_hidden, format_string)
143 minetest.log("action", "hb.register_hudbar: "..tostring(identifier))
144 local hudtable = {}
145 local pos, offset
146 local index = math.floor(hb.get_hudbar_position_index(identifier))
147 hb.registered_slots[index] = true
148 if hb.settings.alignment_pattern == "stack_up" 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 elseif hb.settings.alignment_pattern == "stack_down" then
155 pos = hb.settings.pos_left
156 offset = {
157 x = hb.settings.start_offset_left.x,
158 y = hb.settings.start_offset_left.y + hb.settings.vmargin * index
160 else
161 if index % 2 == 0 then
162 pos = hb.settings.pos_left
163 offset = {
164 x = hb.settings.start_offset_left.x,
165 y = hb.settings.start_offset_left.y - hb.settings.vmargin * (index/2)
167 else
168 pos = hb.settings.pos_right
169 offset = {
170 x = hb.settings.start_offset_right.x,
171 y = hb.settings.start_offset_right.y - hb.settings.vmargin * ((index-1)/2)
175 if format_string == nil then
176 format_string = S("%s: %d/%d")
179 hudtable.add_all = function(player, hudtable, start_value, start_max, start_hidden)
180 if start_value == nil then start_value = hudtable.default_start_value end
181 if start_max == nil then start_max = hudtable.default_start_max end
182 if start_hidden == nil then start_hidden = hudtable.default_start_hidden end
183 local ids = {}
184 local state = {}
185 local name = player:get_player_name()
186 local bgscale, iconscale, text, barnumber, bgiconnumber
187 if start_max == 0 or start_hidden then
188 bgscale = { x=0, y=0 }
189 else
190 bgscale = { x=1, y=1 }
192 if start_hidden then
193 iconscale = { x=0, y=0 }
194 barnumber = 0
195 bgiconnumber = 0
196 text = ""
197 else
198 iconscale = { x=1, y=1 }
199 barnumber = hb.value_to_barlength(start_value, start_max)
200 bgiconnumber = hb.settings.statbar_length
201 text = string.format(format_string, label, start_value, start_max)
203 if hb.settings.bar_type == "progress_bar" then
204 ids.bg = player:hud_add({
205 hud_elem_type = "image",
206 position = pos,
207 scale = bgscale,
208 text = "hudbars_bar_background.png",
209 alignment = {x=1,y=1},
210 offset = { x = offset.x - 1, y = offset.y - 1 },
212 if textures.icon ~= nil then
213 ids.icon = player:hud_add({
214 hud_elem_type = "image",
215 position = pos,
216 scale = iconscale,
217 text = textures.icon,
218 alignment = {x=-1,y=1},
219 offset = { x = offset.x - 3, y = offset.y },
222 elseif hb.settings.bar_type == "statbar_modern" then
223 if textures.bgicon ~= nil then
224 ids.bg = player:hud_add({
225 hud_elem_type = "statbar",
226 position = pos,
227 text = textures.bgicon,
228 number = bgiconnumber,
229 alignment = {x=-1,y=-1},
230 offset = { x = offset.x, y = offset.y },
231 direction = 0,
232 size = {x=24, y=24},
236 local bar_image, bar_size
237 if hb.settings.bar_type == "progress_bar" then
238 bar_image = textures.bar
239 bar_size = nil
240 elseif hb.settings.bar_type == "statbar_classic" or hb.settings.bar_type == "statbar_modern" then
241 bar_image = textures.icon
242 bar_size = {x=24, y=24}
244 ids.bar = player:hud_add({
245 hud_elem_type = "statbar",
246 position = pos,
247 text = bar_image,
248 number = barnumber,
249 alignment = {x=-1,y=-1},
250 offset = offset,
251 direction = 0,
252 size = bar_size,
254 if hb.settings.bar_type == "progress_bar" then
255 ids.text = player:hud_add({
256 hud_elem_type = "text",
257 position = pos,
258 text = text,
259 alignment = {x=1,y=1},
260 number = text_color,
261 direction = 0,
262 offset = { x = offset.x + 2, y = offset.y - 1},
265 -- Do not forget to update hb.get_hudbar_state if you add new fields to the state table
266 state.hidden = start_hidden
267 state.value = start_value
268 state.max = start_max
269 state.text = text
270 state.barlength = hb.value_to_barlength(start_value, start_max)
272 local main_error_text =
273 "[hudbars] Bad initial values of HUD bar identifier “"..tostring(identifier).."” for player "..name..". "
275 if start_max < start_value then
276 minetest.log("error", main_error_text.."start_max ("..start_max..") is smaller than start_value ("..start_value..")!")
278 if start_max < 0 then
279 minetest.log("error", main_error_text.."start_max ("..start_max..") is smaller than 0!")
281 if start_value < 0 then
282 minetest.log("error", main_error_text.."start_value ("..start_value..") is smaller than 0!")
285 hb.hudtables[identifier].hudids[name] = ids
286 hb.hudtables[identifier].hudstate[name] = state
289 hudtable.identifier = identifier
290 hudtable.format_string = format_string
291 hudtable.label = label
292 hudtable.hudids = {}
293 hudtable.hudstate = {}
294 hudtable.default_start_hidden = default_start_hidden
295 hudtable.default_start_value = default_start_value
296 hudtable.default_start_max = default_start_max
298 hb.hudbars_count= hb.hudbars_count + 1
300 hb.hudtables[identifier] = hudtable
303 function hb.init_hudbar(player, identifier, start_value, start_max, start_hidden)
304 if not player_exists(player) then return false end
305 local hudtable = hb.get_hudtable(identifier)
306 hb.hudtables[identifier].add_all(player, hudtable, start_value, start_max, start_hidden)
307 return true
310 function hb.change_hudbar(player, identifier, new_value, new_max_value, new_icon, new_bgicon, new_bar, new_label, new_text_color)
311 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
312 return true
314 if not player_exists(player) then
315 return false
318 local name = player:get_player_name()
319 local hudtable = hb.get_hudtable(identifier)
320 local value_changed, max_changed = false, false
322 if new_value ~= nil then
323 if new_value ~= hudtable.hudstate[name].value then
324 hudtable.hudstate[name].value = new_value
325 value_changed = true
327 else
328 new_value = hudtable.hudstate[name].value
330 if new_max_value ~= nil then
331 if new_max_value ~= hudtable.hudstate[name].max then
332 hudtable.hudstate[name].max = new_max_value
333 max_changed = true
335 else
336 new_max_value = hudtable.hudstate[name].max
339 if hb.settings.bar_type == "progress_bar" then
340 if new_icon ~= nil and hudtable.hudids[name].icon ~= nil then
341 player:hud_change(hudtable.hudids[name].icon, "text", new_icon)
343 if new_bgicon ~= nil and hudtable.hudids[name].bgicon ~= nil then
344 player:hud_change(hudtable.hudids[name].bgicon, "text", new_bgicon)
346 if new_bar ~= nil then
347 player:hud_change(hudtable.hudids[name].bar , "text", new_bar)
349 if new_label ~= nil then
350 hudtable.label = new_label
351 local new_text = string.format(hudtable.format_string, new_label, hudtable.hudstate[name].value, hudtable.hudstate[name].max)
352 player:hud_change(hudtable.hudids[name].text, "text", new_text)
354 if new_text_color ~= nil then
355 player:hud_change(hudtable.hudids[name].text, "number", new_text_color)
357 else
358 if new_icon ~= nil and hudtable.hudids[name].bar ~= nil then
359 player:hud_change(hudtable.hudids[name].bar, "text", new_icon)
361 if new_bgicon ~= nil and hudtable.hudids[name].bg ~= nil then
362 player:hud_change(hudtable.hudids[name].bg, "text", new_bgicon)
366 local main_error_text =
367 "[hudbars] Bad call to hb.change_hudbar, identifier: “"..tostring(identifier).."”, player name: “"..name.."”. "
368 if new_max_value < new_value then
369 minetest.log("error", main_error_text.."new_max_value ("..new_max_value..") is smaller than new_value ("..new_value..")!")
371 if new_max_value < 0 then
372 minetest.log("error", main_error_text.."new_max_value ("..new_max_value..") is smaller than 0!")
374 if new_value < 0 then
375 minetest.log("error", main_error_text.."new_value ("..new_value..") is smaller than 0!")
378 if hudtable.hudstate[name].hidden == false then
379 if max_changed and hb.settings.bar_type == "progress_bar" then
380 if hudtable.hudstate[name].max == 0 then
381 player:hud_change(hudtable.hudids[name].bg, "scale", {x=0,y=0})
382 else
383 player:hud_change(hudtable.hudids[name].bg, "scale", {x=1,y=1})
387 if value_changed or max_changed then
388 local new_barlength = hb.value_to_barlength(new_value, new_max_value)
389 if new_barlength ~= hudtable.hudstate[name].barlength then
390 player:hud_change(hudtable.hudids[name].bar, "number", hb.value_to_barlength(new_value, new_max_value))
391 hudtable.hudstate[name].barlength = new_barlength
394 if hb.settings.bar_type == "progress_bar" then
395 local new_text = string.format(hudtable.format_string, hudtable.label, new_value, new_max_value)
396 if new_text ~= hudtable.hudstate[name].text then
397 player:hud_change(hudtable.hudids[name].text, "text", new_text)
398 hudtable.hudstate[name].text = new_text
403 return true
406 function hb.hide_hudbar(player, identifier)
407 if not player_exists(player) then return false end
408 local name = player:get_player_name()
409 local hudtable = hb.get_hudtable(identifier)
410 if hudtable == nil then return false end
411 if(hudtable.hudstate[name].hidden == false) then
412 if hb.settings.bar_type == "progress_bar" then
413 if hudtable.hudids[name].icon ~= nil then
414 player:hud_change(hudtable.hudids[name].icon, "scale", {x=0,y=0})
416 player:hud_change(hudtable.hudids[name].bg, "scale", {x=0,y=0})
417 player:hud_change(hudtable.hudids[name].text, "text", "")
418 elseif hb.settings.bar_type == "statbar_modern" then
419 player:hud_change(hudtable.hudids[name].bg, "number", 0)
421 player:hud_change(hudtable.hudids[name].bar, "number", 0)
422 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 if(hudtable.hudstate[name].hidden) then
433 local value = hudtable.hudstate[name].value
434 local max = hudtable.hudstate[name].max
435 if hb.settings.bar_type == "progress_bar" then
436 if hudtable.hudids[name].icon ~= nil then
437 player:hud_change(hudtable.hudids[name].icon, "scale", {x=1,y=1})
439 if hudtable.hudstate[name].max ~= 0 then
440 player:hud_change(hudtable.hudids[name].bg, "scale", {x=1,y=1})
442 player:hud_change(hudtable.hudids[name].text, "text", tostring(string.format(hudtable.format_string, hudtable.label, value, max)))
443 elseif hb.settings.bar_type == "statbar_modern" then
444 player:hud_change(hudtable.hudids[name].bg, "number", hb.settings.statbar_length)
446 player:hud_change(hudtable.hudids[name].bar, "number", hb.value_to_barlength(value, max))
447 hudtable.hudstate[name].hidden = false
449 return true
452 function hb.get_hudbar_state(player, identifier)
453 if not player_exists(player) then return nil end
454 local ref = hb.get_hudtable(identifier).hudstate[player:get_player_name()]
455 -- Do not forget to update this chunk of code in case the state changes
456 local copy = {
457 hidden = ref.hidden,
458 value = ref.value,
459 max = ref.max,
460 text = ref.text,
461 barlength = ref.barlength,
463 return copy
466 --register built-in HUD bars
467 if minetest.setting_getbool("enable_damage") or hb.settings.forceload_default_hudbars then
468 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)
469 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)
472 local function hide_builtin(player)
473 local flags = player:hud_get_flags()
474 flags.healthbar = false
475 flags.breathbar = false
476 player:hud_set_flags(flags)
480 local function custom_hud(player)
481 if minetest.setting_getbool("enable_damage") or hb.settings.forceload_default_hudbars then
482 local hide
483 if minetest.setting_getbool("enable_damage") then
484 hide = false
485 else
486 hide = true
488 hb.init_hudbar(player, "health", player:get_hp(), nil, hide)
489 local breath = player:get_breath()
490 local hide_breath
491 if breath == 11 and hb.settings.autohide_breath == true then hide_breath = true else hide_breath = false end
492 hb.init_hudbar(player, "breath", math.min(breath, 10), nil, hide_breath or hide)
496 local function update_health(player)
497 hb.change_hudbar(player, "health", player:get_hp())
500 -- update built-in HUD bars
501 local function update_hud(player)
502 if not player_exists(player) then return end
503 if minetest.setting_getbool("enable_damage") then
504 if hb.settings.forceload_default_hudbars then
505 hb.unhide_hudbar(player, "health")
507 --air
508 local breath = player:get_breath()
510 if breath == 11 and hb.settings.autohide_breath == true then
511 hb.hide_hudbar(player, "breath")
512 else
513 hb.unhide_hudbar(player, "breath")
514 hb.change_hudbar(player, "breath", math.min(breath, 10))
516 --health
517 update_health(player)
518 elseif hb.settings.forceload_default_hudbars then
519 hb.hide_hudbar(player, "health")
520 hb.hide_hudbar(player, "breath")
524 minetest.register_on_player_hpchange(function(player)
525 if hb.players[player:get_player_name()] ~= nil then
526 update_health(player)
528 end)
530 minetest.register_on_respawnplayer(function(player)
531 update_health(player)
532 hb.hide_hudbar(player, "breath")
533 end)
535 minetest.register_on_joinplayer(function(player)
536 hide_builtin(player)
537 custom_hud(player)
538 hb.players[player:get_player_name()] = player
539 end)
541 minetest.register_on_leaveplayer(function(player)
542 hb.players[player:get_player_name()] = nil
543 end)
545 local main_timer = 0
546 local timer = 0
547 minetest.register_globalstep(function(dtime)
548 main_timer = main_timer + dtime
549 timer = timer + dtime
550 if main_timer > hb.settings.tick or timer > 4 then
551 if main_timer > hb.settings.tick then main_timer = 0 end
552 -- only proceed if damage is enabled
553 if minetest.setting_getbool("enable_damage") or hb.settings.forceload_default_hudbars then
554 for _, player in pairs(hb.players) do
555 -- update all hud elements
556 update_hud(player)
560 if timer > 4 then timer = 0 end
561 end)