From 957fdc887b81bc802ed6a3000e75c07c355e5618 Mon Sep 17 00:00:00 2001 From: Treer Date: Fri, 27 Jan 2023 01:59:05 +1100 Subject: [PATCH] Add giant crystal geodes (#35) --- .luacheckrc | 1 + README.md | 2 + depends.txt | 1 + locale/nether.fr.tr | 8 ++ locale/template.txt | 8 ++ mapgen.lua | 16 ++- mapgen_dungeons.lua | 13 +- mapgen_geodes.lua | 221 ++++++++++++++++++++++++++++++++ mod.conf | 2 +- nodes.lua | 57 ++++++++ textures/nether_geode.png | Bin 0 -> 2763 bytes textures/nether_geode_glass.png | Bin 0 -> 568 bytes 12 files changed, 317 insertions(+), 12 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/.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 0000000000000000000000000000000000000000..6835c86bc76025fb464717f3c037d01e20130e37 GIT binary patch literal 2763 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 R%TE9R002ovPDHLkV1f-j6gmI^ literal 0 HcmV?d00001 diff --git a/textures/nether_geode_glass.png b/textures/nether_geode_glass.png new file mode 100644 index 0000000000000000000000000000000000000000..4921c7bb988ff376e17e9b608b2bbf79185d4fc3 GIT binary patch literal 568 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnF3?v&v(vJfvg8-ip*FZkLV4m<)A;CKJ=5`^$ z!!|Z|^Yb3}^*w8B_~hyNY3I&A%a*Z=iRtU>|9<`)$etj5L=dQ#rzFTPcoh<;e|GW# z0|R4}r;B5VMzHO~jb4WXMB2_j$Y|f!xc-8Kf z)V4JRdYoYF4)nB0zs6or_<7FcSBj_qe!4%~{(kEj#$Zi;qa-=Y6EVv@Pi_t7Uhzxj zc=xGmwajys{(h-k@ZiX{gjs9;q_SDfTA=oq%gj#l0?YE-3KKeN+B}5MhAM2fT)p_i z*7Stc2~VGAt1E~U%;b>pKJ<3?*AtE}t@QyFq!L|1$rmBKEg3_SGc|9~_<5p}N^)qOrtGNhMk4!%9mvj0&_D@M(5V zujHQ7RQ%N`+aPo*!{<#a{xOO^Yi0CynWS6sf$0--zb)h0RbQ1DSmrnt0{KPvMUU3U znK-IUKel%IOtq}HMfa;TO(#az2I+4;C1lSfcZK_9s