VoxeLibre/mods/ITEMS/mcl_portals/portal_nether.lua

520 lines
16 KiB
Lua
Raw Normal View History

local S = minetest.get_translator("mcl_portals")
-- 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 = 4 -- TODO: 23
local FRAME_SIZE_Y_MAX = 5 -- TODO: 23
local TELEPORT_DELAY = 3 -- seconds before teleporting in Nether portal
local TELEPORT_COOLOFF = 4 -- after object was teleported, for this many seconds it won't teleported again
local mg_name = minetest.get_mapgen_setting("mg_name")
local superflat = mg_name == "flat" and minetest.get_mapgen_setting("mcl_superflat_classic") == "true"
-- 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 = {}
2017-08-17 03:27:31 +02:00
-- 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)
2017-08-17 03:43:26 +02:00
local p1 = minetest.string_to_pos(meta:get_string("portal_frame1"))
local p2 = minetest.string_to_pos(meta:get_string("portal_frame2"))
2017-08-17 03:27:31 +02:00
if not p1 or not p2 then
return
end
local counter = 1
2017-08-17 03:27:31 +02:00
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)
2017-08-17 03:27:31 +02:00
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. ]]
2017-08-17 03:43:26 +02:00
mp1 = minetest.string_to_pos(m:get_string("portal_frame1"))
2017-08-17 03:27:31 +02:00
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
2017-08-17 03:43:26 +02:00
m:set_string("portal_frame1", "")
m:set_string("portal_frame2", "")
m:set_string("portal_target", "")
2017-08-17 03:27:31 +02:00
end
counter = counter + 1
2017-08-17 03:27:31 +02:00
end
end
end
end
minetest.register_node("mcl_portals:portal", {
description = S("Nether Portal"),
_doc_items_longdesc = S("A Nether portal teleports creatures and objects to the hot and dangerous Nether dimension (and back!). Enter at your own risk!"),
_doc_items_usagehelp = S("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."),
2017-08-17 18:41:58 +02:00
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,
2017-08-21 04:34:50 +02:00
post_effect_color = {a = 180, r = 51, g = 7, b = 89},
alpha = 192,
node_box = {
type = "fixed",
fixed = {
{-0.5, -0.5, -0.1, 0.5, 0.5, 0.1},
},
},
2019-12-13 10:20:08 +01:00
groups = {portal=1, not_in_creative_inventory = 1},
2017-08-17 03:27:31 +02:00
on_destruct = destroy_portal,
2017-08-17 03:27:31 +02:00
_mcl_hardness = -1,
_mcl_blast_resistance = 0,
})
-- Functions
--Build arrival portal
2017-09-19 15:08:46 +02:00
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
2017-09-19 15:08:46 +02:00
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
minetest.log("action", "[mcl_portal] Destination Nether portal generated at "..minetest.pos_to_string(p2).."!")
end
local function find_nether_target_y(target_x, target_z)
if mg_name == "flat" then
return mcl_vars.mg_flat_nether_floor + 1
end
local start_y = math.random(mcl_vars.mg_lava_nether_max + 1, mcl_vars.mg_bedrock_nether_top_min - 5) -- Search start
2017-08-17 01:09:32 +02:00
if not nobj_cave then
nobj_cave = minetest.get_perlin(np_cave)
end
local air = 4
for y = start_y, math.max(mcl_vars.mg_lava_nether_max + 1), -1 do
2019-03-06 04:38:57 +01:00
local nval_cave = nobj_cave:get_3d({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 sx = FRAME_SIZE_X_MIN, FRAME_SIZE_X_MAX do
local xsize = sx - 1
for sy = FRAME_SIZE_Y_MIN, FRAME_SIZE_Y_MAX do
local ysize = sy - 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
end
end
-- Attempts to light a Nether portal at pos and
-- select target position.
-- Pos can be any of the obsidian frame blocks or the inner part.
-- The frame MUST be filled only with air or any fire, which will be replaced with Nether portal blocks.
-- If no Nether portal can be lit, nothing happens.
-- Returns true on success and false on failure.
function mcl_portals.light_nether_portal(pos)
2017-11-21 02:05:52 +01:00
-- Only allow to make portals in Overworld and Nether
local dim = mcl_worlds.pos_to_dimension(pos)
2017-11-21 02:05:52 +01:00
if dim ~= "overworld" and dim ~= "nether" then
return false
end
2017-09-19 15:08:46 +02:00
-- Create Nether portal nodes
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
local nn = minetest.get_node(p).name
if nn ~= "air" and minetest.get_item_group(nn, "fire") ~= 1 then
return false
end
end
end
local param2
if p1.z == p2.z then
param2 = 0
else
param2 = 1
end
2017-09-19 15:08:46 +02:00
-- Find target
local target = {x = p1.x, y = p1.y, z = p1.z}
target.x = target.x + 1
2017-08-17 01:58:17 +02:00
if target.y < mcl_vars.mg_nether_max and target.y > mcl_vars.mg_nether_min then
if superflat then
target.y = mcl_vars.mg_bedrock_overworld_max + 5
elseif mg_name == "flat" then
local ground = minetest.get_mapgen_setting("mgflat_ground_level")
ground = tonumber(ground)
if not ground then
ground = 8
end
target.y = ground + 2
else
target.y = math.random(mcl_vars.mg_overworld_min + 40, mcl_vars.mg_overworld_min + 96)
end
else
target.y = find_nether_target_y(target.x, target.z)
end
local dmin, ymin, ymax = 0, p1.y, p2.y
local dmax = math.max(math.abs(p1.x - p2.x), math.abs(p1.z - p2.z))
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
2017-09-19 15:08:46 +02:00
if d ~= dmin and d ~= dmax and y ~= ymin and y ~= ymax then
minetest.set_node(p, {name = "mcl_portals:portal", param2 = param2})
end
local meta = minetest.get_meta(p)
2017-08-17 03:43:26 +02:00
-- 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)
2019-02-01 01:21:08 +01:00
minetest.add_particlespawner({
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 = "mcl_particles_teleport.png",
})
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)
2017-08-17 03:43:26 +02:00
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 function
local teleport = function(obj, pos, target)
if (not obj:get_luaentity()) and (not obj:is_player()) then
return
end
-- Prevent quick back-and-forth teleportation
if portal_cooloff[obj] then
return
end
2019-02-01 06:33:07 +01:00
local objpos = obj:get_pos()
2017-08-17 04:21:59 +02:00
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
2017-08-17 01:01:09 +02:00
-- Teleport
obj:set_pos(target)
2017-11-21 06:03:07 +01:00
if obj:is_player() then
mcl_worlds.dimension_change(obj, mcl_worlds.pos_to_dimension(target))
2020-04-07 00:55:45 +02:00
minetest.sound_play("mcl_portals_teleport", {pos=target, gain=0.5, max_hear_distance = 16}, true)
2017-11-21 06:03:07 +01:00
end
2017-08-17 01:01:09 +02:00
-- Enable teleportation cooloff for some seconds, to prevent back-and-forth teleportation
portal_cooloff[obj] = true
minetest.after(TELEPORT_COOLOFF, function(o)
portal_cooloff[o] = false
end, obj)
if obj:is_player() then
local name = obj:get_player_name()
minetest.log("action", "[mcl_portal] "..name.." teleported to Nether portal at "..minetest.pos_to_string(target)..".")
end
end
local n = minetest.get_node_or_nil(target)
if n and n.name ~= "mcl_portals:portal" then
-- Emerge target area, wait for emerging to be finished, build destination portal
-- (if there isn't already one, teleport object after a short delay.
local emerge_callback = function(blockpos, action, calls_remaining, param)
minetest.log("verbose", "[mcl_portal] emerge_callack called! action="..action)
if calls_remaining <= 0 and action ~= minetest.EMERGE_CANCELLED and action ~= minetest.EMERGE_ERRORED then
minetest.log("verbose", "[mcl_portal] Area for destination Nether portal emerged!")
build_portal(param.target, param.pos, false)
minetest.after(TELEPORT_DELAY, teleport, obj, pos, target)
end
end
minetest.log("verbose", "[mcl_portal] Emerging area for destination Nether portal ...")
minetest.emerge_area(vector.subtract(target, 7), vector.add(target, 7), emerge_callback, { pos = pos, target = target })
else
minetest.after(TELEPORT_DELAY, teleport, obj, pos, target)
end
2017-08-17 01:01:09 +02:00
end
end
end
end,
})
--[[ ITEM OVERRIDES ]]
local longdesc = minetest.registered_nodes["mcl_core:obsidian"]._doc_items_longdesc
longdesc = longdesc .. "\n" .. S("Obsidian is also used as the frame of Nether portals.")
local usagehelp = S("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.")
minetest.override_item("mcl_core:obsidian", {
_doc_items_longdesc = longdesc,
_doc_items_usagehelp = usagehelp,
2017-08-17 03:27:31 +02:00
on_destruct = destroy_portal,
_on_ignite = function(user, pointed_thing)
local pos = pointed_thing.under
local portal_placed = mcl_portals.light_nether_portal(pos)
if portal_placed then
minetest.log("action", "[mcl_portal] Nether portal activated at "..minetest.pos_to_string(pos)..".")
end
if portal_placed and minetest.get_modpath("doc") then
doc.mark_entry_as_revealed(user:get_player_name(), "nodes", "mcl_portals:portal")
-- Achievement for finishing a Nether portal TO the Nether
local dim = mcl_worlds.pos_to_dimension(pos)
if minetest.get_modpath("awards") and dim ~= "nether" and user:is_player() then
awards.unlock(user:get_player_name(), "mcl:buildNetherPortal")
end
return true
else
return false
end
end,
})