VoxeLibre/mods/ITEMS/mcl_portals/portal_end.lua
2017-08-17 13:39:25 +02:00

414 lines
11 KiB
Lua

-- Parameters
local TCAVE = 0.6
local nobj_cave = nil
-- 3D noise
local np_cave = {
offset = 0,
scale = 1,
spread = {x = 384, y = 128, z = 384}, -- squashed 3:1
seed = 59033,
octaves = 5,
persist = 0.7
}
-- Portal frame material
local portal_frame = "mcl_nether:quartz_block"
-- 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 first = true
-- p1 metadata of first node
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 first then
--[[ Only proceed if the first node still has metadata.
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 == portal_frame or nn == "mcl_portals:portal_end" then
-- Remove portal nodes, but not myself
if nn == "mcl_portals:portal_end" 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
first = false
end
end
end
end
-- Nodes
minetest.register_node("mcl_portals:portal_end", {
description = "End Portal",
tiles = {
"blank.png",
"blank.png",
"blank.png",
"blank.png",
{
name = "mcl_portals_end_portal.png",
animation = {
type = "vertical_frames",
aspect_w = 16,
aspect_h = 16,
length = 2.0,
},
},
{
name = "mcl_portals_end_portal.png",
animation = {
type = "vertical_frames",
aspect_w = 16,
aspect_h = 16,
length = 2.0,
},
},
},
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 = "",
-- This is 15 in MC.
light_source = 14,
post_effect_color = {a = 192, r = 0, g = 0, b = 0},
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 = 18000000,
})
local function build_end_portal(pos, target3)
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, 4 do
minetest.set_node(p, {name = portal_frame})
p.y = p.y + 1
end
for i = 1, 3 do
minetest.set_node(p, {name = portal_frame})
p.x = p.x + 1
end
for i = 1, 4 do
minetest.set_node(p, {name = portal_frame})
p.y = p.y - 1
end
for i = 1, 3 do
minetest.set_node(p, {name = portal_frame})
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 or y == p1.y or y == p2.y) then
minetest.set_node(p, {name = "mcl_portals:portal_end", 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(target3))
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_end_target3_y2(target3_x, target3_z)
local start_y = mcl_vars.mg_end_min + math.random(20, 120) -- Search start
if not nobj_cave then
nobj_cave = minetest.get_perlin(np_cave)
end
local air = 0 -- Consecutive air nodes found
for y = start_y, start_y - 120, -1 do
local nval_cave = nobj_cave:get3d({x = target3_x, y = y, z = target3_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_check2(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 ~= portal_frame 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.
local meta = minetest.get_meta(p)
local p1 = meta:get_string("portal_frame1")
if minetest.string_to_pos(p1) ~= nil then
return false
end
end
return true
end
local function check_end_portal(p1, p2)
if p1.x ~= p2.x then
if not move_check2(p1, p2.x, "x") then
return false
end
if not move_check2(p2, p1.x, "x") then
return false
end
elseif p1.z ~= p2.z then
if not move_check2(p1, p2.z, "z") then
return false
end
if not move_check2(p2, p1.z, "z") then
return false
end
else
return false
end
if not move_check2(p1, p2.y, "y") then
return false
end
if not move_check2(p2, p1.y, "y") then
return false
end
return true
end
local function is_end_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_end_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_end_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
local function make_end_portal(pos)
local p1, p2 = is_end_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 target3 = {x = p1.x, y = p1.y, z = p1.z}
target3.x = target3.x + 1
if target3.y < mcl_vars.mg_end_max and target3.y > mcl_vars.mg_end_min then
target3.y = math.random(mcl_vars.mg_overworld_min + 40, mcl_vars.mg_overworld_min + 96)
else
target3.y = find_end_target3_y2(target3.x, target3.z)
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 = "mcl_portals:portal_end", 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(target3))
end
end
return true
end
minetest.register_abm({
label = "End portal teleportation",
nodenames = {"mcl_portals:portal_end"},
interval = 1,
chance = 2,
action = function(pos, node)
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
local meta = minetest.get_meta(pos)
local target3 = minetest.string_to_pos(meta:get_string("portal_target"))
if target3 then
-- force emerge of target3 area
minetest.get_voxel_manip():read_from_map(target3, target3)
if not minetest.get_node_or_nil(target3) then
minetest.emerge_area(
vector.subtract(target3, 4), vector.add(target3, 4))
end
-- teleport the player
minetest.after(3, function(obj, pos, target3)
local objpos = obj:getpos()
-- 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 objpos == nil then return end --maikerumine added for objects to travel
if minetest.get_node(objpos).name ~= "mcl_portals:portal_end" then
return
end
-- Build destination
local function check_and_build_end_portal(pos, target3)
local n = minetest.get_node_or_nil(target3)
if n and n.name ~= "mcl_portals:portal_end" then
build_end_portal(target3, pos)
minetest.after(2, check_and_build_end_portal, pos, target3)
elseif not n then
minetest.after(1, check_and_build_end_portal, pos, target3)
end
end
check_and_build_end_portal(pos, target3)
-- Teleport
obj:setpos(target3)
minetest.sound_play("mcl_portals_teleport", {pos=target3, gain=0.5, max_hear_distance = 16})
end, obj, pos, target3)
end
end
end
end,
})
--[[ ITEM OVERRIDES ]]
-- Frame material
minetest.override_item(portal_frame, {
on_destruct = destroy_portal,
})
-- Portal opener
minetest.override_item("mcl_end:ender_eye", {
_doc_items_longdesc = "An eye of ander can be used to open a portal to the End.",
_doc_items_usagehelp = "To open an End portal, place an upright frame of quartz blocks with a length of 4 and a height of 5 blocks, leaving only air in the center. After placing this frame, use the eye of ender on the frame.",
on_place = function(itemstack, user, pointed_thing)
-- Use pointed node's on_rightclick function first, if present
local node = minetest.get_node(pointed_thing.under)
if user and not user:get_player_control().sneak then
if minetest.registered_nodes[node.name] and minetest.registered_nodes[node.name].on_rightclick then
return minetest.registered_nodes[node.name].on_rightclick(pointed_thing.under, node, user, itemstack) or itemstack
end
end
-- If used on portal frame, open a portal
if pointed_thing.under and node.name == portal_frame then
local opened = make_end_portal(pointed_thing.under)
if opened then
if minetest.get_modpath("doc") then
doc.mark_entry_as_revealed(user:get_player_name(), "nodes", "mcl_portals:portal_end")
end
minetest.sound_play(
"fire_flint_and_steel",
{pos = pointed_thing.above, gain = 0.5, max_hear_distance = 16})
if not minetest.settings:get_bool("creative_mode") then
itemstack:take_item() -- 1 use
end
end
end
return itemstack
end,
})