Dedicate clay and structures from mapgen core

This commit is contained in:
kay27 2022-01-07 06:24:11 +04:00
parent 54adfe9e30
commit 726159e796
6 changed files with 400 additions and 384 deletions

View File

@ -5,14 +5,14 @@ It also queues your generators to run them in proper order:
### mcl_mapgen.register_on_generated(lvm_callback_function, order_number) ### mcl_mapgen.register_on_generated(lvm_callback_function, order_number)
========================================================================= =========================================================================
Replacement of engine API function `minetest.register_on_generated(function(minp, maxp, blockseed))` Replacement of engine API function `minetest.register_on_generated(function(vm_context))`
It is still unsafe. Cavegen part can and will overwrite outer 1-block layer of the chunk which is expected to be generated. It is still unsafe. Cavegen part can and will overwrite outer 1-block layer of the chunk which is expected to be generated.
Nodes marked as `is_ground_content` could be overwritten. Air and water are usually 'ground content' too. Nodes marked as `is_ground_content` could be overwritten. Air and water are usually 'ground content' too.
For Minetest 5.4 it doesn't recommended to place blocks within lvm callback function. For Minetest 5.4 it doesn't recommended to place blocks within lvm callback function.
See https://git.minetest.land/MineClone2/MineClone2/issues/1395 See https://git.minetest.land/MineClone2/MineClone2/issues/1395
`lvm_callback_function`: chunk callback LVM function definition: `lvm_callback_function`: chunk callback LVM function definition:
`function(vm_context)`: `function(vm_context)`:
Function MUST RETURN `vm_context` back anyway! It will passed into next lvm callback function from the queue. `vm_context` will pass into next lvm callback function from the queue!
`vm_context`: a table which already contains some LVM data as the fields, and some of them can be added in your lvm callback function: `vm_context`: a table which already contains some LVM data as the fields, and some of them can be added in your lvm callback function:
`vm`: curent voxel manipulator object itself; `vm`: curent voxel manipulator object itself;
`blockseed`: seed of this mapchunk; `blockseed`: seed of this mapchunk;
@ -50,7 +50,7 @@ See https://git.minetest.land/MineClone2/MineClone2/issues/1395
### mcl_mapgen.register_mapgen_block_lvm(lvm_callback_function, order_number) ### mcl_mapgen.register_mapgen_block_lvm(lvm_callback_function, order_number)
============================================================================= =============================================================================
Registers lvm callback function to be called when current block (usually 16x16x16 nodes) generation is REALLY 100% finished. Registers lvm callback function to be called when current block (usually 16x16x16 nodes) generation is REALLY 100% finished.
`vm_context` passes into lvm callback function and should always be returned back. `vm_context` passes into lvm callback function.
`lvm_callback_function`: the block callback LVM function definition - same as for chunks - see definition example above; `lvm_callback_function`: the block callback LVM function definition - same as for chunks - see definition example above;
`order_number` (optional): the less, the earlier, `order_number` (optional): the less, the earlier,
e.g. `mcl_mapgen.order.BUILDINGS` or `mcl_mapgen.order.LARGE_BUILDINGS` e.g. `mcl_mapgen.order.BUILDINGS` or `mcl_mapgen.order.LARGE_BUILDINGS`
@ -85,7 +85,7 @@ Set
Registers lvm callback function to be called when current chunk generation is REALLY 100% finished. Registers lvm callback function to be called when current chunk generation is REALLY 100% finished.
It's the most frustrating function from this mod. It can't provide you access to mapgen objects. They are probably gone long ago. It's the most frustrating function from this mod. It can't provide you access to mapgen objects. They are probably gone long ago.
Don't use it for accessing mapgen objects please. Don't use it for accessing mapgen objects please.
`vm_context` passes into lvm callback function and should always be returned back. `vm_context` passes into lvm callback function.
`lvm_callback_function`: the block callback LVM function definition - same as above; `lvm_callback_function`: the block callback LVM function definition - same as above;
`order_number` (optional): the less, the earlier. `order_number` (optional): the less, the earlier.

View File

