529 lines
16 KiB
Lua
529 lines
16 KiB
Lua
--[[
|
|
|
|
Tube Library 2
|
|
==============
|
|
|
|
Copyright (C) 2017-2021 Joachim Stolberg
|
|
|
|
LGPLv2.1+
|
|
See LICENSE.txt for more information
|
|
|
|
tube_api.lua
|
|
|
|
]]--
|
|
|
|
-- Version for compatibility checks, see readme.md/history
|
|
tubelib2.version = 2.0
|
|
|
|
-- for lazy programmers
|
|
local S = function(pos) if pos then return minetest.pos_to_string(pos) end end
|
|
local M = minetest.get_meta
|
|
|
|
-- Load support for intllib.
|
|
local MP = minetest.get_modpath("tubelib2")
|
|
local I,_ = dofile(MP.."/intllib.lua")
|
|
|
|
-- Cardinal directions, regardless of orientation
|
|
local Dir2Str = {"north", "east", "south", "west", "down", "up"}
|
|
|
|
function tubelib2.dir_to_string(dir)
|
|
return Dir2Str[dir]
|
|
end
|
|
|
|
-- Relative directions, dependant on orientation (param2)
|
|
local DirToSide = {"B", "R", "F", "L", "D", "U"}
|
|
|
|
function tubelib2.dir_to_side(dir, param2)
|
|
if dir < 5 then
|
|
dir = (((dir - 1) - (param2 % 4)) % 4) + 1
|
|
end
|
|
return DirToSide[dir]
|
|
end
|
|
|
|
local SideToDir = {B=1, R=2, F=3, L=4, D=5, U=6}
|
|
|
|
function tubelib2.side_to_dir(side, param2)
|
|
local dir = SideToDir[side]
|
|
if dir < 5 then
|
|
dir = (((dir - 1) + (param2 % 4)) % 4) + 1
|
|
end
|
|
return dir
|
|
end
|
|
|
|
|
|
local function Tbl(list)
|
|
local tbl = {}
|
|
for _,item in ipairs(list) do
|
|
tbl[item] = true
|
|
end
|
|
return tbl
|
|
end
|
|
|
|
-- Tubelib2 Class
|
|
local Tube = tubelib2.Tube
|
|
local Turn180Deg = tubelib2.Turn180Deg
|
|
local Dir6dToVector = tubelib2.Dir6dToVector
|
|
|
|
|
|
local function get_pos(pos, dir)
|
|
return vector.add(pos, Dir6dToVector[dir or 0])
|
|
end
|
|
tubelib2.get_pos = get_pos
|
|
|
|
function tubelib2.get_node_lvm(pos)
|
|
local node = minetest.get_node_or_nil(pos)
|
|
if node then
|
|
return node
|
|
end
|
|
local vm = minetest.get_voxel_manip()
|
|
local MinEdge, MaxEdge = vm:read_from_map(pos, pos)
|
|
local data = vm:get_data()
|
|
local param2_data = vm:get_param2_data()
|
|
local area = VoxelArea:new({MinEdge = MinEdge, MaxEdge = MaxEdge})
|
|
local idx = area:indexp(pos)
|
|
node = {
|
|
name = minetest.get_name_from_content_id(data[idx]),
|
|
param2 = param2_data[idx]
|
|
}
|
|
return node
|
|
end
|
|
|
|
local function update1(self, pos, dir)
|
|
local fpos,fdir = self:walk_tube_line(pos, dir)
|
|
self:infotext(get_pos(pos, dir), fpos)
|
|
self:infotext(fpos, get_pos(pos, dir))
|
|
-- Translate pos/dir pointing to the secondary node into
|
|
-- spos/sdir of the secondary node pointing to the tube.
|
|
if fpos and fdir then
|
|
local spos, sdir = get_pos(fpos,fdir), Turn180Deg[fdir]
|
|
self:del_from_cache(spos, sdir)
|
|
self:add_to_cache(pos, dir, spos, sdir)
|
|
self:add_to_cache(spos, sdir, pos, dir)
|
|
self:update_secondary_node(pos, dir, spos, sdir)
|
|
self:update_secondary_node(spos, sdir, pos, dir)
|
|
end
|
|
end
|
|
|
|
local function update2(self, pos1, dir1, pos2, dir2)
|
|
local fpos1,fdir1 = self:walk_tube_line(pos1, dir1)
|
|
local fpos2,fdir2 = self:walk_tube_line(pos2, dir2)
|
|
if not fdir1 or not fdir2 then -- line to long?
|
|
-- reset next tube(s) to head tube(s) again
|
|
local param2 = self:encode_param2(dir1, dir2, 2)
|
|
self:update_after_dig_tube(pos1, param2)
|
|
M(get_pos(pos1, dir1)):set_string("infotext", I("Maximum length reached!"))
|
|
M(get_pos(pos1, dir2)):set_string("infotext", I("Maximum length reached!"))
|
|
return false
|
|
end
|
|
self:infotext(fpos1, fpos2)
|
|
self:infotext(fpos2, fpos1)
|
|
-- Translate fpos/fdir pointing to the secondary node into
|
|
-- spos/sdir of the secondary node pointing to the tube.
|
|
local spos1, sdir1 = get_pos(fpos1,fdir1), Turn180Deg[fdir1]
|
|
local spos2, sdir2 = get_pos(fpos2,fdir2), Turn180Deg[fdir2]
|
|
self:del_from_cache(spos1, sdir1)
|
|
self:del_from_cache(spos2, sdir2)
|
|
self:add_to_cache(spos1, sdir1, spos2, sdir2)
|
|
self:add_to_cache(spos2, sdir2, spos1, sdir1)
|
|
self:update_secondary_node(spos1, sdir1, spos2, sdir2)
|
|
self:update_secondary_node(spos2, sdir2, spos1, sdir1)
|
|
return true
|
|
end
|
|
|
|
local function update3(self, pos, dir1, dir2)
|
|
if pos and dir1 and dir2 then
|
|
local fpos1,fdir1,cnt1 = self:walk_tube_line(pos, dir1)
|
|
local fpos2,fdir2,cnt2 = self:walk_tube_line(pos, dir2)
|
|
self:infotext(fpos1, fpos2)
|
|
self:infotext(fpos2, fpos1)
|
|
-- Translate fpos/fdir pointing to the secondary node into
|
|
-- spos/sdir of the secondary node pointing to the tube.
|
|
if fpos1 and fpos2 and fdir1 and fdir2 then
|
|
local spos1, sdir1 = get_pos(fpos1,fdir1), Turn180Deg[fdir1]
|
|
local spos2, sdir2 = get_pos(fpos2,fdir2), Turn180Deg[fdir2]
|
|
self:del_from_cache(spos1, sdir1)
|
|
self:del_from_cache(spos2, sdir2)
|
|
self:add_to_cache(spos1, sdir1, spos2, sdir2)
|
|
self:add_to_cache(spos2, sdir2, spos1, sdir1)
|
|
self:update_secondary_node(spos1, sdir1, spos2, sdir2)
|
|
self:update_secondary_node(spos2, sdir2, spos1, sdir1)
|
|
return dir1, dir2, fpos1, fpos2, fdir1, fdir2, cnt1 or 0, cnt2 or 0
|
|
end
|
|
return dir1, dir2, pos, pos, dir1, dir2, cnt1 or 0, cnt2 or 0
|
|
end
|
|
end
|
|
|
|
local function update_secondary_nodes_after_node_placed(self, pos, dirs)
|
|
dirs = dirs or self.dirs_to_check
|
|
-- check surrounding for secondary nodes
|
|
for _,dir in ipairs(dirs) do
|
|
local tmp, npos
|
|
if self.force_to_use_tubes then
|
|
tmp, npos = self:get_special_node(pos, dir)
|
|
else
|
|
tmp, npos = self:get_secondary_node(pos, dir)
|
|
end
|
|
if npos then
|
|
self:update_secondary_node(npos, Turn180Deg[dir], pos, dir)
|
|
self:update_secondary_node(pos, dir, npos, Turn180Deg[dir])
|
|
end
|
|
end
|
|
end
|
|
|
|
local function update_secondary_nodes_after_node_dug(self, pos, dirs)
|
|
dirs = dirs or self.dirs_to_check
|
|
-- check surrounding for secondary nodes
|
|
for _,dir in ipairs(dirs) do
|
|
local tmp, npos
|
|
if self.force_to_use_tubes then
|
|
tmp, npos = self:get_special_node(pos, dir)
|
|
else
|
|
tmp, npos = self:get_secondary_node(pos, dir)
|
|
end
|
|
if npos then
|
|
self:del_from_cache(npos, Turn180Deg[dir])
|
|
self:del_from_cache(pos, dir)
|
|
self:update_secondary_node(npos, Turn180Deg[dir])
|
|
self:update_secondary_node(pos, dir)
|
|
end
|
|
end
|
|
end
|
|
|
|
--
|
|
-- API Functions
|
|
--
|
|
|
|
function Tube:new(attr)
|
|
local o = {
|
|
dirs_to_check = attr.dirs_to_check or {1,2,3,4,5,6},
|
|
max_tube_length = attr.max_tube_length or 1000,
|
|
primary_node_names = Tbl(attr.primary_node_names or {}),
|
|
secondary_node_names = Tbl(attr.secondary_node_names or {}),
|
|
valid_node_contact_sides = {},
|
|
show_infotext = attr.show_infotext or false,
|
|
force_to_use_tubes = attr.force_to_use_tubes or false,
|
|
clbk_after_place_tube = attr.after_place_tube,
|
|
tube_type = attr.tube_type or "unknown",
|
|
pairingList = {}, -- teleporting nodes
|
|
connCache = {}, -- connection cache {pos1 = {dir1 = {pos2 = pos2, dir2 = dir2},...}
|
|
special_node_names = {}, -- use add_special_node_names() to register nodes
|
|
debug_info = attr.debug_info, -- debug_info(pos, text)
|
|
}
|
|
o.valid_dirs = Tbl(o.dirs_to_check)
|
|
setmetatable(o, self)
|
|
self.__index = self
|
|
if attr.valid_node_contact_sides then
|
|
o:set_valid_sides_multiple(attr.valid_node_contact_sides)
|
|
end
|
|
return o
|
|
end
|
|
|
|
-- Register (foreign) tubelib compatible nodes.
|
|
function Tube:add_secondary_node_names(names)
|
|
for _,name in ipairs(names) do
|
|
self.secondary_node_names[name] = true
|
|
end
|
|
end
|
|
|
|
|
|
-- Defaults for valid sides configuration
|
|
local function invert_booleans(tab)
|
|
local inversion = {}
|
|
for key, value in pairs(tab) do
|
|
inversion[key] = not value
|
|
end
|
|
return inversion
|
|
end
|
|
local valid_sides_default_true = Tbl(DirToSide)
|
|
local valid_sides_default_false = invert_booleans(valid_sides_default_true)
|
|
local function complete_valid_sides(valid_sides, existing_defaults)
|
|
local valid_sides_complete = {}
|
|
for side, default_value in pairs(existing_defaults) do
|
|
local new_value = valid_sides[side]
|
|
if new_value == nil then
|
|
valid_sides_complete[side] = default_value
|
|
else
|
|
valid_sides_complete[side] = new_value
|
|
end
|
|
end
|
|
return valid_sides_complete
|
|
end
|
|
|
|
-- Set sides which are valid
|
|
-- with a table of name = valid_sides pairs
|
|
function Tube:set_valid_sides_multiple(names)
|
|
for name, valid_sides in pairs(names) do
|
|
self:set_valid_sides(name, valid_sides)
|
|
end
|
|
end
|
|
|
|
-- Set sides which are invalid
|
|
-- with a table of name = valid_sides pairs
|
|
function Tube:set_invalid_sides_multiple(names)
|
|
for name, invalid_sides in pairs(names) do
|
|
self:set_invalid_sides(name, invalid_sides)
|
|
end
|
|
end
|
|
|
|
-- Set sides which are valid
|
|
-- will assume all sides not given are invalid
|
|
-- Only sets new sides, existing sides will remain
|
|
function Tube:set_valid_sides(name, valid_sides)
|
|
local existing_defaults = self.valid_node_contact_sides[name] or valid_sides_default_false
|
|
self.valid_node_contact_sides[name] = complete_valid_sides(Tbl(valid_sides), existing_defaults)
|
|
end
|
|
|
|
-- Set sides which are invalid
|
|
-- will assume all sides not given are valid
|
|
-- Only sets new sides, existing sides will remain
|
|
function Tube:set_invalid_sides(name, invalid_sides)
|
|
local existing_defaults = self.valid_node_contact_sides[name] or valid_sides_default_true
|
|
self.valid_node_contact_sides[name] = complete_valid_sides(invert_booleans(Tbl(invalid_sides)), existing_defaults)
|
|
end
|
|
|
|
-- Checks the list of valid node connection sides to
|
|
-- see if a given side can be connected to
|
|
function Tube:is_valid_side(name, side)
|
|
local valid_sides = self.valid_node_contact_sides[name]
|
|
if valid_sides then
|
|
return valid_sides[side] or false
|
|
end
|
|
end
|
|
|
|
-- Checks if a particular node can be connected to
|
|
-- from a particular direction, taking into account orientation
|
|
function Tube:is_valid_dir(node, dir)
|
|
if node and dir ~= nil and self.valid_node_contact_sides[node.name] then
|
|
local side = tubelib2.dir_to_side(dir, node.param2)
|
|
return self:is_valid_side(node.name, side)
|
|
end
|
|
end
|
|
|
|
-- Checks if a node at a particular position can be connected to
|
|
-- from a particular direction, taking into account orientation
|
|
function Tube:is_valid_dir_pos(pos, dir)
|
|
local node = self:get_node_lvm(pos)
|
|
return self:is_valid_dir(node, dir)
|
|
end
|
|
|
|
-- Register further nodes, which should be updated after
|
|
-- a node/tube is placed/dug
|
|
function Tube:add_special_node_names(names)
|
|
for _,name in ipairs(names) do
|
|
self.special_node_names[name] = true
|
|
end
|
|
end
|
|
|
|
-- Called for each connected node when the tube connection has been changed.
|
|
-- func(node, pos, out_dir, peer_pos, peer_in_dir)
|
|
function Tube:register_on_tube_update(update_secondary_node)
|
|
self.clbk_update_secondary_node = update_secondary_node
|
|
end
|
|
|
|
function Tube:get_pos(pos, dir)
|
|
return vector.add(pos, Dir6dToVector[dir or 0])
|
|
end
|
|
|
|
-- To be called after a secondary node is placed.
|
|
-- dirs is a list with valid dirs, like: {1,2,3,4}
|
|
function Tube:after_place_node(pos, dirs)
|
|
-- [s][f]----[n] x
|
|
-- s..secondary, f..far, n..near, x..node to be placed
|
|
for _,dir in ipairs(self:update_after_place_node(pos, dirs)) do
|
|
if pos and dir then
|
|
update1(self, pos, dir)
|
|
end
|
|
end
|
|
update_secondary_nodes_after_node_placed(self, pos, dirs)
|
|
end
|
|
|
|
-- To be called after a tube/primary node is placed.
|
|
-- Returns false, if placing is not allowed
|
|
function Tube:after_place_tube(pos, placer, pointed_thing)
|
|
-- [s1][f1]----[n1] x [n2]-----[f2][s2]
|
|
-- s..secondary, f..far, n..near, x..node to be placed
|
|
local res,dir1,dir2 = self:update_after_place_tube(pos, placer, pointed_thing)
|
|
if res then -- node placed?
|
|
return update2(self, pos, dir1, pos, dir2)
|
|
end
|
|
return res
|
|
end
|
|
|
|
function Tube:after_dig_node(pos, dirs)
|
|
-- [s][f]----[n] x
|
|
-- s..secondary, f..far, n..near, x..node to be removed
|
|
for _,dir in ipairs(self:update_after_dig_node(pos, dirs)) do
|
|
update1(self, pos, dir)
|
|
end
|
|
update_secondary_nodes_after_node_dug(self, pos, dirs)
|
|
end
|
|
|
|
-- To be called after a tube/primary node is removed.
|
|
function Tube:after_dig_tube(pos, oldnode)
|
|
-- [s1][f1]----[n1] x [n2]-----[f2][s2]
|
|
-- s..secondary, f..far, n..near, x..node to be removed
|
|
|
|
-- update tubes
|
|
if oldnode and oldnode.param2 then
|
|
local dir1, dir2 = self:update_after_dig_tube(pos, oldnode.param2)
|
|
if dir1 then update1(self, pos, dir1) end
|
|
if dir2 then update1(self, pos, dir2) end
|
|
|
|
-- Update secondary nodes, if right beside
|
|
dir1, dir2 = self:decode_param2(pos, oldnode.param2)
|
|
local npos1,ndir1 = get_pos(pos, dir1),Turn180Deg[dir1]
|
|
local npos2,ndir2 = get_pos(pos, dir2),Turn180Deg[dir2]
|
|
self:del_from_cache(npos1,ndir1)
|
|
self:update_secondary_node(npos1,ndir1)
|
|
self:update_secondary_node(npos2,ndir2)
|
|
end
|
|
end
|
|
|
|
|
|
-- From source node to destination node via tubes.
|
|
-- pos is the source node position, dir the output dir
|
|
-- The returned pos is the destination position, dir
|
|
-- is the direction into the destination node.
|
|
function Tube:get_connected_node_pos(pos, dir)
|
|
local key = S(pos)
|
|
if self.connCache[key] and self.connCache[key][dir] then
|
|
local item = self.connCache[key][dir]
|
|
return item.pos2, Turn180Deg[item.dir2]
|
|
end
|
|
local fpos,fdir = self:walk_tube_line(pos, dir)
|
|
local spos = get_pos(fpos,fdir)
|
|
self:add_to_cache(pos, dir, spos, Turn180Deg[fdir])
|
|
self:add_to_cache(spos, Turn180Deg[fdir], pos, dir)
|
|
return spos, fdir
|
|
end
|
|
|
|
-- Check if node at given position is a tubelib2 compatible node,
|
|
-- able to receive and/or deliver items.
|
|
-- If dir == nil then node_pos = pos
|
|
-- Function returns the result (true/false), new pos, and the node
|
|
function Tube:compatible_node(pos, dir)
|
|
local npos = vector.add(pos, Dir6dToVector[dir or 0])
|
|
local node = self:get_node_lvm(npos)
|
|
return self.secondary_node_names[node.name], npos, node
|
|
end
|
|
|
|
|
|
-- To be called from a repair tool in the case of a "WorldEdit" or with
|
|
-- legacy nodes corrupted tube line.
|
|
function Tube:tool_repair_tube(pos)
|
|
local param2 = self:get_primary_node_param2(pos)
|
|
if param2 then
|
|
local dir1, dir2 = self:decode_param2(pos, param2)
|
|
return update3(self, pos, dir1, dir2)
|
|
end
|
|
end
|
|
|
|
-- To be called from a repair tool in the case, tube nodes are "unbreakable".
|
|
function Tube:tool_remove_tube(pos, sound)
|
|
if self:is_primary_node(pos) then
|
|
local _,node = self:get_node(pos)
|
|
minetest.sound_play({name=sound},{
|
|
pos=pos,
|
|
gain=1,
|
|
max_hear_distance=5,
|
|
loop=false})
|
|
minetest.remove_node(pos)
|
|
self:after_dig_tube(pos, node)
|
|
return true
|
|
end
|
|
return false
|
|
end
|
|
|
|
|
|
function Tube:prepare_pairing(pos, tube_dir, sFormspec)
|
|
local meta = M(pos)
|
|
if meta:get_int("tube_dir") ~= 0 then -- already prepared?
|
|
-- update tube_dir only
|
|
meta:set_int("tube_dir", tube_dir)
|
|
elseif tube_dir then
|
|
meta:set_int("tube_dir", tube_dir)
|
|
meta:set_string("channel", nil)
|
|
meta:set_string("infotext", I("Pairing is missing"))
|
|
meta:set_string("formspec", sFormspec)
|
|
else
|
|
meta:set_string("infotext", I("Connection to a tube is missing!"))
|
|
end
|
|
end
|
|
|
|
function Tube:pairing(pos, channel)
|
|
if self.pairingList[channel] and not vector.equals(pos, self.pairingList[channel]) then
|
|
-- store peer position on both nodes
|
|
local peer_pos = self.pairingList[channel]
|
|
local tube_dir1 = self:store_teleport_data(pos, peer_pos)
|
|
local tube_dir2 = self:store_teleport_data(peer_pos, pos)
|
|
update2(self, pos, tube_dir1, peer_pos, tube_dir2)
|
|
self.pairingList[channel] = nil
|
|
return true
|
|
else
|
|
self.pairingList[channel] = pos
|
|
local meta = M(pos)
|
|
meta:set_string("channel", channel)
|
|
meta:set_string("infotext", I("Pairing is missing").." ("..channel..")")
|
|
return false
|
|
end
|
|
end
|
|
|
|
function Tube:stop_pairing(pos, oldmetadata, sFormspec)
|
|
-- unpair peer node
|
|
if oldmetadata and oldmetadata.fields then
|
|
if oldmetadata.fields.tele_pos then
|
|
local tele_pos = minetest.string_to_pos(oldmetadata.fields.tele_pos)
|
|
local peer_meta = M(tele_pos)
|
|
if peer_meta then
|
|
self:after_dig_node(tele_pos, {peer_meta:get_int("tube_dir")})
|
|
peer_meta:set_string("channel", nil)
|
|
peer_meta:set_string("tele_pos", nil)
|
|
peer_meta:set_string("formspec", sFormspec)
|
|
peer_meta:set_string("infotext", I("Pairing is missing"))
|
|
end
|
|
elseif oldmetadata.fields.channel then
|
|
self.pairingList[oldmetadata.fields.channel] = nil
|
|
end
|
|
end
|
|
end
|
|
|
|
|
|
-- Used by chat commands, when tubes are placed e.g. via WorldEdit
|
|
function Tube:replace_tube_line(pos1, pos2)
|
|
if pos1 and pos2 and not vector.equals(pos1, pos2) then
|
|
local check = (((pos1.x == pos2.x) and 1) or 0) +
|
|
(((pos1.y == pos2.y) and 1) or 0) +
|
|
(((pos1.z == pos2.z) and 1) or 0)
|
|
if check == 2 then
|
|
local v = vector.direction(pos1, pos2)
|
|
local dir1 = self:vector_to_dir(v)
|
|
local dir2 = Turn180Deg[dir1]
|
|
|
|
self:replace_nodes(pos1, pos2, dir1, dir2)
|
|
update3(self, pos1, dir1, dir2)
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Used to change the tube nodes texture (e.g. on/off state)
|
|
function Tube:switch_tube_line(pos, dir, state)
|
|
self:switch_nodes(pos, dir, state)
|
|
end
|
|
|
|
-- Generator function to iterate over a tube line
|
|
-- Returns for each tube: i , pos, node
|
|
function Tube:get_tube_line(pos, dir)
|
|
if pos and dir then
|
|
self.ref = {pos = pos, dir = dir}
|
|
return function(self, i)
|
|
if i < self.max_tube_length then
|
|
local new_pos, new_dir, num = self:get_next_tube(self.ref.pos, self.ref.dir)
|
|
if new_pos then
|
|
self.ref.pos, self.ref.dir = new_pos, new_dir
|
|
i = i + 1
|
|
return i, self.ref.pos, self.node
|
|
end
|
|
end
|
|
end, self, 0
|
|
end
|
|
end
|