From 61c9f9eadeb66837c9d9e195dc6906b53a1cee31 Mon Sep 17 00:00:00 2001
From: teknomunk <teknomunk@protonmail.com>
Date: Sat, 29 Jun 2024 09:21:23 -0500
Subject: [PATCH] Drop biome group generation and replace with spawn state
 checks for much improved performance (but has regressions), change logging of
 failure to find spawn point, increase spawn attempts to 4 per second with no
 position retries and only a single mob or group per attempt

---
 mods/ENTITIES/mcl_mobs/spawning.lua | 514 ++++++++++++++--------------
 1 file changed, 254 insertions(+), 260 deletions(-)

diff --git a/mods/ENTITIES/mcl_mobs/spawning.lua b/mods/ENTITIES/mcl_mobs/spawning.lua
index cc8f1c555..2a399d495 100644
--- a/mods/ENTITIES/mcl_mobs/spawning.lua
+++ b/mods/ENTITIES/mcl_mobs/spawning.lua
@@ -47,8 +47,8 @@ local dbg_spawn_succ = 0
 
 local remove_far = true
 
-local WAIT_FOR_SPAWN_ATTEMPT = 10
-local FIND_SPAWN_POS_RETRIES = 16
+local WAIT_FOR_SPAWN_ATTEMPT = 0.25
+local FIND_SPAWN_POS_RETRIES = 1
 local FIND_SPAWN_POS_RETRIES_SUCCESS_RESPIN = 8
 
 local MOB_SPAWN_ZONE_INNER = 24
@@ -596,74 +596,46 @@ local function get_biome_name(pos)
 	return gotten_biome and mt_get_biome_name(gotten_biome.biome)
 end
 
-local biome_groups = {}
-local biome_group_from_name = {}
-local function generate_biome_groups()
-	local remaining_biomes = {}
-	for i = 1,#list_of_all_biomes do
-		remaining_biomes[list_of_all_biomes[i]] = true
-	end
-
-	-- Find the list of biomes that always appear together in spawn definitions
-	local function build_biome_group(biome_name)
-		local biomes = nil
-		for i = 1,#spawn_dictionary do
-			local spawn_def = spawn_dictionary[i]
-			local spawn_def_biomes = spawn_def.biomes
-			if type(spawn_def_biomes) == "table" and table.find(spawn_def_biomes, biome_name) then
-				if not biomes then
-					biomes = table.copy(spawn_def_biomes)
-				else
-					biomes = table.intersect(biomes, spawn_def_biomes)
-				end
-			end
-		end
-
-		return biomes
-	end
-
-	for i = 1,#list_of_all_biomes do
-		-- Select a biome that we haven't already processed
-		local biome_name = list_of_all_biomes[i]
-		if remaining_biomes[biome_name] then
-			local biome_group = build_biome_group(biome_name)
-			if biome_group then
-				table.insert(biome_groups, biome_group)
-
-				-- Make sure that biomes only appear in a single biome group
-				local new_biome_group = {}
-				for j = 1,#biome_group do
-					local biome = biome_group[j]
-					if not biome_group_from_name[biome] then
-						table.insert(new_biome_group, biome)
-					end
-				end
-				biome_group = new_biome_group
-
-				-- Update the index mapping biome's name to biome_group offset and remove from remaining
-				-- biomes list so we don't get duplicate entries
-				for j = 1,#biome_group do
-					local biome = biome_group[j]
-					minetest.log("biome="..biome)
-					assert(not biome_group_from_name[biome])
-					biome_group_from_name[biome] = #biome_groups
-					remaining_biomes[biome] = nil
-				end
-			end
-		end
-	end
-
-	minetest.log("debug="..dump({
-		biome_groups = biome_groups,
-		biome_group_from_name = biome_group_from_name,
-	}))
-end
-
-local function generate_spawn_groups(spawn_def)
-end
-
 local counts = {}
