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 mg_name
= minetest
.get_mapgen_setting("mg_name")
18 spread
= {x
= 384, y
= 128, z
= 384},
24 -- Table of objects (including players) which recently teleported by a
25 -- Nether portal. Those objects have a brief cooloff period before they
26 -- can teleport again. This prevents annoying back-and-forth teleportation.
27 local portal_cooloff
= {}
29 -- Destroy portal if pos (portal frame or portal node) got destroyed
30 local destroy_portal
= function(pos
)
31 -- Deactivate Nether portal
32 local meta
= minetest
.get_meta(pos
)
33 local p1
= minetest
.string_to_pos(meta
:get_string("portal_frame1"))
34 local p2
= minetest
.string_to_pos(meta
:get_string("portal_frame2"))
35 if not p1
or not p2
then
45 local p
= vector
.new(x
, y
, z
)
46 local m
= minetest
.get_meta(p
)
48 --[[ Only proceed if the second node still has metadata.
49 (first node is a corner and not needed for the portal)
50 If it doesn't have metadata, another node propably triggred the delection
51 routine earlier, so we bail out earlier to avoid an infinite cascade
52 of on_destroy events. ]]
53 mp1
= minetest
.string_to_pos(m
:get_string("portal_frame1"))
58 local nn
= minetest
.get_node(p
).name
59 if nn
== "mcl_core:obsidian" or nn
== "mcl_portals:portal" then
60 -- Remove portal nodes, but not myself
61 if nn
== "mcl_portals:portal" and not vector
.equals(p
, pos
) then
62 minetest
.remove_node(p
)
64 -- Clear metadata of portal nodes and the frame
65 m
:set_string("portal_frame1", "")
66 m
:set_string("portal_frame2", "")
67 m
:set_string("portal_target", "")
75 minetest
.register_node("mcl_portals:portal", {
76 description
= "Nether Portal",
77 _doc_items_longdesc
= "A Nether portal teleports creatures and objects to the hot and dangerous Nether dimension (and back!). Enter at your own risk!",
78 _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.",
86 name
= "mcl_portals_portal.png",
88 type = "vertical_frames",
95 name
= "mcl_portals_portal.png",
97 type = "vertical_frames",
104 drawtype
= "nodebox",
106 paramtype2
= "facedir",
107 sunlight_propagates
= true,
108 use_texture_alpha
= true,
112 buildable_to
= false,
113 is_ground_content
= false,
116 post_effect_color
= {a
= 180, r
= 51, g
= 7, b
= 89},
121 {-0.5, -0.5, -0.1, 0.5, 0.5, 0.1},
124 groups
= {not_in_creative_inventory
= 1},
125 on_destruct
= destroy_portal
,
128 _mcl_blast_resistance
= 0,
132 --Build arrival portal
133 local function build_portal(pos
, target
, is_rebuilding
)
134 local p
= {x
= pos
.x
- 1, y
= pos
.y
- 1, z
= pos
.z
}
135 local p1
= {x
= pos
.x
- 1, y
= pos
.y
- 1, z
= pos
.z
}
136 local p2
= {x
= p1
.x
+ 3, y
= p1
.y
+ 4, z
= p1
.z
}
138 for i
= 1, FRAME_SIZE_Y_MIN
- 1 do
139 minetest
.set_node(p
, {name
= "mcl_core:obsidian"})
142 for i
= 1, FRAME_SIZE_X_MIN
- 1 do
143 minetest
.set_node(p
, {name
= "mcl_core:obsidian"})
146 for i
= 1, FRAME_SIZE_Y_MIN
- 1 do
147 minetest
.set_node(p
, {name
= "mcl_core:obsidian"})
150 for i
= 1, FRAME_SIZE_X_MIN
- 1 do
151 minetest
.set_node(p
, {name
= "mcl_core:obsidian"})
155 for x
= p1
.x
, p2
.x
do
156 for y
= p1
.y
, p2
.y
do
157 p
= {x
= x
, y
= y
, z
= p1
.z
}
158 if not ((x
== p1
.x
or x
== p2
.x
) and (y
== p1
.y
or y
== p2
.y
)) then
159 if not (x
== p1
.x
or x
== p2
.x
or y
== p1
.y
or y
== p2
.y
) then
160 minetest
.set_node(p
, {name
= "mcl_portals:portal", param2
= 0})
162 local meta
= minetest
.get_meta(p
)
163 meta
:set_string("portal_frame1", minetest
.pos_to_string(p1
))
164 meta
:set_string("portal_frame2", minetest
.pos_to_string(p2
))
165 meta
:set_string("portal_target", minetest
.pos_to_string(target
))
168 if y
~= p1
.y
and not is_rebuilding
then
172 if minetest
.registered_nodes
[
173 minetest
.get_node(p
).name
].is_ground_content
then
174 minetest
.remove_node(p
)
184 local function find_nether_target_y(target_x
, target_z
)
185 if mg_name
== "flat" then
186 return mcl_vars
.mg_bedrock_nether_bottom_max
+ 5
188 local start_y
= math
.random(mcl_vars
.mg_lava_nether_max
+ 1, mcl_vars
.mg_bedrock_nether_top_min
- 5) -- Search start
189 if not nobj_cave
then
190 nobj_cave
= minetest
.get_perlin(np_cave
)
194 for y
= start_y
, math
.max(mcl_vars
.mg_lava_nether_max
+ 1), -1 do
195 local nval_cave
= nobj_cave
:get3d({x
= target_x
, y
= y
, z
= target_z
})
197 if nval_cave
> TCAVE
then -- Cavern
199 else -- Not cavern, check if 4 nodes of space above
202 else -- Not enough space, reset air to zero
208 return start_y
-- Fallback
211 local function move_check(p1
, max, dir
)
212 local p
= {x
= p1
.x
, y
= p1
.y
, z
= p1
.z
}
213 local d
= math
.sign(max - p1
[dir
])
216 for k
= min, max, d
do
218 local node
= minetest
.get_node(p
)
219 -- Check for obsidian (except at corners)
220 if k
~= min and k
~= max and node
.name
~= "mcl_core:obsidian" then
223 -- Abort if any of the portal frame blocks already has metadata.
224 -- This mod does not yet portals which neighbor each other directly.
225 -- TODO: Reorganize the way how portal frame coordinates are stored.
226 if node
.name
== "mcl_core:obsidian" then
227 local meta
= minetest
.get_meta(p
)
228 local pframe1
= meta
:get_string("portal_frame1")
229 if minetest
.string_to_pos(pframe1
) ~= nil then
238 local function check_portal(p1
, p2
)
240 if not move_check(p1
, p2
.x
, "x") then
243 if not move_check(p2
, p1
.x
, "x") then
246 elseif p1
.z
~= p2
.z
then
247 if not move_check(p1
, p2
.z
, "z") then
250 if not move_check(p2
, p1
.z
, "z") then
257 if not move_check(p1
, p2
.y
, "y") then
260 if not move_check(p2
, p1
.y
, "y") then
267 local function is_portal(pos
)
268 local xsize
, ysize
= FRAME_SIZE_X_MIN
-1, FRAME_SIZE_Y_MIN
-1
269 for d
= -xsize
, xsize
do
270 for y
= -ysize
, ysize
do
271 local px
= {x
= pos
.x
+ d
, y
= pos
.y
+ y
, z
= pos
.z
}
272 local pz
= {x
= pos
.x
, y
= pos
.y
+ y
, z
= pos
.z
+ d
}
274 if check_portal(px
, {x
= px
.x
+ xsize
, y
= px
.y
+ ysize
, z
= px
.z
}) then
275 return px
, {x
= px
.x
+ xsize
, y
= px
.y
+ ysize
, z
= px
.z
}
277 if check_portal(pz
, {x
= pz
.x
, y
= pz
.y
+ ysize
, z
= pz
.z
+ xsize
}) then
278 return pz
, {x
= pz
.x
, y
= pz
.y
+ ysize
, z
= pz
.z
+ xsize
}
284 local function make_portal(pos
)
285 local p1
, p2
= is_portal(pos
)
286 if not p1
or not p2
then
291 for y
= p1
.y
+ 1, p2
.y
- 1 do
294 p
= {x
= p1
.x
+ d
, y
= y
, z
= p1
.z
}
296 p
= {x
= p1
.x
, y
= y
, z
= p1
.z
+ d
}
298 if minetest
.get_node(p
).name
~= "air" then
311 local target
= {x
= p1
.x
, y
= p1
.y
, z
= p1
.z
}
312 target
.x
= target
.x
+ 1
313 if target
.y
< mcl_vars
.mg_nether_max
and target
.y
> mcl_vars
.mg_nether_min
then
314 if mg_name
== "flat" then
315 target
.y
= mcl_vars
.mg_bedrock_overworld_max
+ 5
317 target
.y
= math
.random(mcl_vars
.mg_overworld_min
+ 40, mcl_vars
.mg_overworld_min
+ 96)
320 target
.y
= find_nether_target_y(target
.x
, target
.z
)
323 local dmin
, dmax
, ymin
, ymax
= 0, FRAME_SIZE_X_MIN
- 1, p1
.y
, p2
.y
324 for d
= dmin
, dmax
do
325 for y
= ymin
, ymax
do
326 if not ((d
== dmin
or d
== dmax
) and (y
== ymin
or y
== ymax
)) then
329 p
= {x
= p1
.x
+ d
, y
= y
, z
= p1
.z
}
331 p
= {x
= p1
.x
, y
= y
, z
= p1
.z
+ d
}
333 minetest
.set_node(p
, {name
= "mcl_portals:portal", param2
= param2
})
334 local meta
= minetest
.get_meta(p
)
336 -- Portal frame corners
337 meta
:set_string("portal_frame1", minetest
.pos_to_string(p1
))
338 meta
:set_string("portal_frame2", minetest
.pos_to_string(p2
))
340 -- Portal target coordinates
341 meta
:set_string("portal_target", minetest
.pos_to_string(target
))
350 minetest
.register_abm({
351 label
= "Nether portal teleportation and particles",
352 nodenames
= {"mcl_portals:portal"},
355 action
= function(pos
, node
)
356 minetest
.add_particlespawner(
359 {x
= pos
.x
- 0.25, y
= pos
.y
- 0.25, z
= pos
.z
- 0.25}, --minpos
360 {x
= pos
.x
+ 0.25, y
= pos
.y
+ 0.25, z
= pos
.z
+ 0.25}, --maxpos
361 {x
= -0.8, y
= -0.8, z
= -0.8}, --minvel
362 {x
= 0.8, y
= 0.8, z
= 0.8}, --maxvel
363 {x
= 0, y
= 0, z
= 0}, --minacc
364 {x
= 0, y
= 0, z
= 0}, --maxacc
369 false, --collisiondetection
370 "mcl_particles_teleport.png" --texture
372 for _
,obj
in ipairs(minetest
.get_objects_inside_radius(pos
,1)) do --maikerumine added for objects to travel
373 local lua_entity
= obj
:get_luaentity() --maikerumine added for objects to travel
374 if obj
:is_player() or lua_entity
then
375 -- Prevent quick back-and-forth teleportation
376 if portal_cooloff
[obj
] then
379 local meta
= minetest
.get_meta(pos
)
380 local target
= minetest
.string_to_pos(meta
:get_string("portal_target"))
382 -- force emerge of target area
383 minetest
.get_voxel_manip():read_from_map(target
, target
)
384 if not minetest
.get_node_or_nil(target
) then
385 minetest
.emerge_area(
386 vector
.subtract(target
, 4), vector
.add(target
, 4))
388 -- teleport the object
389 minetest
.after(3, function(obj
, pos
, target
)
390 -- Prevent quick back-and-forth teleportation
391 if portal_cooloff
[obj
] then
394 local objpos
= obj
:getpos()
395 if objpos
== nil then
398 -- If player stands, player is at ca. something+0.5
399 -- which might cause precision problems, so we used ceil.
400 objpos
.y
= math
.ceil(objpos
.y
)
402 if minetest
.get_node(objpos
).name
~= "mcl_portals:portal" then
406 -- Build target portal
407 local function check_and_build_portal(pos
, target
, is_rebuilding
)
408 -- FIXME: This is a horrible hack and a desparate attempt to make sure
409 -- the portal has *really* been placed. Replace this hack!
410 local n
= minetest
.get_node_or_nil(target
)
411 if n
and n
.name
~= "mcl_portals:portal" then
412 build_portal(target
, pos
, is_rebuilding
)
414 minetest
.after(2, check_and_build_portal
, pos
, target
, is_rebuilding
)
417 minetest
.after(1, check_and_build_portal
, pos
, target
, is_rebuilding
)
421 check_and_build_portal(pos
, target
, false)
425 minetest
.sound_play("mcl_portals_teleport", {pos
=target
, gain
=0.5, max_hear_distance
= 16})
427 -- Enable teleportation cooloff for 4 seconds, to prevent back-and-forth teleportation
428 portal_cooloff
[obj
] = true
429 minetest
.after(4, function(o
)
430 portal_cooloff
[o
] = false
433 end, obj
, pos
, target
)
441 --[[ ITEM OVERRIDES ]]
443 local longdesc
= minetest
.registered_nodes
["mcl_core:obsidian"]._doc_items_longdesc
444 longdesc
= longdesc
.. "\n" .. "Obsidian is also used as the frame of Nether portals."
445 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, ignite the obsidian with an appropriate tool, such as flint of steel."
447 minetest
.override_item("mcl_core:obsidian", {
448 _doc_items_longdesc
= longdesc
,
449 _doc_items_usagehelp
= usagehelp
,
450 on_destruct
= destroy_portal
,
451 _on_ignite
= function(user
, pointed_thing
)
452 local pos
= pointed_thing
.under
453 local portal_placed
= make_portal(pos
)
454 if portal_placed
and minetest
.get_modpath("doc") then
455 doc
.mark_entry_as_revealed(user
:get_player_name(), "nodes", "mcl_portals:portal")
457 -- Achievement for finishing a Nether portal TO the Nether
458 local _
, dim
= mcl_util
.y_to_layer(pos
.y
)
459 if minetest
.get_modpath("awards") and dim
~= "nether" and user
:is_player() then
460 awards
.unlock(user
:get_player_name(), "mcl:buildNetherPortal")
463 local node
= minetest
.get_node(pointed_thing
.above
)
464 if node
.name
~= "mcl_portals:portal" then
465 mcl_fire
.set_fire(pointed_thing
)