From c065dc8e539d71bdf165189729d6c67efd40d611 Mon Sep 17 00:00:00 2001 From: Treer Date: Wed, 6 Jan 2021 12:05:39 +1100 Subject: [PATCH] Add the Mantle Adds a magma oceans region to the nether outside the existing nether caverns, which can be reached via tunnels. Other misc changes: * chatcomment nether_whereami, a debug aid for knowing which perlin-noise region you are in * Nether ores no longer obtainable on the ceiling * Move crafts into crafts.lua * Add steam to lava cooling, and play bubbling lava upon death by lava * Add cracked netherbrick a decorative node which can only be obtained from dungeons or structures I encourage someone to improve or replace the cracked netherbrick texture. For copyright purposes it's currently a derivative work (by me, 2020) from nether_brick.png, which is licensed under CC BY-SA 3.0 by PilzAdam, so it can fall under the "All other media" PilzAdam's credit in readme.md rather than need its own entry. --- README.md | 7 +- crafts.lua | 64 +++ depends.txt | 10 +- init.lua | 16 + mapgen.lua | 260 ++++++++++-- mapgen_decorations.lua | 225 +++++++++- mapgen_mantle.lua | 476 ++++++++++++++++++++++ mapgen_nobiomes.lua | 9 +- nodes.lua | 396 ++++++++++++++++-- sounds/nether_lava_bubble.0.ogg | Bin 0 -> 7604 bytes sounds/nether_lava_bubble.ogg | Bin 0 -> 8085 bytes textures/nether_basalt.png | Bin 0 -> 678 bytes textures/nether_basalt_chiselled_side.png | Bin 0 -> 597 bytes textures/nether_basalt_chiselled_top.png | Bin 0 -> 594 bytes textures/nether_basalt_hewn.png | Bin 0 -> 1603 bytes textures/nether_basalt_side.png | Bin 0 -> 518 bytes textures/nether_brick_cracked.png | Bin 0 -> 290 bytes textures/nether_glowstone_deep.png | Bin 0 -> 666 bytes textures/nether_lava_crust_animated.png | Bin 0 -> 18533 bytes textures/nether_lava_source_animated.png | Bin 0 -> 2654 bytes textures/nether_particle_anim4.png | Bin 0 -> 288 bytes textures/nether_rack_deep.png | Bin 0 -> 353 bytes tools.lua | 19 + 23 files changed, 1388 insertions(+), 94 deletions(-) create mode 100644 crafts.lua create mode 100644 mapgen_mantle.lua create mode 100644 sounds/nether_lava_bubble.0.ogg create mode 100644 sounds/nether_lava_bubble.ogg create mode 100644 textures/nether_basalt.png create mode 100644 textures/nether_basalt_chiselled_side.png create mode 100644 textures/nether_basalt_chiselled_top.png create mode 100644 textures/nether_basalt_hewn.png create mode 100644 textures/nether_basalt_side.png create mode 100644 textures/nether_brick_cracked.png create mode 100644 textures/nether_glowstone_deep.png create mode 100644 textures/nether_lava_crust_animated.png create mode 100644 textures/nether_lava_source_animated.png create mode 100644 textures/nether_particle_anim4.png create mode 100644 textures/nether_rack_deep.png diff --git a/README.md b/README.md index 36d6b62..dca4ca7 100644 --- a/README.md +++ b/README.md @@ -36,17 +36,20 @@ SOFTWARE. * `nether_portal_ignite.ogg` is a derivative of "Flame Ignition" by [hykenfreak](https://freesound.org/people/hykenfreak), used under CC BY 3.0. "Nether Portal ignite" is licensed under CC BY 3.0 by Treer. ### [Attribution-ShareAlike 4.0 International (CC BY-SA 4.0)](https://creativecommons.org/licenses/by-sa/4.0/) + * `nether_basalt`* (files starting with "nether_basalt"): Treer, 2020 * `nether_book_`* (files starting with "nether_book"): Treer, 2019-2020 * `nether_fumarole.ogg`: Treer, 2020 + * `nether_lava_bubble`* (files starting with "nether_lava_bubble"): Treer, 2020 + * `nether_lava_crust_animated.png`: Treer, 2019-2020 * `nether_particle_anim`* (files starting with "nether_particle_anim"): Treer, 2019 * `nether_portal_ignition_failure.ogg`: Treer, 2019 * `nether_smoke_puff.png`: Treer, 2020 ### [Attribution-ShareAlike 3.0 Unported (CC BY-SA 3.0)](http://creativecommons.org/licenses/by-sa/3.0/) - * `nether_glowstone.png`: BlockMen + * `nether_glowstone`* (files starting with "nether_glowstone"): BlockMen * `nether_nether_ingot.png` & `nether_nether_lump.png`: color adjusted versions from "[default](https://github.com/minetest/minetest_game/tree/master/mods/default)" mod, originally by Gambit * `nether_portal.png`: [Extex101](https://github.com/Extex101), 2020 - * `nether_rack.png`: Zeg9 + * `nether_rack`* (files starting with "nether_rack"): Zeg9 * `nether_tool_`* (files starting with "nether_tool_"): color adjusted versions from "[default](https://github.com/minetest/minetest_game/tree/master/mods/default)" mod, originals by BlockMen All other media: Copyright © 2013 PilzAdam, licensed under CC BY-SA 3.0 by PilzAdam. \ No newline at end of file diff --git a/crafts.lua b/crafts.lua new file mode 100644 index 0000000..f880e19 --- /dev/null +++ b/crafts.lua @@ -0,0 +1,64 @@ +--[[ + + Copyright (C) 2013 PilzAdam + Copyright (C) 2020 lortas + 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. + +]]-- + +minetest.register_craft({ + output = "nether:brick 4", + recipe = { + {"nether:rack", "nether:rack"}, + {"nether:rack", "nether:rack"}, + } +}) + +minetest.register_craft({ + output = "nether:fence_nether_brick 6", + recipe = { + {"nether:brick", "nether:brick", "nether:brick"}, + {"nether:brick", "nether:brick", "nether:brick"}, + }, +}) + +minetest.register_craft({ + output = "nether:brick_compressed", + recipe = { + {"nether:brick","nether:brick","nether:brick"}, + {"nether:brick","nether:brick","nether:brick"}, + {"nether:brick","nether:brick","nether:brick"}, + } +}) + +minetest.register_craft({ + output = "nether:basalt_hewn", + type = "shapeless", + recipe = { + "nether:basalt", + "nether:basalt", + }, +}) + +minetest.register_craft({ + output = "nether:basalt_chiselled 4", + recipe = { + {"nether:basalt_hewn", "nether:basalt_hewn"}, + {"nether:basalt_hewn", "nether:basalt_hewn"} + } +}) + +-- See tools.lua for tools related crafting \ No newline at end of file diff --git a/depends.txt b/depends.txt index a6ab237..00f426a 100644 --- a/depends.txt +++ b/depends.txt @@ -1,8 +1,8 @@ -stairs default -moreblocks? -mesecons? -loot? -dungeon_loot? +stairs doc_basics? +dungeon_loot? fire? +loot? +mesecons? +moreblocks? diff --git a/init.lua b/init.lua index c6e24c1..c09234e 100644 --- a/init.lua +++ b/init.lua @@ -116,6 +116,7 @@ end dofile(nether.path .. "/portal_api.lua") dofile(nether.path .. "/nodes.lua") dofile(nether.path .. "/tools.lua") +dofile(nether.path .. "/crafts.lua") if nether.NETHER_REALM_ENABLED then if nether.useBiomes then dofile(nether.path .. "/mapgen.lua") @@ -227,3 +228,18 @@ The expedition parties have found no diamonds or gold, and after an experienced }) end + + +-- Play bubbling lava sounds if player killed by lava +minetest.register_on_dieplayer( + function(player, reason) + if reason.node ~= nil and minetest.get_node_group(reason.node, "lava") > 0 or reason.node == "nether:lava_crust" then + minetest.sound_play( + "nether_lava_bubble", + -- this sample was encoded at 3x speed to reduce .ogg file size + -- at the expense of higher frequencies, so pitch it down ~3x + {to_player = player:get_player_name(), pitch = 0.3, gain = 0.8} + ) + end + end +) \ No newline at end of file diff --git a/mapgen.lua b/mapgen.lua index b449c5c..abc145a 100644 --- a/mapgen.lua +++ b/mapgen.lua @@ -2,6 +2,12 @@ Nether mod for minetest + "mapgen.lua" is the modern biomes-based Nether mapgen, which + requires Minetest v5.1 or greater + "mapgen_nobiomes.lua" is the legacy version of the mapgen, only used + in older versions of Minetest or in v6 worlds. + + Copyright (C) 2013 PilzAdam Permission to use, copy, modify, and/or distribute this software for @@ -27,10 +33,32 @@ local NETHER_FLOOR = nether.DEPTH_FLOOR local TCAVE = 0.6 local BLEND = 128 +-- parameters for central region +local REGION_BUFFER_THICKNESS = 0.2 +local CENTER_REGION_LIMIT = TCAVE - REGION_BUFFER_THICKNESS -- Netherrack gives way to Deep-Netherrack here +local CENTER_CAVERN_LIMIT = CENTER_REGION_LIMIT - 0.1 -- Deep-Netherrack gives way to air here +local SURFACE_CRUST_LIMIT = CENTER_CAVERN_LIMIT * 0.25 -- Crusted-lava at the surface of the lava ocean gives way to liquid lava here +local CRUST_LIMIT = CENTER_CAVERN_LIMIT * 0.85 -- Crusted-lava under the surface of the lava ocean gives way to liquid lava here +local BASALT_COLUMN_UPPER_LIMIT = CENTER_CAVERN_LIMIT * 0.9 -- Basalt columns may appear between these upper and lower limits +local BASALT_COLUMN_LOWER_LIMIT = CENTER_CAVERN_LIMIT * 0.25 -- This value is close to SURFACE_CRUST_LIMIT so basalt columns give way to "flowing" lava rivers --- Stuff -local math_max, math_min = math.max, math.min -- avoid needing table lookups each time a common math function is invoked +-- Shared Nether mapgen namespace +-- For mapgen files to share functions and constants +nether.mapgen = {} +local mapgen = nether.mapgen + +mapgen.TCAVE = TCAVE -- const needed in mapgen_mantle.lua +mapgen.BLEND = BLEND -- const needed in mapgen_mantle.lua +mapgen.CENTER_REGION_LIMIT = CENTER_REGION_LIMIT -- const needed in mapgen_mantle.lua +mapgen.CENTER_CAVERN_LIMIT = CENTER_CAVERN_LIMIT -- const needed in mapgen_mantle.lua +mapgen.BASALT_COLUMN_UPPER_LIMIT = BASALT_COLUMN_UPPER_LIMIT -- const needed in mapgen_mantle.lua +mapgen.BASALT_COLUMN_LOWER_LIMIT = BASALT_COLUMN_LOWER_LIMIT -- const needed in mapgen_mantle.lua + +mapgen.ore_ceiling = NETHER_CEILING - BLEND -- leave a solid 128 node cap of netherrack before introducing ores +mapgen.ore_floor = NETHER_FLOOR + BLEND + +local debugf = nether.debug if minetest.read_schematic == nil then -- Using biomes to create the Nether requires the ability for biomes to set "node_cave_liquid = air". @@ -41,6 +69,18 @@ if minetest.read_schematic == nil then error("This " .. nether.modname .. " mapgen requires Minetest v5.1 or greater, use mapgen_nobiomes.lua instead.", 0) end +-- Load helper functions for generating the mantle / center region +dofile(nether.path .. "/mapgen_mantle.lua") + + +-- Misc math functions + +-- avoid needing table lookups each time a common math function is invoked +local math_max, math_min, math_abs, math_floor = math.max, math.min, math.abs, math.floor + + +-- Inject nether_caverns biome + 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 @@ -123,7 +163,7 @@ minetest.register_biome({ 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_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). @@ -152,19 +192,30 @@ minetest.register_ore({ clust_scarcity = 11 * 11 * 11, clust_num_ores = 3, clust_size = 2, - y_max = NETHER_CEILING, - y_min = NETHER_FLOOR, + y_max = mapgen.ore_ceiling, + y_min = mapgen.ore_floor +}) + +minetest.register_ore({ + ore_type = "scatter", + ore = "nether:lava_crust", -- crusted lava replaces of scattered glowstone in the mantle + wherein = "nether:rack_deep", + clust_scarcity = 16 * 16 * 16, + clust_num_ores = 4, + clust_size = 2, + y_max = mapgen.ore_ceiling, + y_min = mapgen.ore_floor }) minetest.register_ore({ ore_type = "scatter", ore = "default:lava_source", - wherein = "nether:rack", + wherein = {"nether:rack", "nether:rack_deep"}, clust_scarcity = 36 * 36 * 36, clust_num_ores = 4, clust_size = 2, - y_max = NETHER_CEILING, - y_min = NETHER_FLOOR, + y_max = mapgen.ore_ceiling, + y_min = mapgen.ore_floor }) minetest.register_ore({ @@ -173,8 +224,8 @@ minetest.register_ore({ wherein = "nether:rack", clust_scarcity = 14 * 14 * 14, clust_size = 8, - y_max = NETHER_CEILING, - y_min = NETHER_FLOOR + y_max = mapgen.ore_ceiling, + y_min = mapgen.ore_floor }) @@ -182,7 +233,7 @@ minetest.register_ore({ -- 3D noise -local np_cave = { +mapgen.np_cave = { offset = 0, scale = 1, spread = {x = 384, y = 128, z = 384}, -- squashed 3:1 @@ -199,23 +250,29 @@ 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_netherbrick = minetest.get_content_id("nether:brick") +local c_netherrack_deep = minetest.get_content_id("nether:rack_deep") +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_native_mapgen = minetest.get_content_id("nether:native_mapgen") -- 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 = {} @@ -310,6 +367,8 @@ end -- (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 @@ -319,9 +378,10 @@ function excavate_dungeons(data, area, rooms) 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) + 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 + node_id = data[vi] + if node_id == c_netherrack or node_id == c_netherrack_deep then data[vi] = c_air end vi = vi + 1 end end @@ -346,20 +406,22 @@ function decorate_dungeons(data, area, rooms) 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 + -- Glowstone chandelier (feel free to replace with a fancy schematic) local vi = area:index(roomInfo.x, room_max.y + 1, roomInfo.z) - if data[vi] == c_netherbrick then data[vi] = c_glowstone end + 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 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 + 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 @@ -377,15 +439,15 @@ function decorate_dungeons(data, area, rooms) 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 + 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 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 + 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 @@ -395,8 +457,43 @@ function decorate_dungeons(data, area, rooms) end +local yblmin = NETHER_FLOOR + BLEND * 2 +local yblmax = NETHER_CEILING - BLEND * 2 +-- At both the top and bottom of the Nether, as set by NETHER_CEILING and NETHER_FLOOR, +-- there is a 128 deep cap of solid netherrack, followed by a 128-deep blending zone +-- where Nether caverns may start to appear. +-- The solid zones and blending zones are achieved by adjusting the np_cave noise to be +-- outside the range where caverns form, this function returns that adjustment. +-- +-- Returns two values: the noise limit adjustment for nether caverns, and the +-- noise limit adjustment for the central region / mantle caverns +mapgen.get_mapgenblend_adjustments = function(y) + + -- floorAndCeilingBlend will normally be 0, but shifts toward 1 in the + -- blending zone, and goes higher than 1 in the solid zone between the + -- blending zone and the end of the nether. + local floorAndCeilingBlend = 0 + if y > yblmax then floorAndCeilingBlend = ((y - yblmax) / BLEND) ^ 2 end + if y < yblmin then floorAndCeilingBlend = ((yblmin - y) / BLEND) ^ 2 end + + -- the nether caverns exist when np_cave noise is greater than TCAVE, so + -- to fade out the nether caverns, adjust TCAVE upward. + local tcave_adj = floorAndCeilingBlend + + -- the central regions exists when np_cave noise is below CENTER_REGION_LIMIT, + -- so to fade out the mantle caverns adjust CENTER_REGION_LIMIT downward. + local centerRegionLimit_adj = -(CENTER_REGION_LIMIT * floorAndCeilingBlend) + + return tcave_adj, centerRegionLimit_adj +end + + + -- On-generated function +local tunnelCandidate_count = 0 +local tunnel_count = 0 +local total_chunk_count = 0 local function on_generated(minp, maxp, seed) if minp.y > NETHER_CEILING or maxp.y < NETHER_FLOOR then @@ -414,38 +511,120 @@ local function on_generated(minp, maxp, seed) local zCaveStride = yCaveStride * yCaveStride local chulens = {x = yCaveStride, y = yCaveStride, z = yCaveStride} - nobj_cave = nobj_cave or minetest.get_perlin_map(np_cave, chulens) + nobj_cave = nobj_cave or minetest.get_perlin_map(mapgen.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 - for y = y0, y1 do -- Y loop first to minimise tcave calculations + local contains_nether = false + local contains_shell = false + local contains_mantle = false + local contains_ocean = false - 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 y = y0, y1 do -- Y loop first to minimise tcave & lava-sea calculations + + local sea_level, cavern_limit_distance = mapgen.find_nearest_lava_sealevel(y) + local above_lavasea = y > sea_level + local below_lavasea = y < sea_level + + local tcave_adj, centerRegionLimit_adj = mapgen.get_mapgenblend_adjustments(y) + local tcave = TCAVE + tcave_adj + local tmantle = CENTER_REGION_LIMIT + centerRegionLimit_adj -- cavern_noise_adj already contains central_region_limit_adj, so tmantle is only for comparisons when cavern_noise_adj hasn't been added to the noise value + local cavern_noise_adj = + CENTER_REGION_LIMIT * (cavern_limit_distance * cavern_limit_distance * cavern_limit_distance) - + centerRegionLimit_adj -- cavern_noise_adj gets added to noise value instead of added to the limit np_noise is compared against, so subtract centerRegionLimit_adj so subtract centerRegionLimit_adj instead of adding for z = z0, z1 do local vi = area:index(x0, y, z) -- Initial voxelmanip index local ni = (z - z0) * zCaveStride + (y - y0) * yCaveStride + 1 + local noise2di = 1 + (z - z0) * yCaveStride for x = x0, x1 do - local id = data[vi] -- Existing node + local cave_noise = nvals_cave[ni] - if nvals_cave[ni] > tcave then + if cave_noise > tcave then + -- Prime region + -- This was the only region in initial versions of the Nether mod. + -- It is the only region that portals from the surface will open into. 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 + contains_nether = true + + elseif -cave_noise > tcave then + -- Secondary/spare region + -- This secondary region is unused until someone decides to do something cool or novel with it. + -- Reaching here would require the player to first find and journey through the central region, + -- as it's always separated from the Prime region by the central region. + + data[vi] = c_netherrack -- For now I've just left this region as solid netherrack instead of air. + + -- Only set contains_nether to true here if you want tunnels created between the secondary region + -- and the central region. + --contains_nether = true + --data[vi] = c_air + else + -- netherrack walls and/or center region/mantle + 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 abs_cave_noise_adjusted >= CENTER_CAVERN_LIMIT then + + local id = data[vi] -- Check existing node to avoid removing dungeons + if id == c_air or id == c_native_mapgen then + if abs_cave_noise < tmantle then + data[vi] = c_netherrack_deep + else + -- the shell seperating the mantle from the rest of the nether... + data[vi] = c_netherrack -- excavate_dungeons() will mostly reverse this inside dungeons + contains_shell = true + end + end + + elseif above_lavasea then + data[vi] = c_air + contains_mantle = 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 + else + data[vi] = c_lava_crust + contains_ocean = true + end end ni = ni + 1 vi = vi + 1 + noise2di = noise2di + 1 end end end + if contains_mantle or contains_ocean then + mapgen.add_basalt_columns(data, area, minp, maxp) -- function provided by mapgen_mantle.lua + end + + if contains_nether and contains_mantle then + tunnelCandidate_count = tunnelCandidate_count + 1 + local success = mapgen.excavate_tunnel_to_center_of_the_nether(data, area, nvals_cave, minp, maxp) -- function provided by mapgen_mantle.lua + if success then tunnel_count = tunnel_count + 1 end + end + total_chunk_count = total_chunk_count + 1 + if total_chunk_count % 50 == 0 then + debugf( + "%s of %s chunks contain both nether and lava-sea (%s%%), %s chunks generated a pathway (%s%%)", + tunnelCandidate_count, + total_chunk_count, + math_floor(tunnelCandidate_count * 100 / total_chunk_count), + tunnel_count, + math_floor(tunnel_count * 100 / total_chunk_count) + ) + 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) @@ -453,10 +632,9 @@ local function on_generated(minp, maxp, seed) 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) + minetest.generate_decorations(vm) + vm:set_lighting({day = 0, night = 0}, minp, maxp) vm:calc_lighting() vm:update_liquids() @@ -467,7 +645,7 @@ end -- use knowledge of the nether mapgen algorithm to return a suitable ground level for placing a portal. -- player_name is optional, allowing a player to spawn a remote portal in their own protected areas. function nether.find_nether_ground_y(target_x, target_z, start_y, player_name) - local nobj_cave_point = minetest.get_perlin(np_cave) + local nobj_cave_point = minetest.get_perlin(mapgen.np_cave) local air = 0 -- Consecutive air nodes found local minp_schem, maxp_schem = nether.get_schematic_volume({x = target_x, y = 0, z = target_z}, nil, "nether_portal") diff --git a/mapgen_decorations.lua b/mapgen_decorations.lua index 7005da4..5ceb5a6 100644 --- a/mapgen_decorations.lua +++ b/mapgen_decorations.lua @@ -25,10 +25,16 @@ -- emerged or not before the decoration was placed. local allow_lava_decorations = nether.useBiomes +-- Keep compatibility with mapgen_nobiomes.lua, so hardcoding 128 +-- instead of using nether.mapgen.BLEND +local decoration_ceiling = nether.DEPTH_CEILING - 128 +local decoration_floor = nether.DEPTH_FLOOR + 128 + 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 D = {name = "nether:rack_deep", prob = 255} local S = {name = "nether:sand", prob = 255, force_place = true} local L = {name = "default:lava_source", prob = 255, force_place = true} local F = {name = "nether:fumarole", prob = 255, force_place = true} @@ -125,8 +131,8 @@ minetest.register_decoration({ sidelen = 80, fill_ratio = 0.0003, biomes = {"nether_caverns"}, - y_max = nether.DEPTH_CEILING, -- keep compatibility with mapgen_nobiomes.lua - y_min = nether.DEPTH_FLOOR, + y_max = decoration_ceiling, + y_min = decoration_floor, schematic = schematic_GlowstoneStalactite, flags = "place_center_x,place_center_z,force_placement,all_ceilings", place_offset_y=-3 @@ -139,8 +145,8 @@ minetest.register_decoration({ sidelen = 80, fill_ratio = 0.0008, biomes = {"nether_caverns"}, - y_max = nether.DEPTH_CEILING, -- keep compatibility with mapgen_nobiomes.lua - y_min = nether.DEPTH_FLOOR, + y_max = decoration_ceiling, + y_min = decoration_floor, schematic = schematic_GlowstoneStalactite, replacements = {["nether:glowstone"] = "nether:rack"}, flags = "place_center_x,place_center_z,all_ceilings", @@ -148,6 +154,193 @@ minetest.register_decoration({ }) +local schematic_GreaterStalactite = { + size = {x = 3, y = 23, z = 3}, + data = { -- note that data is upside down + + _, _, _, + _, _, _, + _, _, _, + _, _, _, + _, _, _, + _, _, _, + _, _, _, + _, _, _, + _, _, _, + _, _, _, + _, _, _, + _, _, _, + _, _, _, + _, D, _, + _, D, _, + _, D, _, + _, D, _, + D, D, D, + D, D, D, + D, D, D, + _, D, _, + _, _, _, + _, _, _, + + _, D, _, -- ypos 0, prob 85% (218/255) + _, D, _, -- ypos 1, prob 85% (218/255) + _, D, _, -- ypos 2, prob 85% (218/255) + _, D, _, -- ypos 3, prob 85% (218/255) + _, D, _, -- ypos 4, prob 85% (218/255) + _, D, _, -- ypos 5, prob 85% (218/255) + _, D, _, -- ypos 6, prob 85% (218/255) + _, D, _, -- ypos 7, prob 85% (218/255) + _, D, _, -- ypos 8, prob 85% (218/255) + _, D, D, -- ypos 9, prob 50% (128/256) to make half of stalactites asymmetric + _, D, D, -- ypos 10, prob 50% (128/256) to make half of stalactites asymmetric + _, D, D, -- ypos 11, prob 50% (128/256) to make half of stalactites asymmetric + _, D, D, -- ypos 12, prob 50% (128/256) to make half of stalactites asymmetric + D, D, D, -- ypos 13, prob 75% (192/256) + D, D, D, -- ypos 14, prob 75% (192/256) + D, D, D, -- ypos 15, prob 100% + D, D, D, -- ypos 16, prob 100% + D, D, D, -- ypos 17, prob 100% + D, D, D, -- ypos 18, prob 100% + D, D, D, -- ypos 19, prob 75% (192/256) + D, D, D, -- ypos 20, prob 85% (218/255) + _, D, D, -- ypos 21, prob 50% (128/256) to make half of stalactites asymmetric + _, D, _, -- ypos 22, prob 100% + + _, _, _, + _, _, _, + _, _, _, + _, _, _, + _, _, _, + _, _, _, + _, _, _, + _, _, _, + _, _, _, + _, _, _, + _, _, _, + _, _, _, + _, D, _, + _, D, _, + _, D, _, + _, D, _, + _, D, _, + D, D, D, + D, D, D, + D, D, D, + _, D, _, + _, D, _, + _, _, _, + + }, + -- 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 = 21, prob = 128}, + {ypos = 20, prob = 218}, + {ypos = 19, prob = 192}, + {ypos = 14, prob = 192}, + {ypos = 13, prob = 192}, + {ypos = 12, prob = 128}, + {ypos = 11, prob = 128}, + {ypos = 10, prob = 128}, + {ypos = 9, prob = 128}, + {ypos = 8, prob = 218}, + {ypos = 7, prob = 218}, + {ypos = 6, prob = 218}, + {ypos = 5, prob = 218}, + {ypos = 4, prob = 218}, + {ypos = 3, prob = 218}, + {ypos = 2, prob = 218}, + {ypos = 1, prob = 218}, + {ypos = 0, prob = 218} + } +} + + + +-- A stalagmite is an upsidedown stalactite, so +-- use the GreaterStalactite to create a ToweringStalagmite schematic +local schematic_ToweringStalagmite = { + size = schematic_GreaterStalactite.size, + data = {}, + yslice_prob = {} +} +local array_length = #schematic_GreaterStalactite.data + 1 +for i, node in ipairs(schematic_GreaterStalactite.data) do + schematic_ToweringStalagmite.data[array_length - i] = node +end +y_size = schematic_GreaterStalactite.size.y +for i, node in ipairs(schematic_GreaterStalactite.yslice_prob) do + schematic_ToweringStalagmite.yslice_prob[i] = { + -- we can safely lower the prob. to gain more variance because floor based schematics + -- don't have the bug where missing lines moves them away from the surface + prob = schematic_GreaterStalactite.yslice_prob[i].prob - 20, + ypos = y_size - 1 - schematic_GreaterStalactite.yslice_prob[i].ypos + } +end + +minetest.register_decoration({ + name = "Deep-glowstone stalactite", + deco_type = "schematic", + place_on = "nether:rack_deep", + sidelen = 80, + fill_ratio = 0.0003, + biomes = {"nether_caverns"}, + y_max = decoration_ceiling, + y_min = decoration_floor, + schematic = schematic_GlowstoneStalactite, + replacements = {["nether:rack"] = "nether:rack_deep", ["nether:glowstone"] = "nether:glowstone_deep"}, + flags = "place_center_x,place_center_z,force_placement,all_ceilings", + place_offset_y=-3 +}) + +minetest.register_decoration({ + name = "Deep-glowstone stalactite outgrowth", + deco_type = "schematic", + place_on = "nether:glowstone_deep", + sidelen = 40, + fill_ratio = 0.15, + biomes = {"nether_caverns"}, + y_max = decoration_ceiling, + y_min = decoration_floor, + schematic = { + size = {x = 1, y = 4, z = 1}, + data = { G, G, G, G } + }, + replacements = {["nether:glowstone"] = "nether:glowstone_deep"}, + flags = "place_center_x,place_center_z,all_ceilings", +}) + +minetest.register_decoration({ + name = "Deep-netherrack stalactite", + deco_type = "schematic", + place_on = "nether:rack_deep", + sidelen = 80, + fill_ratio = 0.0003, + biomes = {"nether_caverns"}, + y_max = decoration_ceiling, + y_min = decoration_floor, + schematic = schematic_GlowstoneStalactite, + replacements = {["nether:rack"] = "nether:rack_deep", ["nether:glowstone"] = "nether:rack_deep"}, + flags = "place_center_x,place_center_z,force_placement,all_ceilings", + place_offset_y=-3 +}) + +minetest.register_decoration({ + name = "Deep-netherrack towering stalagmite", + deco_type = "schematic", + place_on = "nether:rack_deep", + sidelen = 80, + fill_ratio = 0.001, + biomes = {"nether_caverns"}, + y_max = decoration_ceiling, + y_min = decoration_floor, + schematic = schematic_ToweringStalagmite, + replacements = {["nether:basalt"] = "nether:rack_deep"}, + flags = "place_center_x,place_center_z,force_placement,all_floors", + place_offset_y=-2 +}) + -- ======================================= -- Concealed crevice / Lava sinkhole -- ======================================= @@ -161,8 +354,8 @@ if allow_lava_decorations then sidelen = 80, fill_ratio = 0.002, biomes = {"nether_caverns"}, - y_max = nether.DEPTH, -- keep compatibility with mapgen_nobiomes.lua - y_min = nether.DEPTH_FLOOR, + y_max = decoration_ceiling, + y_min = decoration_floor, schematic = { size = {x = 4, y = 7, z = 4}, data = { -- note that data is upside down @@ -263,8 +456,8 @@ minetest.register_decoration({ sidelen = 80, fill_ratio = 0.005, biomes = {"nether_caverns"}, - y_max = nether.DEPTH, - y_min = nether.DEPTH_FLOOR, + y_max = decoration_ceiling, + y_min = decoration_floor, schematic = schematic_fumarole, replacements = replacements_full, flags = "place_center_x,place_center_z,all_floors", @@ -292,8 +485,8 @@ minetest.register_decoration({ sidelen = 8, noise_params = fumarole_clump_noise, biomes = {"nether_caverns"}, - y_max = nether.DEPTH, - y_min = nether.DEPTH_FLOOR, + y_max = decoration_ceiling, + y_min = decoration_floor, schematic = schematic_fumarole, replacements = replacements_full, flags = "place_center_x,place_center_z,all_floors", @@ -308,8 +501,8 @@ minetest.register_decoration({ sidelen = 8, noise_params = fumarole_clump_noise, biomes = {"nether_caverns"}, - y_max = nether.DEPTH, - y_min = nether.DEPTH_FLOOR, + y_max = decoration_ceiling, + y_min = decoration_floor, schematic = schematic_fumarole, replacements = replacements_slab, flags = "place_center_x,place_center_z,all_floors", @@ -324,8 +517,8 @@ minetest.register_decoration({ sidelen = 8, noise_params = fumarole_clump_noise, biomes = {"nether_caverns"}, - y_max = nether.DEPTH, - y_min = nether.DEPTH_FLOOR, + y_max = decoration_ceiling, + y_min = decoration_floor, schematic = { size = {x = 4, y = 4, z = 4}, data = { -- note that data is upside down @@ -363,8 +556,8 @@ minetest.register_decoration({ sidelen = 8, noise_params = fumarole_clump_noise, biomes = {"nether_caverns"}, - y_max = nether.DEPTH, - y_min = nether.DEPTH_FLOOR, + y_max = decoration_ceiling, + y_min = decoration_floor, schematic = { size = {x = 4, y = 5, z = 4}, data = { -- note that data is upside down diff --git a/mapgen_mantle.lua b/mapgen_mantle.lua new file mode 100644 index 0000000..0add888 --- /dev/null +++ b/mapgen_mantle.lua @@ -0,0 +1,476 @@ +--[[ + + Nether mod for minetest + + This file contains helper functions for generating the Mantle + (AKA center region), which are moved into a separate file to keep the + size of mapgen.lua manageable. + + + Copyright (C) 2021 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. + +]]-- + + +local debugf = nether.debug +local mapgen = nether.mapgen + +local BASALT_COLUMN_UPPER_LIMIT = mapgen.BASALT_COLUMN_UPPER_LIMIT +local BASALT_COLUMN_LOWER_LIMIT = mapgen.BASALT_COLUMN_LOWER_LIMIT + + +-- 2D noise for basalt formations +local np_basalt = { + offset =-0.85, + scale = 1, + spread = {x = 46, y = 46, z = 46}, + seed = 1000, + octaves = 5, + persistence = 0.5, + lacunarity = 2.6, + flags = "eased" +} + + +-- Buffers and objects we shouldn't recreate every on_generate + +local nobj_basalt = nil +local nbuf_basalt = {} +local cavePerlin = nil + +-- Content ids + +local c_air = minetest.get_content_id("air") +local c_netherrack_deep = minetest.get_content_id("nether:rack_deep") +local c_glowstone = minetest.get_content_id("nether:glowstone") +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") + + +-- Math funcs +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 + +function random_unit_vector() + return vector.normalize({ + x = math.random() - 0.5, + y = math.random() - 0.5, + z = math.random() - 0.5 + }) +end + +-- returns the smallest component in the vector +function vector_min(v) + return math_min(v.x, math_min(v.y, v.z)) +end + + +-- Mantle mapgen functions (AKA Center region) + +-- 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. +mapgen.find_nearest_lava_sealevel = function(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 sealevel = math.floor((y + 80) / 160) * 160 + --local sealevel = math.floor((y + 120) / 240) * 240 + + 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 + + + + +mapgen.add_basalt_columns = function(data, area, minp, maxp) + -- Basalt columns are structures found in lava oceans, and the only way to obtain + -- nether basalt. + -- Their x, z position is determined by a 2d noise map and a 2d slice of the cave + -- noise (taken at lava-sealevel). + + local x0, y0, z0 = minp.x, math_max(minp.y, nether.DEPTH_FLOOR), minp.z + local x1, y1, z1 = maxp.x, math_min(maxp.y, nether.DEPTH_CEILING), maxp.z + + local yStride = area.ystride + local yCaveStride = x1 - x0 + 1 + + cavePerlin = cavePerlin or minetest.get_perlin(mapgen.np_cave) + nobj_basalt = nobj_basalt or minetest.get_perlin_map(np_basalt, {x = yCaveStride, y = yCaveStride}) + local nvals_basalt = nobj_basalt:get_2d_map_flat({x=minp.x, y=minp.z}, {x=yCaveStride, y=yCaveStride}, nbuf_basalt) + + local nearest_sea_level, _ = mapgen.find_nearest_lava_sealevel(math_floor((y0 + y1) / 2)) + + local leeway = mapgen.CENTER_CAVERN_LIMIT * 0.18 + + for z = z0, z1 do + local noise2di = 1 + (z - z0) * yCaveStride + + for x = x0, x1 do + + local basaltNoise = nvals_basalt[noise2di] + if basaltNoise > 0 then + -- a basalt column is here + + local abs_sealevel_cave_noise = math_abs(cavePerlin:get3d({x = x, y = nearest_sea_level, z = z})) + + -- Add Some quick deterministic noise to the column heights + -- This is probably not good noise, but it doesn't have to be. + local fastNoise = 17 + fastNoise = 37 * fastNoise + y0 + fastNoise = 37 * fastNoise + z + fastNoise = 37 * fastNoise + x + fastNoise = 37 * fastNoise + math_floor(basaltNoise * 32) + + local columnHeight = basaltNoise * 18 + ((fastNoise % 3) - 1) + + -- columns should drop below sealevel where lava rivers are flowing + -- i.e. anywhere abs_sealevel_cave_noise < BASALT_COLUMN_LOWER_LIMIT + -- And we'll also have it drop off near the edges of the lava ocean so that + -- basalt columns can only be found by the player reaching a lava ocean. + local lowerClip = (math_min(math_max(abs_sealevel_cave_noise, BASALT_COLUMN_LOWER_LIMIT - leeway), BASALT_COLUMN_LOWER_LIMIT + leeway) - BASALT_COLUMN_LOWER_LIMIT) / leeway + local upperClip = (math_min(math_max(abs_sealevel_cave_noise, BASALT_COLUMN_UPPER_LIMIT - leeway), BASALT_COLUMN_UPPER_LIMIT + leeway) - BASALT_COLUMN_UPPER_LIMIT) / leeway + local columnHeightAdj = lowerClip * -upperClip -- all are values between 1 and -1 + + columnHeight = columnHeight + math_floor(columnHeightAdj * 12 - 12) + + local vi = area:index(x, y0, z) -- Initial voxelmanip index + + for y = y0, y1 do -- Y loop first to minimise tcave & lava-sea calculations + + if y < nearest_sea_level + columnHeight then + + local id = data[vi] -- Existing node + if id == c_lava_crust or id == c_lavasea_source or (id == c_air and y > nearest_sea_level) then + -- Avoid letting columns extend beyond the central region. + -- (checking node ids saves having to calculate abs_cave_noise_adjusted here + -- to test it against CENTER_CAVERN_LIMIT) + data[vi] = c_basalt + end + end + + vi = vi + yStride + end + end + + noise2di = noise2di + 1 + end + end +end + + +-- returns an array of points from pos1 and pos2 which deviate from a straight line +-- but which don't venture too close to a chunk boundary +function generate_waypoints(pos1, pos2, minp, maxp) + + local segSize = 10 + local maxDeviation = 7 + local minDistanceFromChunkWall = 5 + + local pathVec = vector.subtract(pos2, pos1) + local pathVecNorm = vector.normalize(pathVec) + local pathLength = vector.distance(pos1, pos2) + local minBound = vector.add(minp, minDistanceFromChunkWall) + local maxBound = vector.subtract(maxp, minDistanceFromChunkWall) + + local result = {} + result[1] = pos1 + + local segmentCount = math_floor(pathLength / segSize) + for i = 1, segmentCount do + local waypoint = vector.add(pos1, vector.multiply(pathVec, i / (segmentCount + 1))) + + -- shift waypoint a few blocks in a random direction orthogonally to the pathVec, to make the path crooked. + local crossProduct + repeat + crossProduct = vector.normalize(vector.cross(pathVecNorm, random_unit_vector())) + until vector.length(crossProduct) > 0 + local deviation = vector.multiply(crossProduct, math.random(1, maxDeviation)) + waypoint = vector.add(waypoint, deviation) + waypoint = { + x = math_min(maxBound.x, math_max(minBound.x, waypoint.x)), + y = math_min(maxBound.y, math_max(minBound.y, waypoint.y)), + z = math_min(maxBound.z, math_max(minBound.z, waypoint.z)) + } + + result[#result + 1] = waypoint + end + + result[#result + 1] = pos2 + return result +end + + +function excavate_pathway(data, area, nether_pos, center_pos, minp, maxp) + + local ystride = area.ystride + local zstride = area.zstride + + math.randomseed(nether_pos.x + 10 * nether_pos.y + 100 * nether_pos.z) -- so each tunnel generates deterministically (this doesn't have to be a quality seed) + local dist = math_floor(vector.distance(nether_pos, center_pos)) + local waypoints = generate_waypoints(nether_pos, center_pos, minp, maxp) + + -- 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 + -- Bresenham's line would be good here, but too much lua code + local waypointProgress = (#waypoints - 1) * i / dist + local segmentIndex = math_min(math_floor(waypointProgress) + 1, #waypoints - 1) -- from the integer portion of waypointProgress + local segmentInterp = waypointProgress - (segmentIndex - 1) -- the remaining fractional portion + local segmentStart = waypoints[segmentIndex] + local segmentVector = vector.subtract(waypoints[segmentIndex + 1], segmentStart) + local pos = vector.round(vector.add(segmentStart, vector.multiply(segmentVector, segmentInterp))) + + 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_netherrack_deep 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 + + + -- limit tunnel radius to roughly the closest that startPos or stopPos comes to minp-maxp, so we + -- don't end up exceeding minp-maxp and having excavation filled in when the next chunk is generated. + local startPos, stopPos = linedata[first_filled_index].pos, linedata[last_filled_index].pos + local radiusLimit = vector_min(vector.subtract(startPos, minp)) + radiusLimit = math_min(radiusLimit, vector_min(vector.subtract(stopPos, minp))) + radiusLimit = math_min(radiusLimit, vector_min(vector.subtract(maxp, startPos))) + radiusLimit = math_min(radiusLimit, vector_min(vector.subtract(maxp, stopPos))) + + if radiusLimit < 4 then -- This is a logic check, ignore it. It could be commented out + -- 4 is (79 - 75), and shouldn't be possible if sampling-skip was 10 + -- i.e. if sampling-skip was 10 then {5, 15, 25, 35, 45, 55, 65, 75} should be sampled from possible positions 0 to 79 + debugf("Error: radiusLimit %s is smaller then half the sampling distance. min %s, max %s, start %s, stop %s", radiusLimit, minp, maxp, startPos, stopPos) + end + radiusLimit = radiusLimit + 1 -- chunk walls wont be visibly flat if the radius only exceeds it a little ;) + + -- Second pass: excavate + local start_index, stop_index = math_max(1, first_filled_index - 2), math_min(#linedata, last_filled_index + 3) + for i = start_index, stop_index, 3 do + + -- Adjust radius so that tunnels start wide but thin out in the middle + local distFromEnds = 1 - math_abs(((start_index + stop_index) / 2) - i) / ((stop_index - start_index) / 2) -- from 0 to 1, with 0 at ends and 1 in the middle + -- Have it more flaired at the ends, rather than linear. + -- i.e. sizeAdj approaches 1 quickly as distFromEnds increases + local distFromMiddle = 1 - distFromEnds + local sizeAdj = 1 - (distFromMiddle * distFromMiddle * distFromMiddle) + + local radius = math_min(radiusLimit, math.random(50 - (25 * sizeAdj), 80 - (45 * sizeAdj)) / 10) + local radiusCubed = radius * radius + local radiusCeil = math_floor(radius + 0.5) + + linedata[i].radius = radius -- Needed in third pass + linedata[i].distFromEnds = distFromEnds -- Needed in third pass + + local vi = linedata[i].vi + for z = -radiusCeil, radiusCeil do + local vi_z = vi + z * zstride + for y = -radiusCeil, radiusCeil do + local vi_zy = vi_z + y * ystride + local xSquaredLimit = radiusCubed - (z * z + y * y) + for x = -radiusCeil, radiusCeil do + if x * x < xSquaredLimit then + data[vi_zy + x] = c_air + end + end + end + end + + end + + -- Third pass: decorate + -- Add glowstones to make tunnels to the mantle easyier to find + -- https://i.imgur.com/sRA28x7.jpg + for i = start_index, stop_index, 3 do + if linedata[i].distFromEnds < 0.3 then + local glowcount = 0 + local radius = linedata[i].radius + for _ = 1, 20 do + local testPos = vector.round(vector.add(linedata[i].pos, vector.multiply(random_unit_vector(), radius + 0.5))) + local vi = area:indexp(testPos) + if data[vi] ~= c_air then + data[vi] = c_glowstone + glowcount = glowcount + 1 + --else + -- data[vi] = c_debug + end + if glowcount >= 2 then break end + end + end + end + +end + + +-- excavates a tunnel connecting the Primary or Secondary region with the mantle / central region +-- if a suitable path is found. +-- Returns true if successful +mapgen.excavate_tunnel_to_center_of_the_nether = function(data, area, nvals_cave, minp, maxp) + + local result = false + 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 = mapgen.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 + 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 < mapgen.CENTER_CAVERN_LIMIT and highest > mapgen.TCAVE + 0.03 then + + local mantle_y = area:position(lowest_vi).y + local sealevel, cavern_limit_distance = mapgen.find_nearest_lava_sealevel(mantle_y) + local _, centerRegionLimit_adj = mapgen.get_mapgenblend_adjustments(mantle_y) + local cavern_noise_adj = + mapgen.CENTER_REGION_LIMIT * (cavern_limit_distance * cavern_limit_distance * cavern_limit_distance) - + centerRegionLimit_adj -- cavern_noise_adj gets added to noise value instead of added to the limit np_noise is compared against, so subtract centerRegionLimit_adj instead of adding + + if lowest + cavern_noise_adj < mapgen.CENTER_CAVERN_LIMIT then + excavate_pathway(data, area, area:position(highest_vi), area:position(lowest_vi), minp, maxp) + result = true + end + end + return result +end + + +minetest.register_chatcommand("nether_whereami", + { + description = "Describes which region of the nether the player is in", + privs = {debug = true}, + 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.DEPTH_CEILING or pos.y < nether.DEPTH_FLOOR then + return true, "The Overworld" + end + + cavePerlin = cavePerlin or minetest.get_perlin(mapgen.np_cave) + local densityNoise = cavePerlin:get_3d(pos) + local sea_level, cavern_limit_distance = mapgen.find_nearest_lava_sealevel(pos.y) + local tcave_adj, centerRegionLimit_adj = mapgen.get_mapgenblend_adjustments(pos.y) + local tcave = mapgen.TCAVE + tcave_adj + local tmantle = mapgen.CENTER_REGION_LIMIT + centerRegionLimit_adj + local cavern_noise_adj = + mapgen.CENTER_REGION_LIMIT * (cavern_limit_distance * cavern_limit_distance * cavern_limit_distance) - + centerRegionLimit_adj -- cavern_noise_adj gets added to noise value instead of added to the limit np_noise is compared against, so subtract centerRegionLimit_adj so subtract centerRegionLimit_adj instead of adding + + local desc + + if densityNoise > tcave then + desc = "Positive nether" + elseif -densityNoise > tcave then + desc = "Negative nether" + elseif math_abs(densityNoise) < tmantle then + desc = "Mantle" + + if math_abs(densityNoise) + cavern_noise_adj < mapgen.CENTER_CAVERN_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 + + if tcave_adj > 0 then + desc = desc .. ", approaching y boundary of Nether" + end + + return true, "[Perlin " .. (math_floor(densityNoise * 1000) / 1000) .. "] " .. desc + end + } +) diff --git a/mapgen_nobiomes.lua b/mapgen_nobiomes.lua index 103537d..28792b8 100644 --- a/mapgen_nobiomes.lua +++ b/mapgen_nobiomes.lua @@ -2,6 +2,12 @@ Nether mod for minetest + "mapgen_nobiomes.lua" is the legacy version of the mapgen, only used + in older versions of Minetest or in v6 worlds. + "mapgen.lua" is the modern biomes-based Nether mapgen, which + requires Minetest v5.1 or greater + + Copyright (C) 2013 PilzAdam Permission to use, copy, modify, and/or distribute this software for @@ -190,8 +196,7 @@ minetest.register_on_generated(function(minp, maxp, seed) 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_decorations(vm) vm:set_lighting({day = 0, night = 0}, minp, maxp) vm:calc_lighting() diff --git a/nodes.lua b/nodes.lua index ea94206..2af360e 100644 --- a/nodes.lua +++ b/nodes.lua @@ -75,6 +75,15 @@ minetest.register_node("nether:rack", { sounds = default.node_sound_stone_defaults(), }) +-- Deep Netherrack, found in the mantle / central magma layers +minetest.register_node("nether:rack_deep", { + description = S("Deep-Netherrack"), + tiles = {"nether_rack_deep.png"}, + is_ground_content = true, + groups = {cracky = 3, level = 2}, + sounds = default.node_sound_stone_defaults(), +}) + minetest.register_node("nether:sand", { description = S("Nethersand"), tiles = {"nether_sand.png"}, @@ -95,6 +104,17 @@ minetest.register_node("nether:glowstone", { sounds = default.node_sound_glass_defaults(), }) +-- Deep glowstone, found in the mantle / central magma layers +minetest.register_node("nether:glowstone_deep", { + description = S("Deep-Glowstone"), + tiles = {"nether_glowstone_deep.png"}, + is_ground_content = true, + light_source = 14, + paramtype = "light", + groups = {cracky = 3, oddly_breakable_by_hand = 3}, + sounds = default.node_sound_glass_defaults(), +}) + minetest.register_node("nether:brick", { description = S("Nether Brick"), tiles = {"nether_brick.png"}, @@ -111,6 +131,15 @@ minetest.register_node("nether:brick_compressed", { sounds = default.node_sound_stone_defaults(), }) +-- A decorative node which can only be obtained from dungeons or structures +minetest.register_node("nether:brick_cracked", { + description = S("Cracked Nether Brick"), + tiles = {"nether_brick_cracked.png"}, + is_ground_content = false, + groups = {cracky = 2, level = 2}, + sounds = default.node_sound_stone_defaults(), +}) + local fence_texture = "default_fence_overlay.png^nether_brick.png^default_fence_overlay.png^[makealpha:255,126,126" @@ -169,6 +198,345 @@ if minetest.get_modpath("moreblocks") then end +-- Mantle nodes + +-- Nether basalt is intended as a valuable material and possible portalstone - an alternative to +-- obsidian that's available for other mods to use. +-- It cannot be found in the regions of the nether where Nether portals link to, so requires a journey to obtain. +minetest.register_node("nether:basalt", { + description = S("Blue Basalt"), + tiles = { + "nether_basalt.png", + "nether_basalt.png", + "nether_basalt_side.png", + "nether_basalt_side.png", + "nether_basalt_side.png", + "nether_basalt_side.png" + }, + is_ground_content = true, + groups = {cracky = 1, level = 3}, -- set proper digging times and uses, and maybe explosion immune if api handles that + on_blast = function() --[[blast proof]] end, + sounds = default.node_sound_stone_defaults(), +}) + +-- Potentially a portalstone, but will also be a stepping stone between basalt +-- and chiseled basalt. +-- It can only be introduced by the biomes-based mapgen, since it requires the +-- MT 5.0 world-align texture features. +minetest.register_node("nether:basalt_hewn", { + description = S("Hewn Basalt"), + tiles = {{ + name = "nether_basalt_hewn.png", + align_style = "world", + scale = 2 + }}, + inventory_image = minetest.inventorycube( + "nether_basalt_hewn.png^[sheet:2x2:0,0", + "nether_basalt_hewn.png^[sheet:2x2:0,1", + "nether_basalt_hewn.png^[sheet:2x2:1,1" + ), + is_ground_content = false, + groups = {cracky = 1, level = 2}, + on_blast = function() --[[blast proof]] end, + sounds = default.node_sound_stone_defaults(), +}) + +-- Chiselled basalt is intended as a portalstone - an alternative to obsidian that's +-- available for other mods to use. It is crafted from Hewn Basalt. +-- It should only be introduced by the biomes-based mapgen, since in future it may +-- require the MT 5.0 world-align texture features. +minetest.register_node("nether:basalt_chiselled", { + description = S("Chiselled Basalt"), + tiles = { + "nether_basalt_chiselled_top.png", + "nether_basalt_chiselled_top.png" .. "^[transformFY", + "nether_basalt_chiselled_side.png", + "nether_basalt_chiselled_side.png", + "nether_basalt_chiselled_side.png", + "nether_basalt_chiselled_side.png" + }, + inventory_image = minetest.inventorycube( + "nether_basalt_chiselled_top.png", + "nether_basalt_chiselled_side.png", + "nether_basalt_chiselled_side.png" + ), + paramtype2 = "facedir", + is_ground_content = false, + groups = {cracky = 1, level = 2}, + on_blast = function() --[[blast proof]] end, + sounds = default.node_sound_stone_defaults(), +}) + + +-- Lava-sea source +-- This is a lava source using a different animated texture so that each node +-- is out of phase in its animation from its neighbor. This prevents the magma +-- ocean from visually clumping together into a patchwork of 16x16 squares. +-- It can only be used by the biomes-based mapgen, since it requires the MT 5.0 +-- world-align texture features. +local lavasea_source = {} +local lava_source = minetest.registered_nodes["default:lava_source"] +for key, value in pairs(lava_source) do lavasea_source[key] = value end +lavasea_source.name = nil +lavasea_source.tiles = { + { + name = "nether_lava_source_animated.png", + backface_culling = false, + align_style = "world", + scale = 2, + animation = { + type = "vertical_frames", + aspect_w = 32, + aspect_h = 32, + length = 3.0, + }, + }, + { + name = "nether_lava_source_animated.png", + backface_culling = true, + align_style = "world", + scale = 2, + animation = { + type = "vertical_frames", + aspect_w = 32, + aspect_h = 32, + length = 3.0, + }, + }, +} +lavasea_source.liquid_alternative_source = "nether:lava_source" +lavasea_source.inventory_image = minetest.inventorycube( + "nether_lava_source_animated.png^[sheet:2x16:0,0", + "nether_lava_source_animated.png^[sheet:2x16:0,1", + "nether_lava_source_animated.png^[sheet:2x16:1,1" +) +minetest.register_node("nether:lava_source", lavasea_source) + + +-- a place to store the original ABM function so nether.cool_lava() can call it +local original_cool_lava_action + +nether.cool_lava = function(pos, node) + + local pos_above = {x = pos.x, y = pos.y + 1, z = pos.z} + local node_above = minetest.get_node(pos_above) + + -- Evaporate water sitting above lava, if it's in the Nether. + -- (we don't want Nether mod to affect overworld lava mechanics) + if minetest.get_node_group(node_above.name, "water") > 0 and + pos.y < nether.DEPTH_CEILING and pos.y > nether.DEPTH_FLOOR then + -- cools_lava might be a better group to check for, but perhaps there's + -- something in that group that isn't a liquid and shouldn't be evaporated? + minetest.swap_node(pos_above, {name="air"}) + end + + -- add steam to cooling lava + minetest.add_particlespawner({ + amount = 20, + time = 0.15, + minpos = {x=pos.x - 0.4, y=pos.y - 0, z=pos.z - 0.4}, + maxpos = {x=pos.x + 0.4, y=pos.y + 0.5, z=pos.z + 0.4}, + minvel = {x = -0.5, y = 0.5, z = -0.5}, + maxvel = {x = 0.5, y = 1.5, z = 0.5}, + minacc = {x = 0, y = 0.1, z = 0}, + maxacc = {x = 0, y = 0.2, z = 0}, + minexptime = 0.5, + maxexptime = 1.3, + minsize = 1.5, + maxsize = 3.5, + texture = "nether_particle_anim4.png", + animation = { + type = "vertical_frames", + aspect_w = 7, + aspect_h = 7, + length = 1.4, + } + }) + + if node.name == "nether:lava_source" or node.name == "nether:lava_crust" then + -- use swap_node to avoid triggering the lava_crust's after_destruct + minetest.swap_node(pos, {name = "nether:basalt"}) + + minetest.sound_play("default_cool_lava", + {pos = pos, max_hear_distance = 16, gain = 0.25}, true) + else + -- chain the original ABM action to handle conventional lava + original_cool_lava_action(pos, node) + end +end + + +minetest.register_on_mods_loaded(function() + + -- register a bucket of Lava-sea source - but make it just the same bucket as default lava. + -- (by doing this in register_on_mods_loaded we don't need to declare a soft dependency) + if minetest.get_modpath("bucket") and minetest.global_exists("bucket") then + local lava_bucket = bucket.liquids["default:lava_source"] + if lava_bucket ~= nil then + local lavasea_bucket = {} + for key, value in pairs(lava_bucket) do lavasea_bucket[key] = value end + lavasea_bucket.source = "nether:lava_source" + bucket.liquids[lavasea_bucket.source] = lavasea_bucket + end + end + + -- include "nether:lava_source" in any "default:lava_source" ABMs + local function include_nether_lava(set_of_nodes) + if (type(set_of_nodes) == "table") then + for _, nodename in pairs(set_of_nodes) do + if nodename == "default:lava_source" then + -- I'm amazed this works, but it does + table.insert(set_of_nodes, "nether:lava_source") + break; + end + end + end + end + + for _, abm in pairs(minetest.registered_abms) do + include_nether_lava(abm.nodenames) + include_nether_lava(abm.neighbors) + if abm.label == "Lava cooling" and abm.action ~= nil then + -- lets have lava_crust cool as well + original_cool_lava_action = abm.action + abm.action = nether.cool_lava + table.insert(abm.nodenames, "nether:lava_crust") + end + end + for _, lbm in pairs(minetest.registered_lbms) do + include_nether_lava(lbm.nodenames) + end + --minetest.log("minetest.registered_abms" .. dump(minetest.registered_abms)) + --minetest.log("minetest.registered_lbms" .. dump(minetest.registered_lbms)) +end) + +-- creates a lava splash, and leaves lava_source in place of the lava_crust +local function smash_lava_crust(pos, playsound) + + local lava_particlespawn_def = { + amount = 6, + time = 0.1, + minpos = {x=pos.x - 0.5, y=pos.y + 0.3, z=pos.z - 0.5}, + maxpos = {x=pos.x + 0.5, y=pos.y + 0.5, z=pos.z + 0.5}, + minvel = {x = -1.5, y = 1.5, z = -1.5}, + maxvel = {x = 1.5, y = 5, z = 1.5}, + minacc = {x = 0, y = -10, z = 0}, + maxacc = {x = 0, y = -10, z = 0}, + minexptime = 1, + maxexptime = 1, + minsize = .2, + maxsize = .8, + texture = "^[colorize:#A00:255", + glow = 8 + } + minetest.add_particlespawner(lava_particlespawn_def) + lava_particlespawn_def.texture = "^[colorize:#FB0:255" + lava_particlespawn_def.maxvel.y = 3 + lava_particlespawn_def.glow = 12 + minetest.add_particlespawner(lava_particlespawn_def) + + minetest.set_node(pos, {name = "default:lava_source"}) + + if math.random(1, 3) == 1 and minetest.registered_nodes["fire:basic_flame"] ~= nil then + -- occasionally brief flames will be seen when breaking lava crust + local posAbove = {x = pos.x, y = pos.y + 1, z = pos.z} + if minetest.get_node(posAbove).name == "air" then + minetest.set_node(posAbove, {name = "fire:basic_flame"}) + minetest.get_node_timer(posAbove):set(math.random(7, 15) / 10, 0) + --[[ commented out because the flame sound plays for too long + if minetest.global_exists("fire") and fire.update_player_sound ~= nil then + -- The fire mod only updates its sound every 3 seconds, these flames will be + -- out by then, so start the sound immediately + local players = minetest.get_connected_players() + for n = 1, #players do fire.update_player_sound(players[n]) end + end]] + end + end + + if playsound then + minetest.sound_play( + "nether_lava_bubble", + -- this sample was encoded at 3x speed to reduce .ogg file size + -- at the expense of higher frequencies, so pitch it down ~3x + {pos = pos, pitch = 0.3, max_hear_distance = 8, gain = 0.4} + ) + end +end + + +-- lava_crust nodes can only be used in the biomes-based mapgen, since they require +-- the MT 5.0 world-align texture features. +minetest.register_node("nether:lava_crust", { + description = "Lava crust", + tiles = { + { + name="nether_lava_crust_animated.png", + backface_culling=true, + tileable_vertical=true, + tileable_horizontal=true, + align_style="world", + scale=2, + animation = { + type = "vertical_frames", + aspect_w = 32, + aspect_h = 32, + length = 1.8, + }, + } + }, + inventory_image = minetest.inventorycube( + "nether_lava_crust_animated.png^[sheet:2x48:0,0", + "nether_lava_crust_animated.png^[sheet:2x48:0,1", + "nether_lava_crust_animated.png^[sheet:2x48:1,1" + ), + collision_box = { + type = "fixed", + fixed = { + -- Damage is calculated "starting 0.1 above feet + -- and progressing upwards in 1 node intervals", so + -- lower this node's collision box by more than 0.1 + -- to ensure damage will be taken when standing on + -- the node. + {-0.5, -0.5, -0.5, 0.5, 0.39, 0.5} + }, + }, + selection_box = { + type = "fixed", + fixed = { + -- Keep the selection box matching the visual node, + -- rather than the collision_box. + {-0.5, -0.5, -0.5, 0.5, 0.5, 0.5} + }, + }, + + after_destruct = function(pos, oldnode) + smash_lava_crust(pos, true) + end, + after_dig_node = function(pos, oldnode, oldmetadata, digger) + end, + on_blast = function(pos, intensity) + smash_lava_crust(pos, false) + end, + + paramtype = "light", + light_source = default.LIGHT_MAX - 3, + buildable_to = false, + walkable_to = true, + is_ground_content = true, + drop = { + items = {{ + -- Allow SilkTouch-esque "pickaxes of preservation" to mine the lava crust intact, if PR #10141 gets merged. + tools = {"this line will block early MT versions which don't respect the tool_groups restrictions"}, + tool_groups = {{"pickaxe", "preservation"}}, + items = {"nether:lava_crust"} + }} + }, + --liquid_viscosity = 7, + damage_per_second = 2, + groups = {oddly_breakable_by_hand = 3, cracky = 3, explody = 1, igniter = 1}, +}) + + -- Fumaroles (Chimney's) local function fumarole_startTimer(pos, timeout_factor) @@ -393,31 +761,3 @@ local airlike_darkness = {} for k,v in pairs(minetest.registered_nodes["air"]) do airlike_darkness[k] = v end airlike_darkness.paramtype = "none" minetest.register_node("nether:airlike_darkness", airlike_darkness) - - --- Crafting - -minetest.register_craft({ - output = "nether:brick 4", - recipe = { - {"nether:rack", "nether:rack"}, - {"nether:rack", "nether:rack"}, - } -}) - -minetest.register_craft({ - output = "nether:fence_nether_brick 6", - recipe = { - {"nether:brick", "nether:brick", "nether:brick"}, - {"nether:brick", "nether:brick", "nether:brick"}, - }, -}) - -minetest.register_craft({ - output = "nether:brick_compressed", - recipe = { - {"nether:brick","nether:brick","nether:brick"}, - {"nether:brick","nether:brick","nether:brick"}, - {"nether:brick","nether:brick","nether:brick"}, - } -}) diff --git a/sounds/nether_lava_bubble.0.ogg b/sounds/nether_lava_bubble.0.ogg new file mode 100644 index 0000000000000000000000000000000000000000..4eeef69e697807fb3ff092adefafb01bebe62a75 GIT binary patch literal 7604 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 M07*qoM6N<$g1Ff;M*si- literal 0 HcmV?d00001 diff --git a/textures/nether_basalt_chiselled_side.png b/textures/nether_basalt_chiselled_side.png new file mode 100644 index 0000000000000000000000000000000000000000..46a173a16d6712dbe6b8b295f26af9e03daa8ebe GIT binary patch literal 597 zcmV-b0;>IqP)NmUZnaxtow86-g>` zUAGUAq`F;oJ)gI#3#4K!%bx4D1K9WXaIDj zZW%JlfK6in0;q^k3Z&ihdZ8Nty4)v;%q4S#w605hP9m9eUX*fdiu8H3yXxwmcG*^@ z0e8)L9>?9?ImQNnBnUA`NeK`LfL#UvO3E?b42YuL0Npk~s=GiLHrzeCiWG@Y)K&Pb z@bdiA^fDuEmK@b2;0-y~W0L7#T^luM$SH4~kMV#le+W<+891%gP zYGy7}c2&*uc-*>M``gzswsreMlDp@1+;4x)d0po*=UG)5BXUth=E%&97*YVRiv&dp zB%};?WJWGh00d|tZMVCsY#K0VsH&Ph&)185L|*3uF3D0PMHVuLB!Hn<#xlkVpsL2Q zDHcH8KCkKSJf3$FRTC6|>e+7FfC*BuZtwB%ckxBWkI7cRB%IMD+D001R)MObt} za&&2PbO2OxWo2?8XmoUNb2=|CXK8e3bz&}KZ*4DBa%E+5*@TPy0000KbVXQnLvL_- ja%pF1bO16iGBF@ja%E+5^W4i%00000NkvXXu0mjfr!N9K literal 0 HcmV?d00001 diff --git a/textures/nether_basalt_chiselled_top.png b/textures/nether_basalt_chiselled_top.png new file mode 100644 index 0000000000000000000000000000000000000000..3d6a70e63bfd7adc77f0ff868e4c68cdd280c850 GIT binary patch literal 594 zcmV-Y0JJbD_ zvMb#1+f@xDiK-i<$M#B0N-EXXwXb!M0$JT8r2=SA zCu0xE`~5pXPyk6v1b`sOhjP6>N$dm(o}Pc70r}}I)mjgL00JO5j(b9Yy=@nJwyy4~ z>My^2B}CQZ@$R+8ORBfG{}Lxls{Yto+YaFU{r{Pxsy(x2D2esUudlWf+mpCS0hkp4 zfT+#mANM-}5ZCLoL`j6mvPVe*%pA5!68!S=$Mt%<+@2Bum>GbeRG$IQIx;IB&q@qP z)gB}HC%{qZColVjr0Pj^xm<44w$AfBABh2g-(q{FyLSNRI_-NLXS({mdf#tr zok{FTP%2QM8VE_Sy0+N%*fZyO9*0U&Q40jGW zl2Ny+GK z>qc_Mz1CyxC#fV!07#w0*$zliB1sYnfZNM$_DHmiWUr6}wf9pjh@=pBOfpFWA~3T^ zl8T*dsjlm#YT{8f$WnpaYayYEtn1|>5Cni{+Y_6RkN{LkvfXPvAQc5*W+$eT*d9{q zPLNr9J!bZLK3`v7k%WmSfbC@BfJ^`<0#!e&^~=la<8gOJQmMuudB6bReg7y)WF=Z_ z&A6}2vsG1fkqt00S+)~9Va>+uK(lvE$i! z0s;>ffCR8e^^N2>4MeK5-XxO1M#i>h2s!~-)d-xBgcBq&z&OE{N)m8tkiv3P}RblN6~0Bm+y;_qYF!Lzb$Kl9@gG*?rx5J!W?=`c}Ln*U?i0JN_Ju@hjn)R~x0*s)m3G&mgzY|n< zsrFupNmgC<00C6po*g^D?A3h>f`9}Fsb@BUBb2cnAJqy7q(6WDi>l**0y48^FMEG@ zd0qP{GMU?lTawuL@C%SqRo_6_+q3MJy1GOKlq#MeNWOgecLVzJTtlz@yrYWk$bj1>-p?tBXhOLcgBNE^78WG zkaK1rRuu{R@|nWSL6_~!cs~ybVpl1U7)rUi14<1lKSYjFsn)vN0uY4Y7+qIF1~Xey zyb&Mv@mIldihDS8P?`GyqSZb5tH3^ynhr|4WN!3>4+0Rkz9- zn*>l)c?{L1pDItW44>WHZl8j;swR;*!8rB?aLf%!Rd*E-JgCGVJnOKWpsJ1)CqAkh zkgUFLv5&X`1emn^aX7!FW8|-h1!Y*N-#fSwJKakgBSyE+CnabkJ4RWVP7Ux2mpp)%-~~RcU6D zO;V-AXO|?Yde12;vJ$WLIHook5d)lA^!$J_xd}>r%DHwiBLOGntv|dU-yV z4$R(p>o_98kj#mMco&lH+lL(60%m6K=XDi{?rY^)-6w$&IZplk`?tsAF7e1B`;vOk z(+N-~vL&eZBw+sO^Ir*8HLE=PK=7nNN>xqDv1bENwP&AHN)V{RB~lyav*4QbZ~PzFE3|lbZB*AE@N+PFH~}6 zWpdesi~Ilp02FjZSad^gaCvfRXJ~W)GB7eRAXIW?WpeY}%TE9R002ovPDHLkV1jxp B{n`Kk literal 0 HcmV?d00001 diff --git a/textures/nether_basalt_side.png b/textures/nether_basalt_side.png new file mode 100644 index 0000000000000000000000000000000000000000..7c33847e7f69df7fb2ae35db6dc1ae0201d729c3 GIT binary patch literal 518 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61SBU+%rFB|oCO|{#S9GG!XV7ZFl&wk0|R5a zr;B5V#(CRGXY)iHc$)tj-xgcZawV?v|8wV#OR42a#WO@LpMFr?H21^XzsEm+zTV$I zfBpLW>*wRE@9(eqJ@4kG+4tUGU%q_$nYiiGUq8?HQo86CB)wk!=FDCtV+M`4wfZ;s zRywcSwkX|9^CE)-Lx%G&pKT0XZc`>NobhJc7Dk2~ZzJQQ&8#zBX5M8okymKg#(IOL z;|UXk=PmD-GSZnIs?0V$zRP@PH+!?~a#@(m6e7Nrm2<;g_DMdwW-bn1(!{Xz3g6O{ z#mbTkws5FzH#>R4+ktal>JmR0PX-S!w{QO)T)CG=RWdH^P?DU$kaldJOulZ$>y2A2 zUru0i`p_}0WcS~Ru0}U~U02hqEHk-FAC2jTECuWn3C*M?=tmrtWZPJ@ig_}%HMQWeC z)J`&TiDWogb#7rvdnW&o-`el^12airJgAnqMwB=fm1Gu|FoYDPrWPq=l#~<{Tj}ek zXO?7?Cg~;T=jwwcZnkv)0csEdX>iUjs4U7%&nRIqGB7e!0Ly>AbJ`y$&*16m=d#Wz Gp$PyB5!4(2 literal 0 HcmV?d00001 diff --git a/textures/nether_brick_cracked.png b/textures/nether_brick_cracked.png new file mode 100644 index 0000000000000000000000000000000000000000..1729b2caa6a7bca80e641d3a439ee978e166b71b GIT binary patch literal 290 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!73?$#)eFPHF4e$wZjW9F})YJ3U*7noYb=TB% zQd6^2QSs2yvQ}2MS5vcAQ886i)RvYuQ&KXJmp5kBJp|OqS>O>_%)r1c48n{Iv*t(u z1!sG@IEHXsXPm&0|DS<@F@S-AnL&i1UC^~qNl;LM;oaHgw|7qQRAe|C9yw{>%UMng zJ6Ek*>GgW13d73K$eHtkS`rvuMy*=ez3R?GhTXg0t=`?y-2^n2QQXK-z<@zXNkxg% zC1euA+FR8VIb5S>GPqZBxXA5zzJwvXuzMr3wdG8PRZGpEh^lm!GL+7AuHjxeGo0bv fz1{ERUcb91P;BwPpLc00&^!iDS3j3^P6pHOi4sRR4C6)kD58;vA{1PFG4T~mqe#LmgjkVp5;ha^?0 zRQce_CaJ{1n_UoyMF{;dn#sXFxbJ=Gma}mHG3rc1=5+@o=vAo5-*Zq9Pl**&27w~Np}049|SkkVd`UjrJS|bNiYsj0w4n5 z3xFpm1V^C#Nq`8}p~5g62M7SxVJw0Aq5;-ghg)8yT45cJWv{O%LoEQihFww5Mv)1K zeSlNP;cyUSm4*VwaVXCEDpG)hP0^G^=}?cCOVcz_6xB_0JRX6p>=vVNTe)4HOL=i^ z&*p6RPp9XLNgC2y)+e{y?W(5kjq$ZV7!1ba(RP~+LtlRS=hzzCwe5@5^4kQZa9}Kz z;jgSN%Q8*V<;XXdyy_kvHu}HK_GYU}_OD9Pj?ek+*SFgT%c^ga7xy`ZNAM(EMUw*UlJXM}+Idu-m&+V^w zYhX5={ctn-JaWA|Z}O)f-#iy3zx=V$p4WEW|E>5u@8Flle*gdg07*qoM6N<$g0Or- Aj{pDw literal 0 HcmV?d00001 diff --git a/textures/nether_lava_crust_animated.png b/textures/nether_lava_crust_animated.png new file mode 100644 index 0000000000000000000000000000000000000000..f8f8c25a0a4027ee47ebbd5cd8fb76b82408ab36 GIT binary patch literal 18533 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 JJp0)#@^9EE^5_5n literal 0 HcmV?d00001 diff --git a/textures/nether_particle_anim4.png b/textures/nether_particle_anim4.png new file mode 100644 index 0000000000000000000000000000000000000000..0f1b849a449365aa0770625b80367172f2332982 GIT binary patch literal 288 zcmeAS@N?(olHy`uVBq!ia0vp^>_Du=!VDyP{bD$P6id3JuOkD)#(wTUiL5|AV{wqX z6T`Z5GB1G~p#Yx{SN8&+|EQot(xx6L%UTlT7tCNkWvaug%LkVN1-v|6978y+PwhF# zd%%E)>3rDd?;4V=f0t)AP1;r|*4ciR(UB=v!gTk7)B0;0EG9%QaGN2YqGf(b|9;Wt z^$#MAf^t~o4=V0m{b+jLdlA-Z(@&q9fTpUJxJHyX7L{ZcmoS7BrKT1sWR#Q?6kF-* zr)QRAlqTsV=jZB!C2qEK{{dgTe~ HDWM4f)Glgu literal 0 HcmV?d00001 diff --git a/textures/nether_rack_deep.png b/textures/nether_rack_deep.png new file mode 100644 index 0000000000000000000000000000000000000000..69bb7c29c6bb2734a66423128077aea51bc7637a GIT binary patch literal 353 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbKJXMsm#F#`j)AP6(or^HVL3Wf*x zgt#gw*eWX7DJwgus5q*rI%#OQXllA?YP)OecFW9D8Tc9)_!}Df8yN+dm;{@e z1Y1}{SXxEd*v8u0#o5~@IXEOcI;Oh1WVpHKczPE2`V|ESSL*fu+6J^@gQtsQh=io* zLGC0cMIM#|D_9R555F-%b=%(mmaC0FaT&xo3m!7VbUWe+t6ddypw=qNmC?b0blcKOmld6Dc4 zvu4c=I~q{T_#$srE{~yc?HS8T{iDDBh@HC`;c-{}-yOx07aM}gCX~D{`eXLI=)Vzz u+w@Nwvht!2zX!FpzdRImts?aI#QIs?{D051n(zSK&fw|l=d#Wzp$P#0ID3Zx literal 0 HcmV?d00001 diff --git a/tools.lua b/tools.lua index 637cdf0..26e1425 100644 --- a/tools.lua +++ b/tools.lua @@ -1,3 +1,22 @@ +--[[ + + Copyright (C) 2020 lortas + + 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. + +]]-- + local S = nether.get_translator minetest.register_tool("nether:pick_nether", {