mirror of
https://github.com/minetest-mods/nether.git
synced 2024-11-10 20:40:22 +01:00
47d39871d2
Large caverns using squashed 3D noise Less lava, more glowstone, no air pockets Preserve nether dungeons Allow light to spread from glowstone and make brighter Fix 'is ground content' settings, portals should not be excavated by overgenerated caves Rack drops itself as normal Make brick, fence, stair, slab groups consistent Use 'node sound gravel defaults' for sand Make brick crafting consistent with default Make fence recipe output number consistent with default Make 'emerge area' volume mimimum required size Add 'find nether target y' function to search for a cavern floor with space above for nether portal spawn
648 lines
16 KiB
Lua
648 lines
16 KiB
Lua
-- Parameters
|
|
|
|
local NETHER_DEPTH = -5000
|
|
local TCAVE = 0.6
|
|
local BLEND = 128
|
|
|
|
|
|
-- 3D noise
|
|
|
|
local np_cave = {
|
|
offset = 0,
|
|
scale = 1,
|
|
spread = {x = 384, y = 128, z = 384}, -- squashed 3:1
|
|
seed = 59033,
|
|
octaves = 5,
|
|
persist = 0.7
|
|
}
|
|
|
|
|
|
-- Stuff
|
|
|
|
local yblmax = NETHER_DEPTH - BLEND * 2
|
|
|
|
|
|
-- Functions
|
|
|
|
local function build_portal(pos, target)
|
|
local p = {x = pos.x - 1, y = pos.y - 1, z = pos.z}
|
|
local p1 = {x = pos.x - 1, y = pos.y - 1, z = pos.z}
|
|
local p2 = {x = p1.x + 3, y = p1.y + 4, z = p1.z}
|
|
|
|
for i = 1, 4 do
|
|
minetest.set_node(p, {name = "default:obsidian"})
|
|
p.y = p.y + 1
|
|
end
|
|
for i = 1, 3 do
|
|
minetest.set_node(p, {name = "default:obsidian"})
|
|
p.x = p.x + 1
|
|
end
|
|
for i = 1, 4 do
|
|
minetest.set_node(p, {name = "default:obsidian"})
|
|
p.y = p.y - 1
|
|
end
|
|
for i = 1, 3 do
|
|
minetest.set_node(p, {name = "default:obsidian"})
|
|
p.x = p.x - 1
|
|
end
|
|
|
|
for x = p1.x, p2.x do
|
|
for y = p1.y, p2.y do
|
|
p = {x = x, y = y, z = p1.z}
|
|
if not (x == p1.x or x == p2.x or y == p1.y or y == p2.y) then
|
|
minetest.set_node(p, {name = "nether:portal", param2 = 0})
|
|
end
|
|
local meta = minetest.get_meta(p)
|
|
meta:set_string("p1", minetest.pos_to_string(p1))
|
|
meta:set_string("p2", minetest.pos_to_string(p2))
|
|
meta:set_string("target", minetest.pos_to_string(target))
|
|
|
|
if y ~= p1.y then
|
|
for z = -2, 2 do
|
|
if z ~= 0 then
|
|
p.z = p.z + z
|
|
if minetest.registered_nodes[
|
|
minetest.get_node(p).name].is_ground_content then
|
|
minetest.remove_node(p)
|
|
end
|
|
p.z = p.z - z
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
local function find_nether_target_y(target_x, target_z)
|
|
local start_y = NETHER_DEPTH - math.random(500, 1500) -- Search start
|
|
local nobj_cave_point = minetest.get_perlin(np_cave)
|
|
local air = 0 -- Consecutive air nodes found
|
|
|
|
for y = start_y, start_y - 4096, -1 do
|
|
local nval_cave = nobj_cave_point:get3d({x = target_x, y = y, z = target_z})
|
|
|
|
if nval_cave > TCAVE then -- Cavern
|
|
air = air + 1
|
|
else -- Not cavern, check if 4 nodes of space above
|
|
if air >= 4 then
|
|
return y + 2
|
|
else -- Not enough space, reset air to zero
|
|
air = 0
|
|
end
|
|
end
|
|
end
|
|
|
|
return y -- Fallback
|
|
end
|
|
|
|
local function move_check(p1, max, dir)
|
|
local p = {x = p1.x, y = p1.y, z = p1.z}
|
|
local d = math.abs(max - p1[dir]) / (max - p1[dir])
|
|
|
|
while p[dir] ~= max do
|
|
p[dir] = p[dir] + d
|
|
if minetest.get_node(p).name ~= "default:obsidian" then
|
|
return false
|
|
end
|
|
end
|
|
|
|
return true
|
|
end
|
|
|
|
local function check_portal(p1, p2)
|
|
if p1.x ~= p2.x then
|
|
if not move_check(p1, p2.x, "x") then
|
|
return false
|
|
end
|
|
if not move_check(p2, p1.x, "x") then
|
|
return false
|
|
end
|
|
elseif p1.z ~= p2.z then
|
|
if not move_check(p1, p2.z, "z") then
|
|
return false
|
|
end
|
|
if not move_check(p2, p1.z, "z") then
|
|
return false
|
|
end
|
|
else
|
|
return false
|
|
end
|
|
|
|
if not move_check(p1, p2.y, "y") then
|
|
return false
|
|
end
|
|
if not move_check(p2, p1.y, "y") then
|
|
return false
|
|
end
|
|
|
|
return true
|
|
end
|
|
|
|
local function is_portal(pos)
|
|
for d = -3, 3 do
|
|
for y = -4, 4 do
|
|
local px = {x = pos.x + d, y = pos.y + y, z = pos.z}
|
|
local pz = {x = pos.x, y = pos.y + y, z = pos.z + d}
|
|
|
|
if check_portal(px, {x = px.x + 3, y = px.y + 4, z = px.z}) then
|
|
return px, {x = px.x + 3, y = px.y + 4, z = px.z}
|
|
end
|
|
if check_portal(pz, {x = pz.x, y = pz.y + 4, z = pz.z + 3}) then
|
|
return pz, {x = pz.x, y = pz.y + 4, z = pz.z + 3}
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
local function make_portal(pos)
|
|
local p1, p2 = is_portal(pos)
|
|
if not p1 or not p2 then
|
|
return false
|
|
end
|
|
|
|
for d = 1, 2 do
|
|
for y = p1.y + 1, p2.y - 1 do
|
|
local p
|
|
if p1.z == p2.z then
|
|
p = {x = p1.x + d, y = y, z = p1.z}
|
|
else
|
|
p = {x = p1.x, y = y, z = p1.z + d}
|
|
end
|
|
if minetest.get_node(p).name ~= "air" then
|
|
return false
|
|
end
|
|
end
|
|
end
|
|
|
|
local param2
|
|
if p1.z == p2.z then
|
|
param2 = 0
|
|
else
|
|
param2 = 1
|
|
end
|
|
|
|
local target = {x = p1.x, y = p1.y, z = p1.z}
|
|
target.x = target.x + 1
|
|
if target.y < NETHER_DEPTH then
|
|
target.y = math.random(-32, 1)
|
|
else
|
|
target.y = find_nether_target_y(target.x, target.z)
|
|
end
|
|
|
|
for d = 0, 3 do
|
|
for y = p1.y, p2.y do
|
|
local p = {}
|
|
if param2 == 0 then
|
|
p = {x = p1.x + d, y = y, z = p1.z}
|
|
else
|
|
p = {x = p1.x, y = y, z = p1.z + d}
|
|
end
|
|
if minetest.get_node(p).name == "air" then
|
|
minetest.set_node(p, {name = "nether:portal", param2 = param2})
|
|
end
|
|
local meta = minetest.get_meta(p)
|
|
meta:set_string("p1", minetest.pos_to_string(p1))
|
|
meta:set_string("p2", minetest.pos_to_string(p2))
|
|
meta:set_string("target", minetest.pos_to_string(target))
|
|
end
|
|
end
|
|
|
|
return true
|
|
end
|
|
|
|
|
|
-- ABMs
|
|
|
|
minetest.register_abm({
|
|
nodenames = {"nether:portal"},
|
|
interval = 1,
|
|
chance = 2,
|
|
action = function(pos, node)
|
|
minetest.add_particlespawner(
|
|
32, --amount
|
|
4, --time
|
|
{x = pos.x - 0.25, y = pos.y - 0.25, z = pos.z - 0.25}, --minpos
|
|
{x = pos.x + 0.25, y = pos.y + 0.25, z = pos.z + 0.25}, --maxpos
|
|
{x = -0.8, y = -0.8, z = -0.8}, --minvel
|
|
{x = 0.8, y = 0.8, z = 0.8}, --maxvel
|
|
{x = 0, y = 0, z = 0}, --minacc
|
|
{x = 0, y = 0, z = 0}, --maxacc
|
|
0.5, --minexptime
|
|
1, --maxexptime
|
|
1, --minsize
|
|
2, --maxsize
|
|
false, --collisiondetection
|
|
"nether_particle.png" --texture
|
|
)
|
|
for _, obj in ipairs(minetest.get_objects_inside_radius(pos, 1)) do
|
|
if obj:is_player() then
|
|
local meta = minetest.get_meta(pos)
|
|
local target = minetest.string_to_pos(meta:get_string("target"))
|
|
if target then
|
|
-- force emerge of target area
|
|
minetest.get_voxel_manip():read_from_map(target, target)
|
|
if not minetest.get_node_or_nil(target) then
|
|
minetest.emerge_area(
|
|
vector.subtract(target, 4), vector.add(target, 4))
|
|
end
|
|
-- teleport the player
|
|
minetest.after(3, function(obj, pos, target)
|
|
local objpos = obj:getpos()
|
|
objpos.y = objpos.y + 0.1 -- Fix some glitches at -8000
|
|
if minetest.get_node(objpos).name ~= "nether:portal" then
|
|
return
|
|
end
|
|
|
|
obj:setpos(target)
|
|
|
|
local function check_and_build_portal(pos, target)
|
|
local n = minetest.get_node_or_nil(target)
|
|
if n and n.name ~= "nether:portal" then
|
|
build_portal(target, pos)
|
|
minetest.after(2, check_and_build_portal, pos, target)
|
|
minetest.after(4, check_and_build_portal, pos, target)
|
|
elseif not n then
|
|
minetest.after(1, check_and_build_portal, pos, target)
|
|
end
|
|
end
|
|
|
|
minetest.after(1, check_and_build_portal, pos, target)
|
|
|
|
end, obj, pos, target)
|
|
end
|
|
end
|
|
end
|
|
end,
|
|
})
|
|
|
|
|
|
-- Nodes
|
|
|
|
minetest.register_node("nether:portal", {
|
|
description = "Nether Portal",
|
|
tiles = {
|
|
"nether_transparent.png",
|
|
"nether_transparent.png",
|
|
"nether_transparent.png",
|
|
"nether_transparent.png",
|
|
{
|
|
name = "nether_portal.png",
|
|
animation = {
|
|
type = "vertical_frames",
|
|
aspect_w = 16,
|
|
aspect_h = 16,
|
|
length = 0.5,
|
|
},
|
|
},
|
|
{
|
|
name = "nether_portal.png",
|
|
animation = {
|
|
type = "vertical_frames",
|
|
aspect_w = 16,
|
|
aspect_h = 16,
|
|
length = 0.5,
|
|
},
|
|
},
|
|
},
|
|
drawtype = "nodebox",
|
|
paramtype = "light",
|
|
paramtype2 = "facedir",
|
|
sunlight_propagates = true,
|
|
use_texture_alpha = true,
|
|
walkable = false,
|
|
diggable = false,
|
|
pointable = false,
|
|
buildable_to = false,
|
|
is_ground_content = false,
|
|
drop = "",
|
|
light_source = 5,
|
|
post_effect_color = {a = 180, r = 128, g = 0, b = 128},
|
|
alpha = 192,
|
|
node_box = {
|
|
type = "fixed",
|
|
fixed = {
|
|
{-0.5, -0.5, -0.1, 0.5, 0.5, 0.1},
|
|
},
|
|
},
|
|
groups = {not_in_creative_inventory = 1}
|
|
})
|
|
|
|
minetest.register_node(":default:obsidian", {
|
|
description = "Obsidian",
|
|
tiles = {"default_obsidian.png"},
|
|
is_ground_content = false,
|
|
sounds = default.node_sound_stone_defaults(),
|
|
groups = {cracky = 1, level = 2},
|
|
|
|
on_destruct = function(pos)
|
|
local meta = minetest.get_meta(pos)
|
|
local p1 = minetest.string_to_pos(meta:get_string("p1"))
|
|
local p2 = minetest.string_to_pos(meta:get_string("p2"))
|
|
local target = minetest.string_to_pos(meta:get_string("target"))
|
|
if not p1 or not p2 then
|
|
return
|
|
end
|
|
|
|
for x = p1.x, p2.x do
|
|
for y = p1.y, p2.y do
|
|
for z = p1.z, p2.z do
|
|
local nn = minetest.get_node({x = x, y = y, z = z}).name
|
|
if nn == "default:obsidian" or nn == "nether:portal" then
|
|
if nn == "nether:portal" then
|
|
minetest.remove_node({x = x, y = y, z = z})
|
|
end
|
|
local m = minetest.get_meta({x = x, y = y, z = z})
|
|
m:set_string("p1", "")
|
|
m:set_string("p2", "")
|
|
m:set_string("target", "")
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
meta = minetest.get_meta(target)
|
|
if not meta then
|
|
return
|
|
end
|
|
p1 = minetest.string_to_pos(meta:get_string("p1"))
|
|
p2 = minetest.string_to_pos(meta:get_string("p2"))
|
|
if not p1 or not p2 then
|
|
return
|
|
end
|
|
|
|
for x = p1.x, p2.x do
|
|
for y = p1.y, p2.y do
|
|
for z = p1.z, p2.z do
|
|
local nn = minetest.get_node({x = x, y = y, z = z}).name
|
|
if nn == "default:obsidian" or nn == "nether:portal" then
|
|
if nn == "nether:portal" then
|
|
minetest.remove_node({x = x, y = y, z = z})
|
|
end
|
|
local m = minetest.get_meta({x = x, y = y, z = z})
|
|
m:set_string("p1", "")
|
|
m:set_string("p2", "")
|
|
m:set_string("target", "")
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end,
|
|
})
|
|
|
|
minetest.register_node("nether:rack", {
|
|
description = "Netherrack",
|
|
tiles = {"nether_rack.png"},
|
|
is_ground_content = true,
|
|
groups = {cracky = 3, level = 2},
|
|
sounds = default.node_sound_stone_defaults(),
|
|
})
|
|
|
|
minetest.register_node("nether:sand", {
|
|
description = "Nethersand",
|
|
tiles = {"nether_sand.png"},
|
|
is_ground_content = true,
|
|
groups = {crumbly = 3, level = 2, falling_node = 1},
|
|
sounds = default.node_sound_gravel_defaults({
|
|
footstep = {name = "default_gravel_footstep", gain = 0.45},
|
|
}),
|
|
})
|
|
|
|
minetest.register_node("nether:glowstone", {
|
|
description = "Glowstone",
|
|
tiles = {"nether_glowstone.png"},
|
|
is_ground_content = true,
|
|
light_source = 14,
|
|
paramtype = "light",
|
|
groups = {cracky = 3, oddly_breakable_by_hand = 3},
|
|
sounds = default.node_sound_glass_defaults(),
|
|
})
|
|
|
|
minetest.register_node("nether:brick", {
|
|
description = "Nether Brick",
|
|
tiles = {"nether_brick.png"},
|
|
is_ground_content = false,
|
|
groups = {cracky = 2, level = 2},
|
|
sounds = default.node_sound_stone_defaults(),
|
|
})
|
|
|
|
local fence_texture =
|
|
"default_fence_overlay.png^nether_brick.png^default_fence_overlay.png^[makealpha:255,126,126"
|
|
|
|
minetest.register_node("nether:fence_nether_brick", {
|
|
description = "Nether Brick Fence",
|
|
drawtype = "fencelike",
|
|
tiles = {"nether_brick.png"},
|
|
inventory_image = fence_texture,
|
|
wield_image = fence_texture,
|
|
paramtype = "light",
|
|
sunlight_propagates = true,
|
|
is_ground_content = false,
|
|
selection_box = {
|
|
type = "fixed",
|
|
fixed = {-1/7, -1/2, -1/7, 1/7, 1/2, 1/7},
|
|
},
|
|
groups = {cracky = 2, level = 2},
|
|
sounds = default.node_sound_stone_defaults(),
|
|
})
|
|
|
|
|
|
-- Register stair and slab
|
|
|
|
stairs.register_stair_and_slab(
|
|
"nether_brick",
|
|
"nether:brick",
|
|
{cracky = 2, level = 2},
|
|
{"nether_brick.png"},
|
|
"nether stair",
|
|
"nether slab",
|
|
default.node_sound_stone_defaults()
|
|
)
|
|
|
|
|
|
-- Craftitems
|
|
|
|
minetest.register_craftitem(":default:mese_crystal_fragment", {
|
|
description = "Mese Crystal Fragment",
|
|
inventory_image = "default_mese_crystal_fragment.png",
|
|
on_place = function(stack, _, pt)
|
|
if pt.under and minetest.get_node(pt.under).name == "default:obsidian" then
|
|
local done = make_portal(pt.under)
|
|
if done and not minetest.setting_getbool("creative_mode") then
|
|
stack:take_item()
|
|
end
|
|
end
|
|
|
|
return stack
|
|
end,
|
|
})
|
|
|
|
|
|
-- Crafting
|
|
|
|
minetest.register_craft({
|
|
output = "nether:brick 4",
|
|
recipe = {
|
|
{"nether:rack", "nether:rack"},
|
|
{"nether:rack", "nether:rack"},
|
|
}
|
|
})
|
|
|
|
minetest.register_craft({
|
|
output = "nether:fence_nether_brick 6",
|
|
recipe = {
|
|
{"nether:brick", "nether:brick", "nether:brick"},
|
|
{"nether:brick", "nether:brick", "nether:brick"},
|
|
},
|
|
})
|
|
|
|
|
|
-- Mapgen
|
|
|
|
-- Initialize noise object and localise noise buffer
|
|
|
|
local nobj_cave = nil
|
|
local nbuf_cave
|
|
|
|
|
|
-- Content ids
|
|
|
|
local c_air = minetest.get_content_id("air")
|
|
|
|
local c_stone_with_coal = minetest.get_content_id("default:stone_with_coal")
|
|
local c_stone_with_iron = minetest.get_content_id("default:stone_with_iron")
|
|
local c_stone_with_mese = minetest.get_content_id("default:stone_with_mese")
|
|
local c_stone_with_diamond = minetest.get_content_id("default:stone_with_diamond")
|
|
local c_stone_with_gold = minetest.get_content_id("default:stone_with_gold")
|
|
local c_stone_with_copper = minetest.get_content_id("default:stone_with_copper")
|
|
local c_mese = minetest.get_content_id("default:mese")
|
|
|
|
local c_gravel = minetest.get_content_id("default:gravel")
|
|
local c_dirt = minetest.get_content_id("default:dirt")
|
|
local c_sand = minetest.get_content_id("default:sand")
|
|
|
|
local c_cobble = minetest.get_content_id("default:cobble")
|
|
local c_mossycobble = minetest.get_content_id("default:mossycobble")
|
|
local c_stair_cobble = minetest.get_content_id("stairs:stair_cobble")
|
|
|
|
local c_lava_source = minetest.get_content_id("default:lava_source")
|
|
local c_lava_flowing = minetest.get_content_id("default:lava_flowing")
|
|
local c_water_source = minetest.get_content_id("default:water_source")
|
|
local c_water_flowing = minetest.get_content_id("default:water_flowing")
|
|
|
|
local c_glowstone = minetest.get_content_id("nether:glowstone")
|
|
local c_nethersand = minetest.get_content_id("nether:sand")
|
|
local c_netherbrick = minetest.get_content_id("nether:brick")
|
|
local c_netherrack = minetest.get_content_id("nether:rack")
|
|
|
|
|
|
-- On-generated function
|
|
|
|
minetest.register_on_generated(function(minp, maxp, seed)
|
|
if minp.y > NETHER_DEPTH then
|
|
return
|
|
end
|
|
|
|
local t1 = os.clock()
|
|
|
|
local x1 = maxp.x
|
|
local y1 = maxp.y
|
|
local z1 = maxp.z
|
|
local x0 = minp.x
|
|
local y0 = minp.y
|
|
local z0 = minp.z
|
|
|
|
local vm, emin, emax = minetest.get_mapgen_object("voxelmanip")
|
|
local area = VoxelArea:new{MinEdge = emin, MaxEdge = emax}
|
|
local data = vm:get_data()
|
|
|
|
local x11 = emax.x -- Limits of mapchunk plus mapblock shell
|
|
local y11 = emax.y
|
|
local z11 = emax.z
|
|
local x00 = emin.x
|
|
local y00 = emin.y
|
|
local z00 = emin.z
|
|
|
|
local ystride = x1 - x0 + 1
|
|
local zstride = ystride * ystride
|
|
local chulens = {x = ystride, y = ystride, z = ystride}
|
|
local minposxyz = {x = x0, y = y0, z = z0}
|
|
|
|
nobj_cave = nobj_cave or minetest.get_perlin_map(np_cave, chulens)
|
|
local nvals_cave = nobj_cave:get3dMap_flat(minposxyz, nbuf_cave)
|
|
|
|
for y = y00, y11 do -- Y loop first to minimise tcave calculations
|
|
local tcave
|
|
local in_chunk_y = false
|
|
if y >= y0 and y <= y1 then
|
|
if y > yblmax then
|
|
tcave = TCAVE + ((y - yblmax) / BLEND) ^ 2
|
|
else
|
|
tcave = TCAVE
|
|
end
|
|
in_chunk_y = true
|
|
end
|
|
|
|
for z = z00, z11 do
|
|
local vi = area:index(x00, y, z) -- Initial voxelmanip index
|
|
local ni
|
|
local in_chunk_yz = in_chunk_y and z >= z0 and z <= z1
|
|
|
|
for x = x00, x11 do
|
|
if in_chunk_yz and x == x0 then
|
|
-- Initial noisemap index
|
|
ni = (z - z0) * zstride + (y - y0) * ystride + 1
|
|
end
|
|
local in_chunk_yzx = in_chunk_yz and x >= x0 and x <= x1 -- In mapchunk
|
|
|
|
local id = data[vi] -- Existing node
|
|
-- Cave air, cave liquids and dungeons are overgenerated,
|
|
-- convert these throughout mapchunk plus shell
|
|
if id == c_air or -- Air and liquids to air
|
|
id == c_lava_source or
|
|
id == c_lava_flowing or
|
|
id == c_water_source or
|
|
id == c_water_flowing then
|
|
data[vi] = c_air
|
|
-- Dungeons are preserved so we don't need
|
|
-- to check for cavern in the shell
|
|
elseif id == c_cobble or -- Dungeons (preserved) to netherbrick
|
|
id == c_mossycobble or
|
|
id == c_stair_cobble then
|
|
data[vi] = c_netherbrick
|
|
end
|
|
|
|
if in_chunk_yzx then -- In mapchunk
|
|
if nvals_cave[ni] > tcave then -- Only excavate cavern in mapchunk
|
|
data[vi] = c_air
|
|
elseif id == c_mese then -- Mese block to lava
|
|
data[vi] = c_lava_source
|
|
elseif id == c_stone_with_gold or -- Precious ores to glowstone
|
|
id == c_stone_with_mese or
|
|
id == c_stone_with_diamond then
|
|
data[vi] = c_glowstone
|
|
elseif id == c_gravel or -- Blob ore to nethersand
|
|
id == c_dirt or
|
|
id == c_sand then
|
|
data[vi] = c_nethersand
|
|
else -- All else to netherstone
|
|
data[vi] = c_netherrack
|
|
end
|
|
|
|
ni = ni + 1 -- Only increment noise index in mapchunk
|
|
end
|
|
|
|
vi = vi + 1
|
|
end
|
|
end
|
|
end
|
|
|
|
vm:set_data(data)
|
|
vm:set_lighting({day = 0, night = 0})
|
|
vm:calc_lighting()
|
|
vm:update_liquids()
|
|
vm:write_to_map()
|
|
|
|
local chugent = math.ceil((os.clock() - t1) * 1000)
|
|
print ("[nether] generate chunk " .. chugent .. " ms")
|
|
end)
|