-- Parameters local TCAVE = 0.6 local nobj_cave = nil -- Portal frame sizes local FRAME_SIZE_X_MIN = 4 local FRAME_SIZE_Y_MIN = 5 local FRAME_SIZE_X_MAX = 23 local FRAME_SIZE_Y_MAX = 23 -- 3D noise local np_cave = { offset = 0, scale = 1, spread = {x = 384, y = 128, z = 384}, seed = 59033, octaves = 5, persist = 0.7 } -- Table of objects (including players) which recently teleported by a -- Nether portal. Those objects have a brief cooloff period before they -- can teleport again. This prevents annoying back-and-forth teleportation. local portal_cooloff = {} -- Destroy portal if pos (portal frame or portal node) got destroyed local destroy_portal = function(pos) -- Deactivate Nether portal local meta = minetest.get_meta(pos) local p1 = minetest.string_to_pos(meta:get_string("portal_frame1")) local p2 = minetest.string_to_pos(meta:get_string("portal_frame2")) if not p1 or not p2 then return end local counter = 1 local mp1 for x = p1.x, p2.x do for y = p1.y, p2.y do for z = p1.z, p2.z do local p = vector.new(x, y, z) local m = minetest.get_meta(p) if counter == 2 then --[[ Only proceed if the second node still has metadata. (first node is a corner and not needed for the portal) If it doesn't have metadata, another node propably triggred the delection routine earlier, so we bail out earlier to avoid an infinite cascade of on_destroy events. ]] mp1 = minetest.string_to_pos(m:get_string("portal_frame1")) if not mp1 then return end end local nn = minetest.get_node(p).name if nn == "mcl_core:obsidian" or nn == "mcl_portals:portal" then -- Remove portal nodes, but not myself if nn == "mcl_portals:portal" and not vector.equals(p, pos) then minetest.remove_node(p) end -- Clear metadata of portal nodes and the frame m:set_string("portal_frame1", "") m:set_string("portal_frame2", "") m:set_string("portal_target", "") end counter = counter + 1 end end end end minetest.register_node("mcl_portals:portal", { description = "Nether Portal", _doc_items_longdesc = "A Nether portal teleports creatures and objects to the hot and dangerous Nether dimension (and back!). Enter at your own risk!", _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.", tiles = { "blank.png", "blank.png", "blank.png", "blank.png", { name = "mcl_portals_portal.png", animation = { type = "vertical_frames", aspect_w = 16, aspect_h = 16, length = 0.5, }, }, { name = "mcl_portals_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, is_ground_content = false, drop = "", light_source = 11, post_effect_color = {a = 180, r = 128, g = 23, b = 23}, 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}, on_destruct = destroy_portal, _mcl_hardness = -1, _mcl_blast_resistance = 0, }) -- Functions --Build arrival portal local function build_portal(pos, target) local p = {x = pos.x - 1, y = pos.y - 1, z = pos.z} local p1 = {x = pos.x - 1, y = pos.y - 1, z = pos.z} local p2 = {x = p1.x + 3, y = p1.y + 4, z = p1.z} for i = 1, FRAME_SIZE_Y_MIN - 1 do minetest.set_node(p, {name = "mcl_core:obsidian"}) p.y = p.y + 1 end for i = 1, FRAME_SIZE_X_MIN - 1 do minetest.set_node(p, {name = "mcl_core:obsidian"}) p.x = p.x + 1 end for i = 1, FRAME_SIZE_Y_MIN - 1 do minetest.set_node(p, {name = "mcl_core:obsidian"}) p.y = p.y - 1 end for i = 1, FRAME_SIZE_X_MIN - 1 do minetest.set_node(p, {name = "mcl_core:obsidian"}) p.x = p.x - 1 end for x = p1.x, p2.x do for y = p1.y, p2.y do p = {x = x, y = y, z = p1.z} if not ((x == p1.x or x == p2.x) and (y == p1.y or y == p2.y)) then if not (x == p1.x or x == p2.x or y == p1.y or y == p2.y) then minetest.set_node(p, {name = "mcl_portals:portal", param2 = 0}) end local meta = minetest.get_meta(p) meta:set_string("portal_frame1", minetest.pos_to_string(p1)) meta:set_string("portal_frame2", minetest.pos_to_string(p2)) meta:set_string("portal_target", minetest.pos_to_string(target)) end if y ~= p1.y then for z = -2, 2 do if z ~= 0 then p.z = p.z + z if minetest.registered_nodes[ minetest.get_node(p).name].is_ground_content then minetest.remove_node(p) end p.z = p.z - z end end end end end end local function find_nether_target_y(target_x, target_z) local start_y = mcl_vars.mg_nether_min + math.random(38, 117) -- Search start if not nobj_cave then nobj_cave = minetest.get_perlin(np_cave) end local air = 4 for y = start_y, start_y -117, -1 do local nval_cave = nobj_cave: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 return y + 2 else -- Not enough space, reset air to zero air = 0 end end end return start_y -- Fallback end local function move_check(p1, max, dir) local p = {x = p1.x, y = p1.y, z = p1.z} local d = math.sign(max - p1[dir]) local min = p[dir] for k = min, max, d do p[dir] = k local node = minetest.get_node(p) -- Check for obsidian (except at corners) if k ~= min and k ~= max and node.name ~= "mcl_core:obsidian" then return false end -- Abort if any of the portal frame blocks already has metadata. -- This mod does not yet portals which neighbor each other directly. -- TODO: Reorganize the way how portal frame coordinates are stored. if node.name == "mcl_core:obsidian" then local meta = minetest.get_meta(p) local pframe1 = meta:get_string("portal_frame1") if minetest.string_to_pos(pframe1) ~= nil then return false end end end return true end local function check_portal(p1, p2) if p1.x ~= p2.x then if not move_check(p1, p2.x, "x") then return false end if not move_check(p2, p1.x, "x") then return false end elseif p1.z ~= p2.z then if not move_check(p1, p2.z, "z") then return false end if not move_check(p2, p1.z, "z") then return false end else return false end if not move_check(p1, p2.y, "y") then return false end if not move_check(p2, p1.y, "y") then return false end return true end local function is_portal(pos) local xsize, ysize = FRAME_SIZE_X_MIN-1, FRAME_SIZE_Y_MIN-1 for d = -xsize, xsize do for y = -ysize, ysize do local px = {x = pos.x + d, y = pos.y + y, z = pos.z} local pz = {x = pos.x, y = pos.y + y, z = pos.z + d} if check_portal(px, {x = px.x + xsize, y = px.y + ysize, z = px.z}) then return px, {x = px.x + xsize, y = px.y + ysize, z = px.z} end if check_portal(pz, {x = pz.x, y = pz.y + ysize, z = pz.z + xsize}) then return pz, {x = pz.x, y = pz.y + ysize, z = pz.z + xsize} end end end end local function make_portal(pos) local p1, p2 = is_portal(pos) if not p1 or not p2 then return false end for d = 1, 2 do for y = p1.y + 1, p2.y - 1 do local p if p1.z == p2.z then p = {x = p1.x + d, y = y, z = p1.z} else p = {x = p1.x, y = y, z = p1.z + d} end if minetest.get_node(p).name ~= "air" then return false end end end local param2 if p1.z == p2.z then param2 = 0 else param2 = 1 end local target = {x = p1.x, y = p1.y, z = p1.z} target.x = target.x + 1 if target.y < mcl_vars.mg_nether_max and target.y > mcl_vars.mg_nether_min then target.y = math.random(mcl_vars.mg_overworld_min + 40, mcl_vars.mg_overworld_min + 96) else target.y = find_nether_target_y(target.x, target.z) end local dmin, dmax, ymin, ymax = 0, FRAME_SIZE_X_MIN - 1, p1.y, p2.y for d = dmin, dmax do for y = ymin, ymax do if not ((d == dmin or d == dmax) and (y == ymin or y == ymax)) then local p if param2 == 0 then p = {x = p1.x + d, y = y, z = p1.z} else p = {x = p1.x, y = y, z = p1.z + d} end if minetest.get_node(p).name == "air" then minetest.set_node(p, {name = "mcl_portals:portal", param2 = param2}) end local meta = minetest.get_meta(p) -- Portal frame corners meta:set_string("portal_frame1", minetest.pos_to_string(p1)) meta:set_string("portal_frame2", minetest.pos_to_string(p2)) -- Portal target coordinates meta:set_string("portal_target", minetest.pos_to_string(target)) end end end return true end minetest.register_abm({ label = "Nether portal teleportation and particles", nodenames = {"mcl_portals:portal"}, interval = 1, chance = 2, action = function(pos, node) minetest.add_particlespawner( 32, --amount 4, --time {x = pos.x - 0.25, y = pos.y - 0.25, z = pos.z - 0.25}, --minpos {x = pos.x + 0.25, y = pos.y + 0.25, z = pos.z + 0.25}, --maxpos {x = -0.8, y = -0.8, z = -0.8}, --minvel {x = 0.8, y = 0.8, z = 0.8}, --maxvel {x = 0, y = 0, z = 0}, --minacc {x = 0, y = 0, z = 0}, --maxacc 0.5, --minexptime 1, --maxexptime 1, --minsize 2, --maxsize false, --collisiondetection "mcl_portals_particle.png" --texture ) for _,obj in ipairs(minetest.get_objects_inside_radius(pos,1)) do --maikerumine added for objects to travel local lua_entity = obj:get_luaentity() --maikerumine added for objects to travel if obj:is_player() or lua_entity then -- Prevent quick back-and-forth teleportation if portal_cooloff[obj] then return end local meta = minetest.get_meta(pos) local target = minetest.string_to_pos(meta:get_string("portal_target")) if target then -- force emerge of target area minetest.get_voxel_manip():read_from_map(target, target) if not minetest.get_node_or_nil(target) then minetest.emerge_area( vector.subtract(target, 4), vector.add(target, 4)) end -- teleport the object minetest.after(3, function(obj, pos, target) -- Prevent quick back-and-forth teleportation if portal_cooloff[obj] then return end local objpos = obj:getpos() if objpos == nil then return end -- If player stands, player is at ca. something+0.5 -- which might cause precision problems, so we used ceil. objpos.y = math.ceil(objpos.y) if minetest.get_node(objpos).name ~= "mcl_portals:portal" then return end -- Build target portal local function check_and_build_portal(pos, target) local n = minetest.get_node_or_nil(target) if n and n.name ~= "mcl_portals:portal" then build_portal(target, pos) minetest.after(2, check_and_build_portal, pos, target) elseif not n then minetest.after(1, check_and_build_portal, pos, target) end end check_and_build_portal(pos, target) -- Teleport obj:setpos(target) minetest.sound_play("mcl_portals_teleport", {pos=target, gain=0.5, max_hear_distance = 16}) -- Enable teleportation cooloff for 4 seconds, to prevent back-and-forth teleportation portal_cooloff[obj] = true minetest.after(4, function(o) portal_cooloff[o] = false end, obj) end, obj, pos, target) end end end end, }) --[[ ITEM OVERRIDES ]] -- Frame material minetest.override_item("mcl_core:obsidian", { on_destruct = destroy_portal, _on_ignite = function(user, pointed_thing) local pos = pointed_thing.under local portal_placed = make_portal(pos) if portal_placed and minetest.get_modpath("doc") then doc.mark_entry_as_revealed(user:get_player_name(), "nodes", "mcl_portals:portal") else local node = minetest.get_node(pointed_thing.above) if node.name ~= "mcl_portals:portal" then mcl_fire.set_fire(pointed_thing) end end end, })