diff --git a/.luacheckrc b/.luacheckrc index 29d0418..319eba3 100644 --- a/.luacheckrc +++ b/.luacheckrc @@ -30,5 +30,6 @@ read_globals = { "vector", "VoxelArea", "VoxelManip", + xpanes = { fields = { "register_pane" } }, } diff --git a/README.md b/README.md index 3f20e49..1291f5c 100644 --- a/README.md +++ b/README.md @@ -65,6 +65,8 @@ SOFTWARE. * `nether_book_`* (files starting with "nether_book"): Treer, 2019-2020 * `nether_brick_deep.png`: Treer, 2021 * `nether_fumarole.ogg`: Treer, 2020 + * `nether_geode.png`: Treer, 2021 + * `nether_geode_glass.png`: Treer, 2021 * `nether_lava_bubble`* (files starting with "nether_lava_bubble"): Treer, 2020 * `nether_lava_crust_animated.png`: Treer, 2019-2020 * `nether_lightstaff.png`: Treer, 2021 diff --git a/depends.txt b/depends.txt index 2ccaf36..93b4329 100644 --- a/depends.txt +++ b/depends.txt @@ -7,3 +7,4 @@ loot? mesecons? moreblocks? climate_api? +xpanes? \ No newline at end of file diff --git a/locale/nether.fr.tr b/locale/nether.fr.tr index 12ce386..2bfbb6c 100644 --- a/locale/nether.fr.tr +++ b/locale/nether.fr.tr @@ -64,11 +64,19 @@ Lava Crust= Lava crust is strong enough to walk on, but still hot enough to inflict burns.= Nether Basalt= +Nether Beryl= +Nether Berylite= Nether Brick=Brique du Nether Nether Brick Fence=Barrière en briques du Nether Nether Brick Fence Rail=Clôture en briques du Nether +Nether Crystal Pane= Nether Slab=Dalle du Nether Nether Stair=Escalier du Nether + +Nether geode crystal, found lining the interior walls of Nether geodes= + +Nether geode crystal. A crystalline structure with faint glow found inside large Nether geodes= + Netherrack=Roche du Nether Netherrack from deep in the mantle= Netherrack stair= diff --git a/locale/template.txt b/locale/template.txt index fa3d032..018091c 100644 --- a/locale/template.txt +++ b/locale/template.txt @@ -63,11 +63,19 @@ Lava Crust= Lava crust is strong enough to walk on, but still hot enough to inflict burns.= Nether Basalt= +Nether Beryl= +Nether Berylite= Nether Brick= Nether Brick Fence= Nether Brick Fence Rail= +Nether Crystal Pane= Nether Slab= Nether Stair= + +Nether geode crystal, found lining the interior walls of Nether geodes= + +Nether geode crystal. A crystalline structure with faint glow found inside large Nether geodes= + Netherrack= Netherrack from deep in the mantle= Netherrack stair= diff --git a/mapgen.lua b/mapgen.lua index df82e58..e2316b1 100644 --- a/mapgen.lua +++ b/mapgen.lua @@ -71,6 +71,7 @@ end -- Load specialty helper functions dofile(nether.path .. "/mapgen_dungeons.lua") dofile(nether.path .. "/mapgen_mantle.lua") +dofile(nether.path .. "/mapgen_geodes.lua") -- Misc math functions @@ -283,6 +284,7 @@ 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_crystaldark = minetest.get_content_id("nether:geode") 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") @@ -382,7 +384,8 @@ local function on_generated(minp, maxp, seed) if cave_noise > tcave then -- Prime region -- This was the only region in initial versions of the Nether mod. - -- It is the only region which portals from the surface will open into. + -- It is the only region which portals from the surface will open into, + -- getting to any other regions in the Nether will require Shanks' Pony. data[vi] = c_air contains_nether = true @@ -392,12 +395,11 @@ local function on_generated(minp, maxp, seed) -- 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. + data[vi] = mapgen.getGeodeInteriorNodeId(x, y, z)-- function from mapgen_geodes.lua -- 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 + contains_nether = true else -- netherrack walls and/or center region/mantle abs_cave_noise = math_abs(cave_noise) @@ -414,7 +416,11 @@ local function on_generated(minp, maxp, seed) 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 + if cave_noise > 0 then + data[vi] = c_netherrack -- excavate_dungeons() will mostly reverse this inside dungeons + else + data[vi] = c_crystaldark + end end end diff --git a/mapgen_dungeons.lua b/mapgen_dungeons.lua index 64a73af..ca77ae1 100644 --- a/mapgen_dungeons.lua +++ b/mapgen_dungeons.lua @@ -33,6 +33,7 @@ minetest.set_gen_notify({dungeon = true}) 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_crystaldark = minetest.get_content_id("nether:geode") 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") @@ -164,7 +165,7 @@ nether.mapgen.excavate_dungeons = function(data, area, rooms) 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 + if node_id == c_netherrack or node_id == c_netherrack_deep or node_id == c_crystaldark then data[vi] = c_air end vi = vi + 1 end end @@ -181,7 +182,7 @@ nether.mapgen.excavate_dungeons = function(data, area, rooms) 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 + if node_id == c_netherrack or node_id == c_netherrack_deep or node_id == c_crystaldark then data[vi] = c_air end end end end @@ -231,11 +232,11 @@ nether.mapgen.decorate_dungeons = function(data, area, rooms) 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) + -- Glass panes can't go in the windows because we aren't setting param data. + -- Until a Nether 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 + --if c_netherglass ~= nil and room_seed % 20 >= 12 then window_node = c_netherglass end local function placeWindow(vi, viOutsideOffset, windowNo) if is_dungeon_brick(data[vi]) and is_dungeon_brick(data[vi + yStride]) then diff --git a/mapgen_geodes.lua b/mapgen_geodes.lua new file mode 100644 index 0000000..810e19e --- /dev/null +++ b/mapgen_geodes.lua @@ -0,0 +1,221 @@ +--[[ + + Nether mod for minetest + + This file contains helper functions for generating geode interiors, + a proof-of-concept to demonstrate how the secondary/spare region + in the nether might be put to use by someone. + + + 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 + + +-- Content ids + +local c_air = minetest.get_content_id("air") +local c_crystal = minetest.get_content_id("nether:geodelite") -- geodelite has a faint glow +local c_netherrack = minetest.get_content_id("nether:rack") +local c_glowstone = minetest.get_content_id("nether:glowstone") + +-- Math funcs +local math_max, math_min, math_abs, math_floor, math_pi = math.max, math.min, math.abs, math.floor, math.pi -- avoid needing table lookups each time a common math function is invoked + + +-- Create a tiling space of close-packed spheres, using Hexagonal close packing +-- of spheres with radius 0.5. +-- With a layer of spheres on a flat surface, if the pack-z distance is 1 due to 0.5 +-- radius then the pack-x distance will be the height of an equilateral triangle: sqrt(3) / 2, +-- and the pack-y distance between each layer will be sqrt(6) / 3, +-- The tessellating space will be a rectangular box of 2*pack-x by 1*pack-z by 3*pack-y + +local xPack = math.sqrt(3)/2 -- 0.866, height of an equalateral triangle +local xPack2 = xPack * 2 -- 1.732 +local yPack = math.sqrt(6) / 3 -- 0.816, y height of each layer +local yPack2 = yPack * 2 +local yPack3 = yPack * 3 +local layer2offsetx = xPack / 3 -- 0.289, height to center of equalateral triangle +local layer3offsetx = xPack2 / 3 -- 0.577 +local structureSize = 50 -- magic numbers may need retuning if this changes too much + +local layer1 = { + {0, 0, 0}, + {0, 0, 1}, + {xPack, 0, -0.5}, + {xPack, 0, 0.5}, + {xPack, 0, 1.5}, + {xPack2, 0, 0}, + {xPack2, 0, 1}, +} +local layer2 = { + {layer2offsetx - xPack, yPack, 0}, + {layer2offsetx - xPack, yPack, 1}, + {layer2offsetx, yPack, -0.5}, + {layer2offsetx, yPack, 0.5}, + {layer2offsetx, yPack, 1.5}, + {layer2offsetx + xPack, yPack, 0}, + {layer2offsetx + xPack, yPack, 1}, + {layer2offsetx + xPack2, yPack, -0.5}, + {layer2offsetx + xPack2, yPack, 0.5}, + {layer2offsetx + xPack2, yPack, 1.5}, +} +local layer3 = { + {layer3offsetx - xPack, yPack2, -0.5}, + {layer3offsetx - xPack, yPack2, 0.5}, + {layer3offsetx - xPack, yPack2, 1.5}, + {layer3offsetx, yPack2, 0}, + {layer3offsetx, yPack2, 1}, + {layer3offsetx + xPack, yPack2, -0.5}, + {layer3offsetx + xPack, yPack2, 0.5}, + {layer3offsetx + xPack, yPack2, 1.5}, + {layer3offsetx + xPack2, yPack2, 0}, + {layer3offsetx + xPack2, yPack2, 1}, +} +local layer4 = { + {0, yPack3, 0}, + {0, yPack3, 1}, + {xPack, yPack3, -0.5}, + {xPack, yPack3, 0.5}, + {xPack, yPack3, 1.5}, + {xPack2, yPack3, 0}, + {xPack2, yPack3, 1}, +} +local layers = { + {y = layer1[1][2], points = layer1}, -- layer1[1][2] is the y value of the first point in layer1, and all spheres in a layer have the same y + {y = layer2[1][2], points = layer2}, + {y = layer3[1][2], points = layer3}, + {y = layer4[1][2], points = layer4}, +} + + +-- Geode mapgen functions (AKA proof of secondary/spare region concept) + + +-- fast for small lists +function insertionSort(array) + local i + for i = 2, #array do + local key = array[i] + local j = i - 1 + while j > 0 and array[j] > key do + array[j + 1] = array[j] + j = j - 1 + end + array[j + 1] = key + end + return array +end + + +local distSquaredList = {} +local adj_x = 0 +local adj_y = 0 +local adj_z = 0 +local lasty, lastz +local warpx, warpz + + +-- It's quite a lot to calculate for each air node, but its not terribly slow and +-- it'll be pretty darn rare for chunks in the secondary region to ever get emerged. +mapgen.getGeodeInteriorNodeId = function(x, y, z) + + if z ~= lastz then + lastz = z + -- Calculate structure warping + -- To avoid calculating this for each node there's no warping as you look along the x axis :( + adj_y = math.sin(math_pi / 222 * y) * 30 + + if y ~= lasty then + lasty = y + warpx = math.sin(math_pi / 100 * y) * 10 + warpz = math.sin(math_pi / 43 * y) * 15 + end + local twistRadians = math_pi / 73 * y + local sinTwist, cosTwist = math.sin(twistRadians), math.cos(twistRadians) + adj_x = cosTwist * warpx - sinTwist * warpz + adj_z = sinTwist * warpx + cosTwist * warpz + end + + -- convert x, y, z into a position in the tessellating space + local cell_x = (((x + adj_x) / xPack2 + 0.5) % structureSize) / structureSize * xPack2 + local cell_y = (((y + adj_y) / yPack3 + 0.5) % structureSize) / structureSize * yPack3 + local cell_z = (((z + adj_z) + 0.5) % structureSize) / structureSize -- zPack = 1, so can be omitted + + local iOut = 1 + local i, j + local canSkip = false + + for i = 1, #layers do + + local layer = layers[i] + local dy = cell_y - layer.y + + if dy > -0.71 and dy < 0.71 then -- optimization - don't include points to far away to make a difference. (0.71 comes from sin(45°)) + local points = layer.points + + for j = 1, #points do + + local point = points[j] + local dx = cell_x - point[1] + local dz = cell_z - point[3] + local distSquared = dx*dx + dy*dy + dz*dz + + if distSquared < 0.25 then + -- optimization - point is inside a sphere, so cannot be a wall edge. (0.25 comes from radius of 0.5 squared) + return c_air + end + + distSquaredList[iOut] = distSquared + iOut = iOut + 1 + end + end + end + + -- clear the rest of the array instead of creating a new one to hopefully reduce luajit mem leaks. + while distSquaredList[iOut] ~= nil do + rawset(distSquaredList, iOut, nil) + iOut = iOut + 1 + end + + insertionSort(distSquaredList) + + local d3_1 = distSquaredList[3] - distSquaredList[1] + local d3_2 = distSquaredList[3] - distSquaredList[2] + --local d4_1 = distSquaredList[4] - distSquaredList[1] + --local d4_3 = distSquaredList[4] - distSquaredList[3] + + -- Some shape formulas (tuned for a structureSize of 50) + -- (d3_1 < 0.05) gives connective lines + -- (d3_1 < 0.05 or d3_2 < .02) give fancy elven bridges - prob doesn't need the d3_1 part + -- ((d3_1 < 0.05 or d3_2 < .02) and distSquaredList[1] > .3) tapers the fancy connections in the middle + -- (d4_3 < 0.03 and d3_2 < 0.03) produces caltrops at intersections + -- (d4_1 < 0.1) produces spherish balls at intersections + -- The idea is voronoi based - edges in a voronoi diagram are where each nearby point is at equal distance. + -- In this case we use squared distances to avoid calculating square roots. + + if (d3_1 < 0.05 or d3_2 < .02) and distSquaredList[1] > .3 then + return c_crystal + elseif (distSquaredList[4] - distSquaredList[1]) < 0.08 then + return c_glowstone + else + return c_air + end +end \ No newline at end of file diff --git a/mod.conf b/mod.conf index 76467cb..50426d2 100644 --- a/mod.conf +++ b/mod.conf @@ -1,4 +1,4 @@ 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, climate_api, ethereal, walls +optional_depends = moreblocks, mesecons, loot, dungeon_loot, doc_basics, fire, climate_api, ethereal, xpanes, walls diff --git a/nodes.lua b/nodes.lua index 9aa0d9d..52503f2 100644 --- a/nodes.lua +++ b/nodes.lua @@ -180,6 +180,63 @@ minetest.register_node("nether:rack", { sounds = default.node_sound_stone_defaults(), }) +-- Geode crystals can only be introduced by the biomes-based mapgen, since it requires the +-- MT 5.0 world-align texture features. +minetest.register_node("nether:geode", { + description = S("Nether Beryl"), + _doc_items_longdesc = S("Nether geode crystal, found lining the interior walls of Nether geodes"), + tiles = {{ + name = "nether_geode.png", + align_style = "world", + scale = 4 + }}, + is_ground_content = true, + groups = {cracky = 3, oddly_breakable_by_hand = 3, nether_crystal = 1}, + sounds = default.node_sound_glass_defaults(), +}) + +-- Nether Berylite is a Beryl that can seen in the dark, used to light up the internal structure +-- of the geode, so to avoid player confusion we'll just have it drop plain Beryl, and have only +-- plain Beryl in the creative inventory. +minetest.register_node("nether:geodelite", { + description = S("Nether Berylite"), + _doc_items_longdesc = S("Nether geode crystal. A crystalline structure with faint glow found inside large Nether geodes"), + tiles = {{ + name = "nether_geode.png", + align_style = "world", + scale = 4 + }}, + light_source = 2, + drop = "nether:geode", + is_ground_content = true, + groups = {cracky = 3, oddly_breakable_by_hand = 3, nether_crystal = 1, not_in_creative_inventory = 1}, + sounds = default.node_sound_glass_defaults(), +}) + +if minetest.get_modpath("xpanes") and minetest.global_exists("xpanes") and xpanes.register_pane ~= nil then + xpanes.register_pane("nether_crystal_pane", { + description = S("Nether Crystal Pane"), + textures = { + { + name = "nether_geode_glass.png", + align_style = "world", + scale = 2 + }, + "", + "xpanes_edge_obsidian.png" + }, + inventory_image = "([combine:32x32:-8,-8=nether_geode_glass.png:24,-8=nether_geode_glass.png:-8,24=nether_geode_glass.png:24,24=nether_geode_glass.png)^[resize:16x16^[multiply:#922^default_obsidian_glass.png", + wield_image = "([combine:32x32:-8,-8=nether_geode_glass.png:24,-8=nether_geode_glass.png:-8,24=nether_geode_glass.png:24,24=nether_geode_glass.png)^[resize:16x16^[multiply:#922^default_obsidian_glass.png", use_texture_alpha = true, + sounds = default.node_sound_glass_defaults(), + groups = {snappy=2, cracky=3, oddly_breakable_by_hand=3}, + recipe = { + {"group:nether_crystal", "group:nether_crystal", "group:nether_crystal"}, + {"group:nether_crystal", "group:nether_crystal", "group:nether_crystal"} + } + }) +end + + -- Deep Netherrack, found in the mantle / central magma layers minetest.register_node("nether:rack_deep", { description = S("Deep Netherrack"), diff --git a/textures/nether_geode.png b/textures/nether_geode.png new file mode 100644 index 0000000..6835c86 Binary files /dev/null and b/textures/nether_geode.png differ diff --git a/textures/nether_geode_glass.png b/textures/nether_geode_glass.png new file mode 100644 index 0000000..4921c7b Binary files /dev/null and b/textures/nether_geode_glass.png differ