|
|
|
@ -8,6 +8,7 @@ local end_threshold = tonumber(minetest.settings:get("mcl_mobs_end_threshold"))
|
|
|
|
|
local overworld_threshold = tonumber(minetest.settings:get("mcl_mobs_overworld_threshold")) or 0
|
|
|
|
|
local overworld_sky_threshold = tonumber(minetest.settings:get("mcl_mobs_overworld_sky_threshold")) or 7
|
|
|
|
|
local overworld_passive_threshold = tonumber(minetest.settings:get("mcl_mobs_overworld_passive_threshold")) or 7
|
|
|
|
|
local debug_time_threshold = tonumber(minetest.settings:get("vl_debug_time_threshold")) or 1000
|
|
|
|
|
|
|
|
|
|
local get_node = minetest.get_node
|
|
|
|
|
local get_item_group = minetest.get_item_group
|
|
|
|
@ -33,6 +34,7 @@ local vector_floor = vector.floor
|
|
|
|
|
local table_copy = table.copy
|
|
|
|
|
local table_remove = table.remove
|
|
|
|
|
local pairs = pairs
|
|
|
|
|
local check_line_of_sight = mcl_mobs.check_line_of_sight
|
|
|
|
|
|
|
|
|
|
local logging = minetest.settings:get_bool("mcl_logging_mobs_spawn", false)
|
|
|
|
|
local function mcl_log (message, property)
|
|
|
|
@ -50,9 +52,8 @@ local dbg_spawn_counts = {}
|
|
|
|
|
|
|
|
|
|
local remove_far = true
|
|
|
|
|
|
|
|
|
|
local WAIT_FOR_SPAWN_ATTEMPT = 10
|
|
|
|
|
local FIND_SPAWN_POS_RETRIES = 16
|
|
|
|
|
local FIND_SPAWN_POS_RETRIES_SUCCESS_RESPIN = 8
|
|
|
|
|
local MAX_SPAWN_CYCLE_TIME = 2.5
|
|
|
|
|
local FIND_SPAWN_POS_RETRIES = 1
|
|
|
|
|
|
|
|
|
|
local MOB_SPAWN_ZONE_INNER = 24
|
|
|
|
|
local MOB_SPAWN_ZONE_INNER_SQ = MOB_SPAWN_ZONE_INNER^2 -- squared
|
|
|
|
@ -98,169 +99,9 @@ mcl_log("Percentage of hostile spawns are group: " .. hostile_group_percentage_s
|
|
|
|
|
--do mobs spawn?
|
|
|
|
|
local mobs_spawn = minetest.settings:get_bool("mobs_spawn", true) ~= false
|
|
|
|
|
local spawn_protected = minetest.settings:get_bool("mobs_spawn_protected") ~= false
|
|
|
|
|
local logging = minetest.settings:get_bool("mcl_logging_mobs_spawn",false)
|
|
|
|
|
|
|
|
|
|
-- THIS IS THE BIG LIST OF ALL BIOMES - used for programming/updating mobs
|
|
|
|
|
-- Also used for missing parameter
|
|
|
|
|
-- Please update the list when adding new biomes!
|
|
|
|
|
|
|
|
|
|
local list_of_all_biomes = {
|
|
|
|
|
|
|
|
|
|
-- underground:
|
|
|
|
|
|
|
|
|
|
"FlowerForest_underground",
|
|
|
|
|
"JungleEdge_underground",
|
|
|
|
|
"ColdTaiga_underground",
|
|
|
|
|
"IcePlains_underground",
|
|
|
|
|
"IcePlainsSpikes_underground",
|
|
|
|
|
"MegaTaiga_underground",
|
|
|
|
|
"Taiga_underground",
|
|
|
|
|
"ExtremeHills+_underground",
|
|
|
|
|
"JungleM_underground",
|
|
|
|
|
"ExtremeHillsM_underground",
|
|
|
|
|
"JungleEdgeM_underground",
|
|
|
|
|
"MangroveSwamp_underground",
|
|
|
|
|
|
|
|
|
|
-- ocean:
|
|
|
|
|
|
|
|
|
|
"RoofedForest_ocean",
|
|
|
|
|
"JungleEdgeM_ocean",
|
|
|
|
|
"BirchForestM_ocean",
|
|
|
|
|
"BirchForest_ocean",
|
|
|
|
|
"IcePlains_deep_ocean",
|
|
|
|
|
"Jungle_deep_ocean",
|
|
|
|
|
"Savanna_ocean",
|
|
|
|
|
"MesaPlateauF_ocean",
|
|
|
|
|
"ExtremeHillsM_deep_ocean",
|
|
|
|
|
"Savanna_deep_ocean",
|
|
|
|
|
"SunflowerPlains_ocean",
|
|
|
|
|
"Swampland_deep_ocean",
|
|
|
|
|
"Swampland_ocean",
|
|
|
|
|
"MegaSpruceTaiga_deep_ocean",
|
|
|
|
|
"ExtremeHillsM_ocean",
|
|
|
|
|
"JungleEdgeM_deep_ocean",
|
|
|
|
|
"SunflowerPlains_deep_ocean",
|
|
|
|
|
"BirchForest_deep_ocean",
|
|
|
|
|
"IcePlainsSpikes_ocean",
|
|
|
|
|
"Mesa_ocean",
|
|
|
|
|
"StoneBeach_ocean",
|
|
|
|
|
"Plains_deep_ocean",
|
|
|
|
|
"JungleEdge_deep_ocean",
|
|
|
|
|
"SavannaM_deep_ocean",
|
|
|
|
|
"Desert_deep_ocean",
|
|
|
|
|
"Mesa_deep_ocean",
|
|
|
|
|
"ColdTaiga_deep_ocean",
|
|
|
|
|
"Plains_ocean",
|
|
|
|
|
"MesaPlateauFM_ocean",
|
|
|
|
|
"Forest_deep_ocean",
|
|
|
|
|
"JungleM_deep_ocean",
|
|
|
|
|
"FlowerForest_deep_ocean",
|
|
|
|
|
"MushroomIsland_ocean",
|
|
|
|
|
"MegaTaiga_ocean",
|
|
|
|
|
"StoneBeach_deep_ocean",
|
|
|
|
|
"IcePlainsSpikes_deep_ocean",
|
|
|
|
|
"ColdTaiga_ocean",
|
|
|
|
|
"SavannaM_ocean",
|
|
|
|
|
"MesaPlateauF_deep_ocean",
|
|
|
|
|
"MesaBryce_deep_ocean",
|
|
|
|
|
"ExtremeHills+_deep_ocean",
|
|
|
|
|
"ExtremeHills_ocean",
|
|
|
|
|
"MushroomIsland_deep_ocean",
|
|
|
|
|
"Forest_ocean",
|
|
|
|
|
"MegaTaiga_deep_ocean",
|
|
|
|
|
"JungleEdge_ocean",
|
|
|
|
|
"MesaBryce_ocean",
|
|
|
|
|
"MegaSpruceTaiga_ocean",
|
|
|
|
|
"ExtremeHills+_ocean",
|
|
|
|
|
"Jungle_ocean",
|
|
|
|
|
"RoofedForest_deep_ocean",
|
|
|
|
|
"IcePlains_ocean",
|
|
|
|
|
"FlowerForest_ocean",
|
|
|
|
|
"ExtremeHills_deep_ocean",
|
|
|
|
|
"MesaPlateauFM_deep_ocean",
|
|
|
|
|
"Desert_ocean",
|
|
|
|
|
"Taiga_ocean",
|
|
|
|
|
"BirchForestM_deep_ocean",
|
|
|
|
|
"Taiga_deep_ocean",
|
|
|
|
|
"JungleM_ocean",
|
|
|
|
|
"MangroveSwamp_ocean",
|
|
|
|
|
"MangroveSwamp_deep_ocean",
|
|
|
|
|
|
|
|
|
|
-- water or beach?
|
|
|
|
|
|
|
|
|
|
"MesaPlateauFM_sandlevel",
|
|
|
|
|
"MesaPlateauF_sandlevel",
|
|
|
|
|
"MesaBryce_sandlevel",
|
|
|
|
|
"Mesa_sandlevel",
|
|
|
|
|
|
|
|
|
|
-- beach:
|
|
|
|
|
|
|
|
|
|
"FlowerForest_beach",
|
|
|
|
|
"Forest_beach",
|
|
|
|
|
"StoneBeach",
|
|
|
|
|
"ColdTaiga_beach_water",
|
|
|
|
|
"Taiga_beach",
|
|
|
|
|
"Savanna_beach",
|
|
|
|
|
"Plains_beach",
|
|
|
|
|
"ExtremeHills_beach",
|
|
|
|
|
"ColdTaiga_beach",
|
|
|
|
|
"Swampland_shore",
|
|
|
|
|
"MushroomIslandShore",
|
|
|
|
|
"JungleM_shore",
|
|
|
|
|
"Jungle_shore",
|
|
|
|
|
"BambooJungleM_shore",
|
|
|
|
|
"BambooJungle_shore",
|
|
|
|
|
"MangroveSwamp_shore",
|
|
|
|
|
|
|
|
|
|
-- dimension biome:
|
|
|
|
|
|
|
|
|
|
"Nether",
|
|
|
|
|
"BasaltDelta",
|
|
|
|
|
"CrimsonForest",
|
|
|
|
|
"WarpedForest",
|
|
|
|
|
"SoulsandValley",
|
|
|
|
|
"End",
|
|
|
|
|
|
|
|
|
|
-- Overworld regular:
|
|
|
|
|
|
|
|
|
|
"Mesa",
|
|
|
|
|
"FlowerForest",
|
|
|
|
|
"Swampland",
|
|
|
|
|
"Taiga",
|
|
|
|
|
"ExtremeHills",
|
|
|
|
|
"ExtremeHillsM",
|
|
|
|
|
"ExtremeHills+_snowtop",
|
|
|
|
|
"Jungle",
|
|
|
|
|
"Savanna",
|
|
|
|
|
"BirchForest",
|
|
|
|
|
"MegaSpruceTaiga",
|
|
|
|
|
"MegaTaiga",
|
|
|
|
|
"ExtremeHills+",
|
|
|
|
|
"Forest",
|
|
|
|
|
"Plains",
|
|
|
|
|
"Desert",
|
|
|
|
|
"ColdTaiga",
|
|
|
|
|
"MushroomIsland",
|
|
|
|
|
"IcePlainsSpikes",
|
|
|
|
|
"SunflowerPlains",
|
|
|
|
|
"IcePlains",
|
|
|
|
|
"RoofedForest",
|
|
|
|
|
"ExtremeHills+_snowtop",
|
|
|
|
|
"MesaPlateauFM_grasstop",
|
|
|
|
|
"JungleEdgeM",
|
|
|
|
|
"JungleM",
|
|
|
|
|
"BirchForestM",
|
|
|
|
|
"MesaPlateauF",
|
|
|
|
|
"MesaPlateauFM",
|
|
|
|
|
"MesaPlateauF_grasstop",
|
|
|
|
|
"MesaBryce",
|
|
|
|
|
"JungleEdge",
|
|
|
|
|
"SavannaM",
|
|
|
|
|
"MangroveSwamp",
|
|
|
|
|
"BambooJungle",
|
|
|
|
|
"BambooJungleEdge",
|
|
|
|
|
"BambooJungleEdgeM",
|
|
|
|
|
"BambooJungleM",
|
|
|
|
|
}
|
|
|
|
|
local list_of_all_biomes = {}
|
|
|
|
|
|
|
|
|
|
-- count how many mobs are in an area
|
|
|
|
|
local function count_mobs(pos,r,mob_type)
|
|
|
|
@ -561,11 +402,8 @@ function mcl_mobs:non_spawn_specific(mob_name,dimension,min_light,max_light)
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
function mcl_mobs:spawn_specific(name, dimension, type_of_spawning, biomes, min_light, max_light, interval, chance, aoc, min_height, max_height, day_toggle, on_spawn, check_position)
|
|
|
|
|
|
|
|
|
|
-- Do mobs spawn at all?
|
|
|
|
|
if not mobs_spawn then
|
|
|
|
|
return
|
|
|
|
|
end
|
|
|
|
|
if not mobs_spawn then return end
|
|
|
|
|
|
|
|
|
|
assert(min_height)
|
|
|
|
|
assert(max_height)
|
|
|
|
@ -588,19 +426,20 @@ function mcl_mobs:spawn_specific(name, dimension, type_of_spawning, biomes, min_
|
|
|
|
|
|
|
|
|
|
--load information into the spawn dictionary
|
|
|
|
|
local key = #spawn_dictionary + 1
|
|
|
|
|
spawn_dictionary[key] = {}
|
|
|
|
|
spawn_dictionary[key]["name"] = name
|
|
|
|
|
spawn_dictionary[key]["dimension"] = dimension
|
|
|
|
|
spawn_dictionary[key]["type_of_spawning"] = type_of_spawning
|
|
|
|
|
spawn_dictionary[key]["biomes"] = biomes
|
|
|
|
|
spawn_dictionary[key]["min_light"] = min_light
|
|
|
|
|
spawn_dictionary[key]["max_light"] = max_light
|
|
|
|
|
spawn_dictionary[key]["chance"] = chance
|
|
|
|
|
spawn_dictionary[key]["aoc"] = aoc
|
|
|
|
|
spawn_dictionary[key]["min_height"] = min_height
|
|
|
|
|
spawn_dictionary[key]["max_height"] = max_height
|
|
|
|
|
spawn_dictionary[key]["day_toggle"] = day_toggle
|
|
|
|
|
spawn_dictionary[key]["check_position"] = check_position
|
|
|
|
|
spawn_dictionary[key] = {
|
|
|
|
|
name = name,
|
|
|
|
|
dimension = dimension,
|
|
|
|
|
type_of_spawning = type_of_spawning,
|
|
|
|
|
biomes = biomes,
|
|
|
|
|
min_light = min_light,
|
|
|
|
|
max_light = max_light,
|
|
|
|
|
chance = chance,
|
|
|
|
|
aoc = aoc,
|
|
|
|
|
min_height = min_height,
|
|
|
|
|
max_height = max_height,
|
|
|
|
|
day_toggle = day_toggle,
|
|
|
|
|
check_position = check_position,
|
|
|
|
|
}
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
local function get_next_mob_spawn_pos(pos)
|
|
|
|
@ -678,17 +517,6 @@ local function get_next_mob_spawn_pos(pos)
|
|
|
|
|
return spawning_position_list[math_random(1, #spawning_position_list)]
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
--a simple helper function for mob_spawn
|
|
|
|
|
local function biome_check(biome_list, biome_goal)
|
|
|
|
|
for _, data in pairs(biome_list) do
|
|
|
|
|
if data == biome_goal then
|
|
|
|
|
return true
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
return false
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
local function is_farm_animal(n)
|
|
|
|
|
return n == "mobs_mc:pig" or n == "mobs_mc:cow" or n == "mobs_mc:sheep" or n == "mobs_mc:chicken" or n == "mobs_mc:horse" or n == "mobs_mc:donkey"
|
|
|
|
|
end
|
|
|
|
@ -750,85 +578,73 @@ local function get_biome_name(pos)
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
local function spawn_check(pos, spawn_def)
|
|
|
|
|
if not spawn_def or not pos then return end
|
|
|
|
|
local counts = {}
|
|
|
|
|
local function initial_spawn_check(state, node, spawn_def)
|
|
|
|
|
local function log_fail(reason)
|
|
|
|
|
local count = (counts[reason] or 0) + 1
|
|
|
|
|
counts[reason] = count
|
|
|
|
|
if logging then
|
|
|
|
|
minetest.log("Spawn check for "..tostring(spawn_def and spawn_def.name).." failed - "..reason.." ("..count..") "..dump({
|
|
|
|
|
state = state,
|
|
|
|
|
node = node,
|
|
|
|
|
}))
|
|
|
|
|
end
|
|
|
|
|
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
|
|
|
|
|
mcl_log("Spawn check failed - "..reason.." ("..count..")")
|
|
|
|
|
return false
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
if not initial_spawn_check(state, node, spawn_def) then return false end
|
|
|
|
|
|
|
|
|
|
dbg_spawn_attempts = dbg_spawn_attempts + 1
|
|
|
|
|
local dimension = mcl_worlds.pos_to_dimension(pos)
|
|
|
|
|
|
|
|
|
|
-- 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
|
|
|
|
|
|
|
|
|
|
-- Spawns require enough room for the mob
|
|
|
|
|
local mob_def = minetest.registered_entities[spawn_def.name]
|
|
|
|
|
local mob_type = mob_def.type
|
|
|
|
|
local gotten_node = get_node(pos).name
|
|
|
|
|
if not gotten_node then return end
|
|
|
|
|
if not has_room(mob_def,pos) then return log_fail("mob doesn't fit here") end
|
|
|
|
|
|
|
|
|
|
local biome_name = get_biome_name(pos)
|
|
|
|
|
if not biome_name then return end
|
|
|
|
|
-- 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 is_ground = minetest.get_item_group(gotten_node,"solid") ~= 0
|
|
|
|
|
if not is_ground then
|
|
|
|
|
pos.y = pos.y - 1
|
|
|
|
|
gotten_node = get_node(pos).name
|
|
|
|
|
is_ground = minetest.get_item_group(gotten_node,"solid") ~= 0
|
|
|
|
|
end
|
|
|
|
|
pos.y = pos.y + 1
|
|
|
|
|
local is_water = get_item_group(gotten_node, "water") ~= 0
|
|
|
|
|
local is_lava = get_item_group(gotten_node, "lava") ~= 0
|
|
|
|
|
local is_leaf = get_item_group(gotten_node, "leaves") ~= 0
|
|
|
|
|
local is_bedrock = gotten_node == "mcl_core:bedrock"
|
|
|
|
|
local is_grass = minetest.get_item_group(gotten_node,"grass_block") ~= 0
|
|
|
|
|
|
|
|
|
|
if pos.y >= spawn_def.min_height
|
|
|
|
|
and pos.y <= spawn_def.max_height
|
|
|
|
|
and spawn_def.dimension == dimension
|
|
|
|
|
and biome_check(spawn_def.biomes, biome_name) then
|
|
|
|
|
|
|
|
|
|
mcl_log("Spawn level 1 check - Passed")
|
|
|
|
|
if (is_ground or spawn_def.type_of_spawning ~= "ground")
|
|
|
|
|
and (spawn_def.type_of_spawning ~= "ground" or not is_leaf)
|
|
|
|
|
and (not is_farm_animal(spawn_def.name) or is_grass)
|
|
|
|
|
and (spawn_def.type_of_spawning ~= "water" or is_water)
|
|
|
|
|
and not is_bedrock
|
|
|
|
|
and has_room(mob_def,pos)
|
|
|
|
|
and (spawn_def.check_position and spawn_def.check_position(pos) or spawn_def.check_position == nil)
|
|
|
|
|
and ( not spawn_protected or not minetest.is_protected(pos, "") ) then
|
|
|
|
|
|
|
|
|
|
mcl_log("Spawn level 2 check - Passed")
|
|
|
|
|
local gotten_light = get_node_light(pos)
|
|
|
|
|
|
|
|
|
|
if modern_lighting then
|
|
|
|
|
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
|
|
|
|
|
return mob_def.spawn_check(pos, gotten_light, art_light, sky_light)
|
|
|
|
|
elseif mob_type == "monster" then
|
|
|
|
|
if dimension == "nether" then
|
|
|
|
|
if art_light <= nether_threshold then
|
|
|
|
|
return true
|
|
|
|
|
end
|
|
|
|
|
elseif dimension == "end" then
|
|
|
|
|
if art_light <= end_threshold then
|
|
|
|
|
return true
|
|
|
|
|
end
|
|
|
|
|
elseif dimension == "overworld" then
|
|
|
|
|
if art_light <= overworld_threshold and sky_light <= overworld_sky_threshold then
|
|
|
|
|
return true
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
else
|
|
|
|
|
-- passive threshold is apparently the same in all dimensions ...
|
|
|
|
|
if gotten_light > overworld_passive_threshold then
|
|
|
|
|
return true
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
else
|
|
|
|
|
if gotten_light >= spawn_def.min_light and gotten_light <= spawn_def.max_light then
|
|
|
|
|
return true
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
return false
|
|
|
|
|
return true
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
function mcl_mobs.spawn(pos,id)
|
|
|
|
@ -844,24 +660,133 @@ function mcl_mobs.spawn(pos,id)
|
|
|
|
|
return minetest.add_entity(pos, def.name)
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
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 function spawn_group(p,mob,spawn_on,amount_to_spawn)
|
|
|
|
|
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)
|
|
|
|
|
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)
|
|
|
|
|
local node_name = node.name
|
|
|
|
|
|
|
|
|
|
-- Make sure we can spawn here
|
|
|
|
|
if not node or node_name == "ignore" or node_name == "mcl_core:bedrock" then return end
|
|
|
|
|
|
|
|
|
|
-- Check if it's ground
|
|
|
|
|
local is_water = get_item_group(node_name, "water") ~= 0
|
|
|
|
|
local is_ground = false
|
|
|
|
|
if not is_water then
|
|
|
|
|
is_ground = get_item_group(node_name,"solid") ~= 0
|
|
|
|
|
if not is_ground then
|
|
|
|
|
pos.y = pos.y - 1
|
|
|
|
|
node = get_node(pos)
|
|
|
|
|
node_name = node.name
|
|
|
|
|
is_ground = get_item_group(node_name,"solid") ~= 0
|
|
|
|
|
end
|
|
|
|
|
pos.y = pos.y + 1
|
|
|
|
|
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.is_grass = get_item_group(node_name, "grass_block") ~= 0
|
|
|
|
|
state.is_water = is_water
|
|
|
|
|
|
|
|
|
|
-- 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 = 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)
|
|
|
|
|
-- Find possible spawn locations and shuffle the list
|
|
|
|
|
local nn = find_nodes_in_area_under_air(vector.offset(p,-5,-3,-5), vector.offset(p,5,3,5), spawn_on)
|
|
|
|
|
if not nn or #nn < 1 then
|
|
|
|
|
nn = {}
|
|
|
|
|
table.insert(nn,p)
|
|
|
|
|
end
|
|
|
|
|
table.shuffle(nn)
|
|
|
|
|
--minetest.log("Spawn point list: "..dump(nn))
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
-- Use the first amount_to_spawn positions to spawn mobs. If a spawn position is protected,
|
|
|
|
|
-- it is removed from the list and not counted against the spawn amount. Only one mob will
|
|
|
|
|
-- spawn in a given spot.
|
|
|
|
|
local o
|
|
|
|
|
while amount_to_spawn > 0 and #nn > 0 do
|
|
|
|
|
-- Find the next valid group spawn point
|
|
|
|
|
local sp
|
|
|
|
|
while #nn > 0 and not sp do
|
|
|
|
|
-- Select the next spawn position
|
|
|
|
|
sp = vector.offset(nn[#nn],0,1,0)
|
|
|
|
|
nn[#nn] = nil
|
|
|
|
|
|
|
|
|
|
if spawn_protected and minetest.is_protected(sp, "") then
|
|
|
|
|
sp = nil
|
|
|
|
|
elseif not check_line_of_sight(p, sp) then
|
|
|
|
|
sp = nil
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
if not sp then return o end
|
|
|
|
|
|
|
|
|
|
-- Spawning prohibited in protected areas
|
|
|
|
|
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
|
|
|
|
|
o = mcl_mobs.spawn(sp,mob.name)
|
|
|
|
|
if o then dbg_spawn_succ = dbg_spawn_succ + 1 end
|
|
|
|
|
|
|
|
|
|
--minetest.log("Using spawn point "..vector.to_string(sp))
|
|
|
|
|
|
|
|
|
|
o = mcl_mobs.spawn(sp,mob.name)
|
|
|
|
|
if o then
|
|
|
|
|
amount_to_spawn = amount_to_spawn - 1
|
|
|
|
|
dbg_spawn_succ = dbg_spawn_succ + 1
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
return o
|
|
|
|
@ -882,17 +807,13 @@ minetest.register_chatcommand("spawn_mob",{
|
|
|
|
|
table.insert(modifiers, ":"..capture)
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
local mod1 = string.find(param, ":")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
local mobname = param
|
|
|
|
|
local mod1 = string.find(param, ":")
|
|
|
|
|
if mod1 then
|
|
|
|
|
mobname = string.sub(param, 1, mod1-1)
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
local mob = mcl_mobs.spawn(pos,mobname)
|
|
|
|
|
|
|
|
|
|
if mob then
|
|
|
|
|
for c=1, #modifiers do
|
|
|
|
|
modifs = modifiers[c]
|
|
|
|
@ -941,12 +862,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",
|
|
|
|
@ -1007,156 +923,178 @@ if mobs_spawn then
|
|
|
|
|
local spawning_position = get_next_mob_spawn_pos(pos)
|
|
|
|
|
if spawning_position then return spawning_position end
|
|
|
|
|
max_loops = max_loops - 1
|
|
|
|
|
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
|
|
|
|
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,#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)
|
|
|
|
|
return mob_def
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
mob_chance_offset = mob_chance_offset - mob_chance
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
assert(not "failed")
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
local function spawn_a_mob(pos, cap_space_hostile, cap_space_non_hostile)
|
|
|
|
|
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 logging and fail_count > 16 then
|
|
|
|
|
minetest.log("action", "[Mobs spawn] Cannot find a valid spawn position in last 16 attemtps")
|
|
|
|
|
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
|
|
|
|
|
|
|
|
|
|
while spawn_loop_counter > 0 do
|
|
|
|
|
table.shuffle(mob_library_worker_table)
|
|
|
|
|
local mob_chance_offset = math_random(1, cumulative_chance)
|
|
|
|
|
local mob_index = 1
|
|
|
|
|
local mob_chance = mob_library_worker_table[mob_index].chance
|
|
|
|
|
local step_chance = mob_chance
|
|
|
|
|
while step_chance < mob_chance_offset do
|
|
|
|
|
mob_index = mob_index + 1
|
|
|
|
|
if mob_index <= #mob_library_worker_table then
|
|
|
|
|
mob_chance = mob_library_worker_table[mob_index].chance
|
|
|
|
|
step_chance = step_chance + mob_chance
|
|
|
|
|
else
|
|
|
|
|
break
|
|
|
|
|
end
|
|
|
|
|
-- 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
|
|
|
|
|
--minetest.log(mob_def.name.." "..step_chance.. " "..mob_chance)
|
|
|
|
|
|
|
|
|
|
local mob_def = mob_library_worker_table[mob_index]
|
|
|
|
|
if mob_def and mob_def.name and minetest.registered_entities[mob_def.name] then
|
|
|
|
|
|
|
|
|
|
local mob_def_ent = minetest.registered_entities[mob_def.name]
|
|
|
|
|
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 available")
|
|
|
|
|
|
|
|
|
|
-- 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 (spawn_hostile or spawn_passive) and spawn_check(spawning_position,mob_def) then
|
|
|
|
|
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
|
|
|
|
|
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)
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
spawned = 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
|
|
|
|
|
spawned = mcl_mobs.spawn(spawning_position, mob_def.name)
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
if spawned then
|
|
|
|
|
--mcl_log("We have spawned")
|
|
|
|
|
mob_counts_close, mob_counts_wide, total_mobs = count_mobs_all("spawn_class", pos)
|
|
|
|
|
local new_spawning_position = find_spawning_position(pos, FIND_SPAWN_POS_RETRIES_SUCCESS_RESPIN)
|
|
|
|
|
if new_spawning_position then
|
|
|
|
|
mcl_log("Setting new spawning position")
|
|
|
|
|
spawning_position = new_spawning_position
|
|
|
|
|
else
|
|
|
|
|
mcl_log("Cannot set new spawning position")
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
else
|
|
|
|
|
--mcl_log("Spawn check failed")
|
|
|
|
|
end
|
|
|
|
|
else
|
|
|
|
|
--mcl_log("Cap space full")
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
end
|
|
|
|
|
spawn_loop_counter = spawn_loop_counter - 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 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
|
|
|
|
|
|
|
|
|
|
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, state)
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
--MAIN LOOP
|
|
|
|
|
|
|
|
|
|
local timer = 0
|
|
|
|
|
minetest.register_globalstep(function(dtime)
|
|
|
|
|
|
|
|
|
|
timer = timer + dtime
|
|
|
|
|
if timer < WAIT_FOR_SPAWN_ATTEMPT then return end
|
|
|
|
|
initialize_spawn_data()
|
|
|
|
|
timer = 0
|
|
|
|
|
|
|
|
|
|
local function attempt_spawn()
|
|
|
|
|
local players = get_connected_players()
|
|
|
|
|
local total_mobs, total_non_hostile, total_hostile = count_mobs_total_cap()
|
|
|
|
|
|
|
|
|
@ -1178,6 +1116,35 @@ if mobs_spawn then
|
|
|
|
|
spawn_a_mob(pos, cap_space_hostile, cap_space_non_hostile)
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
local function fixed_timeslice(timer, dtime, timeslice_us, handler)
|
|
|
|
|
timer = timer - dtime
|
|
|
|
|
if timer > 0 then return timer, 0 end
|
|
|
|
|
|
|
|
|
|
-- Time the function
|
|
|
|
|
local start_time_us = minetest.get_us_time()
|
|
|
|
|
handler()
|
|
|
|
|
local stop_time_us = minetest.get_us_time()
|
|
|
|
|
|
|
|
|
|
-- Measure how long this took and calculate the time until the next call
|
|
|
|
|
local took = stop_time_us - start_time_us
|
|
|
|
|
timer = took / timeslice_us
|
|
|
|
|
|
|
|
|
|
return timer, took
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
--MAIN LOOP
|
|
|
|
|
local timer = 0
|
|
|
|
|
minetest.register_globalstep(function(dtime)
|
|
|
|
|
local next_spawn, took = fixed_timeslice(timer, dtime, 1000, attempt_spawn)
|
|
|
|
|
timer = next_spawn
|
|
|
|
|
if timer > MAX_SPAWN_CYCLE_TIME then timer = MAX_SPAWN_CYCLE_TIME end
|
|
|
|
|
|
|
|
|
|
if took > debug_time_threshold then
|
|
|
|
|
minetest.log("action","[mcl_mobs] took "..took.." us")
|
|
|
|
|
minetest.log("Next spawn attempt in "..tostring(timer))
|
|
|
|
|
end
|
|
|
|
|
end)
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
@ -1196,7 +1163,6 @@ function mob_class:despawn_allowed()
|
|
|
|
|
despawn_allowed(self)
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
assert(despawn_allowed({can_despawn=false}) == false, "despawn_allowed - can_despawn false failed")
|
|
|
|
|
assert(despawn_allowed({can_despawn=true}) == true, "despawn_allowed - can_despawn true failed")
|
|
|
|
|
|
|
|
|
@ -1247,3 +1213,10 @@ minetest.register_chatcommand("mobstats",{
|
|
|
|
|
output_mob_stats(mob_counts_wide, total_mobs, true)
|
|
|
|
|
end
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
minetest.register_on_mods_loaded(function()
|
|
|
|
|
for _,def in pairs(minetest.registered_biomes) do
|
|
|
|
|
table.insert(list_of_all_biomes, def.name)
|
|
|
|
|
end
|
|
|
|
|
end)
|
|
|
|
|
|
|
|
|
|