7 local FRAME_SIZE_X_MIN
= 4
8 local FRAME_SIZE_Y_MIN
= 5
9 local FRAME_SIZE_X_MAX
= 23
10 local FRAME_SIZE_Y_MAX
= 23
12 local NETHER_PORTAL_TELEPORT_DELAY
= 3 -- seconds before teleporting in Nether portal
14 local mg_name
= minetest
.get_mapgen_setting("mg_name")
20 spread
= {x
= 384, y
= 128, z
= 384},
26 -- Table of objects (including players) which recently teleported by a
27 -- Nether portal. Those objects have a brief cooloff period before they
28 -- can teleport again. This prevents annoying back-and-forth teleportation.
29 local portal_cooloff
= {}
31 -- Destroy portal if pos (portal frame or portal node) got destroyed
32 local destroy_portal
= function(pos
)
33 -- Deactivate Nether portal
34 local meta
= minetest
.get_meta(pos
)
35 local p1
= minetest
.string_to_pos(meta
:get_string("portal_frame1"))
36 local p2
= minetest
.string_to_pos(meta
:get_string("portal_frame2"))
37 if not p1
or not p2
then
47 local p
= vector
.new(x
, y
, z
)
48 local m
= minetest
.get_meta(p
)
50 --[[ Only proceed if the second node still has metadata.
51 (first node is a corner and not needed for the portal)
52 If it doesn't have metadata, another node propably triggred the delection
53 routine earlier, so we bail out earlier to avoid an infinite cascade
54 of on_destroy events. ]]
55 mp1
= minetest
.string_to_pos(m
:get_string("portal_frame1"))
60 local nn
= minetest
.get_node(p
).name
61 if nn
== "mcl_core:obsidian" or nn
== "mcl_portals:portal" then
62 -- Remove portal nodes, but not myself
63 if nn
== "mcl_portals:portal" and not vector
.equals(p
, pos
) then
64 minetest
.remove_node(p
)
66 -- Clear metadata of portal nodes and the frame
67 m
:set_string("portal_frame1", "")
68 m
:set_string("portal_frame2", "")
69 m
:set_string("portal_target", "")
77 minetest
.register_node("mcl_portals:portal", {
78 description
= "Nether Portal",
79 _doc_items_longdesc
= "A Nether portal teleports creatures and objects to the hot and dangerous Nether dimension (and back!). Enter at your own risk!",
80 _doc_items_usagehelp
= "Stand in the portal for a moment to activate the teleportation. Entering a Nether portal for the first time will also create a new portal in the other dimension. If a Nether portal has been built in the Nether, it will lead to the Overworld. A Nether portal is destroyed if the any of the obsidian which surrounds it is destroyed, or if it was caught in an explosion.",
88 name
= "mcl_portals_portal.png",
90 type = "vertical_frames",
97 name
= "mcl_portals_portal.png",
99 type = "vertical_frames",
106 drawtype
= "nodebox",
108 paramtype2
= "facedir",
109 sunlight_propagates
= true,
110 use_texture_alpha
= true,
114 buildable_to
= false,
115 is_ground_content
= false,
118 post_effect_color
= {a
= 180, r
= 51, g
= 7, b
= 89},
123 {-0.5, -0.5, -0.1, 0.5, 0.5, 0.1},
126 groups
= {not_in_creative_inventory
= 1},
127 on_destruct
= destroy_portal
,
130 _mcl_blast_resistance
= 0,
134 --Build arrival portal
135 local function build_portal(pos
, target
)
136 local p
= {x
= pos
.x
- 1, y
= pos
.y
- 1, z
= pos
.z
}
137 local p1
= {x
= pos
.x
- 1, y
= pos
.y
- 1, z
= pos
.z
}
138 local p2
= {x
= p1
.x
+ 3, y
= p1
.y
+ 4, z
= p1
.z
}
140 for i
= 1, FRAME_SIZE_Y_MIN
- 1 do
141 minetest
.set_node(p
, {name
= "mcl_core:obsidian"})
144 for i
= 1, FRAME_SIZE_X_MIN
- 1 do
145 minetest
.set_node(p
, {name
= "mcl_core:obsidian"})
148 for i
= 1, FRAME_SIZE_Y_MIN
- 1 do
149 minetest
.set_node(p
, {name
= "mcl_core:obsidian"})
152 for i
= 1, FRAME_SIZE_X_MIN
- 1 do
153 minetest
.set_node(p
, {name
= "mcl_core:obsidian"})
157 for x
= p1
.x
, p2
.x
do
158 for y
= p1
.y
, p2
.y
do
159 p
= {x
= x
, y
= y
, z
= p1
.z
}
160 if not ((x
== p1
.x
or x
== p2
.x
) and (y
== p1
.y
or y
== p2
.y
)) then
161 if not (x
== p1
.x
or x
== p2
.x
or y
== p1
.y
or y
== p2
.y
) then
162 minetest
.set_node(p
, {name
= "mcl_portals:portal", param2
= 0})
164 local meta
= minetest
.get_meta(p
)
165 meta
:set_string("portal_frame1", minetest
.pos_to_string(p1
))
166 meta
:set_string("portal_frame2", minetest
.pos_to_string(p2
))
167 meta
:set_string("portal_target", minetest
.pos_to_string(target
))
174 if minetest
.registered_nodes
[
175 minetest
.get_node(p
).name
].is_ground_content
then
176 minetest
.remove_node(p
)
184 minetest
.log("action", "[mcl_portal] Destination Nether portal generated at "..minetest
.pos_to_string(p2
).."!")
187 local function find_nether_target_y(target_x
, target_z
)
188 if mg_name
== "flat" then
189 return mcl_vars
.mg_bedrock_nether_bottom_max
+ 5
191 local start_y
= math
.random(mcl_vars
.mg_lava_nether_max
+ 1, mcl_vars
.mg_bedrock_nether_top_min
- 5) -- Search start
192 if not nobj_cave
then
193 nobj_cave
= minetest
.get_perlin(np_cave
)
197 for y
= start_y
, math
.max(mcl_vars
.mg_lava_nether_max
+ 1), -1 do
198 local nval_cave
= nobj_cave
:get3d({x
= target_x
, y
= y
, z
= target_z
})
200 if nval_cave
> TCAVE
then -- Cavern
202 else -- Not cavern, check if 4 nodes of space above
205 else -- Not enough space, reset air to zero
211 return start_y
-- Fallback
214 local function move_check(p1
, max, dir
)
215 local p
= {x
= p1
.x
, y
= p1
.y
, z
= p1
.z
}
216 local d
= math
.sign(max - p1
[dir
])
219 for k
= min, max, d
do
221 local node
= minetest
.get_node(p
)
222 -- Check for obsidian (except at corners)
223 if k
~= min and k
~= max and node
.name
~= "mcl_core:obsidian" then
226 -- Abort if any of the portal frame blocks already has metadata.
227 -- This mod does not yet portals which neighbor each other directly.
228 -- TODO: Reorganize the way how portal frame coordinates are stored.
229 if node
.name
== "mcl_core:obsidian" then
230 local meta
= minetest
.get_meta(p
)
231 local pframe1
= meta
:get_string("portal_frame1")
232 if minetest
.string_to_pos(pframe1
) ~= nil then
241 local function check_portal(p1
, p2
)
243 if not move_check(p1
, p2
.x
, "x") then
246 if not move_check(p2
, p1
.x
, "x") then
249 elseif p1
.z
~= p2
.z
then
250 if not move_check(p1
, p2
.z
, "z") then
253 if not move_check(p2
, p1
.z
, "z") then
260 if not move_check(p1
, p2
.y
, "y") then
263 if not move_check(p2
, p1
.y
, "y") then
270 local function is_portal(pos
)
271 local xsize
, ysize
= FRAME_SIZE_X_MIN
-1, FRAME_SIZE_Y_MIN
-1
272 for d
= -xsize
, xsize
do
273 for y
= -ysize
, ysize
do
274 local px
= {x
= pos
.x
+ d
, y
= pos
.y
+ y
, z
= pos
.z
}
275 local pz
= {x
= pos
.x
, y
= pos
.y
+ y
, z
= pos
.z
+ d
}
277 if check_portal(px
, {x
= px
.x
+ xsize
, y
= px
.y
+ ysize
, z
= px
.z
}) then
278 return px
, {x
= px
.x
+ xsize
, y
= px
.y
+ ysize
, z
= px
.z
}
280 if check_portal(pz
, {x
= pz
.x
, y
= pz
.y
+ ysize
, z
= pz
.z
+ xsize
}) then
281 return pz
, {x
= pz
.x
, y
= pz
.y
+ ysize
, z
= pz
.z
+ xsize
}
287 -- Attempts to light a Nether portal at pos and
288 -- select target position.
289 -- Pos can be any of the obsidian frame blocks or the inner part.
290 -- The frame MUST be filled only with air or any fire, which will be replaced with Nether portal blocks.
291 -- If no Nether portal can be lit, nothing happens.
292 -- Returns true on success and false on failure.
293 function mcl_portals
.light_nether_portal(pos
)
294 -- Only allow to make portals in Overworld and Nether
295 local dim
= mcl_worlds
.pos_to_dimension(pos
)
296 if dim
~= "overworld" and dim
~= "nether" then
299 -- Create Nether portal nodes
300 local p1
, p2
= is_portal(pos
)
301 if not p1
or not p2
then
306 for y
= p1
.y
+ 1, p2
.y
- 1 do
309 p
= {x
= p1
.x
+ d
, y
= y
, z
= p1
.z
}
311 p
= {x
= p1
.x
, y
= y
, z
= p1
.z
+ d
}
313 local nn
= minetest
.get_node(p
).name
314 if nn
~= "air" and minetest
.get_item_group(nn
, "fire") ~= 1 then
329 local target
= {x
= p1
.x
, y
= p1
.y
, z
= p1
.z
}
330 target
.x
= target
.x
+ 1
331 if target
.y
< mcl_vars
.mg_nether_max
and target
.y
> mcl_vars
.mg_nether_min
then
332 if mg_name
== "flat" then
333 target
.y
= mcl_vars
.mg_bedrock_overworld_max
+ 5
335 target
.y
= math
.random(mcl_vars
.mg_overworld_min
+ 40, mcl_vars
.mg_overworld_min
+ 96)
338 target
.y
= find_nether_target_y(target
.x
, target
.z
)
341 local dmin
, dmax
, ymin
, ymax
= 0, FRAME_SIZE_X_MIN
- 1, p1
.y
, p2
.y
342 for d
= dmin
, dmax
do
343 for y
= ymin
, ymax
do
344 if not ((d
== dmin
or d
== dmax
) and (y
== ymin
or y
== ymax
)) then
347 p
= {x
= p1
.x
+ d
, y
= y
, z
= p1
.z
}
349 p
= {x
= p1
.x
, y
= y
, z
= p1
.z
+ d
}
351 if d
~= dmin
and d
~= dmax
and y
~= ymin
and y
~= ymax
then
352 minetest
.set_node(p
, {name
= "mcl_portals:portal", param2
= param2
})
354 local meta
= minetest
.get_meta(p
)
356 -- Portal frame corners
357 meta
:set_string("portal_frame1", minetest
.pos_to_string(p1
))
358 meta
:set_string("portal_frame2", minetest
.pos_to_string(p2
))
360 -- Portal target coordinates
361 meta
:set_string("portal_target", minetest
.pos_to_string(target
))
370 minetest
.register_abm({
371 label
= "Nether portal teleportation and particles",
372 nodenames
= {"mcl_portals:portal"},
375 action
= function(pos
, node
)
376 minetest
.add_particlespawner(
379 {x
= pos
.x
- 0.25, y
= pos
.y
- 0.25, z
= pos
.z
- 0.25}, --minpos
380 {x
= pos
.x
+ 0.25, y
= pos
.y
+ 0.25, z
= pos
.z
+ 0.25}, --maxpos
381 {x
= -0.8, y
= -0.8, z
= -0.8}, --minvel
382 {x
= 0.8, y
= 0.8, z
= 0.8}, --maxvel
383 {x
= 0, y
= 0, z
= 0}, --minacc
384 {x
= 0, y
= 0, z
= 0}, --maxacc
389 false, --collisiondetection
390 "mcl_particles_teleport.png" --texture
392 for _
,obj
in ipairs(minetest
.get_objects_inside_radius(pos
,1)) do --maikerumine added for objects to travel
393 local lua_entity
= obj
:get_luaentity() --maikerumine added for objects to travel
394 if obj
:is_player() or lua_entity
then
395 -- Prevent quick back-and-forth teleportation
396 if portal_cooloff
[obj
] then
399 local meta
= minetest
.get_meta(pos
)
400 local target
= minetest
.string_to_pos(meta
:get_string("portal_target"))
402 -- force emerge of target area
403 minetest
.get_voxel_manip():read_from_map(target
, target
)
404 if not minetest
.get_node_or_nil(target
) then
405 minetest
.emerge_area(vector
.subtract(target
, 4), vector
.add(target
, 4))
409 local teleport
= function(obj
, pos
, target
)
410 if (not obj
:get_luaentity()) and (not obj
:is_player()) then
413 -- Prevent quick back-and-forth teleportation
414 if portal_cooloff
[obj
] then
417 local objpos
= obj
:getpos()
418 if objpos
== nil then
421 -- If player stands, player is at ca. something+0.5
422 -- which might cause precision problems, so we used ceil.
423 objpos
.y
= math
.ceil(objpos
.y
)
425 if minetest
.get_node(objpos
).name
~= "mcl_portals:portal" then
431 if obj
:is_player() then
432 mcl_worlds
.dimension_change(obj
, mcl_worlds
.pos_to_dimension(target
))
433 minetest
.sound_play("mcl_portals_teleport", {pos
=target
, gain
=0.5, max_hear_distance
= 16})
436 -- Enable teleportation cooloff for 4 seconds, to prevent back-and-forth teleportation
437 portal_cooloff
[obj
] = true
438 minetest
.after(4, function(o
)
439 portal_cooloff
[o
] = false
441 if obj
:is_player() then
442 local name
= obj
:get_player_name()
443 minetest
.log("action", "[mcl_portal] "..name
.." teleported to Nether portal at "..minetest
.pos_to_string(target
)..".")
447 local n
= minetest
.get_node_or_nil(target
)
448 if n
and n
.name
~= "mcl_portals:portal" then
449 -- Emerge target area, wait for emerging to be finished, build destination portal
450 -- (if there isn't already one, teleport object after a short delay.
451 local emerge_callback
= function(blockpos
, action
, calls_remaining
, param
)
452 minetest
.log("verbose", "[mcl_portal] emerge_callack called! action="..action
)
453 if calls_remaining
<= 0 then
454 minetest
.log("verbose", "[mcl_portal] Area for destination Nether portal emerged!")
455 build_portal(param
.target
, param
.pos
, false)
456 minetest
.after(NETHER_PORTAL_TELEPORT_DELAY
, teleport
, obj
, pos
, target
)
459 minetest
.log("verbose", "[mcl_portal] Emerging area for destination Nether portal ...")
460 minetest
.emerge_area(vector
.subtract(target
, 7), vector
.add(target
, 7), emerge_callback
, { pos
= pos
, target
= target
})
462 minetest
.after(NETHER_PORTAL_TELEPORT_DELAY
, teleport
, obj
, pos
, target
)
472 --[[ ITEM OVERRIDES ]]
474 local longdesc
= minetest
.registered_nodes
["mcl_core:obsidian"]._doc_items_longdesc
475 longdesc
= longdesc
.. "\n" .. "Obsidian is also used as the frame of Nether portals."
476 local usagehelp
= "To open a Nether portal, place an upright frame of obsidian with a width of 4 blocks and a height of 5 blocks, leaving only air in the center. After placing this frame, light a fire in the obsidian frame. Nether portals only work in the Overworld and the Nether."
478 minetest
.override_item("mcl_core:obsidian", {
479 _doc_items_longdesc
= longdesc
,
480 _doc_items_usagehelp
= usagehelp
,
481 on_destruct
= destroy_portal
,
482 _on_ignite
= function(user
, pointed_thing
)
483 local pos
= pointed_thing
.under
484 local portal_placed
= mcl_portals
.light_nether_portal(pos
)
485 if portal_placed
then
486 minetest
.log("action", "[mcl_portal] Nether portal activated at "..minetest
.pos_to_string(pos
)..".")
488 if portal_placed
and minetest
.get_modpath("doc") then
489 doc
.mark_entry_as_revealed(user
:get_player_name(), "nodes", "mcl_portals:portal")
491 -- Achievement for finishing a Nether portal TO the Nether
492 local dim
= mcl_worlds
.pos_to_dimension(pos
)
493 if minetest
.get_modpath("awards") and dim
~= "nether" and user
:is_player() then
494 awards
.unlock(user
:get_player_name(), "mcl:buildNetherPortal")