From 0be2f4beb1bb973c0666e37c54a394e207215bf7 Mon Sep 17 00:00:00 2001 From: Treer Date: Sun, 31 Jan 2021 03:44:04 +1100 Subject: [PATCH] Add crystal geodes Initial commit for proof-of-concept demoing how someone might make use of the spare region in the Nether. --- depends.txt | 1 + mapgen.lua | 13 +- mapgen_dungeons.lua | 3 +- mapgen_geodes.lua | 220 ++++++++++++++++++++++++++++++++ mod.conf | 2 +- nodes.lua | 50 ++++++++ textures/nether_geode.png | Bin 0 -> 4547 bytes textures/nether_geode_glass.png | Bin 0 -> 495 bytes 8 files changed, 283 insertions(+), 6 deletions(-) create mode 100644 mapgen_geodes.lua create mode 100644 textures/nether_geode.png create mode 100644 textures/nether_geode_glass.png 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 0000000000000000000000000000000000000000..078f1bba333a37366fcdadcbb422f09f555500d0 GIT binary patch literal 4547 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
R#j{_b$?%;_H?_|=Mme8FF9*2 zl@!D>oS#oAvDhdX=LoLbPM+A(R+C^Elj*}~*&B&82`%V&Y``QnNYhcGHEq5q>qhpJ^*BniD$`QJ5zd9x^$cx^jvOpx9k>G+47usE|i;7j%=+$1hL((rzsroec zHR4Wy2inxYl^1QieJT6?;sCCH{Qdh=k2LbP@NxB@AD5D_vyBypYn(S9)RJ5;jSUXlz-hXj#`OpDfi-H>lIgYSN z$hq`dc|9T4)g_wpb^Sbk5;pUC?%m#Pcix=XgQ*@x-Zco31^+x5y9t&4L5?{DoGt~m zA$;?XwHXH#*F;$QeVUq>RBBb+3ko#ztR4Q)?XEzMAoLpYt3E!==n3%NaCYCZ1yk69 zmKtcw9UJp$G&^4PZS_h#QcMjh z=AQMI*pjtY?^RlF4eIgWWAZ_rE5LtDEsm`h$10cuJOF__f+>_>nCNMl4ZO3U(mAvc yKRwYqg=OI7@l*4Ff`UlFhRF&Krc-Ggkm#WWwv11|Zgc@~kb|u=?z**a%>MzJSeh^Z literal 0 HcmV?d00001 diff --git a/textures/nether_geode_glass.png b/textures/nether_geode_glass.png new file mode 100644 index 0000000000000000000000000000000000000000..8d733a95682316be99c676d12aa0f55ac4bca141 GIT binary patch literal 495 zcmVlfO>eP!z_07|U^bb*WPDz=Q&e(+&(*p&jVN(y{8q z1MmiHJU}{SWXseiU}RxqpwKHsr34lVogD?Wmeu&m;0_MAb`VtJlg@C?x!J5uUnld_i@^|9vZ1z?T>ZT?~8%yM~m%kPQ*d32*3fT2lw z4gw)?qa=Zv_voXwx9=+cSsZgU{sTbtP2&Kpzkb8}xIC(zJ8WS(St_gLE<1aYNO`qa%}(P#7?1?8U^6q zC%I!A%raJ` zv}Jc&q24~fP}5&Lp6rerB?&#S