diff --git a/README.md b/README.md index 7d3a91ff..72527f86 100644 --- a/README.md +++ b/README.md @@ -154,7 +154,7 @@ The game includes the mods from the default [minetest_game](https://github.com/m * [bright_night][] ([Unlicense][lic.unlicense]) -- version: [69b750a Git][ver.bright_night] *2018-06-03* * [desert_life][] ([CC BY-SA][lic.ccbysa4.0]) -- version: [188f984 Git][ver.desert_life] *2020-01-24* * [ethereal][] ([MIT][lic.ethereal]) -- version: [2021-07-18][ver.ethereal] ([patched][patch.ethereal]) - * [nether][] ([ISC / CC BY / CC BY-SA / CC0][lic.nether]) -- version: [3][ver.nether] *2021-01-22* ([patched][patch.nether]) + * [nether][] ([ISC / CC BY / CC BY-SA / CC0][lic.nether]) -- version: [3.1][ver.nether] *2021-07-31* ([patched][patch.nether]) * [snowdrift][] ([MIT / CC BY / CC BY-SA][lic.snowdrift]) -- version: [3342939 Git][ver.snowdrift] *2020-04-24* ([patched][patch.snowdrift]) @@ -396,7 +396,7 @@ The game includes the mods from the default [minetest_game](https://github.com/m [myroofs]: https://forum.minetest.net/viewtopic.php?t=11416 [mysheetmetal]: https://forum.minetest.net/viewtopic.php?t=11702 [mywoodslopes]: https://forum.minetest.net/viewtopic.php?t=11433 -[nether]: https://forum.minetest.net/viewtopic.php?t=5790 +[nether]: https://content.minetest.net/packages/PilzAdam/nether/ [no_fall_damage]: https://forum.minetest.net/viewtopic.php?t=25876 [painting]: https://github.com/minetest-mods/painting [pbmarks]: https://github.com/AntumMT/mod-pbmarks @@ -662,7 +662,7 @@ The game includes the mods from the default [minetest_game](https://github.com/m [ver.myroofs]: https://github.com/minetest-mods/myroofs/tree/fe98461 [ver.mysheetmetal]: https://github.com/minetest-mods/mysheetmetal/tree/7c5ab71 [ver.mywoodslopes]: https://github.com/minetest-mods/mywoodslopes/tree/3a1b531 -[ver.nether]: https://github.com/minetest-mods/nether/tree/v3 +[ver.nether]: https://github.com/minetest-mods/nether/tree/v3.1 [ver.no_fall_damage]: https://repo.or.cz/minetest_no_fall_damage.git/shortlog/refs/tags/1.0.0 [ver.painting]: https://github.com/minetest-mods/painting/tree/da8f4ba [ver.pbmarks]: https://github.com/AntumMT/mod-pbmarks/releases/tag/v1.0 diff --git a/mods/world/nether/README.md b/mods/world/nether/README.md index 9481384e..edf300fd 100644 --- a/mods/world/nether/README.md +++ b/mods/world/nether/README.md @@ -74,4 +74,7 @@ SOFTWARE. * `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 +### [Unlicensed but permission for inclusion by kovaszos_uborka](https://www.planetminecraft.com/member/kovaszos_uborka/) + * `nether_brick_deep.png` (edited by TenPlus1) + +All other media: Copyright © 2013 PilzAdam, licensed under CC BY-SA 3.0 by PilzAdam. diff --git a/mods/world/nether/crafts.lua b/mods/world/nether/crafts.lua index f880e190..88d1d07e 100644 --- a/mods/world/nether/crafts.lua +++ b/mods/world/nether/crafts.lua @@ -44,6 +44,14 @@ minetest.register_craft({ } }) +minetest.register_craft({ + output = "nether:brick_deep 4", + recipe = { + {"nether:rack_deep", "nether:rack_deep"}, + {"nether:rack_deep", "nether:rack_deep"} + } +}) + minetest.register_craft({ output = "nether:basalt_hewn", type = "shapeless", @@ -61,4 +69,4 @@ minetest.register_craft({ } }) --- See tools.lua for tools related crafting \ No newline at end of file +-- See tools.lua for tools related crafting diff --git a/mods/world/nether/depends.txt b/mods/world/nether/depends.txt index 00f426a6..2ccaf363 100644 --- a/mods/world/nether/depends.txt +++ b/mods/world/nether/depends.txt @@ -6,3 +6,4 @@ fire? loot? mesecons? moreblocks? +climate_api? diff --git a/mods/world/nether/init.lua b/mods/world/nether/init.lua index c09234e1..13fcaa64 100644 --- a/mods/world/nether/init.lua +++ b/mods/world/nether/init.lua @@ -42,12 +42,18 @@ end -- Global Nether namespace nether = {} +nether.mapgen = {} -- Shared Nether mapgen namespace, for mapgen files to expose functions and constants nether.modname = minetest.get_current_modname() nether.path = minetest.get_modpath(nether.modname) nether.get_translator = S -- nether.useBiomes allows other mods to know whether they can register ores etc. in the Nether. -- See mapgen.lua for an explanation of why minetest.read_schematic is being checked nether.useBiomes = minetest.get_mapgen_setting("mg_name") ~= "v6" and minetest.read_schematic ~= nil +nether.fogColor = { -- only used if climate_api is installed + netherCaverns = "#1D0504", -- Distance-fog colour for classic nether + mantle = "#070916", -- Distance-fog colour for the Mantle region + geodes = "#300530" -- Distance-fog colour for secondary region +} -- Settings @@ -70,6 +76,19 @@ if nether.DEPTH_FLOOR + 1000 > nether.DEPTH_CEILING then end nether.DEPTH = nether.DEPTH_CEILING -- Deprecated, use nether.DEPTH_CEILING instead. +-- DEPTH_FLOOR_LAYERS gives the bottom Y of all locations that wish to be +-- considered part of the Nether. +-- DEPTH_FLOOR_LAYERS Allows mods to insert extra layers below the +-- Nether, by knowing where their layer ceiling should start, and letting +-- the layers be included in effects which only happen in the Nether. +-- If a mod wishes to add a layer below the Nether it should read +-- nether.DEPTH_FLOOR_LAYERS to find the bottom Y of the Nether and any +-- other layers already under the Nether. The mod should leave a small gap +-- between DEPTH_FLOOR_LAYERS and its ceiling (e.g. use DEPTH_FLOOR_LAYERS - 6 +-- for its ceiling Y, so there is room to shift edge-case biomes), then set +-- nether.DEPTH_FLOOR_LAYERS to reflect the mod's floor Y value, and call +-- shift_existing_biomes() with DEPTH_FLOOR_LAYERS as the floor_y argument. +nether.DEPTH_FLOOR_LAYERS = nether.DEPTH_FLOOR -- A debug-print function that understands vectors etc. and does not -- evaluate when debugging is turned off. @@ -149,7 +168,7 @@ This opens to a truly hellish place, though for small mercies the air there is s The expedition parties have found no diamonds or gold, and after an experienced search party failed to return from the trail of a missing expedition party, I must conclude this is a dangerous place.]], 10 * nether.FASTTRAVEL_FACTOR), is_within_realm = function(pos) -- return true if pos is inside the Nether - return pos.y < nether.DEPTH_CEILING + return pos.y < nether.DEPTH_CEILING and pos.y > nether.DEPTH_FLOOR end, find_realm_anchorPos = function(surface_anchorPos, player_name) @@ -157,7 +176,7 @@ The expedition parties have found no diamonds or gold, and after an experienced local destination_pos = vector.divide(surface_anchorPos, nether.FASTTRAVEL_FACTOR) destination_pos.x = math.floor(0.5 + destination_pos.x) -- round to int destination_pos.z = math.floor(0.5 + destination_pos.z) -- round to int - destination_pos.y = nether.DEPTH_CEILING - 1000 -- temp value so find_nearest_working_portal() returns nether portals + destination_pos.y = nether.DEPTH_CEILING - 1 -- temp value so find_nearest_working_portal() returns nether portals -- a y_factor of 0 makes the search ignore the altitude of the portals (as long as they are in the Nether) local existing_portal_location, existing_portal_orientation = @@ -182,7 +201,7 @@ The expedition parties have found no diamonds or gold, and after an experienced local destination_pos = vector.multiply(realm_anchorPos, nether.FASTTRAVEL_FACTOR) destination_pos.x = math.min(30900, math.max(-30900, destination_pos.x)) -- clip to world boundary destination_pos.z = math.min(30900, math.max(-30900, destination_pos.z)) -- clip to world boundary - destination_pos.y = 0 -- temp value so find_nearest_working_portal() doesn't return nether portals + destination_pos.y = nether.DEPTH_CEILING + 1 -- temp value so find_nearest_working_portal() doesn't return nether portals -- a y_factor of 0 makes the search ignore the altitude of the portals (as long as they are outside the Nether) local existing_portal_location, existing_portal_orientation = @@ -227,7 +246,91 @@ The expedition parties have found no diamonds or gold, and after an experienced end }) -end + + + -- Set appropriate nether distance-fog if climate_api is available + -- + -- Delegating to a mod like climate_api means nether won't unexpectedly stomp on the sky of + -- any other mod. + -- Skylayer is another mod which can perform this role, and skylayer support could be added + -- here as well. However skylayer doesn't provide a position-based method of specifying sky + -- colours out-of-the-box, so the nether mod will have to monitor when players enter and + -- leave the nether. + if minetest.get_modpath("climate_api") and minetest.global_exists("climate_api") and climate_api.register_weather ~= nil then + + climate_api.register_influence( + "nether_biome", + function(pos) + local result = "surface" + + if pos.y <= nether.DEPTH_CEILING and pos.y >= nether.DEPTH_FLOOR then + result = "nether" + + -- since mapgen_nobiomes.lua has no regions it doesn't implement get_region(), + -- so only use get_region() if it exists + if nether.mapgen.get_region ~= nil then + -- the biomes-based mapgen supports 2 extra regions + local regions = nether.mapgen.RegionEnum + local region = nether.mapgen.get_region(pos) + if region == regions.CENTER or region == regions.CENTERSHELL then + result = "mantle" + elseif region == regions.NEGATIVE or region == regions.NEGATIVESHELL then + result = "geode" + end + end + end + + return result + end + ) + + -- using sky type "plain" unfortunately means we don't get smooth fading transitions when + -- the color of the sky changes, but it seems to be the only way to obtain a sky colour + -- which doesn't brighten during the daytime. + local undergroundSky = { + sky_data = { + base_color = nil, + type = "plain", + textures = nil, + clouds = false, + }, + sun_data = { + visible = false, + sunrise_visible = false + }, + moon_data = { + visible = false + }, + star_data = { + visible = false + } + } + + local netherSky, mantleSky, geodeSky = table.copy(undergroundSky), table.copy(undergroundSky), table.copy(undergroundSky) + netherSky.sky_data.base_color = nether.fogColor.netherCaverns + mantleSky.sky_data.base_color = nether.fogColor.mantle + geodeSky.sky_data.base_color = nether.fogColor.geodes + + climate_api.register_weather( + "nether:nether", + { nether_biome = "nether" }, + { ["climate_api:skybox"] = netherSky } + ) + + climate_api.register_weather( + "nether:mantle", + { nether_biome = "mantle" }, + { ["climate_api:skybox"] = mantleSky } + ) + + climate_api.register_weather( + "nether:geode", + { nether_biome = "geode" }, + { ["climate_api:skybox"] = geodeSky } + ) + end + +end -- end of "if nether.NETHER_REALM_ENABLED..." -- Play bubbling lava sounds if player killed by lava diff --git a/mods/world/nether/locale/nether.fr.tr b/mods/world/nether/locale/nether.fr.tr index 48201d8c..f274f9b2 100644 --- a/mods/world/nether/locale/nether.fr.tr +++ b/mods/world/nether/locale/nether.fr.tr @@ -13,9 +13,25 @@ Construction requires 14 blocks of obsidian, which we found deep underground whe Nether Portal=Portail du Nether +### mapgen_mantle.lua ### + +, @1m above lava-sea level= +, @1m below lava-sea level= +, approaching y boundary of Nether= +@1@2@3@4= +Center/Mantle, but outside the caverns= +Center/Mantle, inside cavern= +Describes which region of the nether the player is in= +Negative nether= +Positive nether= +Shell between negative nether and center region= +Shell between positive nether and center region= +The Overworld= +Unknown player position= +[Perlin @1] = + ### nodes.lua ### -= A finely finished block of solid Nether Basalt.= A rough cut solid block of Nether Basalt.= A thin crust of cooled lava with liquid lava beneath= diff --git a/mods/world/nether/locale/template.txt b/mods/world/nether/locale/template.txt index 0dfcf324..ef69c76e 100644 --- a/mods/world/nether/locale/template.txt +++ b/mods/world/nether/locale/template.txt @@ -12,9 +12,25 @@ Construction requires 14 blocks of obsidian, which we found deep underground whe Nether Portal= +### mapgen_mantle.lua ### + +, @1m above lava-sea level= +, @1m below lava-sea level= +, approaching y boundary of Nether= +@1@2@3@4= +Center/Mantle, but outside the caverns= +Center/Mantle, inside cavern= +Describes which region of the nether the player is in= +Negative nether= +Positive nether= +Shell between negative nether and center region= +Shell between positive nether and center region= +The Overworld= +Unknown player position= +[Perlin @1] = + ### nodes.lua ### -= A finely finished block of solid Nether Basalt.= A rough cut solid block of Nether Basalt.= A thin crust of cooled lava with liquid lava beneath= diff --git a/mods/world/nether/mapgen.lua b/mods/world/nether/mapgen.lua index c82a7dbf..df82e584 100644 --- a/mods/world/nether/mapgen.lua +++ b/mods/world/nether/mapgen.lua @@ -45,7 +45,6 @@ local BASALT_COLUMN_LOWER_LIMIT = CENTER_CAVERN_LIMIT * 0.25 -- This value -- 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 @@ -69,7 +68,8 @@ 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 +-- Load specialty helper functions +dofile(nether.path .. "/mapgen_dungeons.lua") dofile(nether.path .. "/mapgen_mantle.lua") @@ -81,7 +81,8 @@ local math_max, math_min, math_abs, math_floor = math.max, math.min, math.abs, m -- Inject nether_caverns biome -local function override_underground_biomes() +-- Move any existing biomes out of the y-range specified by 'floor_y' and 'ceiling_y' +mapgen.shift_existing_biomes = function(floor_y, ceiling_y) -- https://forum.minetest.net/viewtopic.php?p=257522#p257522 -- Q: Is there a way to override an already-registered biome so I can get it out of the -- way of my own underground biomes without disturbing the other biomes registered by @@ -108,7 +109,7 @@ local function override_underground_biomes() end for old_ore_key, old_ore_def in pairs(minetest.registered_ores) do registered_ores_copy[old_ore_key] = old_ore_def - end + end -- clear biomes, decorations, and ores minetest.clear_registered_decorations() @@ -117,23 +118,36 @@ local function override_underground_biomes() -- Restore biomes, adjusted to not overlap the Nether for biome_key, new_biome_def in pairs(registered_biomes_copy) do - local biome_y_max, biome_y_min = tonumber(new_biome_def.y_max), tonumber(new_biome_def.y_min) + -- follow similar min_pos/max_pos processing logic as read_biome_def() in l_mapgen.cpp + local biome_y_max, biome_y_min = 31000, -31000 + if type(new_biome_def.min_pos) == 'table' and type(new_biome_def.min_pos.y) == 'number' then biome_y_min = new_biome_def.min_pos.y end + if type(new_biome_def.max_pos) == 'table' and type(new_biome_def.max_pos.y) == 'number' then biome_y_max = new_biome_def.max_pos.y end + if type(new_biome_def.y_min) == 'number' then biome_y_min = new_biome_def.y_min end + if type(new_biome_def.y_max) == 'number' then biome_y_max = new_biome_def.y_max end - if biome_y_max > NETHER_FLOOR and biome_y_min < NETHER_CEILING then + if biome_y_max > floor_y and biome_y_min < ceiling_y then -- This biome occupies some or all of the depth of the Nether, shift/crop it. - local spaceOccupiedAbove = biome_y_max - NETHER_CEILING - local spaceOccupiedBelow = NETHER_FLOOR - biome_y_min + local new_y_min, new_y_max + local spaceOccupiedAbove = biome_y_max - ceiling_y + local spaceOccupiedBelow = floor_y - biome_y_min if spaceOccupiedAbove >= spaceOccupiedBelow or biome_y_min <= -30000 then -- place the biome above the Nether -- We also shift biomes which extend to the bottom of the map above the Nether, since they -- likely only extend that deep as a catch-all, and probably have a role nearer the surface. - new_biome_def.y_min = NETHER_CEILING + 1 - new_biome_def.y_max = math_max(biome_y_max, NETHER_CEILING + 2) + new_y_min = ceiling_y + 1 + new_y_max = math_max(biome_y_max, ceiling_y + 2) else -- shift the biome to below the Nether - new_biome_def.y_max = NETHER_FLOOR - 1 - new_biome_def.y_min = math_min(biome_y_min, NETHER_CEILING - 2) + new_y_max = floor_y - 1 + new_y_min = math_min(biome_y_min, floor_y - 2) end + + debugf("Moving biome \"%s\" from %s..%s to %s..%s", new_biome_def.name, new_biome_def.y_min, new_biome_def.y_max, new_y_min, new_y_max) + + if type(new_biome_def.min_pos) == 'table' and type(new_biome_def.min_pos.y) == 'number' then new_biome_def.min_pos.y = new_y_min end + if type(new_biome_def.max_pos) == 'table' and type(new_biome_def.max_pos.y) == 'number' then new_biome_def.max_pos.y = new_y_max end + new_biome_def.y_min = new_y_min -- Ensure the new heights are saved, even if original biome never specified one + new_biome_def.y_max = new_y_max end minetest.register_biome(new_biome_def) end @@ -149,7 +163,7 @@ local function override_underground_biomes() end -- Shift any overlapping biomes out of the way before we create the Nether biomes -override_underground_biomes() +mapgen.shift_existing_biomes(NETHER_FLOOR, NETHER_CEILING) -- nether:native_mapgen is used to prevent ores and decorations being generated according -- to landforms created by the native mapgen. @@ -244,6 +258,19 @@ mapgen.np_cave = { --flags = "" } +local cavePointPerlin = nil + +mapgen.get_cave_point_perlin = function() + cavePointPerlin = cavePointPerlin or minetest.get_perlin(mapgen.np_cave) + return cavePointPerlin +end + +mapgen.get_cave_perlin_at = function(pos) + cavePointPerlin = cavePointPerlin or minetest.get_perlin(mapgen.np_cave) + return cavePointPerlin:get_3d(pos) +end + + -- Buffers and objects we shouldn't recreate every on_generate local nobj_cave = nil @@ -256,206 +283,11 @@ local dbuf = {} local c_air = minetest.get_content_id("air") local c_netherrack = minetest.get_content_id("nether:rack") 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 = {} - - -- Unfortunately gennotify only returns dungeon rooms, not corridors. - -- We don't need to check for temples because only dungeons are generated in biomes - -- that define their own dungeon nodes. - local gennotify = minetest.get_mapgen_object("gennotify") - local roomLocations = gennotify["dungeon"] or {} - - -- Excavation should still know to stop if a cave or corridor has removed the dungeon wall. - -- See MapgenBasic::generateDungeons in mapgen.cpp for max room sizes. - local maxRoomSize = 18 - local maxRoomRadius = math.ceil(maxRoomSize / 2) - - local xStride, yStride, zStride = 1, area.ystride, area.zstride - local minEdge, maxEdge = area.MinEdge, area.MaxEdge - - for _, roomPos in ipairs(roomLocations) do - - if area:containsp(roomPos) then -- this safety check does not appear to be necessary, but lets make it explicit - - local room_vi = area:indexp(roomPos) - --data[room_vi] = minetest.get_content_id("default:torch") -- debug - - local startPos = vector.new(roomPos) - if roomPos.y + 1 <= maxEdge.y and data[room_vi + yStride] == c_air then - -- The roomPos coords given by gennotify are at floor level, but whenever possible we - -- want to be performing searches a node higher than floor level to avoids dungeon chests. - startPos.y = startPos.y + 1 - room_vi = area:indexp(startPos) - end - - local bound_min_x = math_max(minEdge.x, roomPos.x - maxRoomRadius) - local bound_min_y = math_max(minEdge.y, roomPos.y - 1) -- room coords given by gennotify are on the floor - local bound_min_z = math_max(minEdge.z, roomPos.z - maxRoomRadius) - - local bound_max_x = math_min(maxEdge.x, roomPos.x + maxRoomRadius) - local bound_max_y = math_min(maxEdge.y, roomPos.y + maxRoomSize) -- room coords given by gennotify are on the floor - local bound_max_z = math_min(maxEdge.z, roomPos.z + maxRoomRadius) - - local room_min = vector.new(startPos) - local room_max = vector.new(startPos) - - local vi = room_vi - while room_max.y < bound_max_y and data[vi + yStride] == c_air do - room_max.y = room_max.y + 1 - vi = vi + yStride - end - - vi = room_vi - while room_min.y > bound_min_y and data[vi - yStride] == c_air do - room_min.y = room_min.y - 1 - vi = vi - yStride - end - - vi = room_vi - while room_max.z < bound_max_z and data[vi + zStride] == c_air do - room_max.z = room_max.z + 1 - vi = vi + zStride - end - - vi = room_vi - while room_min.z > bound_min_z and data[vi - zStride] == c_air do - room_min.z = room_min.z - 1 - vi = vi - zStride - end - - vi = room_vi - while room_max.x < bound_max_x and data[vi + xStride] == c_air do - room_max.x = room_max.x + 1 - vi = vi + xStride - end - - vi = room_vi - while room_min.x > bound_min_x and data[vi - xStride] == c_air do - room_min.x = room_min.x - 1 - vi = vi - xStride - end - - local roomInfo = vector.new(roomPos) - roomInfo.minp = room_min - roomInfo.maxp = room_max - result[#result + 1] = roomInfo - end - end - - return result; -end - --- Only partially excavates dungeons, the rest is left as an exercise for the player ;) --- (Corridors and the parts of rooms which extend beyond the emerge boundary will remain filled) -function excavate_dungeons(data, area, rooms) - - local vi, node_id - - -- any air from the native mapgen has been replaced by netherrack, but - -- we don't want this inside dungeons, so fill dungeon rooms with air - for _, roomInfo in ipairs(rooms) do - - local room_min = roomInfo.minp - local room_max = roomInfo.maxp - - for z = room_min.z, room_max.z do - for y = room_min.y, room_max.y do - vi = area:index(room_min.x, y, z) - for x = room_min.x, room_max.x do - node_id = data[vi] - if node_id == c_netherrack or node_id == c_netherrack_deep then data[vi] = c_air end - vi = vi + 1 - end - end - end - end -end - --- Since we already know where all the rooms and their walls are, and have all the nodes stored --- in a voxelmanip already, we may as well add a little Nether flair to the dungeons found here. -function decorate_dungeons(data, area, rooms) - - local xStride, yStride, zStride = 1, area.ystride, area.zstride - local minEdge, maxEdge = area.MinEdge, area.MaxEdge - - for _, roomInfo in ipairs(rooms) do - - local room_min, room_max = roomInfo.minp, roomInfo.maxp - local room_size = vector.distance(room_min, room_max) - - if room_size > 10 then - local room_seed = roomInfo.x + 3 * roomInfo.z + 13 * roomInfo.y - local window_y = roomInfo.y + math_min(2, room_max.y - roomInfo.y - 1) - - if room_seed % 3 == 0 and room_max.y < maxEdge.y then - -- Glowstone chandelier (feel free to replace with a fancy schematic) - local vi = area:index(roomInfo.x, room_max.y + 1, roomInfo.z) - if is_dungeon_brick(data[vi]) then data[vi] = c_glowstone end - - elseif room_seed % 4 == 0 and room_min.y > minEdge.y - and room_min.x > minEdge.x and room_max.x < maxEdge.x - and room_min.z > minEdge.z and room_max.z < maxEdge.z then - -- lava well (feel free to replace with a fancy schematic) - local vi = area:index(roomInfo.x, room_min.y, roomInfo.z) - if is_dungeon_brick(data[vi - yStride]) then - data[vi - yStride] = c_lava_source - if data[vi - zStride] == c_air then data[vi - zStride] = c_netherbrick_slab end - if data[vi + zStride] == c_air then data[vi + zStride] = c_netherbrick_slab end - if data[vi - xStride] == c_air then data[vi - xStride] = c_netherbrick_slab end - if data[vi + xStride] == c_air then data[vi + xStride] = c_netherbrick_slab end - end - end - - -- Barred windows - if room_seed % 7 < 5 and room_max.x - room_min.x >= 4 and room_max.z - room_min.z >= 4 - and window_y >= minEdge.y and window_y + 1 <= maxEdge.y - and room_min.x > minEdge.x and room_max.x < maxEdge.x - and room_min.z > minEdge.z and room_max.z < maxEdge.z then - --data[area:indexp(roomInfo)] = minetest.get_content_id("default:mese_post_light") -- debug - - -- Until whisper glass is added, every window will be made of netherbrick fence (rather - -- than material depending on room_seed) - local window_node = c_netherfence - - local vi_min = area:index(room_min.x - 1, window_y, roomInfo.z) - local vi_max = area:index(room_max.x + 1, window_y, roomInfo.z) - local locations = {-zStride, zStride, -zStride + yStride, zStride + yStride} - for _, offset in ipairs(locations) do - if is_dungeon_brick(data[vi_min + offset]) then data[vi_min + offset] = window_node end - if is_dungeon_brick(data[vi_max + offset]) then data[vi_max + offset] = window_node end - end - vi_min = area:index(roomInfo.x, window_y, room_min.z - 1) - vi_max = area:index(roomInfo.x, window_y, room_max.z + 1) - locations = {-xStride, xStride, -xStride + yStride, xStride + yStride} - for _, offset in ipairs(locations) do - if is_dungeon_brick(data[vi_min + offset]) then data[vi_min + offset] = window_node end - if is_dungeon_brick(data[vi_max + offset]) then data[vi_max + offset] = window_node end - end - end - - -- Weeds on the floor once Nether weeds are added - end - end -end - local yblmin = NETHER_FLOOR + BLEND * 2 local yblmax = NETHER_CEILING - BLEND * 2 @@ -514,27 +346,29 @@ local function on_generated(minp, maxp, seed) 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 dungeonRooms = mapgen.build_dungeon_room_list(data, area) -- function from mapgen_dungeons.lua local abs_cave_noise, abs_cave_noise_adjusted local contains_nether = false - local contains_shell = false local contains_mantle = false - local contains_ocean = false + local contains_ocean = false 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) -- function provided by mapgen_mantle.lua + local sea_level, cavern_limit_distance = mapgen.find_nearest_lava_sealevel(y) -- function from mapgen_mantle.lua 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 + + -- 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 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 instead of adding + centerRegionLimit_adj for z = z0, z1 do local vi = area:index(x0, y, z) -- Initial voxelmanip index @@ -581,7 +415,6 @@ local function on_generated(minp, maxp, seed) 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 @@ -605,12 +438,12 @@ local function on_generated(minp, maxp, seed) end if contains_mantle or contains_ocean then - mapgen.add_basalt_columns(data, area, minp, maxp) -- function provided by mapgen_mantle.lua + mapgen.add_basalt_columns(data, area, minp, maxp) -- function from 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 + local success = mapgen.excavate_tunnel_to_center_of_the_nether(data, area, nvals_cave, minp, maxp) -- function from mapgen_mantle.lua if success then tunnel_count = tunnel_count + 1 end end total_chunk_count = total_chunk_count + 1 @@ -627,8 +460,8 @@ local function on_generated(minp, maxp, seed) -- any air from the native mapgen has been replaced by netherrack, but we -- don't want netherrack inside dungeons, so fill known dungeon rooms with air. - excavate_dungeons(data, area, dungeonRooms) - decorate_dungeons(data, area, dungeonRooms) + mapgen.excavate_dungeons(data, area, dungeonRooms) -- function from mapgen_dungeons.lua + mapgen.decorate_dungeons(data, area, dungeonRooms) -- function from mapgen_dungeons.lua vm:set_data(data) @@ -645,7 +478,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(mapgen.np_cave) + local nobj_cave_point = mapgen.get_cave_point_perlin() 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") @@ -653,7 +486,7 @@ function nether.find_nether_ground_y(target_x, target_z, start_y, player_name) local maxp = {x = maxp_schem.x, y = 0, z = maxp_schem.z} for y = start_y, math_max(NETHER_FLOOR + BLEND, start_y - 4096), -1 do - local nval_cave = nobj_cave_point:get3d({x = target_x, y = y, z = target_z}) + local nval_cave = nobj_cave_point:get_3d({x = target_x, y = y, z = target_z}) if nval_cave > TCAVE then -- Cavern air = air + 1 @@ -677,8 +510,4 @@ function nether.find_nether_ground_y(target_x, target_z, start_y, player_name) return math_max(start_y, NETHER_FLOOR + BLEND) -- Fallback end --- We don't need to be gen-notified of temples because only dungeons will be generated --- if a biome defines the dungeon nodes -minetest.set_gen_notify({dungeon = true}) - -minetest.register_on_generated(on_generated) \ No newline at end of file +minetest.register_on_generated(on_generated) diff --git a/mods/world/nether/mapgen_dungeons.lua b/mods/world/nether/mapgen_dungeons.lua new file mode 100644 index 00000000..64a73af9 --- /dev/null +++ b/mods/world/nether/mapgen_dungeons.lua @@ -0,0 +1,333 @@ +--[[ + + Nether mod for minetest + + All the Dungeon related functions used by the biomes-based mapgen are here. + + + 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. + +]]-- + + +-- We don't need to be gen-notified of temples because only dungeons will be generated +-- if a biome defines the dungeon nodes +minetest.set_gen_notify({dungeon = true}) + + +-- Content ids + +local c_air = minetest.get_content_id("air") +local c_netherrack = minetest.get_content_id("nether:rack") +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_glowstone_deep = minetest.get_content_id("nether:glowstone_deep") +local c_lava_source = minetest.get_content_id("default:lava_source") + + +-- Misc math functions + +-- avoid needing table lookups each time a common math function is invoked +local math_max, math_min = math.max, math.min + + +-- Dungeon excavation functions + +function is_dungeon_brick(node_id) + return node_id == c_dungeonbrick or node_id == c_dungeonbrick_alt +end + + + +nether.mapgen.build_dungeon_room_list = function(data, area) + + local result = {} + + -- Unfortunately gennotify only returns dungeon rooms, not corridors. + -- We don't need to check for temples because only dungeons are generated in biomes + -- that define their own dungeon nodes. + local gennotify = minetest.get_mapgen_object("gennotify") + local roomLocations = gennotify["dungeon"] or {} + + -- Excavation should still know to stop if a cave or corridor has removed the dungeon wall. + -- See MapgenBasic::generateDungeons in mapgen.cpp for max room sizes. + local maxRoomSize = 18 + local maxRoomRadius = math.ceil(maxRoomSize / 2) + + local xStride, yStride, zStride = 1, area.ystride, area.zstride + local minEdge, maxEdge = area.MinEdge, area.MaxEdge + + for _, roomPos in ipairs(roomLocations) do + + if area:containsp(roomPos) then -- this safety check does not appear to be necessary, but lets make it explicit + + local room_vi = area:indexp(roomPos) + --data[room_vi] = minetest.get_content_id("default:torch") -- debug + + local startPos = vector.new(roomPos) + if roomPos.y + 1 <= maxEdge.y and data[room_vi + yStride] == c_air then + -- The roomPos coords given by gennotify are at floor level, but whenever possible we + -- want to be performing searches a node higher than floor level to avoids dungeon chests. + startPos.y = startPos.y + 1 + room_vi = area:indexp(startPos) + end + + local bound_min_x = math_max(minEdge.x, roomPos.x - maxRoomRadius) + local bound_min_y = math_max(minEdge.y, roomPos.y - 1) -- room coords given by gennotify are on the floor + local bound_min_z = math_max(minEdge.z, roomPos.z - maxRoomRadius) + + local bound_max_x = math_min(maxEdge.x, roomPos.x + maxRoomRadius) + local bound_max_y = math_min(maxEdge.y, roomPos.y + maxRoomSize) -- room coords given by gennotify are on the floor + local bound_max_z = math_min(maxEdge.z, roomPos.z + maxRoomRadius) + + local room_min = vector.new(startPos) + local room_max = vector.new(startPos) + + local vi = room_vi + while room_max.y < bound_max_y and data[vi + yStride] == c_air do + room_max.y = room_max.y + 1 + vi = vi + yStride + end + + vi = room_vi + while room_min.y > bound_min_y and data[vi - yStride] == c_air do + room_min.y = room_min.y - 1 + vi = vi - yStride + end + + vi = room_vi + while room_max.z < bound_max_z and data[vi + zStride] == c_air do + room_max.z = room_max.z + 1 + vi = vi + zStride + end + + vi = room_vi + while room_min.z > bound_min_z and data[vi - zStride] == c_air do + room_min.z = room_min.z - 1 + vi = vi - zStride + end + + vi = room_vi + while room_max.x < bound_max_x and data[vi + xStride] == c_air do + room_max.x = room_max.x + 1 + vi = vi + xStride + end + + vi = room_vi + while room_min.x > bound_min_x and data[vi - xStride] == c_air do + room_min.x = room_min.x - 1 + vi = vi - xStride + end + + local roomInfo = vector.new(roomPos) + roomInfo.minp = room_min + roomInfo.maxp = room_max + result[#result + 1] = roomInfo + end + end + + return result; +end + +-- Only partially excavates dungeons, the rest is left as an exercise for the player ;) +-- (Corridors and the parts of rooms which extend beyond the emerge boundary will remain filled) +nether.mapgen.excavate_dungeons = function(data, area, rooms) + + local vi, node_id + + -- any air from the native mapgen has been replaced by netherrack, but + -- we don't want this inside dungeons, so fill dungeon rooms with air + for _, roomInfo in ipairs(rooms) do + + local room_min = roomInfo.minp + local room_max = roomInfo.maxp + + for z = room_min.z, room_max.z do + for y = room_min.y, room_max.y do + vi = area:index(room_min.x, y, z) + for x = room_min.x, room_max.x do + node_id = data[vi] + if node_id == c_netherrack or node_id == c_netherrack_deep then data[vi] = c_air end + vi = vi + 1 + end + end + end + end + + -- clear netherrack from dungeon stairways + if #rooms > 0 then + local stairPositions = minetest.find_nodes_in_area(area.MinEdge, area.MaxEdge, minetest.registered_biomes["nether_caverns"].node_dungeon_stair) + for _, stairPos in ipairs(stairPositions) do + vi = area:indexp(stairPos) + for i = 1, 4 do + if stairPos.y + i > area.MaxEdge.y then break end + vi = vi + area.ystride + node_id = data[vi] + -- searching forward of the stairs could also be done + if node_id == c_netherrack or node_id == c_netherrack_deep then data[vi] = c_air end + end + end + end +end + +-- Since we already know where all the rooms and their walls are, and have all the nodes stored +-- in a voxelmanip already, we may as well add a little Nether flair to the dungeons found here. +nether.mapgen.decorate_dungeons = function(data, area, rooms) + + local xStride, yStride, zStride = 1, area.ystride, area.zstride + local minEdge, maxEdge = area.MinEdge, area.MaxEdge + + for _, roomInfo in ipairs(rooms) do + + local room_min, room_max = roomInfo.minp, roomInfo.maxp + local room_size = vector.distance(room_min, room_max) + + if room_size > 10 then + local room_seed = roomInfo.x + 3 * roomInfo.z + 13 * roomInfo.y + local window_y = roomInfo.y + math_min(2, room_max.y - roomInfo.y - 1) + local roomWidth = room_max.x - room_min.x + 1 + local roomLength = room_max.z - room_min.z + 1 + + if room_seed % 3 == 0 and room_max.y < maxEdge.y then + -- Glowstone chandelier (feel free to replace with a fancy schematic) + local vi = area:index(roomInfo.x, room_max.y + 1, roomInfo.z) + if is_dungeon_brick(data[vi]) then data[vi] = c_glowstone end + + elseif room_seed % 4 == 0 and room_min.y > minEdge.y + and room_min.x > minEdge.x and room_max.x < maxEdge.x + and room_min.z > minEdge.z and room_max.z < maxEdge.z then + -- lava well (feel free to replace with a fancy schematic) + local vi = area:index(roomInfo.x, room_min.y, roomInfo.z) + if is_dungeon_brick(data[vi - yStride]) then + data[vi - yStride] = c_lava_source + if data[vi - zStride] == c_air then data[vi - zStride] = c_netherbrick_slab end + if data[vi + zStride] == c_air then data[vi + zStride] = c_netherbrick_slab end + if data[vi - xStride] == c_air then data[vi - xStride] = c_netherbrick_slab end + if data[vi + xStride] == c_air then data[vi + xStride] = c_netherbrick_slab end + end + end + + -- Barred windows + if room_seed % 7 < 5 and roomWidth >= 5 and roomLength >= 5 + and window_y >= minEdge.y and window_y + 1 <= maxEdge.y + and room_min.x > minEdge.x and room_max.x < maxEdge.x + and room_min.z > minEdge.z and room_max.z < maxEdge.z then + --data[area:indexp(roomInfo)] = minetest.get_content_id("default:mese_post_light") -- debug + + -- Can't use glass panes because they need the param data set. + -- Until whisper glass is added, every window will be made of netherbrick fence (rather + -- than material depending on room_seed) + local window_node = c_netherfence + --if c_netherglass ~= nil and room_seed % 20 >= 12 then window_node = c_crystallight end + + local function placeWindow(vi, viOutsideOffset, windowNo) + if is_dungeon_brick(data[vi]) and is_dungeon_brick(data[vi + yStride]) then + data[vi] = window_node + + if room_seed % 19 == windowNo then + -- place a glowstone light behind the window + local node_id = data[vi + viOutsideOffset] + if node_id == c_netherrack then + data[vi + viOutsideOffset] = c_glowstone + elseif node_id == c_netherrack_deep then + data[vi + viOutsideOffset] = c_glowstone_deep + end + end + end + end + + local vi_min = area:index(room_min.x - 1, window_y, roomInfo.z) + local vi_max = area:index(room_max.x + 1, window_y, roomInfo.z) + local locations = {-zStride, zStride, -zStride + yStride, zStride + yStride} + for i, offset in ipairs(locations) do + placeWindow(vi_min + offset, -1, i) + placeWindow(vi_max + offset, 1, i + #locations) + 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 i, offset in ipairs(locations) do + placeWindow(vi_min + offset, -zStride, i + #locations * 2) + placeWindow(vi_max + offset, zStride, i + #locations * 3) + end + end + + -- pillars or mezzanine floor + if room_seed % 43 > 10 and roomWidth >= 6 and roomLength >= 6 then + + local pillar_vi = {} + local pillarHeight = 0 + local wallDist = 1 + math.floor((roomWidth + roomLength) / 14) + + local roomHeight = room_max.y - room_min.y + if roomHeight >= 7 then + -- mezzanine floor + local mezzMax = { + x = room_min.x + math.floor(roomWidth / 7 * 4), + y = room_min.y + math.floor(roomHeight / 5 * 3), + z = room_max.z + } + + pillarHeight = mezzMax.y - room_min.y - 1 + pillar_vi = { + area:index(mezzMax.x, room_min.y, room_min.z + wallDist), + area:index(mezzMax.x, room_min.y, room_max.z - wallDist), + } + + if is_dungeon_brick(data[pillar_vi[1] - yStride]) and is_dungeon_brick(data[pillar_vi[2] - yStride]) then + -- The floor of the dungeon looks like it exists (i.e. not erased by nether + -- cavern), so add the mezzanine floor + for z = 0, roomLength - 1 do + local vi = area:index(room_min.x, mezzMax.y, room_min.z + z) + for x = room_min.x, mezzMax.x do + if data[vi] == c_air then data[vi] = c_dungeonbrick end + vi = vi + 1 + end + end + end + + elseif roomHeight >= 4 then + -- 4 pillars + pillarHeight = roomHeight + pillar_vi = { + area:index(room_min.x + wallDist, room_min.y, room_min.z + wallDist), + area:index(room_min.x + wallDist, room_min.y, room_max.z - wallDist), + area:index(room_max.x - wallDist, room_min.y, room_min.z + wallDist), + area:index(room_max.x - wallDist, room_min.y, room_max.z - wallDist) + } + end + + for i = #pillar_vi, 1, -1 do + if not is_dungeon_brick(data[pillar_vi[i] - yStride]) then + -- there's no dungeon floor under this pillar so skip it, it's probably been cut away by nether cavern. + table.remove(pillar_vi, i) + end + end + for y = 0, pillarHeight do + for _, vi in ipairs(pillar_vi) do + if data[vi + y * yStride] == c_air then data[vi + y * yStride] = c_dungeonbrick end + end + end + end + + -- Weeds on the floor once Nether weeds are added + end + end +end diff --git a/mods/world/nether/mapgen_mantle.lua b/mods/world/nether/mapgen_mantle.lua index e453e19f..7883fb08 100644 --- a/mods/world/nether/mapgen_mantle.lua +++ b/mods/world/nether/mapgen_mantle.lua @@ -27,6 +27,7 @@ local debugf = nether.debug local mapgen = nether.mapgen +local S = nether.get_translator local BASALT_COLUMN_UPPER_LIMIT = mapgen.BASALT_COLUMN_UPPER_LIMIT local BASALT_COLUMN_LOWER_LIMIT = mapgen.BASALT_COLUMN_LOWER_LIMIT @@ -49,7 +50,6 @@ local np_basalt = { local nobj_basalt = nil local nbuf_basalt = {} -local cavePerlin = nil -- Content ids @@ -119,7 +119,7 @@ mapgen.add_basalt_columns = function(data, area, minp, maxp) local yStride = area.ystride local yCaveStride = x1 - x0 + 1 - cavePerlin = cavePerlin or minetest.get_perlin(mapgen.np_cave) + local cavePerlin = mapgen.get_cave_point_perlin() 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) @@ -136,7 +136,7 @@ mapgen.add_basalt_columns = function(data, area, minp, maxp) 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})) + local abs_sealevel_cave_noise = math_abs(cavePerlin:get_3d({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. @@ -399,11 +399,14 @@ mapgen.excavate_tunnel_to_center_of_the_nether = function(data, area, nvals_cave 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 _, cavern_limit_distance = mapgen.find_nearest_lava_sealevel(mantle_y) local _, centerRegionLimit_adj = mapgen.get_mapgenblend_adjustments(mantle_y) + + -- 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 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 + centerRegionLimit_adj if lowest + cavern_noise_adj < mapgen.CENTER_CAVERN_LIMIT then excavate_pathway(data, area, area:position(highest_vi), area:position(lowest_vi), minp, maxp) @@ -414,63 +417,96 @@ mapgen.excavate_tunnel_to_center_of_the_nether = function(data, area, nvals_cave end +-- an enumerated list of the different regions in the nether +mapgen.RegionEnum = { + OVERWORLD = {name = "overworld", desc = S("The Overworld") }, -- Outside the Nether / none of the regions in the Nether + POSITIVE = {name = "positive", desc = S("Positive nether") }, -- The classic nether caverns are here - where cavePerlin > 0.6 + POSITIVESHELL = {name = "positive shell", desc = S("Shell between positive nether and center region") }, -- the nether side of the wall/buffer area separating classic nether from the mantle + CENTER = {name = "center", desc = S("Center/Mantle, inside cavern") }, + CENTERSHELL = {name = "center shell", desc = S("Center/Mantle, but outside the caverns") }, -- the mantle side of the wall/buffer area separating the positive and negative regions from the center region + NEGATIVE = {name = "negative", desc = S("Negative nether") }, -- Secondary/spare region - where cavePerlin < -0.6 + NEGATIVESHELL = {name = "negative shell", desc = S("Shell between negative nether and center region") } -- the spare region side of the wall/buffer area separating the negative region from the mantle +} + + +-- Returns (region, noise) where region is a value from mapgen.RegionEnum +-- and noise is the unadjusted cave perlin value +mapgen.get_region = function(pos) + + if pos.y > nether.DEPTH_CEILING or pos.y < nether.DEPTH_FLOOR then + return mapgen.RegionEnum.OVERWORLD, nil + end + + local caveNoise = mapgen.get_cave_perlin_at(pos) + local sealevel, 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 + + -- 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 + local cavern_noise_adj = + mapgen.CENTER_REGION_LIMIT * (cavern_limit_distance * cavern_limit_distance * cavern_limit_distance) - + centerRegionLimit_adj + + local region + if caveNoise > tcave then + region = mapgen.RegionEnum.POSITIVE + elseif -caveNoise > tcave then + region = mapgen.RegionEnum.NEGATIVE + elseif math_abs(caveNoise) < tmantle then + + if math_abs(caveNoise) + cavern_noise_adj < mapgen.CENTER_CAVERN_LIMIT then + region = mapgen.RegionEnum.CENTER + else + region = mapgen.RegionEnum.CENTERSHELL + end + + elseif caveNoise > 0 then + region = mapgen.RegionEnum.POSITIVESHELL + else + region = mapgen.RegionEnum.NEGATIVESHELL + end + + return region, caveNoise +end + + minetest.register_chatcommand("nether_whereami", - { - description = "Describes which region of the nether the player is in", + { + description = S("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 + if player == nil then return false, S("Unknown player position") end + local playerPos = vector.round(player:get_pos()) - 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 + local region, caveNoise = mapgen.get_region(playerPos) + local seaLevel, cavernLimitDistance = mapgen.find_nearest_lava_sealevel(playerPos.y) + local tcave_adj, centerRegionLimit_adj = mapgen.get_mapgenblend_adjustments(playerPos.y) - 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 seaDesc = "" + local boundaryDesc = "" + local perlinDesc = "" - local desc + if region ~= mapgen.RegionEnum.OVERWORLD then - 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" + local seaPos = playerPos.y - seaLevel + if seaPos > 0 then + seaDesc = S(", @1m above lava-sea level", seaPos) else - desc = desc .. " but outside cavern" + seaDesc = S(", @1m below lava-sea level", seaPos) end - elseif densityNoise > 0 then - desc = "Shell between positive nether and center region" - else - desc = "Shell between negative nether and center region" + if tcave_adj > 0 then + boundaryDesc = S(", approaching y boundary of Nether") + end + + perlinDesc = S("[Perlin @1] ", (math_floor(caveNoise * 1000) / 1000)) 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 + return true, S("@1@2@3@4", perlinDesc, region.desc, seaDesc, boundaryDesc) end } ) diff --git a/mods/world/nether/mapgen_nobiomes.lua b/mods/world/nether/mapgen_nobiomes.lua index 28792b8c..1a371282 100644 --- a/mods/world/nether/mapgen_nobiomes.lua +++ b/mods/world/nether/mapgen_nobiomes.lua @@ -216,7 +216,7 @@ function nether.find_nether_ground_y(target_x, target_z, start_y, player_name) local maxp = {x = maxp_schem.x, y = 0, z = maxp_schem.z} for y = start_y, math.max(NETHER_FLOOR + BLEND, start_y - 4096), -1 do - local nval_cave = nobj_cave_point:get3d({x = target_x, y = y, z = target_z}) + local nval_cave = nobj_cave_point:get_3d({x = target_x, y = y, z = target_z}) if nval_cave > TCAVE then -- Cavern air = air + 1 diff --git a/mods/world/nether/mod.conf b/mods/world/nether/mod.conf index 5b6c73a3..727c6f2c 100644 --- a/mods/world/nether/mod.conf +++ b/mods/world/nether/mod.conf @@ -1,4 +1,7 @@ name = nether description = Adds a deep underground realm with different mapgen that you can reach with obsidian portals. depends = stairs, default -optional_depends = moreblocks, mesecons, loot, dungeon_loot, doc_basics, fire +optional_depends = moreblocks, mesecons, loot, dungeon_loot, doc_basics, fire, climate_api, ethereal +release = 8686 +author = PilzAdam +title = Nether diff --git a/mods/world/nether/nether_api.txt b/mods/world/nether/nether_api.txt new file mode 100644 index 00000000..40f86a57 --- /dev/null +++ b/mods/world/nether/nether_api.txt @@ -0,0 +1,78 @@ +Modding/interop guide to Nether +=============================== + +For portals API see portal_api.txt + +The Nether mod exposes some of its functions and data via the lua global +`nether` and `nether.mapgen` + + +* `nether.DEPTH_CEILING`: [read-only] Y value of the top of the Nether. + +* `nether.DEPTH_FLOOR`: [read-only] Y value of the bottom of the Nether. + +* `nether.DEPTH_FLOOR_LAYERS`: [writable] Gives the bottom Y of all + locations that wish to be considered part of the Nether. + DEPTH_FLOOR_LAYERS Allows mods to insert extra layers below the + Nether, by knowing where their layer ceiling should start, and letting + the layers be included in effects which only happen in the Nether. + If a mod wishes to add a layer below the Nether it should read + `nether.DEPTH_FLOOR_LAYERS` to find the bottom Y of the Nether and any + other layers already under the Nether. The mod should leave a small gap + between DEPTH_FLOOR_LAYERS and its ceiling (e.g. use DEPTH_FLOOR_LAYERS - 6 + for its ceiling Y, so there is room to shift edge-case biomes), then set + `nether.DEPTH_FLOOR_LAYERS` to reflect the mod's floor Y value, and call + `shift_existing_biomes()` with DEPTH_FLOOR_LAYERS as the `floor_y` argument. + +* `nether.NETHER_REALM_ENABLED`: [read-only] Gets the value of the "Enable + Nether realm & portal" setting the nether mod exposes in Minetest's + "All Settings" -> "Mods" -> "nether" options. + When false, the entire nether mapgen is disabled (not run), and the portal + to it is not registered. Reasons someone might disable the Nether realm + include if a nether-layer mod was to be used as the Nether instead, or if + the portal mechanic was desired in a game without the Nether, etc. + +* `nether.useBiomes`: [read-only] When this is false, the Nether interop + functions below are not available (nil). + Indicates that the biomes-enabled mapgen is in use. The Nether mod falls back + to older mapgen code for v6 maps and old versions of Minetest, the older + mapgen code doesn't use biomes and doesn't provide API/interop functions. + + +Mapgen functions available when nether.useBiomes is true +-------------------------------------------------------- + +The following functions are nil if `nether.useBiomes` is false, +and also nil if `nether.NETHER_REALM_ENABLED` is false. + +* `nether.mapgen.shift_existing_biomes(floor_y, ceiling_y)` Move any existing + biomes out of the y-range specified by `floor_y` and `ceiling_y`. + +* `nether.mapgen.get_region(pos)`: Returns two values, (region, noise) where + `region` is a value from `nether.mapgen.RegionEnum` and `noise` is the + unadjusted cave perlin value. + * `nether.mapgen.RegionEnum` values are tables which contain an invariant + `name` and a localized `desc`. Current region names include overworld, + positive, positive shell, center, center shell, negative, and negative + shell. + "positive" corresponds to conventional Nether caverns, and "center" + corresponds to the Mantle region. + +* `nether.mapgen.get_cave_point_perlin()`: Returns the PerlinNoise object for + the Nether's cavern noise. + +* `nether.mapgen.get_cave_perlin_at(pos)`: Returns the Nether cavern noise + value at a given 3D position. + + +Other mapgen functions +------------------------------------------- + +If the Nether realm is enabled, then this function will be available +regardless of whether `nether.useBiomes` is true: + +* `nether.find_nether_ground_y(target_x, target_z, start_y, player_name)` + Uses 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. \ No newline at end of file diff --git a/mods/world/nether/nodes.lua b/mods/world/nether/nodes.lua index dea70701..073f6b0f 100644 --- a/mods/world/nether/nodes.lua +++ b/mods/world/nether/nodes.lua @@ -161,6 +161,13 @@ minetest.register_node("nether:fence_nether_brick", { sounds = default.node_sound_stone_defaults(), }) +minetest.register_node("nether:brick_deep", { + description = S("Deep Nether Brick"), + tiles = {"nether_brick_deep.png"}, + is_ground_content = false, + groups = {cracky = 2, level = 2}, + sounds = default.node_sound_stone_defaults() +}) -- Register stair and slab @@ -308,6 +315,8 @@ lavasea_source.tiles = { }, }, } +lavasea_source.groups = { not_in_creative_inventory = 1 } -- Avoid having two lava source blocks in the inv. +for key, value in pairs(lava_source.groups) do lavasea_source.groups[key] = value end lavasea_source.liquid_alternative_source = "nether:lava_source" lavasea_source.inventory_image = minetest.inventorycube( "nether_lava_source_animated.png^[sheet:2x16:0,0", @@ -327,8 +336,8 @@ nether.cool_lava = function(pos, node) -- 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 + if minetest.get_item_group(node_above.name, "water") > 0 and + pos.y < nether.DEPTH_CEILING and pos.y > nether.DEPTH_FLOOR_LAYERS 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"}) @@ -374,7 +383,7 @@ 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 + if minetest.get_modpath("bucket") and minetest.global_exists("bucket") and type(bucket.liquids) == "table" then local lava_bucket = bucket.liquids["default:lava_source"] if lava_bucket ~= nil then local lavasea_bucket = {} @@ -527,7 +536,7 @@ minetest.register_node("nether:lava_crust", { paramtype = "light", light_source = default.LIGHT_MAX - 3, buildable_to = false, - walkable_to = true, + walkable = true, is_ground_content = true, drop = { items = {{ @@ -595,7 +604,7 @@ local function fumarole_onTimer(pos, elapsed) -- Fumaroles in the Nether can catch fire. -- (if taken to the surface and used as cottage chimneys, they don't catch fire) - local inNether = pos.y <= nether.DEPTH and pos.y >= nether.DEPTH_FLOOR + local inNether = pos.y <= nether.DEPTH and pos.y >= nether.DEPTH_FLOOR_LAYERS local canCatchFire = inNether and minetest.registered_nodes["fire:permanent_flame"] ~= nil local smoke_offset = 0 local timeout_factor = 1 diff --git a/mods/world/nether/portal_api.lua b/mods/world/nether/portal_api.lua index 7dd2d547..c67b27d2 100644 --- a/mods/world/nether/portal_api.lua +++ b/mods/world/nether/portal_api.lua @@ -2007,7 +2007,8 @@ local wormhole_nodedef_default = { a = 160, r = 128, g = 0, b = 80 }, sunlight_propagates = true, - use_texture_alpha = "blend", + use_texture_alpha = minetest.features.use_texture_alpha_string_modes + and "blend" or true, walkable = false, diggable = false, pointable = false, @@ -2015,7 +2016,6 @@ local wormhole_nodedef_default = { is_ground_content = false, drop = "", light_source = 5, - alpha = 192, node_box = { type = "fixed", fixed = { @@ -2073,7 +2073,7 @@ function nether.register_portal(name, portaldef) end portaldef.name = name - portaldef.mod_name = minetest.get_current_modname() + portaldef.mod_name = minetest.get_current_modname() or "" -- use portaldef_default for any values missing from portaldef or portaldef.sounds if portaldef.sounds ~= nil then setmetatable(portaldef.sounds, {__index = portaldef_default.sounds}) end @@ -2211,7 +2211,7 @@ function nether.volume_is_natural_and_unprotected(minp, maxp, player_name) if id ~= c_air and id ~= c_ignore and id ~= nil then -- checked for common natural or not emerged local name = minetest.get_name_from_content_id(id) local nodedef = minetest.registered_nodes[name] - if not nodedef.is_ground_content then + if nodedef and not nodedef.is_ground_content then -- trees are natural but not "ground content" local node_groups = nodedef.groups if node_groups == nil or (node_groups.tree == nil and node_groups.leaves == nil and node_groups.leafdecay == nil) then diff --git a/mods/world/nether/portal_api.txt b/mods/world/nether/portal_api.txt index ecac5005..15c82d83 100644 --- a/mods/world/nether/portal_api.txt +++ b/mods/world/nether/portal_api.txt @@ -250,16 +250,16 @@ Used by `nether.register_portal`. -- player_name may be "", e.g. if the portal was ignited by a mesecon, -- and is provided for use with volume_is_natural_and_unprotected() etc. - on_run_wormhole = function(portalDef, anochorPos, orientation), + on_run_wormhole = function(portalDef, anchorPos, orientation), -- invoked once per second per portal - on_extinguish = function(portalDef, anochorPos, orientation), + on_extinguish = function(portalDef, anchorPos, orientation), -- invoked when a portal is extinguished, including when the portal -- it connected to was extinguished. on_player_teleported = function(portalDef, player, oldPos, newPos), -- invoked immediately after a player is teleported - on_ignite = function(portalDef, anochorPos, orientation) + on_ignite = function(portalDef, anchorPos, orientation) -- invoked when a player or mesecon ignites a portal - on_created = function(portalDef, anochorPos, orientation) + on_created = function(portalDef, anchorPos, orientation) -- invoked when a portal creates a remote twin, this is usually when -- a player travels through a portal for the first time. } diff --git a/mods/world/nether/portal_examples.lua b/mods/world/nether/portal_examples.lua index ac71848a..663ee636 100644 --- a/mods/world/nether/portal_examples.lua +++ b/mods/world/nether/portal_examples.lua @@ -106,7 +106,7 @@ end -- Surface-travel portal, playable code example -- --==============================================-- --- These Moore Curve functions requred by surface_portal's find_surface_anchorPos() will +-- These Moore Curve functions required by surface_portal's find_surface_anchorPos() will -- be assigned later in this file. local get_moore_distance -- will be function get_moore_distance(cell_count, x, y): integer local get_moore_coords -- will be function get_moore_coords(cell_count, distance): pos2d diff --git a/mods/world/nether/textures/nether_brick_deep.png b/mods/world/nether/textures/nether_brick_deep.png new file mode 100644 index 00000000..a11e53e4 Binary files /dev/null and b/mods/world/nether/textures/nether_brick_deep.png differ