From fe33f172d4e72277082d78e0dc7c6268269fd1ca Mon Sep 17 00:00:00 2001 From: kay27 Date: Sat, 16 Jan 2021 18:51:30 +0400 Subject: [PATCH] Add player-friendly respawn place search algorithm, fix https://git.minetest.land/Wuzzy/MineClone2/issues/515 https://git.minetest.land/Wuzzy/MineClone2/issues/972 https://git.minetest.land/Wuzzy/MineClone2/issues/981 --- mods/ENVIRONMENT/mcl_void_damage/init.lua | 2 +- mods/ITEMS/mcl_portals/portal_end.lua | 269 +++++++---------- mods/PLAYER/mcl_player/init.lua | 34 +++ mods/PLAYER/mcl_spawn/init.lua | 333 ++++++++++++++++++---- 4 files changed, 420 insertions(+), 218 deletions(-) diff --git a/mods/ENVIRONMENT/mcl_void_damage/init.lua b/mods/ENVIRONMENT/mcl_void_damage/init.lua index bdd60508f..205198a46 100644 --- a/mods/ENVIRONMENT/mcl_void_damage/init.lua +++ b/mods/ENVIRONMENT/mcl_void_damage/init.lua @@ -65,7 +65,7 @@ minetest.register_globalstep(function(dtime) if is_immortal or not enable_damage then -- If damage is disabled, we can't kill players. -- So we just teleport the player back to spawn. - local spawn = mcl_spawn.get_spawn_pos(player) + local spawn = mcl_spawn.get_player_spawn_pos(player) player:set_pos(spawn) mcl_worlds.dimension_change(player, mcl_worlds.pos_to_dimension(spawn)) minetest.chat_send_player(player:get_player_name(), S("The void is off-limits to you!")) diff --git a/mods/ITEMS/mcl_portals/portal_end.lua b/mods/ITEMS/mcl_portals/portal_end.lua index 53e2ddda8..ce8aa774c 100644 --- a/mods/ITEMS/mcl_portals/portal_end.lua +++ b/mods/ITEMS/mcl_portals/portal_end.lua @@ -21,6 +21,21 @@ local destroy_portal = function(pos) end end +local ep_scheme = { + { o={x=0, y=0, z=1}, p=3 }, + { o={x=0, y=0, z=2}, p=3 }, + { o={x=0, y=0, z=3}, p=3 }, + { o={x=1, y=0, z=4}, p=0 }, + { o={x=2, y=0, z=4}, p=0 }, + { o={x=3, y=0, z=4}, p=0 }, + { o={x=4, y=0, z=3}, p=1 }, + { o={x=4, y=0, z=2}, p=1 }, + { o={x=4, y=0, z=1}, p=1 }, + { o={x=3, y=0, z=0}, p=2 }, + { o={x=2, y=0, z=0}, p=2 }, + { o={x=1, y=0, z=0}, p=2 }, +} + -- End portal minetest.register_node("mcl_portals:portal_end", { description = S("End Portal"), @@ -52,7 +67,7 @@ minetest.register_node("mcl_portals:portal_end", { paramtype = "light", sunlight_propagates = true, use_texture_alpha = true, - walkable = true, + walkable = false, diggable = false, pointable = false, buildable_to = false, @@ -107,97 +122,19 @@ end -- Check if pos is part of a valid end portal frame, filled with eyes of ender. local function check_end_portal_frame(pos) - -- Check if pos has an end portal frame with eye of ender - local eframe = function(pos, param2) - local node = minetest.get_node(pos) - if node.name == "mcl_portals:end_portal_frame_eye" then - if param2 == nil or node.param2 == param2 then - return true, node + for i = 1, 12 do + local pos0 = vector.subtract(pos, ep_scheme[i].o) + local portal = true + for j = 1, 12 do + local p = vector.add(pos0, ep_scheme[j].o) + local node = minetest.get_node(p) + if not node or node.name ~= "mcl_portals:end_portal_frame_eye" or node.param2 ~= ep_scheme[j].p then + portal = false + break end end - return false - end - - -- Step 1: Find a row of 3 end portal frames with eyes, all facing the same direction - local streak = 0 - local streak_start, streak_end, streak_start_node, streak_end_node - local last_param2 - local axes = { "x", "z" } - for a=1, #axes do - local axis = axes[a] - for b=pos[axis]-2, pos[axis]+2 do - local cpos = table.copy(pos) - cpos[axis] = b - local e, node = eframe(cpos, last_param2) - if e then - last_param2 = node.param2 - streak = streak + 1 - if streak == 1 then - streak_start = table.copy(pos) - streak_start[axis] = b - streak_start_node = node - elseif streak == 3 then - streak_end = table.copy(pos) - streak_end[axis] = b - streak_end_node = node - break - end - else - streak = 0 - last_param2 = nil - end - end - if streak_end then - break - end - streak = 0 - last_param2 = nil - end - -- Has a row been found? - if streak_end then - -- Step 2: Using the known facedir, check the remaining spots in which we expect - -- “eyed” end portal frames. - local dir = minetest.facedir_to_dir(streak_start_node.param2) - if dir.x ~= 0 then - for i=1, 3 do - if not eframe({x=streak_start.x + i*dir.x, y=streak_start.y, z=streak_start.z - 1}) then - return false - end - if not eframe({x=streak_start.x + i*dir.x, y=streak_start.y, z=streak_end.z + 1}) then - return false - end - if not eframe({x=streak_start.x + 4*dir.x, y=streak_start.y, z=streak_start.z + i-1}) then - return false - end - end - -- All checks survived! We have a valid portal! - local k - if dir.x > 0 then - k = 1 - else - k = -3 - end - return true, { x = streak_start.x + k, y = streak_start.y, z = streak_start.z } - elseif dir.z ~= 0 then - for i=1, 3 do - if not eframe({x=streak_start.x - 1, y=streak_start.y, z=streak_start.z + i*dir.z}) then - return false - end - if not eframe({x=streak_end.x + 1, y=streak_start.y, z=streak_start.z + i*dir.z}) then - return false - end - if not eframe({x=streak_start.x + i-1, y=streak_start.y, z=streak_start.z + 4*dir.z}) then - return false - end - end - local k - if dir.z > 0 then - k = 1 - else - k = -3 - end - -- All checks survived! We have a valid portal! - return true, { x = streak_start.x, y = streak_start.y, z = streak_start.z + k } + if portal then + return true, {x=pos0.x+1, y=pos0.y, z=pos0.z+1} end end return false @@ -222,82 +159,92 @@ local function end_portal_area(pos, destroy) minetest.bulk_set_node(posses, {name=name}) end +function mcl_portals.end_teleport(obj, pos) + if not obj then return end + local pos = pos or obj:get_pos() + if not pos then return end + local dim = mcl_worlds.pos_to_dimension(pos) + + local target + if dim == "end" then + -- End portal in the End: + -- Teleport back to the player's spawn or world spawn in the Overworld. + if obj:is_player() then + target = mcl_spawn.get_player_spawn_pos(obj) + end + + target = target or mcl_spawn.get_world_spawn_pos(obj) + else + -- End portal in any other dimension: + -- Teleport to the End at a fixed position and generate a + -- 5×5 obsidian platform below. + + local platform_pos = mcl_vars.mg_end_platform_pos + -- force emerge of target1 area + minetest.get_voxel_manip():read_from_map(platform_pos, platform_pos) + if not minetest.get_node_or_nil(platform_pos) then + minetest.emerge_area(vector.subtract(platform_pos, 3), vector.add(platform_pos, 3)) + end + + -- Build destination + local function check_and_build_end_portal_destination(pos) + local n = minetest.get_node_or_nil(pos) + if n and n.name ~= "mcl_core:obsidian" then + build_end_portal_destination(pos) + minetest.after(2, check_and_build_end_portal_destination, pos) + elseif not n then + minetest.after(1, check_and_build_end_portal_destination, pos) + end + end + + local platform + build_end_portal_destination(platform_pos) + check_and_build_end_portal_destination(platform_pos) + + target = table.copy(platform_pos) + target.y = target.y + 1 + end + + -- Teleport + obj:set_pos(target) + + if obj:is_player() then + -- Look towards the main End island + if dim ~= "end" then + obj:set_look_horizontal(math.pi/2) + end + mcl_worlds.dimension_change(obj, mcl_worlds.pos_to_dimension(target)) + minetest.sound_play("mcl_portals_teleport", {pos=target, gain=0.5, max_hear_distance = 16}, true) + end +end + +function mcl_portals.end_portal_teleport(pos, node) + for _,obj in ipairs(minetest.get_objects_inside_radius(pos, 1)) do + local lua_entity = obj:get_luaentity() --maikerumine added for objects to travel + if obj:is_player() or lua_entity then + local objpos = obj:get_pos() + if objpos == nil then + return + end + + -- Check if object is actually in portal. + objpos.y = math.ceil(objpos.y) + if minetest.get_node(objpos).name ~= "mcl_portals:portal_end" then + return + end + + mcl_portals.end_teleport(obj, objpos) + + end + end +end + minetest.register_abm({ label = "End portal teleportation", nodenames = {"mcl_portals:portal_end"}, interval = 0.1, chance = 1, - action = function(pos, node) - for _,obj in ipairs(minetest.get_objects_inside_radius(pos, 1)) do - local lua_entity = obj:get_luaentity() --maikerumine added for objects to travel - if obj:is_player() or lua_entity then - local dim = mcl_worlds.pos_to_dimension(pos) - - local objpos = obj:get_pos() - if objpos == nil then - return - end - - -- Check if object is actually in portal. - objpos.y = math.ceil(objpos.y) - if minetest.get_node(objpos).name ~= "mcl_portals:portal_end" then - return - end - - local target - if dim == "end" then - -- End portal in the End: - -- Teleport back to the player's spawn or world spawn in the Overworld. - if obj:is_player() then - _, target = mcl_spawn.spawn(obj) - end - - target = target or mcl_spawn.get_world_spawn_pos(obj) - else - -- End portal in any other dimension: - -- Teleport to the End at a fixed position and generate a - -- 5×5 obsidian platform below. - - local platform_pos = mcl_vars.mg_end_platform_pos - -- force emerge of target1 area - minetest.get_voxel_manip():read_from_map(platform_pos, platform_pos) - if not minetest.get_node_or_nil(platform_pos) then - minetest.emerge_area(vector.subtract(platform_pos, 3), vector.add(platform_pos, 3)) - end - - -- Build destination - local function check_and_build_end_portal_destination(pos) - local n = minetest.get_node_or_nil(pos) - if n and n.name ~= "mcl_core:obsidian" then - build_end_portal_destination(pos) - minetest.after(2, check_and_build_end_portal_destination, pos) - elseif not n then - minetest.after(1, check_and_build_end_portal_destination, pos) - end - end - - local platform - build_end_portal_destination(platform_pos) - check_and_build_end_portal_destination(platform_pos) - - target = table.copy(platform_pos) - target.y = target.y + 1 - end - - -- Teleport - obj:set_pos(target) - - if obj:is_player() then - -- Look towards the main End island - if dim ~= "end" then - obj:set_look_horizontal(math.pi/2) - end - mcl_worlds.dimension_change(obj, mcl_worlds.pos_to_dimension(target)) - minetest.sound_play("mcl_portals_teleport", {pos=target, gain=0.5, max_hear_distance = 16}, true) - end - end - end - end, + action = mcl_portals.end_portal_teleport, }) local rotate_frame, rotate_frame_eye diff --git a/mods/PLAYER/mcl_player/init.lua b/mods/PLAYER/mcl_player/init.lua index dcd232a05..28c12f251 100644 --- a/mods/PLAYER/mcl_player/init.lua +++ b/mods/PLAYER/mcl_player/init.lua @@ -164,3 +164,37 @@ minetest.register_globalstep(function(dtime) end end end) + +-- Don't change HP if the player falls in the water or through End Portal: +minetest.register_on_player_hpchange(function(player, hp_change, reason) + if reason and reason.type == "fall" and player then + local pos = player:get_pos() + local node = minetest.get_node(pos) + local velocity = player:get_velocity() or player:get_player_velocity() or {x=0,y=-10,z=0} + local v_axis_max = math.max(math.abs(velocity.x), math.abs(velocity.y), math.abs(velocity.z)) + local step = {x = velocity.x / v_axis_max, y = velocity.y / v_axis_max, z = velocity.z / v_axis_max} + for i = 1, math.ceil(v_axis_max/5)+1 do -- trace at least 1/5 of the way per second + if not node or node.name == "ignore" then + minetest.get_voxel_manip():read_from_map(pos, pos) + node = minetest.get_node(pos) + end + if node then + if minetest.registered_nodes[node.name].walkable then + return hp_change + end + if minetest.get_item_group(node.name, "water") ~= 0 then + return 0 + end + if node.name == "mcl_portals:portal_end" then + if mcl_portals and mcl_portals.end_teleport then + mcl_portals.end_teleport(player) + end + return 0 + end + end + pos = vector.add(pos, step) + node = minetest.get_node(pos) + end + end + return hp_change +end, true) diff --git a/mods/PLAYER/mcl_spawn/init.lua b/mods/PLAYER/mcl_spawn/init.lua index 8000e7aef..33bcce1e6 100644 --- a/mods/PLAYER/mcl_spawn/init.lua +++ b/mods/PLAYER/mcl_spawn/init.lua @@ -2,7 +2,47 @@ mcl_spawn = {} local S = minetest.get_translator("mcl_spawn") local mg_name = minetest.get_mapgen_setting("mg_name") +local storage = minetest.get_mod_storage() +-- Parameters +------------- + +local respawn_search_interval = 30 -- seconds +local respawn_search_initial_delay = 30 -- seconds +local node_groups_white_list = {"group:soil"} +local biomes_white_list = { + "ColdTaiga", + "Taiga", + "MegaTaiga", + "MegaSpruceTaiga", + "Plains", + "SunflowerPlains", + "Forest", + "FlowerForest", + "BirchForest", + "BirchForestM", + "Jungle", + "JungleM", + "JungleEdge", + "JungleEdgeM", + "Savanna", + "SavannaM", +} + +-- Resolution of search grid in nodes. +local res = 64 +local half_res = 32 -- for emerge areas around the position +local alt_min = -10 +local alt_max = 200 +-- Number of points checked in the square search grid (edge * edge). +local checks = 128 * 128 +-- Starting point for biome checks. This also sets the y co-ordinate for all +-- points checked, so the suitable biomes must be active at this y. +local start_pos = minetest.setting_get_pos("static_spawnpoint") or {x = 0, y = 8, z = 0} +-- Table of suitable biomes +local biome_ids = {} + +-- Bed spawning offsets local node_search_list = { --[[1]] {x = 0, y = 0, z = -1}, -- @@ -19,41 +59,205 @@ local node_search_list = --[[C]] {x = 0, y = 1, z = 1}, -- } -local cached_world_spawn +-- End of parameters +-------------------- + + +-- Initial variables + +local success = storage:get_int("mcl_spawn_success")==1 +local searched = (storage:get_int("mcl_spawn_searched")==1) or mg_name == "v6" or mg_name == "singlenode" or minetest.settings:get("static_spawnpoint") +local wsp = minetest.string_to_pos(storage:get_string("mcl_spawn_world_spawn_point")) or {} -- world spawn position +local check = storage:get_int("mcl_spawn_check") or 0 +local cp = minetest.string_to_pos(storage:get_string("mcl_spawn_cp")) or {x=start_pos.x, y=start_pos.y, z=start_pos.z} +local edge_len = storage:get_int("mcl_spawn_edge_len") or 1 +local edge_dist = storage:get_int("mcl_spawn_edge_dist") or 0 +local dir_step = storage:get_int("mcl_spawn_dir_step") or 0 +local dir_ind = storage:get_int("mcl_spawn_dir_ind") or 1 + +-- Get world 'mapgen_limit' and 'chunksize' to calculate 'spawn_limit'. +-- This accounts for how mapchunks are not generated if they or their shell exceed +-- 'mapgen_limit'. + +local mapgen_limit = tonumber(minetest.get_mapgen_setting("mapgen_limit")) +local chunksize = tonumber(minetest.get_mapgen_setting("chunksize")) +local spawn_limit = math.max(mapgen_limit - (chunksize + 1) * 16, 0) + + +--Functions +----------- + +local function get_far_node(pos) + local node = minetest.get_node(pos) + if node.name ~= "ignore" then + return node + end + minetest.get_voxel_manip():read_from_map(pos, pos) + return minetest.get_node(pos) +end + +local function good_for_respawn(pos, player) + local pos0 = {x = pos.x, y = pos.y - 1, z = pos.z} + local pos1 = {x = pos.x, y = pos.y, z = pos.z} + local pos2 = {x = pos.x, y = pos.y + 1, z = pos.z} + local node0 = get_far_node(pos0) + local node1 = get_far_node(pos1) + local node2 = get_far_node(pos2) + + local nn0, nn1, nn2 = node0.name, node1.name, node2.name + if minetest.get_item_group(nn0, "destroys_items") ~=0 + or minetest.get_item_group(nn1, "destroys_items") ~=0 + or minetest.get_item_group(nn2, "destroys_items") ~=0 + or minetest.get_item_group(nn0, "portal") ~=0 + or minetest.get_item_group(nn1, "portal") ~=0 + or minetest.get_item_group(nn2, "portal") ~=0 + or minetest.is_protected(pos0, player or "") + or minetest.is_protected(pos1, player or "") + or minetest.is_protected(pos2, player or "") + or (not player and minetest.get_node_light(pos1, 0.5) < 8) + or (not player and minetest.get_node_light(pos2, 0.5) < 8) + then + return false + end + + local def0 = minetest.registered_nodes[nn0] + local def1 = minetest.registered_nodes[nn1] + local def2 = minetest.registered_nodes[nn2] + return def0.walkable and (not def1.walkable) and (not def2.walkable) and + (def1.damage_per_second == nil or def2.damage_per_second <= 0) and + (def1.damage_per_second == nil or def2.damage_per_second <= 0) +end + +local function can_find_tree(pos1) + local trees = minetest.find_nodes_in_area(vector.subtract(pos1,half_res), vector.add(pos1,half_res), {"group:tree"}, false) + for _, pos2 in ipairs(trees) do + if not minetest.is_protected(pos2, "") then + if pos2.x < pos1.x then + pos2.x = pos2.x + 1 + elseif pos2.x > pos1.x then + pos2.x = pos2.x - 1 + end + if pos2.z < pos1.z then + pos2.z = pos2.z + 1 + elseif pos2.z > pos1.z then + pos2.z = pos2.z - 1 + end + local way = minetest.find_path(pos1, pos2, res, 1, 3, "A*_noprefetch") + if way then + return true + end + end + end + return false +end + +local function next_pos() + if edge_dist >= edge_len then + edge_dist = 1 + dir_ind = (dir_ind % 4) + 1 + dir_step = dir_step + 1 + edge_len = math.floor(dir_step / 2) + 1 + else + edge_dist = edge_dist + 1 + end + if dir_ind==1 then + cp.z = cp.z + res + elseif dir_ind==2 then + cp.x = cp.x - res + elseif dir_ind==3 then + cp.z = cp.z - res + else + cp.x = cp.x + res + end +end + +-- Spawn position search + +local function next_biome() + while check <= checks do + local biome_data = minetest.get_biome_data(cp) + -- Sometimes biome_data is nil + local biome = biome_data and biome_data.biome + if biome then + minetest.log("verbose", "[mcl_spawn] Search white-listed biome at "..minetest.pos_to_string(cp)..": "..minetest.get_biome_name(biome)) + for _, biome_id in ipairs(biome_ids) do + if biome == biome_id then + cp.y = minetest.get_spawn_level(cp.x, cp.z) or start_pos.y + if cp.y then + wsp = {x = cp.x, y = cp.y, z = cp.z} + return true + end + break + end + end + end + + next_pos() + + -- Check for position being outside world edge + if math.abs(cp.x) > spawn_limit or math.abs(cp.z) > spawn_limit then + check = checks + 1 + return false + end + + check = check + 1 + end + + return false +end + +local function ecb_search_continue(blockpos, action, calls_remaining, param) + if calls_remaining <= 0 then + local pos1 = {x = wsp.x-half_res, y = alt_min, z = wsp.z-half_res} + local pos2 = {x = wsp.x+half_res, y = alt_max, z = wsp.z+half_res} + local nodes = minetest.find_nodes_in_area_under_air(pos1, pos2, node_groups_white_list) + minetest.log("verbose", "[mcl_spawn] Data emerge callback: "..minetest.pos_to_string(wsp).." - "..tostring(nodes and #nodes) .. " node(s) found under air") + if nodes then + for i=1, #nodes do + wsp = nodes[i] + if wsp then + wsp.y = wsp.y + 1 + if good_for_respawn(wsp) and can_find_tree(wsp) then + minetest.log("action", "[mcl_spawn] Dynamic world spawn determined to be "..minetest.pos_to_string(wsp)) + searched = true + success = true + return + end + end + end + end + next_pos() + mcl_spawn.search() + end +end + +function mcl_spawn.search() + if not next_biome() or check > checks then + return false + end + check = check + 1 + if not wsp.y then + wsp.y = 8 + end + local pos1 = {x = wsp.x-half_res, y = alt_min, z = wsp.z-half_res} + local pos2 = {x = wsp.x+half_res, y = alt_max, z = wsp.z+half_res} + minetest.emerge_area(pos1, pos2, ecb_search_continue) +end + mcl_spawn.get_world_spawn_pos = function() - local spawn - spawn = minetest.setting_get_pos("static_spawnpoint") - if spawn then - return spawn - end - if cached_world_spawn then - return cached_world_spawn - end - -- 32 attempts to find a suitable spawn point - spawn = { x=math.random(-16, 16), y=8, z=math.random(-16, 16) } - for i=1, 32 do - local y = minetest.get_spawn_level(spawn.x, spawn.z) - if y then - spawn.y = y - cached_world_spawn = spawn - minetest.log("action", "[mcl_spawn] Dynamic world spawn determined to be "..minetest.pos_to_string(spawn)) - return spawn - end - -- Random walk - spawn.x = spawn.x + math.random(-64, 64) - spawn.z = spawn.z + math.random(-64, 64) + if success then + return wsp end minetest.log("action", "[mcl_spawn] Failed to determine dynamic world spawn!") - -- Use dummy position if nothing found - return { x=math.random(-16, 16), y=8, z=math.random(-16, 16) } + return start_pos end -- Returns a spawn position of player. -- If player is nil or not a player, a world spawn point is returned. -- The second return value is true if returned spawn point is player-chosen, -- false otherwise. -mcl_spawn.get_spawn_pos = function(player) +mcl_spawn.get_bed_spawn_pos = function(player) local spawn, custom_spawn = nil, false if player ~= nil and player:is_player() then local attr = player:get_meta():get_string("mcl_beds:spawn") @@ -101,29 +305,8 @@ mcl_spawn.set_spawn_pos = function(player, pos, message) return spawn_changed end -local function get_far_node(pos) - local node = minetest.get_node(pos) - if node.name ~= "ignore" then - return node - end - minetest.get_voxel_manip():read_from_map(pos, pos) - return minetest.get_node(pos) -end - -local function good_for_respawn(pos) - local node0 = get_far_node({x = pos.x, y = pos.y - 1, z = pos.z}) - local node1 = get_far_node({x = pos.x, y = pos.y, z = pos.z}) - local node2 = get_far_node({x = pos.x, y = pos.y + 1, z = pos.z}) - local def0 = minetest.registered_nodes[node0.name] - local def1 = minetest.registered_nodes[node1.name] - local def2 = minetest.registered_nodes[node2.name] - return def0.walkable and (not def1.walkable) and (not def2.walkable) and - (def1.damage_per_second == nil or def2.damage_per_second <= 0) and - (def1.damage_per_second == nil or def2.damage_per_second <= 0) -end - -mcl_spawn.spawn = function(player) - local pos, custom_spawn = mcl_spawn.get_spawn_pos(player) +mcl_spawn.get_player_spawn_pos = function(player) + local pos, custom_spawn = mcl_spawn.get_bed_spawn_pos(player) if pos and custom_spawn then -- Check if bed is still there local node_bed = get_far_node(pos) @@ -134,7 +317,7 @@ mcl_spawn.spawn = function(player) player:get_meta():set_string("mcl_beds:spawn", "") end minetest.chat_send_player(player:get_player_name(), S("Your spawn bed was missing or blocked.")) - return false + return mcl_spawn.get_world_spawn_pos(), false end -- Find spawning position on/near the bed free of solid or damaging blocks iterating a square spiral 15x15: @@ -151,17 +334,55 @@ mcl_spawn.spawn = function(player) else -- dir.x == 1 offset = {x = -o.z, y = o.y, z = o.x} end - local spawn_pos = vector.add(pos, offset) - if good_for_respawn(spawn_pos) then - player:set_pos(spawn_pos) - return true, spawn_pos + local player_spawn_pos = vector.add(pos, offset) + if good_for_respawn(player_spawn_pos, player:get_player_name()) then + return player_spawn_pos, true end end - - -- We here if we didn't find suitable place for respawn: - return false + -- We here if we didn't find suitable place for respawn end + return mcl_spawn.get_world_spawn_pos(), false +end + +mcl_spawn.spawn = function(player) + local pos, in_bed = mcl_spawn.get_player_spawn_pos(player) + player:set_pos(pos) + return in_bed or success end -- Respawn player at specified respawn position minetest.register_on_respawnplayer(mcl_spawn.spawn) + +function mcl_spawn.shadow_worker() + if #biome_ids < 1 then + for _, biome_name in pairs(biomes_white_list) do + table.insert(biome_ids, minetest.get_biome_id(biome_name)) + end + end + if not searched then + searched = true + mcl_spawn.search() + minetest.log("action", "[mcl_spawn] Started world spawn point search") + end + if success and ((not good_for_respawn(wsp)) or (not can_find_tree(wsp))) then + success = false + minetest.log("action", "[mcl_spawn] World spawn position isn't safe anymore: "..minetest.pos_to_string(wsp)) + mcl_spawn.search() + end + + minetest.after(respawn_search_interval, mcl_spawn.shadow_worker) +end +minetest.after(respawn_search_initial_delay, mcl_spawn.shadow_worker) + +minetest.register_on_shutdown(function() + storage:set_int("mcl_spawn_success", success and 1 or 0) + storage:set_string("mcl_spawn_world_spawn_point", minetest.pos_to_string(wsp)) + storage:set_int("mcl_spawn_searched", searched and 1 or 0) + storage:set_int("mcl_spawn_check", check) + storage:set_string("mcl_spawn_cp", minetest.pos_to_string(cp)) + storage:set_int("mcl_spawn_edge_len", edge_len) + storage:set_int("mcl_spawn_edge_dist", edge_dist) + storage:set_int("mcl_spawn_dir_step", dir_step) + storage:set_int("mcl_spawn_dir_ind", dir_ind) + end +)