-local function spawn_check(pos, spawn_def)
+local function initial_spawn_check(state, node, spawn_def)
+	local function log_fail(reason)
+		local count = (counts[reason] or 0) + 1
+		counts[reason] = count
+		--minetest.log("Spawn check for "..tostring(spawn_def and spawn_def.name).." failed - "..reason.." ("..count..")")
+		return false
+	end
+
+	if not spawn_def then return log_fail("missing spawn_def") end
+	local mob_def = minetest.registered_entities[spawn_def.name]
+
+	if mob_def.type == "monster" then
+		if not state.spawn_hostile then return log_fail("can't spawn hostile") end
+	else
+		if not state.spawn_passive then return log_fail("can't spawn passive") end
+	end
+
+	-- Make the dimention is correct
+	if spawn_def.dimension ~= state.dimension then return log_fail("incorrect dimension") end
+
+	if type(spawn_def.biomes) ~= "table" or not table.find(spawn_def.biomes, state.biome) then
+		return log_fail("Incorrect biome")
+	end
+
+	-- Ground mobs must spawn on solid nodes that are not leafes
+	if spawn_def.type_of_spawning == "ground" and not state.is_ground then
+		return log_fail("not ground node")
+	end
+
+	-- Water mobs must spawn in water
+	if spawn_def.type_of_spawning == "water" and not state.is_water then return log_fail("not water node") end
+
+	-- Farm animals must spawn on grass
+	if is_farm_animal(spawn_def.name) and not state.is_grass then return log_fail("not grass block") end
+
+	return true
+end
+
+local function spawn_check(pos, state, node, spawn_def)
 	local function log_fail(reason)
 		local count = (counts[reason] or 0) + 1
 		counts[reason] = count
@@ -671,53 +643,13 @@ local function spawn_check(pos, spawn_def)
 		return false
 	end
 
-	if not spawn_def or not pos then return log_fail("missing pos or spawn_def") end
-
-	local gotten_node = get_node(pos).name
-	if not gotten_node then return log_fail("unable to get node") end
+	if not initial_spawn_check(state, node, spawn_def) then return false end
 
 	dbg_spawn_attempts = dbg_spawn_attempts + 1
 
 	-- Make sure the mob can spawn at this location
 	if pos.y < spawn_def.min_height or pos.y > spawn_def.max_height then return log_fail("incorrect height") end
 
-	-- Make the dimention is correct
-	local dimension = mcl_worlds.pos_to_dimension(pos)
-	if spawn_def.dimension ~= dimension then return log_fail("incorrect dimension") end
-
-	-- Make sure the biome is correct
-	local biome_name = get_biome_name(pos)
-	if not biome_name then return end
-	if not table.find(spawn_def.biomes, biome_name) then return log_fail("incorrect biome") end
-
-	-- Never spawn directly on bedrock
-	if gotten_node == "mcl_core:bedrock" then return log_fail("tried to spawn on bedrock") end
-
-	-- Spawning prohibited in protected areas
-	if spawn_protected and minetest.is_protected(pos, "") then return log_fail("tried to spawn in protected area") end
-
-	-- Ground mobs must spawn on solid nodes that are not leafes
-	local is_ground = minetest.get_item_group(gotten_node,"solid") ~= 0
-	if not is_ground then
-		mcl_log("Node "..gotten_node.." not solid, trying one block")
-		pos.y = pos.y - 1
-		node_name = get_node(pos).name
-		node_def = registered_nodes[node_name]
-	end
-	if not node_def then return end
-	-- do not spawn on bedrock
-	if node_name == "mcl_core:bedrock" then return end
-	pos.y = pos.y + 1
-	if spawn_def.type_of_spawning == "ground" and (not is_ground or get_item_group(gotten_node, "leaves") ~= 0) then
-		return log_fail("not ground node")
-	end
-
-	-- Water mobs must spawn in water
-	if spawn_def.type_of_spawning == "water" and get_item_group(gotten_node, "water") == 0 then return log_fail("not water node") end
-
-	-- Farm animals must spawn on grass
-	if is_farm_animal(spawn_def.name) and get_item_group(gotten_node, "grass_block") == 0 then return log_fail("not grass block") end
-
 	-- Spawns require enough room for the mob
 	local mob_def = minetest.registered_entities[spawn_def.name]
 	if not has_room(mob_def,pos) then return log_fail("mob doesn't fit here") end
