forked from minetest-mods/nether
Compare commits
15 Commits
c5ef9136ec
...
v2
Author | SHA1 | Date | |
---|---|---|---|
02d062b9c9 | |||
e326a94266 | |||
5b3b56ebec | |||
8769593d6f | |||
5cb9e5fb27 | |||
ab4a031c1c | |||
aac3ea6719 | |||
f4255f5d1f | |||
94222d44e0 | |||
86105b4eb8 | |||
0e26770830 | |||
e5fbc2486b | |||
608d692401 | |||
f7ebd78614 | |||
e113db1478 |
@ -23,6 +23,7 @@ read_globals = {
|
||||
"PseudoRandom",
|
||||
"stairs",
|
||||
"stairsplus",
|
||||
"string.split",
|
||||
table = { fields = { "copy", "getn" } },
|
||||
"vector",
|
||||
"VoxelArea",
|
||||
|
@ -43,5 +43,6 @@ SOFTWARE.
|
||||
### [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.
|
92
init.lua
92
init.lua
@ -19,6 +19,13 @@
|
||||
|
||||
]]--
|
||||
|
||||
-- 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
|
||||
|
||||
local S
|
||||
if minetest.get_translator ~= nil then
|
||||
S = minetest.get_translator("nether")
|
||||
@ -38,25 +45,82 @@ 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
|
||||
|
||||
|
||||
-- Settings
|
||||
nether.DEPTH = -5000 -- The y location of the Nether
|
||||
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
|
||||
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
|
||||
|
||||
|
||||
-- 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) or nether.NETHER_REALM_ENABLED
|
||||
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.
|
||||
|
||||
|
||||
-- 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, ...)
|
||||
|
||||
local args = {...}
|
||||
local argCount = select("#", ...)
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
|
||||
-- Load files
|
||||
dofile(nether.path .. "/portal_api.lua")
|
||||
dofile(nether.path .. "/nodes.lua")
|
||||
if nether.NETHER_REALM_ENABLED then
|
||||
dofile(nether.path .. "/mapgen.lua")
|
||||
if nether.useBiomes then
|
||||
dofile(nether.path .. "/mapgen.lua")
|
||||
else
|
||||
dofile(nether.path .. "/mapgen_nobiomes.lua")
|
||||
end
|
||||
end
|
||||
dofile(nether.path .. "/portal_examples.lua")
|
||||
|
||||
@ -83,15 +147,15 @@ This opens to a truly hellish place, though for small mercies the air there is s
|
||||
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
|
||||
return pos.y < nether.DEPTH_CEILING
|
||||
end,
|
||||
|
||||
find_realm_anchorPos = function(surface_anchorPos)
|
||||
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 - 1000 -- temp value so find_nearest_working_portal() returns nether portals
|
||||
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 =
|
||||
@ -100,13 +164,13 @@ The expedition parties have found no diamonds or gold, and after an experienced
|
||||
if existing_portal_location ~= nil then
|
||||
return existing_portal_location, existing_portal_orientation
|
||||
else
|
||||
local start_y = nether.DEPTH - math.random(500, 1500) -- Search starting altitude
|
||||
destination_pos.y = nether.find_nether_ground_y(destination_pos.x, destination_pos.z, start_y)
|
||||
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)
|
||||
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.
|
||||
@ -125,7 +189,7 @@ The expedition parties have found no diamonds or gold, and after an experienced
|
||||
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")
|
||||
destination_pos.y = nether.find_surface_target_y(destination_pos.x, destination_pos.z, "nether_portal", player_name)
|
||||
return destination_pos
|
||||
end
|
||||
end,
|
||||
@ -161,4 +225,4 @@ The expedition parties have found no diamonds or gold, and after an experienced
|
||||
end
|
||||
|
||||
})
|
||||
end
|
||||
end
|
||||
|
527
mapgen.lua
527
mapgen.lua
@ -22,11 +22,164 @@
|
||||
|
||||
-- Parameters
|
||||
|
||||
local NETHER_DEPTH = nether.DEPTH
|
||||
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 = {
|
||||
@ -40,180 +193,302 @@ local np_cave = {
|
||||
--flags = ""
|
||||
}
|
||||
|
||||
|
||||
-- Stuff
|
||||
|
||||
local yblmax = NETHER_DEPTH - BLEND * 2
|
||||
|
||||
|
||||
|
||||
|
||||
-- Mapgen
|
||||
|
||||
-- Initialize noise object, localise noise and data buffers
|
||||
-- Buffers and objects we shouldn't recreate every on_generate
|
||||
|
||||
local nobj_cave = nil
|
||||
local nbuf_cave = nil
|
||||
local dbuf = 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_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")
|
||||
|
||||
--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")
|
||||
-- Dungeon excavation functions
|
||||
|
||||
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")
|
||||
function build_dungeon_room_list(data, area)
|
||||
|
||||
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 result = {}
|
||||
|
||||
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")
|
||||
-- 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
|
||||
|
||||
minetest.register_on_generated(function(minp, maxp, seed)
|
||||
if minp.y > NETHER_DEPTH then
|
||||
local function on_generated(minp, maxp, seed)
|
||||
|
||||
if minp.y > NETHER_CEILING or maxp.y < NETHER_FLOOR 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 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 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 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 ystride = x1 - x0 + 1
|
||||
local zstride = ystride * ystride
|
||||
local chulens = {x = ystride, y = ystride, z = ystride}
|
||||
local minposxyz = {x = x0, y = y0, z = z0}
|
||||
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:get3dMap_flat(minposxyz, nbuf_cave)
|
||||
local nvals_cave = nobj_cave:get_3d_map_flat(minp, 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
|
||||
local dungeonRooms = build_dungeon_room_list(data, area)
|
||||
|
||||
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
|
||||
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
|
||||
-- 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
|
||||
|
||||
if nvals_cave[ni] > tcave 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
|
||||
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)
|
||||
vm:set_lighting({day = 0, night = 0})
|
||||
|
||||
-- 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)
|
||||
end
|
||||
|
||||
|
||||
-- use knowledge of the nether mapgen algorithm to return a suitable ground level for placing a portal.
|
||||
function nether.find_nether_ground_y(target_x, target_z, start_y)
|
||||
-- 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
|
||||
|
||||
for y = start_y, start_y - 4096, -1 do
|
||||
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
|
||||
local minp = {x = target_x - 1, y = y , z = target_z - 2}
|
||||
local maxp = {x = target_x + 2, y = y + 4, z = target_z + 2}
|
||||
if nether.volume_is_natural(minp, maxp) then
|
||||
return y + 1
|
||||
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)
|
||||
nether.find_nether_ground_y(target_x, target_z, y - 16, player_name)
|
||||
end
|
||||
else -- Not enough space, reset air to zero
|
||||
air = 0
|
||||
@ -221,5 +496,11 @@ function nether.find_nether_ground_y(target_x, target_z, start_y)
|
||||
end
|
||||
end
|
||||
|
||||
return start_y -- Fallback
|
||||
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
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
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
|
29
nodes.lua
29
nodes.lua
@ -21,7 +21,7 @@
|
||||
|
||||
local S = nether.get_translator
|
||||
|
||||
-- Portal/wormhole node
|
||||
-- Portal/wormhole nodes
|
||||
|
||||
nether.register_wormhole_node("nether:portal", {
|
||||
description = S("Nether Portal"),
|
||||
@ -37,6 +37,33 @@ nether.register_wormhole_node("nether:portal", {
|
||||
}
|
||||
})
|
||||
|
||||
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
|
||||
|
||||
|
366
portal_api.lua
366
portal_api.lua
@ -23,8 +23,9 @@
|
||||
|
||||
]]--
|
||||
|
||||
local DEBUG = false
|
||||
local DEBUG_IGNORE_MODSTORAGE = false -- setting true prevents portals from knowing where other portals are, forcing find_realm_anchorpos() etc. to be executed every time
|
||||
-- setting DEBUG_IGNORE_MODSTORAGE true prevents portals from knowing where other
|
||||
-- portals are, forcing find_realm_anchorpos() etc. to be executed every time.
|
||||
local DEBUG_IGNORE_MODSTORAGE = false
|
||||
|
||||
nether.registered_portals = {}
|
||||
nether.registered_portals_count = 0
|
||||
@ -51,6 +52,11 @@ if minetest.get_mod_storage == nil then
|
||||
error(nether.modname .. " does not support Minetest versions earlier than 0.4.16", 0)
|
||||
end
|
||||
|
||||
local S = nether.get_translator
|
||||
nether.portal_destination_not_found_message =
|
||||
S("Mysterious forces prevented you from opening that portal. Please try another location")
|
||||
|
||||
|
||||
--[[
|
||||
|
||||
Positions
|
||||
@ -113,9 +119,28 @@ metadata).
|
||||
|
||||
]]
|
||||
|
||||
local __ = {name = "air", prob = 0}
|
||||
local AA = {name = "air", prob = 255, force_place = true}
|
||||
local OO = {name = "default:obsidian", prob = 255, force_place = true}
|
||||
local facedir_up, facedir_north, facedir_south, facedir_east, facedir_west, facedir_down = 0, 4, 8, 12, 16, 20
|
||||
|
||||
local __ = {name = "air", prob = 0}
|
||||
local AA = {name = "air", prob = 255, force_place = true}
|
||||
local ON = {name = "default:obsidian", facedir = facedir_north + 0, prob = 255, force_place = true}
|
||||
local ON2 = {name = "default:obsidian", facedir = facedir_north + 1, prob = 255, force_place = true}
|
||||
local ON3 = {name = "default:obsidian", facedir = facedir_north + 2, prob = 255, force_place = true}
|
||||
local ON4 = {name = "default:obsidian", facedir = facedir_north + 3, prob = 255, force_place = true}
|
||||
local OS = {name = "default:obsidian", facedir = facedir_south, prob = 255, force_place = true}
|
||||
local OE = {name = "default:obsidian", facedir = facedir_east, prob = 255, force_place = true}
|
||||
local OW = {name = "default:obsidian", facedir = facedir_west, prob = 255, force_place = true}
|
||||
local OU = {name = "default:obsidian", facedir = facedir_up + 0, prob = 255, force_place = true}
|
||||
local OU2 = {name = "default:obsidian", facedir = facedir_up + 1, prob = 255, force_place = true}
|
||||
local OU3 = {name = "default:obsidian", facedir = facedir_up + 2, prob = 255, force_place = true}
|
||||
local OU4 = {name = "default:obsidian", facedir = facedir_up + 3, prob = 255, force_place = true}
|
||||
local OD = {name = "default:obsidian", facedir = facedir_down, prob = 255, force_place = true}
|
||||
|
||||
-- facedirNodeList is a list of node references which should have their facedir value copied into
|
||||
-- param2 before placing a schematic. The facedir values will only be copied when the portal's frame
|
||||
-- node has a paramtype2 of "facedir" or "colorfacedir".
|
||||
-- Having schematics provide this list avoids needing to check every node in the schematic volume.
|
||||
local facedirNodeList = {ON, ON2, ON3, ON4, OS, OE, OW, OU, OU2, OU3, OU4, OD}
|
||||
|
||||
-- This object defines a portal's shape, segregating the shape logic code from portal behaviour code.
|
||||
-- You can create a new "PortalShape" definition object which implements the same
|
||||
@ -285,25 +310,26 @@ nether.PortalShape_Traditional = {
|
||||
AA,AA,AA,AA,
|
||||
AA,AA,AA,AA,
|
||||
AA,AA,AA,AA,
|
||||
|
||||
OO,OO,OO,OO,
|
||||
OO,AA,AA,OO,
|
||||
OO,AA,AA,OO,
|
||||
OO,AA,AA,OO,
|
||||
OO,OO,OO,OO,
|
||||
|
||||
|
||||
ON,OW,OE,ON2,
|
||||
OU,AA,AA,OU,
|
||||
OU,AA,AA,OU,
|
||||
OU,AA,AA,OU,
|
||||
ON4,OE,OW,ON3,
|
||||
|
||||
__,__,__,__,
|
||||
AA,AA,AA,AA,
|
||||
AA,AA,AA,AA,
|
||||
AA,AA,AA,AA,
|
||||
AA,AA,AA,AA,
|
||||
|
||||
|
||||
__,__,__,__,
|
||||
AA,AA,AA,AA,
|
||||
AA,AA,AA,AA,
|
||||
AA,AA,AA,AA,
|
||||
AA,AA,AA,AA,
|
||||
}
|
||||
},
|
||||
facedirNodes = facedirNodeList
|
||||
}
|
||||
} -- End of PortalShape_Traditional class
|
||||
|
||||
@ -449,7 +475,7 @@ nether.PortalShape_Circular = {
|
||||
__,__,AA,AA,AA,__,__,
|
||||
__,__,__,__,__,__,__,
|
||||
__,__,__,__,__,__,__,
|
||||
|
||||
|
||||
__,__,__,__,__,__,__,
|
||||
__,AA,AA,AA,AA,AA,__,
|
||||
__,AA,AA,AA,AA,AA,__,
|
||||
@ -457,7 +483,7 @@ nether.PortalShape_Circular = {
|
||||
__,AA,AA,AA,AA,AA,__,
|
||||
__,AA,AA,AA,AA,AA,__,
|
||||
__,__,__,__,__,__,__,
|
||||
|
||||
|
||||
__,__,__,__,__,__,__,
|
||||
__,AA,AA,AA,AA,AA,__,
|
||||
AA,AA,AA,AA,AA,AA,AA,
|
||||
@ -465,15 +491,15 @@ nether.PortalShape_Circular = {
|
||||
AA,AA,AA,AA,AA,AA,AA,
|
||||
__,AA,AA,AA,AA,AA,__,
|
||||
__,__,AA,AA,AA,__,__,
|
||||
|
||||
__,__,OO,OO,OO,__,__,
|
||||
__,OO,AA,AA,AA,OO,__,
|
||||
OO,AA,AA,AA,AA,AA,OO,
|
||||
OO,AA,AA,AA,AA,AA,OO,
|
||||
OO,AA,AA,AA,AA,AA,OO,
|
||||
__,OO,AA,AA,AA,OO,__,
|
||||
__,__,OO,OO,OO,__,__,
|
||||
|
||||
|
||||
__,__,OW,OW,OW,__,__,
|
||||
__,ON,AA,AA,AA,ON2,__,
|
||||
OU,AA,AA,AA,AA,AA,OD,
|
||||
OU,AA,AA,AA,AA,AA,OD,
|
||||
OU,AA,AA,AA,AA,AA,OD,
|
||||
__,ON4,AA,AA,AA,ON3,__,
|
||||
__,__,OE,OE,OE,__,__,
|
||||
|
||||
__,__,__,__,__,__,__,
|
||||
__,AA,AA,AA,AA,AA,__,
|
||||
AA,AA,AA,AA,AA,AA,AA,
|
||||
@ -481,7 +507,7 @@ nether.PortalShape_Circular = {
|
||||
AA,AA,AA,AA,AA,AA,AA,
|
||||
__,AA,AA,AA,AA,AA,__,
|
||||
__,__,AA,AA,AA,__,__,
|
||||
|
||||
|
||||
__,__,__,__,__,__,__,
|
||||
__,AA,AA,AA,AA,AA,__,
|
||||
__,AA,AA,AA,AA,AA,__,
|
||||
@ -497,7 +523,8 @@ nether.PortalShape_Circular = {
|
||||
__,__,AA,AA,AA,__,__,
|
||||
__,__,__,__,__,__,__,
|
||||
__,__,__,__,__,__,__,
|
||||
}
|
||||
},
|
||||
facedirNodes = facedirNodeList
|
||||
}
|
||||
} -- End of PortalShape_Circular class
|
||||
|
||||
@ -597,35 +624,36 @@ nether.PortalShape_Platform = {
|
||||
size = {x = 5, y = 5, z = 5},
|
||||
data = { -- note that data is upside down
|
||||
__,__,__,__,__,
|
||||
OO,OO,OO,OO,OO,
|
||||
OU4,OW,OW,OW,OU3,
|
||||
__,AA,AA,AA,__,
|
||||
__,AA,AA,AA,__,
|
||||
__,__,__,__,__,
|
||||
|
||||
__,OO,OO,OO,__,
|
||||
OO,AA,AA,AA,OO,
|
||||
|
||||
__,OU4,OW,OU3,__,
|
||||
ON,AA,AA,AA,OS,
|
||||
AA,AA,AA,AA,AA,
|
||||
AA,AA,AA,AA,AA,
|
||||
__,AA,AA,AA,__,
|
||||
|
||||
__,OO,OO,OO,__,
|
||||
OO,AA,AA,AA,OO,
|
||||
|
||||
__,ON,OD,OS,__,
|
||||
ON,AA,AA,AA,OS,
|
||||
AA,AA,AA,AA,AA,
|
||||
AA,AA,AA,AA,AA,
|
||||
__,AA,AA,AA,__,
|
||||
|
||||
__,OO,OO,OO,__,
|
||||
OO,AA,AA,AA,OO,
|
||||
|
||||
__,OU,OE,OU2,__,
|
||||
ON,AA,AA,AA,OS,
|
||||
AA,AA,AA,AA,AA,
|
||||
AA,AA,AA,AA,AA,
|
||||
__,AA,AA,AA,__,
|
||||
|
||||
|
||||
__,__,__,__,__,
|
||||
OO,OO,OO,OO,OO,
|
||||
OU,OE,OE,OE,OU2,
|
||||
__,AA,AA,AA,__,
|
||||
__,AA,AA,AA,__,
|
||||
__,__,__,__,__,
|
||||
}
|
||||
},
|
||||
facedirNodes = facedirNodeList
|
||||
}
|
||||
} -- End of PortalShape_Platform class
|
||||
|
||||
@ -640,8 +668,8 @@ nether.PortalShape_Platform = {
|
||||
-- Portal implementation functions --
|
||||
-- =============================== --
|
||||
|
||||
local debugf = nether.debug
|
||||
local ignition_item_name
|
||||
local S = nether.get_translator
|
||||
local mod_storage = minetest.get_mod_storage()
|
||||
local meseconsAvailable = minetest.get_modpath("mesecon") ~= nil and minetest.global_exists("mesecon")
|
||||
local book_added_as_treasure = false
|
||||
@ -799,7 +827,7 @@ end
|
||||
local function store_portal_location_info(portal_name, anchorPos, orientation, ignited)
|
||||
if not DEBUG_IGNORE_MODSTORAGE then
|
||||
local key = minetest.pos_to_string(anchorPos) .. " is " .. portal_name
|
||||
if DEBUG then minetest.chat_send_all("Adding/updating portal in mod_storage: " .. key) end
|
||||
debugf("Adding/updating portal in mod_storage: " .. key)
|
||||
mod_storage:set_string(
|
||||
key,
|
||||
minetest.serialize({orientation = orientation, active = ignited})
|
||||
@ -812,7 +840,7 @@ end
|
||||
local function remove_portal_location_info(portal_name, anchorPos)
|
||||
if not DEBUG_IGNORE_MODSTORAGE then
|
||||
local key = minetest.pos_to_string(anchorPos) .. " is " .. portal_name
|
||||
if DEBUG then minetest.chat_send_all("Removing portal from mod_storage: " .. key) end
|
||||
debugf("Removing portal from mod_storage: " .. key)
|
||||
mod_storage:set_string(key, "")
|
||||
end
|
||||
end
|
||||
@ -850,7 +878,7 @@ local function list_closest_portals(portal_definition, anchorPos, distance_limit
|
||||
local distance = math.hypot(y * y_factor, math.hypot(x, z))
|
||||
if distance <= distance_limit or distance_limit < 0 then
|
||||
local info = minetest.deserialize(value) or {}
|
||||
if DEBUG then minetest.chat_send_all("found " .. found_name .. " listed at distance " .. distance .. " (within " .. distance_limit .. ") from dest " .. minetest.pos_to_string(anchorPos) .. ", found: " .. minetest.pos_to_string(found_anchorPos) .. " orientation " .. info.orientation) end
|
||||
debugf("found %s listed at distance %.2f (within %.2f) from dest %s, found: %s orientation %s", found_name, distance, distance_limit, anchorPos, found_anchorPos, info.orientation)
|
||||
info.anchorPos = found_anchorPos
|
||||
info.distance = distance
|
||||
result[distance] = info
|
||||
@ -902,14 +930,14 @@ end
|
||||
function extinguish_portal(pos, node_name, frame_was_destroyed)
|
||||
|
||||
-- mesecons seems to invoke action_off() 6 times every time you place a block?
|
||||
if DEBUG then minetest.chat_send_all("extinguish_portal" .. minetest.pos_to_string(pos) .. " " .. node_name) end
|
||||
debugf("extinguish_portal %s %s", pos, node_name)
|
||||
|
||||
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 p1 == nil or p2 == nil then
|
||||
if DEBUG then minetest.chat_send_all(" no active portal found to extinguish") end
|
||||
debugf(" no active portal found to extinguish")
|
||||
return false
|
||||
end
|
||||
|
||||
@ -961,7 +989,7 @@ function extinguish_portal(pos, node_name, frame_was_destroyed)
|
||||
end
|
||||
|
||||
if target ~= nil then
|
||||
if DEBUG then minetest.chat_send_all(" attempting to also extinguish target with wormholePos " .. minetest.pos_to_string(target)) end
|
||||
debugf(" attempting to also extinguish target with wormholePos %s", target)
|
||||
extinguish_portal(target, node_name)
|
||||
end
|
||||
|
||||
@ -978,7 +1006,8 @@ end
|
||||
-- Note: will extinguish any portal using the same nodes that are being set
|
||||
local function set_portal_metadata(portal_definition, anchorPos, orientation, destination_wormholePos, ignite)
|
||||
|
||||
if DEBUG then minetest.chat_send_all("set_portal_metadata(ignite=" .. tostring(ignite) .. ") at " .. minetest.pos_to_string(anchorPos) .. " orient " .. orientation .. ", setting to target " .. minetest.pos_to_string(destination_wormholePos)) end
|
||||
ignite = ignite or false;
|
||||
debugf("set_portal_metadata(ignite=%s) at %s orient %s, setting to target %s", ignite, anchorPos, orientation, destination_wormholePos)
|
||||
|
||||
-- Portal position is stored in metadata as p1 and p2 to keep maps compatible with earlier versions of this mod.
|
||||
-- p1 is the bottom/west/south corner of the portal, and p2 is the opposite corner, together
|
||||
@ -1006,7 +1035,7 @@ local function set_portal_metadata(portal_definition, anchorPos, orientation, de
|
||||
if existing_p1 ~= "" then
|
||||
local existing_p2 = meta:get_string("p2")
|
||||
if existing_p1 ~= p1_string or existing_p2 ~= p2_string then
|
||||
if DEBUG then minetest.chat_send_all("set_portal_metadata() found existing metadata from another portal: existing_p1 " .. existing_p1 .. ", existing_p2" .. existing_p2 .. ", p1 " .. p1_string .. ", p2 " .. p2_string .. ", will existinguish existing portal...") end
|
||||
debugf("set_portal_metadata() found existing metadata from another portal: existing_p1 %s, existing_p2 %s, p1 %s, p2 %s, will extinguish existing portal...", existing_p1, existing_p2, p1_string, p2_string)
|
||||
-- this node is already part of another portal, so extinguish that, because nodes only
|
||||
-- contain a link in the metadata to one portal, and being part of two allows a slew of bugs
|
||||
extinguish_portal(pos, node_name, false)
|
||||
@ -1070,7 +1099,7 @@ local function is_portal_at_anchorPos(portal_definition, anchorPos, orientation,
|
||||
-- area isn't loaded, force loading/emerge of check area
|
||||
minetest.get_voxel_manip():read_from_map(check_pos, check_pos)
|
||||
foundName = minetest.get_node(check_pos).name
|
||||
if DEBUG then minetest.chat_send_all("Forced loading of 'ignore' node at " .. minetest.pos_to_string(check_pos) .. ", got " .. foundName) end
|
||||
debugf("Forced loading of 'ignore' node at %s, got %s", check_pos, foundName)
|
||||
|
||||
if foundName ~= frame_node_name then
|
||||
nodes_are_valid = false
|
||||
@ -1137,8 +1166,29 @@ local function is_within_portal_frame(portal_definition, pos)
|
||||
end
|
||||
|
||||
|
||||
-- sets param2 values in the schematic to match facedir values, or 0 if the portalframe-nodedef doesn't use facedir
|
||||
local function set_schematic_param2(schematic_table, frame_node_name, frame_node_color)
|
||||
|
||||
local paramtype2 = minetest.registered_nodes[frame_node_name].paramtype2
|
||||
local isFacedir = paramtype2 == "facedir" or paramtype2 == "colorfacedir"
|
||||
|
||||
if schematic_table.facedirNodes ~= nil then
|
||||
for _, node in ipairs(schematic_table.facedirNodes) do
|
||||
if isFacedir and node.facedir ~= nil then
|
||||
-- frame_node_color can be nil
|
||||
local colorBits = (frame_node_color or math.floor((node.param2 or 0) / 32)) * 32
|
||||
node.param2 = node.facedir + colorBits
|
||||
else
|
||||
node.param2 = 0
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function build_portal(portal_definition, anchorPos, orientation, destination_wormholePos)
|
||||
|
||||
set_schematic_param2(portal_definition.shape.schematic, portal_definition.frame_node_name, portal_definition.frame_node_color)
|
||||
|
||||
minetest.place_schematic(
|
||||
portal_definition.shape.get_schematicPos_from_anchorPos(anchorPos, orientation),
|
||||
portal_definition.shape.schematic,
|
||||
@ -1159,7 +1209,7 @@ local function build_portal(portal_definition, anchorPos, orientation, destinati
|
||||
function(pos) minetest.swap_node(pos, wormholeNode) end
|
||||
)
|
||||
|
||||
if DEBUG then minetest.chat_send_all("Placed " .. portal_definition.name .. " portal schematic at " .. minetest.pos_to_string(portal_definition.shape.get_schematicPos_from_anchorPos(anchorPos, orientation)) .. ", orientation " .. orientation) end
|
||||
debugf("Placed %s portal schematic at %s, orientation %s", portal_definition.name, portal_definition.shape.get_schematicPos_from_anchorPos(anchorPos, orientation), orientation)
|
||||
|
||||
set_portal_metadata(portal_definition, anchorPos, orientation, destination_wormholePos)
|
||||
|
||||
@ -1173,13 +1223,13 @@ end
|
||||
-- Make portals immortal for ~20 seconds after creation
|
||||
local function remote_portal_checkup(elapsed, portal_definition, anchorPos, orientation, destination_wormholePos)
|
||||
|
||||
if DEBUG then minetest.chat_send_all("portal checkup at " .. elapsed .. " seconds") end
|
||||
debugf("portal checkup at %d seconds", elapsed)
|
||||
|
||||
local wormholePos = portal_definition.shape.get_wormholePos_from_anchorPos(anchorPos, orientation)
|
||||
local wormhole_node = minetest.get_node_or_nil(wormholePos)
|
||||
|
||||
local portalFound, portalLit = false, false
|
||||
if wormhole_node ~= nil and wormhole_node.name == portal_definition.wormhole_node_name then
|
||||
local portalFound, portalLit = false, false
|
||||
if wormhole_node ~= nil and wormhole_node.name == portal_definition.wormhole_node_name then
|
||||
-- a wormhole node was there, but check the whole frame is intact
|
||||
portalFound, portalLit = is_portal_at_anchorPos(portal_definition, anchorPos, orientation, false)
|
||||
end
|
||||
@ -1188,7 +1238,7 @@ local function remote_portal_checkup(elapsed, portal_definition, anchorPos, orie
|
||||
-- ruh roh
|
||||
local message = "Newly created portal at " .. minetest.pos_to_string(anchorPos) .. " was overwritten. Attempting to recreate. Issue spotted after " .. elapsed .. " seconds"
|
||||
minetest.log("warning", message)
|
||||
if DEBUG then minetest.chat_send_all("!!! " .. message) end
|
||||
debugf("!!! " .. message)
|
||||
|
||||
-- A pre-existing portal frame wouldn't have been immediately overwritten, so no need to check for one, just place the portal.
|
||||
build_portal(portal_definition, anchorPos, orientation, destination_wormholePos)
|
||||
@ -1216,7 +1266,7 @@ end
|
||||
-- specified if an existing portal was already found there.
|
||||
local function locate_or_build_portal(portal_definition, suggested_wormholePos, suggested_orientation, destination_wormholePos)
|
||||
|
||||
if DEBUG then minetest.chat_send_all("locate_or_build_portal() called at wormholePos" .. minetest.pos_to_string(suggested_wormholePos) .. " with suggested orient " .. suggested_orientation .. ", targetted to " .. minetest.pos_to_string(destination_wormholePos)) end
|
||||
debugf("locate_or_build_portal() called at wormholePos%s with suggested orient %s, targeted to %s", suggested_wormholePos, suggested_orientation, destination_wormholePos)
|
||||
|
||||
local result_anchorPos;
|
||||
local result_orientation;
|
||||
@ -1237,13 +1287,13 @@ local function locate_or_build_portal(portal_definition, suggested_wormholePos,
|
||||
if result_target ~= nil and vector.equals(result_target, destination_wormholePos) then
|
||||
-- It already links back to the portal the player is teleporting from, so don't
|
||||
-- extinguish it or the player's portal will also extinguish.
|
||||
if DEBUG then minetest.chat_send_all(" Build unnecessary: already a lit portal that links back here at " .. minetest.pos_to_string(found_anchorPos) .. ", orientation " .. result_orientation) end
|
||||
debugf(" Build unnecessary: already a lit portal that links back here at %s, orientation %s", found_anchorPos, result_orientation)
|
||||
else
|
||||
if DEBUG then minetest.chat_send_all(" Build unnecessary: already a lit portal at " .. minetest.pos_to_string(found_anchorPos) .. ", orientation " .. result_orientation .. ", linking to " .. result_target_str .. ". Extinguishing...") end
|
||||
debugf(" Build unnecessary: already a lit portal at %s, orientation %s, linking to %s. Extinguishing...", found_anchorPos, result_orientation, result_target_str)
|
||||
extinguish_portal(found_anchorPos, portal_definition.frame_node_name, false)
|
||||
end
|
||||
else
|
||||
if DEBUG then minetest.chat_send_all(" Build unnecessary: already an unlit portal at " .. minetest.pos_to_string(found_anchorPos) .. ", orientation " .. result_orientation) end
|
||||
debugf(" Build unnecessary: already an unlit portal at %s, orientation %s", found_anchorPos, result_orientation)
|
||||
end
|
||||
-- ignite the portal
|
||||
set_portal_metadata_and_ignite(portal_definition, result_anchorPos, result_orientation, destination_wormholePos)
|
||||
@ -1260,11 +1310,12 @@ end
|
||||
|
||||
|
||||
-- invoked when a player attempts to turn obsidian nodes into an open portal
|
||||
-- player_name is optional, allowing a player to spawn a remote portal in their own protected area
|
||||
-- ignition_node_name is optional
|
||||
local function ignite_portal(ignition_pos, ignition_node_name)
|
||||
local function ignite_portal(ignition_pos, player_name, ignition_node_name)
|
||||
|
||||
if ignition_node_name == nil then ignition_node_name = minetest.get_node(ignition_pos).name end
|
||||
if DEBUG then minetest.chat_send_all("IGNITE the " .. ignition_node_name .. " at " .. minetest.pos_to_string(ignition_pos)) end
|
||||
debugf("IGNITE the %s at %s", ignition_node_name, ignition_pos)
|
||||
|
||||
-- find which sort of portals are made from the node that was clicked on
|
||||
local portal_definition_list = list_portal_definitions_for_frame_node(ignition_node_name)
|
||||
@ -1275,7 +1326,7 @@ local function ignite_portal(ignition_pos, ignition_node_name)
|
||||
-- check it was a portal frame that the player is trying to ignite
|
||||
local anchorPos, orientation, is_ignited = is_within_portal_frame(portal_definition, ignition_pos)
|
||||
if anchorPos == nil then
|
||||
if DEBUG then minetest.chat_send_all("No " .. portal_definition.name .. " portal frame found at " .. minetest.pos_to_string(ignition_pos)) end
|
||||
debugf("No %s portal frame found at ", portal_definition.name, ignition_pos)
|
||||
continue = true -- no portal is here, but perhaps there's more than one portal type we need to search for
|
||||
elseif is_ignited then
|
||||
-- Found a portal, check its metadata and timer is healthy.
|
||||
@ -1287,10 +1338,10 @@ local function ignite_portal(ignition_pos, ignition_node_name)
|
||||
-- metadata is missing, the portal frame node must have been removed without calling
|
||||
-- on_destruct - perhaps by an ABM, then replaced - presumably by a player.
|
||||
-- allowing reigniting will repair the portal
|
||||
if DEBUG then minetest.chat_send_all("Broken portal detected, allowing reignition/repair") end
|
||||
debugf("Broken portal detected, allowing reignition/repair")
|
||||
repair = true
|
||||
else
|
||||
if DEBUG then minetest.chat_send_all("This portal links to " .. meta:get_string("target") .. ". p1=" .. meta:get_string("p1") .. " p2=" .. meta:get_string("p2")) end
|
||||
debugf("This portal links to %s. p1=%s p2=%s", meta:get_string("target"), meta:get_string("p1"), meta:get_string("p2"))
|
||||
|
||||
-- Check the portal's timer is running, and fix if it's not.
|
||||
-- A portal's timer can stop running if the game is played without that portal type being
|
||||
@ -1298,7 +1349,7 @@ local function ignite_portal(ignition_pos, ignition_node_name)
|
||||
-- (if this is a frequent problem, then change the value of "run_at_every_load" in the lbm)
|
||||
local timer = minetest.get_node_timer(get_timerPos_from_p1_and_p2(minetest.string_to_pos(p1), minetest.string_to_pos(p2)))
|
||||
if timer ~= nil and timer:get_timeout() == 0 then
|
||||
if DEBUG then minetest.chat_send_all("Portal timer was not running: restarting the timer.") end
|
||||
debugf("Portal timer was not running: restarting the timer.")
|
||||
timer:start(1)
|
||||
end
|
||||
end
|
||||
@ -1307,23 +1358,30 @@ local function ignite_portal(ignition_pos, ignition_node_name)
|
||||
end
|
||||
|
||||
if continue == false then
|
||||
if DEBUG then minetest.chat_send_all("Found portal frame. Looked at " .. minetest.pos_to_string(ignition_pos) .. ", found at " .. minetest.pos_to_string(anchorPos) .. " orientation " .. orientation) end
|
||||
debugf("Found portal frame. Looked at %s, found at %s orientation %s", ignition_pos, anchorPos, orientation)
|
||||
|
||||
local destination_anchorPos, destination_orientation
|
||||
if portal_definition.is_within_realm(ignition_pos) then
|
||||
destination_anchorPos, destination_orientation = portal_definition.find_surface_anchorPos(anchorPos)
|
||||
destination_anchorPos, destination_orientation = portal_definition.find_surface_anchorPos(anchorPos, player_name or "")
|
||||
else
|
||||
destination_anchorPos, destination_orientation = portal_definition.find_realm_anchorPos(anchorPos)
|
||||
destination_anchorPos, destination_orientation = portal_definition.find_realm_anchorPos(anchorPos, player_name or "")
|
||||
end
|
||||
if destination_orientation == nil then
|
||||
debugf("No destination_orientation given")
|
||||
destination_orientation = orientation
|
||||
end
|
||||
if DEBUG and destination_orientation == nil then minetest.chat_send_all("No destination_orientation given") end
|
||||
if destination_orientation == nil then destination_orientation = orientation end
|
||||
|
||||
if destination_anchorPos == nil then
|
||||
if DEBUG then minetest.chat_send_all("No portal destination available here!") end
|
||||
if destination_anchorPos == nil or destination_anchorPos.y == nil then
|
||||
-- destination_anchorPos.y was also checked for nil in case portal_definition.find_surface_anchorPos()
|
||||
-- had used nether.find_surface_target_y() and that had returned nil.
|
||||
debugf("No portal destination available here!")
|
||||
if (player_name or "") ~= "" then
|
||||
minetest.chat_send_player(player_name, nether.portal_destination_not_found_message)
|
||||
end
|
||||
return false
|
||||
else
|
||||
local destination_wormholePos = portal_definition.shape.get_wormholePos_from_anchorPos(destination_anchorPos, destination_orientation)
|
||||
if DEBUG then minetest.chat_send_all("Destination set to " .. minetest.pos_to_string(destination_anchorPos)) end
|
||||
debugf("Destination set to %s", destination_anchorPos)
|
||||
|
||||
-- ignition/BURN_BABY_BURN
|
||||
set_portal_metadata_and_ignite(portal_definition, anchorPos, orientation, destination_wormholePos)
|
||||
@ -1362,7 +1420,7 @@ local function ensure_remote_portal_then_teleport(playerName, portal_definition,
|
||||
local local_p1, local_p2 = portal_definition.shape:get_p1_and_p2_from_anchorPos(local_anchorPos, local_orientation)
|
||||
local p1_at_playerPos = minetest.string_to_pos(meta:get_string("p1"))
|
||||
if p1_at_playerPos == nil or not vector.equals(local_p1, p1_at_playerPos) then
|
||||
if DEBUG then minetest.chat_send_all("the player already teleported from " .. minetest.pos_to_string(local_anchorPos) .. ", and is now standing in a different portal - " .. meta:get_string("p1")) end
|
||||
debugf("the player already teleported from %s, and is now standing in a different portal - %s", local_anchorPos, meta:get_string("p1"))
|
||||
return -- the player already teleported, and is now standing in a different portal
|
||||
end
|
||||
|
||||
@ -1370,7 +1428,7 @@ local function ensure_remote_portal_then_teleport(playerName, portal_definition,
|
||||
|
||||
if dest_wormhole_node == nil then
|
||||
-- area not emerged yet, delay and retry
|
||||
if DEBUG then minetest.chat_send_all("ensure_remote_portal_then_teleport() could not find anything yet at " .. minetest.pos_to_string(destination_wormholePos)) end
|
||||
debugf("ensure_remote_portal_then_teleport() could not find anything yet at %s", destination_wormholePos)
|
||||
minetest.after(1, ensure_remote_portal_then_teleport, playerName, portal_definition, local_anchorPos, local_orientation, destination_wormholePos)
|
||||
else
|
||||
local local_wormholePos = portal_definition.shape.get_wormholePos_from_anchorPos(local_anchorPos, local_orientation)
|
||||
@ -1387,9 +1445,9 @@ local function ensure_remote_portal_then_teleport(playerName, portal_definition,
|
||||
local remoteMeta = minetest.get_meta(destination_wormholePos)
|
||||
local remoteTarget = minetest.string_to_pos(remoteMeta:get_string("target"))
|
||||
if remoteTarget == nil then
|
||||
if DEBUG then minetest.chat_send_all("Failed to test whether target portal links back to this one") end
|
||||
debugf("Failed to test whether target portal links back to this one")
|
||||
elseif not vector.equals(remoteTarget, local_wormholePos) then
|
||||
if DEBUG then minetest.chat_send_all("Target portal is already linked, extinguishing then relighting to point back at this one") end
|
||||
debugf("Target portal is already linked, extinguishing then relighting to point back at this one")
|
||||
extinguish_portal(remoteTarget, portal_definition.frame_node_name, false)
|
||||
set_portal_metadata_and_ignite(
|
||||
portal_definition,
|
||||
@ -1399,7 +1457,7 @@ local function ensure_remote_portal_then_teleport(playerName, portal_definition,
|
||||
)
|
||||
end
|
||||
|
||||
if DEBUG then minetest.chat_send_all("Teleporting player from wormholePos" .. minetest.pos_to_string(local_wormholePos) .. " to wormholePos" .. minetest.pos_to_string(destination_wormholePos)) end
|
||||
debugf("Teleporting player from wormholePos%s to wormholePos%s", local_wormholePos, destination_wormholePos)
|
||||
|
||||
-- play the teleport sound
|
||||
if portal_definition.sounds.teleport ~= nil then
|
||||
@ -1426,7 +1484,7 @@ local function ensure_remote_portal_then_teleport(playerName, portal_definition,
|
||||
-- which will leave a confused player.
|
||||
-- I don't think this is worth preventing, but I document it incase someone describes entering a portal
|
||||
-- and then the portal turning off.
|
||||
if DEBUG then minetest.chat_send_all("ensure_remote_portal_then_teleport() saw " .. dest_wormhole_node.name .. " at " .. minetest.pos_to_string(destination_wormholePos) .. " rather than a wormhole. Calling locate_or_build_portal()") end
|
||||
debugf("ensure_remote_portal_then_teleport() saw %s at %s rather than a wormhole. Calling locate_or_build_portal()", dest_wormhole_node.name, destination_wormholePos)
|
||||
|
||||
local new_dest_anchorPos, new_dest_orientation = locate_or_build_portal(portal_definition, destination_wormholePos, local_orientation, local_wormholePos)
|
||||
local new_dest_wormholePos = portal_definition.shape.get_wormholePos_from_anchorPos(new_dest_anchorPos, new_dest_orientation)
|
||||
@ -1443,10 +1501,10 @@ local function ensure_remote_portal_then_teleport(playerName, portal_definition,
|
||||
-- local portal to also be extinguished.
|
||||
local message = "Local portal at " .. minetest.pos_to_string(local_anchorPos) .. " was extinguished while linking to existing portal at " .. minetest.pos_to_string(new_dest_anchorPos)
|
||||
minetest.log("error", message)
|
||||
if DEBUG then minetest.chat_send_all("!ERROR! - " .. message) end
|
||||
debugf("!ERROR! - " .. message)
|
||||
else
|
||||
destination_wormholePos = new_dest_wormholePos
|
||||
if DEBUG then minetest.chat_send_all(" updating target to where remote portal was found - " .. minetest.pos_to_string(destination_wormholePos)) end
|
||||
debugf(" updating target to where remote portal was found - %s", destination_wormholePos)
|
||||
|
||||
set_portal_metadata(
|
||||
portal_definition,
|
||||
@ -1781,22 +1839,22 @@ function register_frame_node(frame_node_name)
|
||||
extended_node_def.replaced_by_portalapi.mesecons = extended_node_def.mesecons
|
||||
extended_node_def.mesecons = {effector = {
|
||||
action_on = function (pos, node)
|
||||
if DEBUG then minetest.chat_send_all("portal frame material: mesecons action ON") end
|
||||
ignite_portal(pos, node.name)
|
||||
debugf("portal frame material: mesecons action ON")
|
||||
ignite_portal(pos, nil, node.name)
|
||||
end,
|
||||
action_off = function (pos, node)
|
||||
if DEBUG then minetest.chat_send_all("portal frame material: mesecons action OFF") end
|
||||
debugf("portal frame material: mesecons action OFF")
|
||||
extinguish_portal(pos, node.name, false)
|
||||
end
|
||||
}}
|
||||
extended_node_def.replaced_by_portalapi.on_destruct = extended_node_def.on_destruct
|
||||
extended_node_def.on_destruct = function(pos)
|
||||
if DEBUG then minetest.chat_send_all("portal frame material: destruct") end
|
||||
debugf("portal frame material: destruct")
|
||||
extinguish_portal(pos, frame_node_name, true)
|
||||
end
|
||||
extended_node_def.replaced_by_portalapi.on_blast = extended_node_def.on_blast
|
||||
extended_node_def.on_blast = function(pos, intensity)
|
||||
if DEBUG then minetest.chat_send_all("portal frame material: blast") end
|
||||
debugf("portal frame material: blast")
|
||||
extinguish_portal(pos, frame_node_name, extended_node_def.replaced_by_portalapi.on_blast == nil)
|
||||
if extended_node_def.replaced_by_portalapi.on_blast ~= nil then
|
||||
extended_node_def.replaced_by_portalapi.on_blast(pos, intensity)
|
||||
@ -1864,6 +1922,10 @@ function test_portaldef_is_valid(portal_definition)
|
||||
assert(portal_definition.wormhole_node_color >= 0 and portal_definition.wormhole_node_color < 8, "portaldef.wormhole_node_color must be between 0 and 7 (inclusive)")
|
||||
assert(portal_definition.is_within_realm ~= nil, "portaldef.is_within_realm() must be implemented")
|
||||
assert(portal_definition.find_realm_anchorPos ~= nil, "portaldef.find_realm_anchorPos() must be implemented")
|
||||
|
||||
if portal_definition.frame_node_color ~= nil then
|
||||
assert(portal_definition.frame_node_color >= 0 and portal_definition.frame_node_color < 8, "portal_definition.frame_node_color must be between 0 and 7 (inclusive)")
|
||||
end
|
||||
-- todo
|
||||
|
||||
return result
|
||||
@ -1888,9 +1950,9 @@ minetest.register_lbm({
|
||||
local timer = minetest.get_node_timer(timerPos)
|
||||
if timer ~= nil then
|
||||
timer:start(1)
|
||||
if DEBUG then minetest.chat_send_all("LBM started portal timer " .. minetest.pos_to_string(timerPos)) end
|
||||
elseif DEBUG then
|
||||
minetest.chat_send_all("get_node_timer" .. minetest.pos_to_string(timerPos) .. " returned null")
|
||||
debugf("LBM started portal timer %s", timerPos)
|
||||
else
|
||||
debugf("get_node_timer%s returned null", timerPos)
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -1916,7 +1978,7 @@ local wormhole_nodedef_default = {
|
||||
type = "vertical_frames",
|
||||
aspect_w = 16,
|
||||
aspect_h = 16,
|
||||
length = 0.5,
|
||||
length = 0.9,
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -1925,7 +1987,7 @@ local wormhole_nodedef_default = {
|
||||
type = "vertical_frames",
|
||||
aspect_w = 16,
|
||||
aspect_h = 16,
|
||||
length = 0.5,
|
||||
length = 0.9,
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -2034,7 +2096,7 @@ function nether.register_portal(name, portaldef)
|
||||
end
|
||||
|
||||
if portaldef.find_surface_anchorPos == nil then -- default to using find_surface_target_y()
|
||||
portaldef.find_surface_anchorPos = function(pos)
|
||||
portaldef.find_surface_anchorPos = function(pos, player_name)
|
||||
|
||||
local destination_pos = {x = pos.x, y = 0, z = pos.z}
|
||||
local existing_portal_location, existing_portal_orientation =
|
||||
@ -2042,7 +2104,7 @@ function nether.register_portal(name, portaldef)
|
||||
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, name)
|
||||
destination_pos.y = nether.find_surface_target_y(destination_pos.x, destination_pos.z, name, player_name)
|
||||
return destination_pos
|
||||
end
|
||||
end
|
||||
@ -2106,10 +2168,10 @@ end
|
||||
function nether.register_portal_ignition_item(item_name, ignition_failure_sound)
|
||||
|
||||
minetest.override_item(item_name, {
|
||||
on_place = function(stack, _, pt)
|
||||
on_place = function(stack, placer, pt)
|
||||
local done = false
|
||||
if pt.under and nether.is_frame_node[minetest.get_node(pt.under).name] then
|
||||
done = ignite_portal(pt.under)
|
||||
done = ignite_portal(pt.under, placer:get_player_name())
|
||||
if done and not minetest.settings:get_bool("creative_mode") then
|
||||
stack:take_item()
|
||||
end
|
||||
@ -2128,24 +2190,24 @@ end
|
||||
|
||||
-- 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.
|
||||
-- player_name is optional, allowing a player to spawn a remote portal in their own protected areas.
|
||||
-- (Water also fails this test, unless it is unemerged)
|
||||
function nether.volume_is_natural(minp, maxp)
|
||||
function nether.volume_is_natural_and_unprotected(minp, maxp, player_name)
|
||||
|
||||
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 emin, emax = vm:read_from_map(minp, maxp)
|
||||
local area = VoxelArea:new({MinEdge = emin, MaxEdge = emax})
|
||||
local data = vm:get_data()
|
||||
|
||||
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
|
||||
for z = minp.z, maxp.z do
|
||||
for y = minp.y, maxp.y do
|
||||
local vi = area:index(minp.x, y, z)
|
||||
for x = minp.x, maxp.x do
|
||||
local id = data[vi] -- Existing node
|
||||
if DEBUG and id == nil then minetest.chat_send_all("nil block at index " .. vi) end
|
||||
if id == nil then debugf("nil block at index " .. vi) end
|
||||
if id ~= c_air and id ~= c_ignore and id ~= nil then -- checked for common natural or not emerged
|
||||
local name = minetest.get_name_from_content_id(id)
|
||||
local nodedef = minetest.registered_nodes[name]
|
||||
@ -2153,7 +2215,7 @@ function nether.volume_is_natural(minp, maxp)
|
||||
-- trees are natural but not "ground content"
|
||||
local node_groups = nodedef.groups
|
||||
if node_groups == nil or (node_groups.tree == nil and node_groups.leaves == nil and node_groups.leafdecay == nil) then
|
||||
if DEBUG then minetest.chat_send_all("volume_is_natural() found unnatural node " .. name) end
|
||||
debugf("volume_is_natural_and_unprotected() found unnatural node %s", name)
|
||||
return false
|
||||
end
|
||||
end
|
||||
@ -2163,13 +2225,73 @@ function nether.volume_is_natural(minp, maxp)
|
||||
end
|
||||
end
|
||||
|
||||
if DEBUG then minetest.chat_send_all("Volume is natural") end
|
||||
if minetest.is_area_protected(minp, maxp, player_name or "") then
|
||||
debugf("Volume is protected against player '%s', %s-%s", player_name, minp, maxp)
|
||||
return false;
|
||||
end
|
||||
|
||||
debugf("Volume is natural and unprotected for player '%s', %s-%s", player_name, minp, maxp)
|
||||
return true
|
||||
end
|
||||
|
||||
-- Deprecated, use nether.volume_is_natural_and_unprotected() instead.
|
||||
function nether.volume_is_natural(minp, maxp)
|
||||
|
||||
if nether.deprecation_warning_volume_is_natural == nil then
|
||||
local stack = debug.traceback("", 2);
|
||||
local calling_func = (string.split(stack, "\n", false, 2, false)[2] or ""):trim()
|
||||
minetest.log("warning",
|
||||
"Deprecated function \"nether.volume_is_natural()\" invoked, use \"nether.volume_is_natural_and_unprotected()\" instead. " ..
|
||||
calling_func)
|
||||
nether.deprecation_warning_volume_is_natural = true;
|
||||
end
|
||||
|
||||
return nether.volume_is_natural_and_unprotected(minp, maxp)
|
||||
end
|
||||
|
||||
-- Gets the volume that may be altered if a portal is placed at the anchor_pos
|
||||
-- orientation is optional, but specifying it will reduce the volume returned
|
||||
-- portal_name is optional, but specifying it will reduce the volume returned
|
||||
-- returns minp, maxp
|
||||
function nether.get_schematic_volume(anchor_pos, orientation, portal_name)
|
||||
|
||||
if orientation == nil then
|
||||
-- Return a volume large enough for any orientation
|
||||
local minp0, maxp0 = nether.get_schematic_volume(anchor_pos, 0, portal_name)
|
||||
local minp1, maxp1 = nether.get_schematic_volume(anchor_pos, 1, portal_name)
|
||||
|
||||
-- ToDo: If an asymmetric portal is used with an anchor not at the center of the
|
||||
-- schematic then we will also need to check orientations 3 and 4.
|
||||
-- (The currently existing portal-shapes are not affected)
|
||||
return
|
||||
{x = math.min(minp0.x, minp1.x), y = math.min(minp0.y, minp1.y), z = math.min(minp0.z, minp1.z)},
|
||||
{x = math.max(maxp0.x, maxp1.x), y = math.max(maxp0.y, maxp1.y), z = math.max(maxp0.z, maxp1.z)}
|
||||
end
|
||||
|
||||
-- Assume the largest possible portal shape unless we know it's a smaller one.
|
||||
local shape_defintion = nether.PortalShape_Circular
|
||||
if portal_name ~= nil and nether.registered_portals[portal_name] ~= nil then
|
||||
shape_defintion = nether.registered_portals[portal_name].shape
|
||||
end
|
||||
|
||||
local size = shape_defintion.schematic.size
|
||||
local minp = shape_defintion.get_schematicPos_from_anchorPos(anchor_pos, orientation);
|
||||
local maxp
|
||||
|
||||
if (orientation % 2) == 0 then
|
||||
maxp = {x = minp.x + size.x - 1, y = minp.y + size.y - 1, z = minp.z + size.z - 1}
|
||||
else
|
||||
maxp = {x = minp.x + size.z - 1, y = minp.y + size.y - 1, z = minp.z + size.x - 1}
|
||||
end
|
||||
return minp, maxp
|
||||
end
|
||||
|
||||
|
||||
-- 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.
|
||||
function nether.find_surface_target_y(target_x, target_z, portal_name)
|
||||
-- portal_name is optional, providing it allows existing portals on the surface to be reused, and
|
||||
-- a potentially smaller volume to be checked by volume_is_natural_and_unprotected().
|
||||
-- player_name is optional, allowing a player to spawn a remote portal in their own protected areas.
|
||||
function nether.find_surface_target_y(target_x, target_z, portal_name, player_name)
|
||||
|
||||
assert(target_x ~= nil and target_z ~= nil, "Arguments `target_x` and `target_z` cannot be nil when calling find_surface_target_y()")
|
||||
|
||||
@ -2199,24 +2321,35 @@ function nether.find_surface_target_y(target_x, target_z, portal_name)
|
||||
end
|
||||
end
|
||||
|
||||
for y = start_y, start_y - 256, -16 do
|
||||
local minp_schem, maxp_schem = nether.get_schematic_volume({x = target_x, y = 0, z = target_z}, nil, portal_name)
|
||||
local minp = {x = minp_schem.x, y = 0, z = minp_schem.z}
|
||||
local maxp = {x = maxp_schem.x, y = 0, z = maxp_schem.z}
|
||||
|
||||
-- Starting searchstep at -16 and making it larger by 2 after each step gives a 20-step search range down to -646:
|
||||
-- 0, -16, -34, -54, -76, -100, -126, -154, -184, -216, -250, -286, -324, -364, -406, -450, -496, -544, -594, -646
|
||||
local searchstep = -16;
|
||||
|
||||
local y = start_y
|
||||
while y > start_y - 650 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 nether.volume_is_natural(minp, maxp) then
|
||||
minp.y = minp_schem.y + y
|
||||
maxp.y = maxp_schem.y + y
|
||||
if nether.volume_is_natural_and_unprotected(minp, maxp, player_name) then
|
||||
return y
|
||||
elseif portal_name ~= nil and nether.registered_portals[portal_name] ~= nil then
|
||||
-- players have built here - don't grief.
|
||||
-- but reigniting existing portals in portal rooms is fine - desirable even.
|
||||
local anchorPos, orientation, is_ignited = is_within_portal_frame(nether.registered_portals[portal_name], {x = target_x, y = y, z = target_z})
|
||||
if anchorPos ~= nil then
|
||||
if DEBUG then minetest.chat_send_all("Volume_is_natural check failed, but a portal frame is here " .. minetest.pos_to_string(anchorPos) .. ", so this is still a good target y level") end
|
||||
debugf("volume_is_natural_and_unprotected check failed, but a portal frame is here %s, so this is still a good target y level", anchorPos)
|
||||
return y
|
||||
end
|
||||
end
|
||||
y = y + searchstep
|
||||
searchstep = searchstep - 2
|
||||
end
|
||||
|
||||
return start_y - 256 -- Fallback
|
||||
return nil -- Portal ignition failure. Possibly due to a large protected area.
|
||||
end
|
||||
|
||||
|
||||
@ -2239,7 +2372,7 @@ function nether.find_nearest_working_portal(portal_name, anchorPos, distance_lim
|
||||
|
||||
for _, dist in ipairs(dist_list) do
|
||||
local portal_info = contenders[dist]
|
||||
if DEBUG then minetest.chat_send_all("checking portal from mod_storage at " .. minetest.pos_to_string(portal_info.anchorPos) .. " orientation " .. portal_info.orientation) end
|
||||
debugf("checking portal from mod_storage at %s orientation %s", portal_info.anchorPos, portal_info.orientation)
|
||||
|
||||
-- the mod_storage list of portals is unreliable - e.g. it won't know if inactive portals have been
|
||||
-- destroyed, so check the portal is still there
|
||||
@ -2248,7 +2381,8 @@ function nether.find_nearest_working_portal(portal_name, anchorPos, distance_lim
|
||||
if portalFound then
|
||||
return portal_info.anchorPos, portal_info.orientation
|
||||
else
|
||||
if DEBUG then minetest.chat_send_all("Portal wasn't found, removing portal from mod_storage at " .. minetest.pos_to_string(portal_info.anchorPos) .. " orientation " .. portal_info.orientation) end
|
||||
debugf("Portal wasn't found, removing portal from mod_storage at %s orientation %s",
|
||||
portal_info.anchorPos, portal_info.orientation)
|
||||
-- The portal at that location must have been destroyed
|
||||
remove_portal_location_info(portal_name, portal_info.anchorPos)
|
||||
end
|
||||
|
@ -49,17 +49,24 @@ surface.
|
||||
Helper functions
|
||||
----------------
|
||||
|
||||
* `nether.volume_is_natural(minp, maxp)`: returns a boolean
|
||||
* `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)`: returns a
|
||||
suitable anchorPos
|
||||
* `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
|
||||
@ -115,6 +122,12 @@ Used by `nether.register_portal`.
|
||||
-- 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:
|
||||
@ -127,6 +140,9 @@ Used by `nether.register_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.
|
||||
@ -199,7 +215,7 @@ Used by `nether.register_portal`.
|
||||
-- Ideally implementations are fast, as this function can be used to
|
||||
-- sift through a list of portals.
|
||||
|
||||
find_realm_anchorPos = function(surface_anchorPos),
|
||||
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)
|
||||
@ -208,8 +224,12 @@ Used by `nether.register_portal`.
|
||||
-- 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),
|
||||
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)
|
||||
@ -222,6 +242,10 @@ Used by `nether.register_portal`.
|
||||
-- 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
|
||||
|
@ -85,7 +85,7 @@ This portal is different to the others, rather than acting akin to a doorway it
|
||||
return pos.y > FLOATLAND_LEVEL - 200
|
||||
end,
|
||||
|
||||
find_realm_anchorPos = function(surface_anchorPos)
|
||||
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}
|
||||
|
||||
@ -116,6 +116,7 @@ if minetest.settings:get_bool("nether_enable_portal_example_surfacetravel", ENAB
|
||||
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.
|
||||
@ -130,13 +131,13 @@ Due to such difficulties, we never learned what determines the direction and dis
|
||||
return true
|
||||
end,
|
||||
|
||||
find_realm_anchorPos = function(surface_anchorPos)
|
||||
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)
|
||||
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.
|
||||
@ -191,7 +192,7 @@ Due to such difficulties, we never learned what determines the direction and dis
|
||||
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")
|
||||
destination_pos.y = nether.find_surface_target_y(destination_pos.x, destination_pos.z, "surface_portal", player_name)
|
||||
|
||||
return destination_pos
|
||||
end
|
||||
|
@ -16,4 +16,10 @@ nether_realm_enabled (Enable Nether realm & portal) bool true
|
||||
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_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
|
Binary file not shown.
Before Width: | Height: | Size: 282 B After Width: | Height: | Size: 1.5 KiB |
BIN
textures/nether_portal_alt.png
Normal file
BIN
textures/nether_portal_alt.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 282 B |
Reference in New Issue
Block a user