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

This commit is contained in:
teknomunk 2024-05-17 14:54:18 +00:00
parent 3b1c55c234
commit 3ad862c61c
1 changed files with 126 additions and 117 deletions

View File

@ -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()