More randomness for slime chunks

The old code caused a diagonal pattern. If some x,z was a slime chunk, then so was x+1,z-1.
Use a classic pseudo-random hashing approach instead, by multiplication of chunk numbers
with large primes that should be more random.
Also make slime density and light level configurable.

Allow using a 3d chunking system where y is also used.
This commit is contained in:
kno10 2024-06-25 19:17:39 +02:00 committed by Erich Schubert
parent 026ea5940c
commit 2adf3a9904
2 changed files with 46 additions and 111 deletions

@ -1,112 +1,43 @@
--License for code WTFPL and otherwise stated in readmes
local S = minetest.get_translator("mobs_mc")
-- size for slime chunk logic
local MAPBLOCK_SIZE = 16
local seed = minetest.get_mapgen_setting("seed")
local slime_chunk_match
-- seed + a module specific constant (to avoid co-occurring patterns of spawning), reduce to 32 bit (see below)
local seed = (minetest.get_mapgen_setting("seed") + 54321) % 4294967296
-- slime density, where default N=10 is every 10th chunk
local slime_density = math.floor(tonumber(minetest.settings:get("slime_density")) or 10)
-- use 3D chunking instead of 2d chunks
local slime_3d_chunks = minetest.settings:get_bool("slime_3d_chunks", false)
-- maximum light level, for slimes in caves only, not magma/swamps
local slime_max_light = (tonumber(minetest.settings:get("slime_max_light")) or minetest.LIGHT_MAX) + 1
-- maximum light level for swamp spawning
local swamp_light_max = 7
-- maximum height to spawn in slime chunks
local slime_chunk_spawn_max = mcl_worlds.layer_to_y(40)
local x_modifier
local z_modifier
local function split_by_char (inputstr, sep, limit)
if sep == nil then
sep = "%d"
end
local t = {}
local i = 0
for str in string.gmatch(inputstr, "(["..sep.."])") do
i = i --+ 1
table.insert(t, tonumber(str))
if limit and i >= limit then
break
end
end
return t
-- Pseudo-random mixing of integers.
-- lua does not have true integers with overflow, so we emulate this
local function mix_integers(a,b)
-- We have 53 bits usable in a double. We assume the inputs to have 32 bit.
-- 720007 and 1662833 are primes less than 21 bit, we then simulate the runover to 32 bit
return (a * 720007 + b * 1662833) % 4294967296
end
--Seed: "16002933932875202103" == random seed
--Seed: "1807191622654296300" == cheese
--Seed: "1" = 1
local function process_seed (seed)
--minetest.log("seed: " .. seed)
local split_chars = split_by_char(tostring(seed), nil, 10)
slime_chunk_match = split_chars[1]
x_modifier = split_chars[2]
z_modifier = split_chars[3]
--minetest.log("x_modifier: " .. tostring(x_modifier))
--minetest.log("z_modifier: " .. tostring(z_modifier))
--minetest.log("slime_chunk_match: " .. tostring(slime_chunk_match))
end
local processed = process_seed (seed)
local function convert_to_chunk_value (co_ord, modifier)
local converted = math.floor(math.abs(co_ord) / MAPBLOCK_SIZE)
if modifier then
converted = (converted + modifier)
end
converted = converted % 10
--minetest.log("co_ord: " .. co_ord)
--minetest.log("converted: " .. converted)
return converted
end
assert(convert_to_chunk_value(-16) == 1, "Incorrect convert_to_chunk_value result")
assert(convert_to_chunk_value(-15) == 0, "Incorrect convert_to_chunk_value result")
assert(convert_to_chunk_value(-1) == 0, "Incorrect convert_to_chunk_value result")
assert(convert_to_chunk_value(0) == 0, "Incorrect convert_to_chunk_value result")
assert(convert_to_chunk_value(1) == 0, "Incorrect convert_to_chunk_value result")
assert(convert_to_chunk_value(15) == 0, "Incorrect convert_to_chunk_value result")
assert(convert_to_chunk_value(16) == 1, "Incorrect convert_to_chunk_value result")
assert(convert_to_chunk_value(31) == 1, "Incorrect convert_to_chunk_value result")
assert(convert_to_chunk_value(32) == 2, "Incorrect convert_to_chunk_value result")
assert(convert_to_chunk_value(1599) == 9, "Incorrect convert_to_chunk_value result")
assert(convert_to_chunk_value(1600) == 0, "Incorrect convert_to_chunk_value result")
assert(convert_to_chunk_value(0,9) == 9, "Incorrect convert_to_chunk_value result")
assert(convert_to_chunk_value(16,5) == 6, "Incorrect convert_to_chunk_value result")
assert(convert_to_chunk_value(1599,4) == 3, "Incorrect convert_to_chunk_value result")
local function calculate_chunk_value (pos, x_mod, z_mod)
local chunk_val = math.abs(convert_to_chunk_value(pos.x, x_mod) - convert_to_chunk_value(pos.z, z_mod)) % 10
return chunk_val
end
assert(calculate_chunk_value(vector.new(0,0,0)) == 0, "calculate_chunk_value failed")
assert(calculate_chunk_value(vector.new(0,0,0), 1, 1) == 0, "calculate_chunk_value failed")
assert(calculate_chunk_value(vector.new(0,0,0), 2, 1) == 1, "calculate_chunk_value failed")
assert(calculate_chunk_value(vector.new(64,0,16)) == (4-1), "calculate_chunk_value failed")
assert(calculate_chunk_value(vector.new(16,0,64)) == (3), "calculate_chunk_value failed")
assert(calculate_chunk_value(vector.new(-160,0,-160)) == 0, "calculate_chunk_value failed")
local math_floor = math.floor
local math_max = math.max
local function is_slime_chunk(pos)
if not pos then return end
local chunk_val = calculate_chunk_value (pos, x_modifier, z_modifier)
local slime_chunk = chunk_val == slime_chunk_match
--minetest.log("x: " ..pos.x .. ", z:" .. pos.z)
--minetest.log("seed slime_chunk_match: " .. tostring(slime_chunk_match))
--minetest.log("chunk_val: " .. tostring(chunk_val))
--minetest.log("Is slime chunk: " .. tostring(slime_chunk))
return slime_chunk
if not pos then return end -- no position given
if slime_density < 1 then return end -- no slime chunks
local val = mix_integers(mix_integers(seed, math_floor(pos.x / MAPBLOCK_SIZE)), math_floor(pos.z / MAPBLOCK_SIZE))
if slime_3d_chunks then -- also use y coordinate for chunking
val = mix_integers(val, math_floor(pos.y / MAPBLOCK_SIZE))
end
--minetest.log("Chunk " .. tostring(val) .. " is a slime chunk: " .. tostring(val % slime_density == 0))
return val % slime_density == 0
end
local check_position = function (pos)
return is_slime_chunk(pos)
end
-- Returns a function that spawns children in a circle around pos.
-- To be used as on_die callback.
-- self: mob reference
@ -127,7 +58,7 @@ local spawn_children_on_die = function(child_mob, spawn_distance, eject_speed)
local spawn_count = math.random(2, 4)
for i = 1, spawn_count do
dir = vector.new(math.cos(angle), 0, math.sin(angle))
posadd = vector.normalize(dir) * spawn_distance
posadd = dir * spawn_distance
newpos = pos + posadd
-- If child would end up in a wall, use position of the "mother", unless
-- the "mother" was stuck as well
@ -162,16 +93,12 @@ local spawn_children_on_die = function(child_mob, spawn_distance, eject_speed)
end
end
local swamp_light_max = 7
-- FIXME: redundant check?
local function slime_spawn_check(pos, environmental_light, artificial_light, sky_light)
local maxlight = swamp_light_max
if pos.y <= slime_chunk_spawn_max and is_slime_chunk(pos) then
maxlight = minetest.LIGHT_MAX + 1
return math_max(artificial_light, sky_light) <= slime_max_light
end
return math.max(artificial_light, sky_light) <= maxlight
return math_max(artificial_light, sky_light) <= swamp_light_max
end
-- Slime
@ -322,13 +249,13 @@ mcl_mobs:spawn_specific(
"ground",
cave_biomes,
0,
minetest.LIGHT_MAX+1,
slime_max_light,
30,
1000,
4,
cave_min,
cave_max,
nil, nil, check_position)
nil, nil, is_slime_chunk)
mcl_mobs:spawn_specific(
"mobs_mc:slime_tiny",
@ -349,13 +276,13 @@ mcl_mobs:spawn_specific(
"ground",
cave_biomes,
0,
minetest.LIGHT_MAX+1,
slime_max_light,
30,
1000,
4,
cave_min,
cave_max,
nil, nil, check_position)
nil, nil, is_slime_chunk)
mcl_mobs:spawn_specific(
"mobs_mc:slime_small",
@ -376,13 +303,13 @@ mcl_mobs:spawn_specific(
"ground",
cave_biomes,
0,
minetest.LIGHT_MAX+1,
slime_max_light,
30,
1000,
4,
cave_min,
cave_max,
nil, nil, check_position)
nil, nil, is_slime_chunk)
mcl_mobs:spawn_specific(
"mobs_mc:slime_big",
@ -559,3 +486,4 @@ mcl_mobs:non_spawn_specific("mobs_mc:magma_cube_big","overworld",0, minetest.LIG
mcl_mobs.register_egg("mobs_mc:slime_big", S("Slime"), "#52a03e", "#7ebf6d")
-- FIXME: add spawn eggs for small and tiny slimes and magma cubes

@ -221,6 +221,13 @@ mcl_mobs_overworld_threshold (Artificial light threshold to stop monster spawns
mcl_mobs_overworld_sky_threshold (Skylight threshold to stop monster spawns in the Overworld) int 7 0 14
mcl_mobs_overworld_passive_threshold (Combined light threshold to stop animal and npc spawns in the Overworld) int 7 0 14
# Slime chunk density, default is one in ten, 0 to disable cave slime
slime_density (Slime chunk density) int 10 0
# Use 3d chunking instead of 2d chunking
slime_3d_chunks (Slime chunk placement in 3d) bool false
# Slime chunk maximum light for spawning, default is no limit
slime_max_light (Maximum light level in slime chunks) int 14 0 14
[Audio]
# Enable flame sound.
flame_sound (Flame sound) bool true