Compare commits
74 Commits
Author | SHA1 | Date | |
---|---|---|---|
02d062b9c9 | |||
e326a94266 | |||
5b3b56ebec | |||
8769593d6f | |||
5cb9e5fb27 | |||
ab4a031c1c | |||
aac3ea6719 | |||
f4255f5d1f | |||
94222d44e0 | |||
86105b4eb8 | |||
0e26770830 | |||
e5fbc2486b | |||
608d692401 | |||
f7ebd78614 | |||
e113db1478 | |||
7939161535 | |||
32e5d818e8 | |||
aa2bfe9472 | |||
4c3545f17a | |||
39d655b4c8 | |||
36d1f6f573 | |||
0618325710 | |||
8f29a35175 | |||
e3ac1ade79 | |||
363872c760 | |||
40913ee841 | |||
e765f5f504 | |||
58da96549a | |||
d0ce9dd633 | |||
1d19546345 | |||
3945467152 | |||
478684062d | |||
4ebdf7f25f | |||
4985e199b1 | |||
48cbd87b59 | |||
43f1f24d53 | |||
a971e0359e | |||
6752964c96 | |||
b9e85582f9 | |||
11a818212a | |||
58eb65c0c8 | |||
6e0cadb1ec | |||
9bfbb64e96 | |||
b8ec09f402 | |||
0f3f42e5c0 | |||
d5a551b4cf | |||
31cf6a9bc3 | |||
7cff6b8cc2 | |||
27a660b731 | |||
dd6c19d005 | |||
835ad9686d | |||
e2666146ca | |||
0c7a6e95c5 | |||
52b20925d5 | |||
6401052f92 | |||
c106b88e98 | |||
1a6d15def3 | |||
43b37e96b2 | |||
b6e2a3335a | |||
3126d067a1 | |||
83c9ad574f | |||
f38211e55d | |||
2113d332f8 | |||
9a4c7ed875 | |||
59275bb242 | |||
c7d83eceb7 | |||
edf961907e | |||
7a4d9ebf01 | |||
a085e194fa | |||
614a3f91a7 | |||
b49f4ce73d | |||
6551f5c120 | |||
b8dab52e41 | |||
bf145d4c11 |
11
.luacheckrc
@ -1,18 +1,29 @@
|
||||
unused_args = false
|
||||
allow_defined_top = true
|
||||
|
||||
globals = {
|
||||
"nether"
|
||||
}
|
||||
|
||||
read_globals = {
|
||||
"core",
|
||||
"default",
|
||||
"DIR_DELIM",
|
||||
"dump",
|
||||
"dungeon_loot",
|
||||
"intllib",
|
||||
"ItemStack",
|
||||
"loot",
|
||||
"doc",
|
||||
"math.hypot",
|
||||
"mesecon",
|
||||
"minetest",
|
||||
"nodeupdate",
|
||||
"PcgRandom",
|
||||
"PseudoRandom",
|
||||
"stairs",
|
||||
"stairsplus",
|
||||
"string.split",
|
||||
table = { fields = { "copy", "getn" } },
|
||||
"vector",
|
||||
"VoxelArea",
|
||||
|
35
README.md
@ -1,4 +1,10 @@
|
||||
Nether Mod for Minetest
|
||||
Nether Mod for Minetest, with Portals API.
|
||||
|
||||
See portal_api.txt for how to create custom portals to your own realms.
|
||||
|
||||
See settingtypes.txt or go to "Settings"->"All Settings"->"Mods"->"nether"
|
||||
in the game to view the options provided by this mod.
|
||||
|
||||
|
||||
## License of source code:
|
||||
|
||||
@ -17,15 +23,26 @@ 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.
|
||||
|
||||
## License of media (textures and sounds)
|
||||
## License and attribution of media (textures and sounds)
|
||||
|
||||
Attribution-ShareAlike 3.0 Unported (CC BY-SA 3.0)
|
||||
http://creativecommons.org/licenses/by-sa/3.0/
|
||||
### [Public Domain Dedication (CC0 1.0)](https://creativecommons.org/publicdomain/zero/1.0/)
|
||||
|
||||
## Authors of media files
|
||||
* `nether_portal_teleport.ogg` is a timing adjusted version of "teleport" by outroelison (https://freesound.org/people/outroelison), used under CC0 1.0
|
||||
|
||||
Everything not listed in here:
|
||||
Copyright (C) 2013 PilzAdam
|
||||
### [Attribution 3.0 Unported (CC BY 3.0)](https://creativecommons.org/licenses/by/3.0/)
|
||||
|
||||
* `nether_portal_ambient.ogg` & `nether_portal_ambient.0.ogg` are extractions from "Deep Cinematic Rumble Stereo" by [Patrick Lieberkind](http://www.lieberkindvisuals.dk), used under CC BY 3.0
|
||||
* `nether_portal_extinguish.ogg` is an extraction from "Tight Laser Weapon Hit Scifi" by damjancd (https://freesound.org/people/damjancd), used under CC BY 3.0
|
||||
* `nether_portal_ignite.ogg` is a derivative of "Flame Ignition" by hykenfreak (https://freesound.org/people/hykenfreak), used under CC BY 3.0. "Nether Portal ignite" is licensed under CC BY 3.0 by Treer.
|
||||
|
||||
- nether_rack.png: Zeg9
|
||||
- nether_glowstone.png: BlockMen
|
||||
### [Attribution-ShareAlike 4.0 International (CC BY-SA 4.0)](https://creativecommons.org/licenses/by-sa/4.0/)
|
||||
* `nether_book_`* (files starting with "nether_book"): Treer, 2019-2020
|
||||
* `nether_portal_ignition_failure.ogg`: Treer, 2019
|
||||
* `nether_particle_anim`* (files starting with "nether_particle_anim"): Treer, 2019
|
||||
|
||||
### [Attribution-ShareAlike 3.0 Unported (CC BY-SA 3.0)](http://creativecommons.org/licenses/by-sa/3.0/)
|
||||
* `nether_rack.png`: Zeg9
|
||||
* `nether_glowstone.png`: BlockMen
|
||||
* `nether_portal.png`: [Extex101](https://github.com/Extex101), 2020
|
||||
|
||||
All other media: Copyright © 2013 PilzAdam, licensed under CC BY-SA 3.0 by PilzAdam.
|
@ -1,3 +1,7 @@
|
||||
stairs
|
||||
default
|
||||
moreblocks?
|
||||
mesecons?
|
||||
loot?
|
||||
dungeon_loot?
|
||||
doc_basics?
|
||||
|
835
init.lua
@ -1,4 +1,3 @@
|
||||
|
||||
--[[
|
||||
|
||||
Nether mod for minetest
|
||||
@ -20,690 +19,210 @@
|
||||
|
||||
]]--
|
||||
|
||||
-- Set DEBUG_FLAGS to determine the behavior of nether.debug():
|
||||
-- 0 = off
|
||||
-- 1 = print(...)
|
||||
-- 2 = minetest.chat_send_all(...)
|
||||
-- 4 = minetest.log("info", ...)
|
||||
local DEBUG_FLAGS = 0
|
||||
|
||||
-- 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,
|
||||
lacunarity = 2.0,
|
||||
--flags = ""
|
||||
}
|
||||
|
||||
|
||||
-- Stuff
|
||||
|
||||
local yblmax = NETHER_DEPTH - BLEND * 2
|
||||
|
||||
|
||||
-- Functions
|
||||
|
||||
local function build_portal(pos, target)
|
||||
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}
|
||||
|
||||
local path = minetest.get_modpath("nether") .. "/schematics/nether_portal.mts"
|
||||
minetest.place_schematic({x = p1.x, y = p1.y, z = p1.z - 2}, path, 0, nil, true)
|
||||
|
||||
for y = p1.y, p2.y do
|
||||
for x = p1.x, p2.x do
|
||||
local meta = minetest.get_meta({x = x, y = y, z = p1.z})
|
||||
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
|
||||
local S
|
||||
if minetest.get_translator ~= nil then
|
||||
S = minetest.get_translator("nether")
|
||||
else
|
||||
-- mock the translator function for MT 0.4
|
||||
S = function(str, ...)
|
||||
local args={...}
|
||||
return str:gsub(
|
||||
"@%d+",
|
||||
function(match) return args[tonumber(match:sub(2))] end
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
-- Global Nether namespace
|
||||
nether = {}
|
||||
nether.modname = minetest.get_current_modname()
|
||||
nether.path = minetest.get_modpath(nether.modname)
|
||||
nether.get_translator = S
|
||||
-- nether.useBiomes allows other mods to know whether they can register ores etc. in the Nether.
|
||||
-- See mapgen.lua for an explanation of why minetest.read_schematic is being checked
|
||||
nether.useBiomes = minetest.get_mapgen_setting("mg_name") ~= "v6" and minetest.read_schematic ~= nil
|
||||
|
||||
local function volume_is_natural(minp, maxp)
|
||||
local c_air = minetest.get_content_id("air")
|
||||
local c_ignore = minetest.get_content_id("ignore")
|
||||
|
||||
local vm = minetest.get_voxel_manip()
|
||||
local pos1 = {x = minp.x, y = minp.y, z = minp.z}
|
||||
local pos2 = {x = maxp.x, y = maxp.y, z = maxp.z}
|
||||
local emin, emax = vm:read_from_map(pos1, pos2)
|
||||
local area = VoxelArea:new({MinEdge = emin, MaxEdge = emax})
|
||||
local data = vm:get_data()
|
||||
-- Settings
|
||||
nether.DEPTH_CEILING = -5000 -- The y location of the Nether's celing
|
||||
nether.DEPTH_FLOOR = -11000 -- The y location of the Nether's floor
|
||||
nether.FASTTRAVEL_FACTOR = 8 -- 10 could be better value for Minetest, since there's no sprint, but ex-Minecraft players will be mathing for 8
|
||||
nether.PORTAL_BOOK_LOOT_WEIGHTING = 0.9 -- Likelyhood of finding the Book of Portals (guide) in dungeon chests. Set to 0 to disable.
|
||||
nether.NETHER_REALM_ENABLED = true -- Setting to false disables the Nether and Nether portal
|
||||
|
||||
for z = pos1.z, pos2.z do
|
||||
for y = pos1.y, pos2.y do
|
||||
local vi = area:index(pos1.x, y, z)
|
||||
for x = pos1.x, pos2.x do
|
||||
local id = data[vi] -- Existing node
|
||||
if id ~= c_air and id ~= c_ignore then -- These are natural
|
||||
local name = minetest.get_name_from_content_id(id)
|
||||
if not minetest.registered_nodes[name].is_ground_content then
|
||||
return false
|
||||
end
|
||||
end
|
||||
vi = vi + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return true
|
||||
-- Override default settings with values from the .conf file, if any are present.
|
||||
nether.FASTTRAVEL_FACTOR = tonumber(minetest.settings:get("nether_fasttravel_factor") or nether.FASTTRAVEL_FACTOR)
|
||||
nether.PORTAL_BOOK_LOOT_WEIGHTING = tonumber(minetest.settings:get("nether_portalBook_loot_weighting") or nether.PORTAL_BOOK_LOOT_WEIGHTING)
|
||||
nether.NETHER_REALM_ENABLED = minetest.settings:get_bool("nether_realm_enabled", nether.NETHER_REALM_ENABLED)
|
||||
nether.DEPTH_CEILING = tonumber(minetest.settings:get("nether_depth_ymax") or nether.DEPTH_CEILING)
|
||||
nether.DEPTH_FLOOR = tonumber(minetest.settings:get("nether_depth_ymin") or nether.DEPTH_FLOOR)
|
||||
|
||||
if nether.DEPTH_FLOOR + 1000 > nether.DEPTH_CEILING then
|
||||
error("The lower limit of the Nether must be set at least 1000 lower than the upper limit, and more than 3000 is recommended. Set settingtypes.txt, or 'All Settings' -> 'Mods' -> 'nether' -> 'Nether depth'", 0)
|
||||
end
|
||||
nether.DEPTH = nether.DEPTH_CEILING -- Deprecated, use nether.DEPTH_CEILING instead.
|
||||
|
||||
|
||||
local function find_nether_target_y(target_x, target_z, start_y)
|
||||
local nobj_cave_point = minetest.get_perlin(np_cave)
|
||||
local air = 0 -- Consecutive air nodes found
|
||||
-- A debug-print function that understands vectors etc. and does not
|
||||
-- evaluate when debugging is turned off.
|
||||
-- Works like string.format(), treating the message as a format string.
|
||||
-- nils, tables, and vectors passed as arguments to nether.debug() are
|
||||
-- converted to strings and can be included inside the message with %s
|
||||
function nether.debug(message, ...)
|
||||
|
||||
for y = start_y, start_y - 4096, -1 do
|
||||
local nval_cave = nobj_cave_point:get3d({x = target_x, y = y, z = target_z})
|
||||
local args = {...}
|
||||
local argCount = select("#", ...)
|
||||
|
||||
if nval_cave > TCAVE then -- Cavern
|
||||
air = air + 1
|
||||
else -- Not cavern, check if 4 nodes of space above
|
||||
if air >= 4 then
|
||||
-- Check volume for non-natural nodes
|
||||
local minp = {x = target_x - 1, y = y - 1, z = target_z - 2}
|
||||
local maxp = {x = target_x + 2, y = y + 3, z = target_z + 2}
|
||||
if volume_is_natural(minp, maxp) then
|
||||
return y + 2
|
||||
else -- Restart search a little lower
|
||||
find_nether_target_y(target_x, target_z, y - 16)
|
||||
end
|
||||
else -- Not enough space, reset air to zero
|
||||
air = 0
|
||||
for i = 1, argCount do
|
||||
local arg = args[i]
|
||||
if arg == nil then
|
||||
-- convert nils to strings
|
||||
args[i] = '<nil>'
|
||||
elseif type(arg) == "table" then
|
||||
local tableCount = 0
|
||||
for _,_ in pairs(arg) do tableCount = tableCount + 1 end
|
||||
if tableCount == 3 and arg.x ~= nil and arg.y ~= nil and arg.z ~= nil then
|
||||
-- convert vectors to strings
|
||||
args[i] = minetest.pos_to_string(arg)
|
||||
else
|
||||
-- convert tables to strings
|
||||
-- (calling function can use dump() if a multi-line listing is desired)
|
||||
args[i] = string.gsub(dump(arg, ""), "\n", " ")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return start_y -- Fallback
|
||||
local composed_message = "nether: " .. string.format(message, unpack(args))
|
||||
|
||||
if math.floor(DEBUG_FLAGS / 1) % 2 == 1 then print(composed_message) end
|
||||
if math.floor(DEBUG_FLAGS / 2) % 2 == 1 then minetest.chat_send_all(composed_message) end
|
||||
if math.floor(DEBUG_FLAGS / 4) % 2 == 1 then minetest.log("info", composed_message) end
|
||||
end
|
||||
if DEBUG_FLAGS == 0 then
|
||||
-- do as little evaluation as possible
|
||||
nether.debug = function() end
|
||||
end
|
||||
|
||||
|
||||
local function find_surface_target_y(target_x, target_z, start_y)
|
||||
for y = start_y, start_y - 256, -16 do
|
||||
-- Check volume for non-natural nodes
|
||||
local minp = {x = target_x - 1, y = y - 1, z = target_z - 2}
|
||||
local maxp = {x = target_x + 2, y = y + 3, z = target_z + 2}
|
||||
if volume_is_natural(minp, maxp) then
|
||||
return y
|
||||
end
|
||||
end
|
||||
|
||||
return start_y - 256 -- 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
|
||||
-- Load files
|
||||
dofile(nether.path .. "/portal_api.lua")
|
||||
dofile(nether.path .. "/nodes.lua")
|
||||
if nether.NETHER_REALM_ENABLED then
|
||||
if nether.useBiomes then
|
||||
dofile(nether.path .. "/mapgen.lua")
|
||||
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
|
||||
dofile(nether.path .. "/mapgen_nobiomes.lua")
|
||||
end
|
||||
end
|
||||
dofile(nether.path .. "/portal_examples.lua")
|
||||
|
||||
|
||||
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 = find_surface_target_y(target.x, target.z, -16)
|
||||
else
|
||||
local start_y = NETHER_DEPTH - math.random(500, 1500) -- Search start
|
||||
target.y = find_nether_target_y(target.x, target.z, start_y)
|
||||
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(o, p, t)
|
||||
local objpos = o:getpos()
|
||||
objpos.y = objpos.y + 0.1 -- Fix some glitches at -8000
|
||||
if minetest.get_node(objpos).name ~= "nether:portal" then
|
||||
return
|
||||
end
|
||||
|
||||
o:setpos(t)
|
||||
|
||||
local function check_and_build_portal(pp, tt)
|
||||
local n = minetest.get_node_or_nil(tt)
|
||||
if n and n.name ~= "nether:portal" then
|
||||
build_portal(tt, pp)
|
||||
minetest.after(2, check_and_build_portal, pp, tt)
|
||||
minetest.after(4, check_and_build_portal, pp, tt)
|
||||
elseif not n then
|
||||
minetest.after(1, check_and_build_portal, pp, tt)
|
||||
end
|
||||
end
|
||||
|
||||
minetest.after(1, check_and_build_portal, p, t)
|
||||
|
||||
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()
|
||||
-- Portals are ignited by right-clicking with a mese crystal fragment
|
||||
nether.register_portal_ignition_item(
|
||||
"default:mese_crystal_fragment",
|
||||
{name = "nether_portal_ignition_failure", gain = 0.3}
|
||||
)
|
||||
|
||||
-- StairsPlus
|
||||
|
||||
if minetest.get_modpath("moreblocks") then
|
||||
stairsplus:register_all(
|
||||
"nether", "brick", "nether:brick", {
|
||||
description = "Nether Brick",
|
||||
groups = {cracky = 2, level = 2},
|
||||
tiles = {"nether_brick.png"},
|
||||
sounds = default.node_sound_stone_defaults(),
|
||||
if nether.NETHER_REALM_ENABLED then
|
||||
-- Use the Portal API to add a portal type which goes to the Nether
|
||||
-- See portal_api.txt for documentation
|
||||
nether.register_portal("nether_portal", {
|
||||
shape = nether.PortalShape_Traditional,
|
||||
frame_node_name = "default:obsidian",
|
||||
wormhole_node_color = 0, -- 0 is magenta
|
||||
title = S("Nether Portal"),
|
||||
book_of_portals_pagetext = S([[Construction requires 14 blocks of obsidian, which we found deep underground where water had solidified molten rock. The finished frame is four blocks wide, five blocks high, and stands vertically, like a doorway.
|
||||
|
||||
This opens to a truly hellish place, though for small mercies the air there is still breathable. There is an intriguing dimensional mismatch happening between this realm and ours, as after opening the second portal into it we observed that 10 strides taken in the Nether appear to be an equivalent of @1 in the natural world.
|
||||
|
||||
The expedition parties have found no diamonds or gold, and after an experienced search party failed to return from the trail of a missing expedition party, I must conclude this is a dangerous place.]], 10 * nether.FASTTRAVEL_FACTOR),
|
||||
|
||||
is_within_realm = function(pos) -- return true if pos is inside the Nether
|
||||
return pos.y < nether.DEPTH_CEILING
|
||||
end,
|
||||
|
||||
find_realm_anchorPos = function(surface_anchorPos, player_name)
|
||||
-- divide x and z by a factor of 8 to implement Nether fast-travel
|
||||
local destination_pos = vector.divide(surface_anchorPos, nether.FASTTRAVEL_FACTOR)
|
||||
destination_pos.x = math.floor(0.5 + destination_pos.x) -- round to int
|
||||
destination_pos.z = math.floor(0.5 + destination_pos.z) -- round to int
|
||||
destination_pos.y = nether.DEPTH_CEILING - 1000 -- temp value so find_nearest_working_portal() returns nether portals
|
||||
|
||||
-- a y_factor of 0 makes the search ignore the altitude of the portals (as long as they are in the Nether)
|
||||
local existing_portal_location, existing_portal_orientation =
|
||||
nether.find_nearest_working_portal("nether_portal", destination_pos, 8, 0)
|
||||
|
||||
if existing_portal_location ~= nil then
|
||||
return existing_portal_location, existing_portal_orientation
|
||||
else
|
||||
local start_y = nether.DEPTH_CEILING - math.random(500, 1500) -- Search starting altitude
|
||||
destination_pos.y = nether.find_nether_ground_y(destination_pos.x, destination_pos.z, start_y, player_name)
|
||||
return destination_pos
|
||||
end
|
||||
end,
|
||||
|
||||
find_surface_anchorPos = function(realm_anchorPos, player_name)
|
||||
-- A portal definition doesn't normally need to provide a find_surface_anchorPos() function,
|
||||
-- since find_surface_target_y() will be used by default, but Nether portals also scale position
|
||||
-- to create fast-travel.
|
||||
-- Defining a custom function also means we can look for existing nearby portals.
|
||||
|
||||
-- Multiply x and z by a factor of 8 to implement Nether fast-travel
|
||||
local destination_pos = vector.multiply(realm_anchorPos, nether.FASTTRAVEL_FACTOR)
|
||||
destination_pos.x = math.min(30900, math.max(-30900, destination_pos.x)) -- clip to world boundary
|
||||
destination_pos.z = math.min(30900, math.max(-30900, destination_pos.z)) -- clip to world boundary
|
||||
destination_pos.y = 0 -- temp value so find_nearest_working_portal() doesn't return nether portals
|
||||
|
||||
-- a y_factor of 0 makes the search ignore the altitude of the portals (as long as they are outside the Nether)
|
||||
local existing_portal_location, existing_portal_orientation =
|
||||
nether.find_nearest_working_portal("nether_portal", destination_pos, 8 * nether.FASTTRAVEL_FACTOR, 0)
|
||||
|
||||
if existing_portal_location ~= nil then
|
||||
return existing_portal_location, existing_portal_orientation
|
||||
else
|
||||
destination_pos.y = nether.find_surface_target_y(destination_pos.x, destination_pos.z, "nether_portal", player_name)
|
||||
return destination_pos
|
||||
end
|
||||
end,
|
||||
|
||||
on_ignite = function(portalDef, anchorPos, orientation)
|
||||
|
||||
-- make some sparks fly
|
||||
local p1, p2 = portalDef.shape:get_p1_and_p2_from_anchorPos(anchorPos, orientation)
|
||||
local pos = vector.divide(vector.add(p1, p2), 2)
|
||||
|
||||
local textureName = portalDef.particle_texture
|
||||
if type(textureName) == "table" then textureName = textureName.name end
|
||||
|
||||
minetest.add_particlespawner({
|
||||
amount = 110,
|
||||
time = 0.1,
|
||||
minpos = {x = pos.x - 0.5, y = pos.y - 1.2, z = pos.z - 0.5},
|
||||
maxpos = {x = pos.x + 0.5, y = pos.y + 1.2, z = pos.z + 0.5},
|
||||
minvel = {x = -5, y = -1, z = -5},
|
||||
maxvel = {x = 5, y = 1, z = 5},
|
||||
minacc = {x = 0, y = 0, z = 0},
|
||||
maxacc = {x = 0, y = 0, z = 0},
|
||||
minexptime = 0.1,
|
||||
maxexptime = 0.5,
|
||||
minsize = 0.2 * portalDef.particle_texture_scale,
|
||||
maxsize = 0.8 * portalDef.particle_texture_scale,
|
||||
collisiondetection = false,
|
||||
texture = textureName .. "^[colorize:#F4F:alpha",
|
||||
animation = portalDef.particle_texture_animation,
|
||||
glow = 8
|
||||
})
|
||||
|
||||
end
|
||||
|
||||
})
|
||||
end
|
||||
|
||||
stairs.register_stair_and_slab("brick", "nether:brick",
|
||||
{cracky=3, oddly_breakable_by_hand=1},
|
||||
{"nether_brick.png"},
|
||||
"nether stair",
|
||||
"nether slab",
|
||||
default.node_sound_stone_defaults())
|
||||
|
||||
|
||||
-- Craftitems
|
||||
|
||||
minetest.override_item("default:mese_crystal_fragment", {
|
||||
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.settings:get_bool("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, localise noise and data buffers
|
||||
|
||||
local nobj_cave = nil
|
||||
local nbuf_cave = nil
|
||||
local dbuf = nil
|
||||
|
||||
|
||||
-- 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 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(dbuf)
|
||||
|
||||
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()
|
||||
end)
|
||||
|
506
mapgen.lua
Normal file
@ -0,0 +1,506 @@
|
||||
--[[
|
||||
|
||||
Nether mod for minetest
|
||||
|
||||
Copyright (C) 2013 PilzAdam
|
||||
|
||||
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.
|
||||
|
||||
]]--
|
||||
|
||||
|
||||
-- Parameters
|
||||
|
||||
local NETHER_CEILING = nether.DEPTH_CEILING
|
||||
local NETHER_FLOOR = nether.DEPTH_FLOOR
|
||||
local TCAVE = 0.6
|
||||
local BLEND = 128
|
||||
|
||||
|
||||
-- Stuff
|
||||
|
||||
local math_max, math_min = math.max, math.min -- avoid needing table lookups each time a common math function is invoked
|
||||
|
||||
if minetest.read_schematic == nil then
|
||||
-- Using biomes to create the Nether requires the ability for biomes to set "node_cave_liquid = air".
|
||||
-- This feature was introduced by paramat in b1b40fef1 on 2019-05-19, but we can't test for
|
||||
-- it directly. However b2065756c was merged a few months later (in 2019-08-14) and it is easy
|
||||
-- to directly test for - it adds minetest.read_schematic() - so we use this as a proxy-test
|
||||
-- for whether the Minetest engine is recent enough to have implemented node_cave_liquid=air
|
||||
error("This " .. nether.modname .. " mapgen requires Minetest v5.1 or greater, use mapgen_nobiomes.lua instead.", 0)
|
||||
end
|
||||
|
||||
local function override_underground_biomes()
|
||||
-- https://forum.minetest.net/viewtopic.php?p=257522#p257522
|
||||
-- Q: Is there a way to override an already-registered biome so I can get it out of the
|
||||
-- way of my own underground biomes without disturbing the other biomes registered by
|
||||
-- default?
|
||||
-- A: No, all you can do is use a mod to clear all biomes then re-register the complete
|
||||
-- set but with your changes. It has been described as hacky but this is actually the
|
||||
-- official way to alter biomes, most mods and subgames would want to completely change
|
||||
-- all biomes anyway.
|
||||
-- To avoid the engine side of mapgen becoming overcomplex the approach is to require mods
|
||||
-- to do slightly more complex stuff in Lua.
|
||||
|
||||
-- take a copy of all biomes, decorations, and ores. Regregistering a biome changes its ID, so
|
||||
-- any decorations or ores using the 'biomes' field must afterwards be cleared and re-registered.
|
||||
-- https://github.com/minetest/minetest/issues/9288
|
||||
local registered_biomes_copy = {}
|
||||
local registered_decorations_copy = {}
|
||||
local registered_ores_copy = {}
|
||||
|
||||
for old_biome_key, old_biome_def in pairs(minetest.registered_biomes) do
|
||||
registered_biomes_copy[old_biome_key] = old_biome_def
|
||||
end
|
||||
for old_decoration_key, old_decoration_def in pairs(minetest.registered_decorations) do
|
||||
registered_decorations_copy[old_decoration_key] = old_decoration_def
|
||||
end
|
||||
for old_ore_key, old_ore_def in pairs(minetest.registered_ores) do
|
||||
registered_ores_copy[old_ore_key] = old_ore_def
|
||||
end
|
||||
|
||||
-- clear biomes, decorations, and ores
|
||||
minetest.clear_registered_decorations()
|
||||
minetest.clear_registered_ores()
|
||||
minetest.clear_registered_biomes()
|
||||
|
||||
-- Restore biomes, adjusted to not overlap the Nether
|
||||
for biome_key, new_biome_def in pairs(registered_biomes_copy) do
|
||||
local biome_y_max, biome_y_min = tonumber(new_biome_def.y_max), tonumber(new_biome_def.y_min)
|
||||
|
||||
if biome_y_max > NETHER_FLOOR and biome_y_min < NETHER_CEILING then
|
||||
-- This biome occupies some or all of the depth of the Nether, shift/crop it.
|
||||
local spaceOccupiedAbove = biome_y_max - NETHER_CEILING
|
||||
local spaceOccupiedBelow = NETHER_FLOOR - biome_y_min
|
||||
if spaceOccupiedAbove >= spaceOccupiedBelow or biome_y_min <= -30000 then
|
||||
-- place the biome above the Nether
|
||||
-- We also shift biomes which extend to the bottom of the map above the Nether, since they
|
||||
-- likely only extend that deep as a catch-all, and probably have a role nearer the surface.
|
||||
new_biome_def.y_min = NETHER_CEILING + 1
|
||||
new_biome_def.y_max = math_max(biome_y_max, NETHER_CEILING + 2)
|
||||
else
|
||||
-- shift the biome to below the Nether
|
||||
new_biome_def.y_max = NETHER_FLOOR - 1
|
||||
new_biome_def.y_min = math_min(biome_y_min, NETHER_CEILING - 2)
|
||||
end
|
||||
end
|
||||
minetest.register_biome(new_biome_def)
|
||||
end
|
||||
|
||||
-- Restore biome decorations
|
||||
for decoration_key, new_decoration_def in pairs(registered_decorations_copy) do
|
||||
minetest.register_decoration(new_decoration_def)
|
||||
end
|
||||
-- Restore biome ores
|
||||
for ore_key, new_ore_def in pairs(registered_ores_copy) do
|
||||
minetest.register_ore(new_ore_def)
|
||||
end
|
||||
end
|
||||
|
||||
-- Shift any overlapping biomes out of the way before we create the Nether biomes
|
||||
override_underground_biomes()
|
||||
|
||||
-- nether:native_mapgen is used to prevent ores and decorations being generated according
|
||||
-- to landforms created by the native mapgen.
|
||||
-- Ores and decorations can be registered against "nether:rack" instead, and the lua
|
||||
-- on_generate() callback will carve the Nether with nether:rack before invoking
|
||||
-- generate_decorations and generate_ores.
|
||||
minetest.register_node("nether:native_mapgen", {})
|
||||
|
||||
minetest.register_biome({
|
||||
name = "nether_caverns",
|
||||
node_stone = "nether:native_mapgen", -- nether:native_mapgen is used here to prevent the native mapgen from placing ores and decorations.
|
||||
node_filler = "nether:native_mapgen", -- The lua on_generate will transform nether:rack_native into nether:rack then decorate and add ores.
|
||||
node_dungeon = "nether:brick",
|
||||
--node_dungeon_alt = "default:mossycobble",
|
||||
node_dungeon_stair = "stairs:stair_nether_brick",
|
||||
-- Setting node_cave_liquid to "air" avoids the need to filter lava and water out of the mapchunk and
|
||||
-- surrounding shell (overdraw nodes beyond the mapchunk).
|
||||
-- This feature was introduced by paramat in b1b40fef1 on 2019-05-19, and this mapgen.lua file should only
|
||||
-- be run if the Minetest version includes it. The earliest tag made after 2019-05-19 is 5.1.0 on 2019-10-13,
|
||||
-- however we shouldn't test version numbers. minetest.read_schematic() was added by b2065756c and merged in
|
||||
-- 2019-08-14 and is easy to test for, we don't use it but it should make a good proxy-test for whether the
|
||||
-- Minetest version is recent enough to have implemented node_cave_liquid=air
|
||||
node_cave_liquid = "air",
|
||||
y_max = NETHER_CEILING,
|
||||
y_min = NETHER_FLOOR,
|
||||
vertical_blend = 0,
|
||||
heat_point = 50,
|
||||
humidity_point = 50,
|
||||
})
|
||||
|
||||
|
||||
-- Ores and decorations
|
||||
|
||||
dofile(nether.path .. "/mapgen_decorations.lua")
|
||||
|
||||
minetest.register_ore({
|
||||
ore_type = "scatter",
|
||||
ore = "nether:glowstone",
|
||||
wherein = "nether:rack",
|
||||
clust_scarcity = 11 * 11 * 11,
|
||||
clust_num_ores = 3,
|
||||
clust_size = 2,
|
||||
y_max = NETHER_CEILING,
|
||||
y_min = NETHER_FLOOR,
|
||||
})
|
||||
|
||||
minetest.register_ore({
|
||||
ore_type = "scatter",
|
||||
ore = "default:lava_source",
|
||||
wherein = "nether:rack",
|
||||
clust_scarcity = 36 * 36 * 36,
|
||||
clust_num_ores = 4,
|
||||
clust_size = 2,
|
||||
y_max = NETHER_CEILING,
|
||||
y_min = NETHER_FLOOR,
|
||||
})
|
||||
|
||||
minetest.register_ore({
|
||||
ore_type = "blob",
|
||||
ore = "nether:sand",
|
||||
wherein = "nether:rack",
|
||||
clust_scarcity = 14 * 14 * 14,
|
||||
clust_size = 8,
|
||||
y_max = NETHER_CEILING,
|
||||
y_min = NETHER_FLOOR
|
||||
})
|
||||
|
||||
|
||||
-- Mapgen
|
||||
|
||||
-- 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,
|
||||
lacunarity = 2.0,
|
||||
--flags = ""
|
||||
}
|
||||
|
||||
-- Buffers and objects we shouldn't recreate every on_generate
|
||||
|
||||
local nobj_cave = nil
|
||||
local nbuf_cave = {}
|
||||
local dbuf = {}
|
||||
|
||||
local yblmin = NETHER_FLOOR + BLEND * 2
|
||||
local yblmax = NETHER_CEILING - BLEND * 2
|
||||
|
||||
-- Content ids
|
||||
|
||||
local c_air = minetest.get_content_id("air")
|
||||
local c_netherrack = minetest.get_content_id("nether:rack")
|
||||
local c_netherbrick = minetest.get_content_id("nether:brick")
|
||||
local c_netherbrick_slab = minetest.get_content_id("stairs:slab_nether_brick")
|
||||
local c_netherfence = minetest.get_content_id("nether:fence_nether_brick")
|
||||
local c_glowstone = minetest.get_content_id("nether:glowstone")
|
||||
local c_lava_source = minetest.get_content_id("default:lava_source")
|
||||
local c_native_mapgen = minetest.get_content_id("nether:native_mapgen")
|
||||
|
||||
|
||||
-- Dungeon excavation functions
|
||||
|
||||
function build_dungeon_room_list(data, area)
|
||||
|
||||
local result = {}
|
||||
|
||||
-- Unfortunately gennotify only returns dungeon rooms, not corridors.
|
||||
-- We don't need to check for temples because only dungeons are generated in biomes
|
||||
-- that define their own dungeon nodes.
|
||||
local gennotify = minetest.get_mapgen_object("gennotify")
|
||||
local roomLocations = gennotify["dungeon"] or {}
|
||||
|
||||
-- Excavation should still know to stop if a cave or corridor has removed the dungeon wall.
|
||||
-- See MapgenBasic::generateDungeons in mapgen.cpp for max room sizes.
|
||||
local maxRoomSize = 18
|
||||
local maxRoomRadius = math.ceil(maxRoomSize / 2)
|
||||
|
||||
local xStride, yStride, zStride = 1, area.ystride, area.zstride
|
||||
local minEdge, maxEdge = area.MinEdge, area.MaxEdge
|
||||
|
||||
for _, roomPos in ipairs(roomLocations) do
|
||||
|
||||
if area:containsp(roomPos) then -- this safety check does not appear to be necessary, but lets make it explicit
|
||||
|
||||
local room_vi = area:indexp(roomPos)
|
||||
--data[room_vi] = minetest.get_content_id("default:torch") -- debug
|
||||
|
||||
local startPos = vector.new(roomPos)
|
||||
if roomPos.y + 1 <= maxEdge.y and data[room_vi + yStride] == c_air then
|
||||
-- The roomPos coords given by gennotify are at floor level, but whenever possible we
|
||||
-- want to be performing searches a node higher than floor level to avoids dungeon chests.
|
||||
startPos.y = startPos.y + 1
|
||||
room_vi = area:indexp(startPos)
|
||||
end
|
||||
|
||||
local bound_min_x = math_max(minEdge.x, roomPos.x - maxRoomRadius)
|
||||
local bound_min_y = math_max(minEdge.y, roomPos.y - 1) -- room coords given by gennotify are on the floor
|
||||
local bound_min_z = math_max(minEdge.z, roomPos.z - maxRoomRadius)
|
||||
|
||||
local bound_max_x = math_min(maxEdge.x, roomPos.x + maxRoomRadius)
|
||||
local bound_max_y = math_min(maxEdge.y, roomPos.y + maxRoomSize) -- room coords given by gennotify are on the floor
|
||||
local bound_max_z = math_min(maxEdge.z, roomPos.z + maxRoomRadius)
|
||||
|
||||
local room_min = vector.new(startPos)
|
||||
local room_max = vector.new(startPos)
|
||||
|
||||
local vi = room_vi
|
||||
while room_max.y < bound_max_y and data[vi + yStride] == c_air do
|
||||
room_max.y = room_max.y + 1
|
||||
vi = vi + yStride
|
||||
end
|
||||
|
||||
vi = room_vi
|
||||
while room_min.y > bound_min_y and data[vi - yStride] == c_air do
|
||||
room_min.y = room_min.y - 1
|
||||
vi = vi - yStride
|
||||
end
|
||||
|
||||
vi = room_vi
|
||||
while room_max.z < bound_max_z and data[vi + zStride] == c_air do
|
||||
room_max.z = room_max.z + 1
|
||||
vi = vi + zStride
|
||||
end
|
||||
|
||||
vi = room_vi
|
||||
while room_min.z > bound_min_z and data[vi - zStride] == c_air do
|
||||
room_min.z = room_min.z - 1
|
||||
vi = vi - zStride
|
||||
end
|
||||
|
||||
vi = room_vi
|
||||
while room_max.x < bound_max_x and data[vi + xStride] == c_air do
|
||||
room_max.x = room_max.x + 1
|
||||
vi = vi + xStride
|
||||
end
|
||||
|
||||
vi = room_vi
|
||||
while room_min.x > bound_min_x and data[vi - xStride] == c_air do
|
||||
room_min.x = room_min.x - 1
|
||||
vi = vi - xStride
|
||||
end
|
||||
|
||||
local roomInfo = vector.new(roomPos)
|
||||
roomInfo.minp = room_min
|
||||
roomInfo.maxp = room_max
|
||||
result[#result + 1] = roomInfo
|
||||
end
|
||||
end
|
||||
|
||||
return result;
|
||||
end
|
||||
|
||||
-- Only partially excavates dungeons, the rest is left as an exercise for the player ;)
|
||||
-- (Corridors and the parts of rooms which extend beyond the emerge boundary will remain filled)
|
||||
function excavate_dungeons(data, area, rooms)
|
||||
|
||||
-- any air from the native mapgen has been replaced by netherrack, but
|
||||
-- we don't want this inside dungeons, so fill dungeon rooms with air
|
||||
for _, roomInfo in ipairs(rooms) do
|
||||
|
||||
local room_min = roomInfo.minp
|
||||
local room_max = roomInfo.maxp
|
||||
|
||||
for z = room_min.z, room_max.z do
|
||||
for y = room_min.y, room_max.y do
|
||||
local vi = area:index(room_min.x, y, z)
|
||||
for x = room_min.x, room_max.x do
|
||||
if data[vi] == c_netherrack then data[vi] = c_air end
|
||||
vi = vi + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Since we already know where all the rooms and their walls are, and have all the nodes stored
|
||||
-- in a voxelmanip already, we may as well add a little Nether flair to the dungeons found here.
|
||||
function decorate_dungeons(data, area, rooms)
|
||||
|
||||
local xStride, yStride, zStride = 1, area.ystride, area.zstride
|
||||
local minEdge, maxEdge = area.MinEdge, area.MaxEdge
|
||||
|
||||
for _, roomInfo in ipairs(rooms) do
|
||||
|
||||
local room_min, room_max = roomInfo.minp, roomInfo.maxp
|
||||
local room_size = vector.distance(room_min, room_max)
|
||||
|
||||
if room_size > 10 then
|
||||
local room_seed = roomInfo.x + 3 * roomInfo.z + 13 * roomInfo.y
|
||||
local window_y = roomInfo.y + math_min(2, room_max.y - roomInfo.y - 1)
|
||||
|
||||
if room_seed % 3 == 0 and room_max.y < maxEdge.y then
|
||||
-- Glowstone chandelier
|
||||
local vi = area:index(roomInfo.x, room_max.y + 1, roomInfo.z)
|
||||
if data[vi] == c_netherbrick then data[vi] = c_glowstone end
|
||||
|
||||
elseif room_seed % 4 == 0 and room_min.y > minEdge.y
|
||||
and room_min.x > minEdge.x and room_max.x < maxEdge.x
|
||||
and room_min.z > minEdge.z and room_max.z < maxEdge.z then
|
||||
-- lava well (feel free to replace with a fancy schematic)
|
||||
local vi = area:index(roomInfo.x, room_min.y, roomInfo.z)
|
||||
if data[vi - yStride] == c_netherbrick then data[vi - yStride] = c_lava_source end
|
||||
if data[vi - zStride] == c_air then data[vi - zStride] = c_netherbrick_slab end
|
||||
if data[vi + zStride] == c_air then data[vi + zStride] = c_netherbrick_slab end
|
||||
if data[vi - xStride] == c_air then data[vi - xStride] = c_netherbrick_slab end
|
||||
if data[vi + xStride] == c_air then data[vi + xStride] = c_netherbrick_slab end
|
||||
end
|
||||
|
||||
-- Barred windows
|
||||
if room_seed % 7 < 5 and room_max.x - room_min.x >= 4 and room_max.z - room_min.z >= 4
|
||||
and window_y >= minEdge.y and window_y + 1 <= maxEdge.y
|
||||
and room_min.x > minEdge.x and room_max.x < maxEdge.x
|
||||
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
|
||||
|
||||
-- Until whisper glass is added, every window will be made of netherbrick fence (rather
|
||||
-- than material depending on room_seed)
|
||||
local window_node = c_netherfence
|
||||
|
||||
local vi_min = area:index(room_min.x - 1, window_y, roomInfo.z)
|
||||
local vi_max = area:index(room_max.x + 1, window_y, roomInfo.z)
|
||||
local locations = {-zStride, zStride, -zStride + yStride, zStride + yStride}
|
||||
for _, offset in ipairs(locations) do
|
||||
if data[vi_min + offset] == c_netherbrick then data[vi_min + offset] = window_node end
|
||||
if data[vi_max + offset] == c_netherbrick then data[vi_max + offset] = window_node end
|
||||
end
|
||||
vi_min = area:index(roomInfo.x, window_y, room_min.z - 1)
|
||||
vi_max = area:index(roomInfo.x, window_y, room_max.z + 1)
|
||||
locations = {-xStride, xStride, -xStride + yStride, xStride + yStride}
|
||||
for _, offset in ipairs(locations) do
|
||||
if data[vi_min + offset] == c_netherbrick then data[vi_min + offset] = window_node end
|
||||
if data[vi_max + offset] == c_netherbrick then data[vi_max + offset] = window_node end
|
||||
end
|
||||
end
|
||||
|
||||
-- Weeds on the floor once Nether weeds are added
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-- On-generated function
|
||||
|
||||
local function on_generated(minp, maxp, seed)
|
||||
|
||||
if minp.y > NETHER_CEILING or maxp.y < NETHER_FLOOR then
|
||||
return
|
||||
end
|
||||
|
||||
local vm, emerge_min, emerge_max = minetest.get_mapgen_object("voxelmanip")
|
||||
local area = VoxelArea:new{MinEdge=emerge_min, MaxEdge=emerge_max}
|
||||
local data = vm:get_data(dbuf)
|
||||
|
||||
local x0, y0, z0 = minp.x, math_max(minp.y, NETHER_FLOOR), minp.z
|
||||
local x1, y1, z1 = maxp.x, math_min(maxp.y, NETHER_CEILING), maxp.z
|
||||
|
||||
local yCaveStride = x1 - x0 + 1
|
||||
local zCaveStride = yCaveStride * yCaveStride
|
||||
local chulens = {x = yCaveStride, y = yCaveStride, z = yCaveStride}
|
||||
|
||||
nobj_cave = nobj_cave or minetest.get_perlin_map(np_cave, chulens)
|
||||
local nvals_cave = nobj_cave:get_3d_map_flat(minp, nbuf_cave)
|
||||
|
||||
|
||||
local dungeonRooms = build_dungeon_room_list(data, area)
|
||||
|
||||
for y = y0, y1 do -- Y loop first to minimise tcave calculations
|
||||
|
||||
local tcave = TCAVE
|
||||
if y > yblmax then tcave = TCAVE + ((y - yblmax) / BLEND) ^ 2 end
|
||||
if y < yblmin then tcave = TCAVE + ((yblmin - y) / BLEND) ^ 2 end
|
||||
|
||||
for z = z0, z1 do
|
||||
local vi = area:index(x0, y, z) -- Initial voxelmanip index
|
||||
local ni = (z - z0) * zCaveStride + (y - y0) * yCaveStride + 1
|
||||
|
||||
for x = x0, x1 do
|
||||
|
||||
local id = data[vi] -- Existing node
|
||||
|
||||
if nvals_cave[ni] > tcave then
|
||||
data[vi] = c_air
|
||||
elseif id == c_air or id == c_native_mapgen then
|
||||
data[vi] = c_netherrack -- excavate_dungeons() will mostly reverse this inside dungeons
|
||||
end
|
||||
|
||||
ni = ni + 1
|
||||
vi = vi + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- any air from the native mapgen has been replaced by netherrack, but we
|
||||
-- don't want netherrack inside dungeons, so fill known dungeon rooms with air.
|
||||
excavate_dungeons(data, area, dungeonRooms)
|
||||
decorate_dungeons(data, area, dungeonRooms)
|
||||
|
||||
vm:set_data(data)
|
||||
|
||||
-- avoid generating decorations on the underside of the bottom of the nether
|
||||
if minp.y > NETHER_FLOOR and maxp.y < NETHER_CEILING then minetest.generate_decorations(vm) end
|
||||
|
||||
minetest.generate_ores(vm)
|
||||
vm:set_lighting({day = 0, night = 0}, minp, maxp)
|
||||
vm:calc_lighting()
|
||||
vm:update_liquids()
|
||||
vm:write_to_map()
|
||||
end
|
||||
|
||||
|
||||
-- use knowledge of the nether mapgen algorithm to return a suitable ground level for placing a portal.
|
||||
-- player_name is optional, allowing a player to spawn a remote portal in their own protected areas.
|
||||
function nether.find_nether_ground_y(target_x, target_z, start_y, player_name)
|
||||
local nobj_cave_point = minetest.get_perlin(np_cave)
|
||||
local air = 0 -- Consecutive air nodes found
|
||||
|
||||
local minp_schem, maxp_schem = nether.get_schematic_volume({x = target_x, y = 0, z = target_z}, nil, "nether_portal")
|
||||
local minp = {x = minp_schem.x, y = 0, z = minp_schem.z}
|
||||
local maxp = {x = maxp_schem.x, y = 0, z = maxp_schem.z}
|
||||
|
||||
for y = start_y, math_max(NETHER_FLOOR + BLEND, 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
|
||||
local portal_y = y + 1
|
||||
-- Check volume for non-natural nodes
|
||||
minp.y = minp_schem.y + portal_y
|
||||
maxp.y = maxp_schem.y + portal_y
|
||||
if nether.volume_is_natural_and_unprotected(minp, maxp, player_name) then
|
||||
return portal_y
|
||||
else -- Restart search a little lower
|
||||
nether.find_nether_ground_y(target_x, target_z, y - 16, player_name)
|
||||
end
|
||||
else -- Not enough space, reset air to zero
|
||||
air = 0
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return math_max(start_y, NETHER_FLOOR + BLEND) -- Fallback
|
||||
end
|
||||
|
||||
-- We don't need to be gen-notified of temples because only dungeons will be generated
|
||||
-- if a biome defines the dungeon nodes
|
||||
minetest.set_gen_notify({dungeon = true})
|
||||
|
||||
minetest.register_on_generated(on_generated)
|
138
mapgen_decorations.lua
Normal file
@ -0,0 +1,138 @@
|
||||
--[[
|
||||
|
||||
Register decorations for Nether mapgen
|
||||
|
||||
Copyright (C) 2020 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.
|
||||
|
||||
]]--
|
||||
|
||||
-- Lava is unreliable in the old Nether mapgen because it removes lava
|
||||
-- from the overdraw areas, so any decorations involving lava will often
|
||||
-- have the lava missing depending on whether nearby chunks were already
|
||||
-- emerged or not before the decoration was placed.
|
||||
local allow_lava_decorations = nether.useBiomes
|
||||
|
||||
local _ = {name = "air", prob = 0}
|
||||
local A = {name = "air", prob = 255, force_place = true}
|
||||
local G = {name = "nether:glowstone", prob = 255, force_place = true}
|
||||
local N = {name = "nether:rack", prob = 255}
|
||||
local S = {name = "nether:sand", prob = 255, force_place = true}
|
||||
local L = {name = "default:lava_source", prob = 255, force_place = true}
|
||||
|
||||
|
||||
-- =================
|
||||
-- Stalactites
|
||||
-- =================
|
||||
|
||||
local schematic_GlowstoneStalactite = {
|
||||
size = {x = 5, y = 10, z = 5},
|
||||
data = {
|
||||
_, _, _, _, _,
|
||||
_, _, _, _, _,
|
||||
_, _, _, _, _,
|
||||
_, _, _, _, _,
|
||||
_, _, _, _, _,
|
||||
_, _, _, _, _,
|
||||
_, _, _, _, _,
|
||||
_, _, _, _, _,
|
||||
_, N, G, N, _,
|
||||
_, N, N, N, _,
|
||||
|
||||
_, _, _, _, _,
|
||||
_, _, _, _, _,
|
||||
_, _, _, _, _,
|
||||
_, _, _, _, _,
|
||||
_, _, _, _, _,
|
||||
_, _, G, _, _,
|
||||
_, _, G, _, _,
|
||||
_, G, G, G, _,
|
||||
N, G, G, G, N,
|
||||
N, N, G, N, N,
|
||||
|
||||
_, _, N, _, _, -- ypos 0, prob 25% (64/256)
|
||||
_, _, G, _, _, -- ypos 1, prob 37% (96/256)
|
||||
_, _, G, _, _, -- ypos 2, prob 100%
|
||||
_, _, G, _, _, -- ypos 3, prob 100%
|
||||
_, _, G, G, _, -- ypos 4, prob 50% (128/256) to make half of stalactites asymmetric
|
||||
_, G, G, G, _, -- ypos 5, prob 75% (192/256)
|
||||
_, G, G, G, _, -- ypos 6, prob 75% (192/256)
|
||||
_, G, G, G, _, -- ypos 7, prob 100%
|
||||
G, G, G, G, G, -- ypos 8, prob 100%
|
||||
N, G, G, G, N, -- ypos 9, prob 75% (192/256)
|
||||
|
||||
_, _, _, _, _,
|
||||
_, _, _, _, _,
|
||||
_, _, _, _, _,
|
||||
_, _, _, _, _,
|
||||
_, _, G, _, _,
|
||||
_, _, G, _, _,
|
||||
_, _, G, _, _,
|
||||
_, G, G, G, _,
|
||||
N, G, G, G, N,
|
||||
N, N, G, N, N,
|
||||
|
||||
_, _, _, _, _,
|
||||
_, _, _, _, _,
|
||||
_, _, _, _, _,
|
||||
_, _, _, _, _,
|
||||
_, _, _, _, _,
|
||||
_, _, _, _, _,
|
||||
_, _, _, _, _,
|
||||
_, _, _, _, _,
|
||||
_, N, G, N, _,
|
||||
_, N, N, N, _
|
||||
},
|
||||
-- Y-slice probabilities do not function correctly for ceiling schematic
|
||||
-- decorations because they are inverted, so ypos numbers have been inverted
|
||||
-- to match, and a larger offset in place_offset_y should be used (e.g. -3).
|
||||
yslice_prob = {
|
||||
{ypos = 9, prob = 192},
|
||||
{ypos = 6, prob = 192},
|
||||
{ypos = 5, prob = 192},
|
||||
{ypos = 4, prob = 128},
|
||||
{ypos = 1, prob = 96},
|
||||
{ypos = 0, prob = 64}
|
||||
}
|
||||
}
|
||||
|
||||
minetest.register_decoration({
|
||||
name = "Glowstone stalactite",
|
||||
deco_type = "schematic",
|
||||
place_on = "nether:rack",
|
||||
sidelen = 80,
|
||||
fill_ratio = 0.0003,
|
||||
biomes = {"nether_caverns"},
|
||||
y_max = nether.DEPTH_CEILING, -- keep compatibility with mapgen_nobiomes.lua
|
||||
y_min = nether.DEPTH_FLOOR,
|
||||
schematic = schematic_GlowstoneStalactite,
|
||||
flags = "place_center_x,place_center_z,force_placement,all_ceilings",
|
||||
place_offset_y=-3
|
||||
})
|
||||
|
||||
minetest.register_decoration({
|
||||
name = "Netherrack stalactite",
|
||||
deco_type = "schematic",
|
||||
place_on = "nether:rack",
|
||||
sidelen = 80,
|
||||
fill_ratio = 0.0008,
|
||||
biomes = {"nether_caverns"},
|
||||
y_max = nether.DEPTH_CEILING, -- keep compatibility with mapgen_nobiomes.lua
|
||||
y_min = nether.DEPTH_FLOOR,
|
||||
schematic = schematic_GlowstoneStalactite,
|
||||
replacements = {["nether:glowstone"] = "nether:rack"},
|
||||
flags = "place_center_x,place_center_z,all_ceilings",
|
||||
place_offset_y=-3
|
||||
})
|
236
mapgen_nobiomes.lua
Normal file
@ -0,0 +1,236 @@
|
||||
--[[
|
||||
|
||||
Nether mod for minetest
|
||||
|
||||
Copyright (C) 2013 PilzAdam
|
||||
|
||||
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.
|
||||
|
||||
]]--
|
||||
|
||||
|
||||
-- Parameters
|
||||
|
||||
local NETHER_CEILING = nether.DEPTH_CEILING
|
||||
local NETHER_FLOOR = nether.DEPTH_FLOOR
|
||||
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,
|
||||
lacunarity = 2.0,
|
||||
--flags = ""
|
||||
}
|
||||
|
||||
|
||||
-- Stuff
|
||||
|
||||
local yblmin = NETHER_FLOOR + BLEND * 2
|
||||
local yblmax = NETHER_CEILING - BLEND * 2
|
||||
|
||||
|
||||
|
||||
-- Mapgen
|
||||
|
||||
dofile(nether.path .. "/mapgen_decorations.lua")
|
||||
|
||||
-- Initialize noise object, localise noise and data buffers
|
||||
|
||||
local nobj_cave = nil
|
||||
local nbuf_cave = {}
|
||||
local dbuf = {}
|
||||
|
||||
|
||||
-- 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_CEILING or maxp.y < NETHER_FLOOR then
|
||||
return
|
||||
end
|
||||
|
||||
local x1 = maxp.x
|
||||
local y1 = math.min(maxp.y, NETHER_CEILING)
|
||||
local z1 = maxp.z
|
||||
local x0 = minp.x
|
||||
local y0 = math.max(minp.y, NETHER_FLOOR)
|
||||
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(dbuf)
|
||||
|
||||
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:get_3d_map_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
|
||||
tcave = TCAVE
|
||||
if y > yblmax then tcave = TCAVE + ((y - yblmax) / BLEND) ^ 2 end
|
||||
if y < yblmin then tcave = TCAVE + ((yblmin - y) / BLEND) ^ 2 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)
|
||||
|
||||
-- avoid generating decorations on the underside of the bottom of the nether
|
||||
if minp.y > NETHER_FLOOR and maxp.y < NETHER_CEILING then minetest.generate_decorations(vm) end
|
||||
|
||||
vm:set_lighting({day = 0, night = 0}, minp, maxp)
|
||||
vm:calc_lighting()
|
||||
vm:update_liquids()
|
||||
vm:write_to_map()
|
||||
end)
|
||||
|
||||
|
||||
-- use knowledge of the nether mapgen algorithm to return a suitable ground level for placing a portal.
|
||||
-- player_name is optional, allowing a player to spawn a remote portal in their own protected areas.
|
||||
function nether.find_nether_ground_y(target_x, target_z, start_y, player_name)
|
||||
local nobj_cave_point = minetest.get_perlin(np_cave)
|
||||
local air = 0 -- Consecutive air nodes found
|
||||
|
||||
local minp_schem, maxp_schem = nether.get_schematic_volume({x = target_x, y = 0, z = target_z}, nil, "nether_portal")
|
||||
local minp = {x = minp_schem.x, y = 0, z = minp_schem.z}
|
||||
local maxp = {x = maxp_schem.x, y = 0, z = maxp_schem.z}
|
||||
|
||||
for y = start_y, math.max(NETHER_FLOOR + BLEND, 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
|
||||
local portal_y = y + 1
|
||||
-- Check volume for non-natural nodes
|
||||
minp.y = minp_schem.y + portal_y
|
||||
maxp.y = maxp_schem.y + portal_y
|
||||
if nether.volume_is_natural_and_unprotected(minp, maxp, player_name) then
|
||||
return portal_y
|
||||
else -- Restart search a little lower
|
||||
nether.find_nether_ground_y(target_x, target_z, y - 16, player_name)
|
||||
end
|
||||
else -- Not enough space, reset air to zero
|
||||
air = 0
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return math.max(start_y, NETHER_FLOOR + BLEND) -- Fallback
|
||||
end
|
170
nodes.lua
Normal file
@ -0,0 +1,170 @@
|
||||
--[[
|
||||
|
||||
Nether mod for minetest
|
||||
|
||||
Copyright (C) 2013 PilzAdam
|
||||
|
||||
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 S = nether.get_translator
|
||||
|
||||
-- Portal/wormhole nodes
|
||||
|
||||
nether.register_wormhole_node("nether:portal", {
|
||||
description = S("Nether Portal"),
|
||||
post_effect_color = {
|
||||
-- post_effect_color can't be changed dynamically in Minetest like the portal colour is.
|
||||
-- If you need a different post_effect_color then use register_wormhole_node to create
|
||||
-- another wormhole node and set it as the wormhole_node_name in your portaldef.
|
||||
-- Hopefully this colour is close enough to magenta to work with the traditional magenta
|
||||
-- portals, close enough to red to work for a red portal, and also close enough to red to
|
||||
-- work with blue & cyan portals - since blue portals are sometimes portrayed as being red
|
||||
-- from the opposite side / from the inside.
|
||||
a = 160, r = 128, g = 0, b = 80
|
||||
}
|
||||
})
|
||||
|
||||
local portal_animation2 = {
|
||||
name = "nether_portal_alt.png",
|
||||
animation = {
|
||||
type = "vertical_frames",
|
||||
aspect_w = 16,
|
||||
aspect_h = 16,
|
||||
length = 0.5,
|
||||
},
|
||||
}
|
||||
|
||||
nether.register_wormhole_node("nether:portal_alt", {
|
||||
description = S("Portal"),
|
||||
tiles = {
|
||||
"nether_transparent.png",
|
||||
"nether_transparent.png",
|
||||
"nether_transparent.png",
|
||||
"nether_transparent.png",
|
||||
portal_animation2,
|
||||
portal_animation2
|
||||
},
|
||||
post_effect_color = {
|
||||
-- hopefully blue enough to work with blue portals, and green enough to
|
||||
-- work with cyan portals.
|
||||
a = 120, r = 0, g = 128, b = 188
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
-- Nether nodes
|
||||
|
||||
minetest.register_node("nether:rack", {
|
||||
description = S("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 = S("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 = S("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 = S("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 = S("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"},
|
||||
S("Nether Stair"),
|
||||
S("Nether Slab"),
|
||||
default.node_sound_stone_defaults()
|
||||
)
|
||||
|
||||
-- StairsPlus
|
||||
|
||||
if minetest.get_modpath("moreblocks") then
|
||||
stairsplus:register_all(
|
||||
"nether", "brick", "nether:brick", {
|
||||
description = S("Nether Brick"),
|
||||
groups = {cracky = 2, level = 2},
|
||||
tiles = {"nether_brick.png"},
|
||||
sounds = default.node_sound_stone_defaults(),
|
||||
})
|
||||
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"},
|
||||
},
|
||||
})
|
||||
|
||||
|
2391
portal_api.lua
Normal file
262
portal_api.txt
Normal file
@ -0,0 +1,262 @@
|
||||
Portal API Reference
|
||||
====================
|
||||
|
||||
The portal system used to get to the Nether can be used to create portals
|
||||
to other realms.
|
||||
|
||||
Pick a node type to have your portals built from, a shape in which the
|
||||
portals must be built, and provide 3 functions for portals to find their
|
||||
destination with:
|
||||
* `find_realm_anchorPos(surface_anchorPos)`
|
||||
* `find_surface_anchorPos(realm_anchorPos)`
|
||||
* `is_within_realm(pos)`
|
||||
|
||||
Optionally decorate by choosing portal colors, particles, media etc.
|
||||
|
||||
See `init.lua` and `portal_examples.lua` for examples of 3 different portals.
|
||||
|
||||
Portal code is more efficient when each type of portal uses a different type
|
||||
of node to build its frame out of - consider creating your own node for
|
||||
players to build portals from. However it is possible to register more than
|
||||
one kind of portal with the same frame material — such as obsidian — provided
|
||||
the size of the PortalShape is distinct from any other type of portal that is
|
||||
using the same node for its frame, and portal sizes remain small.
|
||||
|
||||
|
||||
Realms
|
||||
------
|
||||
|
||||
This API uses the concept of a realm for each type of portal. If a portal is
|
||||
outside its realm then it links to a portal inside the realm, if a portal is
|
||||
inside its realm then it links to the outside.
|
||||
|
||||
You get to decide what constitutes your realm by implementing the function
|
||||
`is_within_realm(position)`.
|
||||
|
||||
For example, the Nether realm is defined as existing at a certain depth and
|
||||
anything above that depth is considered outside the Realm.
|
||||
|
||||
In contrast, the "Surface portal" - an example in portal_examples.lua, only
|
||||
uses one realm. Its `is_within_realm()` function always returns true so that
|
||||
any time a portal is opened it will use `find_surface_anchorPos()`.
|
||||
|
||||
Note that the name "find_surface_anchorPos" is a Nether-centric misnomer, as
|
||||
different types of portals are free to use different definitions of a realm
|
||||
such that leaving the realm might not be synonymous with travelling to the
|
||||
surface.
|
||||
|
||||
|
||||
Helper functions
|
||||
----------------
|
||||
|
||||
* `nether.volume_is_natural_and_unprotected(minp, maxp, player_name)`: returns
|
||||
a boolean.
|
||||
* use this when determining where to spawn a portal, to avoid overwriting
|
||||
player builds. It checks the area for any nodes that aren't ground or
|
||||
trees.
|
||||
Water will fail this test, unless it is unemerged.
|
||||
* player_name is optional, providing it allows the player's own protected
|
||||
areas to be treated as unprotected.
|
||||
|
||||
* `nether.find_surface_target_y(target_x, target_z, portal_name, player_name)`:
|
||||
returns a suitable anchorPos
|
||||
* Can be used when implementing custom find_surface_anchorPos() functions
|
||||
* portal_name is optional, providing it allows existing portals on the
|
||||
surface to be reused.
|
||||
* player_name is optional, providing it prevents the exclusion of surface
|
||||
target areas which are protected by the player.
|
||||
* May return nil in extreme circumstances, such as the surface being
|
||||
protected down to a great depth.
|
||||
|
||||
* `nether.find_nearest_working_portal(portal_name, anchorPos, distance_limit, y_factor)`: returns
|
||||
(anchorPos, orientation), or nil if no portal was found within the
|
||||
distance_limit.
|
||||
* A y_factor of 0 means y does not affect the distance_limit, a y_factor
|
||||
of 1 means y is included (the default if y_factor is nil), and a y_factor
|
||||
of 2 would squash the search-sphere by a factor of 2 on the y-axis, etc.
|
||||
* Only portals in the same realm as the anchorPos will be returned, even if
|
||||
y_factor is 0.
|
||||
* Pass a nil (or negative) distance_limit to indicate no distance limit
|
||||
|
||||
|
||||
API functions
|
||||
-------------
|
||||
|
||||
Call these functions only at load time:
|
||||
|
||||
* `nether.register_portal(name, portal_definition)`
|
||||
* Returns true on success. Can return false if the portal definition
|
||||
clashes with a portal already registered by another mod, e.g. if the size
|
||||
and frame node is not unique.
|
||||
A false return value should be handled, you could:
|
||||
* Fall back to using a secondary material for portals to be built with.
|
||||
* Use error() to exit lua with a message explaining how two mods are
|
||||
clashing and how it can be resolved.
|
||||
* Continue without a portal (the reason will be logged for the user).
|
||||
* `nether.register_portal_ignition_item(name, ignition_failure_sound)`
|
||||
* ignition_failure_sound is optional, it plays any time an attempt to use
|
||||
the item occurs if a portal is not ignited.
|
||||
* `nether.register_wormhole_node(name, nodedef_overrides)`
|
||||
* Can be used to register wormhole nodes with a different post_effect_color
|
||||
from the "nether:portal" node. "Post effect color" is the tint the world
|
||||
takes on when you are standing inside a portal. `post_effect_color` is the
|
||||
only key/value that is needed in the nodedef_overrides table to achieve that,
|
||||
but the function allows any nodedef key/value to be specified/overridden.
|
||||
* After `register_wormhole_node()`, invoke `register_portal()` and include
|
||||
`wormhole_node_name` in the portal_definition, assigning it the name of the
|
||||
new wormhole node.
|
||||
* `nether.unregister_portal(name)`
|
||||
* Unregisters the portal from the engine, and deletes the entry with key
|
||||
`name` from `nether.registered_portals` and associated internal tables.
|
||||
* Returns true on success
|
||||
* You will probably never need to call this, it exists only for completeness.
|
||||
|
||||
|
||||
Portal definition
|
||||
-----------------
|
||||
|
||||
Used by `nether.register_portal`.
|
||||
|
||||
{
|
||||
frame_node_name = "default:obsidian",
|
||||
-- Required. For best results, have your portal constructed of a
|
||||
-- material nobody else is using.
|
||||
|
||||
frame_node_color = 0,
|
||||
-- Optional.
|
||||
-- A value from 0 to 7. Only used if the frame node's paramtype2 is
|
||||
-- "colorfacedir", in which case this color will be used when a remote
|
||||
-- portal is created.
|
||||
|
||||
shape = nether.PortalShape_Traditional,
|
||||
-- Optional.
|
||||
-- Shapes available are:
|
||||
-- nether.PortalShape_Traditional (default)
|
||||
-- nether.PortalShape_Circular
|
||||
-- nether.PortalShape_Platform
|
||||
-- New shapes can be created, but are beyond the scope of this guide.
|
||||
|
||||
wormhole_node_name = "nether:portal",
|
||||
-- Optional. Allows a custom wormhole node to be specified.
|
||||
-- Useful if you want the portals to have a different post_effect_color
|
||||
-- or texture.
|
||||
-- The Nether mod provides:
|
||||
-- "nether:portal" (default)
|
||||
-- "nether:portal_alt"
|
||||
|
||||
wormhole_node_color = 0,
|
||||
-- Optional. Defaults to 0/magenta.
|
||||
-- A value from 0 to 7 corresponding to the color of pixels in
|
||||
-- nether_portals_palette.png:
|
||||
-- 0 traditional/magenta
|
||||
-- 1 black
|
||||
-- 2 blue
|
||||
-- 3 green
|
||||
-- 4 cyan
|
||||
-- 5 red
|
||||
-- 6 yellow
|
||||
-- 7 white
|
||||
|
||||
particle_color = "#808",
|
||||
-- Optional. Will default to a colour matching the wormhole_node_color
|
||||
-- if not specified.
|
||||
|
||||
particle_texture = "image.png",
|
||||
-- Optional. Hardware colouring (i.e. particle_color) is applied to
|
||||
-- this texture, use particle_texture_colored instead if you want to
|
||||
-- use the colors of the image.
|
||||
-- Animation and particle scale may also be specified, e.g:
|
||||
-- particle_texture = {
|
||||
-- name = "nether_particle_anim1.png",
|
||||
-- animation = {
|
||||
-- type = "vertical_frames",
|
||||
-- aspect_w = 7,
|
||||
-- aspect_h = 7,
|
||||
-- length = 1,
|
||||
-- },
|
||||
-- scale = 1.5
|
||||
-- },
|
||||
-- See lua_api.txt for Tile Animation definition
|
||||
-- Some animated and non-animated textures are provided by this mod:
|
||||
-- nether_particle.png (original)
|
||||
-- nether_particle_anim1.png (stars)
|
||||
-- nether_particle_anim2.png (bubbles)
|
||||
-- nether_particle_anim3.png (sparks)
|
||||
-- nether_particle_anim4.png (particles)
|
||||
|
||||
title = "Gateway to Moria",
|
||||
-- Optional. Provides a title for the portal.
|
||||
-- Used in the Book of Portals or Help modpack.
|
||||
|
||||
book_of_portals_pagetext = "Everything I need the player to know",
|
||||
-- Optional. Provides the text for the portal in the Book of Portals
|
||||
-- and Help modpack.
|
||||
-- The Book of Portals is a book that can be found in chests, and
|
||||
-- provides players with instructions on how to build and use the
|
||||
-- portal, so be sure to mention the node type the frame must be built
|
||||
-- from.
|
||||
-- This can also provide flavortext or details about where the portal
|
||||
-- will take the player.
|
||||
|
||||
sounds = {
|
||||
ambient = <SimpleSoundSpec>,
|
||||
-- if the ambient SimpleSoundSpec is a table it can also contain a
|
||||
-- "length" int, which is the number of seconds to wait before
|
||||
-- repeating the ambient sound. Default is 3.
|
||||
ignite = <SimpleSoundSpec>,
|
||||
extinguish = <SimpleSoundSpec>,
|
||||
teleport = <SimpleSoundSpec>,
|
||||
}
|
||||
-- sounds is optional
|
||||
|
||||
within_realm = function(pos),
|
||||
-- Required. Return true if a portal at pos is in the realm, rather
|
||||
-- than the surface world.
|
||||
-- Ideally implementations are fast, as this function can be used to
|
||||
-- sift through a list of portals.
|
||||
|
||||
find_realm_anchorPos = function(surface_anchorPos, player_name),
|
||||
-- Required. Return a position in the realm that a portal created at
|
||||
-- surface_anchorPos will link to.
|
||||
-- Return an anchorPos or (anchorPos, orientation)
|
||||
-- If orientation is not specified then the orientation of the surface
|
||||
-- portal will be used.
|
||||
-- If the location of an existing portal is returned then include the
|
||||
-- orientation, otherwise the existing portal could be overwritten by
|
||||
-- a new one with the orientation of the surface portal.
|
||||
-- Return nil, or a position with a nil y component, to prevent the
|
||||
-- portal from igniting.
|
||||
-- player_name may be "", e.g. if the portal was ignited by a mesecon,
|
||||
-- and is provided for use with volume_is_natural_and_unprotected() etc.
|
||||
|
||||
find_surface_anchorPos = function(realm_anchorPos, player_name),
|
||||
-- Optional. If you don't implement this then a position near the
|
||||
-- surface will be picked.
|
||||
-- Return an anchorPos or (anchorPos, orientation)
|
||||
-- The name of this function is a Nether-centric misnomer. It should
|
||||
-- return a position outside the realm, and different types of portals
|
||||
-- are free to use different definitions of a realm such that leaving
|
||||
-- the realm might not be synonymous with travelling to the surface.
|
||||
-- If orientation is not specified then the orientation of the realm
|
||||
-- portal will be used.
|
||||
-- If the location of an existing portal is returned then include the
|
||||
-- orientation, otherwise the existing portal could be overwritten by
|
||||
-- a new one with the orientation of the realm portal.
|
||||
-- Return nil, or a position with a nil y component, to prevent the
|
||||
-- portal from igniting.
|
||||
-- player_name may be "", e.g. if the portal was ignited by a mesecon,
|
||||
-- and is provided for use with volume_is_natural_and_unprotected() etc.
|
||||
|
||||
on_run_wormhole = function(portalDef, anochorPos, orientation),
|
||||
-- invoked once per second per portal
|
||||
on_extinguish = function(portalDef, anochorPos, orientation),
|
||||
-- invoked when a portal is extinguished, including when the portal
|
||||
-- it connected to was extinguished.
|
||||
on_player_teleported = function(portalDef, player, oldPos, newPos),
|
||||
-- invoked immediately after a player is teleported
|
||||
on_ignite = function(portalDef, anochorPos, orientation)
|
||||
-- invoked when a player or mesecon ignites a portal
|
||||
on_created = function(portalDef, anochorPos, orientation)
|
||||
-- invoked when a portal creates a remote twin, this is usually when
|
||||
-- a player travels through a portal for the first time.
|
||||
}
|
320
portal_examples.lua
Normal file
@ -0,0 +1,320 @@
|
||||
--[[
|
||||
|
||||
Nether mod portal examples for Minetest
|
||||
|
||||
These portal API examples work independently of the Nether realm
|
||||
and Nether portal. To try these examples, enable them in:
|
||||
Minetest -> Settings -> All settings -> Mods -> nether
|
||||
Once enabled, details on how to build them can be found in dungeon
|
||||
chests in the book of portals.
|
||||
|
||||
--
|
||||
|
||||
Copyright (C) 2020 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 S = nether.get_translator
|
||||
|
||||
|
||||
local ENABLE_PORTAL_EXAMPLE_FLOATLANDS = false
|
||||
local ENABLE_PORTAL_EXAMPLE_SURFACETRAVEL = false
|
||||
|
||||
-- Sets how far a Surface Portal will travel, measured in cells along the Moore curve,
|
||||
-- which are about 117 nodes square each. Larger numbers will generally mean further distance
|
||||
-- as-the-crow-flies, but this will not always be true due to the how the Moore curve
|
||||
-- frequently doubles back upon itself.
|
||||
-- This doubling-back prevents the surface portal from taking players easily accross the
|
||||
-- map - the curve is 262144 cells long!
|
||||
local SURFACE_TRAVEL_DISTANCE = 26
|
||||
|
||||
|
||||
--=================================================--
|
||||
-- Portal to the Floatlands, playable code example --
|
||||
--==================================================--
|
||||
local FLOATLANDS_ENABLED
|
||||
local FLOATLAND_LEVEL = 1280
|
||||
|
||||
if minetest.settings:get_bool("nether_enable_portal_example_floatlands", ENABLE_PORTAL_EXAMPLE_FLOATLANDS) or ENABLE_PORTAL_EXAMPLE_FLOATLANDS then
|
||||
|
||||
local floatlands_flavortext = ""
|
||||
if minetest.get_mapgen_setting("mg_name") == "v7" then
|
||||
local mgv7_spflags = minetest.get_mapgen_setting("mgv7_spflags")
|
||||
FLOATLANDS_ENABLED = mgv7_spflags ~= nil and mgv7_spflags:find("floatlands") ~= nil and mgv7_spflags:find("nofloatlands") == nil
|
||||
FLOATLAND_LEVEL = minetest.get_mapgen_setting("mgv7_floatland_level") or 1280
|
||||
|
||||
if FLOATLANDS_ENABLED then
|
||||
floatlands_flavortext = "\n\n " .. S("There is a floating land of hills and forests up there, over the edges of which is a perilous drop all the way back down to sea level. We have not found how far these pristine lands extend. I have half a mind to retire there one day.")
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
nether.register_portal("floatlands_portal", {
|
||||
shape = nether.PortalShape_Platform,
|
||||
frame_node_name = "default:ice",
|
||||
wormhole_node_color = 7, -- 7 is white
|
||||
particle_texture = {
|
||||
name = "nether_particle_anim1.png",
|
||||
animation = {
|
||||
type = "vertical_frames",
|
||||
aspect_w = 7,
|
||||
aspect_h = 7,
|
||||
length = 1,
|
||||
},
|
||||
scale = 1.5
|
||||
},
|
||||
title = S("Floatlands Portal"),
|
||||
book_of_portals_pagetext = S([[Requiring 21 blocks of ice, and constructed in the shape of a 3 × 3 platform with walls, or like a bowl. A finished platform is 2 blocks high, and 5 blocks wide at the widest in both directions.
|
||||
|
||||
This portal is different to the others, rather than acting akin to a doorway it appears to the eye more like a small pool of water which can be stepped into. Upon setting foot in the portal we found ourselves at a tremendous altitude.@1]],
|
||||
floatlands_flavortext),
|
||||
|
||||
is_within_realm = function(pos) -- return true if pos is inside the Nether
|
||||
return pos.y > FLOATLAND_LEVEL - 200
|
||||
end,
|
||||
|
||||
find_realm_anchorPos = function(surface_anchorPos, player_name)
|
||||
-- TODO: Once paramat finishes adjusting the floatlands, implement a surface algorithm that finds land
|
||||
local destination_pos = {x = surface_anchorPos.x ,y = FLOATLAND_LEVEL + 2, z = surface_anchorPos.z}
|
||||
|
||||
-- a y_factor of 0 makes the search ignore the altitude of the portals (as long as they are in the Floatlands)
|
||||
local existing_portal_location, existing_portal_orientation = nether.find_nearest_working_portal("floatlands_portal", destination_pos, 10, 0)
|
||||
if existing_portal_location ~= nil then
|
||||
return existing_portal_location, existing_portal_orientation
|
||||
else
|
||||
return destination_pos
|
||||
end
|
||||
end
|
||||
})
|
||||
|
||||
end
|
||||
|
||||
|
||||
--==============================================--
|
||||
-- Surface-travel portal, playable code example --
|
||||
--==============================================--
|
||||
|
||||
-- These Moore Curve functions requred by surface_portal's find_surface_anchorPos() will
|
||||
-- be assigned later in this file.
|
||||
local get_moore_distance -- will be function get_moore_distance(cell_count, x, y): integer
|
||||
local get_moore_coords -- will be function get_moore_coords(cell_count, distance): pos2d
|
||||
|
||||
if minetest.settings:get_bool("nether_enable_portal_example_surfacetravel", ENABLE_PORTAL_EXAMPLE_SURFACETRAVEL) or ENABLE_PORTAL_EXAMPLE_SURFACETRAVEL then
|
||||
|
||||
nether.register_portal("surface_portal", {
|
||||
shape = nether.PortalShape_Circular,
|
||||
frame_node_name = "default:tinblock",
|
||||
wormhole_node_name = "nether:portal_alt",
|
||||
wormhole_node_color = 4, -- 4 is cyan
|
||||
title = S("Surface Portal"),
|
||||
book_of_portals_pagetext = S([[Requiring 16 blocks of tin and constructed in a circular fashion, a finished frame is seven blocks wide, seven blocks high, and stands vertically like a doorway.
|
||||
|
||||
These travel a distance along the ground, and even when constructed deep underground will link back up to the surface. They appear to favor a strange direction, with the exit portal linking back only for as long as the portal stays open — attempting to reopen a portal from the exit doorway leads to a new destination along this favored direction. It has stymied our ability to study the behavior of these portals because without constructing dual portals and keeping both open it's hard to step through more than one and still be able to return home.
|
||||
|
||||
Due to such difficulties, we never learned what determines the direction and distance where the matching twin portal will appear, and I have lost my friend and protégé. In cavalier youth and with little more than a rucksack, Coudreau has decided to follow the chain as far as it goes, and has not been seen since. Coudreau believes it works in epicycles, but I am not convinced. Still, I cling to the hope that one day the portal will open and Coudreau will step out from whichever place leads to this one, perhaps with an epic tale to tell.]]),
|
||||
|
||||
is_within_realm = function(pos)
|
||||
-- Always return true, because these portals always just take you around the surface
|
||||
-- rather than taking you to a different realm
|
||||
return true
|
||||
end,
|
||||
|
||||
find_realm_anchorPos = function(surface_anchorPos, player_name)
|
||||
-- This function isn't needed, since this type of portal always goes to the surface
|
||||
minetest.log("error" , "find_realm_anchorPos called for surface portal")
|
||||
return {x=0, y=0, z=0}
|
||||
end,
|
||||
|
||||
find_surface_anchorPos = function(realm_anchorPos, player_name)
|
||||
-- A portal definition doesn't normally need to provide a find_surface_anchorPos() function,
|
||||
-- since find_surface_target_y() will be used by default, but these portals travel around the
|
||||
-- surface (following a Moore curve) so will be calculating a different x and z to realm_anchorPos.
|
||||
|
||||
local cellCount = 512
|
||||
local maxDistFromOrigin = 30000 -- the world edges are at X=30927, X=−30912, Z=30927 and Z=−30912
|
||||
|
||||
-- clip realm_anchorPos to maxDistFromOrigin, and move the origin so that all values are positive
|
||||
local x = math.min(maxDistFromOrigin, math.max(-maxDistFromOrigin, realm_anchorPos.x)) + maxDistFromOrigin
|
||||
local z = math.min(maxDistFromOrigin, math.max(-maxDistFromOrigin, realm_anchorPos.z)) + maxDistFromOrigin
|
||||
|
||||
local divisor = math.ceil(maxDistFromOrigin * 2 / cellCount)
|
||||
local distance = get_moore_distance(cellCount, math.floor(x / divisor + 0.5), math.floor(z / divisor + 0.5))
|
||||
local destination_distance = (distance + SURFACE_TRAVEL_DISTANCE) % (cellCount * cellCount)
|
||||
local moore_pos = get_moore_coords(cellCount, destination_distance)
|
||||
local target_x = moore_pos.x * divisor - maxDistFromOrigin
|
||||
local target_z = moore_pos.y * divisor - maxDistFromOrigin
|
||||
|
||||
local search_radius = divisor / 2 - 5 -- any portal within this area will do
|
||||
|
||||
-- a y_factor of 0 makes the search ignore the altitude of the portals
|
||||
local existing_portal_location, existing_portal_orientation =
|
||||
nether.find_nearest_working_portal("surface_portal", {x = target_x, y = 0, z = target_z}, search_radius, 0)
|
||||
|
||||
if existing_portal_location ~= nil then
|
||||
-- use the existing portal that was found near target_x, target_z
|
||||
return existing_portal_location, existing_portal_orientation
|
||||
else
|
||||
-- find a good location for the new portal, or if that isn't possible then at
|
||||
-- least adjust the coords a little so portals don't line up in a grid
|
||||
local adj_x, adj_z = 0, 0
|
||||
|
||||
-- Deterministically look for a location in the cell where get_spawn_level() can give
|
||||
-- us a surface height, since nether.find_surface_target_y() works *much* better when
|
||||
-- it can use get_spawn_level()
|
||||
local prng = PcgRandom( -- seed the prng so that all portals for these Moore Curve coords will use the same random location
|
||||
moore_pos.x * 65732 +
|
||||
moore_pos.y * 729 +
|
||||
minetest.get_mapgen_setting("seed") * 3
|
||||
)
|
||||
|
||||
local attemptLimit = 15 -- how many attempts we'll make at finding a good location
|
||||
for attempt = 1, attemptLimit do
|
||||
adj_x = math.floor(prng:rand_normal_dist(-search_radius, search_radius, 2) + 0.5)
|
||||
adj_z = math.floor(prng:rand_normal_dist(-search_radius, search_radius, 2) + 0.5)
|
||||
if minetest.get_spawn_level == nil or minetest.get_spawn_level(target_x + adj_x, target_z + adj_z) ~= nil then
|
||||
-- Found a location which will be at ground level - unless a player has built there.
|
||||
-- Or this is MT 0.4 which does not have get_spawn_level(), so there's no point looking
|
||||
-- at any further further random locations.
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
local destination_pos = {x = target_x + adj_x, y = 0, z = target_z + adj_z}
|
||||
destination_pos.y = nether.find_surface_target_y(destination_pos.x, destination_pos.z, "surface_portal", player_name)
|
||||
|
||||
return destination_pos
|
||||
end
|
||||
end
|
||||
})
|
||||
|
||||
end
|
||||
|
||||
--=========================================--
|
||||
-- Hilbert curve and Moore curve functions --
|
||||
--=========================================--
|
||||
|
||||
-- These are space-filling curves, used by the surface_portal example as a way to determine where
|
||||
-- to place portals. https://en.wikipedia.org/wiki/Moore_curve
|
||||
|
||||
|
||||
-- Flip a quadrant on a diagonal axis
|
||||
-- cell_count is the number of cells across the square is split into, and must be a power of 2
|
||||
-- if flip_twice is true then pos does not change (even numbers of flips cancel out)
|
||||
-- if flip_direction is true then the position is flipped along the \ diagonal
|
||||
-- if flip_direction is false then the position is flipped along the / diagonal
|
||||
local function hilbert_flip(cell_count, pos, flip_direction, flip_twice)
|
||||
if not flip_twice then
|
||||
if flip_direction then
|
||||
pos.x = (cell_count - 1) - pos.x;
|
||||
pos.y = (cell_count - 1) - pos.y;
|
||||
end
|
||||
|
||||
local temp_x = pos.x;
|
||||
pos.x = pos.y;
|
||||
pos.y = temp_x;
|
||||
end
|
||||
end
|
||||
|
||||
local function test_bit(cell_count, value, flag)
|
||||
local bit_value = cell_count / 2
|
||||
while bit_value > flag and bit_value >= 1 do
|
||||
if value >= bit_value then value = value - bit_value end
|
||||
bit_value = bit_value / 2
|
||||
end
|
||||
return value >= bit_value
|
||||
end
|
||||
|
||||
-- Converts (x,y) to distance
|
||||
-- starts at bottom left corner, i.e. (0, 0)
|
||||
-- ends at bottom right corner, i.e. (cell_count - 1, 0)
|
||||
local function get_hilbert_distance (cell_count, x, y)
|
||||
local distance = 0
|
||||
local pos = {x=x, y=y}
|
||||
local rx, ry
|
||||
|
||||
local s = cell_count / 2
|
||||
while s > 0 do
|
||||
|
||||
if test_bit(cell_count, pos.x, s) then rx = 1 else rx = 0 end
|
||||
if test_bit(cell_count, pos.y, s) then ry = 1 else ry = 0 end
|
||||
|
||||
local rx_XOR_ry = rx
|
||||
if ry == 1 then rx_XOR_ry = 1 - rx_XOR_ry end -- XOR'd ry against rx
|
||||
|
||||
distance = distance + s * s * (2 * rx + rx_XOR_ry)
|
||||
hilbert_flip(cell_count, pos, rx > 0, ry > 0);
|
||||
|
||||
s = math.floor(s / 2)
|
||||
end
|
||||
return distance;
|
||||
end
|
||||
|
||||
-- Converts distance to (x,y)
|
||||
local function get_hilbert_coords(cell_count, distance)
|
||||
local pos = {x=0, y=0}
|
||||
local rx, ry
|
||||
|
||||
local s = 1
|
||||
while s < cell_count do
|
||||
rx = math.floor(distance / 2) % 2
|
||||
ry = distance % 2
|
||||
if rx == 1 then ry = 1 - ry end -- XOR ry with rx
|
||||
|
||||
hilbert_flip(s, pos, rx > 0, ry > 0);
|
||||
pos.x = pos.x + s * rx
|
||||
pos.y = pos.y + s * ry
|
||||
distance = math.floor(distance / 4)
|
||||
|
||||
s = s * 2
|
||||
end
|
||||
return pos
|
||||
end
|
||||
|
||||
|
||||
-- Converts (x,y) to distance
|
||||
-- A Moore curve is a variation of the Hilbert curve that has the start and
|
||||
-- end next to each other.
|
||||
-- Top middle point is the start/end location
|
||||
get_moore_distance = function(cell_count, x, y)
|
||||
|
||||
local quadLength = cell_count / 2
|
||||
local quadrant = 1 - math.floor(y / quadLength)
|
||||
if math.floor(x / quadLength) == 1 then quadrant = 3 - quadrant end
|
||||
local flipDirection = x < quadLength
|
||||
|
||||
local pos = {x = x % quadLength, y = y % quadLength}
|
||||
hilbert_flip(quadLength, pos, flipDirection, false)
|
||||
|
||||
return (quadrant * quadLength * quadLength) + get_hilbert_distance(quadLength, pos.x, pos.y)
|
||||
end
|
||||
|
||||
|
||||
-- Converts distance to (x,y)
|
||||
-- A Moore curve is a variation of the Hilbert curve that has the start and
|
||||
-- end next to each other.
|
||||
-- Top middle point is the start/end location
|
||||
get_moore_coords = function(cell_count, distance)
|
||||
local quadLength = cell_count / 2
|
||||
local quadDistance = quadLength * quadLength
|
||||
local quadrant = math.floor(distance / quadDistance)
|
||||
local flipDirection = distance * 2 < cell_count * cell_count
|
||||
local pos = get_hilbert_coords(quadLength, distance % quadDistance)
|
||||
hilbert_flip(quadLength, pos, flipDirection, false)
|
||||
|
||||
if quadrant >= 2 then pos.x = pos.x + quadLength end
|
||||
if quadrant % 3 == 0 then pos.y = pos.y + quadLength end
|
||||
|
||||
return pos
|
||||
end
|
25
settingtypes.txt
Normal file
@ -0,0 +1,25 @@
|
||||
# Travelling a short distance in the Nether can correspond to a much further distance on the surface.
|
||||
#
|
||||
# A factor of 10 might be a better value for Minetest, since there's no sprint, but ex-Minecraft players will be mathing for 8.
|
||||
nether_fasttravel_factor (Nether fast-travel factor) int 8
|
||||
|
||||
# The likelyhood of finding a Book containing all the portal plans in a dungeon chest.
|
||||
# Set to 0 to disable, or 10 to have it extremely common.
|
||||
#
|
||||
# (This value will be treated as 0 when the Nether portal is the only type of portal available, or when the help modpack is installed)
|
||||
nether_portalBook_loot_weighting (Likelyhood of finding Book of Portals in dungeon chests) int 9
|
||||
|
||||
# Turn off to disable the Nether and Nether portal
|
||||
nether_realm_enabled (Enable Nether realm & portal) bool true
|
||||
|
||||
# Enables the Floatlands portal api code example
|
||||
nether_enable_portal_example_floatlands (Enable example portal: Floatlands) bool false
|
||||
|
||||
# Enables the Surface-travel portal api code example
|
||||
nether_enable_portal_example_surfacetravel (Enable example portal: Surface-travel) bool false
|
||||
|
||||
[Nether depth]
|
||||
#The depth where the Nether begins / the Nether ceiling
|
||||
nether_depth_ymax (Upper limit of Nether) int -5000 -30000 32767
|
||||
#The lower limit of the Nether must be at least 1000 lower than the upper limit, and more than 3000 lower is recommended.
|
||||
nether_depth_ymin (Lower limit of Nether) int -11000 -32768 30000
|
BIN
sounds/nether_book_open.ogg
Normal file
BIN
sounds/nether_portal_ambient.0.ogg
Normal file
BIN
sounds/nether_portal_ambient.ogg
Normal file
BIN
sounds/nether_portal_extinguish.ogg
Normal file
BIN
sounds/nether_portal_ignite.ogg
Normal file
BIN
sounds/nether_portal_ignition_failure.ogg
Normal file
BIN
sounds/nether_portal_teleport.ogg
Normal file
BIN
textures/nether_book_background.png
Normal file
After Width: | Height: | Size: 6.1 KiB |
BIN
textures/nether_book_close.png
Normal file
After Width: | Height: | Size: 961 B |
BIN
textures/nether_book_diagram_circular.png
Normal file
After Width: | Height: | Size: 4.1 KiB |
BIN
textures/nether_book_diagram_platform.png
Normal file
After Width: | Height: | Size: 3.3 KiB |
BIN
textures/nether_book_diagram_traditional.png
Normal file
After Width: | Height: | Size: 5.3 KiB |
BIN
textures/nether_book_of_portals.png
Normal file
After Width: | Height: | Size: 542 B |
Before Width: | Height: | Size: 226 B After Width: | Height: | Size: 149 B |
BIN
textures/nether_particle_anim1.png
Normal file
After Width: | Height: | Size: 292 B |
BIN
textures/nether_particle_anim2.png
Normal file
After Width: | Height: | Size: 275 B |
BIN
textures/nether_particle_anim3.png
Normal file
After Width: | Height: | Size: 385 B |
Before Width: | Height: | Size: 382 B After Width: | Height: | Size: 1.5 KiB |
BIN
textures/nether_portal_alt.png
Normal file
After Width: | Height: | Size: 282 B |
BIN
textures/nether_portals_palette.png
Normal file
After Width: | Height: | Size: 875 B |