From 726159e796811fa303ab9e40218ef8dd260dc493 Mon Sep 17 00:00:00 2001 From: kay27 Date: Fri, 7 Jan 2022 06:24:11 +0400 Subject: [PATCH] Dedicate clay and structures from mapgen core --- mods/CORE/mcl_mapgen/API.md | 8 +- mods/CORE/mcl_mapgen/init.lua | 6 +- mods/MAPGEN/mcl_dungeons/init.lua | 33 +- mods/MAPGEN/mcl_mapgen_core/clay.lua | 58 ++++ mods/MAPGEN/mcl_mapgen_core/init.lua | 365 +-------------------- mods/MAPGEN/mcl_mapgen_core/structures.lua | 314 ++++++++++++++++++ 6 files changed, 400 insertions(+), 384 deletions(-) create mode 100644 mods/MAPGEN/mcl_mapgen_core/clay.lua create mode 100644 mods/MAPGEN/mcl_mapgen_core/structures.lua diff --git a/mods/CORE/mcl_mapgen/API.md b/mods/CORE/mcl_mapgen/API.md index 75307f321..1587e19f4 100644 --- a/mods/CORE/mcl_mapgen/API.md +++ b/mods/CORE/mcl_mapgen/API.md @@ -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) ========================================================================= -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. 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. See https://git.minetest.land/MineClone2/MineClone2/issues/1395 `lvm_callback_function`: chunk callback LVM function definition: `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`: curent voxel manipulator object itself; `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) ============================================================================= 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; `order_number` (optional): the less, the earlier, 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. 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. -`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; `order_number` (optional): the less, the earlier. diff --git a/mods/CORE/mcl_mapgen/init.lua b/mods/CORE/mcl_mapgen/init.lua index d2224075b..16271b4d2 100644 --- a/mods/CORE/mcl_mapgen/init.lua +++ b/mods/CORE/mcl_mapgen/init.lua @@ -241,7 +241,7 @@ minetest.register_on_generated(function(minp, maxp, chunkseed) 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} for _, v in pairs(queue_blocks_lvm) do - vm_context = v.callback_function(vm_context) + v.callback_function(vm_context) end end if nodes_block > 0 then @@ -264,7 +264,7 @@ minetest.register_on_generated(function(minp, maxp, chunkseed) if #queue_unsafe_engine > 0 then for _, v in pairs(queue_unsafe_engine) do - vm_context = v.f(vm_context) + v.f(vm_context) end if vm_context.write then vm:set_data(data) @@ -304,7 +304,7 @@ minetest.register_on_generated(function(minp, maxp, chunkseed) chunkseed = seed, } for _, v in pairs(queue_chunks_lvm) do - v.f(vm_context) + vm_context = v.f(vm_context) end for _, v in pairs(queue_chunks_nodes) do v.f(minp, maxp, seed, vm_context) diff --git a/mods/MAPGEN/mcl_dungeons/init.lua b/mods/MAPGEN/mcl_dungeons/init.lua index eb802d850..c3e406ca1 100644 --- a/mods/MAPGEN/mcl_dungeons/init.lua +++ b/mods/MAPGEN/mcl_dungeons/init.lua @@ -110,7 +110,7 @@ local loottable = } -- Bonus loot for v6 mapgen: Otherwise unobtainable saplings. -if mg_name == "v6" then +if mcl_mapgen.v6 then table.insert(loottable, { stacks_min = 1, stacks_max = 3, @@ -137,36 +137,11 @@ local function spawn_dungeon(p1, p2, dim, pr, dontcheck) local y_ceiling = y + dim.y + 1 if check then - local result1, result2 = true, true local dim_x, dim_z = dim.x, dim.z 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 - 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 - 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") + 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 + return end end diff --git a/mods/MAPGEN/mcl_mapgen_core/clay.lua b/mods/MAPGEN/mcl_mapgen_core/clay.lua new file mode 100644 index 000000000..ea44dc7ec --- /dev/null +++ b/mods/MAPGEN/mcl_mapgen_core/clay.lua @@ -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) diff --git a/mods/MAPGEN/mcl_mapgen_core/init.lua b/mods/MAPGEN/mcl_mapgen_core/init.lua index b1e9d8e09..69841cc62 100644 --- a/mods/MAPGEN/mcl_mapgen_core/init.lua +++ b/mods/MAPGEN/mcl_mapgen_core/init.lua @@ -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_desert_stone", "mcl_stairs:stair_sandstone") -local mg_name = minetest.get_mapgen_setting("mg_name") -local superflat = mg_name == "flat" and minetest.get_mapgen_setting("mcl_superflat_classic") == "true" - -local WITCH_HUT_HEIGHT = 3 -- Exact Y level to spawn witch huts at. This height refers to the height of the floor - --- End exit portal position -local END_EXIT_PORTAL_POS = vector.new(-3, -27003, -3) +local mg_name = mcl_mapgen.name +local superflat = mcl_mapgen.superflat +local v6 = mcl_mapgen.v6 +local singlenode = mcl_mapgen.singlenode -- Content IDs 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 -- - if mg_name == "v6" then + if v6 then -- Generate everywhere in v6, but rarely. -- Common spawn @@ -1104,7 +1101,7 @@ mcl_vars.mg_dungeons = mcl_mapgen.dungeons mg_flags.dungeons = false -- Apply mapgen-specific mapgen code -if mg_name == "v6" then +if v6 then register_mgv6_decorations() elseif superflat then -- Enforce superflat-like mapgen: no caves, decor, lakes and hills @@ -1125,20 +1122,6 @@ if string.len(mg_flags_str) > 0 then end 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), -- minp and maxp (from an on_generated callback) and returns the real world coordinates -- as X, Z. @@ -1151,82 +1134,11 @@ end return x, z 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 local perlin_structures local perlin_vines, perlin_vines_fine, perlin_vines_upwards, perlin_vines_length, perlin_vines_density 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_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) 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 dragon_spawn_pos = vector.add(pos, vector.new(3, 11, 3)) 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 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 -- local lvm_buffer = {} @@ -1754,7 +1420,7 @@ local function generate_underground_mushrooms(minp, maxp, seed) end local nether_wart_chance -if mg_name == "v6" then +if v6 then nether_wart_chance = 85 else 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) - if mg_name ~= "singlenode" then + if not singlenode then -- 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.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. if minp.y <= mcl_mapgen.overworld.max and maxp.y >= mcl_mapgen.overworld.min then -- v6 mapgen: - if mg_name == "v6" then + if v6 then --[[ Remove broken double plants caused by v6 weirdness. 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 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 - 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"}) for n=1, #nodes do 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) elseif minp.y <= mcl_mapgen.end_.max and maxp.y >= mcl_mapgen.end_.min then 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"}) else 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 end - if mg_name ~= "singlenode" then + if not singlenode then -- Generate special decorations generate_underground_mushrooms(minp, maxp, blockseed) generate_nether_decorations(minp, maxp, blockseed) - generate_structures(minp, maxp, blockseed, biomemap) end return vm_context --, lvm_used, shadow end 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") diff --git a/mods/MAPGEN/mcl_mapgen_core/structures.lua b/mods/MAPGEN/mcl_mapgen_core/structures.lua new file mode 100644 index 000000000..526060319 --- /dev/null +++ b/mods/MAPGEN/mcl_mapgen_core/structures.lua @@ -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