--code copied from Pilzadam's nether mod and edited local nether_start = nether.v.nether_start local nether_bottom = nether.v.nether_bottom local nether_buildings = nether.v.nether_buildings -- kills the player if he uses PilzAdam portal local portal_target = nether_buildings+1 local damage_enabled = minetest.settings:get_bool"enable_damage" local mclike_portal = false local abm_allowed minetest.after(5, function() abm_allowed = true end) local save_path = minetest.get_worldpath() .. "/nether_players" local players_trapped_in_nether = {} -- Load the list of players which are trapped in the nether -- (or would be trapped if nether.trap_players was true) do local file = io.open(save_path, "r") if file then local contents = file:read"*all" io.close(file) if contents then local playernames = string.split(contents, " ") for i = 1,#playernames do players_trapped_in_nether[playernames[i]] = true end end end end local function save_nether_players() local playernames,n = {},1 for name in pairs(players_trapped_in_nether) do playernames[n] = name n = n+1 end local f = io.open(save_path, "w") assert(f, "Could not open nether_players file for writing.") f:write(table.concat(playernames, " ")) io.close(f) end -- Nether aware mods will need to know if a player is in the nether. function nether.is_player_in_nether(player) local pos = player:get_pos() return (pos.y < nether_start) and (pos.y >= nether_bottom) end -- For testing nether trap state tracking. function nether.is_player_trapped_in_nether(player) return players_trapped_in_nether[player:get_player_name()] end local update_background if nether.trap_players then function update_background(player, down) if down then player:set_sky({r=15, g=0, b=0}, "plain") else player:set_sky(nil, "regular") end end else function update_background()end end -- Nether aware mods may have other means of moving players between the Nether -- and Overworld, and if so, they should tell us about it so we can keep track -- of the player state. function nether.external_nether_teleport(player, pos) if not nether.trap_players then player:set_pos(pos) return end local destination_in_nether = (pos.y < nether_start) and (pos.y >= nether_bottom) update_background(player, destination_in_nether) local pname = player:get_player_name() players_trapped_in_nether[pname] = destination_in_nether or nil player:set_pos(pos) end -- Has the player dug their way out of the nether? -- Has nether.trap_players been disabled? function nether.registry_update(player) local pos = player:get_pos() local in_nether = (pos.y < nether_start) and (pos.y >= nether_bottom) local pname = player:get_player_name() if nether.trap_players then players_trapped_in_nether[pname] = in_nether or nil update_background(player, in_nether) elseif players_trapped_in_nether[pname] then players_trapped_in_nether[pname] = nil update_background(player, false) end end -- returns nodename if area is generated, else calls generation function local function generated_or_generate(pos) local node = minetest.get_node_or_nil(pos) if node then return node.name end minetest.get_voxel_manip():read_from_map(pos, pos) node = minetest.get_node_or_nil(pos) if not node then minetest.emerge_area(vector.subtract(pos, 80), vector.add(pos, 80)) return false end return node.name end -- where the player appears after dying local function get_player_died_target(player) local target = vector.add(player:get_pos(), {x=math.random(-100,100), y=0, z=math.random(-100,100)}) target.y = portal_target + math.random(4) return target end -- used for obsidian portal local function obsidian_teleport(player, pname, target) minetest.chat_send_player(pname, "For some reason you arrived here. Type " .. "/nether_help to find out things like craft recipes.") players_trapped_in_nether[pname] = true save_nether_players() update_background(player, true) if target then player:set_pos(target) else player:set_hp(0) end end -- teleports players to nether or helps it local function player_to_nether(player, pos) local pname = player:get_player_name() players_trapped_in_nether[pname] = true save_nether_players() update_background(player, true) if pos then player:set_pos(pos) return end minetest.chat_send_player(pname, "For some reason you arrived here. " .. "Type /nether_help to find out things like craft recipes.") if nether.trap_players then player:set_hp(0) end if not damage_enabled or not nether.trap_players then player:set_pos(get_player_died_target(player)) end end local function player_from_nether(player, pos) local pname = player:get_player_name() if players_trapped_in_nether[pname] then players_trapped_in_nether[pname] = nil save_nether_players() end update_background(player, false) player:set_pos(pos) end local function player_exists(name) local players = minetest.get_connected_players() for i = 1,#players do if players[i]:get_player_name() == name then return true end end return false end -- Chatcommands (edited) written by sss minetest.register_chatcommand("to_hell", { params = "[]", description = "Send someone to hell", func = function(name, pname) if not minetest.check_player_privs(name, {nether=true}) then return false, "You need the nether privilege to execute this chatcommand." end if not player_exists(pname) then pname = name end local player = minetest.get_player_by_name(pname) if not player then return false, "Something went wrong." end minetest.chat_send_player(pname, "Go to hell !!!") player_to_nether(player) return true, pname.." is now in the nether." end }) minetest.register_chatcommand("from_hell", { params = "[]", description = "Extract from hell", func = function(name, pname) if not minetest.check_player_privs(name, {nether=true}) then return false, "You need the nether priv to execute this chatcommand." end if not player_exists(pname) then pname = name end local player = minetest.get_player_by_name(pname) if not player then return false, "Something went wrong." end minetest.chat_send_player(pname, "You are free now") local pos = player:get_pos() player_from_nether(player, {x=pos.x, y=100, z=pos.z}) return true, pname.." is now out of the nether." end }) -- Useful for debugging Nether player state tracking. Written by Deathwing777 minetest.register_chatcommand("in_hell", { params = "[]", description = "Is the player in hell?", func = function(name, pname) if not minetest.check_player_privs(name, {nether=true}) then return false, "You need the nether priv to execute this chatcommand." end if not player_exists(pname) then pname = name end local player = minetest.get_player_by_name(pname) if not player then return false, "Something went wrong." end local status = pname.." is in the " if nether.is_player_in_nether(player) then status = status.."NETHER!" else status = status.."OVERWORLD!" end return true, status end }) -- Useful for debugging Nether player state tracking. Written by Deathwing777 minetest.register_chatcommand("trapped_in_hell", { params = "[]", description = "Is the player trapped in hell?", func = function(name, pname) if not minetest.check_player_privs(name, {nether=true}) then return false, "You need the nether priv to execute this chatcommand." end if not player_exists(pname) then pname = name end local player = minetest.get_player_by_name(pname) if not player then return false, "Something went wrong." end local status = pname if nether.is_player_trapped_in_nether(player) then status = status.." is TRAPPED in nether!" else status = status.." is NOT trapped in nether!" end return true, status end }) -- Useful for debugging Nether player state tracking. Written by Deathwing777 minetest.register_chatcommand("update_hells_registry", { params = "[]", description = "Update player state if they got to or from the nether in another way.", func = function(name, pname) if not minetest.check_player_privs(name, {nether=true}) then return false, "You need the nether priv to execute this chatcommand." end if not player_exists(pname) then pname = name end local player = minetest.get_player_by_name(pname) if not player then return false, "Something went wrong." end nether.registry_update(player) local status = pname if nether.is_player_trapped_in_nether(player) then status = status.." is TRAPPED in nether!" else status = status.." is NOT trapped in nether!" end return true, status end }) -- Disallow teleportation and change spawn positions if the nether traps players if nether.trap_players then -- randomly set player position when he/she dies in nether minetest.register_on_respawnplayer(function(player) local pname = player:get_player_name() if not players_trapped_in_nether[pname] then return end local target = get_player_died_target(player) player:set_pos(target) minetest.after(0, function(pname, target) -- fixes respawn bug local player = minetest.get_player_by_name(pname) if player then player:move_to(target) end end, pname, target) return true end) -- override set_pos etc, to disallow player teleportion by e.g. travelnet local function can_teleport(player, pos) if not player:is_player() then -- the same metatable is used for entities return true end local pname = player:get_player_name() local in_nether = players_trapped_in_nether[pname] == true -- test if the target is valid if pos.y < nether_start then if in_nether then return true end elseif not in_nether then return true end -- test if the current position is valid local current_pos = player:get_pos() local now_in_nether = current_pos.y < nether_start if now_in_nether ~= in_nether then if in_nether then minetest.log("action", "Player \"" .. pname .. "\" has to be in the nether, teleporting it!") update_background(player, true) current_pos.y = portal_target player:set_pos(current_pos) else minetest.log("action", "Player \"" .. pname .. "\" must not be in the nether, teleporting it!") update_background(player, false) current_pos.y = 20 player:set_pos(current_pos) end return false end minetest.chat_send_player(pname, "You can not simply teleport to or from the nether!") minetest.log("action", "Player \"" .. pname .. "\" attempted to teleport from or to the nether, ignoring.") return false end local methods = {"set_pos", "move_to", "setpos", "moveto"} local metatable_overridden minetest.register_on_joinplayer(function(player) -- set the background when the player joins if player:get_pos().y < nether_start then update_background(player, true) end -- overide set_pos etc. if not yet done if metatable_overridden then return end metatable_overridden = true local mt = getmetatable(player) for i = 1,#methods do local methodname = methods[i] local origfunc = mt[methodname] mt[methodname] = function(...) if can_teleport(...) then origfunc(...) end end end end) end -- removes the violet stuff from the obsidian portal local function remove_portal_essence(pos) for z = -1,1 do for y = -2,2 do for x = -1,1 do local p = {x=pos.x+x, y=pos.y+y, z=pos.z+z} if minetest.get_node(p).name == "nether:portal" then minetest.remove_node(p) end end end end end -- change parts of the particledefinition instead of recreating it every time local particledef = { amount = 32, time = 4, minvel = {x=0, y=1, z=0}, maxvel = {x=0, y=2, z=0}, minacc = {x=-0.5,y=-3,z=-0.3}, maxacc = {x=0.5,y=-0.4,z=0.3}, minexptime = 1, maxexptime = 1, minsize = 0.4, maxsize = 3, collisiondetection = true, } -- teleports player to neter (obsidian portal) local function obsi_teleport_player(player, pos, target) local pname = player:get_player_name() if players_trapped_in_nether[pname] then return end local objpos = player:get_pos() objpos.y = objpos.y+0.1 -- Fix some glitches at -8000 if minetest.get_node(vector.round(objpos)).name ~= "nether:portal" then return end local has_teleported if (damage_enabled and nether.trap_players) then obsidian_teleport(player, pname) has_teleported = true elseif not mclike_portal then local target = vector.round(get_player_died_target(player)) if generated_or_generate(target) then obsidian_teleport(player, pname, target) has_teleported = true end end if not has_teleported then -- e.g. ungenerated area return end remove_portal_essence(pos) minetest.sound_play("nether_portal_usual", {to_player=pname, gain=1}) end -- abm for particles of the obsidian portal essence and for teleporting minetest.register_abm({ nodenames = {"nether:portal"}, interval = 1, chance = 2, catch_up = false, action = function(pos, node) if not abm_allowed then return end particledef.minpos = {x=pos.x-0.25, y=pos.y-0.5, z=pos.z-0.25} particledef.maxpos = {x=pos.x+0.25, y=pos.y+0.34, z=pos.z+0.25} particledef.texture = "nether_portal_particle.png^[transform" .. math.random(0, 7) minetest.add_particlespawner(particledef) for _,obj in pairs(minetest.get_objects_inside_radius(pos, 1)) do if obj:is_player() then local meta = minetest.get_meta(pos) local target = minetest.string_to_pos(meta:get_string("target")) if target then minetest.after(3, obsi_teleport_player, obj, pos, target) end end end end, }) local function move_check(p1, max, dir) local p = {x=p1.x, y=p1.y, z=p1.z} local d = math.abs(max-p1[dir]) / (max-p1[dir]) while p[dir] ~= max do p[dir] = p[dir] + d if minetest.get_node(p).name ~= "default:obsidian" then return false 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 -- tests if it's an obsidian portal local function is_portal(pos) for d=-3,3 do for y=-4,4 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+3, y=px.y+4, z=px.z}) then return px, {x=px.x+3, y=px.y+4, z=px.z} end if check_portal(pz, {x=pz.x, y=pz.y+4, z=pz.z+3}) then return pz, {x=pz.x, y=pz.y+4, z=pz.z+3} end end end end -- put here the function for creating a second portal local create_second_portal if mclike_portal then function create_second_portal(target) -- change target here end end -- adds the violet portal essence local function make_portal(pos) local p1, p2 = is_portal(pos) if not p1 or not p2 then print("[nether] something failed.") return false end local in_nether = p1.y < nether_start if in_nether and not mclike_portal then print("[nether] aborted, obsidian portals can't be used to get out") return 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 in_nether then target.y = 0 create_second_portal(target) else target.y = portal_target + math.random(4) end if not generated_or_generate(target) and mclike_portal then return false end for d=0,3 do for y=p1.y,p2.y do 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="nether:portal", param2=param2}) end local meta = minetest.get_meta(p) 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(target)) end end print("[nether] construction accepted.") return true end -- destroy the portal when destroying obsidian minetest.override_item("default:obsidian", { on_destruct = function(pos) 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 == "default:obsidian" or nn == "nether:portal" then if nn == "nether:portal" 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 == "default:obsidian" or nn == "nether:portal" then if nn == "nether:portal" 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 }) -- override mese crystal fragment for making an obsidian portal minetest.after(0.1, function() minetest.override_item("default:mese_crystal_fragment", { on_place = function(stack, player, pt) if pt.under and minetest.get_node(pt.under).name == "default:obsidian" then local done = make_portal(pt.under) if done then local pname = player:get_player_name() minetest.chat_send_player(pname, "Warning: If you are in the nether you may not be " .. "able to find the way out!") if not minetest.is_creative_enabled(pname) then stack:take_item() end end end return stack end }) end) -- Get positions for an empty square around the origin local function vector_square(r) local tab, n = {}, 1 for i = -r+1, r do for j = -1, 1, 2 do local a, b = r*j, i*j tab[n] = {a, -b} tab[n+1] = {b, a} n=n+2 end end return tab end local function is_netherportal(pos) local x, y, z = pos.x, pos.y, pos.z for _,i in pairs({-1, 3}) do if minetest.get_node({x=x, y=y+i, z=z}).name ~= "nether:white" then return end end for _,sn in pairs(vector_square(1)) do if minetest.get_node({x=x+sn[1], y=y-1, z=z+sn[2]}).name ~= "nether:netherrack" or minetest.get_node({x=x+sn[1], y=y+3, z=z+sn[2]}).name ~= "nether:blood_cooked" then return end end for _,sn in pairs(vector_square(2)) do if minetest.get_node({x=x+sn[1], y=y-1, z=z+sn[2]}).name ~= "nether:netherrack_black" or minetest.get_node({x=x+sn[1], y=y+3, z=z+sn[2]}).name ~= "nether:wood_empty" then return end end for i = -1,1,2 do for j = -1,1,2 do if minetest.get_node({x=x+i, y=y+2, z=z+j}).name ~= "nether:apple" then return end end end for i = -2,2,4 do for j = 0,2 do for k = -2,2,4 do if minetest.get_node({x=x+i, y=y+j, z=z+k}).name ~= "nether:netherrack_brick_blue" then return end end end end for i = -1,1 do for j = -1,1 do if minetest.get_node({x=x+i, y=y+4, z=z+j}).name ~= "nether:wood_empty" then return end end end return true end -- cache known portals local known_portals_d = {} local known_portals_u = {} local function get_portal(t, z,x) return t[z] and t[z][x] end local function set_portal(t, z,x, y) t[z] = t[z] or {} t[z][x] = y end local function get_player_nodepos(player) local pos = player:get_pos() pos.y = pos.y + player:get_properties().collisionbox[2] + 0.5 return vector.round(pos) end -- used when a player eats that fruit in a portal function nether.teleport_player(player) if not player then minetest.log("error", "[nether] Missing player.") return end local pos = get_player_nodepos(player) if not is_netherportal(pos) then return end minetest.sound_play("nether_teleporter", {pos=pos}) local meta = minetest.get_meta({x=pos.x, y=pos.y-1, z=pos.z}) if pos.y < nether_start then set_portal(known_portals_d, pos.z,pos.x, pos.y) local my = tonumber(meta:get_string("y")) local y = get_portal(known_portals_u, pos.z,pos.x) if y then if y ~= my then meta:set_string("y", y) end else y = my or 100 end pos.y = y - 0.3 player_from_nether(player, pos) else set_portal(known_portals_u, pos.z,pos.x, pos.y) local my = tonumber(meta:get_string("y")) local y = get_portal(known_portals_d, pos.z,pos.x) if y then if y ~= my then meta:set_string("y", y) end else y = my or portal_target+math.random(4) end pos.y = y - 0.3 player_to_nether(player, pos) end minetest.sound_play("nether_teleporter", {pos=pos}) return true end