2017-12-20 06:03:58 +01:00
--[[
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 .
] ] --
2016-05-25 08:59:47 +02:00
-- Parameters
2013-04-29 20:33:09 +02:00
local NETHER_DEPTH = - 5000
2016-05-30 21:55:04 +02:00
local TCAVE = 0.6
local BLEND = 128
2019-06-29 05:18:18 +02:00
local DEBUG = true
2016-05-30 21:55:04 +02:00
-- 3D noise
local np_cave = {
offset = 0 ,
scale = 1 ,
spread = { x = 384 , y = 128 , z = 384 } , -- squashed 3:1
seed = 59033 ,
octaves = 5 ,
2016-12-29 00:03:33 +01:00
persist = 0.7 ,
lacunarity = 2.0 ,
--flags = ""
2016-05-30 21:55:04 +02:00
}
-- Stuff
local yblmax = NETHER_DEPTH - BLEND * 2
2013-04-29 20:33:09 +02:00
2016-05-25 08:59:47 +02:00
2019-06-29 05:18:18 +02:00
netherportal = { } -- portal API
2016-05-25 08:59:47 +02:00
-- Functions
2013-04-29 20:33:09 +02:00
2016-05-25 08:59:47 +02:00
2019-06-29 05:18:18 +02:00
--[[
AnchorPos and WormholdPos are node locations in a portal defined as follows :
.
+--------+--------+--------+--------+
| | Frame | |
| | | | |
+--------+--------+--------+--------+
| | | |
| | | |
+--------+ + +--------+
| | Wormhole | |
| | | |
+--------+ + +--------+
| | Wormhole | |
| | Pos | |
+--------+--------+--------+--------+
| Anchor | | | |
| Pos | | | |
+--------+--------+--------+--------+
+ X / East or + Z / North ----->
A better location for AnchorPos would be directly under WormholePos , as it ' s more centered
and you don ' t need to know the portal ' s orientation to find AnchorPos from the WormholePos
or vice - versa , however AnchorPos is in the bottom / south / west - corner to keep compatibility
with earlier versions of this mod ( which only records portal corners p1 & p2 in the node metadata ) .
Orientation is 0 or 90 , 0 meaning a portal that faces north / south - i.e . obsidian running
east / west .
] ]
-- This object defines a portal's shape, segregating the shape logic code from portal physics.
-- You can create a new "PortalShape" definition object which implements the same
-- functions if you wish to register a custom shaped portal in register_portal().
-- Since it's symmetric, this PortalShape definition has only implemented orientations of 0 and 90
local TraditionalPortalShape = {
-- todo: use size to update is_portal_frame
size = vector.new ( 4 , 5 , 1 ) , -- size of the portal, and not necessarily the size of the schematic, which may clear area around the portal.
schematic_filename = minetest.get_modpath ( " nether " ) .. " /schematics/nether_portal.mts " ,
-- returns the coords for minetest.place_schematic() that will place the schematic on the anchorPos
get_schematicPos_from_anchorPos = function ( anchorPos , orientation )
assert ( orientation , " no orientation passed " )
if orientation == 0 then
return { x = anchorPos.x , y = anchorPos.y , z = anchorPos.z - 2 }
else
return { x = anchorPos.x - 2 , y = anchorPos.y , z = anchorPos.z }
end
end ,
get_wormholePos_from_anchorPos = function ( anchorPos , orientation )
assert ( orientation , " no orientation passed " )
if orientation == 0 then
return { x = anchorPos.x + 1 , y = anchorPos.y + 1 , z = anchorPos.z }
else
return { x = anchorPos.x , y = anchorPos.y + 1 , z = anchorPos.z + 1 }
end
end ,
get_anchorPos_from_wormholePos = function ( wormholePos , orientation )
assert ( orientation , " no orientation passed " )
if orientation == 0 then
return { x = wormholePos.x - 1 , y = wormholePos.y - 1 , z = wormholePos.z }
else
return { x = wormholePos.x , y = wormholePos.y - 1 , z = wormholePos.z - 1 }
end
end ,
apply_func_to_frame_nodes = function ( anchorPos , orientation , func )
local shortCircuited
if orientation == 0 then
-- use short-circuiting of boolean evaluation to allow func() to cause an abort by returning true
shortCircuited =
func ( { x = anchorPos.x + 0 , y = anchorPos.y , z = anchorPos.z } ) or
func ( { x = anchorPos.x + 1 , y = anchorPos.y , z = anchorPos.z } ) or
func ( { x = anchorPos.x + 2 , y = anchorPos.y , z = anchorPos.z } ) or
func ( { x = anchorPos.x + 3 , y = anchorPos.y , z = anchorPos.z } ) or
func ( { x = anchorPos.x + 0 , y = anchorPos.y + 4 , z = anchorPos.z } ) or
func ( { x = anchorPos.x + 1 , y = anchorPos.y + 4 , z = anchorPos.z } ) or
func ( { x = anchorPos.x + 2 , y = anchorPos.y + 4 , z = anchorPos.z } ) or
func ( { x = anchorPos.x + 3 , y = anchorPos.y + 4 , z = anchorPos.z } ) or
func ( { x = anchorPos.x , y = anchorPos.y + 1 , z = anchorPos.z } ) or
func ( { x = anchorPos.x , y = anchorPos.y + 2 , z = anchorPos.z } ) or
func ( { x = anchorPos.x , y = anchorPos.y + 3 , z = anchorPos.z } ) or
func ( { x = anchorPos.x + 3 , y = anchorPos.y + 1 , z = anchorPos.z } ) or
func ( { x = anchorPos.x + 3 , y = anchorPos.y + 2 , z = anchorPos.z } ) or
func ( { x = anchorPos.x + 3 , y = anchorPos.y + 3 , z = anchorPos.z } )
else
shortCircuited =
func ( { x = anchorPos.x , y = anchorPos.y , z = anchorPos.z + 0 } ) or
func ( { x = anchorPos.x , y = anchorPos.y , z = anchorPos.z + 1 } ) or
func ( { x = anchorPos.x , y = anchorPos.y , z = anchorPos.z + 2 } ) or
func ( { x = anchorPos.x , y = anchorPos.y , z = anchorPos.z + 3 } ) or
func ( { x = anchorPos.x , y = anchorPos.y + 4 , z = anchorPos.z + 0 } ) or
func ( { x = anchorPos.x , y = anchorPos.y + 4 , z = anchorPos.z + 1 } ) or
func ( { x = anchorPos.x , y = anchorPos.y + 4 , z = anchorPos.z + 2 } ) or
func ( { x = anchorPos.x , y = anchorPos.y + 4 , z = anchorPos.z + 3 } ) or
func ( { x = anchorPos.x , y = anchorPos.y + 1 , z = anchorPos.z } ) or
func ( { x = anchorPos.x , y = anchorPos.y + 2 , z = anchorPos.z } ) or
func ( { x = anchorPos.x , y = anchorPos.y + 3 , z = anchorPos.z } ) or
func ( { x = anchorPos.x , y = anchorPos.y + 1 , z = anchorPos.z + 3 } ) or
func ( { x = anchorPos.x , y = anchorPos.y + 2 , z = anchorPos.z + 3 } ) or
func ( { x = anchorPos.x , y = anchorPos.y + 3 , z = anchorPos.z + 3 } )
end
return not shortCircuited
end ,
apply_func_to_wormhole_nodes = function ( anchorPos , orientation , func )
local shortCircuited
if orientation == 0 then
local wormholePos = { x = anchorPos.x + 1 , y = anchorPos.y + 1 , z = anchorPos.z }
-- use short-circuiting of boolean evaluation to allow func() to cause an abort by returning true
shortCircuited =
func ( { x = wormholePos.x + 0 , y = wormholePos.y + 0 , z = wormholePos.z } ) or
func ( { x = wormholePos.x + 1 , y = wormholePos.y + 0 , z = wormholePos.z } ) or
func ( { x = wormholePos.x + 0 , y = wormholePos.y + 1 , z = wormholePos.z } ) or
func ( { x = wormholePos.x + 1 , y = wormholePos.y + 1 , z = wormholePos.z } ) or
func ( { x = wormholePos.x + 0 , y = wormholePos.y + 2 , z = wormholePos.z } ) or
func ( { x = wormholePos.x + 1 , y = wormholePos.y + 2 , z = wormholePos.z } )
else
local wormholePos = { x = anchorPos.x , y = anchorPos.y + 1 , z = anchorPos.z + 1 }
shortCircuited =
func ( { x = wormholePos.x , y = wormholePos.y + 0 , z = wormholePos.z + 0 } ) or
func ( { x = wormholePos.x , y = wormholePos.y + 0 , z = wormholePos.z + 1 } ) or
func ( { x = wormholePos.x , y = wormholePos.y + 1 , z = wormholePos.z + 0 } ) or
func ( { x = wormholePos.x , y = wormholePos.y + 1 , z = wormholePos.z + 1 } ) or
func ( { x = wormholePos.x , y = wormholePos.y + 2 , z = wormholePos.z + 0 } ) or
func ( { x = wormholePos.x , y = wormholePos.y + 2 , z = wormholePos.z + 1 } )
end
return not shortCircuited
end ,
-- Check for whether the portal is blocked in, and if so then provide a safe way
-- on one side for the player to step out of the portal. Suggest including a roof
-- incase the portal was blocked with lava flowing from above.
disable_portal_trap = function ( anchorPos , orientation )
assert ( orientation , " no orientation passed " )
-- Not implemented yet. It may not need to be implemented because if you
-- wait in a portal long enough you teleport again. So a trap portal would have to link
-- to one of two blocked-in portals which link to each other - which is possible, but
-- quite extreme.
end
}
local registered_portals = {
[ " default:obsidian " ] = {
shape = TraditionalPortalShape ,
wormhole_node_name = " nether:portal " ,
frame_node_name = " default:obsidian " ,
find_realm_anchorPos = function ( pos )
end ,
find_surface_anchorPos = function ( pos )
end
}
}
-- p1 and p2 are used to keep maps backwards compatible with earlier versions of this mod.
2019-06-29 15:08:31 +02:00
-- p1 is the bottom/west/south corner of the portal, and p2 is the opposite corner, together
-- they define the bounding volume for the portal.
2019-06-29 05:18:18 +02:00
local function get_p1_and_p2_from_anchorPos ( portal_shape , anchorPos , orientation )
assert ( orientation , " no orientation passed " )
local p1 = anchorPos
local p2
if orientation == 0 then
p2 = { x = p1.x + portal_shape.size . x - 1 , y = p1.y + portal_shape.size . y - 1 , z = p1.z }
else
p2 = { x = p1.x , y = p1.y + portal_shape.size . y - 1 , z = p1.z + portal_shape.size . x - 1 }
end
return p1 , p2
end
-- orientation is the rotation degrees passed to place_schematic: 0, 90, 180, or 270
local function get_param2_from_orientation ( param2 , orientation )
return orientation / 90
end
local function get_orientation_from_param2 ( param2 )
return param2 * 90
end
local function set_portal_metadata ( portal_definition , anchorPos , orientation , destination_wormholePos , ignite )
-- p1 and p2 are used here to keep maps backwards compatible with earlier versions of this mod
-- (p2's value is the opposite corner of the portal frame to p1, according to the fixed portal shape of earlier versions of this mod)
local p1 , p2 = get_p1_and_p2_from_anchorPos ( portal_definition.shape , anchorPos , orientation )
local param2 = get_param2_from_orientation ( 0 , orientation )
local updateFunc = function ( pos )
if ignite and minetest.get_node ( pos ) . name == " air " then
minetest.set_node ( pos , { name = portal_definition.wormhole_node_name , param2 = param2 } )
end
local meta = minetest.get_meta ( pos )
meta : set_string ( " p1 " , minetest.pos_to_string ( p1 ) )
meta : set_string ( " p2 " , minetest.pos_to_string ( p2 ) )
meta : set_string ( " target " , minetest.pos_to_string ( destination_wormholePos ) )
end
portal_definition.shape . apply_func_to_frame_nodes ( anchorPos , orientation , updateFunc )
portal_definition.shape . apply_func_to_wormhole_nodes ( anchorPos , orientation , updateFunc )
end
local function set_portal_metadata_and_ignite ( portal_definition , anchorPos , orientation , destination_wormholePos )
set_portal_metadata ( portal_definition , anchorPos , orientation , destination_wormholePos , true )
end
-- Checks pos, and if it's part of a portal or portal frame then three values are returned: anchorPos, orientation, is_ignited
-- where orientation is 0 or 90 (0 meaning a portal that faces north/south - i.e. obsidian running east/west)
local function is_portal_frame ( portal_definition , pos )
local nodes_are_valid -- using closures as a way for the functions to return extra information - by setting this variable
local portal_is_ignited -- using closures as a way for the functions to return extra information - by setting this variable
local frame_node_name = portal_definition.frame_node_name
local check_frame_Func = function ( check_pos )
if minetest.get_node ( check_pos ) . name ~= frame_node_name then
nodes_are_valid = false
return true -- short-circuit the search
end
end
local wormhole_node_name = portal_definition.wormhole_node_name
local check_wormhole_Func = function ( check_pos )
local node_name = minetest.get_node ( check_pos ) . name
if node_name ~= wormhole_node_name then
portal_is_ignited = false ;
if node_name ~= " air " then
nodes_are_valid = false
return true -- short-circuit the search
end
end
end
-- this function returns two bools: portal found, portal is lit
local is_portal_at_anchorPos = function ( anchorPos , orientation )
nodes_are_valid = true
portal_is_ignited = true
portal_definition.shape . apply_func_to_frame_nodes ( anchorPos , orientation , check_frame_Func )
if nodes_are_valid then
-- a valid frame exists at anchorPos, check the wormhole is either ignited or unobstructed
portal_definition.shape . apply_func_to_wormhole_nodes ( anchorPos , orientation , check_wormhole_Func )
end
return nodes_are_valid , portal_is_ignited and nodes_are_valid -- returns two bools: portal was found, portal is lit
end
local width_minus_1 = portal_definition.shape . size.x - 1
local height_minus_1 = portal_definition.shape . size.y - 1
local depth_minus_1 = portal_definition.shape . size.z - 1
for d = - depth_minus_1 , depth_minus_1 do
for w = - width_minus_1 , width_minus_1 do
for y = - height_minus_1 , height_minus_1 do
local testAnchorPos_x = { x = pos.x + w , y = pos.y + y , z = pos.z + d }
local portal_found , portal_lit = is_portal_at_anchorPos ( testAnchorPos_x , 0 )
2016-05-25 08:59:47 +02:00
2019-06-29 05:18:18 +02:00
if portal_found then
return testAnchorPos_x , 0 , portal_lit
else
-- try orthogonal orientation
local testForAnchorPos_z = { x = pos.x + d , y = pos.y + y , z = pos.z + w }
portal_found , portal_lit = is_portal_at_anchorPos ( testForAnchorPos_z , 90 )
if portal_found then return testForAnchorPos_z , 90 , portal_lit end
end
end
end
2016-05-31 20:13:56 +02:00
end
2019-06-29 05:18:18 +02:00
end
local function build_portal ( portal_definition , anchorPos , orientation , destination_wormholePos )
minetest.place_schematic (
portal_definition.shape . get_schematicPos_from_anchorPos ( anchorPos , orientation ) ,
portal_definition.shape . schematic_filename ,
orientation ,
nil ,
true
)
if DEBUG then minetest.chat_send_all ( " Placed portal schematic at " .. minetest.pos_to_string ( portal_definition.shape . get_schematicPos_from_anchorPos ( anchorPos , orientation ) ) .. " , orientation " .. orientation ) end
set_portal_metadata ( portal_definition , anchorPos , orientation , destination_wormholePos )
end
2019-06-29 15:08:31 +02:00
-- Used to find or build the remote twin after a portal is opened.
2019-06-29 05:18:18 +02:00
-- If a portal is found that is already lit then the destination_wormholePos argument is ignored - the anchorPos
-- of the portal that was found will be returned but its destination will be unchanged.
-- * suggested_anchorPos indicates where the portal should be built
-- * destination_wormholePos is the wormholePos of the destination portal this one will be linked to.
-- * suggested_orientation is the suggested schematic rotation: 0, 90, 180, 270 (0 meaning a portal that faces north/south - i.e. obsidian running east/west)
--
-- Returns the final (anchorPos, orientation), as they may differ from the anchorPos and orientation that was
-- specified if an existing portal was already found there.
local function locate_or_build_portal ( portal_definition , suggested_anchorPos , suggested_orientation , destination_wormholePos )
if DEBUG then minetest.chat_send_all ( " locate_or_build_portal at " .. minetest.pos_to_string ( suggested_anchorPos ) .. " , targetted to " .. minetest.pos_to_string ( destination_wormholePos ) .. " , orientation " .. suggested_orientation ) end
local result_anchorPos = suggested_anchorPos ;
local result_orientation = suggested_orientation ;
local place_new_portal = true
-- Searching for an existing portal at wormholePos seems better than at anchorPos, though isn't important
local suggested_wormholePos = portal_definition.shape . get_wormholePos_from_anchorPos ( suggested_anchorPos , suggested_orientation )
local found_anchorPos , found_orientation , is_ignited = is_portal_frame ( portal_definition , suggested_wormholePos )
if found_anchorPos ~= nil then
-- A portal is already here, we don't have to build one, though we may need to ignite it
result_anchorPos = found_anchorPos
result_orientation = found_orientation
if is_ignited then
if DEBUG then minetest.chat_send_all ( " Build aborted: already a portal at " .. minetest.pos_to_string ( found_anchorPos ) .. " , orientation " .. result_orientation ) end
else
if DEBUG then minetest.chat_send_all ( " Build aborted: already an unlit portal at " .. minetest.pos_to_string ( found_anchorPos ) .. " , orientation " .. result_orientation ) end
-- ignite the portal
set_portal_metadata_and_ignite ( portal_definition , result_anchorPos , result_orientation , destination_wormholePos )
end
else
build_portal ( portal_definition , result_anchorPos , result_orientation , destination_wormholePos )
2016-05-31 20:13:56 +02:00
end
2019-06-29 05:18:18 +02:00
return result_anchorPos , result_orientation
2016-05-31 20:13:56 +02:00
end
2016-05-25 08:59:47 +02:00
2016-05-31 20:13:56 +02:00
local function volume_is_natural ( minp , maxp )
local c_air = minetest.get_content_id ( " air " )
local c_ignore = minetest.get_content_id ( " ignore " )
local vm = minetest.get_voxel_manip ( )
local pos1 = { x = minp.x , y = minp.y , z = minp.z }
local pos2 = { x = maxp.x , y = maxp.y , z = maxp.z }
local emin , emax = vm : read_from_map ( pos1 , pos2 )
local area = VoxelArea : new ( { MinEdge = emin , MaxEdge = emax } )
local data = vm : get_data ( )
for z = pos1.z , pos2.z do
for y = pos1.y , pos2.y do
local vi = area : index ( pos1.x , y , z )
for x = pos1.x , pos2.x do
local id = data [ vi ] -- Existing node
if id ~= c_air and id ~= c_ignore then -- These are natural
local name = minetest.get_name_from_content_id ( id )
if not minetest.registered_nodes [ name ] . is_ground_content then
return false
2013-04-29 20:33:09 +02:00
end
end
2016-05-31 20:13:56 +02:00
vi = vi + 1
2013-04-29 20:33:09 +02:00
end
end
end
2016-05-31 20:13:56 +02:00
return true
2013-04-29 20:33:09 +02:00
end
2016-05-31 20:13:56 +02:00
local function find_nether_target_y ( target_x , target_z , start_y )
2016-05-30 21:55:04 +02:00
local nobj_cave_point = minetest.get_perlin ( np_cave )
local air = 0 -- Consecutive air nodes found
for y = start_y , start_y - 4096 , - 1 do
local nval_cave = nobj_cave_point : get3d ( { x = target_x , y = y , z = target_z } )
if nval_cave > TCAVE then -- Cavern
air = air + 1
else -- Not cavern, check if 4 nodes of space above
if air >= 4 then
2016-05-31 20:13:56 +02:00
-- Check volume for non-natural nodes
local minp = { x = target_x - 1 , y = y - 1 , z = target_z - 2 }
local maxp = { x = target_x + 2 , y = y + 3 , z = target_z + 2 }
if volume_is_natural ( minp , maxp ) then
return y + 2
else -- Restart search a little lower
find_nether_target_y ( target_x , target_z , y - 16 )
end
2016-05-30 21:55:04 +02:00
else -- Not enough space, reset air to zero
air = 0
end
end
end
2016-05-31 20:13:56 +02:00
return start_y -- Fallback
end
2019-06-29 05:18:18 +02:00
--todo: find_surface_target_y() fails and selects locations under the surface
2016-05-31 20:13:56 +02:00
local function find_surface_target_y ( target_x , target_z , start_y )
for y = start_y , start_y - 256 , - 16 do
-- Check volume for non-natural nodes
local minp = { x = target_x - 1 , y = y - 1 , z = target_z - 2 }
local maxp = { x = target_x + 2 , y = y + 3 , z = target_z + 2 }
if volume_is_natural ( minp , maxp ) then
return y
end
end
2017-12-20 06:31:53 +01:00
return start_y - 256 -- Fallback
2016-05-30 21:55:04 +02:00
end
2016-05-31 20:13:56 +02:00
2019-06-29 05:18:18 +02:00
-- invoked when a player attempts to turn obsidian nodes into an open portal
local function ignite_portal ( ignition_pos )
2016-05-25 08:59:47 +02:00
2019-06-29 05:18:18 +02:00
local ignition_node_name = minetest.get_node ( ignition_pos ) . name
2013-04-29 20:33:09 +02:00
2019-06-29 15:08:31 +02:00
-- find which sort of portals are made from the node that was clicked on
2019-06-29 05:18:18 +02:00
local portal_definition = registered_portals [ ignition_node_name ]
if portal_definition == nil then
return false -- no portals are made from the node at ignition_pos
end
2016-05-31 20:13:56 +02:00
2019-06-29 05:18:18 +02:00
-- check it was a portal frame that the player is trying to ignite
local anchorPos , orientation , is_ignited = is_portal_frame ( portal_definition , ignition_pos )
if anchorPos == nil then
if DEBUG then minetest.chat_send_all ( " No portal frame found at " .. minetest.pos_to_string ( ignition_pos ) ) end
return false -- no portal is here
elseif is_ignited then
if DEBUG then
local meta = minetest.get_meta ( ignition_pos )
if meta ~= nil then minetest.chat_send_all ( " This portal links to " .. meta : get_string ( " target " ) .. " . p1= " .. meta : get_string ( " p1 " ) .. " p2= " .. meta : get_string ( " p2 " ) ) end
2013-04-29 20:33:09 +02:00
end
2019-06-29 05:18:18 +02:00
return false -- portal is already ignited
2013-04-29 20:33:09 +02:00
end
2019-06-29 05:18:18 +02:00
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
2016-05-26 07:29:25 +02:00
2019-06-29 05:18:18 +02:00
-- pick a destination
local destination_wormholePos = portal_definition.shape . get_wormholePos_from_anchorPos ( anchorPos , orientation )
if anchorPos.y < NETHER_DEPTH then
destination_wormholePos.y = find_surface_target_y ( destination_wormholePos.x , destination_wormholePos.z , - 16 )
else
local start_y = NETHER_DEPTH - math.random ( 500 , 1500 ) -- Search start
destination_wormholePos.y = find_nether_target_y ( destination_wormholePos.x , destination_wormholePos.z , start_y )
2013-04-29 20:33:09 +02:00
end
2019-06-29 05:18:18 +02:00
if DEBUG then minetest.chat_send_all ( " Destinaton set to " .. minetest.pos_to_string ( destination_wormholePos ) ) end
-- ignition/BURN_BABY_BURN
set_portal_metadata_and_ignite ( portal_definition , anchorPos , orientation , destination_wormholePos )
2016-05-26 07:29:25 +02:00
2013-04-29 20:33:09 +02:00
return true
end
2016-05-31 20:13:56 +02:00
2019-06-29 15:08:31 +02:00
local function extinguish_portal ( pos , node_name )
-- find which sort of portals are made from the node that was clicked on
local portal_definition = registered_portals [ node_name ]
if portal_definition == nil then
minetest.log ( " error " , " extinguish_portal() invoked on " .. node_name .. " but no registered portal is constructed from " .. node_name )
return false -- no portal frames are made from this type of node
end
local frame_node_name = portal_definition.frame_node_name
local wormhole_node_name = portal_definition.wormhole_node_name
local meta = minetest.get_meta ( pos )
local p1 = minetest.string_to_pos ( meta : get_string ( " p1 " ) )
local p2 = minetest.string_to_pos ( meta : get_string ( " p2 " ) )
local target = minetest.string_to_pos ( meta : get_string ( " target " ) )
if not p1 or not p2 then
return
end
for x = p1.x , p2.x do
for y = p1.y , p2.y do
for z = p1.z , p2.z do
local nn = minetest.get_node ( { x = x , y = y , z = z } ) . name
if nn == frame_node_name or nn == wormhole_node_name then
if nn == wormhole_node_name then
minetest.remove_node ( { x = x , y = y , z = z } )
end
local m = minetest.get_meta ( { x = x , y = y , z = z } )
m : set_string ( " p1 " , " " )
m : set_string ( " p2 " , " " )
m : set_string ( " target " , " " )
end
end
end
end
meta = minetest.get_meta ( target )
if not meta then
return
end
p1 = minetest.string_to_pos ( meta : get_string ( " p1 " ) )
p2 = minetest.string_to_pos ( meta : get_string ( " p2 " ) )
if not p1 or not p2 then
return
end
for x = p1.x , p2.x do
for y = p1.y , p2.y do
for z = p1.z , p2.z do
local nn = minetest.get_node ( { x = x , y = y , z = z } ) . name
if nn == frame_node_name or nn == wormhole_node_name then
if nn == wormhole_node_name then
minetest.remove_node ( { x = x , y = y , z = z } )
end
local m = minetest.get_meta ( { x = x , y = y , z = z } )
m : set_string ( " p1 " , " " )
m : set_string ( " p2 " , " " )
m : set_string ( " target " , " " )
end
end
end
end
end
2019-06-29 05:18:18 +02:00
-- invoked when a player is standing in a portal
local function ensure_remote_portal_then_teleport ( player , portal_definition , local_anchorPos , local_orientation , destination_wormholePos )
2016-05-25 08:59:47 +02:00
2019-06-29 05:18:18 +02:00
-- check player is still standing in a portal
local playerPos = player : getpos ( )
playerPos.y = playerPos.y + 0.1 -- Fix some glitches at -8000
if minetest.get_node ( playerPos ) . name ~= portal_definition.wormhole_node_name then
return -- the player has moved out of the portal
2013-04-29 20:33:09 +02:00
end
2019-06-29 05:18:18 +02:00
-- debounce - check player is still standing in the same portal that called this function
local meta = minetest.get_meta ( playerPos )
if not vector.equals ( local_anchorPos , minetest.string_to_pos ( meta : get_string ( " p1 " ) ) ) 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
return -- the player already teleported, and is now standing in a different portal
2013-04-29 20:33:09 +02:00
end
2016-05-26 07:29:25 +02:00
2019-06-29 05:18:18 +02:00
local destination_anchorPos = portal_definition.shape . get_anchorPos_from_wormholePos ( destination_wormholePos , local_orientation )
local node = minetest.get_node_or_nil ( destination_wormholePos )
2016-05-26 07:29:25 +02:00
2019-06-29 05:18:18 +02:00
if node == nil then
-- area not emerged yet, delay and retry
if DEBUG then minetest.chat_send_all ( " ensure_remote_portal_then_teleport() could not find anything yet at " .. minetest.pos_to_string ( destination_wormholePos ) ) end
minetest.after ( 1 , ensure_remote_portal_then_teleport , player , portal_definition , local_anchorPos , local_orientation , destination_wormholePos )
2016-05-25 08:59:47 +02:00
else
2019-06-29 05:18:18 +02:00
local local_wormholePos = portal_definition.shape . get_wormholePos_from_anchorPos ( local_anchorPos , local_orientation )
if node.name == portal_definition.wormhole_node_name then
-- portal exists
local destination_orientation = get_orientation_from_param2 ( node.param2 )
portal_definition.shape . disable_portal_trap ( destination_anchorPos , destination_orientation )
-- rotate the player if the destination portal is a different orientation
local rotation_angle = math.rad ( destination_orientation - local_orientation )
local offset = vector.subtract ( playerPos , local_wormholePos ) -- preserve player's position in the portal
local rotated_offset = { x = math.cos ( rotation_angle ) * offset.x - math.sin ( rotation_angle ) * offset.z , y = offset.y , z = math.sin ( rotation_angle ) * offset.x + math.cos ( rotation_angle ) * offset.z }
player : setpos ( vector.add ( destination_wormholePos , rotated_offset ) )
player : set_look_horizontal ( player : get_look_horizontal ( ) + rotation_angle )
else
-- destination portal still needs to be built
if DEBUG then minetest.chat_send_all ( " ensure_remote_portal_then_teleport() saw " .. node.name .. " at " .. minetest.pos_to_string ( destination_wormholePos ) .. " rather than a wormhole. Calling locate_or_build_portal() " ) end
2016-05-26 07:29:25 +02:00
2019-06-29 05:18:18 +02:00
local new_dest_anchorPos , new_dest_orientation = locate_or_build_portal ( portal_definition , destination_anchorPos , local_orientation , local_wormholePos )
2016-05-26 07:29:25 +02:00
2019-06-29 05:18:18 +02:00
if local_orientation ~= new_dest_orientation or not vector.equals ( destination_anchorPos , new_dest_anchorPos ) then
-- Update the local portal's target to match where the existing remote portal was found
destination_anchorPos = new_dest_anchorPos
destination_wormholePos = portal_definition.shape . get_wormholePos_from_anchorPos ( new_dest_anchorPos , new_dest_orientation )
if DEBUG then minetest.chat_send_all ( " update target to " .. minetest.pos_to_string ( destination_wormholePos ) ) end
set_portal_metadata (
portal_definition ,
local_anchorPos ,
local_orientation ,
destination_wormholePos
)
minetest.after ( 0.1 , ensure_remote_portal_then_teleport , player , portal_definition , local_anchorPos , local_orientation , destination_wormholePos )
end
2013-04-29 20:33:09 +02:00
end
end
end
2016-05-25 08:59:47 +02:00
-- ABMs
minetest.register_abm ( {
nodenames = { " nether:portal " } ,
interval = 1 ,
chance = 2 ,
action = function ( pos , node )
2017-12-20 06:35:05 +01:00
minetest.add_particlespawner ( {
2019-06-26 11:18:03 +02:00
amount = 32 ,
time = 4 ,
minpos = { x = pos.x - 0.25 , y = pos.y - 0.25 , z = pos.z - 0.25 } ,
maxpos = { x = pos.x + 0.25 , y = pos.y + 0.25 , z = pos.z + 0.25 } ,
minvel = { x = - 0.8 , y = - 0.8 , z = - 0.8 } ,
maxvel = { x = 0.8 , y = 0.8 , z = 0.8 } ,
minacc = { x = 0 , y = 0 , z = 0 } ,
maxacc = { x = 0 , y = 0 , z = 0 } ,
minexptime = 0.5 ,
maxexptime = 1 ,
minsize = 1 ,
maxsize = 2 ,
collisiondetection = false ,
texture = " nether_particle.png "
2017-12-20 06:35:05 +01:00
} )
2016-05-25 08:59:47 +02:00
for _ , obj in ipairs ( minetest.get_objects_inside_radius ( pos , 1 ) ) do
if obj : is_player ( ) then
local meta = minetest.get_meta ( pos )
2019-06-29 05:18:18 +02:00
local destination_wormholePos = minetest.string_to_pos ( meta : get_string ( " target " ) )
local local_p1 = minetest.string_to_pos ( meta : get_string ( " p1 " ) )
if destination_wormholePos ~= nil and local_p1 ~= nil then
-- find out what sort of portal we're in
local p1_node_name = minetest.get_node ( local_p1 ) . name
local portal_definition = registered_portals [ p1_node_name ]
if portal_definition == nil then
if p1_node_name ~= " ignore " then
-- I've seen cases where the p1_node_name temporarily returns "ignore", but it comes right - perhaps it happens when playerPos and anchorPos are in different chunks?
if DEBUG then minetest.chat_send_all ( " Weirdness: No portal with a \" " .. p1_node_name .. " \" frame is registered. Portal metadata at " .. minetest.pos_to_string ( pos ) .. " claims node " .. minetest.pos_to_string ( local_p1 ) .. " is its portal corner (p1), but that location contains \" " .. p1_node_name .. " \" " ) end
else
minetest.log ( " error " , " No portal with a \" " .. p1_node_name .. " \" frame is registered. Portal metadata at " .. minetest.pos_to_string ( pos ) .. " claims node " .. minetest.pos_to_string ( local_p1 ) .. " is its portal corner (p1), but that location contains \" " .. p1_node_name .. " \" " )
end
return
end
2016-05-25 08:59:47 +02:00
-- force emerge of target area
2019-06-29 05:18:18 +02:00
minetest.get_voxel_manip ( ) : read_from_map ( destination_wormholePos , destination_wormholePos )
if not minetest.get_node_or_nil ( destination_wormholePos ) then
minetest.emerge_area ( vector.subtract ( destination_wormholePos , 4 ) , vector.add ( destination_wormholePos , 4 ) )
2016-05-25 08:59:47 +02:00
end
2016-05-26 07:29:25 +02:00
2019-06-29 05:18:18 +02:00
local local_orientation = get_orientation_from_param2 ( node.param2 )
minetest.after (
3 , -- hopefully target area is emerged in 3 seconds
function ( )
ensure_remote_portal_then_teleport (
obj ,
portal_definition ,
local_p1 ,
local_orientation ,
destination_wormholePos
)
2016-05-25 08:59:47 +02:00
end
2019-06-29 05:18:18 +02:00
)
2016-05-25 08:59:47 +02:00
end
end
end
end ,
} )
-- Nodes
minetest.register_node ( " nether:portal " , {
description = " Nether Portal " ,
tiles = {
" nether_transparent.png " ,
" nether_transparent.png " ,
" nether_transparent.png " ,
" nether_transparent.png " ,
{
name = " nether_portal.png " ,
animation = {
type = " vertical_frames " ,
aspect_w = 16 ,
aspect_h = 16 ,
length = 0.5 ,
} ,
} ,
{
name = " nether_portal.png " ,
animation = {
type = " vertical_frames " ,
aspect_w = 16 ,
aspect_h = 16 ,
length = 0.5 ,
} ,
} ,
} ,
drawtype = " nodebox " ,
paramtype = " light " ,
paramtype2 = " facedir " ,
sunlight_propagates = true ,
use_texture_alpha = true ,
walkable = false ,
diggable = false ,
pointable = false ,
buildable_to = false ,
2016-05-30 21:55:04 +02:00
is_ground_content = false ,
2016-05-25 08:59:47 +02:00
drop = " " ,
light_source = 5 ,
post_effect_color = { a = 180 , r = 128 , g = 0 , b = 128 } ,
alpha = 192 ,
node_box = {
type = " fixed " ,
fixed = {
{ - 0.5 , - 0.5 , - 0.1 , 0.5 , 0.5 , 0.1 } ,
} ,
} ,
groups = { not_in_creative_inventory = 1 }
} )
2013-04-29 20:33:09 +02:00
minetest.register_node ( " :default:obsidian " , {
description = " Obsidian " ,
tiles = { " default_obsidian.png " } ,
2016-05-30 21:55:04 +02:00
is_ground_content = false ,
2013-04-29 20:33:09 +02:00
sounds = default.node_sound_stone_defaults ( ) ,
2016-05-25 08:59:47 +02:00
groups = { cracky = 1 , level = 2 } ,
2016-05-26 07:29:25 +02:00
2019-06-29 15:08:31 +02:00
mesecons = { effector = {
action_on = function ( pos , node )
ignite_portal ( pos , node.name )
end ,
action_off = function ( pos , node )
extinguish_portal ( pos , node.name )
2013-04-29 20:33:09 +02:00
end
2019-06-29 15:08:31 +02:00
} } ,
on_destruct = function ( pos )
extinguish_portal ( pos , " default:obsidian " )
2013-04-29 20:33:09 +02:00
end ,
} )
2013-04-29 23:26:12 +02:00
minetest.register_node ( " nether:rack " , {
2013-04-29 20:33:09 +02:00
description = " Netherrack " ,
2013-04-30 15:26:03 +02:00
tiles = { " nether_rack.png " } ,
2013-04-29 20:33:09 +02:00
is_ground_content = true ,
2016-05-25 08:59:47 +02:00
groups = { cracky = 3 , level = 2 } ,
2013-04-29 20:33:09 +02:00
sounds = default.node_sound_stone_defaults ( ) ,
} )
2013-04-29 23:26:12 +02:00
minetest.register_node ( " nether:sand " , {
2013-04-29 20:33:09 +02:00
description = " Nethersand " ,
2013-04-30 15:26:03 +02:00
tiles = { " nether_sand.png " } ,
2013-04-29 20:33:09 +02:00
is_ground_content = true ,
2016-05-25 08:59:47 +02:00
groups = { crumbly = 3 , level = 2 , falling_node = 1 } ,
2016-05-30 21:55:04 +02:00
sounds = default.node_sound_gravel_defaults ( {
2016-05-25 08:59:47 +02:00
footstep = { name = " default_gravel_footstep " , gain = 0.45 } ,
2013-04-29 20:33:09 +02:00
} ) ,
} )
minetest.register_node ( " nether:glowstone " , {
description = " Glowstone " ,
tiles = { " nether_glowstone.png " } ,
is_ground_content = true ,
2016-05-30 21:55:04 +02:00
light_source = 14 ,
paramtype = " light " ,
2016-05-25 08:59:47 +02:00
groups = { cracky = 3 , oddly_breakable_by_hand = 3 } ,
2013-04-29 20:33:09 +02:00
sounds = default.node_sound_glass_defaults ( ) ,
} )
2013-04-29 23:36:45 +02:00
minetest.register_node ( " nether:brick " , {
description = " Nether Brick " ,
tiles = { " nether_brick.png " } ,
2016-05-30 21:55:04 +02:00
is_ground_content = false ,
2016-05-25 08:59:47 +02:00
groups = { cracky = 2 , level = 2 } ,
2013-04-29 23:36:45 +02:00
sounds = default.node_sound_stone_defaults ( ) ,
} )
2015-11-23 08:08:46 +01:00
local fence_texture =
" default_fence_overlay.png^nether_brick.png^default_fence_overlay.png^[makealpha:255,126,126 "
2016-05-25 08:59:47 +02:00
2015-11-23 08:08:46 +01:00
minetest.register_node ( " nether:fence_nether_brick " , {
description = " Nether Brick Fence " ,
drawtype = " fencelike " ,
tiles = { " nether_brick.png " } ,
inventory_image = fence_texture ,
wield_image = fence_texture ,
paramtype = " light " ,
sunlight_propagates = true ,
is_ground_content = false ,
selection_box = {
type = " fixed " ,
fixed = { - 1 / 7 , - 1 / 2 , - 1 / 7 , 1 / 7 , 1 / 2 , 1 / 7 } ,
} ,
2016-05-30 21:55:04 +02:00
groups = { cracky = 2 , level = 2 } ,
2015-11-23 08:08:46 +01:00
sounds = default.node_sound_stone_defaults ( ) ,
} )
2016-05-25 08:59:47 +02:00
-- Register stair and slab
stairs.register_stair_and_slab (
" nether_brick " ,
" nether:brick " ,
2016-05-30 21:55:04 +02:00
{ cracky = 2 , level = 2 } ,
2016-05-25 08:59:47 +02:00
{ " nether_brick.png " } ,
" nether stair " ,
" nether slab " ,
default.node_sound_stone_defaults ( )
)
2016-06-12 01:52:48 +02:00
-- StairsPlus
if minetest.get_modpath ( " moreblocks " ) then
stairsplus : register_all (
" nether " , " brick " , " nether:brick " , {
description = " Nether Brick " ,
groups = { cracky = 2 , level = 2 } ,
tiles = { " nether_brick.png " } ,
sounds = default.node_sound_stone_defaults ( ) ,
} )
end
2016-05-25 08:59:47 +02:00
2015-11-18 01:21:05 +01:00
2016-05-25 08:59:47 +02:00
-- Craftitems
2017-12-21 05:52:57 +01:00
minetest.override_item ( " default:mese_crystal_fragment " , {
2016-05-25 08:59:47 +02:00
on_place = function ( stack , _ , pt )
if pt.under and minetest.get_node ( pt.under ) . name == " default:obsidian " then
2019-06-29 05:18:18 +02:00
local done = ignite_portal ( pt.under )
2017-12-20 06:39:29 +01:00
if done and not minetest.settings : get_bool ( " creative_mode " ) then
2016-05-25 08:59:47 +02:00
stack : take_item ( )
end
end
return stack
end ,
} )
-- Crafting
2016-04-17 08:05:44 +02:00
minetest.register_craft ( {
2016-05-30 21:55:04 +02:00
output = " nether:brick 4 " ,
2016-04-17 08:05:44 +02:00
recipe = {
2016-05-30 21:55:04 +02:00
{ " nether:rack " , " nether:rack " } ,
{ " nether:rack " , " nether:rack " } ,
}
2016-04-17 08:05:44 +02:00
} )
minetest.register_craft ( {
2016-05-30 21:55:04 +02:00
output = " nether:fence_nether_brick 6 " ,
2016-04-17 08:05:44 +02:00
recipe = {
{ " nether:brick " , " nether:brick " , " nether:brick " } ,
{ " nether:brick " , " nether:brick " , " nether:brick " } ,
} ,
} )
2016-05-25 05:42:32 +02:00
-- Mapgen
2016-12-29 00:03:33 +01:00
-- Initialize noise object, localise noise and data buffers
2016-05-30 21:55:04 +02:00
local nobj_cave = nil
2017-12-20 06:31:53 +01:00
local nbuf_cave = nil
local dbuf = nil
2016-05-30 21:55:04 +02:00
-- Content ids
local c_air = minetest.get_content_id ( " air " )
2017-12-20 06:31:53 +01:00
--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")
2016-05-30 21:55:04 +02:00
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 " )
2017-12-20 06:31:53 +01:00
--local c_stone_with_copper = minetest.get_content_id("default:stone_with_copper")
2016-05-30 21:55:04 +02:00
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
2016-05-25 05:42:32 +02:00
minetest.register_on_generated ( function ( minp , maxp , seed )
2016-05-30 21:55:04 +02:00
if minp.y > NETHER_DEPTH then
2016-05-25 05:42:32 +02:00
return
end
2016-05-30 21:55:04 +02:00
local x1 = maxp.x
local y1 = maxp.y
local z1 = maxp.z
local x0 = minp.x
local y0 = minp.y
local z0 = minp.z
2016-05-25 05:42:32 +02:00
local vm , emin , emax = minetest.get_mapgen_object ( " voxelmanip " )
local area = VoxelArea : new { MinEdge = emin , MaxEdge = emax }
2016-12-29 00:03:33 +01:00
local data = vm : get_data ( dbuf )
2016-05-25 05:42:32 +02:00
2016-05-30 21:55:04 +02:00
local x11 = emax.x -- Limits of mapchunk plus mapblock shell
local y11 = emax.y
local z11 = emax.z
local x00 = emin.x
local y00 = emin.y
local z00 = emin.z
local ystride = x1 - x0 + 1
local zstride = ystride * ystride
local chulens = { x = ystride , y = ystride , z = ystride }
local minposxyz = { x = x0 , y = y0 , z = z0 }
nobj_cave = nobj_cave or minetest.get_perlin_map ( np_cave , chulens )
local nvals_cave = nobj_cave : get3dMap_flat ( minposxyz , nbuf_cave )
for y = y00 , y11 do -- Y loop first to minimise tcave calculations
local tcave
local in_chunk_y = false
if y >= y0 and y <= y1 then
if y > yblmax then
tcave = TCAVE + ( ( y - yblmax ) / BLEND ) ^ 2
else
tcave = TCAVE
end
in_chunk_y = true
end
for z = z00 , z11 do
local vi = area : index ( x00 , y , z ) -- Initial voxelmanip index
local ni
local in_chunk_yz = in_chunk_y and z >= z0 and z <= z1
for x = x00 , x11 do
if in_chunk_yz and x == x0 then
-- Initial noisemap index
ni = ( z - z0 ) * zstride + ( y - y0 ) * ystride + 1
end
local in_chunk_yzx = in_chunk_yz and x >= x0 and x <= x1 -- In mapchunk
local id = data [ vi ] -- Existing node
-- Cave air, cave liquids and dungeons are overgenerated,
-- convert these throughout mapchunk plus shell
if id == c_air or -- Air and liquids to air
id == c_lava_source or
id == c_lava_flowing or
id == c_water_source or
id == c_water_flowing then
data [ vi ] = c_air
-- Dungeons are preserved so we don't need
-- to check for cavern in the shell
elseif id == c_cobble or -- Dungeons (preserved) to netherbrick
id == c_mossycobble or
id == c_stair_cobble then
data [ vi ] = c_netherbrick
end
if in_chunk_yzx then -- In mapchunk
if nvals_cave [ ni ] > tcave then -- Only excavate cavern in mapchunk
data [ vi ] = c_air
elseif id == c_mese then -- Mese block to lava
data [ vi ] = c_lava_source
elseif id == c_stone_with_gold or -- Precious ores to glowstone
id == c_stone_with_mese or
id == c_stone_with_diamond then
data [ vi ] = c_glowstone
elseif id == c_gravel or -- Blob ore to nethersand
id == c_dirt or
id == c_sand then
data [ vi ] = c_nethersand
else -- All else to netherstone
data [ vi ] = c_netherrack
end
ni = ni + 1 -- Only increment noise index in mapchunk
end
vi = vi + 1
end
2016-05-25 05:42:32 +02:00
end
2013-04-29 20:33:09 +02:00
end
2016-05-25 05:42:32 +02:00
vm : set_data ( data )
vm : set_lighting ( { day = 0 , night = 0 } )
vm : calc_lighting ( )
vm : update_liquids ( )
vm : write_to_map ( )
end )