15 Commits

Author SHA1 Message Date
02d062b9c9 Handle large protected depths
Portals will now fail to ignite instead of defaulting to a fallback depth of (starty - 256) when no unprotected target can be found.
The search depth is also increased from (starty - 256) to (starty - 646)

This is to properly handle large protected areas that are sometimes set up at spawn - see issue #26
2020-08-02 12:19:30 +10:00
e326a94266 Add nether.debug() (#28) 2020-07-26 15:07:39 +10:00
5b3b56ebec Respect protected areas when spawning remote portals (#27)
* respect protected areas when spawning remote portals

* Use portal_shape to determine area to check for natural/protected

* Log warning on deprecated function call
2020-07-26 14:34:06 +10:00
8769593d6f Reduce lava in biomes-based mapgen (#25)
The biomes-based mapgen was creating the same amount of lava as the original nether mapgen, but it doesn't have the same issue of chunk emerge order sometimes causing lava in the overdraw regions to get removed. This adjustment will hopefully balance that a little.

Also makes glowstone stalactite a bit rarer.

Adjusting lava ore scarcity in the biomes-mapgen doesn't cause forwards or backwards compatibilty issues with existing maps, likewise with schematic rarety like glowstone stalactites, so can afford to fiddle and tune.
2020-07-12 12:12:00 +10:00
5cb9e5fb27 Merge pull request #24 from Treer/bugfix/OutOfMemory
Fix Out Of Memory issue
2020-06-18 00:47:03 +10:00
ab4a031c1c Fix Out Of Memory issue
Also another naming convention update - it's been get_3d_map_flat() sin Jan 2018.
2020-06-18 00:41:07 +10:00
aac3ea6719 Merge pull request #13 from Treer/feature/biome_mapgen
Switch mapgen to using biomes (still with backwards compatibility)
2020-06-08 21:05:16 +10:00
f4255f5d1f Add biomes-based implementation of the mapgen, and decorations
Prevents the c++/native mapgen from placing ores or decorations (when useBiomes is true).
The biomes-based implementation is faster, more deterministic, and keeps its dungeons, but requires MT 5.1 features to work.

Also
 * Glowstone & Netherrack stalactites
 * Include the new decorations in mapgen_nobiomes
 * Decorate dungeons - Add windows and glowstone "chandeliers" to some rooms
 * Configurable Nether floor and ceiling depths
2020-06-08 21:00:58 +10:00
94222d44e0 Preserve original mapgen as a "nobiomes" version
For v6 mapgen which doesn't support biomes, or MT versions prior to 5.1 which don't support the necessary biome features
2020-06-05 19:36:30 +10:00
86105b4eb8 fix lighting bug in mapgen
Removes hard black edges appearing at emerge boundaries.
2020-06-05 19:32:14 +10:00
0e26770830 Merge pull request #18 from Treer/feature/portals-api-facedir-support
Add facedir support to Portals API
2020-06-01 22:08:15 +10:00
e5fbc2486b Allow a colorfacedir color to be specified for a portal's frame node
A color could also be specified via param2 in a portal schematic.
2020-05-31 19:48:26 +10:00
608d692401 Fix logic bug in parsing of nether_realm_enabled
The second argument of minetest.settings:get_bool is the default value (for when the configuration value is not set). Remove the superfluous `or nether.NETHER_REALM_ENABLED` which rendered the config option useless, because it always forced the variable to true.
2020-05-31 09:40:23 +02:00
f7ebd78614 New/extra portal animation
(new animation by Extex101)
The original texture is now "nether_portal_alt.png", and used to provide a "nether:portal_alt" node, which is used/demoed by the Surface-portal as the original animation still has a "it's full of stars" charm coming out when used for lighter coloured portals.
2020-05-31 09:31:16 +02:00
e113db1478 Add facedir support to Portals API
Portal shematics include facedir information for when new portals are spawned using frame nodes that are facedir or colorfacedir
2020-03-04 00:31:33 +11:00
13 changed files with 1177 additions and 264 deletions

View File

@ -23,6 +23,7 @@ read_globals = {
"PseudoRandom", "PseudoRandom",
"stairs", "stairs",
"stairsplus", "stairsplus",
"string.split",
table = { fields = { "copy", "getn" } }, table = { fields = { "copy", "getn" } },
"vector", "vector",
"VoxelArea", "VoxelArea",

View File

@ -43,5 +43,6 @@ SOFTWARE.
### [Attribution-ShareAlike 3.0 Unported (CC BY-SA 3.0)](http://creativecommons.org/licenses/by-sa/3.0/) ### [Attribution-ShareAlike 3.0 Unported (CC BY-SA 3.0)](http://creativecommons.org/licenses/by-sa/3.0/)
* `nether_rack.png`: Zeg9 * `nether_rack.png`: Zeg9
* `nether_glowstone.png`: BlockMen * `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. All other media: Copyright © 2013 PilzAdam, licensed under CC BY-SA 3.0 by PilzAdam.

View File

@ -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 local S
if minetest.get_translator ~= nil then if minetest.get_translator ~= nil then
S = minetest.get_translator("nether") S = minetest.get_translator("nether")
@ -38,25 +45,82 @@ nether = {}
nether.modname = minetest.get_current_modname() nether.modname = minetest.get_current_modname()
nether.path = minetest.get_modpath(nether.modname) nether.path = minetest.get_modpath(nether.modname)
nether.get_translator = S 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 -- Settings
nether.DEPTH = -5000 -- The y location of the Nether nether.DEPTH_CEILING = -5000 -- The y location of the Nether's celing
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.DEPTH_FLOOR = -11000 -- The y location of the Nether's floor
nether.PORTAL_BOOK_LOOT_WEIGHTING = 0.9 -- Likelyhood of finding the Book of Portals (guide) in dungeon chests. Set to 0 to disable. 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.NETHER_REALM_ENABLED = true -- Setting to false disables the Nether and Nether portal 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. -- 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.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.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 -- Load files
dofile(nether.path .. "/portal_api.lua") dofile(nether.path .. "/portal_api.lua")
dofile(nether.path .. "/nodes.lua") dofile(nether.path .. "/nodes.lua")
if nether.NETHER_REALM_ENABLED then 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 end
dofile(nether.path .. "/portal_examples.lua") 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), 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 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, 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 -- divide x and z by a factor of 8 to implement Nether fast-travel
local destination_pos = vector.divide(surface_anchorPos, nether.FASTTRAVEL_FACTOR) 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.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.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) -- 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 = 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 if existing_portal_location ~= nil then
return existing_portal_location, existing_portal_orientation return existing_portal_location, existing_portal_orientation
else else
local start_y = nether.DEPTH - math.random(500, 1500) -- Search starting altitude 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) destination_pos.y = nether.find_nether_ground_y(destination_pos.x, destination_pos.z, start_y, player_name)
return destination_pos return destination_pos
end end
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, -- 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 -- since find_surface_target_y() will be used by default, but Nether portals also scale position
-- to create fast-travel. -- 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 if existing_portal_location ~= nil then
return existing_portal_location, existing_portal_orientation return existing_portal_location, existing_portal_orientation
else 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 return destination_pos
end end
end, end,
@ -161,4 +225,4 @@ The expedition parties have found no diamonds or gold, and after an experienced
end end
}) })
end end

View File

@ -22,11 +22,164 @@
-- Parameters -- Parameters
local NETHER_DEPTH = nether.DEPTH local NETHER_CEILING = nether.DEPTH_CEILING
local NETHER_FLOOR = nether.DEPTH_FLOOR
local TCAVE = 0.6 local TCAVE = 0.6
local BLEND = 128 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 -- 3D noise
local np_cave = { local np_cave = {
@ -40,180 +193,302 @@ local np_cave = {
--flags = "" --flags = ""
} }
-- Buffers and objects we shouldn't recreate every on_generate
-- Stuff
local yblmax = NETHER_DEPTH - BLEND * 2
-- Mapgen
-- Initialize noise object, localise noise and data buffers
local nobj_cave = nil local nobj_cave = nil
local nbuf_cave = nil local nbuf_cave = {}
local dbuf = nil local dbuf = {}
local yblmin = NETHER_FLOOR + BLEND * 2
local yblmax = NETHER_CEILING - BLEND * 2
-- Content ids -- 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") -- Dungeon excavation functions
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") function build_dungeon_room_list(data, area)
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 result = {}
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") -- Unfortunately gennotify only returns dungeon rooms, not corridors.
local c_nethersand = minetest.get_content_id("nether:sand") -- We don't need to check for temples because only dungeons are generated in biomes
local c_netherbrick = minetest.get_content_id("nether:brick") -- that define their own dungeon nodes.
local c_netherrack = minetest.get_content_id("nether:rack") 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 -- On-generated function
minetest.register_on_generated(function(minp, maxp, seed) local function on_generated(minp, maxp, seed)
if minp.y > NETHER_DEPTH then
if minp.y > NETHER_CEILING or maxp.y < NETHER_FLOOR then
return return
end end
local x1 = maxp.x local vm, emerge_min, emerge_max = minetest.get_mapgen_object("voxelmanip")
local y1 = maxp.y local area = VoxelArea:new{MinEdge=emerge_min, MaxEdge=emerge_max}
local z1 = maxp.z
local x0 = minp.x
local y0 = minp.y
local z0 = minp.z
local vm, emin, emax = minetest.get_mapgen_object("voxelmanip")
local area = VoxelArea:new{MinEdge = emin, MaxEdge = emax}
local data = vm:get_data(dbuf) local data = vm:get_data(dbuf)
local x11 = emax.x -- Limits of mapchunk plus mapblock shell local x0, y0, z0 = minp.x, math_max(minp.y, NETHER_FLOOR), minp.z
local y11 = emax.y local x1, y1, z1 = maxp.x, math_min(maxp.y, NETHER_CEILING), maxp.z
local z11 = emax.z
local x00 = emin.x
local y00 = emin.y
local z00 = emin.z
local ystride = x1 - x0 + 1 local yCaveStride = x1 - x0 + 1
local zstride = ystride * ystride local zCaveStride = yCaveStride * yCaveStride
local chulens = {x = ystride, y = ystride, z = ystride} local chulens = {x = yCaveStride, y = yCaveStride, z = yCaveStride}
local minposxyz = {x = x0, y = y0, z = z0}
nobj_cave = nobj_cave or minetest.get_perlin_map(np_cave, chulens) 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 dungeonRooms = build_dungeon_room_list(data, area)
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 for y = y0, y1 do -- Y loop first to minimise tcave calculations
if in_chunk_yz and x == x0 then
-- Initial noisemap index local tcave = TCAVE
ni = (z - z0) * zstride + (y - y0) * ystride + 1 if y > yblmax then tcave = TCAVE + ((y - yblmax) / BLEND) ^ 2 end
end if y < yblmin then tcave = TCAVE + ((yblmin - y) / BLEND) ^ 2 end
local in_chunk_yzx = in_chunk_yz and x >= x0 and x <= x1 -- In mapchunk
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 local id = data[vi] -- Existing node
-- Cave air, cave liquids and dungeons are overgenerated,
-- convert these throughout mapchunk plus shell if nvals_cave[ni] > tcave then
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 data[vi] = c_air
-- Dungeons are preserved so we don't need elseif id == c_air or id == c_native_mapgen then
-- to check for cavern in the shell data[vi] = c_netherrack -- excavate_dungeons() will mostly reverse this inside dungeons
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 end
ni = ni + 1
vi = vi + 1 vi = vi + 1
end end
end 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_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:calc_lighting()
vm:update_liquids() vm:update_liquids()
vm:write_to_map() vm:write_to_map()
end) end
-- use knowledge of the nether mapgen algorithm to return a suitable ground level for placing a portal. -- 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 nobj_cave_point = minetest.get_perlin(np_cave)
local air = 0 -- Consecutive air nodes found 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}) local nval_cave = nobj_cave_point:get3d({x = target_x, y = y, z = target_z})
if nval_cave > TCAVE then -- Cavern if nval_cave > TCAVE then -- Cavern
air = air + 1 air = air + 1
else -- Not cavern, check if 4 nodes of space above else -- Not cavern, check if 4 nodes of space above
if air >= 4 then if air >= 4 then
local portal_y = y + 1
-- Check volume for non-natural nodes -- Check volume for non-natural nodes
local minp = {x = target_x - 1, y = y , z = target_z - 2} minp.y = minp_schem.y + portal_y
local maxp = {x = target_x + 2, y = y + 4, z = target_z + 2} maxp.y = maxp_schem.y + portal_y
if nether.volume_is_natural(minp, maxp) then if nether.volume_is_natural_and_unprotected(minp, maxp, player_name) then
return y + 1 return portal_y
else -- Restart search a little lower 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 end
else -- Not enough space, reset air to zero else -- Not enough space, reset air to zero
air = 0 air = 0
@ -221,5 +496,11 @@ function nether.find_nether_ground_y(target_x, target_z, start_y)
end end
end end
return start_y -- Fallback return math_max(start_y, NETHER_FLOOR + BLEND) -- Fallback
end 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
View 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
View 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

View File

@ -21,7 +21,7 @@
local S = nether.get_translator local S = nether.get_translator
-- Portal/wormhole node -- Portal/wormhole nodes
nether.register_wormhole_node("nether:portal", { nether.register_wormhole_node("nether:portal", {
description = S("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 -- Nether nodes

View File

@ -23,8 +23,9 @@
]]-- ]]--
local DEBUG = false -- setting DEBUG_IGNORE_MODSTORAGE true prevents portals from knowing where other
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 -- portals are, forcing find_realm_anchorpos() etc. to be executed every time.
local DEBUG_IGNORE_MODSTORAGE = false
nether.registered_portals = {} nether.registered_portals = {}
nether.registered_portals_count = 0 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) error(nether.modname .. " does not support Minetest versions earlier than 0.4.16", 0)
end 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 Positions
@ -113,9 +119,28 @@ metadata).
]] ]]
local __ = {name = "air", prob = 0} local facedir_up, facedir_north, facedir_south, facedir_east, facedir_west, facedir_down = 0, 4, 8, 12, 16, 20
local AA = {name = "air", prob = 255, force_place = true}
local OO = {name = "default:obsidian", prob = 255, force_place = true} 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. -- 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 -- 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, AA,AA,AA,AA,
AA,AA,AA,AA, AA,AA,AA,AA,
OO,OO,OO,OO, ON,OW,OE,ON2,
OO,AA,AA,OO, OU,AA,AA,OU,
OO,AA,AA,OO, OU,AA,AA,OU,
OO,AA,AA,OO, OU,AA,AA,OU,
OO,OO,OO,OO, 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,
__,__,__,__, __,__,__,__,
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 } -- 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,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,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,AA,AA,AA,AA,__, __,AA,AA,AA,AA,AA,__,
__,__,AA,AA,AA,__,__, __,__,AA,AA,AA,__,__,
__,__,OO,OO,OO,__,__, __,__,OW,OW,OW,__,__,
__,OO,AA,AA,AA,OO,__, __,ON,AA,AA,AA,ON2,__,
OO,AA,AA,AA,AA,AA,OO, OU,AA,AA,AA,AA,AA,OD,
OO,AA,AA,AA,AA,AA,OO, OU,AA,AA,AA,AA,AA,OD,
OO,AA,AA,AA,AA,AA,OO, OU,AA,AA,AA,AA,AA,OD,
__,OO,AA,AA,AA,OO,__, __,ON4,AA,AA,AA,ON3,__,
__,__,OO,OO,OO,__,__, __,__,OE,OE,OE,__,__,
__,__,__,__,__,__,__, __,__,__,__,__,__,__,
__,AA,AA,AA,AA,AA,__, __,AA,AA,AA,AA,AA,__,
AA,AA,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,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,__,__, __,__,AA,AA,AA,__,__,
__,__,__,__,__,__,__, __,__,__,__,__,__,__,
__,__,__,__,__,__,__, __,__,__,__,__,__,__,
} },
facedirNodes = facedirNodeList
} }
} -- End of PortalShape_Circular class } -- End of PortalShape_Circular class
@ -597,35 +624,36 @@ nether.PortalShape_Platform = {
size = {x = 5, y = 5, z = 5}, size = {x = 5, y = 5, z = 5},
data = { -- note that data is upside down data = { -- note that data is upside down
__,__,__,__,__, __,__,__,__,__,
OO,OO,OO,OO,OO, OU4,OW,OW,OW,OU3,
__,AA,AA,AA,__, __,AA,AA,AA,__,
__,AA,AA,AA,__, __,AA,AA,AA,__,
__,__,__,__,__, __,__,__,__,__,
__,OO,OO,OO,__, __,OU4,OW,OU3,__,
OO,AA,AA,AA,OO, ON,AA,AA,AA,OS,
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,__,
__,OO,OO,OO,__, __,ON,OD,OS,__,
OO,AA,AA,AA,OO, ON,AA,AA,AA,OS,
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,__,
__,OO,OO,OO,__, __,OU,OE,OU2,__,
OO,AA,AA,AA,OO, ON,AA,AA,AA,OS,
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,__,
__,__,__,__,__, __,__,__,__,__,
OO,OO,OO,OO,OO, OU,OE,OE,OE,OU2,
__,AA,AA,AA,__, __,AA,AA,AA,__,
__,AA,AA,AA,__, __,AA,AA,AA,__,
__,__,__,__,__, __,__,__,__,__,
} },
facedirNodes = facedirNodeList
} }
} -- End of PortalShape_Platform class } -- End of PortalShape_Platform class
@ -640,8 +668,8 @@ nether.PortalShape_Platform = {
-- Portal implementation functions -- -- Portal implementation functions --
-- =============================== -- -- =============================== --
local debugf = nether.debug
local ignition_item_name local ignition_item_name
local S = nether.get_translator
local mod_storage = minetest.get_mod_storage() local mod_storage = minetest.get_mod_storage()
local meseconsAvailable = minetest.get_modpath("mesecon") ~= nil and minetest.global_exists("mesecon") local meseconsAvailable = minetest.get_modpath("mesecon") ~= nil and minetest.global_exists("mesecon")
local book_added_as_treasure = false local book_added_as_treasure = false
@ -799,7 +827,7 @@ end
local function store_portal_location_info(portal_name, anchorPos, orientation, ignited) local function store_portal_location_info(portal_name, anchorPos, orientation, ignited)
if not DEBUG_IGNORE_MODSTORAGE then if not DEBUG_IGNORE_MODSTORAGE then
local key = minetest.pos_to_string(anchorPos) .. " is " .. portal_name 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( mod_storage:set_string(
key, key,
minetest.serialize({orientation = orientation, active = ignited}) minetest.serialize({orientation = orientation, active = ignited})
@ -812,7 +840,7 @@ end
local function remove_portal_location_info(portal_name, anchorPos) local function remove_portal_location_info(portal_name, anchorPos)
if not DEBUG_IGNORE_MODSTORAGE then if not DEBUG_IGNORE_MODSTORAGE then
local key = minetest.pos_to_string(anchorPos) .. " is " .. portal_name 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, "") mod_storage:set_string(key, "")
end end
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)) local distance = math.hypot(y * y_factor, math.hypot(x, z))
if distance <= distance_limit or distance_limit < 0 then if distance <= distance_limit or distance_limit < 0 then
local info = minetest.deserialize(value) or {} 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.anchorPos = found_anchorPos
info.distance = distance info.distance = distance
result[distance] = info result[distance] = info
@ -902,14 +930,14 @@ end
function extinguish_portal(pos, node_name, frame_was_destroyed) function extinguish_portal(pos, node_name, frame_was_destroyed)
-- mesecons seems to invoke action_off() 6 times every time you place a block? -- 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 meta = minetest.get_meta(pos)
local p1 = minetest.string_to_pos(meta:get_string("p1")) local p1 = minetest.string_to_pos(meta:get_string("p1"))
local p2 = minetest.string_to_pos(meta:get_string("p2")) local p2 = minetest.string_to_pos(meta:get_string("p2"))
local target = minetest.string_to_pos(meta:get_string("target")) local target = minetest.string_to_pos(meta:get_string("target"))
if p1 == nil or p2 == nil then 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 return false
end end
@ -961,7 +989,7 @@ function extinguish_portal(pos, node_name, frame_was_destroyed)
end end
if target ~= nil then 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) extinguish_portal(target, node_name)
end end
@ -978,7 +1006,8 @@ end
-- Note: will extinguish any portal using the same nodes that are being set -- 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) 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. -- 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 -- 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 if existing_p1 ~= "" then
local existing_p2 = meta:get_string("p2") local existing_p2 = meta:get_string("p2")
if existing_p1 ~= p1_string or existing_p2 ~= p2_string then 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 -- 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 -- 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) 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 -- area isn't loaded, force loading/emerge of check area
minetest.get_voxel_manip():read_from_map(check_pos, check_pos) minetest.get_voxel_manip():read_from_map(check_pos, check_pos)
foundName = minetest.get_node(check_pos).name 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 if foundName ~= frame_node_name then
nodes_are_valid = false nodes_are_valid = false
@ -1137,8 +1166,29 @@ local function is_within_portal_frame(portal_definition, pos)
end 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) 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( minetest.place_schematic(
portal_definition.shape.get_schematicPos_from_anchorPos(anchorPos, orientation), portal_definition.shape.get_schematicPos_from_anchorPos(anchorPos, orientation),
portal_definition.shape.schematic, 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 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) set_portal_metadata(portal_definition, anchorPos, orientation, destination_wormholePos)
@ -1173,13 +1223,13 @@ end
-- Make portals immortal for ~20 seconds after creation -- Make portals immortal for ~20 seconds after creation
local function remote_portal_checkup(elapsed, portal_definition, anchorPos, orientation, destination_wormholePos) 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 wormholePos = portal_definition.shape.get_wormholePos_from_anchorPos(anchorPos, orientation)
local wormhole_node = minetest.get_node_or_nil(wormholePos) local wormhole_node = minetest.get_node_or_nil(wormholePos)
local portalFound, portalLit = false, false local portalFound, portalLit = false, false
if wormhole_node ~= nil and wormhole_node.name == portal_definition.wormhole_node_name then 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 -- a wormhole node was there, but check the whole frame is intact
portalFound, portalLit = is_portal_at_anchorPos(portal_definition, anchorPos, orientation, false) portalFound, portalLit = is_portal_at_anchorPos(portal_definition, anchorPos, orientation, false)
end end
@ -1188,7 +1238,7 @@ local function remote_portal_checkup(elapsed, portal_definition, anchorPos, orie
-- ruh roh -- ruh roh
local message = "Newly created portal at " .. minetest.pos_to_string(anchorPos) .. " was overwritten. Attempting to recreate. Issue spotted after " .. elapsed .. " seconds" 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) 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. -- 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) build_portal(portal_definition, anchorPos, orientation, destination_wormholePos)
@ -1216,7 +1266,7 @@ end
-- specified if an existing portal was already found there. -- specified if an existing portal was already found there.
local function locate_or_build_portal(portal_definition, suggested_wormholePos, suggested_orientation, destination_wormholePos) 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_anchorPos;
local result_orientation; 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 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 -- 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. -- 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 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) extinguish_portal(found_anchorPos, portal_definition.frame_node_name, false)
end end
else 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 end
-- ignite the portal -- ignite the portal
set_portal_metadata_and_ignite(portal_definition, result_anchorPos, result_orientation, destination_wormholePos) 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 -- 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 -- 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 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 -- 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) 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 -- 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) local anchorPos, orientation, is_ignited = is_within_portal_frame(portal_definition, ignition_pos)
if anchorPos == nil then 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 continue = true -- no portal is here, but perhaps there's more than one portal type we need to search for
elseif is_ignited then elseif is_ignited then
-- Found a portal, check its metadata and timer is healthy. -- 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 -- 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. -- on_destruct - perhaps by an ABM, then replaced - presumably by a player.
-- allowing reigniting will repair the portal -- 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 repair = true
else 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. -- 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 -- 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) -- (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))) 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 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) timer:start(1)
end end
end end
@ -1307,23 +1358,30 @@ local function ignite_portal(ignition_pos, ignition_node_name)
end end
if continue == false then 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 local destination_anchorPos, destination_orientation
if portal_definition.is_within_realm(ignition_pos) then 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 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 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 destination_anchorPos == nil or destination_anchorPos.y == nil then
if DEBUG then minetest.chat_send_all("No portal destination available here!") end -- 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 return false
else else
local destination_wormholePos = portal_definition.shape.get_wormholePos_from_anchorPos(destination_anchorPos, destination_orientation) 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 -- ignition/BURN_BABY_BURN
set_portal_metadata_and_ignite(portal_definition, anchorPos, orientation, destination_wormholePos) 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 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")) 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 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 return -- the player already teleported, and is now standing in a different portal
end end
@ -1370,7 +1428,7 @@ local function ensure_remote_portal_then_teleport(playerName, portal_definition,
if dest_wormhole_node == nil then if dest_wormhole_node == nil then
-- area not emerged yet, delay and retry -- 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) minetest.after(1, ensure_remote_portal_then_teleport, playerName, portal_definition, local_anchorPos, local_orientation, destination_wormholePos)
else else
local local_wormholePos = portal_definition.shape.get_wormholePos_from_anchorPos(local_anchorPos, local_orientation) 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 remoteMeta = minetest.get_meta(destination_wormholePos)
local remoteTarget = minetest.string_to_pos(remoteMeta:get_string("target")) local remoteTarget = minetest.string_to_pos(remoteMeta:get_string("target"))
if remoteTarget == nil then 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 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) extinguish_portal(remoteTarget, portal_definition.frame_node_name, false)
set_portal_metadata_and_ignite( set_portal_metadata_and_ignite(
portal_definition, portal_definition,
@ -1399,7 +1457,7 @@ local function ensure_remote_portal_then_teleport(playerName, portal_definition,
) )
end 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 -- play the teleport sound
if portal_definition.sounds.teleport ~= nil then 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. -- which will leave a confused player.
-- I don't think this is worth preventing, but I document it incase someone describes entering a portal -- I don't think this is worth preventing, but I document it incase someone describes entering a portal
-- and then the portal turning off. -- 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_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) 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 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) 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) minetest.log("error", message)
if DEBUG then minetest.chat_send_all("!ERROR! - " .. message) end debugf("!ERROR! - " .. message)
else else
destination_wormholePos = new_dest_wormholePos 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( set_portal_metadata(
portal_definition, 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.replaced_by_portalapi.mesecons = extended_node_def.mesecons
extended_node_def.mesecons = {effector = { extended_node_def.mesecons = {effector = {
action_on = function (pos, node) action_on = function (pos, node)
if DEBUG then minetest.chat_send_all("portal frame material: mesecons action ON") end debugf("portal frame material: mesecons action ON")
ignite_portal(pos, node.name) ignite_portal(pos, nil, node.name)
end, end,
action_off = function (pos, node) 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) extinguish_portal(pos, node.name, false)
end end
}} }}
extended_node_def.replaced_by_portalapi.on_destruct = extended_node_def.on_destruct extended_node_def.replaced_by_portalapi.on_destruct = extended_node_def.on_destruct
extended_node_def.on_destruct = function(pos) 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) extinguish_portal(pos, frame_node_name, true)
end end
extended_node_def.replaced_by_portalapi.on_blast = extended_node_def.on_blast extended_node_def.replaced_by_portalapi.on_blast = extended_node_def.on_blast
extended_node_def.on_blast = function(pos, intensity) 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) 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 if extended_node_def.replaced_by_portalapi.on_blast ~= nil then
extended_node_def.replaced_by_portalapi.on_blast(pos, intensity) 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.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.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") 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 -- todo
return result return result
@ -1888,9 +1950,9 @@ minetest.register_lbm({
local timer = minetest.get_node_timer(timerPos) local timer = minetest.get_node_timer(timerPos)
if timer ~= nil then if timer ~= nil then
timer:start(1) timer:start(1)
if DEBUG then minetest.chat_send_all("LBM started portal timer " .. minetest.pos_to_string(timerPos)) end debugf("LBM started portal timer %s", timerPos)
elseif DEBUG then else
minetest.chat_send_all("get_node_timer" .. minetest.pos_to_string(timerPos) .. " returned null") debugf("get_node_timer%s returned null", timerPos)
end end
end end
end end
@ -1916,7 +1978,7 @@ local wormhole_nodedef_default = {
type = "vertical_frames", type = "vertical_frames",
aspect_w = 16, aspect_w = 16,
aspect_h = 16, aspect_h = 16,
length = 0.5, length = 0.9,
}, },
}, },
{ {
@ -1925,7 +1987,7 @@ local wormhole_nodedef_default = {
type = "vertical_frames", type = "vertical_frames",
aspect_w = 16, aspect_w = 16,
aspect_h = 16, aspect_h = 16,
length = 0.5, length = 0.9,
}, },
}, },
}, },
@ -2034,7 +2096,7 @@ function nether.register_portal(name, portaldef)
end end
if portaldef.find_surface_anchorPos == nil then -- default to using find_surface_target_y() 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 destination_pos = {x = pos.x, y = 0, z = pos.z}
local existing_portal_location, existing_portal_orientation = local existing_portal_location, existing_portal_orientation =
@ -2042,7 +2104,7 @@ function nether.register_portal(name, portaldef)
if existing_portal_location ~= nil then if existing_portal_location ~= nil then
return existing_portal_location, existing_portal_orientation return existing_portal_location, existing_portal_orientation
else 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 return destination_pos
end end
end end
@ -2106,10 +2168,10 @@ end
function nether.register_portal_ignition_item(item_name, ignition_failure_sound) function nether.register_portal_ignition_item(item_name, ignition_failure_sound)
minetest.override_item(item_name, { minetest.override_item(item_name, {
on_place = function(stack, _, pt) on_place = function(stack, placer, pt)
local done = false local done = false
if pt.under and nether.is_frame_node[minetest.get_node(pt.under).name] then 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 if done and not minetest.settings:get_bool("creative_mode") then
stack:take_item() stack:take_item()
end end
@ -2128,24 +2190,24 @@ end
-- use this when determining where to spawn a portal, to avoid overwriting player builds -- 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. -- 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) -- (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_air = minetest.get_content_id("air")
local c_ignore = minetest.get_content_id("ignore") local c_ignore = minetest.get_content_id("ignore")
local vm = minetest.get_voxel_manip() local vm = minetest.get_voxel_manip()
local pos1 = {x = minp.x, y = minp.y, z = minp.z} local emin, emax = vm:read_from_map(minp, maxp)
local pos2 = {x = maxp.x, y = maxp.y, z = maxp.z}
local emin, emax = vm:read_from_map(pos1, pos2)
local area = VoxelArea:new({MinEdge = emin, MaxEdge = emax}) local area = VoxelArea:new({MinEdge = emin, MaxEdge = emax})
local data = vm:get_data() local data = vm:get_data()
for z = pos1.z, pos2.z do for z = minp.z, maxp.z do
for y = pos1.y, pos2.y do for y = minp.y, maxp.y do
local vi = area:index(pos1.x, y, z) local vi = area:index(minp.x, y, z)
for x = pos1.x, pos2.x do for x = minp.x, maxp.x do
local id = data[vi] -- Existing node 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 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 name = minetest.get_name_from_content_id(id)
local nodedef = minetest.registered_nodes[name] local nodedef = minetest.registered_nodes[name]
@ -2153,7 +2215,7 @@ function nether.volume_is_natural(minp, maxp)
-- trees are natural but not "ground content" -- trees are natural but not "ground content"
local node_groups = nodedef.groups 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 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 return false
end end
end end
@ -2163,13 +2225,73 @@ function nether.volume_is_natural(minp, maxp)
end end
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 return true
end 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 -- 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. -- portal_name is optional, providing it allows existing portals on the surface to be reused, and
function nether.find_surface_target_y(target_x, target_z, portal_name) -- 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()") 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
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 -- Check volume for non-natural nodes
local minp = {x = target_x - 1, y = y - 1, z = target_z - 2} minp.y = minp_schem.y + y
local maxp = {x = target_x + 2, y = y + 3, z = target_z + 2} maxp.y = maxp_schem.y + y
if nether.volume_is_natural(minp, maxp) then if nether.volume_is_natural_and_unprotected(minp, maxp, player_name) then
return y return y
elseif portal_name ~= nil and nether.registered_portals[portal_name] ~= nil then elseif portal_name ~= nil and nether.registered_portals[portal_name] ~= nil then
-- players have built here - don't grief. -- players have built here - don't grief.
-- but reigniting existing portals in portal rooms is fine - desirable even. -- 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}) 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 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 return y
end end
end end
y = y + searchstep
searchstep = searchstep - 2
end end
return start_y - 256 -- Fallback return nil -- Portal ignition failure. Possibly due to a large protected area.
end end
@ -2239,7 +2372,7 @@ function nether.find_nearest_working_portal(portal_name, anchorPos, distance_lim
for _, dist in ipairs(dist_list) do for _, dist in ipairs(dist_list) do
local portal_info = contenders[dist] 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 -- 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 -- 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 if portalFound then
return portal_info.anchorPos, portal_info.orientation return portal_info.anchorPos, portal_info.orientation
else 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 -- The portal at that location must have been destroyed
remove_portal_location_info(portal_name, portal_info.anchorPos) remove_portal_location_info(portal_name, portal_info.anchorPos)
end end

View File

@ -49,17 +49,24 @@ surface.
Helper functions 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 * 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 player builds. It checks the area for any nodes that aren't ground or
trees. trees.
Water will fail this test, unless it is unemerged. 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 * `nether.find_surface_target_y(target_x, target_z, portal_name, player_name)`:
suitable anchorPos returns a suitable anchorPos
* Can be used when implementing custom find_surface_anchorPos() functions * Can be used when implementing custom find_surface_anchorPos() functions
* portal_name is optional, providing it allows existing portals on the * portal_name is optional, providing it allows existing portals on the
surface to be reused. 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 * `nether.find_nearest_working_portal(portal_name, anchorPos, distance_limit, y_factor)`: returns
(anchorPos, orientation), or nil if no portal was found within the (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 -- Required. For best results, have your portal constructed of a
-- material nobody else is using. -- 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, shape = nether.PortalShape_Traditional,
-- Optional. -- Optional.
-- Shapes available are: -- Shapes available are:
@ -127,6 +140,9 @@ Used by `nether.register_portal`.
-- Optional. Allows a custom wormhole node to be specified. -- Optional. Allows a custom wormhole node to be specified.
-- Useful if you want the portals to have a different post_effect_color -- Useful if you want the portals to have a different post_effect_color
-- or texture. -- or texture.
-- The Nether mod provides:
-- "nether:portal" (default)
-- "nether:portal_alt"
wormhole_node_color = 0, wormhole_node_color = 0,
-- Optional. Defaults to 0/magenta. -- 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 -- Ideally implementations are fast, as this function can be used to
-- sift through a list of portals. -- 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 -- Required. Return a position in the realm that a portal created at
-- surface_anchorPos will link to. -- surface_anchorPos will link to.
-- Return an anchorPos or (anchorPos, orientation) -- 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 -- If the location of an existing portal is returned then include the
-- orientation, otherwise the existing portal could be overwritten by -- orientation, otherwise the existing portal could be overwritten by
-- a new one with the orientation of the surface portal. -- 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 -- Optional. If you don't implement this then a position near the
-- surface will be picked. -- surface will be picked.
-- Return an anchorPos or (anchorPos, orientation) -- 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 -- If the location of an existing portal is returned then include the
-- orientation, otherwise the existing portal could be overwritten by -- orientation, otherwise the existing portal could be overwritten by
-- a new one with the orientation of the realm portal. -- 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), on_run_wormhole = function(portalDef, anochorPos, orientation),
-- invoked once per second per portal -- invoked once per second per portal

View File

@ -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 return pos.y > FLOATLAND_LEVEL - 200
end, 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 -- 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} 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", { nether.register_portal("surface_portal", {
shape = nether.PortalShape_Circular, shape = nether.PortalShape_Circular,
frame_node_name = "default:tinblock", frame_node_name = "default:tinblock",
wormhole_node_name = "nether:portal_alt",
wormhole_node_color = 4, -- 4 is cyan wormhole_node_color = 4, -- 4 is cyan
title = S("Surface Portal"), 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. 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 return true
end, 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 -- 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") minetest.log("error" , "find_realm_anchorPos called for surface portal")
return {x=0, y=0, z=0} return {x=0, y=0, z=0}
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, -- 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 -- 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. -- 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 end
local destination_pos = {x = target_x + adj_x, y = 0, z = target_z + adj_z} 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 return destination_pos
end end

View File

@ -16,4 +16,10 @@ nether_realm_enabled (Enable Nether realm & portal) bool true
nether_enable_portal_example_floatlands (Enable example portal: Floatlands) bool false nether_enable_portal_example_floatlands (Enable example portal: Floatlands) bool false
# Enables the Surface-travel portal api code example # 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 282 B