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/mapgen.lua b/mapgen.lua index df82e58..df6e6fc 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") @@ -392,12 +394,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) -- 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 +415,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..d58b0fd 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 diff --git a/mapgen_geodes.lua b/mapgen_geodes.lua new file mode 100644 index 0000000..280809c --- /dev/null +++ b/mapgen_geodes.lua @@ -0,0 +1,220 @@ +--[[ + + 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, lasty +local adj_z = 0, 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 d4_1 < 0.08 then + return c_glowstone + end + + return c_air +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 f222c48..8fb1ddc 100644 --- a/nodes.lua +++ b/nodes.lua @@ -180,6 +180,56 @@ 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 + }}, + --light_source = 1, + 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. +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, + is_ground_content = true, + drop = "nether:geode", + groups = {cracky = 3, oddly_breakable_by_hand = 3, nether_crystal = 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 = {"nether_geode_glass.png", "", "xpanes_edge_obsidian.png"}, + inventory_image = "nether_geode_glass.png", + wield_image = "nether_geode_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..078f1bb 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..8d733a9 Binary files /dev/null and b/textures/nether_geode_glass.png differ