603 lines
15 KiB
Lua
603 lines
15 KiB
Lua
--[[
|
|
Advanced Portals (advanced_portals), A minetest add-on that allows certain users to create and place portals on their minetest world.
|
|
|
|
Copyright (c) 2020 Genshin <emperor_genshin@hotmail.com>
|
|
License: GPLv3
|
|
--]]
|
|
|
|
|
|
--Delay to when handlers update
|
|
local update_interval = 25
|
|
|
|
local particle_emitter_interval = 4
|
|
|
|
--Delay to when ambiance will loop
|
|
local ambiance_interval = 200
|
|
local ambiance_list = {}
|
|
|
|
local debug = minetest.settings:get_bool("enable_portals_debug") or false
|
|
|
|
local function get_count_from_table(table)
|
|
count = 0
|
|
for k,v in pairs(table) do
|
|
count = count + 1
|
|
end
|
|
return count
|
|
end
|
|
|
|
|
|
|
|
local function do_particle_effect(pos, delay, amount, texture, min_size, max_size, radius, gravity, glow)
|
|
|
|
radius = radius or 2
|
|
min_size = min_size or 0.5
|
|
max_size = max_size or 1
|
|
gravity = gravity or -10
|
|
glow = glow or 0
|
|
delay = delay or 0.25
|
|
|
|
minetest.add_particlespawner({
|
|
amount = amount,
|
|
time = delay,
|
|
minpos = pos,
|
|
maxpos = pos,
|
|
minvel = {x = -radius, y = -radius, z = -radius},
|
|
maxvel = {x = radius, y = radius, z = radius},
|
|
minacc = {x = 0, y = gravity, z = 0},
|
|
maxacc = {x = 0, y = gravity, z = 0},
|
|
minexptime = 0.1,
|
|
maxexptime = 1,
|
|
minsize = min_size,
|
|
maxsize = max_size,
|
|
texture = texture,
|
|
glow = glow,
|
|
})
|
|
end
|
|
|
|
|
|
local function update_ambiance_to_players_within_radius(self, distance)
|
|
local pos = self.object:get_pos()
|
|
local node = self.portal_nodename
|
|
local ambiance_sound = get_portal_handler_sounds(node, "portal_ambiance")
|
|
local ambiance_distance = get_portal_handler_sounds(node, "sound_distance")
|
|
local entities = minetest.get_objects_inside_radius(pos, ambiance_distance)
|
|
local players = minetest.get_connected_players()
|
|
local within_radius = {}
|
|
|
|
--Get the following players within the radius and stack them in a table
|
|
for k,v in pairs(entities) do
|
|
local entity = v
|
|
|
|
if not entity then
|
|
within_radius = {}
|
|
break
|
|
end
|
|
|
|
if entity:is_player() then
|
|
local name = entity:get_player_name()
|
|
|
|
within_radius[name] = entity
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
--Check which players are ouside of radius
|
|
for _,player in pairs(players) do
|
|
local name = player:get_player_name()
|
|
|
|
if within_radius[name] then
|
|
|
|
if not ambiance_list[name] then
|
|
|
|
local ambiance = minetest.sound_play(ambiance_sound, {
|
|
object = player,
|
|
loop = true,
|
|
})
|
|
|
|
ambiance_list[name] = ambiance
|
|
|
|
end
|
|
|
|
else
|
|
|
|
if ambiance_list[name] then
|
|
minetest.sound_stop(ambiance_list[name])
|
|
ambiance_list[name] = nil
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
local function force_stop_ambiance_to_players()
|
|
local players = minetest.get_connected_players()
|
|
|
|
--Check which players are ouside of radius
|
|
for _,player in pairs(players) do
|
|
local name = player:get_player_name()
|
|
|
|
if name then
|
|
|
|
if ambiance_list[name] then
|
|
minetest.sound_stop(ambiance_list[name])
|
|
ambiance_list[name] = nil
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
--[Local Function] Teleport Player or Mob to a location or pparticle_emitter_intervalortal
|
|
local function teleport_entity(userdata, self)
|
|
local portal_data = get_portal_data(self.portal)
|
|
local linked = portal_data.linked
|
|
local linked_portal_data = nil
|
|
local pos = nil
|
|
local height = nil
|
|
local node = self.portal_nodename
|
|
local warp_sound = get_portal_handler_sounds(node, "portal_warp") or ""
|
|
local sound_distance = get_portal_handler_sounds(node, "sound_distance")
|
|
local entity_pos = userdata:get_pos()
|
|
|
|
if linked == true then
|
|
linked_portal_data = get_portal_data(portal_data.linked_portal)
|
|
|
|
if not linked_portal_data then
|
|
return
|
|
end
|
|
|
|
pos = linked_portal_data.location
|
|
--height = get_portal_handler_origin_height(self.portal_nodename, "warp_handler")
|
|
|
|
if pos == nil then
|
|
minetest.log("error", "Failed to find location of linked portal. Refusing to teleport entity to a unknown location")
|
|
return
|
|
end
|
|
|
|
if debug == true then
|
|
minetest.chat_send_all("Teleported Something! - Linked")
|
|
end
|
|
|
|
do_particle_effect(entity_pos, 0.25, 1, "warp_particle.png", 10, 20, radius, 20, 30)
|
|
userdata:set_pos({x = pos.x, y = pos.y + 3, z = pos.z})
|
|
|
|
elseif linked == false then
|
|
pos = portal_data.coordinates
|
|
|
|
if pos == nil then
|
|
minetest.log("error", "Failed to find specified destination. Refusing to teleport entity to a unknown location")
|
|
return
|
|
end
|
|
|
|
if debug == true then
|
|
minetest.chat_send_all("Teleported Something! - Not Linked")
|
|
end
|
|
|
|
do_particle_effect(entity_pos, 5, 25, "warp_particle.png", math.random(5,10), math.random(10,20), radius, 20, 10)
|
|
userdata:set_pos(pos)
|
|
end
|
|
|
|
if userdata:is_player() then
|
|
|
|
local name = userdata:get_player_name()
|
|
|
|
if ambiance_list[name] then
|
|
minetest.sound_stop(ambiance_list[name])
|
|
ambiance_list[name] = nil
|
|
end
|
|
|
|
end
|
|
|
|
minetest.sound_play(warp_sound, {
|
|
pos = self.object:get_pos(),
|
|
max_hear_distance = sound_distance,
|
|
gain = 10.0,
|
|
})
|
|
|
|
minetest.sound_play(warp_sound, {
|
|
pos = pos,
|
|
max_hear_distance = sound_distance,
|
|
gain = 10.0,
|
|
})
|
|
|
|
return
|
|
end
|
|
|
|
|
|
--[Local Function] Get node from vertical position
|
|
local function get_node_from_vertical_pos(userdata, direction, vertical_height)
|
|
local pos = userdata:get_pos()
|
|
|
|
|
|
if direction == nil then else
|
|
if direction == "up" then
|
|
pos = {x = pos.x, y = pos.y + vertical_height, z = pos.z}
|
|
elseif direction == "down" then
|
|
pos = {x = pos.x, y = pos.y - vertical_height, z = pos.z}
|
|
end
|
|
end
|
|
|
|
local result = tostring(minetest.get_node(pos)["name"]) or "Error"
|
|
|
|
return result
|
|
end
|
|
|
|
|
|
|
|
--[Local Function] Portal Warp Handler Behavior
|
|
local function handler_step(self)
|
|
|
|
self.update_timer = self.update_timer + 1
|
|
|
|
if self.emit_particles == "true" then
|
|
|
|
self.particle_emitter_timer = self.particle_emitter_timer + 1
|
|
|
|
if self.particle_emitter_timer >= particle_emitter_interval then
|
|
local texture = get_portal_particle_values(self.portal_nodename, "texture")
|
|
local delay = get_portal_particle_values(self.portal_nodename, "delay")
|
|
local spread = get_portal_particle_values(self.portal_nodename, "spread")
|
|
|
|
local pos = self.object:get_pos()
|
|
do_particle_effect(pos, delay, 1, texture, math.random(5,8), math.random(8,12), spread, 1, 30)
|
|
self.particle_emitter_timer = 0
|
|
end
|
|
|
|
end
|
|
|
|
|
|
if self.update_timer >= update_interval then
|
|
|
|
if self.origin_height == nil then
|
|
force_stop_ambiance_to_players()
|
|
self.object:remove()
|
|
return
|
|
end
|
|
|
|
local portal_node = tostring(get_node_from_vertical_pos(self.object, "down", self.origin_height))
|
|
|
|
--minetest.chat_send_all(tostring(portal_node)..", "..tostring(self.portal_node)..", "..tostring(self.origin_height)..", "..tostring(self.portal))
|
|
|
|
if self.portal == nil or self.portal_node == nil then --delete yourself if it's not associated with a portal (failsafe)
|
|
force_stop_ambiance_to_players()
|
|
--minetest.chat_send_all("Uh Oh 2 - Handler")
|
|
self.object:remove()
|
|
return
|
|
|
|
elseif portal_node ~= self.portal_node then --No Portal below you?, if so then kill yourself (failsafe)
|
|
force_stop_ambiance_to_players()
|
|
--minetest.chat_send_all("Uh Oh 3 - Handler")
|
|
self.object:remove()
|
|
return
|
|
end
|
|
|
|
update_ambiance_to_players_within_radius(self)
|
|
|
|
local portal_data = get_portal_data(self.portal)
|
|
|
|
--If data hasn't loaded successfully, skip the rest of the stuff (failsafe)
|
|
if portal_data == nil then
|
|
return
|
|
end
|
|
|
|
self.emit_particles = get_portal_particle_values(self.portal_nodename, "flag") or self.emit_particles
|
|
|
|
local pos = self.object:get_pos()
|
|
local detected_entities = minetest.get_objects_inside_radius(pos, self.scan_radius)
|
|
local private = portal_data.private
|
|
|
|
if private == true then
|
|
local whitelist = portal_data.whitelist
|
|
|
|
for _,entity in pairs(detected_entities) do
|
|
|
|
if entity:is_player() then
|
|
|
|
if debug == true then
|
|
minetest.chat_send_all("Attempting to teleport someone! - Private")
|
|
end
|
|
|
|
local player = entity
|
|
local player_name = player:get_player_name()
|
|
|
|
for k,v in pairs(whitelist) do
|
|
local allowed = v
|
|
local pname = player:get_player_name()
|
|
|
|
if pname == allowed or player_name == self.portal_owner then
|
|
teleport_entity(player, self)
|
|
end
|
|
|
|
end
|
|
|
|
else
|
|
|
|
local npc = entity:get_luaentity()
|
|
|
|
if npc.type and npc ~= self then
|
|
if debug == true then
|
|
minetest.chat_send_all("Attempting to teleport something! - Private")
|
|
end
|
|
|
|
teleport_entity(npc.object, self)
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
elseif private == false then
|
|
|
|
for _,entity in pairs(detected_entities) do
|
|
|
|
if entity:is_player() then
|
|
|
|
if debug == true then
|
|
minetest.chat_send_all("Attempting to teleport someone! - Not Private")
|
|
end
|
|
|
|
teleport_entity(entity, self)
|
|
else
|
|
local npc = entity:get_luaentity()
|
|
|
|
if npc.type and npc ~= self then
|
|
|
|
if debug == true then
|
|
minetest.chat_send_all("Attempting to teleport something! - Not Private")
|
|
end
|
|
|
|
teleport_entity(npc.object, self)
|
|
end
|
|
|
|
end
|
|
|
|
|
|
end
|
|
|
|
|
|
end
|
|
|
|
::portal_handler_loop_end::
|
|
self.update_timer = 0
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
local function update_portal_nametag(self)
|
|
|
|
local portal_data = get_portal_data(self.portal)
|
|
|
|
--If data hasn't loaded successfully, skip the rest of the stuff (failsafe)
|
|
if portal_data == nil then
|
|
return
|
|
end
|
|
|
|
self.object:set_properties({
|
|
nametag = minetest.colorize("#0066ff", portal_data.nametag).."\n"..minetest.colorize("#959595", "["..portal_data.portal_name.."]")
|
|
})
|
|
|
|
end
|
|
|
|
|
|
--[Local Function] Portal Nametag Behavior
|
|
local function nametag_step(self)
|
|
|
|
|
|
self.update_timer = self.update_timer + 1
|
|
|
|
if self.update_timer >= update_interval then
|
|
|
|
if self.origin_height == nil then
|
|
--minetest.chat_send_all("Uh Oh 1 - Nametag")
|
|
self.object:remove()
|
|
return
|
|
end
|
|
|
|
local portal_node = tostring(get_node_from_vertical_pos(self.object, "down", self.origin_height))
|
|
|
|
--minetest.chat_send_all(tostring(portal_node)..", "..tostring(self.portal_node)..", "..tostring(self.origin_height)..", "..tostring(self.portal))
|
|
|
|
if self.portal == nil or self.portal_node == nil then --delete yourself if it's not associated with a portal (failsafe)
|
|
--minetest.chat_send_all("Uh Oh 2 - Nametag")
|
|
self.object:remove()
|
|
return
|
|
|
|
elseif portal_node ~= self.portal_node then --No Portal below you?, if so then kill yourself (failsafe)
|
|
--minetest.chat_send_all("Uh Oh 3 - Nametag")
|
|
self.object:remove()
|
|
return
|
|
end
|
|
|
|
local portal_data = get_portal_data(self.portal)
|
|
|
|
--If data hasn't loaded successfully, skip the rest of the stuff (failsafe)
|
|
if portal_data == nil then
|
|
return
|
|
end
|
|
|
|
update_portal_nametag(self)
|
|
|
|
self.update_timer = 0
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
--[Entity] Portal Teleportation Handler
|
|
minetest.register_entity("advanced_portals:portal_handler", {
|
|
physical = false,
|
|
collisionbox = {0, 0, 0, 0, 0, 0},
|
|
visual = "sprite",
|
|
glow = 30,
|
|
textures = {"portal_deactivated.png"},
|
|
visual_size = {x = 0, y = 0},
|
|
origin_height = nil,
|
|
portal = nil,
|
|
portal_node = nil,
|
|
portal_nodename = nil,
|
|
scan_radius = 2,
|
|
update_timer = 0,
|
|
particle_emitter_timer = 0,
|
|
emit_particles = "false",
|
|
particle_texture = "",
|
|
particle_delay = 0,
|
|
particle_spread = 0,
|
|
|
|
on_activate = function(self, staticdata, dtime_s)
|
|
-- load entity variables
|
|
local tmp = minetest.deserialize(staticdata)
|
|
|
|
if tmp then
|
|
|
|
for _,stat in pairs(tmp) do
|
|
self[_] = stat
|
|
end
|
|
end
|
|
|
|
self.set = self.set
|
|
self.radius = self.radius
|
|
self.object:set_armor_groups({immortal = 100}) --Can't die by anything
|
|
self.object:set_properties(self)
|
|
|
|
|
|
if debug == true then
|
|
self.object:set_properties({
|
|
nametag = "<[DEBUG]: Portal Handler Position>"
|
|
})
|
|
end
|
|
|
|
if self.portal == nil then else
|
|
|
|
local portal_data = get_portal_data(self.portal)
|
|
local current_origin_height = get_portal_handler_origin_height(self.portal_nodename, "warp_handler")
|
|
|
|
--print("[DEBUG] warp handler current_origin_height = "..current_origin_height)
|
|
|
|
if current_origin_height ~= self.origin_height then
|
|
local pos = portal_data.location
|
|
self.origin_height = current_origin_height
|
|
self.object:set_pos({x = pos.x, y = pos.y + current_origin_height, z = pos.z})
|
|
end
|
|
|
|
end
|
|
|
|
|
|
end,
|
|
|
|
get_staticdata = function(self)
|
|
|
|
local tmp = {}
|
|
|
|
for _,stat in pairs(self) do
|
|
|
|
local t = type(stat)
|
|
|
|
if t ~= 'function'
|
|
and t ~= 'nil'
|
|
and t ~= 'userdata' then
|
|
tmp[_] = self[_]
|
|
end
|
|
end
|
|
|
|
return minetest.serialize(tmp)
|
|
end,
|
|
|
|
on_punch = function(self, hitter) --Can't get punched by anything
|
|
return
|
|
end,
|
|
|
|
on_step = function(self)
|
|
handler_step(self)
|
|
end
|
|
})
|
|
|
|
|
|
--[Entity] Portal Nametag
|
|
minetest.register_entity("advanced_portals:portal_nametag", {
|
|
physical = false,
|
|
collisionbox = {0, 0, 0, 0, 0, 0},
|
|
visual = "sprite",
|
|
tag = "",
|
|
glow = 30,
|
|
textures = {"portal_deactivated.png"},
|
|
visual_size = {x = 0, y = 0},
|
|
origin_height = nil,
|
|
portal = nil,
|
|
set = false,
|
|
portal_node = nil,
|
|
portal_nodename = nil,
|
|
scan_radius = 2,
|
|
update_timer = 0,
|
|
|
|
on_activate = function(self, staticdata, dtime_s)
|
|
-- load entity variables
|
|
local tmp = minetest.deserialize(staticdata)
|
|
|
|
if tmp then
|
|
|
|
for _,stat in pairs(tmp) do
|
|
self[_] = stat
|
|
end
|
|
end
|
|
|
|
self.radius = self.radius
|
|
self.object:set_armor_groups({immortal = 100}) --Can't die by anything
|
|
self.object:set_properties(self)
|
|
update_portal_nametag(self)
|
|
|
|
if self.portal == nil then else
|
|
|
|
local portal_data = get_portal_data(self.portal)
|
|
local current_origin_height = get_portal_handler_origin_height(self.portal_nodename, "nametag")
|
|
|
|
--print("[DEBUG] nametag current_origin_height = "..current_origin_height)
|
|
|
|
if current_origin_height ~= self.origin_height then
|
|
local pos = portal_data.location
|
|
self.origin_height = current_origin_height
|
|
self.object:set_pos({x = pos.x, y = pos.y + current_origin_height, z = pos.z})
|
|
end
|
|
|
|
end
|
|
|
|
end,
|
|
|
|
get_staticdata = function(self)
|
|
|
|
local tmp = {}
|
|
|
|
for _,stat in pairs(self) do
|
|
|
|
local t = type(stat)
|
|
|
|
if t ~= 'function'
|
|
and t ~= 'nil'
|
|
and t ~= 'userdata' then
|
|
tmp[_] = self[_]
|
|
end
|
|
end
|
|
|
|
return minetest.serialize(tmp)
|
|
end,
|
|
|
|
on_punch = function(self, hitter) --Can't get punched by anything
|
|
return
|
|
end,
|
|
|
|
on_step = function(self)
|
|
nametag_step(self)
|
|
end,
|
|
|
|
})
|