From 3ad862c61cccb29d105f75ee8eb0b82436a2bd44 Mon Sep 17 00:00:00 2001 From: teknomunk Date: Fri, 17 May 2024 14:54:18 +0000 Subject: [PATCH] Optimize world respawn point reselection by only considering positions within 3 nodes of tree nodes and when validating a position can still pathfind to a tree start checking trees from the closest to the respawn point --- mods/PLAYER/mcl_spawn/init.lua | 243 +++++++++++++++++---------------- 1 file changed, 126 insertions(+), 117 deletions(-) diff --git a/mods/PLAYER/mcl_spawn/init.lua b/mods/PLAYER/mcl_spawn/init.lua index 89ededeed..ae20d5bad 100644 --- a/mods/PLAYER/mcl_spawn/init.lua +++ b/mods/PLAYER/mcl_spawn/init.lua @@ -146,60 +146,87 @@ local function good_for_respawn(pos, player) local def0 = minetest.registered_nodes[nn0] local def1 = minetest.registered_nodes[nn1] local def2 = minetest.registered_nodes[nn2] + + -- Be safe around undefined nodes + if not def0 then return false end + if not def1 then return false end + if not def2 then return false end + 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, trees) - if not emerge_pos1 or not emerge_pos2 then return false end - local trees = trees or get_trees(pos1) - if not trees then return false end - if (attempts_to_find_trees * 3 < #trees) then - -- random search - for i = 1, attempts_to_find_trees do - local pos2 = trees[math.random(1,#trees)] - 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 +local ADJACENT_OFFSETS = { + vector.new( 1,0, 0), + vector.new(-1,0, 0), + vector.new( 0,0, 1), + vector.new( 0,0,-1), + vector.new( 0,1, 0), +} + +local function pathfind_next_to(target, pos1, distance) + local closest_free_pos = nil + local closest_free_dist = nil + for j=1,4 do + local test_pos = vector.add(target, ADJACENT_OFFSETS[j]) + if minetest.get_node(test_pos).name == "air" and minetest.get_node(vector.offset(test_pos,0,-1,0)).name ~= "air" then + local new_dist = vector.distance(pos1, test_pos) + if not closest_free_pos or new_dist < closest_free_dist then + closest_free_pos = test_pos + closest_free_dist = new_dist end end + end + + local way = false + if closest_free_pos then + return minetest.find_path(pos1, closest_free_pos, distance, 1, 3, "A*_noprefetch") + end +end + +local function can_find_tree(pos1, trees) + local start_time = minetest.get_us_time() + + if not emerge_pos1 or not emerge_pos2 then + minetest.log("verbose", "[mcl_spawn] Missing emerge position in can_find_tree()") + return false + end + local trees = trees or get_trees(pos1) + if not trees then + minetest.log("verbose", "[mcl_spawn] No trees in area in can_find_tree()") return false end - for i, pos2 in ipairs(trees) do - -- full search - 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 + -- Search from closest to furthest from pos1 + for i = 2,#trees do + for j = 1,(i-1) do + if vector.distance(pos1,trees[j]) > vector.distance(pos1,trees[j+1]) then + local tmp = trees[j+1] + trees[j] = trees[j+1] + trees[j+1] = tmp end end - if i > attempts_to_find_trees then return false end end + + for i = 1,#trees do + local possible_tree = trees[i] + + local start_pathfind = minetest.get_us_time() + local way = pathfind_next_to(possible_tree, pos1, res) + local stop_pathfind = minetest.get_us_time() + + if stop_pathfind > start_time + 0.25e6 then + minetest.log("info","[mcl_spawn] can_find_tree() timed out") + return false; + end + + if way then + return true + end + end + return false end @@ -285,86 +312,52 @@ local function next_biome() return false end +local function find_spawn_in_area(pos1, pos2) + local nodes = minetest.find_nodes_in_area(pos1, pos2, {"group:tree"}) + if #nodes <= 0 then return nil end + + for i = 1,#nodes do + local pos = nodes[i] + + local candidates = minetest.find_nodes_in_area_under_air( + vector.offset(pos,-3,-2,-3), + vector.offset(pos, 3, 2, 3), + {"group:solid"} + ) + + for j = 1,#candidates do + local candidate = vector.offset(candidates[j],0,1,0) + local gfs = good_for_respawn(candidate) + local way = false + if vector.distance(candidate, pos) <= 1 then + way = true + else + local way = pathfind_next_to(candidate, pos, 5) + end + if gfs and way then + return candidate + end + end + end + + return nil +end + local function ecb_search_continue(blockpos, action, calls_remaining, param) if calls_remaining <= 0 then emerge_pos1 = {x = wsp.x-half_res, y = alt_min, z = wsp.z-half_res} emerge_pos2 = {x = wsp.x+half_res, y = alt_max, z = wsp.z+half_res} - local nodes = minetest.find_nodes_in_area_under_air(emerge_pos1, emerge_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 - if no_trees_area_counter >= 0 then - local trees = get_trees(emerge_pos1, emerge_pos2) - if trees and #trees > 0 then - no_trees_area_counter = 0 - if attempts_to_find_pos * 3 < #nodes then - -- random - for i=1, attempts_to_find_pos do - wsp = nodes[math.random(1,#nodes)] - if wsp then - wsp.y = wsp.y + 1 - if good_for_respawn(wsp) and can_find_tree(wsp, trees) then - minetest.log("action", "[mcl_spawn] Dynamic world spawn randomly determined to be "..minetest.pos_to_string(wsp)) - searched = true - success = true - return - end - end - end - else - -- in a sequence - for i=1, math.min(#nodes, attempts_to_find_pos) do - wsp = nodes[i] - if wsp then - wsp.y = wsp.y + 1 - if good_for_respawn(wsp) and can_find_tree(wsp, trees) 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 - else - no_trees_area_counter = no_trees_area_counter + 1 - if no_trees_area_counter > 10 then - minetest.log("verbose", "[mcl_spawn] More than 10 times no trees at all! Won't search trees next 200 calls") - no_trees_area_counter = -200 - end - end - else -- seems there are no trees but we'll check it later, after next 200 calls - no_trees_area_counter = no_trees_area_counter + 1 - if attempts_to_find_pos * 3 < #nodes then - -- random - for i=1, attempts_to_find_pos do - wsp = nodes[math.random(1,#nodes)] - if wsp then - wsp.y = wsp.y + 1 - if good_for_respawn(wsp) then - minetest.log("action", "[mcl_spawn] Dynamic world spawn randomly determined to be "..minetest.pos_to_string(wsp) .. " (no trees)") - searched = true - success = true - return - end - end - end - else - -- in a sequence - for i=1, math.min(#nodes, attempts_to_find_pos) do - wsp = nodes[i] - if wsp then - wsp.y = wsp.y + 1 - if good_for_respawn(wsp) then - minetest.log("action", "[mcl_spawn] Dynamic world spawn determined to be "..minetest.pos_to_string(wsp) .. " (no trees)") - searched = true - success = true - return - end - end - end - end - end + local start_time = minetest.get_us_time() + + local spawn_pos = find_spawn_in_area(emerge_pos1, emerge_pos2) + if spawn_pos then + wsp = spawn_pos + minetest.log("action", "[mcl_spawn] Dynamic world spawn randomly determined to be "..minetest.pos_to_string(wsp)) + searched = true + success = true + return end + next_pos() mcl_spawn.search() end @@ -565,8 +558,24 @@ function mcl_spawn.shadow_worker() if success then local wsp_node = minetest.get_node(wsp) - if not (wsp_node and wsp_node.name == "ignore") - and ((not good_for_respawn(wsp)) or ((no_trees_area_counter >= 0) and not can_find_tree(wsp))) then + local spawn_bad = false + + if not spawn_bad and not wsp_node or wsp_node.name == "ignore" then + spawn_bad = true + minetest.log("verbose", "[mcl_spawn] World spawn point could not be checked or is 'ignore' wsp_node="..dump(wsp_node)) + end + + if not spawn_bad and not good_for_respawn(wsp) then + spawn_bad = true + minetest.log("verbose", "[mcl_spawn] World spawn point is not good for respawn") + end + + if not spawn_bad and ( (no_trees_area_counter >= 0) and not can_find_tree(wsp) ) then + spawn_bad = true + minetest.log("verbose", "[mcl_spawn] No trees near spawn point") + end + + if spawn_bad then success = false minetest.log("action", "[mcl_spawn] World spawn position isn't safe anymore: "..minetest.pos_to_string(wsp)) mcl_spawn.search()