mcl_experience = { on_add_xp = {}, } local modpath = minetest.get_modpath(minetest.get_current_modname()) dofile(modpath .. "/command.lua") dofile(modpath .. "/orb.lua") dofile(modpath .. "/bottle.lua") -- local storage local hud_bars = {} local hud_levels = {} local caches = {} -- helpers local function xp_to_level(xp) local xp = xp or 0 local a, b, c, D if xp > 1507 then a, b, c = 4.5, -162.5, 2220 - xp elseif xp > 352 then a, b, c = 2.5, -40.5, 360 - xp else a, b, c = 1, 6, -xp end D = b * b - 4 * a * c if D == 0 then return math.floor(-b / 2 / a) elseif D > 0 then local v1, v2 = -b / 2 / a, math.sqrt(D) / 2 / a return math.floor(math.max(v1 - v2, v1 + v2)) end return 0 end local function level_to_xp(level) if level >= 1 and level <= 16 then return math.floor(math.pow(level, 2) + 6 * level) elseif level >= 17 and level <= 31 then return math.floor(2.5 * math.pow(level, 2) - 40.5 * level + 360) elseif level >= 32 then return math.floor(4.5 * math.pow(level, 2) - 162.5 * level + 2220) end return 0 end local function calculate_bounds(level) return level_to_xp(level), level_to_xp(level + 1) end local function xp_to_bar(xp, level) local xp_min, xp_max = calculate_bounds(level) return (xp - xp_min) / (xp_max - xp_min) end local function bar_to_xp(bar, level) local xp_min, xp_max = calculate_bounds(level) return xp_min + bar * (xp_max - xp_min) end local function get_time() return minetest.get_us_time() / 1000000 end -- api function mcl_experience.get_level(player) return caches[player].level end function mcl_experience.set_level(player, level) local cache = caches[player] if level ~= cache.level then mcl_experience.set_xp(player, math.floor(bar_to_xp(xp_to_bar(mcl_experience.get_xp(player), cache.level), level))) end end function mcl_experience.get_xp(player) return player:get_meta():get_int("xp") end function mcl_experience.set_xp(player, xp) player:get_meta():set_int("xp", xp) mcl_experience.update(player) end function mcl_experience.add_xp(player, xp) for _, cb in ipairs(mcl_experience.on_add_xp) do xp = cb.func(player, xp) or xp if xp == 0 then break end end local cache = caches[player] local old_level = cache.level mcl_experience.set_xp(player, mcl_experience.get_xp(player) + xp) local current_time = get_time() if current_time - cache.last_time > 0.01 then local name = player:get_player_name() if old_level == cache.level then minetest.sound_play("mcl_experience", { to_player = name, gain = 0.1, pitch = math.random(75, 99) / 100, }) cache.last_time = current_time else minetest.sound_play("mcl_experience_level_up", { to_player = name, gain = 0.2, }) cache.last_time = current_time + 0.2 end end end function mcl_experience.throw_xp(pos, total_xp) local i, j = 0, 0 local obs = {} while i < total_xp and j < 100 do local xp = math.min(math.random(1, math.min(32767, total_xp - math.floor(i / 2))), total_xp - i) local obj = minetest.add_entity(pos, "mcl_experience:orb", tostring(xp)) if not obj then return false end obj:set_velocity(vector.new( math.random(-2, 2) * math.random(), math.random(2, 5), math.random(-2, 2) * math.random() )) i = i + xp j = j + 1 table.insert(obs, obj) end return obs end function mcl_experience.remove_hud(player) if hud_bars[player] then player:hud_remove(hud_bars[player]) hud_bars[player] = nil end if hud_levels[player] then player:hud_remove(hud_levels[player]) hud_levels[player] = nil end end function mcl_experience.setup_hud(player) if hud_bars[player] and hud_levels[player] then return end mcl_experience.remove_hud(player) caches[player] = { last_time = get_time(), } if not minetest.is_creative_enabled(player:get_player_name()) then hud_bars[player] = player:hud_add({ hud_elem_type = "image", position = { x = 0.5, y = 1 }, offset = { x = (-9 * 28) - 3, y = -(48 + 24 + 16 - 5) }, scale = { x = 0.35, y = 0.375 }, alignment = { x = 1, y = 1 }, z_index = 11, }) hud_levels[player] = player:hud_add({ hud_elem_type = "text", position = { x = 0.5, y = 1 }, number = 0x80FF20, offset = { x = 0, y = -(48 + 24 + 24) }, z_index = 12, }) end end function mcl_experience.update(player) local xp = mcl_experience.get_xp(player) local cache = caches[player] cache.level = xp_to_level(xp) if not minetest.is_creative_enabled(player:get_player_name()) then if not hud_bars[player] then mcl_experience.setup_hud(player) end player:hud_change(hud_bars[player], "text", "(mcl_experience_bar_background.png^[lowpart:" .. math.floor(math.floor(xp_to_bar(xp, cache.level) * 18) / 18 * 100) .. ":mcl_experience_bar.png)^[resize:40x1456^[transformR270" ) if cache.level == 0 then player:hud_change(hud_levels[player], "text", "") else player:hud_change(hud_levels[player], "text", tostring(cache.level)) end end end function mcl_experience.register_on_add_xp(func, priority) table.insert(mcl_experience.on_add_xp, { func = func, priority = priority or 0 }) end -- callbacks minetest.register_on_joinplayer(function(player) mcl_experience.setup_hud(player) mcl_experience.update(player) end) minetest.register_on_leaveplayer(function(player) hud_bars[player] = nil hud_levels[player] = nil caches[player] = nil end) local keep_inventory = vl_tuning.setting("gamerule:keepInventory") minetest.register_on_dieplayer(function(player) if not keep_inventory[1] then mcl_experience.throw_xp(player:get_pos(), mcl_experience.get_xp(player)) mcl_experience.set_xp(player, 0) end end) minetest.register_on_mods_loaded(function() table.sort(mcl_experience.on_add_xp, function(a, b) return a.priority < b.priority end) end) mcl_gamemode.register_on_gamemode_change(function(player, old_gamemode, new_gamemode) if new_gamemode == "survival" then mcl_experience.setup_hud(player) mcl_experience.update(player) elseif new_gamemode == "creative" then mcl_experience.remove_hud(player) end end)