@@ -725,49 +657,6 @@ local function spawn_check(pos, spawn_def)
 	-- Don't spawn if the spawn definition has a custom check and that fails
 	if spawn_def.check_position and not spawn_def.check_position(pos) then return log_fail("custom position check failed") end
 
-	local gotten_light = get_node_light(pos)
-
-	-- Legacy lighting
-	if not modern_lighting then
-		if gotten_light < spawn_def.min_light or gotten_light > spawn_def.max_light then
-			return log_fail("incorrect light level")
-		end
-		return false
-	end
-
-	-- Modern lighting
-	local my_node = get_node(pos)
-	local sky_light = minetest.get_natural_light(pos)
-	local art_light = minetest.get_artificial_light(my_node.param1)
-
-	if mob_def.spawn_check then
-		if not mob_def.spawn_check(pos, gotten_light, art_light, sky_light) then
-			return log_fail("mob_def.spawn_check failed")
-		end
-	elseif mob_def.type == "monster" then
-		if dimension == "nether" then
-			if art_light > nether_threshold then
-				return log_fail("artificial light too high")
-			end
-		elseif dimension == "end" then
-			if art_light > end_threshold then
-				return log_fail("artificial light too high")
-			end
-		elseif dimension == "overworld" then
-			if art_light > overworld_threshold then
-				return log_fail("artificial light too high")
-			end
-			if sky_light > overworld_sky_threshold then
-				return log_fail("sky light too high")
-			end
-		end
-	else
-		-- passive threshold is apparently the same in all dimensions ...
-		if gotten_light < overworld_passive_threshold then
-			return log_fail("light too low")
-		end
-	end
-
 	return true
 end
 
@@ -811,7 +700,83 @@ function mcl_mobs.spawn(pos,id)
 	return obj
 end
 
-local function spawn_group(p,mob,spawn_on,amount_to_spawn)
+local function build_state_for_position(pos, parent_state)
+	-- Get spawning parameters for this location
+	local biome_name = get_biome_name(pos)
+	if not biome_name then return end
+
+	local dimension = mcl_worlds.pos_to_dimension(pos)
+
+	-- Get node and make sure it's loaded and a valid spawn point
+	local node = get_node(pos)
+
+	-- Check if it's ground
+	local is_ground = minetest.get_item_group(node.name,"solid") ~= 0
+	if not is_ground then
+		pos.y = pos.y - 1
+		node = get_node(pos)
+		is_ground = minetest.get_item_group(node.name,"solid") ~= 0
+	end
+	pos.y = pos.y + 1
+
+	-- Make sure we can spawn here
+	if not node or node.name == "ignore" or node.name == "mcl_core:bedrock" then return end
+
+	-- Build spawn state data
+	local state = {
+		spawn_hostile = true,
+		spawn_passive = true,
+	}
+	if parent_state then state = table.copy(parent_state) end
+
+	state.biome = biome_name
+	state.dimension = dimension
+
+	state.is_ground = is_ground and get_item_group(node.name, "leaves") == 0
+	state.grass = get_item_group(node.name, "grass_block") ~= 0
+	state.water = get_item_group(node.name, "water") ~= 0
+
+	-- Check light level
+	local gotten_light = get_node_light(pos)
+
+	-- Legacy lighting
+	if not modern_lighting then
+		if gotten_light < spawn_def.min_light or gotten_light > spawn_def.max_light then
+			state.light = gotten_light
+		end
+	else
+		-- Modern lighting
+		local light_node = minetest.get_node(pos)
+		local sky_light = minetest.get_natural_light(pos) or 0
+		local art_light = minetest.get_artificial_light(light_node.param1)
+
+		if dimension == "nether" then
+			if art_light > nether_threshold then
+				state.spawn_hostile = false
+			end
+		elseif dimension == "end" then
+			if art_light > end_threshold then
+				state.spawn_hostile = false
+			end
+		elseif dimension == "overworld" then
+			if art_light > overworld_threshold then
+				state.spawn_hostile = false
+			end
+			if sky_light > overworld_sky_threshold then
+				state.spawn_hostile = false
+			end
+		end
+
+		-- passive threshold is apparently the same in all dimensions ...
+		if gotten_light < overworld_passive_threshold then
+			state.spawn_passive = false
+		end
+	end
+
+	return state,node
+end
+
+local function spawn_group(p, mob, spawn_on, amount_to_spawn, parent_state)
 	local nn= minetest.find_nodes_in_area_under_air(vector.offset(p,-5,-3,-5),vector.offset(p,5,3,5),spawn_on)
 	local o
 	table.shuffle(nn)
