From 3a4a8799eaa79cb7b9103b8db9e810affdce4156 Mon Sep 17 00:00:00 2001 From: teknomunk Date: Sun, 17 Mar 2024 19:23:06 +0000 Subject: [PATCH 1/7] Change mob spawning randomization from polar coordinates to spherical coordinates, move position validation code from find_spawning_position to get_next_mob_spawn_pos, minimize code remaining in find_spawning_position --- mods/ENTITIES/mcl_mobs/spawning.lua | 86 +++++++++++++++-------------- 1 file changed, 45 insertions(+), 41 deletions(-) diff --git a/mods/ENTITIES/mcl_mobs/spawning.lua b/mods/ENTITIES/mcl_mobs/spawning.lua index 6bfca0082..d82d2d2cf 100755 --- a/mods/ENTITIES/mcl_mobs/spawning.lua +++ b/mods/ENTITIES/mcl_mobs/spawning.lua @@ -603,17 +603,47 @@ local function get_next_mob_spawn_pos(pos) -- TODO We should consider spawning something a little further away sporadically. -- It would be good for sky farms and variance, rather than all being on the 24 - 32 block away radius local distance = math_random(MOB_SPAWN_ZONE_INNER, MOB_SPAWN_ZONE_MIDDLE) - local angle = math_random() * two_pi -- TODO Floor xoff and zoff and add 0.5 so it tries to spawn in the middle of the square. Less failed attempts. - local xoff = math_round(distance * math_cos(angle)) - local zoff = math_round(distance * math_sin(angle)) - return vector.offset(pos, xoff, 0, zoff) -end + -- Use spherical coordinates https://en.wikipedia.org/wiki/Spherical_coordinate_system#Cartesian_coordinates + local theta = math_random() * two_pi + local phi = math_random() * two_pi + local xoff = math_round(distance * math_sin(theta) * math_cos(phi)) + local yoff = math_round(distance * math_cos(theta)) + local zoff = math_round(distance * math_sin(theta) * math_sin(phi)) + local goal_pos = vector.offset(pos, xoff, yoff, zoff) -local function decypher_limits(posy) - posy = math_floor(posy) - return posy - MOB_SPAWN_ZONE_MIDDLE, posy + MOB_SPAWN_ZONE_MIDDLE + if not ( math.abs(goal_pos.x) <= SPAWN_MAPGEN_LIMIT and math.abs(pos.y) <= SPAWN_MAPGEN_LIMIT and math.abs(goal_pos.z) <= SPAWN_MAPGEN_LIMIT ) then + mcl_log("Pos outside mapgen limits: " .. minetest.pos_to_string(goal_pos)) + return nil + end + + -- Ask engine for valid spawn locations + local spawning_position_list = find_nodes_in_area_under_air( + {x = goal_pos.x, y = math_round(pos.y) - MOB_SPAWN_ZONE_MIDDLE, z = goal_pos.z}, + {x = goal_pos.x, y = math_round(pos.y) + MOB_SPAWN_ZONE_MIDDLE, z = goal_pos.z}, + {"group:solid", "group:water", "group:lava"} + ) + + -- Select only the locations at a valid distance + local valid_positions = {} + for _,check_pos in ipairs(spawning_position_list) do + local dist = vector.distance(pos, check_pos) + if dist >= MOB_SPAWN_ZONE_INNER and dist <= MOB_SPAWN_ZONE_OUTER then + valid_positions[#valid_positions + 1] = check_pos + end + end + spawning_position_list = valid_positions + + -- No valid locations, failed to find a position + if #spawning_position_list == 0 then + mcl_log("Spawning position isn't good. Do not spawn: " .. minetest.pos_to_string(goal_pos)) + return nil + end + + -- Pick a random valid location + mcl_log("Spawning positions available: " .. minetest.pos_to_string(goal_pos)) + return spawning_position_list[math_random(1, #spawning_position_list)] end --a simple helper function for mob_spawn @@ -938,42 +968,16 @@ if mobs_spawn then local function find_spawning_position(pos, max_times) local spawning_position - - local max_loops = 1 - if max_times then max_loops = max_times end - - local y_min, y_max = decypher_limits(pos.y) + local max_loops = max_times or 1 --mcl_log("mapgen_limit: " .. SPAWN_MAPGEN_LIMIT) - local i = 0 - repeat - local goal_pos = get_next_mob_spawn_pos(pos) + while max_loops > 0 do + local spawning_position = get_next_mob_spawn_pos(pos) + if spawning_position then return spawning_position + max_loops = max_loops - 1 - if math.abs(goal_pos.x) <= SPAWN_MAPGEN_LIMIT and math.abs(pos.y) <= SPAWN_MAPGEN_LIMIT and math.abs(goal_pos.z) <= SPAWN_MAPGEN_LIMIT then - 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 - mcl_log("Spawning positions available: " .. minetest.pos_to_string(goal_pos)) - spawning_position = spawning_position_list[math_random(1, #spawning_position_list)] - else - mcl_log("Spawning position isn't good. Do not spawn: " .. minetest.pos_to_string(goal_pos)) - end - - else - mcl_log("Pos outside mapgen limits: " .. minetest.pos_to_string(goal_pos)) - end - - - i = i + 1 - if i >= max_loops then - mcl_log("Cancel finding spawn positions at: " .. max_loops) - break - end - until spawning_position - return spawning_position + end + return nil end local cumulative_chance = nil From 74cf14ef0f6495f232f71d90f78db2bbbf2f9cd7 Mon Sep 17 00:00:00 2001 From: teknomunk Date: Sun, 17 Mar 2024 20:33:35 +0000 Subject: [PATCH 2/7] Close if statement --- mods/ENTITIES/mcl_mobs/spawning.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mods/ENTITIES/mcl_mobs/spawning.lua b/mods/ENTITIES/mcl_mobs/spawning.lua index d82d2d2cf..e2047238d 100755 --- a/mods/ENTITIES/mcl_mobs/spawning.lua +++ b/mods/ENTITIES/mcl_mobs/spawning.lua @@ -973,7 +973,7 @@ if mobs_spawn then --mcl_log("mapgen_limit: " .. SPAWN_MAPGEN_LIMIT) while max_loops > 0 do local spawning_position = get_next_mob_spawn_pos(pos) - if spawning_position then return spawning_position + if spawning_position then return spawning_position end max_loops = max_loops - 1 end From 3850fc1a9fc4340026b8ab11fca7425679053d1f Mon Sep 17 00:00:00 2001 From: teknomunk Date: Sun, 17 Mar 2024 20:54:39 +0000 Subject: [PATCH 3/7] Limit y range to the same hemisphere (top/bottom) --- mods/ENTITIES/mcl_mobs/spawning.lua | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/mods/ENTITIES/mcl_mobs/spawning.lua b/mods/ENTITIES/mcl_mobs/spawning.lua index e2047238d..0191059ff 100755 --- a/mods/ENTITIES/mcl_mobs/spawning.lua +++ b/mods/ENTITIES/mcl_mobs/spawning.lua @@ -618,10 +618,20 @@ local function get_next_mob_spawn_pos(pos) return nil end + local y_min + local y_max + if goal_pos.y > pos.y then + y_min = math_round(pos.y) + y_max = math_round(pos.y) + MOB_SPAWN_ZONE_MIDDLE + else + y_min = math_round(pos.y) - MOB_SPAWN_ZONE_MIDDLE + y_max = math_round(pos.y) + end + -- Ask engine for valid spawn locations local spawning_position_list = find_nodes_in_area_under_air( - {x = goal_pos.x, y = math_round(pos.y) - MOB_SPAWN_ZONE_MIDDLE, z = goal_pos.z}, - {x = goal_pos.x, y = math_round(pos.y) + MOB_SPAWN_ZONE_MIDDLE, z = goal_pos.z}, + {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"} ) From bd6c8d35c30800779ea3f7714e908dff0b3b2d08 Mon Sep 17 00:00:00 2001 From: teknomunk Date: Mon, 18 Mar 2024 18:20:46 +0000 Subject: [PATCH 4/7] Change y_min/y_max calculations --- mods/ENTITIES/mcl_mobs/spawning.lua | 29 ++++++++++++++++++++++++----- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/mods/ENTITIES/mcl_mobs/spawning.lua b/mods/ENTITIES/mcl_mobs/spawning.lua index 0191059ff..b1cf71bcf 100755 --- a/mods/ENTITIES/mcl_mobs/spawning.lua +++ b/mods/ENTITIES/mcl_mobs/spawning.lua @@ -23,6 +23,7 @@ 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 math_sqrt = math.sqrt local vector_distance = vector.distance local vector_new = vector.new @@ -618,15 +619,33 @@ local function get_next_mob_spawn_pos(pos) return nil end + -- Calculate upper/lower y limits + local R1 = MOB_SPAWN_ZONE_MIDDLE + local d = vector_distance( pos, vector.new( goal_pos.x, pos.y, goal_pos.z ) ) -- distance from player to projected point on horizontal plane + local y1 = math_sqrt( R1*R1 - d*d ) -- absolue value of distance to outer sphere + local y_min local y_max - if goal_pos.y > pos.y then - y_min = math_round(pos.y) - y_max = math_round(pos.y) + MOB_SPAWN_ZONE_MIDDLE + if d >= MOB_SPAWN_ZONE_INNER then + -- Outer region, y range has both ends on the outer sphere + y_min = pos.y - y1 + y_max = pos.y + y1 else - y_min = math_round(pos.y) - MOB_SPAWN_ZONE_MIDDLE - y_max = math_round(pos.y) + -- Inner region, y range spans between inner and outer spheres + local R2 = MOB_SPAWN_ZONE_INNER + local y2 = math_sqrt( R2*R2 - d*d ) + if goal_pos.y > pos. y then + -- Upper hemisphere + y_min = pos.y + y2 + y_max = pos.y + y1 + else + -- Lower hemisphere + y_min = pos.y - y1 + y_max = pos.y - y2 + end end + y_min = math_round(y_min) + y_max = math_round(y_max) -- Ask engine for valid spawn locations local spawning_position_list = find_nodes_in_area_under_air( From 1bc0a0b41125c39159b10ca9604ee9a5da699f84 Mon Sep 17 00:00:00 2001 From: teknomunk Date: Sun, 14 Apr 2024 09:52:54 +0000 Subject: [PATCH 5/7] Implement a continuous distribution function using a piecewise linear function for selecting the distance from the player to spawn mobs with, fix crash when no nodes are found under air at the goal position --- mods/ENTITIES/mcl_mobs/spawning.lua | 39 ++++++++++++++++++++++++----- 1 file changed, 33 insertions(+), 6 deletions(-) diff --git a/mods/ENTITIES/mcl_mobs/spawning.lua b/mods/ENTITIES/mcl_mobs/spawning.lua index b1cf71bcf..ce3c21cac 100755 --- a/mods/ENTITIES/mcl_mobs/spawning.lua +++ b/mods/ENTITIES/mcl_mobs/spawning.lua @@ -596,14 +596,42 @@ function mcl_mobs:spawn_specific(name, dimension, type_of_spawning, biomes, min_ spawn_dictionary[key]["max_height"] = max_height spawn_dictionary[key]["day_toggle"] = day_toggle spawn_dictionary[key]["check_position"] = check_position - end +-- Calculate the inverse of a piecewise linear function f(x). Line segments are represented as two +-- adjacent points specified as { x, f(x) }. At least 2 points are required. If there are most solutions, +-- the one with a lower x value will be chosen. +local function inverse_pwl(fx, f) + if fx < f[1][2] then + return f[1][1] + end + + for i=2,#f do + local x0,fx0 = unpack(f[i-1]) + local x1,fx1 = unpack(f[i ]) + if fx < fx1 then + return (fx - fx0) * (x1 - x0) / (fx1 - fx0) + x0 + end + end + + return f[#f][1] +end + +local SPAWN_DISTANCE_CDF_PWL = { + {0.000,0.00}, + {0.083,0.40}, + {0.416,0.75}, + {1.000,1.00}, +} + local two_pi = 2 * math.pi local function get_next_mob_spawn_pos(pos) - -- TODO We should consider spawning something a little further away sporadically. - -- It would be good for sky farms and variance, rather than all being on the 24 - 32 block away radius - local distance = math_random(MOB_SPAWN_ZONE_INNER, MOB_SPAWN_ZONE_MIDDLE) + -- Select a distance such that distances closer to the player are selected much more often than + -- those further away from the player. + local fx = (math_random(1,10000)-1) / 10000 + local x = inverse_pwl(fx, SPAWN_DISTANCE_CDF_PWL) + distance = x * (MOB_SPAWN_ZONE_OUTER - MOB_SPAWN_ZONE_INNER) + MOB_SPAWN_ZONE_INNER + --print("Using spawn distance of "..tostring(distance).." fx="..tostring(fx)..",x="..tostring(x)) -- TODO Floor xoff and zoff and add 0.5 so it tries to spawn in the middle of the square. Less failed attempts. -- Use spherical coordinates https://en.wikipedia.org/wiki/Spherical_coordinate_system#Cartesian_coordinates @@ -652,7 +680,7 @@ local function get_next_mob_spawn_pos(pos) {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"} - ) + ) or {} -- Select only the locations at a valid distance local valid_positions = {} @@ -1232,7 +1260,6 @@ function mob_class:check_despawn(pos, dtime) end end - minetest.register_chatcommand("mobstats",{ privs = { debug = true }, func = function(n,param) From 2a014ca79c75ab96514bbe2ef9fb828645b890cb Mon Sep 17 00:00:00 2001 From: teknomunk Date: Sun, 14 Apr 2024 10:08:49 +0000 Subject: [PATCH 6/7] Change y range calculation to use outer zone distance and not middle to let mobs spawn far away from the player --- mods/ENTITIES/mcl_mobs/spawning.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mods/ENTITIES/mcl_mobs/spawning.lua b/mods/ENTITIES/mcl_mobs/spawning.lua index ce3c21cac..8cac1c672 100755 --- a/mods/ENTITIES/mcl_mobs/spawning.lua +++ b/mods/ENTITIES/mcl_mobs/spawning.lua @@ -648,7 +648,7 @@ local function get_next_mob_spawn_pos(pos) end -- Calculate upper/lower y limits - local R1 = MOB_SPAWN_ZONE_MIDDLE + local R1 = MOB_SPAWN_ZONE_OUTER local d = vector_distance( pos, vector.new( goal_pos.x, pos.y, goal_pos.z ) ) -- distance from player to projected point on horizontal plane local y1 = math_sqrt( R1*R1 - d*d ) -- absolue value of distance to outer sphere From 0279dde3d0f96d0cf8736856256807927f8bd97e Mon Sep 17 00:00:00 2001 From: teknomunk Date: Mon, 6 May 2024 07:10:57 +0000 Subject: [PATCH 7/7] Limit check to +/- 16 nodes from goal position (maximum 33 nodes, 3 map blocks) --- mods/ENTITIES/mcl_mobs/spawning.lua | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/mods/ENTITIES/mcl_mobs/spawning.lua b/mods/ENTITIES/mcl_mobs/spawning.lua index 8cac1c672..b0e6260aa 100755 --- a/mods/ENTITIES/mcl_mobs/spawning.lua +++ b/mods/ENTITIES/mcl_mobs/spawning.lua @@ -675,6 +675,14 @@ local function get_next_mob_spawn_pos(pos) y_min = math_round(y_min) y_max = math_round(y_max) + -- Limit total range of check to 32 nodes (maximum of 3 map blocks) + if y_max > goal_pos.y + 16 then + y_max = goal_pos.y + 16 + end + if y_min < goal_pos.y - 16 then + y_min = goal_pos.y - 16 + end + -- Ask engine for valid spawn locations local spawning_position_list = find_nodes_in_area_under_air( {x = goal_pos.x, y = y_min, z = goal_pos.z},