From 53042b6f48c1063408be33f976cb9137f27783ad Mon Sep 17 00:00:00 2001 From: kay27 Date: Fri, 15 Apr 2022 11:48:58 +0200 Subject: [PATCH] Adopt mob spawning from mcl5 --- mods/ENTITIES/mcl_mobs/spawning.lua | 896 +++++++++++----------------- 1 file changed, 357 insertions(+), 539 deletions(-) diff --git a/mods/ENTITIES/mcl_mobs/spawning.lua b/mods/ENTITIES/mcl_mobs/spawning.lua index 2e7f523ba..424989426 100644 --- a/mods/ENTITIES/mcl_mobs/spawning.lua +++ b/mods/ENTITIES/mcl_mobs/spawning.lua @@ -1,180 +1,204 @@ --lua locals -local get_node = minetest.get_node -local get_item_group = minetest.get_item_group -local get_node_light = minetest.get_node_light +local get_node = minetest.get_node +local get_item_group = minetest.get_item_group +local get_node_light = minetest.get_node_light local find_nodes_in_area_under_air = minetest.find_nodes_in_area_under_air -local new_vector = vector.new +local get_biome_name = minetest.get_biome_name +local get_objects_inside_radius = minetest.get_objects_inside_radius +local get_connected_players = minetest.get_connected_players +local minetest_get_perlin = minetest.get_perlin + local math_random = math.random -local get_biome_name = minetest.get_biome_name -local max = math.max -local get_objects_inside_radius = minetest.get_objects_inside_radius -local vector_distance = vector.distance +local math_floor = math.floor +local math_ceil = math.ceil +local math_cos = math.cos +local math_sin = math.sin +local math_round = function(x) return (x > 0) and math_floor(x + 0.5) or math_ceil(x - 0.5) end + +--local vector_distance = vector.distance +local vector_new = vector.new +local vector_floor = vector.floor + +local table_copy = table.copy +local table_remove = table.remove + +local pairs = pairs -- range for mob count local aoc_range = 32 ---[[ - -THIS IS THE BIG LIST OF ALL BIOMES - used for programming/updating mobs - -underground: -"FlowerForest_underground", -"JungleEdge_underground",local spawning_position = spawning_position_list[math.random(1,#spawning_position_list)] -"ColdTaiga_underground", -"IcePlains_underground", -"IcePlainsSpikes_underground", -"MegaTaiga_underground", -"Taiga_underground", -"ExtremeHills+_underground", -"JungleM_underground", -"ExtremeHillsM_underground", -"JungleEdgeM_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", - -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", - -dimension biome: -"Nether", -"End", - -Overworld regular: -"Mesa", -"FlowerForest", -"Swampland", -"Taiga", -"ExtremeHills", -"Jungle", -"Savanna", -"BirchForest", -"MegaSpruceTaiga", -"MegaTaiga", -"ExtremeHills+", -"Forest", -"Plains", -"Desert", -"ColdTaiga", -"MushroomIsland", -"IcePlainsSpikes", -"SunflowerPlains", -"IcePlains", -"RoofedForest", -"ExtremeHills+_snowtop", -"MesaPlateauFM_grasstop", -"JungleEdgeM", -"ExtremeHillsM", -"JungleM", -"BirchForestM", -"MesaPlateauF", -"MesaPlateauFM", -"MesaPlateauF_grasstop", -"MesaBryce", -"JungleEdge", -"SavannaM", -]]-- - - - +--do mobs spawn? local mobs_spawn = minetest.settings:get_bool("mobs_spawn", true) ~= false --- count how many mobs of one type are inside an area -local count_mobs = function(pos,mobtype) + +local noise_params = { + offset = 0, + scale = 3, + spread = { + x = 301, + y = 50, + z = 304, + }, + seed = 100, + octaves = 3, + persistence = 0.5, +} + +-- 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", + + -- 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", + + -- 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", + + -- dimension biome: + + "Nether", + "End", + + -- Overworld regular: + + "Mesa", + "FlowerForest", + "Swampland", + "Taiga", + "ExtremeHills", + "Jungle", + "Savanna", + "BirchForest", + "MegaSpruceTaiga", + "MegaTaiga", + "ExtremeHills+", + "Forest", + "Plains", + "Desert", + "ColdTaiga", + "MushroomIsland", + "IcePlainsSpikes", + "SunflowerPlains", + "IcePlains", + "RoofedForest", + "ExtremeHills+_snowtop", + "MesaPlateauFM_grasstop", + "JungleEdgeM", + "ExtremeHillsM", + "JungleM", + "BirchForestM", + "MesaPlateauF", + "MesaPlateauFM", + "MesaPlateauF_grasstop", + "MesaBryce", + "JungleEdge", + "SavannaM", +} + +-- count how many mobs are in an area +local function count_mobs(pos) local num = 0 - local objs = get_objects_inside_radius(pos, aoc_range) - for n = 1, #objs do - local obj = objs[n]:get_luaentity() - if obj and obj.name and obj._cmi_is_mob then - -- count hostile mobs only - if mobtype == "hostile" then - if obj.spawn_class == "hostile" then - num = num + 1 - end - -- count passive mobs only - else - num = num + 1 - end + for _,object in pairs(get_objects_inside_radius(pos, aoc_range)) do + if object and object:get_luaentity() and object:get_luaentity()._cmi_is_mob then + num = num + 1 end end - return num end @@ -215,11 +239,73 @@ WARNING: BIOME INTEGRATION NEEDED -> How to get biome through lua?? --this is where all of the spawning information is kept local spawn_dictionary = {} +local summary_chance = 0 + +function mobs:spawn_setup(def) + if not mobs_spawn then return end + + if not def then + minetest.log("warning", "Empty mob spawn setup definition") + return + end + + local name = def.name + if not name then + minetest.log("warning", "Missing mob name") + return + end + + local dimension = def.dimension or "overworld" + local type_of_spawning = def.type_of_spawning or "ground" + local biomes = def.biomes or list_of_all_biomes + local min_light = def.min_light or 0 + local max_light = def.max_light or (minetest.LIGHT_MAX + 1) + local chance = def.chance or 1000 + local aoc = def.aoc or aoc_range + local min_height = def.min_height or mcl_mapgen.overworld.min + local max_height = def.max_height or mcl_mapgen.overworld.max + local day_toggle = def.day_toggle + local on_spawn = def.on_spawn + local check_position = def.check_position + + -- chance/spawn number override in minetest.conf for registered mob + local numbers = minetest.settings:get(name) + if numbers then + numbers = numbers:split(",") + chance = tonumber(numbers[1]) or chance + aoc = tonumber(numbers[2]) or aoc + if chance == 0 then + minetest.log("warning", string.format("[mobs] %s has spawning disabled", name)) + return + end + minetest.log("action", string.format("[mobs] Chance setting for %s changed to %s (total: %s)", name, chance, aoc)) + end + + if chance < 1 then + chance = 1 + minetest.log("warning", "Chance shouldn't be less than 1 (mob name: " .. name ..")") + end + + spawn_dictionary[#spawn_dictionary + 1] = { + 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, + on_spawn = on_spawn, + } + summary_chance = summary_chance + chance +end function mobs:spawn_specific(name, dimension, type_of_spawning, biomes, min_light, max_light, interval, chance, aoc, min_height, max_height, day_toggle, on_spawn) - --print(dump(biomes)) - -- Do mobs spawn at all? if not mobs_spawn then return @@ -238,180 +324,7 @@ function mobs:spawn_specific(name, dimension, type_of_spawning, biomes, min_ligh return end - minetest.log("action", - string.format("[mobs] Chance setting for %s changed to %s (total: %s)", name, chance, aoc)) - end - - --[[ - local spawn_action - spawn_action = function(pos, node, active_object_count, active_object_count_wider, name) - - local orig_pos = table.copy(pos) - -- is mob actually registered? - if not mobs.spawning_mobs[name] - or not minetest.registered_entities[name] then - minetest.log("warning", "Mob spawn of "..name.." failed, unknown entity or mob is not registered for spawning!") - return - end - - -- additional custom checks for spawning mob - if mobs:spawn_abm_check(pos, node, name) == true then - minetest.log("info", "Mob spawn of "..name.." at "..minetest.pos_to_string(pos).." failed, ABM check rejected!") - return - end - - -- count nearby mobs in same spawn class - local entdef = minetest.registered_entities[name] - local spawn_class = entdef and entdef.spawn_class - if not spawn_class then - if entdef.type == "monster" then - spawn_class = "hostile" - else - spawn_class = "passive" - end - end - local in_class_cap = count_mobs(pos, "!"..spawn_class) < MOB_CAP[spawn_class] - -- do not spawn if too many of same mob in area - if active_object_count_wider >= max_per_block -- large-range mob cap - or (not in_class_cap) -- spawn class mob cap - or count_mobs(pos, name) >= aoc then -- per-mob mob cap - -- too many entities - minetest.log("info", "Mob spawn of "..name.." at "..minetest.pos_to_string(pos).." failed, too crowded!") - return - end - - -- if toggle set to nil then ignore day/night check - if day_toggle ~= nil then - - local tod = (minetest.get_timeofday() or 0) * 24000 - - if tod > 4500 and tod < 19500 then - -- daylight, but mob wants night - if day_toggle == false then - -- mob needs night - minetest.log("info", "Mob spawn of "..name.." at "..minetest.pos_to_string(pos).." failed, mob needs light!") - return - end - else - -- night time but mob wants day - if day_toggle == true then - -- mob needs day - minetest.log("info", "Mob spawn of "..name.." at "..minetest.pos_to_string(pos).." failed, mob needs daylight!") - return - end - end - end - - -- spawn above node - pos.y = pos.y + 1 - - -- only spawn away from player - local objs = minetest.get_objects_inside_radius(pos, 24) - - for n = 1, #objs do - - if objs[n]:is_player() then - -- player too close - minetest.log("info", "Mob spawn of "..name.." at "..minetest.pos_to_string(pos).." failed, player too close!") - return - end - end - - -- mobs cannot spawn in protected areas when enabled - if not spawn_protected - and minetest.is_protected(pos, "") then - minetest.log("info", "Mob spawn of "..name.." at "..minetest.pos_to_string(pos).." failed, position is protected!") - return - end - - -- are we spawning within height limits? - if pos.y > max_height - or pos.y < min_height then - minetest.log("info", "Mob spawn of "..name.." at "..minetest.pos_to_string(pos).." failed, out of height limit!") - return - end - - -- are light levels ok? - local light = minetest.get_node_light(pos) - if not light - or light > max_light - or light < min_light then - minetest.log("info", "Mob spawn of "..name.." at "..minetest.pos_to_string(pos).." failed, bad light!") - return - end - - -- do we have enough space to spawn mob? - local ent = minetest.registered_entities[name] - local width_x = max(1, math.ceil(ent.collisionbox[4] - ent.collisionbox[1])) - local min_x, max_x - if width_x % 2 == 0 then - max_x = math.floor(width_x/2) - min_x = -(max_x-1) - else - max_x = math.floor(width_x/2) - min_x = -max_x - end - - local width_z = max(1, math.ceil(ent.collisionbox[6] - ent.collisionbox[3])) - local min_z, max_z - if width_z % 2 == 0 then - max_z = math.floor(width_z/2) - min_z = -(max_z-1) - else - max_z = math.floor(width_z/2) - min_z = -max_z - end - - local max_y = max(0, math.ceil(ent.collisionbox[5] - ent.collisionbox[2]) - 1) - - for y = 0, max_y do - for x = min_x, max_x do - for z = min_z, max_z do - local pos2 = {x = pos.x+x, y = pos.y+y, z = pos.z+z} - if minetest.registered_nodes[node_ok(pos2).name].walkable == true then - -- inside block - minetest.log("info", "Mob spawn of "..name.." at "..minetest.pos_to_string(pos).." failed, too little space!") - if ent.spawn_small_alternative ~= nil and (not minetest.registered_nodes[node_ok(pos).name].walkable) then - minetest.log("info", "Trying to spawn smaller alternative mob: "..ent.spawn_small_alternative) - spawn_action(orig_pos, node, active_object_count, active_object_count_wider, ent.spawn_small_alternative) - end - return - end - end - end - end - - -- tweak X/Y/Z spawn pos - if width_x % 2 == 0 then - pos.x = pos.x + 0.5 - end - if width_z % 2 == 0 then - pos.z = pos.z + 0.5 - end - pos.y = pos.y - 0.5 - - local mob = minetest.add_entity(pos, name) - minetest.log("action", "Mob spawned: "..name.." at "..minetest.pos_to_string(pos)) - - if on_spawn then - - local ent = mob:get_luaentity() - - on_spawn(ent, pos) - end - end - - local function spawn_abm_action(pos, node, active_object_count, active_object_count_wider) - spawn_action(pos, node, active_object_count, active_object_count_wider, name) - end - ]]-- - - local entdef = minetest.registered_entities[name] - local spawn_class - if entdef.type == "monster" then - spawn_class = "hostile" - else - spawn_class = "passive" + minetest.log("action", string.format("[mobs] Chance setting for %s changed to %s (total: %s)", name, chance, aoc)) end --load information into the spawn dictionary @@ -423,106 +336,34 @@ function mobs:spawn_specific(name, dimension, type_of_spawning, biomes, min_ligh spawn_dictionary[key]["biomes"] = biomes spawn_dictionary[key]["min_light"] = min_light spawn_dictionary[key]["max_light"] = max_light - spawn_dictionary[key]["interval"] = interval 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]["on_spawn"] = spawn_abm_action - spawn_dictionary[key]["spawn_class"] = spawn_class - --[[ - minetest.register_abm({ - label = name .. " spawning", - nodenames = nodes, - neighbors = neighbors, - interval = interval, - chance = floor(max(1, chance * mobs_spawn_chance)), - catch_up = false, - action = spawn_abm_action, - }) - ]]-- + summary_chance = summary_chance + chance end --- compatibility with older mob registration --- we're going to forget about this for now -j4i ---[[ -function mobs:register_spawn(name, nodes, max_light, min_light, chance, active_object_count, max_height, day_toggle) - - mobs:spawn_specific(name, nodes, {"air"}, min_light, max_light, 30, - chance, active_object_count, -31000, max_height, day_toggle) +local two_pi = 2 * math.pi +local function get_next_mob_spawn_pos(pos) + local distance = math_random(25, 32) + local angle = math_random() * two_pi + return { + x = math_round(pos.x + distance * math_cos(angle)), + y = pos.y, + z = math_round(pos.z + distance * math_sin(angle)) + } end -]]-- - - ---Don't disable this yet-j4i --- MarkBu's spawn function - -function mobs:spawn(def) - --does nothing for now - --[[ - local name = def.name - local nodes = def.nodes or {"group:soil", "group:stone"} - local neighbors = def.neighbors or {"air"} - local min_light = def.min_light or 0 - local max_light = def.max_light or 15 - local interval = def.interval or 30 - local chance = def.chance or 5000 - local active_object_count = def.active_object_count or 1 - local min_height = def.min_height or -31000 - local max_height = def.max_height or 31000 - local day_toggle = def.day_toggle - local on_spawn = def.on_spawn - - mobs:spawn_specific(name, nodes, neighbors, min_light, max_light, interval, - chance, active_object_count, min_height, max_height, day_toggle, on_spawn) - ]]-- -end - - - -local axis ---inner and outer part of square donut radius -local inner = 1 -local outer = 65 -local int = {-1,1} -local position_calculation = function(pos) - - pos = vector.floor(pos) - - --this is used to determine the axis buffer from the player - axis = math.random(0,1) - - --cast towards the direction - if axis == 0 then --x - pos.x = pos.x + math.random(inner,outer)*int[math.random(1,2)] - pos.z = pos.z + math.random(-outer,outer) - else --z - pos.z = pos.z + math.random(inner,outer)*int[math.random(1,2)] - pos.x = pos.x + math.random(-outer,outer) - end - return(pos) -end - ---[[ -local decypher_limits_dictionary = { - ["overworld"] = {mcl_vars.mg_overworld_min,mcl_vars.mg_overworld_max}, - ["nether"] = {mcl_vars.mg_nether_min, mcl_vars.mg_nether_max}, - ["end"] = {mcl_vars.mg_end_min, mcl_vars.mg_end_max} -} -]]-- local function decypher_limits(posy) - --local min_max_table = decypher_limits_dictionary[dimension] - --return min_max_table[1],min_max_table[2] - posy = math.floor(posy) + posy = math_floor(posy) return posy - 32, posy + 32 end --a simple helper function for mob_spawn local function biome_check(biome_list, biome_goal) - for _,data in ipairs(biome_list) do + for _, data in pairs(biome_list) do if data == biome_goal then return true end @@ -532,124 +373,101 @@ local function biome_check(biome_list, biome_goal) end ---todo mob limiting ---MAIN LOOP - if mobs_spawn then + + local perlin_noise + + local function spawn_a_mob(pos, dimension, y_min, y_max) + local dimension = dimension or mcl_worlds.pos_to_dimension(pos) + local goal_pos = get_next_mob_spawn_pos(pos) + local spawning_position_list = find_nodes_in_area_under_air( + {x = goal_pos.x, y = y_min, z = goal_pos.z}, + {x = goal_pos.x, y = y_max, z = goal_pos.z}, + {"group:solid", "group:water", "group:lava"} + ) + if #spawning_position_list <= 0 then return end + local spawning_position = spawning_position_list[math_random(1, #spawning_position_list)] + + --hard code mob limit in area to 5 for now + if count_mobs(spawning_position) >= 5 then return end + + local gotten_node = get_node(spawning_position).name + local gotten_biome = minetest.get_biome_data(spawning_position) + if not gotten_node or not gotten_biome then return end + gotten_biome = get_biome_name(gotten_biome.biome) --makes it easier to work with + + --add this so mobs don't spawn inside nodes + spawning_position.y = spawning_position.y + 1 + + --only need to poll for node light if everything else worked + local gotten_light = get_node_light(spawning_position) + + local is_water = get_item_group(gotten_node, "water") ~= 0 + local is_lava = get_item_group(gotten_node, "lava") ~= 0 + local is_ground = not (is_water or is_lava) + if not is_ground then + spawning_position.y = spawning_position.y - 1 + end + + local mob_def + + --create a disconnected clone of the spawn dictionary + --prevents memory leak + local mob_library_worker_table = table_copy(spawn_dictionary) + + --grab mob that fits into the spawning location + --randomly grab a mob, don't exclude any possibilities + perlin_noise = perlin_noise or minetest_get_perlin(noise_params) + local noise = perlin_noise:get_3d(spawning_position) + local current_summary_chance = summary_chance + while #mob_library_worker_table > 0 do + local mob_chance_offset = (math_round(noise * current_summary_chance + 12345) % current_summary_chance) + 1 + 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 + mob_chance = mob_library_worker_table[mob_index].chance + step_chance = step_chance + mob_chance + end + local mob_def = mob_library_worker_table[mob_index] + if mob_def + and spawning_position.y >= mob_def.min_height + and spawning_position.y <= mob_def.max_height + and mob_def.dimension == dimension + and biome_check(mob_def.biomes, gotten_biome) + and gotten_light >= mob_def.min_light + and gotten_light <= mob_def.max_light + and (is_ground or mob_def.type_of_spawning ~= "ground") + and (mob_def.check_position and mob_def.check_position(spawning_position) or true) + then + --everything is correct, spawn mob + local object = minetest.add_entity(spawning_position, mob_def.name) + if object then + return mob_def.on_spawn and mob_def.on_spawn(object, pos) + end + end + current_summary_chance = current_summary_chance - mob_chance + table_remove(mob_library_worker_table, mob_index) + end + end + + + --MAIN LOOP + local timer = 0 minetest.register_globalstep(function(dtime) timer = timer + dtime - if timer >= 8 then - timer = 0 - for _,player in pairs(minetest.get_connected_players()) do - for i = 1,math_random(3,8) do - repeat -- after this line each "break" means "continue" - local player_pos = player:get_pos() - - local _,dimension = mcl_worlds.y_to_layer(player_pos.y) - - if dimension == "void" or dimension == "default" then - break -- ignore void and unloaded area - end - - local min,max = decypher_limits(player_pos.y) - - local goal_pos = position_calculation(player_pos) - - local spawning_position_list = find_nodes_in_area_under_air(new_vector(goal_pos.x,min,goal_pos.z), vector.new(goal_pos.x,max,goal_pos.z), {"group:solid", "group:water", "group:lava"}) - - --couldn't find node - if #spawning_position_list <= 0 then - break - end - - local spawning_position = spawning_position_list[math_random(1,#spawning_position_list)] - - --Prevent strange behavior/too close to player - if not spawning_position or vector_distance(player_pos, spawning_position) < 15 then - break - end - - local gotten_node = get_node(spawning_position) - local gotten_node_name = gotten_node.name - local gotten_node_def = minetest.registered_nodes[gotten_node_name] - - if not gotten_node_name or not gotten_node_def or gotten_node_name == "air" then --skip air nodes - break - end - - if gotten_node_def.use_texture_alpha and gotten_node_def.use_texture_alpha ~= "opaque" then - break - end --don't spawn on nonopaque nodes - - local leaf = minetest.get_item_group(gotten_node_name,"leaves") - if leaf ~= 0 then - break end --don't spawn on treetops - - local gotten_biome = minetest.get_biome_data(spawning_position) - - if not gotten_biome then - break --skip if in unloaded area - end - - gotten_biome = get_biome_name(gotten_biome.biome) --makes it easier to work with - - --grab random mob - local mob_def = spawn_dictionary[math.random(1,#spawn_dictionary)] - - if not mob_def then - break --skip if something ridiculous happens (nil mob def) - end - - --skip if not correct dimension - if mob_def.dimension ~= dimension then - break - end - - --skip if not in correct biome - if not biome_check(mob_def.biomes, gotten_biome) then - break - end - - --add this so mobs don't spawn inside nodes - spawning_position.y = spawning_position.y + 1 - - if spawning_position.y < mob_def.min_height or spawning_position.y > mob_def.max_height then - break - end - - --only need to poll for node light if everything else worked - local gotten_light = get_node_light(spawning_position) - - --don't spawn if not in light limits - if gotten_light < mob_def.min_light or gotten_light > mob_def.max_light then - break - end - - local is_water = get_item_group(gotten_node_name, "water") ~= 0 - local is_lava = get_item_group(gotten_node_name, "lava") ~= 0 - - if mob_def.type_of_spawning == "ground" and is_water then - break - end - - if mob_def.type_of_spawning == "ground" and is_lava then - break - end - - --finally do the heavy check (for now) of mobs in area - if count_mobs(spawning_position, mob_def.spawn_class) >= mob_def.aoc then - break - end - - --adjust the position for water and lava mobs - if mob_def.type_of_spawning == "water" or mob_def.type_of_spawning == "lava" then - spawning_position.y = spawning_position.y - 1 - end - - --everything is correct, spawn mob - minetest.add_entity(spawning_position, mob_def.name) - until true --this is a safety catch + if timer < 10 then return end + timer = 0 + for _, player in pairs(get_connected_players()) do + local pos = player:get_pos() + local dimension = mcl_worlds.pos_to_dimension(pos) + -- ignore void and unloaded area + if dimension ~= "void" and dimension ~= "default" then + local y_min, y_max = decypher_limits(pos.y) + for i = 1, math_random(1, 4) do + spawn_a_mob(pos, dimension, y_min, y_max) end end end