--- Quests HUD. -- @module hud -- Boilerplate to support localized strings if intllib mod is installed. local S = minetest.get_translator("quests") local show_max = 10 -- the maximum visible quests. local hud_config = { position = {x = 1, y = 0.2}, offset = { x = -200, y = 0}, number = quests.colors.new } --- Set the HUD position (not offset). -- Supplied positions may be `nil` and will be skipped. -- @note Calling this function while quests are already displayed will result -- in undefined behaviour. Call it beforehand. function quests.set_hud_position(x, y) hud_config.position.x = x or hud_config.position.x hud_config.position.y = y or hud_config.position.y end --- Set the HUD offset (not position). -- Supplied positions may be `nil` and will be skipped. -- @note Calling this function while quests are already displayed will result -- in undefined behaviour. Call it beforehand. function quests.set_hud_offset(x, y) hud_config.offset.x = x or hud_config.offset.x hud_config.offset.y = y or hud_config.offset.y end --- Show quests HUD to player. -- The HUD can only show up to `show_max` quests -- @param playername Player whose quests HUD must be shown -- @param autohide Whether to automatically hide the HUD once it's empty function quests.show_hud(playername, autohide) if quests.hud[playername] == nil then quests.hud[playername] = { autohide = autohide } end if quests.hud[playername].list ~= nil then return end local player = minetest.get_player_by_name(playername) if player == nil then return false end quests.hud[playername].header = player:hud_add({ hud_elem_type = "text", alignment = {x=1, y=1}, position = {x = hud_config.position.x, y = hud_config.position.y}, offset = {x = hud_config.offset.x, y = hud_config.offset.y - 20}, number = hud_config.number, text = S("Quests") .. ":" }) quests.hud[playername].list = {} quests.update_hud(playername) end --- Hide quests HUD to player. -- @param playername Player whose quests HUD must be hidden function quests.hide_hud(playername) local player = minetest.get_player_by_name(playername) if player == nil or quests.hud[playername] == nil or quests.hud[playername].list == nil then return end for _,quest in pairs(quests.hud[playername].list) do if quest.text and quest.text.id then player:hud_remove(quest.text.id) end if quest.background and quest.background.id then player:hud_remove(quest.background.id) end if quest.bar and quest.bar.id then player:hud_remove(quest.bar.id) end end player:hud_remove(quests.hud[playername].header) quests.hud[playername].list = nil end local function get_quest_hud_string(title, value, max) return title .. "\n ("..quests.round(value, 2).."/"..max..")" end local function get_hud_list(playername) local deftable = {} local counter = 0 for questname, plr_quest in quests.sorted_pairs(quests.active_quests[playername]) do local quest = quests.registered_quests[questname] local hide_from_hud if quests.info_quests[playername] and quests.info_quests[playername][questname] then hide_from_hud = quests.info_quests[playername][questname].hide_from_hud else hide_from_hud = false end if quest and not hide_from_hud then -- Quest might have been deleted local function get_table(name, value, max) local def = { text = { hud_elem_type = "text", alignment = { x=1, y= 1 }, position = {x = hud_config.position.x, y = hud_config.position.y}, offset = {x = hud_config.offset.x, y = hud_config.offset.y + counter * 40}, number = hud_config.number, text = name } } if plr_quest.finished then if quests.failed_quests[playername] and quests.failed_quests[playername][questname] then def.text.number = quests.colors.failed else def.text.number = quests.colors.success end else def.text.number = hud_config.number end if value and max then def.bar = { hud_elem_type = "image", scale = { x = math.floor(20 * value / max), y = 1 }, alignment = { x = 1, y = 1 }, position = { x = hud_config.position.x, y = hud_config.position.y }, offset = { x = hud_config.offset.x + 2, y = hud_config.offset.y + counter * 40 + 24 }, text = "quests_questbar.png" } def.background = { hud_elem_type = "image", scale = { x = 1, y = 1 }, size = { x = 2, y = 4 }, alignment = { x = 1, y = 1 }, position = { x = hud_config.position.x, y = hud_config.position.y }, offset = { x = hud_config.offset.x, y = hud_config.offset.y + counter * 40 + 22 }, text = "quests_questbar_background.png" } end return def end if quest.simple then deftable[questname] = get_table(get_quest_hud_string(quest.title, plr_quest.value, quest.max), plr_quest.value, quest.max) counter = counter + 1 else deftable[questname] = get_table(quest.title) counter = counter + 0.5 for taskname, task in pairs(quest.tasks) do local plr_task = quests.active_quests[playername][questname][taskname] if plr_task.visible and not plr_task.disabled and not plr_task.finished then deftable[questname .. "#" .. taskname] = get_table("- " .. get_quest_hud_string(task.title, plr_task.value, task.max), plr_task.value, task.max) counter = counter + 1 if counter >= show_max + 1 then break end end end end if counter >= show_max + 1 then break end end end return deftable end local DELETED = {} -- only for internal use -- updates the hud function quests.update_hud(playername) if quests.hud[playername] == nil or quests.active_quests[playername] == nil then return end if quests.hud[playername].list == nil then if quests.hud[playername].autohide and next(quests.active_quests[playername]) ~= nil then quests.show_hud(playername) end return end local player = minetest.get_player_by_name(playername) if player == nil then return end if quests.hud[playername].autohide then if next(quests.active_quests[playername]) == nil then player:hud_change(quests.hud[playername].header, "text", S("No more Quests")) minetest.after(3, function(playername) if next(quests.active_quests[playername]) ~= nil then player:hud_change(quests.hud[playername].header, "text", S("Quests")..":") quests.update_hud(playername) else quests.hide_hud(playername) end end, playername) end end -- Check for changes in the hud local function table_diff(tab1, tab2) local result_tab for k, v in pairs(tab2) do if not tab1[k] or tab1[k] ~= v then if type(tab1[k]) == "table" and type(v) == "table" then local diff = table_diff(tab1[k], v) if diff ~= nil then if not result_tab then result_tab = {} end result_tab[k] = diff end else if not result_tab then result_tab = {} end result_tab[k] = v end end end for k, _ in pairs(tab1) do if tab2[k] == nil then if not result_tab then result_tab = {} end result_tab[k] = DELETED end end return result_tab end -- Merge `from` into table `into` local function table_merge(from, into) for k, v in pairs(from) do if type(v) == "table" and type(into[k]) == "table" then table_merge(v, into[k]) else into[k] = v end end end local old_hud = quests.hud[playername].list local new_hud = get_hud_list(playername) local diff = table_diff(old_hud, new_hud) -- Copy the HUD IDs from the old table to the new one, to avoid loosing them for questname, hud_elms in pairs(old_hud) do for elm_name, elm_def in pairs(hud_elms) do if new_hud[questname] and new_hud[questname][elm_name] then new_hud[questname][elm_name].id = elm_def.id end end end --[[ Disabled because it had issues with MT engine and too much quests. HUD elements sometimes won't update. if diff ~= nil then for questname, hud_elms in pairs(diff) do if hud_elms == DELETED then for elm_name, elm_def in pairs(old_hud[questname]) do player:hud_remove(elm_def.id) end else for elm_name, elm_def in pairs(hud_elms) do if not old_hud[questname] or not old_hud[questname][elm_name] or not old_hud[questname][elm_name].id then new_hud[questname][elm_name].id = player:hud_add(elm_def) else for elm_prop_name, elm_prop in pairs(elm_def) do if elm_prop_name ~= "id" then if type(elm_prop) == "table" then -- For table-based properties, MT expects a full table to be specified, -- so we must create a merged table. Just merge the changes with the old -- HUD table, since it will disappear. table_merge(elm_prop, old_hud[questname][elm_name][elm_prop_name]) else old_hud[questname][elm_name][elm_prop_name] = elm_prop end player:hud_change(new_hud[questname][elm_name].id, elm_prop_name, old_hud[questname][elm_name][elm_prop_name]) end end end end end end end]] for questname, hud_elms in pairs(old_hud) do for elm_name, elm_def in pairs(hud_elms) do player:hud_remove(elm_def.id) end end for questname, hud_elms in pairs(new_hud) do for elm_name, elm_def in pairs(hud_elms) do elm_def.id = player:hud_add(elm_def) end end quests.hud[playername].list = new_hud end minetest.register_on_joinplayer(function(player) local playername = player:get_player_name() if quests.hud[playername] ~= nil then ---if not quests.hud[playername].first then --- return ---end local list = quests.hud[playername].list local autohide = quests.hud[playername].autohide local central_message_enabled = quests.hud[playername].central_message_enabled quests.hud[playername] = { autohide = autohide, central_message_enabled = central_message_enabled } else -- new player quests.hud[playername] = { autohide = true, central_message_enabled = true } quests.active_quests[playername] = {} end minetest.after(1, function(playername) quests.show_hud(playername) end, playername) end)