A Minetest mod which replaces the default health and breath bar by nice-looking HUD bars and provides a powerful API for modders to add custom HUD bars. https://content.minetest.net/packages/Wuzzy/hudbars/
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

570 lines
18KB

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