spawn room check that tolerates plants

This commit is contained in:
kno10 2024-12-21 16:45:53 +01:00
parent 2f457d9c32
commit 4be9503794
3 changed files with 42 additions and 67 deletions

@ -153,7 +153,7 @@ function mcl_mobs.register_mob(name, def)
attack_type = def.attack_type, attack_type = def.attack_type,
attack_frequency = def.attack_frequency, attack_frequency = def.attack_frequency,
fly = def.fly or false, fly = def.fly or false,
fly_in = def.fly_in or {"air", "__airlike"}, fly_in = (type(def.fly_in) == "string" and {def.fly_in}) or def.fly_in or {"air"},
owner = def.owner or "", owner = def.owner or "",
order = def.order or "", order = def.order or "",
on_die = def.on_die, on_die = def.on_die,

@ -297,19 +297,13 @@ function mob_class:flight_check()
if not def then return false end -- nil check if not def then return false end -- nil check
local fly_in for _,checknode in pairs(self.fly_in or {"air"}) do
if type(self.fly_in) == "string" then
fly_in = { self.fly_in }
elseif type(self.fly_in) == "table" then
fly_in = self.fly_in
else
return false
end
for _,checknode in pairs(fly_in) do
if nod == checknode or nod == "ignore" then if nod == checknode or nod == "ignore" then
return true return true
end end
if checknode == "air" and def.drawtype == "airlike" then
return true
end
end end
return false return false

@ -532,16 +532,14 @@ end
local function has_room(self, pos) local function has_room(self, pos)
local cb = self.spawnbox or self.collisionbox local cb = self.spawnbox or self.collisionbox
local nodes = {} local nodes = self.fly_in or { "air" }
if self.fly_in then local airlike = false
local t = type(self.fly_in) for i=1,#nodes do
if t == "table" then if nodes[i] == "air" then
nodes = table.copy(self.fly_in) airlike = true
elseif t == "string" then break
table.insert(nodes,self.fly_in)
end end
end end
table.insert(nodes,"air")
-- Calculate area to check for room -- Calculate area to check for room
local cb_height = cb[5] - cb[2] local cb_height = cb[5] - cb[2]
@ -555,61 +553,41 @@ local function has_room(self, pos)
math.round(pos.z + cb[6])) math.round(pos.z + cb[6]))
-- Check if the entire spawn volume is free -- Check if the entire spawn volume is free
local dx = p2.x - p1.x + 1 local p = vector.copy(p1)
local dy = p2.y - p1.y + 1 local headroom = cb_height - (p2.y - p1.y) -- headroom needed in top layer
local dz = p2.z - p1.z + 1 for y = p1.y,p2.y do
local found_nodes = minetest.find_nodes_in_area(p1,p2,nodes) or 0 p.y = y
local n = #found_nodes local check_headroom = headroom < 1 and y == p2.y and minetest.get_node_boxes
if n == dx * dy * dz then
return true
end
-- If we don't have an implementation of get_node_boxes, we can't check for sub-node space
if not minetest.get_node_boxes then return false end
-- Check if it's possible for a sub-node space check to succeed
local needed_in_bottom_section = dx * ( dy - 1) * dz
if n < needed_in_bottom_section then return false end
-- Make sure the entire volume except for the top level is free before checking the top layer
if dy > 1 then
-- Remove nodes in the top layer from the count
for i = 1,#found_nodes do
if found_nodes[i].y == p2.y then
n = n - 1
end
end
-- If the entire volume except the top layer isn't air (or nodes) then we can't spawn this mob here
if n < needed_in_bottom_section then return false end
end
-- Check the top layer to see if we have enough space to spawn in
local top_layer_height = 1
local processed = {}
for x = p1.x,p2.x do
for z = p1.z,p2.z do for z = p1.z,p2.z do
local test_pos = vector.new(x,p2.y,z) p.z = z
local node = minetest.get_node(test_pos) or { name = "ignore" } for x = p1.x,p2.x do
local cache_name = string.format("%s-%d", node.name, node.param2) p.x = x
if not processed[cache_name] then local node = get_node(p)
-- Calculate node bounding box and select the lowest y value local nam = node.name
local boxes = minetest.get_node_boxes("collision_box", test_pos, node) if nam == "ignore" then goto continue end
if nam == "air" and airlike then goto continue end
if airlike then
local n_def = registered_nodes[nam]
if not n_def then goto continue end
if not n_def.walkable and n_def.liquidtype == "none" then goto continue end
end
for i = 1,#nodes do
-- todo: support groups, too?
if nam == nodes[i] then goto continue end
end
if not check_headroom then return false end
-- perform sub-node checks in top layer
local boxes = minetest.get_node_boxes("collision_box", p, node)
for i = 1,#boxes do for i = 1,#boxes do
local box = boxes[i] local box = boxes[i]
local y_test = box[2] + 0.5 if box[2] + 0.5 < headroom then return false end
if y_test < top_layer_height then top_layer_height = y_test end if box[5] + 0.5 < headroom then return false end
local y_test = box[5] + 0.5
if y_test < top_layer_height then top_layer_height = y_test end
end end
::continue::
end end
end end
end end
if top_layer_height + dy - 1 >= cb_height then return true end return true
-- We don't have room
return false
end end
mcl_mobs.custom_biomecheck = nil mcl_mobs.custom_biomecheck = nil
@ -701,6 +679,9 @@ function mcl_mobs.spawn(pos,id)
local def = minetest.registered_entities[id] or minetest.registered_entities["mobs_mc:"..id] or minetest.registered_entities["extra_mobs:"..id] local def = minetest.registered_entities[id] or minetest.registered_entities["mobs_mc:"..id] or minetest.registered_entities["extra_mobs:"..id]
if not def or not def.is_mob or (def.can_spawn and not def.can_spawn(pos)) then return false end if not def or not def.is_mob or (def.can_spawn and not def.can_spawn(pos)) then return false end
if not has_room(def, pos) then return false end if not has_room(def, pos) then return false end
if math.round(pos.y) == pos.y then -- node spawn
pos.y = pos.y - 0.5 - def.collisionbox[2] -- spawn just above ground below
end
local obj = minetest.add_entity(pos, def.name) local obj = minetest.add_entity(pos, def.name)
-- initialize head bone -- initialize head bone
if def.head_swivel and def.head_bone_position then if def.head_swivel and def.head_bone_position then