@ -241,7 +241,7 @@ minetest.register_on_generated(function(minp, maxp, chunkseed)
vm_context.blockseed = blockseed vm_context.blockseed = blockseed
vm_context.minp, vm_context.maxp = {x=x, y=y, z=z}, {x=x+LAST_NODE, y=y+LAST_NODE, z=z+LAST_NODE} vm_context.minp, vm_context.maxp = {x=x, y=y, z=z}, {x=x+LAST_NODE, y=y+LAST_NODE, z=z+LAST_NODE}
for _, v in pairs(queue_blocks_lvm) do for _, v in pairs(queue_blocks_lvm) do
vm_context = v.callback_function(vm_context) v.callback_function(vm_context)
end end
end end
if nodes_block > 0 then if nodes_block > 0 then
@ -264,7 +264,7 @@ minetest.register_on_generated(function(minp, maxp, chunkseed)
if #queue_unsafe_engine > 0 then if #queue_unsafe_engine > 0 then
for _, v in pairs(queue_unsafe_engine) do for _, v in pairs(queue_unsafe_engine) do
vm_context = v.f(vm_context) v.f(vm_context)
end end
if vm_context.write then if vm_context.write then
vm:set_data(data) vm:set_data(data)
@ -304,7 +304,7 @@ minetest.register_on_generated(function(minp, maxp, chunkseed)
chunkseed = seed, chunkseed = seed,
} }
for _, v in pairs(queue_chunks_lvm) do for _, v in pairs(queue_chunks_lvm) do
v.f(vm_context) vm_context = v.f(vm_context)
end end
for _, v in pairs(queue_chunks_nodes) do for _, v in pairs(queue_chunks_nodes) do
v.f(minp, maxp, seed, vm_context) v.f(minp, maxp, seed, vm_context)

View File

@ -110,7 +110,7 @@ local loottable =
} }
-- Bonus loot for v6 mapgen: Otherwise unobtainable saplings. -- Bonus loot for v6 mapgen: Otherwise unobtainable saplings.
if mg_name == "v6" then if mcl_mapgen.v6 then
table.insert(loottable, { table.insert(loottable, {
stacks_min = 1, stacks_min = 1,
stacks_max = 3, stacks_max = 3,
@ -137,36 +137,11 @@ local function spawn_dungeon(p1, p2, dim, pr, dontcheck)
local y_ceiling = y + dim.y + 1 local y_ceiling = y + dim.y + 1
if check then if check then
local result1, result2 = true, true
local dim_x, dim_z = dim.x, dim.z local dim_x, dim_z = dim.x, dim.z
local size = dim_z*dim_x local size = dim_z*dim_x
local time1 = minetest.get_us_time()
for i=1,100 do
for tx = x+1, x+dim_x do
for tz = z+1, z+dim_z do
if not registered_nodes[get_node({x = tx, y = y_floor , z = tz}).name].walkable
or not registered_nodes[get_node({x = tx, y = y_ceiling, z = tz}).name].walkable then
result1 = false
end
end
end
end
local time2 = minetest.get_us_time()
for i=1,100 do
if #minetest_find_nodes_in_area({x=x+1,y=y_floor,z=z+1}, {x=x+dim_z,y=y_floor,z=z+dim_z}, "group:walkabke") < size if #minetest_find_nodes_in_area({x=x+1,y=y_floor,z=z+1}, {x=x+dim_z,y=y_floor,z=z+dim_z}, "group:walkabke") < size
or #minetest_find_nodes_in_area({x=x+1,y=y_floor,z=z+1}, {x=x+dim_z,y=y_floor,z=z+dim_z}, "group:walkabke") < size then or #minetest_find_nodes_in_area({x=x+1,y=y_floor,z=z+1}, {x=x+dim_z,y=y_floor,z=z+dim_z}, "group:walkabke") < size then
result2 = false return
end
end
local time3 = minetest.get_us_time()
if result1 == result2 then
local d1, d2 = time2-time1, time3-time2
local m1 = m1 + d1
local m2 = m2 + d2
minetest.chat_send_all("m1 = " .. tostring(m1))
minetest.chat_send_all("m2 = " .. tostring(m2))
else
minetest.log("warning", "results mismatch")
end end
end end

View File

@ -0,0 +1,58 @@
-- Generate Clay
mcl_mapgen.register_mapgen_lvm(function(c)
local minp, maxp, blockseed, voxelmanip_data, voxelmanip_area, lvm_used = c.minp, c.maxp, c.chunkseed, c.data, c.area, c.write or false
-- TODO: Make clay generation reproducible for same seed.
if maxp.y < -5 or minp.y > 0 then
return c
end
c.vm = c.vm or mcl_mapgen.get_voxel_manip(c)
minetest.log("warning", "CLAY!")
local pr = PseudoRandom(blockseed)
perlin_clay = perlin_clay or minetest.get_perlin({
offset = 0.5,
scale = 0.2,
spread = {x = 5, y = 5, z = 5},
seed = -316,
octaves = 1,
persist = 0.0
})
for y=math.max(minp.y, 0), math.min(maxp.y, -8), -1 do
-- Assume X and Z lengths are equal
local divlen = 4
local divs = (maxp.x-minp.x)/divlen+1;
for divx=0+1,divs-2 do
for divz=0+1,divs-2 do
-- Get position and shift it a bit randomly so the clay do not obviously appear in a grid
local cx = minp.x + math.floor((divx+0.5)*divlen) + pr:next(-1,1)
local cz = minp.z + math.floor((divz+0.5)*divlen) + pr:next(-1,1)
local water_pos = voxelmanip_area:index(cx, y+1, cz)
local waternode = voxelmanip_data[water_pos]
local surface_pos = voxelmanip_area:index(cx, y, cz)
local surfacenode = voxelmanip_data[surface_pos]
local genrnd = pr:next(1, 20)
if genrnd == 1 and perlin_clay:get_3d({x=cx,y=y,z=cz}) > 0 and waternode == c_water and
(surfacenode == c_dirt or minetest.get_item_group(minetest.get_name_from_content_id(surfacenode), "sand") == 1) then
local diamondsize = pr:next(1, 3)
for x1 = -diamondsize, diamondsize do
for z1 = -(diamondsize - math.abs(x1)), diamondsize - math.abs(x1) do
local ccpos = voxelmanip_area:index(cx+x1, y, cz+z1)
local claycandidate = voxelmanip_data[ccpos]
if voxelmanip_data[ccpos] == c_dirt or minetest.get_item_group(minetest.get_name_from_content_id(claycandidate), "sand") == 1 then
voxelmanip_data[ccpos] = c_clay
lvm_used = true
end
end
end
end
end
end
end
c.write = lvm_used
return c
end)

View File

@ -45,13 +45,10 @@ minetest.register_alias("mapgen_stair_sandstonebrick", "mcl_stairs:stair_sandsto
minetest.register_alias("mapgen_stair_sandstone_block", "mcl_stairs:stair_sandstone") minetest.register_alias("mapgen_stair_sandstone_block", "mcl_stairs:stair_sandstone")
minetest.register_alias("mapgen_stair_desert_stone", "mcl_stairs:stair_sandstone") minetest.register_alias("mapgen_stair_desert_stone", "mcl_stairs:stair_sandstone")
local mg_name = minetest.get_mapgen_setting("mg_name") local mg_name = mcl_mapgen.name
local superflat = mg_name == "flat" and minetest.get_mapgen_setting("mcl_superflat_classic") == "true" local superflat = mcl_mapgen.superflat
local v6 = mcl_mapgen.v6
local WITCH_HUT_HEIGHT = 3 -- Exact Y level to spawn witch huts at. This height refers to the height of the floor local singlenode = mcl_mapgen.singlenode
-- End exit portal position
local END_EXIT_PORTAL_POS = vector.new(-3, -27003, -3)
-- Content IDs -- Content IDs
local c_bedrock = minetest.get_content_id("mcl_core:bedrock") local c_bedrock = minetest.get_content_id("mcl_core:bedrock")
@ -451,7 +448,7 @@ if minetest.settings:get_bool("mcl_generate_ores", true) then
-- Emerald -- Emerald
-- --
if mg_name == "v6" then if v6 then
-- Generate everywhere in v6, but rarely. -- Generate everywhere in v6, but rarely.
-- Common spawn -- Common spawn
@ -1104,7 +1101,7 @@ mcl_vars.mg_dungeons = mcl_mapgen.dungeons
mg_flags.dungeons = false mg_flags.dungeons = false
-- Apply mapgen-specific mapgen code -- Apply mapgen-specific mapgen code
if mg_name == "v6" then if v6 then
register_mgv6_decorations() register_mgv6_decorations()
elseif superflat then elseif superflat then
-- Enforce superflat-like mapgen: no caves, decor, lakes and hills -- Enforce superflat-like mapgen: no caves, decor, lakes and hills
@ -1125,20 +1122,6 @@ if string.len(mg_flags_str) > 0 then
end end
minetest.set_mapgen_setting("mg_flags", mg_flags_str, true) minetest.set_mapgen_setting("mg_flags", mg_flags_str, true)
-- Helper function for converting a MC probability to MT, with
-- regards to MapBlocks.
-- Some MC generated structures are generated on per-chunk
-- probability.
-- The MC probability is 1/x per Minecraft chunk (16×16).
-- x: The MC probability is 1/x.
-- minp, maxp: MapBlock limits
-- returns: Probability (1/return_value) for a single MT mapblock
local function minecraft_chunk_probability(x, minp, maxp)
-- 256 is the MC chunk height
return x * (((maxp.x-minp.x+1)*(maxp.z-minp.z+1)) / 256)
end
-- Takes an index of a biomemap table (from minetest.get_mapgen_object), -- Takes an index of a biomemap table (from minetest.get_mapgen_object),
-- minp and maxp (from an on_generated callback) and returns the real world coordinates -- minp and maxp (from an on_generated callback) and returns the real world coordinates
-- as X, Z. -- as X, Z.
@ -1151,82 +1134,11 @@ end
return x, z return x, z
end]] end]]
-- Takes x and z coordinates and minp and maxp of a generated chunk
-- (in on_generated callback) and returns a biomemap index)
-- Inverse function of biomemap_to_xz
local function xz_to_biomemap_index(x, z, minp, maxp)
local xwidth = maxp.x - minp.x + 1
local zwidth = maxp.z - minp.z + 1
local minix = x % xwidth
local miniz = z % zwidth
return (minix + miniz * zwidth) + 1
end
-- Perlin noise objects -- Perlin noise objects
local perlin_structures local perlin_structures
local perlin_vines, perlin_vines_fine, perlin_vines_upwards, perlin_vines_length, perlin_vines_density local perlin_vines, perlin_vines_fine, perlin_vines_upwards, perlin_vines_length, perlin_vines_density
local perlin_clay local perlin_clay
-- Generate Clay
mcl_mapgen.register_mapgen_lvm(function(c)
local minp, maxp, blockseed, voxelmanip_data, voxelmanip_area, lvm_used = c.minp, c.maxp, c.chunkseed, c.data, c.area, c.write or false
-- TODO: Make clay generation reproducible for same seed.
if maxp.y < -5 or minp.y > 0 then
return c
end
c.vm = c.vm or mcl_mapgen.get_voxel_manip(c)
minetest.log("warning", "CLAY!")
local pr = PseudoRandom(blockseed)
perlin_clay = perlin_clay or minetest.get_perlin({
offset = 0.5,
scale = 0.2,
spread = {x = 5, y = 5, z = 5},
seed = -316,
octaves = 1,
persist = 0.0
})
for y=math.max(minp.y, 0), math.min(maxp.y, -8), -1 do
-- Assume X and Z lengths are equal
local divlen = 4
local divs = (maxp.x-minp.x)/divlen+1;
for divx=0+1,divs-2 do
for divz=0+1,divs-2 do
-- Get position and shift it a bit randomly so the clay do not obviously appear in a grid
local cx = minp.x + math.floor((divx+0.5)*divlen) + pr:next(-1,1)
local cz = minp.z + math.floor((divz+0.5)*divlen) + pr:next(-1,1)
local water_pos = voxelmanip_area:index(cx, y+1, cz)
local waternode = voxelmanip_data[water_pos]
local surface_pos = voxelmanip_area:index(cx, y, cz)
local surfacenode = voxelmanip_data[surface_pos]
local genrnd = pr:next(1, 20)
if genrnd == 1 and perlin_clay:get_3d({x=cx,y=y,z=cz}) > 0 and waternode == c_water and
(surfacenode == c_dirt or minetest.get_item_group(minetest.get_name_from_content_id(surfacenode), "sand") == 1) then
local diamondsize = pr:next(1, 3)
for x1 = -diamondsize, diamondsize do
for z1 = -(diamondsize - math.abs(x1)), diamondsize - math.abs(x1) do
local ccpos = voxelmanip_area:index(cx+x1, y, cz+z1)
local claycandidate = voxelmanip_data[ccpos]
if voxelmanip_data[ccpos] == c_dirt or minetest.get_item_group(minetest.get_name_from_content_id(claycandidate), "sand") == 1 then
voxelmanip_data[ccpos] = c_clay
lvm_used = true
end
end
end
end
end
end
end
c.write = lvm_used
return c
end)
local dragon_spawn_pos = false local dragon_spawn_pos = false
local dragon_spawned, portal_generated = false, false local dragon_spawned, portal_generated = false, false
@ -1252,7 +1164,7 @@ if portal_generated and not dragon_spawned then
minetest.after(10, try_to_spawn_ender_dragon) minetest.after(10, try_to_spawn_ender_dragon)
end end
local function generate_end_exit_portal(pos) function mcl_mapgen_core.generate_end_exit_portal(pos)
if dragon_spawn_pos then return false end if dragon_spawn_pos then return false end
dragon_spawn_pos = vector.add(pos, vector.new(3, 11, 3)) dragon_spawn_pos = vector.add(pos, vector.new(3, 11, 3))
mcl_structures.call_struct(pos, "end_exit_portal", nil, nil, function() mcl_structures.call_struct(pos, "end_exit_portal", nil, nil, function()
@ -1266,252 +1178,6 @@ local function generate_end_exit_portal(pos)
portal_generated = true portal_generated = true
end end
-- TODO: Try to use more efficient structure generating code
local function generate_structures(minp, maxp, blockseed, biomemap)
local chunk_has_desert_well = false
local chunk_has_desert_temple = false
local chunk_has_igloo = false
local struct_min, struct_max = -3, 111 --64
if maxp.y >= struct_min and minp.y <= struct_max then
-- Generate structures
local pr = PcgRandom(blockseed)
perlin_structures = perlin_structures or minetest.get_perlin(329, 3, 0.6, 100)
-- Assume X and Z lengths are equal
local divlen = 5
for x0 = minp.x, maxp.x, divlen do for z0 = minp.z, maxp.z, divlen do
-- Determine amount from perlin noise
local amount = math.floor(perlin_structures:get_2d({x=x0, y=z0}) * 9)
-- Find random positions based on this random
local p, ground_y
for i=0, amount do
p = {x = pr:next(x0, x0+divlen-1), y = 0, z = pr:next(z0, z0+divlen-1)}
-- Find ground level
ground_y = nil
local nn
for y = struct_max, struct_min, -1 do
p.y = y
local checknode = minetest.get_node(p)
if checknode then
nn = checknode.name
local def = minetest.registered_nodes[nn]
if def and def.walkable then
ground_y = y
break
end
end
end
if ground_y then
p.y = ground_y+1
local nn0 = minetest.get_node(p).name
-- Check if the node can be replaced
if minetest.registered_nodes[nn0] and minetest.registered_nodes[nn0].buildable_to then
-- Desert temples and desert wells
if nn == "mcl_core:sand" or (nn == "mcl_core:sandstone") then
if not chunk_has_desert_temple and not chunk_has_desert_well and ground_y > 3 then
-- Spawn desert temple
-- TODO: Check surface
if pr:next(1,12000) == 1 then
mcl_structures.call_struct(p, "desert_temple", nil, pr)
chunk_has_desert_temple = true
end
end
if not chunk_has_desert_temple and not chunk_has_desert_well and ground_y > 3 then
local desert_well_prob = minecraft_chunk_probability(1000, minp, maxp)
-- Spawn desert well
if pr:next(1, desert_well_prob) == 1 then
-- Check surface
local surface = minetest.find_nodes_in_area({x=p.x,y=p.y-1,z=p.z}, {x=p.x+5, y=p.y-1, z=p.z+5}, "mcl_core:sand")
if #surface >= 25 then
mcl_structures.call_struct(p, "desert_well", nil, pr)
chunk_has_desert_well = true
end
end
end
-- Igloos
elseif not chunk_has_igloo and (nn == "mcl_core:snowblock" or nn == "mcl_core:snow" or (minetest.get_item_group(nn, "grass_block_snow") == 1)) then
if pr:next(1, 4400) == 1 then
-- Check surface
local floor = {x=p.x+9, y=p.y-1, z=p.z+9}
local surface = minetest.find_nodes_in_area({x=p.x,y=p.y-1,z=p.z}, floor, "mcl_core:snowblock")
local surface2 = minetest.find_nodes_in_area({x=p.x,y=p.y-1,z=p.z}, floor, "mcl_core:dirt_with_grass_snow")
if #surface + #surface2 >= 63 then
mcl_structures.call_struct(p, "igloo", nil, pr)
chunk_has_igloo = true
end
end
end
-- Fossil
if nn == "mcl_core:sandstone" or nn == "mcl_core:sand" and not chunk_has_desert_temple and ground_y > 3 then
local fossil_prob = minecraft_chunk_probability(64, minp, maxp)
if pr:next(1, fossil_prob) == 1 then
-- Spawn fossil below desert surface between layers 40 and 49
local p1 = {x=p.x, y=pr:next(mcl_worlds.layer_to_y(40), mcl_worlds.layer_to_y(49)), z=p.z}
-- Very rough check of the environment (we expect to have enough stonelike nodes).
-- Fossils may still appear partially exposed in caves, but this is O.K.
local p2 = vector.add(p1, 4)
local nodes = minetest.find_nodes_in_area(p1, p2, {"mcl_core:sandstone", "mcl_core:stone", "mcl_core:diorite", "mcl_core:andesite", "mcl_core:granite", "mcl_core:stone_with_coal", "mcl_core:dirt", "mcl_core:gravel"})
if #nodes >= 100 then -- >= 80%
mcl_structures.call_struct(p1, "fossil", nil, pr)
end
end
end
-- Witch hut
if ground_y <= 0 and nn == "mcl_core:dirt" then
local prob = minecraft_chunk_probability(48, minp, maxp)
if pr:next(1, prob) == 1 then
local swampland = minetest.get_biome_id("Swampland")
local swampland_shore = minetest.get_biome_id("Swampland_shore")
-- Where do witches live?
local here_be_witches = false
if mg_name == "v6" then
-- v6: In Normal biome
if biomeinfo.get_v6_biome(p) == "Normal" then
here_be_witches = true
end
else
-- Other mapgens: In swampland biome
local bi = xz_to_biomemap_index(p.x, p.z, minp, maxp)
if biomemap[bi] == swampland or biomemap[bi] == swampland_shore then
here_be_witches = true
end
end
if here_be_witches then
local r = tostring(pr:next(0, 3) * 90) -- "0", "90", "180" or 270"
local p1 = {x=p.x-1, y=WITCH_HUT_HEIGHT+2, z=p.z-1}
local size
if r == "0" or r == "180" then
size = {x=10, y=4, z=8}
else
size = {x=8, y=4, z=10}
end
local p2 = vector.add(p1, size)
-- This checks free space at the “body” of the hut and a bit around.
-- ALL nodes must be free for the placement to succeed.
local free_nodes = minetest.find_nodes_in_area(p1, p2, {"air", "mcl_core:water_source", "mcl_flowers:waterlily"})
if #free_nodes >= ((size.x+1)*(size.y+1)*(size.z+1)) then
local place = {x=p.x, y=WITCH_HUT_HEIGHT-1, z=p.z}
-- FIXME: For some mysterious reason (black magic?) this
-- function does sometimes NOT spawn the witch hut. One can only see the
-- oak wood nodes in the water, but no hut. :-/
mcl_structures.call_struct(place, "witch_hut", r, pr)
-- TODO: Spawn witch in or around hut when the mob sucks less.
local function place_tree_if_free(pos, prev_result)
local nn = minetest.get_node(pos).name
if nn == "mcl_flowers:waterlily" or nn == "mcl_core:water_source" or nn == "mcl_core:water_flowing" or nn == "air" then
minetest.set_node(pos, {name="mcl_core:tree", param2=0})
return prev_result
else
return false
end
end
local offsets
if r == "0" then
offsets = {
{x=1, y=0, z=1},
{x=1, y=0, z=5},
{x=6, y=0, z=1},
{x=6, y=0, z=5},
}
elseif r == "180" then
offsets = {
{x=2, y=0, z=1},
{x=2, y=0, z=5},
{x=7, y=0, z=1},
{x=7, y=0, z=5},
}
elseif r == "270" then
offsets = {
{x=1, y=0, z=1},
{x=5, y=0, z=1},
{x=1, y=0, z=6},
{x=5, y=0, z=6},
}
elseif r == "90" then
offsets = {
{x=1, y=0, z=2},
{x=5, y=0, z=2},
{x=1, y=0, z=7},
{x=5, y=0, z=7},
}
end
for o=1, #offsets do
local ok = true
for y=place.y-1, place.y-64, -1 do
local tpos = vector.add(place, offsets[o])
tpos.y = y
ok = place_tree_if_free(tpos, ok)
if not ok then
break
end
end
end
end
end
end
end
-- Ice spikes in v6
-- In other mapgens, ice spikes are generated as decorations.
if mg_name == "v6" and not chunk_has_igloo and nn == "mcl_core:snowblock" then
local spike = pr:next(1,58000)
if spike < 3 then
-- Check surface
local floor = {x=p.x+4, y=p.y-1, z=p.z+4}
local surface = minetest.find_nodes_in_area({x=p.x+1,y=p.y-1,z=p.z+1}, floor, {"mcl_core:snowblock"})
-- Check for collision with spruce
local spruce_collisions = minetest.find_nodes_in_area({x=p.x+1,y=p.y+2,z=p.z+1}, {x=p.x+4, y=p.y+6, z=p.z+4}, {"mcl_core:sprucetree", "mcl_core:spruceleaves"})
if #surface >= 9 and #spruce_collisions == 0 then
mcl_structures.call_struct(p, "ice_spike_large", nil, pr)
end
elseif spike < 100 then
-- Check surface
local floor = {x=p.x+6, y=p.y-1, z=p.z+6}
local surface = minetest.find_nodes_in_area({x=p.x+1,y=p.y-1,z=p.z+1}, floor, {"mcl_core:snowblock", "mcl_core:dirt_with_grass_snow"})
-- Check for collision with spruce
local spruce_collisions = minetest.find_nodes_in_area({x=p.x+1,y=p.y+1,z=p.z+1}, {x=p.x+6, y=p.y+6, z=p.z+6}, {"mcl_core:sprucetree", "mcl_core:spruceleaves"})
if #surface >= 25 and #spruce_collisions == 0 then
mcl_structures.call_struct(p, "ice_spike_small", nil, pr)
end
end
end
end
end
end
end end
-- End exit portal
elseif minp.y <= END_EXIT_PORTAL_POS.y and maxp.y >= END_EXIT_PORTAL_POS.y and
minp.x <= END_EXIT_PORTAL_POS.x and maxp.x >= END_EXIT_PORTAL_POS.x and
minp.z <= END_EXIT_PORTAL_POS.z and maxp.z >= END_EXIT_PORTAL_POS.z then
for y=maxp.y, minp.y, -1 do
local p = {x=END_EXIT_PORTAL_POS.x, y=y, z=END_EXIT_PORTAL_POS.z}
if minetest.get_node(p).name == "mcl_end:end_stone" then
generate_end_exit_portal(p)
return
end
end
generate_end_exit_portal(END_EXIT_PORTAL_POS)
end
end
-- Buffers for LuaVoxelManip -- Buffers for LuaVoxelManip
-- local lvm_buffer = {} -- local lvm_buffer = {}
@ -1754,7 +1420,7 @@ local function generate_underground_mushrooms(minp, maxp, seed)
end end
local nether_wart_chance local nether_wart_chance
if mg_name == "v6" then if v6 then
nether_wart_chance = 85 nether_wart_chance = 85
else else
nether_wart_chance = 170 nether_wart_chance = 170
@ -1930,7 +1596,7 @@ local function basic_safe(vm_context)
lvm_used = set_layers(data, area, c_void , nil, mcl_mapgen.realm_barrier_overworld_end_max+1, mcl_mapgen.overworld.min -1, minp, maxp, lvm_used, pr) lvm_used = set_layers(data, area, c_void , nil, mcl_mapgen.realm_barrier_overworld_end_max+1, mcl_mapgen.overworld.min -1, minp, maxp, lvm_used, pr)
if mg_name ~= "singlenode" then if not singlenode then
-- Bedrock -- Bedrock
lvm_used = set_layers(data, area, c_bedrock, bedrock_check, mcl_mapgen.overworld.bedrock_min, mcl_mapgen.overworld.bedrock_max, minp, maxp, lvm_used, pr) lvm_used = set_layers(data, area, c_bedrock, bedrock_check, mcl_mapgen.overworld.bedrock_min, mcl_mapgen.overworld.bedrock_max, minp, maxp, lvm_used, pr)
lvm_used = set_layers(data, area, c_bedrock, bedrock_check, mcl_mapgen.nether.bedrock_bottom_min, mcl_mapgen.nether.bedrock_bottom_max, minp, maxp, lvm_used, pr) lvm_used = set_layers(data, area, c_bedrock, bedrock_check, mcl_mapgen.nether.bedrock_bottom_min, mcl_mapgen.nether.bedrock_bottom_max, minp, maxp, lvm_used, pr)
@ -1963,7 +1629,7 @@ local function basic_safe(vm_context)
-- A snowy grass block must be below a top snow or snow block at all times. -- A snowy grass block must be below a top snow or snow block at all times.
if minp.y <= mcl_mapgen.overworld.max and maxp.y >= mcl_mapgen.overworld.min then if minp.y <= mcl_mapgen.overworld.max and maxp.y >= mcl_mapgen.overworld.min then
-- v6 mapgen: -- v6 mapgen:
if mg_name == "v6" then if v6 then
--[[ Remove broken double plants caused by v6 weirdness. --[[ Remove broken double plants caused by v6 weirdness.
v6 might break the bottom part of double plants because of how it works. v6 might break the bottom part of double plants because of how it works.
@ -2028,7 +1694,7 @@ local function basic_safe(vm_context)
-- * Replace water with Nether lava. -- * Replace water with Nether lava.
-- * Replace stone, sand dirt in v6 so the Nether works in v6. -- * Replace stone, sand dirt in v6 so the Nether works in v6.
elseif emin.y <= mcl_mapgen.nether.max and emax.y >= mcl_mapgen.nether.min then elseif emin.y <= mcl_mapgen.nether.max and emax.y >= mcl_mapgen.nether.min then
if mg_name == "v6" then if v6 then
local nodes = minetest.find_nodes_in_area(emin, emax, {"mcl_core:water_source", "mcl_core:stone", "mcl_core:sand", "mcl_core:dirt"}) local nodes = minetest.find_nodes_in_area(emin, emax, {"mcl_core:water_source", "mcl_core:stone", "mcl_core:sand", "mcl_core:dirt"})
for n=1, #nodes do for n=1, #nodes do
local p_pos = area:index(nodes[n].x, nodes[n].y, nodes[n].z) local p_pos = area:index(nodes[n].x, nodes[n].y, nodes[n].z)
@ -2056,7 +1722,7 @@ local function basic_safe(vm_context)
-- * Generate spawn platform (End portal destination) -- * Generate spawn platform (End portal destination)
elseif minp.y <= mcl_mapgen.end_.max and maxp.y >= mcl_mapgen.end_.min then elseif minp.y <= mcl_mapgen.end_.max and maxp.y >= mcl_mapgen.end_.min then
local nodes local nodes
if mg_name == "v6" then if v6 then
nodes = minetest.find_nodes_in_area(emin, emax, {"mcl_core:water_source", "mcl_core:stone", "mcl_core:sand", "mcl_core:dirt"}) nodes = minetest.find_nodes_in_area(emin, emax, {"mcl_core:water_source", "mcl_core:stone", "mcl_core:sand", "mcl_core:dirt"})
else else
nodes = minetest.find_nodes_in_area(emin, emax, {"mcl_core:water_source"}) nodes = minetest.find_nodes_in_area(emin, emax, {"mcl_core:water_source"})
@ -2105,14 +1771,17 @@ local function basic_safe(vm_context)
lvm_used = true lvm_used = true
end end
if mg_name ~= "singlenode" then if not singlenode then
-- Generate special decorations -- Generate special decorations
generate_underground_mushrooms(minp, maxp, blockseed) generate_underground_mushrooms(minp, maxp, blockseed)
generate_nether_decorations(minp, maxp, blockseed) generate_nether_decorations(minp, maxp, blockseed)
generate_structures(minp, maxp, blockseed, biomemap)
end end
return vm_context --, lvm_used, shadow return vm_context --, lvm_used, shadow
end end
mcl_mapgen.register_mapgen_block_lvm(basic_safe, 1) mcl_mapgen.register_mapgen_block_lvm(basic_safe, 1)
local modpath = minetest.get_modpath(minetest.get_current_modname())
dofile(modpath .. "/clay.lua")
dofile(modpath .. "/structures.lua")

View File

@ -0,0 +1,314 @@
local END_EXIT_PORTAL_POS = vector.new(-3, -27003, -3) -- End exit portal position
local WITCH_HUT_HEIGHT = 3 -- Exact Y level to spawn witch huts at. This height refers to the height of the floor
local OVERWORLD_STRUCT_MIN, OVERWORLD_STRUCT_MAX = mcl_mapgen.overworld.min, mcl_mapgen.overworld.max
local END_STRUCT_MIN, END_STRUCT_MAX = mcl_mapgen.end_.min, mcl_mapgen.end_.max
local DIVLEN = 5
local V6 = mcl_mapgen.v6
local math_min, math_max = math.min, math.max
local math_floor = math.floor
local minetest_get_node = minetest.get_node
local minetest_get_mapgen_object = minetest.get_mapgen_object
local minetest_find_nodes_in_area = minetest.find_nodes_in_area
-- TODO: Try to use more efficient structure generating code
local function determine_ground_level(p, vm_context)
local emax = vm_context.emax
local emax_y = emax.y
local y = math_min(OVERWORLD_STRUCT_MAX, emax_y)
if y < emax_y then
y = y + 1
end
p.y = y
local checknode = minetest_get_node(p)
if checknode.name ~= "air" then
return
end
for y = y - 1, math_max(OVERWORLD_STRUCT_MIN, vm_context.emin.y), -1 do
p.y = y
local checknode = minetest_get_node(p)
if checknode then
local nn = checknode.name
local def = minetest.registered_nodes[nn]
if def and def.walkable then
return p, y, nn
end
end
end
end
-- Helper function for converting a MC probability to MT, with
-- regards to MapBlocks.
-- Some MC generated structures are generated on per-chunk
-- probability.
-- The MC probability is 1/x per Minecraft chunk (16×16).
-- x: The MC probability is 1/x.
-- minp, maxp: MapBlock limits
-- returns: Probability (1/return_value) for a single MT mapblock
local function minecraft_chunk_probability(x, minp, maxp)
-- 256 is the MC chunk height
return x * (((maxp.x-minp.x+1)*(maxp.z-minp.z+1)) / 256)
end
-- Takes x and z coordinates and minp and maxp of a generated chunk
-- (in on_generated callback) and returns a biomemap index)
-- Inverse function of biomemap_to_xz
local function xz_to_biomemap_index(x, z, minp, maxp)
local xwidth = maxp.x - minp.x + 1
local zwidth = maxp.z - minp.z + 1
local minix = x % xwidth
local miniz = z % zwidth
return (minix + miniz * zwidth) + 1
end
local chunk_has_desert_struct
local chunk_has_igloo
local function spawn_desert_temples_and_desert_wells(p, nn, pr, vm_context)
if chunk_has_desert_struct or p.y < 5 then return end
if nn ~= "mcl_core:sand" and nn ~= "mcl_core:sandstone" then return end
-- Spawn desert temple
if pr:next(1,12000) == 1 then
mcl_structures.call_struct(p, "desert_temple", nil, pr)
chunk_has_desert_struct = true
return true
end
-- Spawn desert well
local desert_well_prob = minecraft_chunk_probability(1000, vm_context.minp, vm_context.maxp)
if pr:next(1, desert_well_prob) ~= 1 then return end
-- Check surface
local surface = minetest_find_nodes_in_area({x=p.x,y=p.y-1,z=p.z}, {x=p.x+5, y=p.y-1, z=p.z+5}, "mcl_core:sand")
if #surface < 25 then return end
mcl_structures.call_struct(p, "desert_well", nil, pr)
chunk_has_desert_struct = true
return true
end
local function spawn_igloos(p, nn, pr)
if chunk_has_igloo then return end
if nn ~= "mcl_core:snowblock" and nn ~= "mcl_core:snow" and minetest.get_item_group(nn, "grass_block_snow") ~= 1 then return end
if pr:next(1, 4400) ~= 1 then return end
-- Check surface
local floor = {x=p.x+9, y=p.y-1, z=p.z+9}
local surface = minetest_find_nodes_in_area({x=p.x,y=p.y-1,z=p.z}, floor, "mcl_core:snowblock")
local surface2 = minetest_find_nodes_in_area({x=p.x,y=p.y-1,z=p.z}, floor, "mcl_core:dirt_with_grass_snow")
if #surface + #surface2 < 63 then return end
mcl_structures.call_struct(p, "igloo", nil, pr)
chunk_has_igloo = true
return true
end
local function spawn_fossil(p, nn, pr, vm_context)
if chunk_has_desert_struct or p.y > 4 then return end
if nn ~= "mcl_core:sandstone" and nn ~= "mcl_core:sand" then return end
local fossil_prob = minecraft_chunk_probability(64, vm_context.minp, vm_context.maxp)
if pr:next(1, fossil_prob) ~= 1 then return end
-- Spawn fossil below desert surface between layers 40 and 49
local p1 = {x=p.x, y=pr:next(mcl_worlds.layer_to_y(40), mcl_worlds.layer_to_y(49)), z=p.z}
-- Very rough check of the environment (we expect to have enough stonelike nodes).
-- Fossils may still appear partially exposed in caves, but this is O.K.
local p2 = vector.add(p1, 4)
local nodes = minetest_find_nodes_in_area(p1, p2, {"mcl_core:sandstone", "mcl_core:stone", "mcl_core:diorite", "mcl_core:andesite", "mcl_core:granite", "mcl_core:stone_with_coal", "mcl_core:dirt", "mcl_core:gravel"})
if #nodes < 100 then return end
-- >= 80%
mcl_structures.call_struct(p1, "fossil", nil, pr)
end
local witch_hut_offsets = {
["0"] = {
{x=1, y=0, z=1}, {x=1, y=0, z=5}, {x=6, y=0, z=1}, {x=6, y=0, z=5},
},
["180"] = {
{x=2, y=0, z=1}, {x=2, y=0, z=5}, {x=7, y=0, z=1}, {x=7, y=0, z=5},
},
["270"] = {
{x=1, y=0, z=1}, {x=5, y=0, z=1}, {x=1, y=0, z=6}, {x=5, y=0, z=6},
},
["90"] = {
{x=1, y=0, z=2}, {x=5, y=0, z=2}, {x=1, y=0, z=7}, {x=5, y=0, z=7},
},
}
local function spawn_witch_hut(p, nn, pr, vm_context)
if p.y <= 1 or nn ~= "mcl_core:dirt" then return end
local minp, maxp = vm_context.minp, vm_context.maxp
local prob = minecraft_chunk_probability(48, minp, maxp)
if pr:next(1, prob) ~= 1 then return end
-- Where do witches live?
if V6 then
-- v6: In Normal biome
if biomeinfo.get_v6_biome(p) ~= "Normal" then return end
else
-- Other mapgens: In swampland biome
local biomemap = vm_context.biomemap
if not biomemap then
vm_context.biomemap = vm_context.biomemap or minetest_get_mapgen_object('biomemap')
biomemap = vm_context.biomemap
end
local swampland = minetest.get_biome_id("Swampland")
local swampland_shore = minetest.get_biome_id("Swampland_shore")
local bi = xz_to_biomemap_index(p.x, p.z, minp, maxp)
if biomemap[bi] ~= swampland and biomemap[bi] ~= swampland_shore then return end
end
local r = tostring(pr:next(0, 3) * 90) -- "0", "90", "180" or 270"
local p1 = {x=p.x-1, y=WITCH_HUT_HEIGHT+2, z=p.z-1}
local size
if r == "0" or r == "180" then
size = {x=10, y=4, z=8}
else
size = {x=8, y=4, z=10}
end
local p2 = vector.add(p1, size)
-- This checks free space at the “body” of the hut and a bit around.
-- ALL nodes must be free for the placement to succeed.
local free_nodes = minetest_find_nodes_in_area(p1, p2, {"air", "mcl_core:water_source", "mcl_flowers:waterlily"})
if #free_nodes < ((size.x+1)*(size.y+1)*(size.z+1)) then return end
local place = {x=p.x, y=WITCH_HUT_HEIGHT-1, z=p.z}
-- FIXME: For some mysterious reason (black magic?) this
-- function does sometimes NOT spawn the witch hut. One can only see the
-- oak wood nodes in the water, but no hut. :-/
mcl_structures.call_struct(place, "witch_hut", r, pr)
-- TODO: Spawn witch in or around hut when the mob sucks less.
local function place_tree_if_free(pos, prev_result)
local nn = minetest.get_node(pos).name
if nn == "mcl_flowers:waterlily" or nn == "mcl_core:water_source" or nn == "mcl_core:water_flowing" or nn == "air" then
minetest.set_node(pos, {name="mcl_core:tree", param2=0})
return prev_result
else
return false
end
end
local offsets = witch_hut_offsets[r]
for o=1, #offsets do
local ok = true
for y=place.y-1, place.y-64, -1 do
local tpos = vector.add(place, offsets[o])
tpos.y = y
ok = place_tree_if_free(tpos, ok)
if not ok then
break
end
end
end
end
-- TODO: Check spikes sizes, it looks like we have to swap them:
local function spawn_ice_spike_large(p, pr)
-- Check surface
local floor = {x=p.x+4, y=p.y-1, z=p.z+4}
local surface = minetest_find_nodes_in_area({x=p.x+1,y=p.y-1,z=p.z+1}, floor, {"mcl_core:snowblock"})
if #surface < 9 then return end
-- Check for collision with spruce
local spruce_collisions = minetest_find_nodes_in_area({x=p.x+1,y=p.y+2,z=p.z+1}, {x=p.x+4, y=p.y+6, z=p.z+4}, {"mcl_core:sprucetree", "mcl_core:spruceleaves"})
if #spruce_collisions > 0 then return end
mcl_structures.call_struct(p, "ice_spike_large", nil, pr)
return true
end
local function spawn_ice_spike_small(p, pr)
-- Check surface
local floor = {x=p.x+6, y=p.y-1, z=p.z+6}
local surface = minetest_find_nodes_in_area({x=p.x+1,y=p.y-1,z=p.z+1}, floor, {"mcl_core:snowblock", "mcl_core:dirt_with_grass_snow"})
if #surface < 25 then return end
-- Check for collision with spruce
local spruce_collisions = minetest_find_nodes_in_area({x=p.x+1,y=p.y+1,z=p.z+1}, {x=p.x+6, y=p.y+6, z=p.z+6}, {"mcl_core:sprucetree", "mcl_core:spruceleaves"})
if #spruce_collisions > 0 then return end
mcl_structures.call_struct(p, "ice_spike_small", nil, pr)
return true
end
local function spawn_spikes_in_v6(p, nn, pr)
-- In other mapgens, ice spikes are generated as decorations.
if chunk_has_igloo or nn ~= "mcl_core:snowblock" then return end
local spike = pr:next(1,58000)
if spike < 3 then
return spawn_ice_spike_large(p, pr)
elseif spike < 100 then
return spawn_ice_spike_small(p, pr)
end
end
local function generate_structures(vm_context)
local pr = PcgRandom(vm_context.blockseed)
perlin_structures = perlin_structures or minetest.get_perlin(329, 3, 0.6, 100)
chunk_has_desert_struct = false
chunk_has_igloo = false
local minp, maxp = vm_context.minp, vm_context.maxp
-- Assume X and Z lengths are equal
for x0 = minp.x, maxp.x, DIVLEN do for z0 = minp.z, maxp.z, DIVLEN do
-- Determine amount from perlin noise
local amount = math_floor(perlin_structures:get_2d({x=x0, y=z0}) * 9)
-- Find random positions based on this random
local p, ground_y
for i=0, amount do
p = {x = pr:next(x0, x0 + DIVLEN - 1), y = 0, z = pr:next(z0, z0 + DIVLEN - 1)}
p, ground_y, nn = determine_ground_level(p, vm_context)
if ground_y then
p.y = ground_y + 1
local nn0 = minetest.get_node(p).name
-- Check if the node can be replaced
if minetest.registered_nodes[nn0] and minetest.registered_nodes[nn0].buildable_to then
if not spawn_desert_temples_and_desert_wells(p, nn, pr, vm_context) then
spawn_igloos(p, nn, pr, vm_context)
end
spawn_fossil(p, nn, pr, vm_context)
spawn_witch_hut(p, nn, pr, vm_context)
if V6 then
spawn_spikes_in_v6(p, nn, pr, vm_context)
end
end
end
end
end end
return vm_context
end
local function generate_end_structures(vm_context)
local minp, maxp = vm_context.minp, vm_context.maxp
if minp.y <= END_EXIT_PORTAL_POS.y and maxp.y >= END_EXIT_PORTAL_POS.y
and minp.x <= END_EXIT_PORTAL_POS.x and maxp.x >= END_EXIT_PORTAL_POS.x
and minp.z <= END_EXIT_PORTAL_POS.z and maxp.z >= END_EXIT_PORTAL_POS.z
then
local p = {x=END_EXIT_PORTAL_POS.x, z=END_EXIT_PORTAL_POS.z}
for y = maxp.y, minp.y, -1 do
p.y = y
if minetest.get_node(p).name == "mcl_end:end_stone" then
mcl_mapgen_core.generate_end_exit_portal(p)
break
end
end
end
return vm_context
end
if not mcl_mapgen.singlenode then
mcl_mapgen.register_on_generated(function(vm_context)
local minp, maxp = vm_context.minp, vm_context.maxp
local minp_y, maxp_y = minp.y, maxp.y
if maxp_y >= OVERWORLD_STRUCT_MIN and minp_y <= OVERWORLD_STRUCT_MAX then
return generate_structures(vm_context)
-- End exit portal
elseif maxp_y >= END_STRUCT_MIN and minp_y <= END_STRUCT_MAX then
return generate_end_structures(vm_context)
end
return vm_context
end)
end