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 0000000..4eeef69 Binary files /dev/null and b/sounds/nether_lava_bubble.0.ogg differ diff --git a/sounds/nether_lava_bubble.ogg b/sounds/nether_lava_bubble.ogg new file mode 100644 index 0000000..44a5031 Binary files /dev/null and b/sounds/nether_lava_bubble.ogg differ diff --git a/textures/nether_basalt.png b/textures/nether_basalt.png new file mode 100644 index 0000000..08a348f Binary files /dev/null and b/textures/nether_basalt.png differ diff --git a/textures/nether_basalt_chiselled_side.png b/textures/nether_basalt_chiselled_side.png new file mode 100644 index 0000000..46a173a Binary files /dev/null and b/textures/nether_basalt_chiselled_side.png differ diff --git a/textures/nether_basalt_chiselled_top.png b/textures/nether_basalt_chiselled_top.png new file mode 100644 index 0000000..3d6a70e Binary files /dev/null and b/textures/nether_basalt_chiselled_top.png differ diff --git a/textures/nether_basalt_hewn.png b/textures/nether_basalt_hewn.png new file mode 100644 index 0000000..60db5e3 Binary files /dev/null and b/textures/nether_basalt_hewn.png differ diff --git a/textures/nether_basalt_side.png b/textures/nether_basalt_side.png new file mode 100644 index 0000000..7c33847 Binary files /dev/null and b/textures/nether_basalt_side.png differ diff --git a/textures/nether_brick_cracked.png b/textures/nether_brick_cracked.png new file mode 100644 index 0000000..1729b2c Binary files /dev/null and b/textures/nether_brick_cracked.png differ diff --git a/textures/nether_glowstone_deep.png b/textures/nether_glowstone_deep.png new file mode 100644 index 0000000..0e37b8c Binary files /dev/null and b/textures/nether_glowstone_deep.png differ diff --git a/textures/nether_lava_crust_animated.png b/textures/nether_lava_crust_animated.png new file mode 100644 index 0000000..f8f8c25 Binary files /dev/null and b/textures/nether_lava_crust_animated.png differ diff --git a/textures/nether_lava_source_animated.png b/textures/nether_lava_source_animated.png new file mode 100644 index 0000000..c8514ad Binary files /dev/null and b/textures/nether_lava_source_animated.png differ diff --git a/textures/nether_particle_anim4.png b/textures/nether_particle_anim4.png new file mode 100644 index 0000000..0f1b849 Binary files /dev/null and b/textures/nether_particle_anim4.png differ diff --git a/textures/nether_rack_deep.png b/textures/nether_rack_deep.png new file mode 100644 index 0000000..69bb7c2 Binary files /dev/null and b/textures/nether_rack_deep.png differ 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", {