@@ -822,7 +787,9 @@ local function spawn_group(p,mob,spawn_on,amount_to_spawn)
 
 	for i = 1, amount_to_spawn do
 		local sp = vector.offset(nn[math.random(#nn)],0,1,0)
-		if spawn_check(nn[math.random(#nn)],mob) then
+		local state, node = build_state_for_position(sp, parent_state)
+
+		if spawn_check(sp, state, node, mob) then
 			if mob.type_of_spawning == "water" then
 				sp = get_water_spawn(sp)
 			end
@@ -907,12 +874,7 @@ minetest.register_chatcommand("spawn_mob",{
 })
 
 if mobs_spawn then
-
-	-- Get pos to spawn, x and z are randomised, y is range
-
-
 	local function mob_cap_space (pos, mob_type, mob_counts_close, mob_counts_wide, cap_space_hostile, cap_space_non_hostile)
-
 		-- Some mob examples
 		--type = "monster", spawn_class = "hostile",
 		--type = "animal", spawn_class = "passive",
@@ -978,28 +940,19 @@ if mobs_spawn then
 		return nil
 	end
 
-	local cumulative_chance = nil
-	local mob_library_worker_table = nil
-	local function initialize_spawn_data()
-		if not mob_library_worker_table then
-			mob_library_worker_table = table_copy(spawn_dictionary)
+	local function select_random_mob_def(spawn_table)
+		if #spawn_table == 0 then return nil end
+
+		local cumulative_chance = 0
+		for i = 1,#spawn_table do
+			cumulative_chance = cumulative_chance + spawn_table[i].chance
 		end
 
-		if not cumulative_chance then
-			cumulative_chance = 0
-			for k, v in pairs(mob_library_worker_table) do
-				cumulative_chance = cumulative_chance + v.chance
-			end
-		end
-	end
-
-	local function select_random_mob_def()
 		local mob_chance_offset = math_random(1, 1e6) / 1e6 * cumulative_chance
-
 		minetest.log("action", "mob_chance_offset = "..tostring(mob_chance_offset).."/"..tostring(cumulative_chance))
 
-		for i = 1,#mob_library_worker_table do
-			local mob_def = mob_library_worker_table[i]
+		for i = 1,#spawn_table do
+			local mob_def = spawn_table[i]
 			local mob_chance = mob_def.chance
 			if mob_chance_offset <= mob_chance then
 				minetest.log(mob_def.name.." "..mob_chance)
@@ -1012,110 +965,145 @@ if mobs_spawn then
 		assert(not "failed")
 	end
 
+	local spawn_lists = {}
+	local function get_spawn_list(pos, cap_space_hostile, cap_space_non_hostile)
+		local spawn_hostile = false
+		local spawn_passive = false
+
+		-- Check capacity
+		local mob_counts_close, mob_counts_wide, total_mobs = count_mobs_all("spawn_class", pos)
+		local cap_space_hostile = mob_cap_space(pos, "hostile", mob_counts_close, mob_counts_wide, cap_space_hostile, cap_space_non_hostile )
+		if cap_space_hostile > 0 then
+			spawn_hostile = true
+		end
+		local cap_space_passive = mob_cap_space(pos, "passive", mob_counts_close, mob_counts_wide, cap_space_hostile, cap_space_non_hostile )
+		if cap_space_passive > 0 then
+			if math.random(100) < peaceful_percentage_spawned then
+				spawn_passive = true
+			end
+		end
+
+		-- Merge light level chcekss with cap checks
+		local state, node = build_state_for_position(pos)
+		if not state then return end
+		state.spawn_hostile = spawn_hostile and state.spawn_hostile
+		state.spawn_passive = spawn_passive and state.spawn_passive
+
+		-- Make sure it is possible to spawn a mob here
+		if not state.spawn_hostile and not state.spawn_passive then return end
+
+		-- Check the cache to see if we have already built a spawn list for this state
+		local state_hash = compute_hash(state) -- from mcl_enchanting
+		local spawn_list = spawn_lists[state_hash]
+		state.cap_space_hostile = cap_space_hostile
+		state.cap_space_passive = cap_space_passive
+		if spawn_list then
+			return spawn_list, state, node
+		end
+
+		-- Build a spawn list for this state
+		spawn_list = {}
+		local spawn_names = {}
+		for _,def in pairs(spawn_dictionary) do
+			if initial_spawn_check(state, node, def) then
+				table.insert(spawn_list, def)
+				table.insert(spawn_names, def.name)
+			end
+		end
+
+		if logging then
+			minetest.log(dump({
+				pos = pos,
+				node = node,
+				state = state,
+				state_hash = state_hash,
+				spawn_names = spawn_names,
+			}))
+		end
+		spawn_lists[state_hash] = spawn_list
+		return spawn_list, state, node
+	end
+
 	-- Spawns one mob or one group of mobs
+	local fail_count = 0
 	local function spawn_a_mob(pos, cap_space_hostile, cap_space_non_hostile)
 		local spawning_position = find_spawning_position(pos, FIND_SPAWN_POS_RETRIES)
 		if not spawning_position then
-			minetest.log("action", "[Mobs spawn] Cannot find a valid spawn position after retries: " .. FIND_SPAWN_POS_RETRIES)
+			fail_count = fail_count + 1
+			if fail_count > 16 then
+				minetest.log("action", "[Mobs spawn] Cannot find a valid spawn position after retries: " .. FIND_SPAWN_POS_RETRIES)
+			end
+			return
+		end
+		fail_count = 0
+
+		-- Spawning prohibited in protected areas
+		if spawn_protected and minetest.is_protected(spawning_position, "") then return end
+
+		-- Select a mob
+		local spawn_list, state, node = get_spawn_list(spawning_position, cap_space_hostile, cap_space_non_hostile)
+		if not spawn_list then return end
+		local mob_def = select_random_mob_def(spawn_list)
+		if not mob_def or not mob_def.name then return end
+		local mob_def_ent = minetest.registered_entities[mob_def.name]
+		if not mob_def_ent then return end
+
+		local cap_space_available = state.cap_space_passive
+		if mob_def_ent.type == "monster" then
+			cap_space_available = state.cap_space_hostile
+		end
+
+		-- Make sure we would be spawning a mob
+		if not spawn_check(spawning_position, state, node, mob_def) then
+			mcl_log("Spawn check failed")
 			return
 		end
 
-		local mob_counts_close, mob_counts_wide, total_mobs = count_mobs_all("spawn_class", spawning_position)
-		--output_mob_stats(mob_counts_close, total_mobs)
-		--output_mob_stats(mob_counts_wide)
-
-		--grab mob that fits into the spawning location
-		--use random weighted choice with replacement to grab a mob, don't exclude any possibilities
-		--shuffle table once every loop to provide equal inclusion probability to all mobs
-		--repeat grabbing a mob to maintain existing spawn rates
-		local spawn_loop_counter = #mob_library_worker_table
-
-		local spawn_check_cache = {}
-		local function inner_loop()
-			local mob_def = select_random_mob_def()
-
-			if not mob_def or not mob_def.name then return end
-			local mob_def_ent = minetest.registered_entities[mob_def.name]
-			if not mob_def_ent then return end
-
-			-- Check capacity
-			local mob_spawn_class = mob_def_ent.spawn_class
-			local cap_space_available = mob_cap_space(spawning_position, mob_spawn_class, mob_counts_close, mob_counts_wide, cap_space_hostile, cap_space_non_hostile)
-			if cap_space_available == 0 then
-				mcl_log("Cap space full")
+		-- Water mob special case
+		if mob_def.type_of_spawning == "water" then
+			spawning_position = get_water_spawn(spawning_position)
+			if not spawning_position then
+				minetest.log("warning","[mcl_mobs] no water spawn for mob "..mob_def.name.." found at "..minetest.pos_to_string(vector.round(pos)))
 				return
 			end
+		end
 
-			-- Spawn caps for animals and water creatures fill up rapidly. Need to throttle this somewhat
-			-- for performance and for early game challenge. We don't want to reduce hostiles though.
-			local spawn_hostile = (mob_spawn_class == "hostile")
-			local spawn_passive = (mob_spawn_class ~= "hostile") and math.random(100) < peaceful_percentage_spawned
-			--mcl_log("Spawn_passive: " .. tostring(spawn_passive))
-			--mcl_log("Spawn_hostile: " .. tostring(spawn_hostile))
+		if mob_def_ent.can_spawn and not mob_def_ent.can_spawn(spawning_position) then
+			minetest.log("warning","[mcl_mobs] mob "..mob_def.name.." refused to spawn at "..minetest.pos_to_string(vector.round(spawning_position)))
+			return
+		end
 
-			-- Make sure we would be spawning a mob
-			if not (spawn_hostile or spawn_passive) then return end
-			if not (spawn_check_cache[mob_def.name] or spawn_check(spawning_position, mob_def)) then
-				mcl_log("Spawn check failed")
-				return
-			end
-			spawn_check_cache[mob_def.name] = true
+		--everything is correct, spawn mob
+		local spawn_in_group = mob_def_ent.spawn_in_group or 4
 
-			-- Water mob special case
-			if mob_def.type_of_spawning == "water" then
-				spawning_position = get_water_spawn(spawning_position)
-				if not spawning_position then
-					minetest.log("warning","[mcl_mobs] no water spawn for mob "..mob_def.name.." found at "..minetest.pos_to_string(vector.round(pos)))
-					return
-				end
-			end
+		local spawned
+		if spawn_in_group then
+			local group_min = mob_def_ent.spawn_in_group_min or 1
+			if not group_min then group_min = 1 end
 
-			if mob_def_ent.can_spawn and not mob_def_ent.can_spawn(spawning_position) then
-				minetest.log("warning","[mcl_mobs] mob "..mob_def.name.." refused to spawn at "..minetest.pos_to_string(vector.round(spawning_position)))
-				return
-			end
-
-			--everything is correct, spawn mob
-			local spawn_in_group = mob_def_ent.spawn_in_group or 4
-
-			local spawn_group_hostile = (mob_spawn_class == "hostile") and (math.random(100) < hostile_group_percentage_spawned)
-			local spawn_group_passive = (mob_spawn_class ~= "hostile") and (math.random(100) < peaceful_group_percentage_spawned)
-
-			mcl_log("spawn_group_hostile: " .. tostring(spawn_group_hostile))
-			mcl_log("spawn_group_passive: " .. tostring(spawn_group_passive))
-
-			local spawned
-			if spawn_in_group and (spawn_group_hostile or spawn_group_passive) then
-				local group_min = mob_def_ent.spawn_in_group_min or 1
-				if not group_min then group_min = 1 end
-
-				local amount_to_spawn = math.random(group_min, spawn_in_group)
-				mcl_log("Spawning quantity: " .. amount_to_spawn)
-				amount_to_spawn = math.min(amount_to_spawn, cap_space_available)
-				mcl_log("throttled spawning quantity: " .. amount_to_spawn)
+			local amount_to_spawn = math.random(group_min, spawn_in_group)
+			mcl_log("Spawning quantity: " .. amount_to_spawn)
+			amount_to_spawn = math.min(amount_to_spawn, cap_space_available)
+			mcl_log("throttled spawning quantity: " .. amount_to_spawn)
 
+			if amount_to_spawn > 1 then
 				if logging then
 					minetest.log("action", "[mcl_mobs] A group of " ..amount_to_spawn .. " " .. mob_def.name ..
 						"mob spawns on " ..minetest.get_node(vector.offset(spawning_position,0,-1,0)).name ..
 						" at " .. minetest.pos_to_string(spawning_position, 1)
 					)
 				end
-				return spawn_group(spawning_position,mob_def,{minetest.get_node(vector.offset(spawning_position,0,-1,0)).name}, amount_to_spawn)
-			else
-				if logging then
-					minetest.log("action", "[mcl_mobs] Mob " .. mob_def.name .. " spawns on " ..
-						minetest.get_node(vector.offset(spawning_position,0,-1,0)).name .." at "..
-						minetest.pos_to_string(spawning_position, 1)
-					)
-				end
-				return mcl_mobs.spawn(spawning_position, mob_def.name)
+				return spawn_group(spawning_position,mob_def,{minetest.get_node(vector.offset(spawning_position,0,-1,0)).name}, amount_to_spawn, state)
 			end
 		end
 
-		while spawn_loop_counter > 0 do
-			if inner_loop() then return end
-			spawn_loop_counter = spawn_loop_counter - 1
+		if logging then
+			minetest.log("action", "[mcl_mobs] Mob " .. mob_def.name .. " spawns on " ..
+				minetest.get_node(vector.offset(spawning_position,0,-1,0)).name .." at "..
+				minetest.pos_to_string(spawning_position, 1)
+			)
 		end
+		return mcl_mobs.spawn(spawning_position, mob_def.name)
 	end
 
 
@@ -1126,7 +1114,6 @@ if mobs_spawn then
 
 		timer = timer + dtime
 		if timer < WAIT_FOR_SPAWN_ATTEMPT then return end
-		initialize_spawn_data()
 		timer = 0
 
 		local start_time_us = minetest.get_us_time()
@@ -1141,7 +1128,10 @@ if mobs_spawn then
 
 		if total_mobs > mob_cap.total or total_mobs > #players * mob_cap.player then
 			minetest.log("action","[mcl_mobs] global mob cap reached. no cycle spawning.")
-			minetest.log("action","[mcl_mobs] took "..(minetest.get_us_time() - start_time_us).." us")
+			local took = (minetest.get_us_time() - start_time_us)
+			if took > 1000 then
+				minetest.log("action","[mcl_mobs] took "..took.." us")
+			end
 			return
 		end --mob cap per player
 
@@ -1153,7 +1143,11 @@ if mobs_spawn then
 				spawn_a_mob(pos, cap_space_hostile, cap_space_non_hostile)
 			end
 		end
-		minetest.log("action","[mcl_mobs] took "..(minetest.get_us_time() - start_time_us).." us")
+
+		local took = (minetest.get_us_time() - start_time_us)
+		if took > 1000 then
+			minetest.log("action","[mcl_mobs] took "..took.." us")
+		end
 	end)
 end