diff --git a/mods/ENTITIES/mcl_mobs/spawning.lua b/mods/ENTITIES/mcl_mobs/spawning.lua index 6be30eadf..cef93f66e 100644 --- a/mods/ENTITIES/mcl_mobs/spawning.lua +++ b/mods/ENTITIES/mcl_mobs/spawning.lua @@ -42,11 +42,11 @@ if not logging then mcl_log = function() end end local dbg_spawn_attempts = 0 local dbg_spawn_succ = 0 local exclude_time = 0 +local note = nil local remove_far = true local MAX_SPAWN_CYCLE_TIME = 1.65 -- 33 timesteps at 0.05 seconds each -local FIND_SPAWN_POS_RETRIES = 1 local MOB_SPAWN_ZONE_INNER = 24 local MOB_SPAWN_ZONE_INNER_SQ = MOB_SPAWN_ZONE_INNER^2 -- squared @@ -604,6 +604,7 @@ function mcl_mobs.spawn(pos,id) retry = true end if not retry or not has_room(def, pos) then + --note = "no room for mob" return false end end @@ -612,6 +613,7 @@ function mcl_mobs.spawn(pos,id) end local start_time = core.get_us_time() local obj = core.add_entity(pos, def.name) + --note = "spawned a mob" exclude_time = exclude_time + core.get_us_time() - start_time -- initialize head bone if def.head_swivel and def.head_bone_position then @@ -641,12 +643,12 @@ end ---@field light integer ---@field hash integer --- State serialization table workspace -local _sst = {"","","","","","","","",0} ---@param pos vector.Vector ---@param parent_state mcl_mobs.SpawnState? +---@param spawn_hostile boolean +---@param spawn_passive boolean ---@return mcl_mobs.SpawnState?, core.Node? -local function build_state_for_position(pos, parent_state) +local function build_state_for_position(pos, parent_state, spawn_hostile, spawn_passive) local dimension, dim_id = mcl_worlds.pos_to_dimension(pos) -- Get node and make sure it's loaded and a valid spawn point @@ -654,13 +656,6 @@ local function build_state_for_position(pos, parent_state) local node_name = node.name if not node or node_name == "ignore" or node_name == "mcl_core:bedrock" then return end - -- Get spawning parameters for this location - local biome_name,biome_id = get_biome_name(pos) - if not biome_name then return end - - ---@type integer - local hash_num = biome_id * 8 + dim_id - local node_def = core.registered_nodes[node_name] or core.nodedef_default local groups = node_def.groups or {} @@ -682,27 +677,14 @@ local function build_state_for_position(pos, parent_state) pos.y = pos.y + 1 end is_ground = is_ground and (groups.leaves or 0) == 0 - hash_num = hash_num + (is_water and 0x400 or 0) + (is_lava and 0x800 or 0) + (is_ground and 0x1000 or 0) - - -- Build spawn state data - local state = parent_state and table.copy(parent_state) or {} - local spawn_hostile = true - local spawn_passive = true - - state.biome = biome_name - state.dimension = dimension - - state.is_ground = is_ground - state.is_grass = (groups.grass_block or 0) ~= 0 - state.is_water = is_water - state.is_lava = is_lava -- Check light level local gotten_light = get_node_light(pos) + local light = 0 -- Legacy lighting if not modern_lighting then - state.light = gotten_light or 0 + light = gotten_light or 0 else -- Modern lighting local light_node = get_node(pos) @@ -710,18 +692,42 @@ local function build_state_for_position(pos, parent_state) local art_light = core.get_artificial_light(light_node.param1) if dimension == "nether" then - spawn_hostile = art_light <= nether_threshold + spawn_hostile = spawn_hostile and art_light <= nether_threshold elseif dimension == "end" then - spawn_hostile = art_light <= end_threshold + spawn_hostile = spawn_hostile and art_light <= end_threshold elseif dimension == "overworld" then - spawn_hostile = art_light <= overworld_threshold and sky_light <= overworld_sky_threshold + spawn_hostile = spawn_hostile and art_light <= overworld_threshold and sky_light <= overworld_sky_threshold end -- passive threshold is apparently the same in all dimensions ... - spawn_passive = gotten_light >= overworld_passive_threshold + spawn_passive = spawn_passive and gotten_light >= overworld_passive_threshold end + + -- Impossible to spawn a mob here + if not spawn_hostile and not spawn_passive then + --note = "can't spawn either hostile or passive mobs here" + return + end + + -- Get biome information + local biome_name,biome_id = get_biome_name(pos) + if not biome_name then return end + + -- Build spawn state data + local state = parent_state and table.copy(parent_state) or {} + state.biome = biome_name + state.dimension = dimension + state.is_ground = is_ground + state.is_grass = (groups.grass_block or 0) ~= 0 + state.is_water = is_water + state.is_lava = is_lava + state.light = light state.spawn_passive = spawn_passive state.spawn_hostile = spawn_hostile + + ---@type integer + local hash_num = biome_id * 8 + dim_id + hash_num = hash_num + (is_water and 0x400 or 0) + (is_lava and 0x800 or 0) + (is_ground and 0x1000 or 0) hash_num = hash_num + (spawn_passive and 0x2000 or 0) + (spawn_hostile and 0x4000 or 0) + 0x8000 * (state.light or 0) state.hash = hash_num @@ -759,7 +765,7 @@ local function spawn_group(p, mob, spawn_on, amount_to_spawn, parent_state) if not sp then return o end -- Get state for each new position - local state, node = build_state_for_position(sp, parent_state) + local state, node = build_state_for_position(sp, parent_state, true, true) if spawn_check(sp, state, node, mob) then if mob.type_of_spawning == "water" then @@ -882,18 +888,6 @@ if mobs_spawn then return cap_space_available end - local function find_spawning_position(pos, max_times) - local max_loops = max_times or 1 - - while max_loops > 0 do - 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 function select_random_mob_def(spawn_table) if #spawn_table == 0 then return nil end @@ -925,13 +919,16 @@ if mobs_spawn then local spawn_passive = cap_space_passive > 0 and math_random(100) < peaceful_percentage_spawned -- Merge light level checks 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 + local state, node = build_state_for_position(pos, nil, spawn_hostile, spawn_passive) + if not state then + --note = note or "no valid state for position" + return + end -- Make sure it is possible to spawn a mob here - if not state.spawn_hostile and not state.spawn_passive then return end + 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 = state.hash @@ -981,24 +978,34 @@ if mobs_spawn then -- 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) + local spawning_position = get_next_mob_spawn_pos(pos) if not spawning_position then fail_count = fail_count + 1 if logging and fail_count > 16 then core.log("action", "[Mobs spawn] Could not find a valid spawn position in last 16 attempts") end + --note = "no valid spawn position" return end fail_count = 0 -- Spawning prohibited in protected areas - if spawn_protected and core.is_protected(spawning_position, "") then return end + if spawn_protected and core.is_protected(spawning_position, "") then + --note = "position protected" + 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 or not state then return end + if not spawn_list or not state then + --note = note or "no spawnable mobs for pos" + return + end local mob_def = select_random_mob_def(spawn_list) - if not mob_def or not mob_def.name then return end + if not mob_def or not mob_def.name then + --note = "no mob definition" + return + end local mob_def_ent = core.registered_entities[mob_def.name] local cap_space_available = mob_def_ent.type == "monster" and state.cap_space_hostile or state.cap_space_passive @@ -1012,6 +1019,7 @@ if mobs_spawn then -- Make sure we would be spawning a mob if not spawn_check(spawning_position, state, node, mob_def) then if logging then mcl_log("Spawn check failed") end + --note = "spawn check failed" return end @@ -1022,6 +1030,7 @@ if mobs_spawn then if logging then mcl_log("[mcl_mobs] no water spawn for mob "..mob_def.name.." found at "..core.pos_to_string(vector.round(pos))) end + --note = "no water" return end end @@ -1030,6 +1039,7 @@ if mobs_spawn then if logging then mcl_log("[mcl_mobs] mob "..mob_def.name.." refused to spawn at "..core.pos_to_string(vector.round(spawning_position))) end + --note = "mob refused to spawn" return end @@ -1081,6 +1091,7 @@ if mobs_spawn then if total_mobs > mob_cap.total or total_mobs >= #players * mob_cap.player then core.log("action","[mcl_mobs] global mob cap reached. no cycle spawning.") + --note = "global mob cap reached" return end --mob cap per player @@ -1126,15 +1137,15 @@ if mobs_spawn then timer = timer - dtime if timer > 0 then return end + note = nil 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 profile or logging and took > debug_time_threshold then total_time = total_time + took - - core.log("Next spawn attempt in "..tostring(timer).." previous attempt took "..took.." us") - core.log("Totals: "..tostring(total_time / (core.get_us_time() - start_time) * 100).."% count="..count..", "..tostring(total_time/count).."us per spawn attempt") + core.log("Totals: "..tostring(total_time / (core.get_us_time() - start_time) * 100).."% count="..count.. + ", "..tostring(total_time/count).."us per spawn attempt, took="..took.." us, note="..(note or "")) end end) end