diff --git a/init.lua b/init.lua index a00cf9e..cbc684b 100644 --- a/init.lua +++ b/init.lua @@ -38,25 +38,40 @@ nether = {} nether.modname = minetest.get_current_modname() nether.path = minetest.get_modpath(nether.modname) nether.get_translator = S + -- nether.useBiomes allows other mods to know whether they can register ores etc. in the Nether. + -- See mapgen.lua for an explanation of why minetest.read_schematic is being checked +nether.useBiomes = minetest.get_mapgen_setting("mg_name") ~= "v6" and minetest.read_schematic ~= nil + -- Settings -nether.DEPTH = -5000 -- The y location of the Nether -nether.FASTTRAVEL_FACTOR = 8 -- 10 could be better value for Minetest, since there's no sprint, but ex-Minecraft players will be mathing for 8 -nether.PORTAL_BOOK_LOOT_WEIGHTING = 0.9 -- Likelyhood of finding the Book of Portals (guide) in dungeon chests. Set to 0 to disable. -nether.NETHER_REALM_ENABLED = true -- Setting to false disables the Nether and Nether portal +nether.DEPTH_CEILING = -5000 -- The y location of the Nether's celing +nether.DEPTH_FLOOR = -11000 -- The y location of the Nether's floor +nether.FASTTRAVEL_FACTOR = 8 -- 10 could be better value for Minetest, since there's no sprint, but ex-Minecraft players will be mathing for 8 +nether.PORTAL_BOOK_LOOT_WEIGHTING = 0.9 -- Likelyhood of finding the Book of Portals (guide) in dungeon chests. Set to 0 to disable. +nether.NETHER_REALM_ENABLED = true -- Setting to false disables the Nether and Nether portal -- Override default settings with values from the .conf file, if any are present. nether.FASTTRAVEL_FACTOR = tonumber(minetest.settings:get("nether_fasttravel_factor") or nether.FASTTRAVEL_FACTOR) nether.PORTAL_BOOK_LOOT_WEIGHTING = tonumber(minetest.settings:get("nether_portalBook_loot_weighting") or nether.PORTAL_BOOK_LOOT_WEIGHTING) nether.NETHER_REALM_ENABLED = minetest.settings:get_bool("nether_realm_enabled", nether.NETHER_REALM_ENABLED) +nether.DEPTH_CEILING = tonumber(minetest.settings:get("nether_depth_ymax") or nether.DEPTH_CEILING) +nether.DEPTH_FLOOR = tonumber(minetest.settings:get("nether_depth_ymin") or nether.DEPTH_FLOOR) +if nether.DEPTH_FLOOR + 1000 > nether.DEPTH_CEILING then + error("The lower limit of the Nether must be set at least 1000 lower than the upper limit, and more than 3000 is recommended. Set settingtypes.txt, or 'All Settings' -> 'Mods' -> 'nether' -> 'Nether depth'", 0) +end +nether.DEPTH = nether.DEPTH_CEILING -- Deprecated, use nether.DEPTH_CEILING instead. -- Load files dofile(nether.path .. "/portal_api.lua") dofile(nether.path .. "/nodes.lua") if nether.NETHER_REALM_ENABLED then - dofile(nether.path .. "/mapgen.lua") + if nether.useBiomes then + dofile(nether.path .. "/mapgen.lua") + else + dofile(nether.path .. "/mapgen_nobiomes.lua") + end end dofile(nether.path .. "/portal_examples.lua") @@ -83,7 +98,7 @@ This opens to a truly hellish place, though for small mercies the air there is s The expedition parties have found no diamonds or gold, and after an experienced search party failed to return from the trail of a missing expedition party, I must conclude this is a dangerous place.]], 10 * nether.FASTTRAVEL_FACTOR), is_within_realm = function(pos) -- return true if pos is inside the Nether - return pos.y < nether.DEPTH + return pos.y < nether.DEPTH_CEILING end, find_realm_anchorPos = function(surface_anchorPos) @@ -91,7 +106,7 @@ The expedition parties have found no diamonds or gold, and after an experienced local destination_pos = vector.divide(surface_anchorPos, nether.FASTTRAVEL_FACTOR) destination_pos.x = math.floor(0.5 + destination_pos.x) -- round to int destination_pos.z = math.floor(0.5 + destination_pos.z) -- round to int - destination_pos.y = nether.DEPTH - 1000 -- temp value so find_nearest_working_portal() returns nether portals + destination_pos.y = nether.DEPTH_CEILING - 1000 -- temp value so find_nearest_working_portal() returns nether portals -- a y_factor of 0 makes the search ignore the altitude of the portals (as long as they are in the Nether) local existing_portal_location, existing_portal_orientation = @@ -100,7 +115,7 @@ The expedition parties have found no diamonds or gold, and after an experienced if existing_portal_location ~= nil then return existing_portal_location, existing_portal_orientation else - local start_y = nether.DEPTH - math.random(500, 1500) -- Search starting altitude + local start_y = nether.DEPTH_CEILING - math.random(500, 1500) -- Search starting altitude destination_pos.y = nether.find_nether_ground_y(destination_pos.x, destination_pos.z, start_y) return destination_pos end diff --git a/mapgen.lua b/mapgen.lua new file mode 100644 index 0000000..eaf2b71 --- /dev/null +++ b/mapgen.lua @@ -0,0 +1,500 @@ +--[[ + + 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.max, math.min -- 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 = "default:mossycobble", + 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 = "default:lava_source", + wherein = "nether:rack", + 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 = "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 = nil +local dbuf = nil + +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_netherbrick = minetest.get_content_id("nether:brick") +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_native_mapgen = minetest.get_content_id("nether:native_mapgen") + + +-- Dungeon excavation functions + +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) + + -- 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 + local vi = area:index(room_min.x, y, z) + for x = room_min.x, room_max.x do + if data[vi] == c_netherrack 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 + local vi = area:index(roomInfo.x, room_max.y + 1, roomInfo.z) + if data[vi] == c_netherbrick 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 data[vi - yStride] == c_netherbrick then data[vi - yStride] = c_lava_source end + 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 + + -- 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 data[vi_min + offset] == c_netherbrick then data[vi_min + offset] = window_node end + if data[vi_max + offset] == c_netherbrick 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 data[vi_min + offset] == c_netherbrick then data[vi_min + offset] = window_node end + if data[vi_max + offset] == c_netherbrick then data[vi_max + offset] = window_node end + end + end + + -- Weeds on the floor once Nether weeds are added + end + end +end + + +-- 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:get3dMap_flat(minp, nbuf_cave) + + + local dungeonRooms = build_dungeon_room_list(data, area) + + for y = y0, y1 do -- Y loop first to minimise tcave 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 + + 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 + + if nvals_cave[ni] > tcave then + data[vi] = c_air + elseif id == c_air or id == c_native_mapgen then + data[vi] = c_netherrack -- excavate_dungeons() will mostly reverse this inside dungeons + end + + ni = ni + 1 + vi = vi + 1 + end + end + 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) + + -- 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 + + minetest.generate_ores(vm) + 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) \ No newline at end of file diff --git a/mapgen_decorations.lua b/mapgen_decorations.lua new file mode 100644 index 0000000..b210db8 --- /dev/null +++ b/mapgen_decorations.lua @@ -0,0 +1,138 @@ +--[[ + + Register decorations for Nether mapgen + + Copyright (C) 2020 Treer + + 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. + +]]-- + +-- Lava is unreliable in the old Nether mapgen because it removes lava +-- from the overdraw areas, so any decorations involving lava will often +-- have the lava missing depending on whether nearby chunks were already +-- emerged or not before the decoration was placed. +local allow_lava_decorations = nether.useBiomes + +local _ = {name = "air", prob = 0} +local A = {name = "air", prob = 255, force_place = true} +local G = {name = "nether:glowstone", prob = 255, force_place = true} +local N = {name = "nether:rack", prob = 255} +local S = {name = "nether:sand", prob = 255, force_place = true} +local L = {name = "default:lava_source", prob = 255, force_place = true} + + +-- ================= +-- Stalactites +-- ================= + +local schematic_GlowstoneStalactite = { + size = {x = 5, y = 10, z = 5}, + data = { + _, _, _, _, _, + _, _, _, _, _, + _, _, _, _, _, + _, _, _, _, _, + _, _, _, _, _, + _, _, _, _, _, + _, _, _, _, _, + _, _, _, _, _, + _, N, G, N, _, + _, N, N, N, _, + + _, _, _, _, _, + _, _, _, _, _, + _, _, _, _, _, + _, _, _, _, _, + _, _, _, _, _, + _, _, G, _, _, + _, _, G, _, _, + _, G, G, G, _, + N, G, G, G, N, + N, N, G, N, N, + + _, _, N, _, _, -- ypos 0, prob 25% (64/256) + _, _, G, _, _, -- ypos 1, prob 37% (96/256) + _, _, G, _, _, -- ypos 2, prob 100% + _, _, G, _, _, -- ypos 3, prob 100% + _, _, G, G, _, -- ypos 4, prob 50% (128/256) to make half of stalactites asymmetric + _, G, G, G, _, -- ypos 5, prob 75% (192/256) + _, G, G, G, _, -- ypos 6, prob 75% (192/256) + _, G, G, G, _, -- ypos 7, prob 100% + G, G, G, G, G, -- ypos 8, prob 100% + N, G, G, G, N, -- ypos 9, prob 75% (192/256) + + _, _, _, _, _, + _, _, _, _, _, + _, _, _, _, _, + _, _, _, _, _, + _, _, G, _, _, + _, _, G, _, _, + _, _, G, _, _, + _, G, G, G, _, + N, G, G, G, N, + N, N, G, N, N, + + _, _, _, _, _, + _, _, _, _, _, + _, _, _, _, _, + _, _, _, _, _, + _, _, _, _, _, + _, _, _, _, _, + _, _, _, _, _, + _, _, _, _, _, + _, N, G, N, _, + _, N, N, N, _ + }, + -- Y-slice probabilities do not function correctly for ceiling schematic + -- decorations because they are inverted, so ypos numbers have been inverted + -- to match, and a larger offset in place_offset_y should be used (e.g. -3). + yslice_prob = { + {ypos = 9, prob = 192}, + {ypos = 6, prob = 192}, + {ypos = 5, prob = 192}, + {ypos = 4, prob = 128}, + {ypos = 1, prob = 96}, + {ypos = 0, prob = 64} + } +} + +minetest.register_decoration({ + name = "Glowstone stalactite", + deco_type = "schematic", + place_on = "nether:rack", + sidelen = 80, + fill_ratio = 0.0004, + biomes = {"nether_caverns"}, + y_max = nether.DEPTH_CEILING, -- keep compatibility with mapgen_nobiomes.lua + y_min = nether.DEPTH_FLOOR, + schematic = schematic_GlowstoneStalactite, + flags = "place_center_x,place_center_z,force_placement,all_ceilings", + place_offset_y=-3 +}) + +minetest.register_decoration({ + name = "Netherrack stalactite", + deco_type = "schematic", + place_on = "nether:rack", + sidelen = 80, + fill_ratio = 0.0007, + biomes = {"nether_caverns"}, + y_max = nether.DEPTH_CEILING, -- keep compatibility with mapgen_nobiomes.lua + y_min = nether.DEPTH_FLOOR, + schematic = schematic_GlowstoneStalactite, + replacements = {["nether:glowstone"] = "nether:rack"}, + flags = "place_center_x,place_center_z,all_ceilings", + place_offset_y=-3 +}) diff --git a/mapgen_nobiomes.lua b/mapgen_nobiomes.lua index 6359804..fb707e5 100644 --- a/mapgen_nobiomes.lua +++ b/mapgen_nobiomes.lua @@ -19,11 +19,11 @@ ]]-- -nether.DEPTH_FLOOR = -30912 -- this mapgen will create Nether all the way down -- Parameters -local NETHER_DEPTH = nether.DEPTH +local NETHER_CEILING = nether.DEPTH_CEILING +local NETHER_FLOOR = nether.DEPTH_FLOOR local TCAVE = 0.6 local BLEND = 128 @@ -44,13 +44,15 @@ local np_cave = { -- Stuff -local yblmax = NETHER_DEPTH - BLEND * 2 - +local yblmin = NETHER_FLOOR + BLEND * 2 +local yblmax = NETHER_CEILING - BLEND * 2 -- Mapgen +dofile(nether.path .. "/mapgen_decorations.lua") + -- Initialize noise object, localise noise and data buffers local nobj_cave = nil @@ -92,15 +94,15 @@ local c_netherrack = minetest.get_content_id("nether:rack") -- On-generated function minetest.register_on_generated(function(minp, maxp, seed) - if minp.y > NETHER_DEPTH then + if minp.y > NETHER_CEILING or maxp.y < NETHER_FLOOR then return end local x1 = maxp.x - local y1 = maxp.y + local y1 = math.min(maxp.y, NETHER_CEILING) local z1 = maxp.z local x0 = minp.x - local y0 = minp.y + local y0 = math.max(minp.y, NETHER_FLOOR) local z0 = minp.z local vm, emin, emax = minetest.get_mapgen_object("voxelmanip") @@ -126,11 +128,9 @@ minetest.register_on_generated(function(minp, maxp, seed) local tcave local in_chunk_y = false if y >= y0 and y <= y1 then - if y > yblmax then - tcave = TCAVE + ((y - yblmax) / BLEND) ^ 2 - else - tcave = TCAVE - end + tcave = TCAVE + if y > yblmax then tcave = TCAVE + ((y - yblmax) / BLEND) ^ 2 end + if y < yblmin then tcave = TCAVE + ((yblmin - y) / BLEND) ^ 2 end in_chunk_y = true end @@ -189,6 +189,10 @@ minetest.register_on_generated(function(minp, maxp, seed) end vm:set_data(data) + + -- 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() diff --git a/settingtypes.txt b/settingtypes.txt index dbea779..fca72e5 100644 --- a/settingtypes.txt +++ b/settingtypes.txt @@ -16,4 +16,10 @@ nether_realm_enabled (Enable Nether realm & portal) bool true nether_enable_portal_example_floatlands (Enable example portal: Floatlands) bool false # Enables the Surface-travel portal api code example -nether_enable_portal_example_surfacetravel (Enable example portal: Surface-travel) bool false \ No newline at end of file +nether_enable_portal_example_surfacetravel (Enable example portal: Surface-travel) bool false + +[Nether depth] +#The depth where the Nether begins / the Nether ceiling +nether_depth_ymax (Upper limit of Nether) int -5000 -30000 32767 +#The lower limit of the Nether must be at least 1000 lower than the upper limit, and more than 3000 lower is recommended. +nether_depth_ymin (Lower limit of Nether) int -11000 -32768 30000 \ No newline at end of file