123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839 |
- --[[
- Nether mod for minetest
- Copyright (C) 2013 PilzAdam
- Permission to use, copy, modify, and/or distribute this software for
- any purpose with or without fee is hereby granted, provided that the
- above copyright notice and this permission notice appear in all copies.
- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
- WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
- WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR
- BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES
- OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
- WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
- ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
- SOFTWARE.
- ]]--
- -- Parameters
- local NETHER_CEILING = nether.DEPTH_CEILING
- local NETHER_FLOOR = nether.DEPTH_FLOOR
- local TCAVE = 0.6
- local BLEND = 128
- -- Stuff
- local math_max, math_min, math_abs, math_floor = math.max, math.min, math.abs, math.floor -- avoid needing table lookups each time a common math function is invoked
- if minetest.read_schematic == nil then
- -- Using biomes to create the Nether requires the ability for biomes to set "node_cave_liquid = air".
- -- This feature was introduced by paramat in b1b40fef1 on 2019-05-19, but we can't test for
- -- it directly. However b2065756c was merged a few months later (in 2019-08-14) and it is easy
- -- to directly test for - it adds minetest.read_schematic() - so we use this as a proxy-test
- -- for whether the Minetest engine is recent enough to have implemented node_cave_liquid=air
- error("This " .. nether.modname .. " mapgen requires Minetest v5.1 or greater, use mapgen_nobiomes.lua instead.", 0)
- end
- local function override_underground_biomes()
- -- https://forum.minetest.net/viewtopic.php?p=257522#p257522
- -- Q: Is there a way to override an already-registered biome so I can get it out of the
- -- way of my own underground biomes without disturbing the other biomes registered by
- -- default?
- -- A: No, all you can do is use a mod to clear all biomes then re-register the complete
- -- set but with your changes. It has been described as hacky but this is actually the
- -- official way to alter biomes, most mods and subgames would want to completely change
- -- all biomes anyway.
- -- To avoid the engine side of mapgen becoming overcomplex the approach is to require mods
- -- to do slightly more complex stuff in Lua.
- -- take a copy of all biomes, decorations, and ores. Regregistering a biome changes its ID, so
- -- any decorations or ores using the 'biomes' field must afterwards be cleared and re-registered.
- -- https://github.com/minetest/minetest/issues/9288
- local registered_biomes_copy = {}
- local registered_decorations_copy = {}
- local registered_ores_copy = {}
- for old_biome_key, old_biome_def in pairs(minetest.registered_biomes) do
- registered_biomes_copy[old_biome_key] = old_biome_def
- end
- for old_decoration_key, old_decoration_def in pairs(minetest.registered_decorations) do
- registered_decorations_copy[old_decoration_key] = old_decoration_def
- end
- for old_ore_key, old_ore_def in pairs(minetest.registered_ores) do
- registered_ores_copy[old_ore_key] = old_ore_def
- end
- -- clear biomes, decorations, and ores
- minetest.clear_registered_decorations()
- minetest.clear_registered_ores()
- minetest.clear_registered_biomes()
- -- Restore biomes, adjusted to not overlap the Nether
- for biome_key, new_biome_def in pairs(registered_biomes_copy) do
- local biome_y_max, biome_y_min = tonumber(new_biome_def.y_max), tonumber(new_biome_def.y_min)
- if biome_y_max > NETHER_FLOOR and biome_y_min < NETHER_CEILING then
- -- This biome occupies some or all of the depth of the Nether, shift/crop it.
- local spaceOccupiedAbove = biome_y_max - NETHER_CEILING
- local spaceOccupiedBelow = NETHER_FLOOR - biome_y_min
- if spaceOccupiedAbove >= spaceOccupiedBelow or biome_y_min <= -30000 then
- -- place the biome above the Nether
- -- We also shift biomes which extend to the bottom of the map above the Nether, since they
- -- likely only extend that deep as a catch-all, and probably have a role nearer the surface.
- new_biome_def.y_min = NETHER_CEILING + 1
- new_biome_def.y_max = math_max(biome_y_max, NETHER_CEILING + 2)
- else
- -- shift the biome to below the Nether
- new_biome_def.y_max = NETHER_FLOOR - 1
- new_biome_def.y_min = math_min(biome_y_min, NETHER_CEILING - 2)
- end
- end
- minetest.register_biome(new_biome_def)
- end
- -- Restore biome decorations
- for decoration_key, new_decoration_def in pairs(registered_decorations_copy) do
- minetest.register_decoration(new_decoration_def)
- end
- -- Restore biome ores
- for ore_key, new_ore_def in pairs(registered_ores_copy) do
- minetest.register_ore(new_ore_def)
- end
- end
- -- Shift any overlapping biomes out of the way before we create the Nether biomes
- override_underground_biomes()
- -- nether:native_mapgen is used to prevent ores and decorations being generated according
- -- to landforms created by the native mapgen.
- -- Ores and decorations are registered against "nether:rack" instead, and the lua
- -- on_generate() callback will carve the Nether with nether:rack before invoking
- -- generate_decorations and generate_ores.
- minetest.register_node("nether:native_mapgen", {})
- minetest.register_biome({
- name = "nether_caverns",
- node_stone = "nether:native_mapgen", -- nether:native_mapgen is used here to prevent the native mapgen from placing ores and decorations.
- node_filler = "nether:native_mapgen", -- The lua on_generate will transform nether:rack_native into nether:rack then decorate and add ores.
- node_dungeon = "nether:brick",
- node_dungeon_alt = "nether:brick_cracked",
- node_dungeon_stair = "stairs:stair_nether_brick",
- -- Setting node_cave_liquid to "air" avoids the need to filter lava and water out of the mapchunk and
- -- surrounding shell (overdraw nodes beyond the mapchunk).
- -- This feature was introduced by paramat in b1b40fef1 on 2019-05-19, and this mapgen.lua file should only
- -- be run if the Minetest version includes it. The earliest tag made after 2019-05-19 is 5.1.0 on 2019-10-13,
- -- however we shouldn't test version numbers. minetest.read_schematic() was added by b2065756c and merged in
- -- 2019-08-14 and is easy to test for, we don't use it but it should make a good proxy-test for whether the
- -- Minetest version is recent enough to have implemented node_cave_liquid=air
- node_cave_liquid = "air",
- y_max = NETHER_CEILING,
- y_min = NETHER_FLOOR,
- vertical_blend = 0,
- heat_point = 50,
- humidity_point = 50,
- })
- -- Ores and decorations
- dofile(nether.path .. "/mapgen_decorations.lua")
- minetest.register_ore({
- ore_type = "scatter",
- ore = "nether:glowstone",
- wherein = "nether:rack",
- clust_scarcity = 11 * 11 * 11,
- clust_num_ores = 3,
- clust_size = 2,
- y_max = NETHER_CEILING,
- y_min = NETHER_FLOOR,
- })
- minetest.register_ore({
- ore_type = "scatter",
- ore = "nether:lava_crust",
- wherein = "nether:basalt",
- clust_scarcity = 16 * 16 * 16,
- clust_num_ores = 3,
- clust_size = 2,
- y_max = NETHER_CEILING,
- y_min = NETHER_FLOOR,
- })
- minetest.register_ore({
- ore_type = "scatter",
- ore = "default:lava_source",
- wherein = {"nether:rack", "nether:basalt"},
- clust_scarcity = 32 * 32 * 32,
- clust_num_ores = 4,
- clust_size = 2,
- y_max = NETHER_CEILING,
- y_min = NETHER_FLOOR,
- })
- minetest.register_ore({
- ore_type = "scatter",
- ore = "epic:stone_with_titanium",
- wherein = {"nether:rack"},
- clust_scarcity = 20 * 20 * 20,
- clust_num_ores = 6,
- clust_size = 2,
- y_max = NETHER_CEILING,
- y_min = NETHER_FLOOR,
- })
- minetest.register_ore({
- ore_type = "scatter",
- ore = "epic:nether_with_gold",
- wherein = {"nether:rack"},
- clust_scarcity = 16 * 16 * 16,
- clust_num_ores = 12,
- clust_size = 4,
- y_max = NETHER_CEILING,
- y_min = NETHER_FLOOR,
- })
- minetest.register_ore({
- ore_type = "scatter",
- ore = "epic:nether_with_diamond",
- wherein = {"nether:rack"},
- clust_scarcity = 17 * 17 * 17,
- clust_num_ores = 12,
- clust_size = 4,
- y_max = NETHER_CEILING,
- y_min = NETHER_FLOOR,
- })
- minetest.register_ore({
- ore_type = "blob",
- ore = "nether:sand",
- wherein = "nether:rack",
- clust_scarcity = 14 * 14 * 14,
- clust_size = 8,
- y_max = NETHER_CEILING,
- y_min = NETHER_FLOOR
- })
- -- Mapgen
- -- 3D noise
- local np_cave = {
- offset = 0,
- scale = 1,
- spread = {x = 384, y = 128, z = 384}, -- squashed 3:1
- seed = 59033,
- octaves = 5,
- persist = 0.7,
- lacunarity = 2.0,
- --flags = ""
- }
- -- Buffers and objects we shouldn't recreate every on_generate
- local nobj_cave = nil
- local nbuf_cave = {}
- local dbuf = {}
- local yblmin = NETHER_FLOOR + BLEND * 2
- local yblmax = NETHER_CEILING - BLEND * 2
- -- Content ids
- local c_air = minetest.get_content_id("air")
- local c_netherrack = minetest.get_content_id("nether:rack")
- local c_dungeonbrick = minetest.get_content_id("nether:brick")
- local c_dungeonbrick_alt = minetest.get_content_id("nether:brick_cracked")
- local c_netherbrick_slab = minetest.get_content_id("stairs:slab_nether_brick")
- local c_netherfence = minetest.get_content_id("nether:fence_nether_brick")
- local c_glowstone = minetest.get_content_id("nether:glowstone")
- local c_lava_source = minetest.get_content_id("default:lava_source")
- local c_lavasea_source = minetest.get_content_id("nether:lava_source") -- same as lava but with staggered animation to look better as an ocean
- local c_lava_crust = minetest.get_content_id("nether:lava_crust")
- local c_basalt = minetest.get_content_id("nether:basalt")
- local c_native_mapgen = minetest.get_content_id("nether:native_mapgen")
- local c_debug = minetest.get_content_id("default:glass")
- -- Dungeon excavation functions
- function is_dungeon_brick(node_id)
- return node_id == c_dungeonbrick or node_id == c_dungeonbrick_alt
- end
- function build_dungeon_room_list(data, area)
- local result = {}
- -- Unfortunately gennotify only returns dungeon rooms, not corridors.
- -- We don't need to check for temples because only dungeons are generated in biomes
- -- that define their own dungeon nodes.
- local gennotify = minetest.get_mapgen_object("gennotify")
- local roomLocations = gennotify["dungeon"] or {}
- -- Excavation should still know to stop if a cave or corridor has removed the dungeon wall.
- -- See MapgenBasic::generateDungeons in mapgen.cpp for max room sizes.
- local maxRoomSize = 18
- local maxRoomRadius = math.ceil(maxRoomSize / 2)
- local xStride, yStride, zStride = 1, area.ystride, area.zstride
- local minEdge, maxEdge = area.MinEdge, area.MaxEdge
- for _, roomPos in ipairs(roomLocations) do
- if area:containsp(roomPos) then -- this safety check does not appear to be necessary, but lets make it explicit
- local room_vi = area:indexp(roomPos)
- --data[room_vi] = minetest.get_content_id("default:torch") -- debug
- local startPos = vector.new(roomPos)
- if roomPos.y + 1 <= maxEdge.y and data[room_vi + yStride] == c_air then
- -- The roomPos coords given by gennotify are at floor level, but whenever possible we
- -- want to be performing searches a node higher than floor level to avoids dungeon chests.
- startPos.y = startPos.y + 1
- room_vi = area:indexp(startPos)
- end
- local bound_min_x = math_max(minEdge.x, roomPos.x - maxRoomRadius)
- local bound_min_y = math_max(minEdge.y, roomPos.y - 1) -- room coords given by gennotify are on the floor
- local bound_min_z = math_max(minEdge.z, roomPos.z - maxRoomRadius)
- local bound_max_x = math_min(maxEdge.x, roomPos.x + maxRoomRadius)
- local bound_max_y = math_min(maxEdge.y, roomPos.y + maxRoomSize) -- room coords given by gennotify are on the floor
- local bound_max_z = math_min(maxEdge.z, roomPos.z + maxRoomRadius)
- local room_min = vector.new(startPos)
- local room_max = vector.new(startPos)
- local vi = room_vi
- while room_max.y < bound_max_y and data[vi + yStride] == c_air do
- room_max.y = room_max.y + 1
- vi = vi + yStride
- end
- vi = room_vi
- while room_min.y > bound_min_y and data[vi - yStride] == c_air do
- room_min.y = room_min.y - 1
- vi = vi - yStride
- end
- vi = room_vi
- while room_max.z < bound_max_z and data[vi + zStride] == c_air do
- room_max.z = room_max.z + 1
- vi = vi + zStride
- end
- vi = room_vi
- while room_min.z > bound_min_z and data[vi - zStride] == c_air do
- room_min.z = room_min.z - 1
- vi = vi - zStride
- end
- vi = room_vi
- while room_max.x < bound_max_x and data[vi + xStride] == c_air do
- room_max.x = room_max.x + 1
- vi = vi + xStride
- end
- vi = room_vi
- while room_min.x > bound_min_x and data[vi - xStride] == c_air do
- room_min.x = room_min.x - 1
- vi = vi - xStride
- end
- local roomInfo = vector.new(roomPos)
- roomInfo.minp = room_min
- roomInfo.maxp = room_max
- result[#result + 1] = roomInfo
- end
- end
- return result;
- end
- -- Only partially excavates dungeons, the rest is left as an exercise for the player ;)
- -- (Corridors and the parts of rooms which extend beyond the emerge boundary will remain filled)
- function excavate_dungeons(data, area, rooms)
- local vi, node_id
- -- any air from the native mapgen has been replaced by netherrack, but
- -- we don't want this inside dungeons, so fill dungeon rooms with air
- for _, roomInfo in ipairs(rooms) do
- local room_min = roomInfo.minp
- local room_max = roomInfo.maxp
- for z = room_min.z, room_max.z do
- for y = room_min.y, room_max.y do
- vi = area:index(room_min.x, y, z)
- for x = room_min.x, room_max.x do
- node_id = data[vi]
- if node_id == c_netherrack or node_id == c_basalt then data[vi] = c_air end
- vi = vi + 1
- end
- end
- end
- end
- end
- -- Since we already know where all the rooms and their walls are, and have all the nodes stored
- -- in a voxelmanip already, we may as well add a little Nether flair to the dungeons found here.
- function decorate_dungeons(data, area, rooms)
- local xStride, yStride, zStride = 1, area.ystride, area.zstride
- local minEdge, maxEdge = area.MinEdge, area.MaxEdge
- for _, roomInfo in ipairs(rooms) do
- local room_min, room_max = roomInfo.minp, roomInfo.maxp
- local room_size = vector.distance(room_min, room_max)
- if room_size > 10 then
- local room_seed = roomInfo.x + 3 * roomInfo.z + 13 * roomInfo.y
- local window_y = roomInfo.y + math_min(2, room_max.y - roomInfo.y - 1)
- if room_seed % 3 == 0 and room_max.y < maxEdge.y then
- -- Glowstone chandelier (feel free to replace with a fancy schematic)
- local vi = area:index(roomInfo.x, room_max.y + 1, roomInfo.z)
- if is_dungeon_brick(data[vi]) then data[vi] = c_glowstone end
- elseif room_seed % 4 == 0 and room_min.y > minEdge.y
- and room_min.x > minEdge.x and room_max.x < maxEdge.x
- and room_min.z > minEdge.z and room_max.z < maxEdge.z then
- -- lava well (feel free to replace with a fancy schematic)
- local vi = area:index(roomInfo.x, room_min.y, roomInfo.z)
- if is_dungeon_brick(data[vi - yStride]) then
- data[vi - yStride] = c_lava_source
- if data[vi - zStride] == c_air then data[vi - zStride] = c_netherbrick_slab end
- if data[vi + zStride] == c_air then data[vi + zStride] = c_netherbrick_slab end
- if data[vi - xStride] == c_air then data[vi - xStride] = c_netherbrick_slab end
- if data[vi + xStride] == c_air then data[vi + xStride] = c_netherbrick_slab end
- end
- end
- -- Barred windows
- if room_seed % 7 < 5 and room_max.x - room_min.x >= 4 and room_max.z - room_min.z >= 4
- and window_y >= minEdge.y and window_y + 1 <= maxEdge.y
- and room_min.x > minEdge.x and room_max.x < maxEdge.x
- and room_min.z > minEdge.z and room_max.z < maxEdge.z then
- --data[area:indexp(roomInfo)] = minetest.get_content_id("default:mese_post_light") -- debug
- -- Until whisper glass is added, every window will be made of netherbrick fence (rather
- -- than material depending on room_seed)
- local window_node = c_netherfence
- local vi_min = area:index(room_min.x - 1, window_y, roomInfo.z)
- local vi_max = area:index(room_max.x + 1, window_y, roomInfo.z)
- local locations = {-zStride, zStride, -zStride + yStride, zStride + yStride}
- for _, offset in ipairs(locations) do
- if is_dungeon_brick(data[vi_min + offset]) then data[vi_min + offset] = window_node end
- if is_dungeon_brick(data[vi_max + offset]) then data[vi_max + offset] = window_node end
- end
- vi_min = area:index(roomInfo.x, window_y, room_min.z - 1)
- vi_max = area:index(roomInfo.x, window_y, room_max.z + 1)
- locations = {-xStride, xStride, -xStride + yStride, xStride + yStride}
- for _, offset in ipairs(locations) do
- if is_dungeon_brick(data[vi_min + offset]) then data[vi_min + offset] = window_node end
- if is_dungeon_brick(data[vi_max + offset]) then data[vi_max + offset] = window_node end
- end
- end
- -- Weeds on the floor once Nether weeds are added
- end
- end
- end
- local REGION_BUFFER_THICKNESS = 0.2
- local CENTER_REGION_LIMIT = TCAVE - REGION_BUFFER_THICKNESS
- local BASALT_LIMIT = CENTER_REGION_LIMIT - 0.1
- local SURFACE_CRUST_LIMIT = BASALT_LIMIT * 0.25
- local CRUST_LIMIT = BASALT_LIMIT * 0.85
- -- Returns (absolute height, fractional distance from ceiling or sea floor)
- -- the fractional distance from ceiling or sea floor is a value between 0 and 1 (inclusive)
- -- Note it may find the most relevent sea-level - not necesssarily the one you are closest
- -- to, since the space above the sea reaches much higher than the depth below the sea.
- local function find_nearest_lava_sealevel(y)
- -- todo: put oceans near the bottom of chunks to improve ability to generate tunnels to the center
- -- todo: constrain y to be not near the bounds of the nether
- -- todo: add some random adj at each level, seeded only by the level height
- local sealevel = math.floor((y + 100) / 200) * 200
- local cavern_limits_fraction
- local height_above_sea = y - sealevel
- if height_above_sea >= 0 then
- cavern_limits_fraction = math_min(1, height_above_sea / 95)
- else
- -- approaches 1 much faster as the lava sea is shallower than the cavern above it
- cavern_limits_fraction = math_min(1, -height_above_sea / 40)
- end
- return sealevel, cavern_limits_fraction
- end
- local caveperlin
- minetest.register_chatcommand("whereami",
- {
- description = "Describes which region of the nether the player is in",
- func = function(name, param)
- local player = minetest.get_player_by_name(name)
- if player == nil then return false, "Unknown player position" end
- local pos = vector.round(player:get_pos())
- if pos.y > NETHER_CEILING or pos.y < NETHER_FLOOR then
- return true, "The Overworld"
- end
- caveperlin = caveperlin or minetest.get_perlin(np_cave)
- local densityNoise = caveperlin:get_3d(pos)
- local sea_level, cavern_limit_distance = find_nearest_lava_sealevel(pos.y)
- local desc
- if densityNoise > 0.6 then
- desc = "Positive nether"
- elseif densityNoise < -0.6 then
- desc = "Negative nether"
- elseif math_abs(densityNoise) < CENTER_REGION_LIMIT then
- desc = "Nether Core"
- local cavern_noise_adj = CENTER_REGION_LIMIT * (cavern_limit_distance * cavern_limit_distance * cavern_limit_distance)
- if math_abs(densityNoise) + cavern_noise_adj < BASALT_LIMIT then
- desc = desc .. " inside cavern"
- else
- desc = desc .. " but outside cavern"
- end
- elseif densityNoise > 0 then
- desc = "Shell between positive nether and center region"
- else
- desc = "Shell between negative nether and center region"
- end
- local sea_pos = pos.y - sea_level
- if sea_pos > 0 then
- desc = desc .. ", " .. sea_pos .. "m above lava-sea level"
- else
- desc = desc .. ", " .. sea_pos .. "m below lava-sea level"
- end
- return true, "[Perlin " .. (math_floor(densityNoise * 1000) / 1000) .. "] " .. desc
- end
- }
- )
- function draw_pathway(data, area, nether_pos, center_pos)
- local dist = math_floor(vector.distance(nether_pos, center_pos))
- local step = vector.subtract(center_pos, nether_pos)
- local ystride = area.ystride
- local zstride = area.zstride
- -- first pass: record path details
- local linedata = {}
- local last_pos = {}
- local line_index = 1
- local first_filled_index, boundary_index, last_filled_index
- for i = 0, dist do
- pos = vector.round(vector.add(nether_pos, vector.multiply(step, i / dist))) -- bresenham's line would be good here, but too much lua code
- if not vector.equals(pos, last_pos) then
- local vi = area:indexp(pos)
- local node_id = data[vi]
- linedata[line_index] = {
- pos = pos,
- vi = vi,
- node_id = node_id
- }
- if boundary_index == nil and node_id == c_basalt then boundary_index = line_index end
- if node_id == c_air then
- if boundary_index ~= nil and last_filled_index == nil then last_filled_index = line_index end
- else
- if first_filled_index == nil then first_filled_index = line_index end
- end
- line_index = line_index + 1
- last_pos = pos
- end
- end
- first_filled_index = first_filled_index or 1
- last_filled_index = last_filled_index or #linedata
- boundary_index = boundary_index or last_filled_index
- --minetest.chat_send_all((last_filled_index - first_filled_index) .. " instead of " .. dist .. ", with steps reduced to " .. #linedata)
- -- second pass: excavate
- -- excavation radius should be limited to half the sampling distance so we don't end up
- -- exceeding minp-maxp and having excavation filled in when the next chunk is generated.
- local start_index, stop_index = math_max(1, first_filled_index - 4), math_min(#linedata, last_filled_index + 4)
- for i = start_index, stop_index do
- local vi = linedata[i].vi
- local radius_squared = 26.5 - 3 * (i / (stop_index - start_index + 1))
- for x = -5, 5 do
- for y = -5, 5 do
- if x * x + y * y < radius_squared then
- data[vi + y * ystride + x] = c_air
- data[vi + y * ystride + x * zstride] = c_air
- data[vi + y * zstride + x] = c_air
- end
- end
- end
- end
- -- thrid pass: decorate
- --for i = start_index, stop_index do data[linedata[i].vi] = c_glowstone end
- -- add glowstone to make tunnels easyier to find
- local vi = linedata[boundary_index].vi
- local glowcount = 0
- for x = -6, 6 do
- for y = -6, 6 do
- if glowcount > 3 then break end
- local radius_squared = x * x + y * y
- if radius_squared < 27 and radius_squared >= 25 then
- if data[vi + y * ystride + x] ~= c_air then data[vi + y * ystride + x] = c_glowstone glowcount = glowcount + 1 end
- if data[vi + y * ystride + x * zstride] ~= c_air then data[vi + y * ystride + x * zstride] = c_glowstone glowcount = glowcount + 1 end
- if data[vi + y * zstride + x] ~= c_air then data[vi + y * zstride + x] = c_glowstone glowcount = glowcount + 1 end
- end
- end
- end
- end
- function excavate_tunnel_to_center_of_the_nether(data, area, nvals_cave, minp, maxp)
- local extent = vector.subtract(maxp, minp)
- local skip = 10 -- sampling rate of 1 in 10
- local highest = -1000
- local lowest = 1000
- local lowest_vi
- local highest_vi
- local yCaveStride = maxp.x - minp.x + 1
- local zCaveStride = yCaveStride * yCaveStride
- local vi_offset = area:indexp(vector.add(minp, math_floor(skip / 2))) -- start half the sampling distance away from minp
- local vi, ni
- for y = 0, extent.y - 1, skip do
- local sealevel = find_nearest_lava_sealevel(minp.y + y)
- if minp.y + y > sealevel then -- only create tunnels above sea level
- for z = 0, extent.z - 1, skip do
- vi = vi_offset + y * area.ystride + z * area.zstride + 1
- ni = z * zCaveStride + y * yCaveStride + 1
- for x = 0, extent.x - 1, skip do
- local noise = math_abs(nvals_cave[ni])
- if noise < lowest then
- lowest = noise
- lowest_vi = vi
- end
- if noise > highest then
- highest = noise
- highest_vi = vi
- end
- ni = ni + skip
- vi = vi + skip
- end
- end
- end
- end
- if lowest < BASALT_LIMIT and highest > TCAVE + 0.03 then
- local sealevel, cavern_limit_distance = find_nearest_lava_sealevel(area:position(lowest_vi).y)
- local cavern_noise_adj = CENTER_REGION_LIMIT * (cavern_limit_distance * cavern_limit_distance * cavern_limit_distance)
- if lowest + cavern_noise_adj < BASALT_LIMIT then
- draw_pathway(data, area, area:position(highest_vi), area:position(lowest_vi))
- end
- end
- end
- local pathway_chunk_count = 0
- local total_chunk_count = 0
- -- On-generated function
- local function on_generated(minp, maxp, seed)
- if minp.y > NETHER_CEILING or maxp.y < NETHER_FLOOR then
- return
- end
- local vm, emerge_min, emerge_max = minetest.get_mapgen_object("voxelmanip")
- local area = VoxelArea:new{MinEdge=emerge_min, MaxEdge=emerge_max}
- local data = vm:get_data(dbuf)
- local x0, y0, z0 = minp.x, math_max(minp.y, NETHER_FLOOR), minp.z
- local x1, y1, z1 = maxp.x, math_min(maxp.y, NETHER_CEILING), maxp.z
- local yCaveStride = x1 - x0 + 1
- local zCaveStride = yCaveStride * yCaveStride
- local chulens = {x = yCaveStride, y = yCaveStride, z = yCaveStride}
- nobj_cave = nobj_cave or minetest.get_perlin_map(np_cave, chulens)
- local nvals_cave = nobj_cave:get_3d_map_flat(minp, nbuf_cave)
- local dungeonRooms = build_dungeon_room_list(data, area)
- local abs_cave_noise, abs_cave_noise_adjusted
- local contains_nether = false
- local contains_shell = false
- local contains_center = false
- local contains_ocean = false
- for y = y0, y1 do -- Y loop first to minimise tcave & lava-sea calculations
- local tcave = TCAVE
- if y > yblmax then tcave = TCAVE + ((y - yblmax) / BLEND) ^ 2 end
- if y < yblmin then tcave = TCAVE + ((yblmin - y) / BLEND) ^ 2 end
- local sea_level, cavern_limit_distance = find_nearest_lava_sealevel(y)
- local above_lavasea = y > sea_level
- local below_lavasea = y < sea_level
- local cavern_noise_adj = CENTER_REGION_LIMIT * (cavern_limit_distance * cavern_limit_distance * cavern_limit_distance)
- for z = z0, z1 do
- local vi = area:index(x0, y, z) -- Initial voxelmanip index
- local ni = (z - z0) * zCaveStride + (y - y0) * yCaveStride + 1
- for x = x0, x1 do
- local id = data[vi] -- Existing node
- local cave_noise = nvals_cave[ni]
- if cave_noise > tcave then
- -- prime region
- data[vi] = c_air
- contains_nether = true
- elseif -cave_noise > tcave then
- -- secondary/spare region
- data[vi] = c_air
- else
- -- netherrack walls and/or center region
- abs_cave_noise = math_abs(cave_noise)
- -- abs_cave_noise_adjusted makes the center region smaller as distance from the lava ocean
- -- increases, we do this by pretending the abs_cave_noise value is higher.
- abs_cave_noise_adjusted = abs_cave_noise + cavern_noise_adj
- if above_lavasea and abs_cave_noise_adjusted < BASALT_LIMIT then
- data[vi] = c_air
- contains_center = true
- elseif abs_cave_noise_adjusted < SURFACE_CRUST_LIMIT or (below_lavasea and abs_cave_noise_adjusted < CRUST_LIMIT) then
- data[vi] = c_lavasea_source
- contains_ocean = true
- elseif abs_cave_noise_adjusted < BASALT_LIMIT then
- data[vi] = c_lava_crust
- contains_ocean = true
- elseif id == c_air or id == c_native_mapgen then
- if abs_cave_noise < CENTER_REGION_LIMIT then
- data[vi] = c_basalt
- else
- -- the shell seperating the basalt realm from the rest of the nether...
- -- put some holes in it
- data[vi] = c_netherrack -- excavate_dungeons() will mostly reverse this inside dungeons
- contains_shell = true
- end
- end
- end
- ni = ni + 1
- vi = vi + 1
- end
- end
- end
- total_chunk_count = total_chunk_count + 1
- if contains_nether and contains_shell and contains_center and contains_ocean then
- pathway_chunk_count = pathway_chunk_count + 1
- end
- --if total_chunk_count % 50 == 0 then
- -- minetest.chat_send_all(pathway_chunk_count .. " of " .. total_chunk_count .. " chunks contain both nether and lava-sea (" .. math_floor(pathway_chunk_count * 100 / total_chunk_count) .. "%)")
- --end
- if contains_nether and contains_center then
- excavate_tunnel_to_center_of_the_nether(data, area, nvals_cave, minp, maxp)
- end
- -- any air from the native mapgen has been replaced by netherrack, but we
- -- don't want netherrack inside dungeons, so fill known dungeon rooms with air.
- excavate_dungeons(data, area, dungeonRooms)
- decorate_dungeons(data, area, dungeonRooms)
- vm:set_data(data)
- minetest.generate_ores(vm)
- -- avoid generating decorations on the underside of the bottom of the nether
- if minp.y > NETHER_FLOOR and maxp.y < NETHER_CEILING then minetest.generate_decorations(vm) end
- vm:set_lighting({day = 0, night = 0}, minp, maxp)
- vm:calc_lighting()
- vm:update_liquids()
- vm:write_to_map()
- end
- -- use knowledge of the nether mapgen algorithm to return a suitable ground level for placing a portal.
- function nether.find_nether_ground_y(target_x, target_z, start_y)
- local nobj_cave_point = minetest.get_perlin(np_cave)
- local air = 0 -- Consecutive air nodes found
- for y = start_y, math_max(NETHER_FLOOR + BLEND, start_y - 4096), -1 do
- local nval_cave = nobj_cave_point:get3d({x = target_x, y = y, z = target_z})
- if nval_cave > TCAVE then -- Cavern
- air = air + 1
- else -- Not cavern, check if 4 nodes of space above
- if air >= 4 then
- -- Check volume for non-natural nodes
- local minp = {x = target_x - 1, y = y , z = target_z - 2}
- local maxp = {x = target_x + 2, y = y + 4, z = target_z + 2}
- if nether.volume_is_natural(minp, maxp) then
- return y + 1
- else -- Restart search a little lower
- nether.find_nether_ground_y(target_x, target_z, y - 16)
- end
- else -- Not enough space, reset air to zero
- air = 0
- end
- end
- end
- return math_max(start_y, NETHER_FLOOR + BLEND) -- Fallback
- end
- -- We don't need to be gen-notified of temples because only dungeons will be generated
- -- if a biome defines the dungeon nodes
- minetest.set_gen_notify({dungeon = true})
- minetest.register_on_generated(on_generated)
|