Major rewrite of items in tubes
authorNovatux <nathanael.courant@laposte.net>
Thu, 14 Aug 2014 14:22:03 +0000 (16:22 +0200)
committerNovatux <nathanael.courant@laposte.net>
Thu, 14 Aug 2014 14:22:03 +0000 (16:22 +0200)
autoplace_tubes.lua [changed mode: 0644->0755]
common.lua [new file with mode: 0755]
init.lua [changed mode: 0644->0755]
item_transport.lua [changed mode: 0644->0755]
luaentity.lua [new file with mode: 0755]
trashcan.lua
tubes.lua [changed mode: 0644->0755]

old mode 100644 (file)
new mode 100755 (executable)
index c9d5d9f..42cf98b
@@ -1,72 +1,38 @@
 -- autorouting for pneumatic tubes
 
-local function in_table(table,element)
-       for _,el in ipairs(table) do
-               if el==element then return true end
-       end
-       return false
-end
-
 local function is_tube(nodename)
-       return in_table(pipeworks.tubenodes,nodename)
-end
-
-if pipeworks == nil then
-       pipeworks = {}
+       return table.contains(pipeworks.tubenodes, nodename)
 end
 
 --a function for determining which side of the node we are on
 local function nodeside(node, tubedir)
-       if node and (node.param2 < 0 or node.param2 > 23) then node.param2 = 0 end
+       if node.param2 < 0 or node.param2 > 23 then
+               node.param2 = 0
+       end
 
-       --get a vector pointing back
        local backdir = minetest.facedir_to_dir(node.param2)
-
-       --check whether the vector is equivalent to the tube direction; if it is, the tube's on the backside
-       if backdir.x == tubedir.x and backdir.y == tubedir.y and backdir.z == tubedir.z then
+       local back = vector.dot(backdir, tubedir)
+       if back == 1 then
                return "back"
-       end
-
-       --check whether the vector is antiparallel with the tube direction; that indicates the front
-       if backdir.x == -tubedir.x and backdir.y == -tubedir.y and backdir.z == -tubedir.z then
+       elseif back == -1 then
                return "front"
        end
 
-       --facedir is defined in terms of the top-bottom axis of the node; we'll take advantage of that
-       local topdir = ({[0]={x=0, y=1, z=0},
-       {x=0, y=0, z=1},
-       {x=0, y=0, z=-1},
-       {x=1, y=0, z=0},
-       {x=-1, y=0, z=0},
-       {x=0, y=-1, z=0}})[math.floor(node.param2/4)]
-
-       --is this the top?
-       if topdir.x == tubedir.x and topdir.y == tubedir.y and topdir.z == tubedir.z then
+       local topdir = minetest.facedir_to_top_dir(node.param2)
+       local top = vector.dot(topdir, tubedir)
+       if top == 1 then
                return "top"
-       end
-
-       --or the bottom?
-       if topdir.x == -tubedir.x and topdir.y == -tubedir.y and topdir.z == -tubedir.z then
+       elseif top == -1 then
                return "bottom"
        end
 
-       --we shall apply some maths to obtain the right-facing vector
-       local rightdir = {x=topdir.y*backdir.z - backdir.y*topdir.z,
-       y=topdir.z*backdir.x - backdir.z*topdir.x,
-       z=topdir.x*backdir.y - backdir.x*topdir.y}
-
-       --is this the right side?
-       if rightdir.x == tubedir.x and rightdir.y == tubedir.y and rightdir.z == tubedir.z then
+       local rightdir = minetest.facedir_to_right_dir(node.param2)
+       local right = vector.dot(rightdir, tubedir)
+       if right == 1 then
                return "right"
-       end
-
-       --or the left?
-       if rightdir.x == -tubedir.x and rightdir.y == -tubedir.y and rightdir.z == -tubedir.z then
+       else
                return "left"
        end
-
-       --we should be done by now; initiate panic mode
-       minetest.log("error", "nodeside has been confused by its parameters; see pipeworks autoplace_tubes.lua, line 78")
 end
 
 local vts = {0, 3, 1, 4, 2, 5}
@@ -78,23 +44,23 @@ local function tube_autoroute(pos)
        if not is_tube(nctr.name) then return end
 
        local adjustments = {
-               { x=-1, y=0, z=0 },
-               { x=1, y=0, z=0  },
-               { x=0, y=-1, z=0 },
-               { x=0, y=1, z=0  },
-               { x=0, y=0, z=-1 },
-               { x=0, y=0, z=1 }
+               {x = -1, y =  0, z =  0},
+               {x =  1, y =  0, z =  0},
+               {x =  0, y = -1, z =  0},
+               {x =  0, y =  1, z =  0},
+               {x =  0, y =  0, z = -1},
+               {x =  0, y =  0, z =  1}
        }
        -- xm = 1, xp = 2, ym = 3, yp = 4, zm = 5, zp = 6
 
        local positions = {}
        local nodes = {}
-       for i,adj in ipairs(adjustments) do
-               positions[i] = {x=pos.x+adj.x, y=pos.y+adj.y, z=pos.z+adj.z}
+       for i, adj in ipairs(adjustments) do
+               positions[i] = vector.add(pos, adj)
                nodes[i] = minetest.get_node(positions[i])
        end
 
-       for i,node in ipairs(nodes) do
+       for i, node in ipairs(nodes) do
                local idef = minetest.registered_nodes[node.name]
                -- handle the tubes themselves
                if is_tube(node.name) then
@@ -102,7 +68,9 @@ local function tube_autoroute(pos)
                -- handle new style connectors
                elseif idef and idef.tube and idef.tube.connect_sides then
                        local dir = adjustments[i]
-                       if idef.tube.connect_sides[nodeside(node, {x=-dir.x, y=-dir.y, z=-dir.z})] then active[i] = 1 end
+                       if idef.tube.connect_sides[nodeside(node, vector.multiply(dir, -1))] then
+                               active[i] = 1
+                       end
                end
        end
 
@@ -110,18 +78,17 @@ local function tube_autoroute(pos)
 
        local nodedef = minetest.registered_nodes[nctr.name]
        local basename = nodedef.basename
-       local newname
        if nodedef.style == "old" then
                local nsurround = ""
-               for i,n in ipairs(active) do
-                       nsurround = nsurround .. n
+               for i, n in ipairs(active) do
+                       nsurround = nsurround..n
                end
                nctr.name = basename.."_"..nsurround
        elseif nodedef.style == "6d" then
                local s = 0
-               for i,n in ipairs(active) do
+               for i, n in ipairs(active) do
                        if n == 1 then
-                               s = s+2^vts[i]
+                               s = s + 2^vts[i]
                        end
                end
                nctr.name = basename.."_"..tube_table[s]
@@ -131,14 +98,9 @@ local function tube_autoroute(pos)
 end
 
 function pipeworks.scan_for_tube_objects(pos)
-       if not pos or not pos.x or not pos.y or not pos.z then return end
-       tube_autoroute({ x=pos.x-1, y=pos.y  , z=pos.z   })
-       tube_autoroute({ x=pos.x+1, y=pos.y  , z=pos.z   })
-       tube_autoroute({ x=pos.x  , y=pos.y-1, z=pos.z   })
-       tube_autoroute({ x=pos.x  , y=pos.y+1, z=pos.z   })
-       tube_autoroute({ x=pos.x  , y=pos.y  , z=pos.z-1 })
-       tube_autoroute({ x=pos.x  , y=pos.y  , z=pos.z+1 })
-       tube_autoroute(pos)
+       for side = 0, 6 do
+               tube_autoroute(vector.add(pos, directions.side_to_dir(side)))
+       end
 end
 
 minetest.register_on_placenode(function(pos, newnode, placer, oldnode, itemstack)
@@ -157,7 +119,7 @@ minetest.register_on_dignode(function(pos, oldnode, digger)
        end
 end)
 
-if minetest.get_modpath("mesecons_mvps") ~= nil then
+if minetest.get_modpath("mesecons_mvps") then
        mesecon:register_on_mvps_move(function(moved_nodes)
                for _, n in ipairs(moved_nodes) do
                        pipeworks.scan_for_tube_objects(n.pos)
diff --git a/common.lua b/common.lua
new file mode 100755 (executable)
index 0000000..6a92198
--- /dev/null
@@ -0,0 +1,144 @@
+----------------------
+-- Vector functions --
+----------------------
+
+function vector.cross(a, b)
+       return {
+               x = a.y * b.z - a.z * b.y,
+               y = a.z * b.x - a.x * b.z,
+               z = a.x * b.y - a.y * b.x
+       }
+end
+
+function vector.dot(a, b)
+       return a.x * b.x + a.y * b.y + a.z * b.z
+end
+
+-----------------------
+-- Facedir functions --
+-----------------------
+
+function minetest.facedir_to_top_dir(facedir)
+       return  ({[0] = {x =  0, y =  1, z =  0},
+                       {x =  0, y =  0, z =  1},
+                       {x =  0, y =  0, z = -1},
+                       {x =  1, y =  0, z =  0},
+                       {x = -1, y =  0, z =  0},
+                       {x =  0, y = -1, z =  0}})
+               [math.floor(facedir / 4)]
+end
+
+function minetest.facedir_to_right_dir(facedir)
+       return vector.cross(
+               minetest.facedir_to_top_dir(facedir),
+               minetest.facedir_to_dir(facedir)
+       )
+end
+
+directions = {}
+function directions.side_to_dir(side)
+       return ({[0] = vector.new(),
+               vector.new( 0,  1,  0),
+               vector.new( 0, -1,  0),
+               vector.new( 1,  0,  0),
+               vector.new(-1,  0,  0),
+               vector.new( 0,  0,  1),
+               vector.new( 0,  0, -1)
+       })[side]
+end
+
+function directions.dir_to_side(dir)
+       local c = vector.dot(dir, vector.new(1, 2, 3)) + 4
+       return ({6, 2, 4, 0, 3, 1, 5})[c]
+end
+
+----------------------
+-- String functions --
+----------------------
+
+--[[function string.split(str, sep)
+       local fields = {}
+       local index = 1
+       local expr = "([^"..sep.."])+"
+       string.gsub(str, expr, function(substring)
+               fields[index] = substring
+               index = index + 1
+       end)
+       return fields
+end]]
+
+function string.startswith(str, substr)
+       return str:sub(1, substr:len()) == substr
+end
+
+---------------------
+-- Table functions --
+---------------------
+
+function table.contains(tbl, element)
+       for _, elt in pairs(tbl) do
+               if elt == element then
+                       return true
+               end
+       end
+       return false
+end
+
+function table.extend(tbl, tbl2)
+       local index = #tbl + 1
+       for _, elt in ipairs(tbl2) do
+               tbl[index] = elt
+               index = index + 1
+       end
+end
+
+function table.recursive_replace(tbl, pattern, replace_with)
+       if type(tbl) == "table" then
+               local tbl2 = {}
+               for key, value in pairs(tbl) do
+                       tbl2[key] = table.recursive_replace(value, pattern, replace_with)
+               end
+               return tbl2
+       elseif type(tbl) == "string" then
+               return tbl:gsub(pattern, replace_with)
+       else
+               return tbl
+       end
+end
+
+------------------------
+-- Formspec functions --
+------------------------
+
+fs_helpers = {}
+function fs_helpers.on_receive_fields(pos, fields)
+       local meta = minetest.get_meta(pos)
+       for field, value in pairs(fields) do
+               if field:startswith("fs_helpers_cycling:") then
+                       local l = field:split(":")
+                       local new_value = tonumber(l[2])
+                       local meta_name = l[3]
+                       meta:set_int(meta_name, new_value)
+               end
+       end
+end
+
+function fs_helpers.cycling_button(meta, base, meta_name, values)
+       local current_value = meta:get_int(meta_name)
+       local new_value = (current_value + 1) % (#values)
+       local text = values[current_value + 1]
+       local field = "fs_helpers_cycling:"..new_value..":"..meta_name
+       return base..";"..field..";"..text.."]"
+end
+
+---------
+-- Env --
+---------
+
+function minetest.load_position(pos)
+       if minetest.get_node_or_nil(pos) then
+               return
+       end
+       local vm = minetest.get_voxel_manip()
+       vm:read_from_map(pos, pos)
+end
\ No newline at end of file
old mode 100644 (file)
new mode 100755 (executable)
index f78b41f..b6c91d6
--- a/init.lua
+++ b/init.lua
@@ -106,9 +106,11 @@ end
 -------------------------------------------
 -- Load the various other parts of the mod
 
+dofile(pipeworks.modpath.."/common.lua")
 dofile(pipeworks.modpath.."/models.lua")
 dofile(pipeworks.modpath.."/autoplace_pipes.lua")
 dofile(pipeworks.modpath.."/autoplace_tubes.lua")
+dofile(pipeworks.modpath.."/luaentity.lua")
 dofile(pipeworks.modpath.."/item_transport.lua")
 dofile(pipeworks.modpath.."/flowing_logic.lua")
 dofile(pipeworks.modpath.."/crafts.lua")
old mode 100644 (file)
new mode 100755 (executable)
index f01506b..db4ffb0
@@ -1,34 +1,17 @@
-dofile(pipeworks.modpath.."/compat.lua")
-
---and an extra function for getting the right-facing vector
-local function facedir_to_right_dir(facedir)
-       
-       --find the other directions
-       local backdir = minetest.facedir_to_dir(facedir)
-       local topdir = ({[0]={x=0, y=1, z=0},
-                                                                       {x=0, y=0, z=1},
-                                                                       {x=0, y=0, z=-1},
-                                                                       {x=1, y=0, z=0},
-                                                                       {x=-1, y=0, z=0},
-                                                                       {x=0, y=-1, z=0}})[math.floor(facedir/4)]
-       
-       --return a cross product
-               return {x=topdir.y*backdir.z - backdir.y*topdir.z,
-                                               y=topdir.z*backdir.x - backdir.z*topdir.x,
-                                               z=topdir.x*backdir.y - backdir.x*topdir.y}
-end
-
 local fakePlayer = {
     get_player_name = function() return ":pipeworks" end,
     -- any other player functions called by allow_metadata_inventory_take anywhere...
     -- perhaps a custom metaclass that errors specially when fakePlayer.<property> is not found?
 }
 
-function pipeworks.tube_item(pos, item)
+function pipeworks.tube_item(pos, start_pos, velocity, item)
        -- Take item in any format
        local stack = ItemStack(item)
-       local obj = minetest.add_entity(pos, "pipeworks:tubed_item")
-       obj:get_luaentity():set_item(stack:to_string())
+       local obj = luaentity.add_entity(pos, "pipeworks:tubed_item")
+       obj:set_item(stack:to_string())
+       obj.start_pos = vector.new(start_pos)
+       obj:setvelocity(velocity)
+       --obj:set_color("red") -- todo: this is test-only code
        return obj
 end
 
@@ -52,19 +35,16 @@ local function set_filter_formspec(data, meta)
                        "item_image[0,0;1,1;pipeworks:"..data.name.."]"..
                        "label[1,0;"..minetest.formspec_escape(itemname).."]"..
                        "label[0,1;Prefer item types:]"..
-                       "list[current_name;main;0,1.5;8,2;]"
-       local slotseq_mode = meta:get_int("slotseq_mode")
-       if slotseq_mode == 1 then
-               formspec = formspec .. "button[0,3.5;4,1;slotseq_mode2;Sequence slots Randomly]"
-       elseif slotseq_mode == 2 then
-               formspec = formspec .. "button[0,3.5;4,1;slotseq_mode0;Sequence slots by Rotation]"
-       else
-               formspec = formspec .. "button[0,3.5;4,1;slotseq_mode1;Sequence slots by Priority]"
-       end
-       formspec = formspec .. "list[current_player;main;0,4.5;8,4;]"
+                       "list[current_name;main;0,1.5;8,2;]"..
+                       fs_helpers.cycling_button(meta, "button[0,3.5;4,1", "slotseq_mode",
+                               {"Sequence slots by Priority",
+                                "Sequence slots Randomly",
+                                "Sequence slots by Rotation"})..
+                       "list[current_player;main;0,4.5;8,4;]"
        meta:set_string("formspec", formspec)
 end
 
+-- todo SOON: this function has *way too many* parameters
 local function grabAndFire(data,slotseq_mode,filtmeta,frominv,frominvname,frompos,fromnode,filtername,fromtube,fromdef,dir,all)
        local sposes = {}
        for spos,stack in ipairs(frominv:get_list(frominvname)) do
@@ -130,10 +110,9 @@ local function grabAndFire(data,slotseq_mode,filtmeta,frominv,frominvname,frompo
                                                fromdef.on_metadata_inventory_take(frompos, frominvname, spos, item, fakePlayer)
                                        end
                                end
-                               local item1 = pipeworks.tube_item(vector.add(frompos, vector.multiply(dir, 1.4)), item)
-                               item1:get_luaentity().start_pos = vector.add(frompos, dir)
-                               item1:setvelocity(dir)
-                               item1:setacceleration({x=0, y=0, z=0})
+                               local pos = vector.add(frompos, vector.multiply(dir, 1.4))
+                               local start_pos = vector.add(frompos, dir)
+                               local item1 = pipeworks.tube_item(pos, start_pos, dir, item)
                                return true-- only fire one item, please
                        end
        end
@@ -143,8 +122,8 @@ end
 local function punch_filter(data, filtpos, filtnode)
        local filtmeta = minetest.get_meta(filtpos)
        local filtinv = filtmeta:get_inventory()
-       local dir = facedir_to_right_dir(filtnode.param2)
-       local frompos = {x=filtpos.x - dir.x, y=filtpos.y - dir.y, z=filtpos.z - dir.z}
+       local dir = minetest.facedir_to_right_dir(filtnode.param2)
+       local frompos = vector.subtract(filtpos, dir)
        local fromnode = minetest.get_node(frompos)
        if not fromnode then return end
        local fromdef = minetest.registered_nodes[fromnode.name]
@@ -197,7 +176,7 @@ for _, data in ipairs({
                        "pipeworks_"..data.name.."_top.png",
                },
                paramtype2 = "facedir",
-               groups = {snappy=2,choppy=2,oddly_breakable_by_hand=2,tubedevice=1,mesecon=2},
+               groups = {snappy = 2, choppy = 2, oddly_breakable_by_hand = 2, mesecon = 2},
                legacy_facedir_simple = true,
                sounds = default.node_sound_wood_defaults(),
                on_construct = function(pos)
@@ -208,28 +187,17 @@ for _, data in ipairs({
                        inv:set_size("main", 8*2)
                end,
                on_receive_fields = function(pos, formname, fields, sender)
+                       fs_helpers.on_receive_fields(pos, fields)
                        local meta = minetest.get_meta(pos)
-                       for k, _ in pairs(fields) do
-                               if k:sub(1, 12) == "slotseq_mode" then
-                                       local mode = tonumber(k:sub(13, 13))
-                                       meta:set_int("slotseq_mode", mode)
-                                       meta:set_int("slotseq_index", mode == 2 and 1 or 0)
-                               end
-                       end
+                       meta:set_int("slotseq_index", 1)
                        set_filter_formspec(data, meta)
                        set_filter_infotext(data, meta)
                end,
-               can_dig = function(pos,player)
+               can_dig = function(pos, player)
                        local meta = minetest.get_meta(pos)
                        local inv = meta:get_inventory()
                        return inv:is_empty("main")
                end,
-               after_place_node = function(pos)
-                       pipeworks.scan_for_tube_objects(pos)
-               end,
-               after_dig_node = function(pos)
-                       pipeworks.scan_for_tube_objects(pos)
-               end,
                mesecons = {
                        effector = {
                                action_on = function(pos, node)
@@ -237,21 +205,13 @@ for _, data in ipairs({
                                end,
                        },
                },
-               tube={connect_sides={right=1}},
+               tube = {connect_sides = {right = 1}},
                on_punch = function (pos, node, puncher)
                        punch_filter(data, pos, node)
                end,
        })
 end
 
-local function roundpos(pos)
-       return {x=math.floor(pos.x+0.5),y=math.floor(pos.y+0.5),z=math.floor(pos.z+0.5)}
-end
-
-local function addVect(pos,vect)
-       return {x=pos.x+vect.x,y=pos.y+vect.y,z=pos.z+vect.z}
-end
-
 local adjlist={{x=0,y=0,z=1},{x=0,y=0,z=-1},{x=0,y=1,z=0},{x=0,y=-1,z=0},{x=1,y=0,z=0},{x=-1,y=0,z=0}}
 
 function pipeworks.notvel(tbl, vel)
@@ -263,18 +223,20 @@ function pipeworks.notvel(tbl, vel)
 end
 
 local function go_next(pos, velocity, stack)
-       local chests = {}
-       local tubes = {}
+       local next_positions = {}
+       local max_priority = 0
        local cnode = minetest.get_node(pos)
        local cmeta = minetest.get_meta(pos)
-       local n
        local can_go
        local speed = math.abs(velocity.x + velocity.y + velocity.z)
+       if speed == 0 then
+               speed = 1
+       end
        local vel = {x = velocity.x/speed, y = velocity.y/speed, z = velocity.z/speed,speed=speed}
        if speed >= 4.1 then
                speed = 4
        elseif speed >= 1.1 then
-               speed = speed-0.1
+               speed = speed - 0.1
        else
                speed = 1
        end
@@ -284,58 +246,40 @@ local function go_next(pos, velocity, stack)
        else
                can_go = pipeworks.notvel(adjlist, vel)
        end
-       local meta = nil
-       for _,vect in ipairs(can_go) do
-               local npos = addVect(pos,vect)
+       for _, vect in ipairs(can_go) do
+               local npos = vector.add(pos, vect)
                local node = minetest.get_node(npos)
-               local tube_receiver = minetest.get_item_group(node.name,"tubedevice_receiver")
-               meta = minetest.get_meta(npos)
-               local tubelike = meta:get_int("tubelike")
-               if tube_receiver == 1 then
-                       if minetest.registered_nodes[node.name].tube and
-                               minetest.registered_nodes[node.name].tube.can_insert and
-                               minetest.registered_nodes[node.name].tube.can_insert(npos, node, stack, vect) then
-                               local i = #chests + 1
-                               chests[i] = {}
-                               chests[i].pos = npos
-                               chests[i].vect = vect
+               local tubedevice = minetest.get_item_group(node.name, "tubedevice")
+               local tube_def = minetest.registered_nodes[node.name].tube
+               local tube_priority = (tube_def and tube_def.priority) or 100
+               if tubedevice > 0 and tube_priority >= max_priority then
+                       if not tube_def or not tube_def.can_insert or
+                                       tubedef.can_insert(npos, node, stack, vect) then
+                               if tube_priority > max_priority then
+                                       max_priority = tube_priority
+                                       next_positions = {}
+                               end
+                               next_positions[#next_positions + 1] = {pos = npos, vect = vect}
                        end
-               elseif tubelike == 1 then
-                       local i = #tubes + 1
-                       tubes[i] = {}
-                       tubes[i].pos = npos
-                       tubes[i].vect = vect
                end
        end
-       if chests[1] == nil then--no chests found
-               if tubes[1] == nil then
-                       return 0
-               else
-                       n = (cmeta:get_int("tubedir")%(#tubes)) + 1
-                       if pipeworks.enable_cyclic_mode then
-                               cmeta:set_int("tubedir",n)
-                       end
-                       velocity.x = tubes[n].vect.x*vel.speed
-                       velocity.y = tubes[n].vect.y*vel.speed
-                       velocity.z = tubes[n].vect.z*vel.speed
-               end
-       else
-               n = (cmeta:get_int("tubedir")%(#chests))+1
-               if pipeworks.enable_cyclic_mode then
-                       cmeta:set_int("tubedir",n)
-               end
-               velocity.x = chests[n].vect.x*speed
-               velocity.y = chests[n].vect.y*speed
-               velocity.z = chests[n].vect.z*speed
+
+       if not next_positions[1] then
+               return false, nil
+       end
+       
+       local n = (cmeta:get_int("tubedir") % (#next_positions)) + 1
+       if pipeworks.enable_cyclic_mode then
+               cmeta:set_int("tubedir", n)
        end
-       return 1
+       local new_velocity = vector.multiply(next_positions[n].vect, vel.speed)
+       return true, new_velocity
 end
 
 minetest.register_entity("pipeworks:tubed_item", {
        initial_properties = {
                hp_max = 1,
                physical = false,
---             collisionbox = {0,0,0,0,0,0},
                collisionbox = {0.1, 0.1, 0.1, 0.1, 0.1, 0.1},
                visual = "wielditem",
                visual_size = {x = 0.15, y = 0.15},
@@ -343,113 +287,126 @@ minetest.register_entity("pipeworks:tubed_item", {
                spritediv = {x = 1, y = 1},
                initial_sprite_basepos = {x = 0, y = 0},
                is_visible = false,
-               start_pos = {},
-               route = {},
-               removed = false
        },
-       
-       itemstring = '',
+
        physical_state = false,
 
-       set_item = function(self, itemstring)
-               self.itemstring = itemstring
+       from_data = function(self, itemstring)
                local stack = ItemStack(itemstring)
+               local itemtable = stack:to_table()
+               local itemname = nil
+               if itemtable then
+                       itemname = stack:to_table().name
+               end
+               local item_texture = nil
+               local item_type = ""
+               if minetest.registered_items[itemname] then
+                       item_texture = minetest.registered_items[itemname].inventory_image
+                       item_type = minetest.registered_items[itemname].type
+               end
                self.object:set_properties({
                        is_visible = true,
-                       textures = { stack:get_name() },
+                       textures = {stack:get_name()}
                })
                local def = stack:get_definition()
                self.object:setyaw((def and def.type == "node") and 0 or math.pi * 0.25)
        end,
 
-       get_staticdata = function(self)
-               if self.start_pos == nil or self.removed then
-                       return
-               end
-               local velocity = self.object:getvelocity()
-               self.object:setpos(self.start_pos)
-               return  minetest.serialize({
-                       itemstring = self.itemstring,
-                       velocity = velocity,
-                       start_pos = self.start_pos
-               })
+       get_staticdata = luaentity.get_staticdata,
+       on_activate = luaentity.on_activate,
+})
+
+minetest.register_entity("pipeworks:color_entity", {
+       initial_properties = {
+               hp_max = 1,
+               physical = false,
+               collisionbox = {0.1, 0.1, 0.1, 0.1, 0.1, 0.1},
+               visual = "cube",
+               visual_size = {x = 3.5, y = 3.5, z = 3.5}, -- todo: find correct size
+               textures = {""},
+               is_visible = false,
+       },
+
+       physical_state = false,
+
+       from_data = function(self, color)
+               local t = "pipeworks_color_"..color..".png"
+               local prop = {
+                       is_visible = true,
+                       visual = "cube",
+                       textures = {t, t, t, t, t, t} -- todo: textures
+               }
+               self.object:set_properties(prop)
        end,
 
-       on_activate = function(self, staticdata)
-               if  staticdata=="" or staticdata==nil then return end
-               local item = minetest.deserialize(staticdata)
-               local stack = ItemStack(item.itemstring)
-               local itemtable = stack:to_table()
-               local itemname = nil
-               if itemtable then
-                       itemname = stack:to_table().name
+       get_staticdata = luaentity.get_staticdata,
+       on_activate = luaentity.on_activate,
+})
+
+luaentity.register_entity("pipeworks:tubed_item", {
+       itemstring = '',
+       item_entity = nil,
+       color_entity = nil,
+       color = nil,
+       start_pos = nil,
+
+       set_item = function(self, item)
+               local itemstring = ItemStack(item):to_string() -- Accept any input format
+               if self.itemstring == itemstring then
+                       return
                end
-               
-               if itemname then 
-               self.start_pos=item.start_pos
-               self.object:setvelocity(item.velocity)
-               self.object:setacceleration({x=0, y=0, z=0})
-               self.object:setpos(item.start_pos)
+               if self.item_entity then
+                       self:remove_attached_entity(self.item_entity)
                end
-               self:set_item(item.itemstring)
+               self.itemstring = itemstring
+               self.item_entity = self:add_attached_entity("pipeworks:tubed_item", itemstring)
        end,
        
-       remove = function(self)
-               self.object:remove()
-               self.removed = true
-               self.itemstring = ''
+       set_color = function(self, color)
+               if self.color == color then
+                       return
+               end
+               self.color = color
+               if self.color_entity then
+                       self:remove_attached_entity(self.color_entity)
+               end
+               if color then
+                       self.color_entity = self:add_attached_entity("pipeworks:color_entity", color)
+               else
+                       self.color_entity = nil
+               end
        end,
 
        on_step = function(self, dtime)
-               if self.removed then
-                       return
-               end
                if self.start_pos == nil then
-                       local pos = self.object:getpos()
-                       self.start_pos = roundpos(pos)
+                       local pos = self:getpos()
+                       self.start_pos = vector.round(pos)
+                       self:setpos(pos)
                end
-               local pos = self.object:getpos()
-               local node = minetest.get_node(pos)
-               local meta = minetest.get_meta(pos)
-               local tubelike = meta:get_int("tubelike")
+               
+               local pos = self:getpos()
                local stack = ItemStack(self.itemstring)
-               local drop_pos = nil
+               local drop_pos
                
-               local velocity = self.object:getvelocity()
-       
-               if velocity == nil then return end
-       
-               local velocitycopy = {x = velocity.x, y = velocity.y, z = velocity.z}
+               local velocity = self:getvelocity()
                
                local moved = false
                local speed = math.abs(velocity.x + velocity.y + velocity.z)
+               if speed == 0 then
+                       speed = 1
+                       moved = true
+               end
                local vel = {x = velocity.x / speed, y = velocity.y / speed, z = velocity.z / speed, speed = speed}
                
-               if math.abs(vel.x) == 1 then
-                       local next_node = math.abs(pos.x - self.start_pos.x)
-                       if next_node >= 1 then 
-                               self.start_pos.x = self.start_pos.x + vel.x
-                               moved = true
-                       end
-               elseif math.abs(vel.y) == 1 then
-               local next_node = math.abs(pos.y - self.start_pos.y)
-                       if next_node >= 1 then 
-                               self.start_pos.y = self.start_pos.y + vel.y
-                               moved = true
-                       end     
-               elseif math.abs(vel.z) == 1 then
-                       local next_node = math.abs(pos.z - self.start_pos.z)
-                       if next_node >= 1 then 
-                               self.start_pos.z = self.start_pos.z + vel.z
-                               moved = true
-                       end
+               if vector.distance(pos, self.start_pos) >= 1 then
+                       self.start_pos = vector.add(self.start_pos, vel)
+                       moved = true
                end
                
-               local sposcopy = {x = self.start_pos.x, y = self.start_pos.y, z = self.start_pos.z}
-               
-               node = minetest.get_node(self.start_pos)
+               minetest.load_position(self.start_pos)
+               local node = minetest.get_node(self.start_pos)
                if moved and minetest.get_item_group(node.name, "tubedevice_receiver") == 1 then
-                       local leftover = nil
+                       local leftover
                        if minetest.registered_nodes[node.name].tube and minetest.registered_nodes[node.name].tube.insert_object then
                                leftover = minetest.registered_nodes[node.name].tube.insert_object(self.start_pos, node, stack, vel)
                        else
@@ -459,62 +416,48 @@ minetest.register_entity("pipeworks:tubed_item", {
                                self:remove()
                                return
                        end
-                       velocity.x = -velocity.x
-                       velocity.y = -velocity.y
-                       velocity.z = -velocity.z
-                       self.object:setvelocity(velocity)
+                       velocity = vector.multiply(velocity, -1)
+                       self:setvelocity(velocity)
                        self:set_item(leftover:to_string())
                        return
                end
                
                if moved then
-                       if go_next (self.start_pos, velocity, stack) == 0 then
+                       local found_next, new_velocity = go_next(self.start_pos, velocity, stack) -- todo: color
+                       if not found_next then
                                drop_pos = minetest.find_node_near(vector.add(self.start_pos, velocity), 1, "air")
                                if drop_pos then 
                                        minetest.item_drop(stack, "", drop_pos)
                                        self:remove()
+                                       return
                                end
                        end
-               end
-               
-               if velocity.x~=velocitycopy.x or velocity.y~=velocitycopy.y or velocity.z~=velocitycopy.z or 
-                               self.start_pos.x~=sposcopy.x or self.start_pos.y~=sposcopy.y or self.start_pos.z~=sposcopy.z then
-                       self.object:setpos(self.start_pos)
-                       self.object:setvelocity(velocity)
+                       
+                       if new_velocity and not vector.equals(velocity, new_velocity) then
+                               self:setpos(self.start_pos)
+                               self:setvelocity(new_velocity)
+                       end
                end
        end
 })
 
-if minetest.get_modpath("mesecons_mvps") ~= nil then
-       local function add_table(table,toadd)
-               local i = 1
-               while true do
-                       o = table[i]
-                       if o == toadd then return end
-                       if o == nil then break end
-                       i = i+1
-               end
-               table[i] = toadd
-       end
+if minetest.get_modpath("mesecons_mvps") then
        mesecon:register_mvps_unmov("pipeworks:tubed_item")
+       mesecon:register_mvps_unmov("pipeworks:color_entity")
        mesecon:register_on_mvps_move(function(moved_nodes)
-               local objects_to_move = {}
+               local moved = {}
                for _, n in ipairs(moved_nodes) do
-                       local objects = minetest.get_objects_inside_radius(n.oldpos, 1)
-                       for _, obj in ipairs(objects) do
-                               local entity = obj:get_luaentity()
-                               if entity and entity.name == "pipeworks:tubed_item" then
-                                       --objects_to_move[#objects_to_move+1] = obj
-                                       add_table(objects_to_move, obj)
-                               end
-                       end
+                       moved[minetest.hash_node_position(n.oldpos)] = vector.subtract(n.pos, n.oldpos)
                end
-               if #objects_to_move > 0 then
-                       local dir = vector.subtract(moved_nodes[1].pos, moved_nodes[1].oldpos)
-                       for _, obj in ipairs(objects_to_move) do
-                               local entity = obj:get_luaentity()
-                               obj:setpos(vector.add(obj:getpos(), dir))
-                               entity.start_pos = vector.add(entity.start_pos, dir)
+               for id, entity in pairs(luaentity.entities) do
+                       if entity.name == "pipeworks:tubed_item" then
+                               local pos = entity:getpos()
+                               local rpos = vector.round(pos)
+                               local dir = moved[minetest.hash_node_position(rpos)]
+                               if dir then
+                                       entity:setpos(vector.add(pos, dir))
+                                       entity.start_pos = vector.add(entity.start_pos, dir)
+                               end
                        end
                end
        end)
diff --git a/luaentity.lua b/luaentity.lua
new file mode 100755 (executable)
index 0000000..a4ef4a0
--- /dev/null
@@ -0,0 +1,335 @@
+local max_entity_id = 1000000000000 -- If you need more, there's a problem with your code
+
+luaentity = {}
+
+luaentity.registered_entities = {}
+
+local filename = minetest.get_worldpath().."/luaentities"
+local function read_file()
+       local f = io.open(filename, "r")
+       if f == nil then return {} end
+       local t = f:read("*all")
+       f:close()
+       if t == "" or t == nil then return {} end
+       return minetest.deserialize(t)
+end
+
+local function write_file(tbl)
+       local f = io.open(filename, "w")
+       f:write(minetest.serialize(tbl))
+       f:close()
+end
+
+local function read_entities()
+       local t = read_file()
+       for _, entity in pairs(t) do
+               setmetatable(entity, luaentity.registered_entities[entity.name])
+       end
+       return t
+end
+
+local function write_entities()
+       for _, entity in pairs(luaentity.entities) do
+               setmetatable(entity, nil)
+               for _, attached in pairs(entity._attached_entities) do
+                       if attached.entity then
+                               attached.entity:remove()
+                               attached.entity = nil
+                       end
+               end
+               entity._attached_entities_master = nil
+       end
+       write_file(luaentity.entities)
+end
+
+minetest.after(0, function()
+       luaentity.entities = read_entities()
+end)
+minetest.register_on_shutdown(write_entities)
+ -- todo: load that from file (datastorage?) -> don't forget about metatables (are those serialized?) / do not blindly save -> the attached_entities have to be removed
+luaentity.entities_index = 0
+
+local function get_blockpos(pos)
+       return {x = math.floor(pos.x / 16),
+               y = math.floor(pos.y / 16),
+               z = math.floor(pos.z / 16)}
+end
+
+local active_blocks = {} -- These only contain active blocks near players (i.e., not forceloaded ones)
+local handle_active_blocks_step = 2
+local handle_active_blocks_timer = 0
+minetest.register_globalstep(function(dtime)
+       handle_active_blocks_timer = handle_active_blocks_timer + dtime
+       if handle_active_blocks_timer >= handle_active_blocks_step then
+               handle_active_blocks_timer = handle_active_blocks_timer - handle_active_blocks_step
+               local active_block_range = tonumber(minetest.setting_get("active_block_range"))
+               local new_active_blocks = {}
+               for _, player in ipairs(minetest.get_connected_players()) do
+                       local blockpos = get_blockpos(player:getpos())
+                       local minp = vector.subtract(blockpos, active_block_range)
+                       local maxp = vector.add(blockpos, active_block_range)
+
+                       for x = minp.x, maxp.x do
+                       for y = minp.y, maxp.y do
+                       for z = minp.z, maxp.z do
+                               local pos = {x = x, y = y, z = z}
+                               new_active_blocks[minetest.hash_node_position(pos)] = pos
+                       end
+                       end
+                       end
+               end
+               active_blocks = new_active_blocks
+               -- todo: callbacks on block load/unload
+       end
+end)
+
+local function is_active(pos)
+       return active_blocks[minetest.hash_node_position(get_blockpos(pos))] ~= nil
+end
+
+local entitydef_default = {
+       _attach = function(self, attached, attach_to)
+               local attached_def = self._attached_entities[attached]
+               local attach_to_def = self._attached_entities[attach_to]
+               attached_def.entity:set_attach(
+                       attach_to_def.entity, "",
+                       vector.subtract(attached_def.offset, attach_to_def.offset), -- todo: Does not work because is object space
+                       vector.new(0, 0, 0)
+               )
+       end,
+       _set_master = function(self, index)
+               self._attached_entities_master = index
+               if not index then
+                       return
+               end
+               local def = self._attached_entities[index]
+               if not def.entity then
+                       return
+               end
+               def.entity:setpos(vector.add(self._pos, def.offset))
+               def.entity:setvelocity(self._velocity)
+               def.entity:setacceleration(self._acceleration)
+       end,
+       _attach_all = function(self)
+               local master = self._attached_entities_master
+               if not master then
+                       return
+               end
+               for id, entity in pairs(self._attached_entities) do
+                       if id ~= master and entity.entity then
+                               self:_attach(id, master)
+                       end
+               end
+       end,
+       _detach_all = function(self)
+               local master = self._attached_entities_master
+               for id, entity in pairs(self._attached_entities) do
+                       if id ~= master and entity.entity then
+                               entity.entity:set_detach()
+                       end
+               end
+       end,
+       _add_attached = function(self, index)
+               local entity = self._attached_entities[index]
+               if entity.entity then
+                       return
+               end
+               local entity_pos = vector.add(self._pos, entity.offset)
+               if not is_active(entity_pos) then
+                       return
+               end
+               local ent = minetest.add_entity(entity_pos, entity.name):get_luaentity()
+               ent:from_data(entity.data)
+               ent.parent_id = self._id
+               ent.attached_id = index
+               entity.entity = ent.object
+               local master = self._attached_entities_master
+               if master then
+                       self:_attach(index, master)
+               else
+                       self:_set_master(index)
+               end
+       end,
+       _remove_attached = function(self, index)
+               local master = self._attached_entities_master
+               local entity = self._attached_entities[index]
+               local ent = entity.entity
+               entity.entity = nil
+               if index == master then
+                       self:_detach_all()
+                       local newmaster
+                       for id, attached in pairs(self._attached_entities) do
+                               if id ~= master and attached.entity then
+                                       newmaster = id
+                                       break
+                               end
+                       end
+                       self:_set_master(newmaster)
+                       self:_attach_all()
+               elseif master and ent then
+                       ent:set_detach()
+               end
+               if ent then
+                       ent:remove()
+               end
+       end,
+       _add_loaded = function(self)
+               for id, _ in pairs(self._attached_entities) do
+                       self:_add_attached(id)
+               end
+       end,
+       getid = function(self)
+               return self._id
+       end,
+       getpos = function(self)
+               return vector.new(self._pos)
+       end,
+       setpos = function(self, pos)
+               self._pos = vector.new(pos)
+               --for _, entity in pairs(self._attached_entities) do
+               --      if entity.entity then
+               --              entity.entity:setpos(vector.add(self._pos, entity.offset))
+               --      end
+               --end
+               local master = self._attached_entities_master
+               if master then
+                       local master_def = self._attached_entities[master]
+                       master_def.entity:setpos(vector.add(self._pos, master_def.offset))
+               end
+       end,
+       getvelocity = function(self)
+               return vector.new(self._velocity)       
+       end,
+       setvelocity = function(self, velocity)
+               self._velocity = vector.new(velocity)
+               local master = self._attached_entities_master
+               if master then
+                       self._attached_entities[master].entity:setvelocity(self._velocity)
+               end
+       end,
+       getacceleration = function(self)
+               return vector.new(self._acceleration)
+       end,
+       setacceleration = function(self, acceleration)
+               self._acceleration = vector.new(acceleration)
+               local master = self._attached_entities_master
+               if master then
+                       self._attached_entities[master].entity:setacceleration(self._acceleration)
+               end
+       end,
+       remove = function(self)
+               self:_detach_all()
+               for _, entity in pairs(self._attached_entities) do
+                       if entity.entity then
+                               entity.entity:remove()
+                       end
+               end
+               luaentity.entities[self._id] = nil
+       end,
+       add_attached_entity = function(self, name, data, offset)
+               local index = #self._attached_entities + 1
+               self._attached_entities[index] = {
+                       name = name,
+                       data = data,
+                       offset = vector.new(offset),
+               }
+               self:_add_attached(index)
+               return index
+       end,
+       remove_attached_entity = function(self, index)
+               self:_remove_attached(index)
+               self._attached_entities[index] = nil
+       end,
+}
+
+function luaentity.register_entity(name, prototype)
+       -- name = check_modname_prefix(name)
+       prototype.name = name
+       setmetatable(prototype, {__index = entitydef_default})
+       prototype.__index = prototype -- Make it possible to use it as metatable
+       luaentity.registered_entities[name] = prototype
+end
+
+-- function luaentity.get_entity_definition(entity)
+--      return luaentity.registered_entities[entity.name]
+-- end
+
+function luaentity.add_entity(pos, name)
+       local index = luaentity.entities_index
+       while luaentity.entities[index] do
+               index = index + 1
+               if index >= max_entity_id then
+                       index = 0
+               end
+       end
+       luaentity.entities_index = index
+
+       local entity = {
+               name = name,
+               _id = index,
+               _pos = vector.new(pos),
+               _velocity = {x = 0, y = 0, z = 0},
+               _acceleration = {x = 0, y = 0, z = 0},
+               _attached_entities = {},
+       }
+       
+       local prototype = luaentity.registered_entities[name]
+       setmetatable(entity, prototype) -- Default to prototype for other methods
+       luaentity.entities[index] = entity
+
+       if entity.on_activate then
+               entity:on_activate()
+       end
+       return entity
+end
+
+-- todo: check if remove in get_staticdata works
+function luaentity.get_staticdata(self)
+       local parent = luaentity.entities[self.parent_id]
+       if parent and parent._remove_attached then
+               parent:_remove_attached(self.attached_id)
+       end
+       return "toremove"
+end
+
+function luaentity.on_activate(self, staticdata)
+       if staticdata == "toremove" then
+               self.object:remove()
+       end
+end
+
+function luaentity.get_objects_inside_radius(pos, radius)
+       local objects = {}
+       local index = 1
+       for id, entity in pairs(luaentity.entities) do
+               if vector.distance(pos, entity:getpos()) <= radius then
+                       objects[index] = entity
+                       index = index + 1
+               end
+       end
+end
+
+minetest.register_globalstep(function(dtime)
+       for id, entity in pairs(luaentity.entities) do
+               local master = entity._attached_entities_master
+               if master then
+                       local master_def = entity._attached_entities[master]
+                       local master_entity = master_def.entity
+                       entity._pos = vector.subtract(master_entity:getpos(), master_def.offset)
+                       entity._velocity = master_entity:getvelocity()
+                       entity._acceleration = master_entity:getacceleration()
+               else
+                       entity._pos = vector.add(vector.add(
+                               entity._pos,
+                               vector.multiply(entity._velocity, dtime)),
+                               vector.multiply(entity._acceleration, 0.5 * dtime * dtime))
+                       entity._velocity = vector.add(
+                               entity._velocity,
+                               vector.multiply(entity._acceleration, dtime))
+               end
+               entity:_add_loaded()
+               if entity.on_step then
+                       entity:on_step(dtime)
+               end
+       end
+end)
index 880ab593290fe97022e470471bae70472ae7eb40..fdec79f604d2babdb0720f69982e0f6b2982d8dd 100644 (file)
@@ -9,15 +9,13 @@ minetest.register_node("pipeworks:trashcan", {
                "pipeworks_trashcan_side.png",
                "pipeworks_trashcan_side.png",
        }, 
-       groups = { snappy = 3, tubedevice = 1, tubedevice_receiver = 1 }, 
+       groups = {snappy = 3, tubedevice = 1, tubedevice_receiver = 1}, 
        tube = {
                insert_object = function(pos, node, stack, direction)
                        return ItemStack("")
-               end, 
-               can_insert = function(pos, node, stack, direction)
-                       return true
-               end, 
-               connect_sides = { left = 1, right = 1, front = 1, back = 1, top = 1, bottom = 1 },
+               end,
+               connect_sides = {left = 1, right = 1, front = 1, back = 1, top = 1, bottom = 1},
+               priority = 1, -- Lower than anything else
        }, 
        on_construct = function(pos)
                local meta = minetest.get_meta(pos)
old mode 100644 (file)
new mode 100755 (executable)
index 97f0237..c0375ef
--- a/tubes.lua
+++ b/tubes.lua
@@ -20,7 +20,7 @@ local register_one_tube = function(name, tname, dropname, desc, plain, noctrs, e
        end
        
        for _, v in ipairs(connects) do
-               pipeworks.add_node_box(outboxes, pipeworks.tube_boxes[v])
+               table.extend(outboxes, pipeworks.tube_boxes[v])
                table.insert(outsel, pipeworks.tube_selectboxes[v])
                outimgs[vti[v]] = noctrs[v]
        end
@@ -31,13 +31,13 @@ local register_one_tube = function(name, tname, dropname, desc, plain, noctrs, e
                outimgs[vti[v]] = ends[v]
        end
 
-       local tgroups = {snappy = 3, tube = 1, not_in_creative_inventory = 1}
+       local tgroups = {snappy = 3, tube = 1, tubedevice = 1, not_in_creative_inventory = 1}
        local tubedesc = desc.." "..dump(connects).."... You hacker, you."
        local iimg = plain[1]
        local wscale = {x = 1, y = 1, z = 1}
 
        if #connects == 0 then
-               tgroups = {snappy = 3, tube = 1}
+               tgroups = {snappy = 3, tube = 1, tubedevice = 1}
                tubedesc = desc
                iimg=inv
                outimgs = {
@@ -50,7 +50,8 @@ local register_one_tube = function(name, tname, dropname, desc, plain, noctrs, e
                wscale = {x = 1, y = 1, z = 0.01}
        end
        
-       table.insert(pipeworks.tubenodes, name.."_"..tname)
+       local rname = name.."_"..tname
+       table.insert(pipeworks.tubenodes, rname)
        
        local nodedef = {
                description = tubedesc,
@@ -62,7 +63,7 @@ local register_one_tube = function(name, tname, dropname, desc, plain, noctrs, e
                wield_scale = wscale,
                paramtype = "light",
                selection_box = {
-                       type = "fixed",
+                       type = "fixed",
                        fixed = outsel
                },
                node_box = {
@@ -77,26 +78,22 @@ local register_one_tube = function(name, tname, dropname, desc, plain, noctrs, e
                style = style,
                drop = name.."_"..dropname,
                tubelike = 1,
-               tube = {connect_sides = {front = 1, back = 1, left = 1, right = 1, top = 1, bottom = 1}},
-               on_construct = function(pos)
-                       local meta = minetest.get_meta(pos)
-                       meta:set_int("tubelike", 1)
-                       if minetest.registered_nodes[name.."_"..tname].on_construct_ then
-                               minetest.registered_nodes[name.."_"..tname].on_construct_(pos)
-                       end
-               end,
-               after_place_node = function(pos)
+               tube = {
+                       connect_sides = {front = 1, back = 1, left = 1, right = 1, top = 1, bottom = 1},
+                       priority = 50
+               },
+               --[[after_place_node = function(pos)
                        pipeworks.scan_for_tube_objects(pos)
-                       if minetest.registered_nodes[name.."_"..tname].after_place_node_ then
-                               minetest.registered_nodes[name.."_"..tname].after_place_node_(pos)
+                       if minetest.registered_nodes[rname].after_place_node_ then
+                               minetest.registered_nodes[rname].after_place_node_(pos)
                        end
                end,
                after_dig_node = function(pos)
                        pipeworks.scan_for_tube_objects(pos)
-                       if minetest.registered_nodes[name.."_"..tname].after_dig_node_ then
-                               minetest.registered_nodes[name.."_"..tname].after_dig_node_(pos)
+                       if minetest.registered_nodes[rname].after_dig_node_ then
+                               minetest.registered_nodes[rname].after_dig_node_(pos)
                        end
-               end
+               end]]
        }
        if style == "6d" then
                nodedef.paramtype2 = "facedir"
@@ -105,9 +102,9 @@ local register_one_tube = function(name, tname, dropname, desc, plain, noctrs, e
        if special == nil then special = {} end
 
        for key, value in pairs(special) do
-               if key == "on_construct" or key == "after_dig_node" or key == "after_place_node" then
-                       nodedef[key.."_"] = value
-               elseif key == "groups" then
+               --if key == "after_dig_node" or key == "after_place_node" then
+               --      nodedef[key.."_"] = value
+               if key == "groups" then
                        for group, val in pairs(value) do
                                nodedef.groups[group] = val
                        end
@@ -115,19 +112,12 @@ local register_one_tube = function(name, tname, dropname, desc, plain, noctrs, e
                        for key, val in pairs(value) do
                                nodedef.tube[key] = val
                        end
-               elseif type(value) == "table" then
-                       nodedef[key] = pipeworks.replace_name(value, "#id", tname)
-               elseif type(value) == "string" then
-                       nodedef[key] = string.gsub(value, "#id", tname)
                else
-                       nodedef[key] = value
+                       nodedef[key] = table.recursive_replace(value, "#id", tname)
                end
        end
 
-       local prefix = ":"
-       if string.find(name, "pipeworks:") then prefix = "" end
-
-       minetest.register_node(prefix..name.."_"..tname, nodedef)
+       minetest.register_node(rname, nodedef)
 end
 
 pipeworks.register_tube = function(name, desc, plain, noctrs, ends, short, inv, special, old_registration)
@@ -181,22 +171,18 @@ pipeworks.register_tube = function(name, desc, plain, noctrs, ends, short, inv,
                                wield_image = inv,
                                paramtype = "light",
                                sunlight_propagates = true,
-                               description = desc.." (legacy)",
-                               on_construct = function(pos)
-                                       local meta = minetest.get_meta(pos)
-                                       meta:set_int("tubelike", 1)
-                               end,
-                               after_place_node = function(pos)
+                               description = "Pneumatic tube segment (legacy)",
+                               --[[after_place_node = function(pos)
                                        pipeworks.scan_for_tube_objects(pos)
                                        if minetest.registered_nodes[name.."_1"].after_place_node_ then
                                                minetest.registered_nodes[name.."_1"].after_place_node_(pos)
                                        end
-                               end,
-                               groups = {not_in_creative_inventory = 1, tube_to_update = 1},
+                               end,]]
+                               groups = {not_in_creative_inventory = 1, tube_to_update = 1, tube = 1},
                                tube = {connect_sides = {front = 1, back = 1, left = 1, right = 1, top = 1, bottom = 1}},
                                drop = name.."_1",
                        })
-                       table.insert(pipeworks.tubenodes,cname)
+                       table.insert(pipeworks.tubenodes, cname)
                        for xm = 0, 1 do
                        for xp = 0, 1 do
                        for ym = 0, 1 do
@@ -221,8 +207,8 @@ if REGISTER_COMPATIBILITY then
                interval = 1,
                chance = 1,
                action = function(pos, node, active_object_count, active_object_count_wider)
-                       local minp = {x = pos.x-1, y = pos.y-1, z = pos.z-1}
-                       local maxp = {x = pos.x+1, y = pos.y+1, z = pos.z+1}
+                       local minp = vector.subtract(pos, 1)
+                       local maxp = vector.add(pos, 1)
                        if table.getn(minetest.find_nodes_in_area(minp, maxp, "ignore")) == 0 then
                                pipeworks.scan_for_tube_objects(pos)
                        end
@@ -241,7 +227,7 @@ local end_textures = {"pipeworks_tube_end.png", "pipeworks_tube_end.png", "pipew
 local short_texture = "pipeworks_tube_short.png"
 local inv_texture = "pipeworks_tube_inv.png"
 
-pipeworks.register_tube("pipeworks:tube", "Pneumatic Tube Segment", plain_textures, noctr_textures, end_textures, short_texture, inv_texture)
+pipeworks.register_tube("pipeworks:tube", "Pneumatic tube segment", plain_textures, noctr_textures, end_textures, short_texture, inv_texture)
 
 if pipeworks.enable_mese_tube then
        local mese_noctr_textures = {"pipeworks_mese_tube_noctr_1.png", "pipeworks_mese_tube_noctr_2.png", "pipeworks_mese_tube_noctr_3.png",
@@ -252,6 +238,39 @@ if pipeworks.enable_mese_tube then
                                   "pipeworks_mese_tube_end.png", "pipeworks_mese_tube_end.png", "pipeworks_mese_tube_end.png"}
        local mese_short_texture = "pipeworks_mese_tube_short.png"
        local mese_inv_texture = "pipeworks_mese_tube_inv.png"
+       local function update_formspec(pos)
+               local meta = minetest.get_meta(pos)
+               local old_formspec = meta:get_string("formspec")
+               if string.find(old_formspec, "button0") then -- Old version
+                       local inv = meta:get_inventory()
+                       for i = 1, 6 do
+                               for _, stack in ipairs(inv:get_list("line"..i)) do
+                                       minetest.item_drop(stack, "", pos)
+                               end
+                       end
+               end
+               meta:set_string("formspec",
+                       "size[8,11]"..
+                       "list[current_name;line1;1,0;6,1;]"..
+                       "list[current_name;line2;1,1;6,1;]"..
+                       "list[current_name;line3;1,2;6,1;]"..
+                       "list[current_name;line4;1,3;6,1;]"..
+                       "list[current_name;line5;1,4;6,1;]"..
+                       "list[current_name;line6;1,5;6,1;]"..
+                       "image[0,0;1,1;pipeworks_white.png]"..
+                       "image[0,1;1,1;pipeworks_black.png]"..
+                       "image[0,2;1,1;pipeworks_green.png]"..
+                       "image[0,3;1,1;pipeworks_yellow.png]"..
+                       "image[0,4;1,1;pipeworks_blue.png]"..
+                       "image[0,5;1,1;pipeworks_red.png]"..
+                       fs_helpers.cycling_button(meta, "button[7,0;1,1", "b1s", {"Off", "On"})..
+                       fs_helpers.cycling_button(meta, "button[7,1;1,1", "b2s", {"Off", "On"})..
+                       fs_helpers.cycling_button(meta, "button[7,2;1,1", "b3s", {"Off", "On"})..
+                       fs_helpers.cycling_button(meta, "button[7,3;1,1", "b4s", {"Off", "On"})..
+                       fs_helpers.cycling_button(meta, "button[7,4;1,1", "b5s", {"Off", "On"})..
+                       fs_helpers.cycling_button(meta, "button[7,5;1,1", "b6s", {"Off", "On"})..
+                       "list[current_player;main;0,7;8,4;]")
+       end
        pipeworks.register_tube("pipeworks:mese_tube", "Sorting Pneumatic Tube Segment", mese_plain_textures, mese_noctr_textures,
                                mese_end_textures, mese_short_texture, mese_inv_texture,
                                {tube = {can_go = function(pos, node, velocity, stack)
@@ -266,6 +285,7 @@ if pipeworks.enable_mese_tube then
                                                                         if st:get_name() == name then
                                                                                 found = true
                                                                                 table.insert(tbl, vect)
+                                                                                break
                                                                         end
                                                                 end
                                                         end
@@ -288,71 +308,37 @@ if pipeworks.enable_mese_tube then
                                                 meta:set_int("l"..tostring(i).."s", 1)
                                                 inv:set_size("line"..tostring(i), 6*1)
                                         end
-                                        meta:set_string("formspec",
-                                                        "size[8,11]"..
-                                                        "list[current_name;line1;1,0;6,1;]"..
-                                                        "list[current_name;line2;1,1;6,1;]"..
-                                                        "list[current_name;line3;1,2;6,1;]"..
-                                                        "list[current_name;line4;1,3;6,1;]"..
-                                                        "list[current_name;line5;1,4;6,1;]"..
-                                                        "list[current_name;line6;1,5;6,1;]"..
-                                                        "image[0,0;1,1;pipeworks_white.png]"..
-                                                        "image[0,1;1,1;pipeworks_black.png]"..
-                                                        "image[0,2;1,1;pipeworks_green.png]"..
-                                                        "image[0,3;1,1;pipeworks_yellow.png]"..
-                                                        "image[0,4;1,1;pipeworks_blue.png]"..
-                                                        "image[0,5;1,1;pipeworks_red.png]"..
-                                                        "button[7,0;1,1;button10;On]"..
-                                                        "button[7,1;1,1;button20;On]"..
-                                                        "button[7,2;1,1;button30;On]"..
-                                                        "button[7,3;1,1;button40;On]"..
-                                                        "button[7,4;1,1;button50;On]"..
-                                                        "button[7,5;1,1;button60;On]"..
-                                                        "list[current_player;main;0,7;8,4;]")
-                                        meta:set_string("infotext", "Sorting Pneumatic Tube Segment")
+                                        update_formspec(pos)
+                                        meta:set_string("infotext", "Mese pneumatic tube")
                                 end,
+                                on_punch = update_formspec,
                                 on_receive_fields = function(pos, formname, fields, sender)
-                                        local meta = minetest.get_meta(pos)
-                                        local i
-                                        if fields.quit then return end
-                                        for key, _ in pairs(fields) do
-                                               if key:sub(1, 6) == "button" then
-                                                       local i = key:sub(7, 7)
-                                                       local s = key:sub(8, 8)
-                                                       if s == "" then s = 1 - meta:get_int("l"..i.."s") end
-                                                       meta:set_int("l"..i.."s", s)
-                                               end
-                                        end
-                                        local frm = "size[8,11]"..
-                                                "list[current_name;line1;1,0;6,1;]"..
-                                                "list[current_name;line2;1,1;6,1;]"..
-                                                "list[current_name;line3;1,2;6,1;]"..
-                                                "list[current_name;line4;1,3;6,1;]"..
-                                                "list[current_name;line5;1,4;6,1;]"..
-                                                "list[current_name;line6;1,5;6,1;]"..
-                                                "image[0,0;1,1;pipeworks_white.png]"..
-                                                "image[0,1;1,1;pipeworks_black.png]"..
-                                                "image[0,2;1,1;pipeworks_green.png]"..
-                                                "image[0,3;1,1;pipeworks_yellow.png]"..
-                                                "image[0,4;1,1;pipeworks_blue.png]"..
-                                                "image[0,5;1,1;pipeworks_red.png]"
-                                        for i = 1, 6 do
-                                                local st = meta:get_int("l"..tostring(i).."s")
-                                                if st == 0 then
-                                                        frm = frm.."button[7,"..tostring(i-1)..";1,1;button"..tostring(i).."1;Off]"
-                                                else
-                                                        frm = frm.."button[7,"..tostring(i-1)..";1,1;button"..tostring(i).."0;On]"
-                                                end
-                                        end
-                                        frm = frm.."list[current_player;main;0,7;8,4;]"
-                                        meta:set_string("formspec", frm)
+                                        fs_helpers.on_receive_fields(pos, fields)
+                                        update_formspec(pos)
                                 end,
                                 can_dig = function(pos, player)
                                         local meta = minetest.get_meta(pos)
                                         local inv = meta:get_inventory()
                                         return (inv:is_empty("line1") and inv:is_empty("line2") and inv:is_empty("line3") and
                                                         inv:is_empty("line4") and inv:is_empty("line5") and inv:is_empty("line6"))
-                                end
+                                end,
+                                allow_metadata_inventory_put = function(pos, listname, index, stack, player)
+                                       local inv = minetest.get_meta(pos):get_inventory()
+                                       local stack_copy = ItemStack(stack)
+                                       stack_copy:set_count(1)
+                                       inv:set_stack(listname, index, stack_copy)
+                                       return 0
+                                end,
+                                allow_metadata_inventory_take = function(pos, listname, index, stack, player)
+                                       local inv = minetest.get_meta(pos):get_inventory()
+                                       inv:set_stack(listname, index, ItemStack(""))
+                                       return 0
+                                end,
+                                allow_metadata_inventory_move = function(pos, from_list, from_index, to_list, to_index, count, player)
+                                       local inv = minetest.get_meta(pos):get_inventory()
+                                       inv:set_stack(from_list, from_index, ItemStack(""))
+                                       return 0
+                                end,
                                }, true) -- Must use old tubes, since the textures are rotated with 6d ones
 end
 
@@ -360,6 +346,7 @@ if pipeworks.enable_detector_tube then
        local detector_plain_textures = {"pipeworks_detector_tube_plain.png", "pipeworks_detector_tube_plain.png", "pipeworks_detector_tube_plain.png",
                                         "pipeworks_detector_tube_plain.png", "pipeworks_detector_tube_plain.png", "pipeworks_detector_tube_plain.png"}
        local detector_inv_texture = "pipeworks_detector_tube_inv.png"
+       local detector_tube_step = 2 * tonumber(minetest.setting_get("dedicated_server_step"))
        pipeworks.register_tube("pipeworks:detector_tube_on", "Detecting Pneumatic Tube Segment on (you hacker you)", detector_plain_textures, noctr_textures,
                                end_textures, short_texture, detector_inv_texture,
                                {tube = {can_go = function(pos, node, velocity, stack)
@@ -367,12 +354,8 @@ if pipeworks.enable_detector_tube then
                                                 local name = minetest.get_node(pos).name
                                                 local nitems = meta:get_int("nitems")+1
                                                 meta:set_int("nitems", nitems)
-                                                local saved_pos = { x = pos.x, y = pos.y, z = pos.z }
-                                                minetest.after(0, function ()
-                                                minetest.after(0, function ()
-                                                       minetest.after(0, minetest.registered_nodes[name].item_exit, saved_pos)
-                                                end)
-                                                end)
+                                                local saved_pos = vector.new(pos)
+                                                minetest.after(detector_tube_step, minetest.registered_nodes[name].item_exit, saved_pos)
                                                 return pipeworks.notvel(pipeworks.meseadjlist,velocity)
                                        end},
                                 groups = {mesecon = 2, not_in_creative_inventory = 1},
@@ -396,13 +379,11 @@ if pipeworks.enable_detector_tube then
                                         local meta = minetest.get_meta(pos)
                                         meta:set_int("nitems", 1)
                                         local name = minetest.get_node(pos).name
-                                        local saved_pos = { x = pos.x, y = pos.y, z = pos.z }
-                                        minetest.after(0, function ()
-                                        minetest.after(0, function ()
-                                               minetest.after(0, minetest.registered_nodes[name].item_exit, saved_pos)
-                                        end)
-                                        end)
-       end})
+                                        local saved_pos = vector.new(pos)
+                                        minetest.after(detector_tube_step, minetest.registered_nodes[name].item_exit, saved_pos)
+                               
+                               end
+       })
        pipeworks.register_tube("pipeworks:detector_tube_off", "Detecting Pneumatic Tube Segment", detector_plain_textures, noctr_textures,
                                end_textures, short_texture, detector_inv_texture,
                                {tube = {can_go = function(pos, node, velocity, stack)
@@ -474,7 +455,6 @@ if pipeworks.enable_accelerator_tube then
 end
 
 if pipeworks.enable_crossing_tube then
-       -- FIXME: The textures are not the correct ones
        local crossing_noctr_textures = {"pipeworks_crossing_tube_noctr.png", "pipeworks_crossing_tube_noctr.png", "pipeworks_crossing_tube_noctr.png",
                                         "pipeworks_crossing_tube_noctr.png", "pipeworks_crossing_tube_noctr.png", "pipeworks_crossing_tube_noctr.png"}
        local crossing_plain_textures = {"pipeworks_crossing_tube_plain.png" ,"pipeworks_crossing_tube_plain.png", "pipeworks_crossing_tube_plain.png",
@@ -513,10 +493,7 @@ if pipeworks.enable_sand_tube then
                                       for _, object in ipairs(minetest.get_objects_inside_radius(pos, 2)) do
                                               if not object:is_player() and object:get_luaentity() and object:get_luaentity().name == "__builtin:item" then
                                                       if object:get_luaentity().itemstring ~= "" then
-                                                              local titem = pipeworks.tube_item(pos,object:get_luaentity().itemstring)
-                                                              titem:get_luaentity().start_pos = {x = pos.x, y = pos.y-1, z = pos.z}
-                                                              titem:setvelocity({x = 0.01, y = 1, z = -0.01})
-                                                              titem:setacceleration({x = 0, y = 0, z = 0})
+                                                              pipeworks.tube_item(pos, pos, vector.new(0, 0, 0), object:get_luaentity().itemstring)
                                                       end
                                                       object:get_luaentity().itemstring = ""
                                                       object:remove()
@@ -576,10 +553,7 @@ if pipeworks.enable_mese_sand_tube then
                                       for _,object in ipairs(get_objects_with_square_radius(pos, minetest.env:get_meta(pos):get_int("dist"))) do
                                               if not object:is_player() and object:get_luaentity() and object:get_luaentity().name == "__builtin:item" then
                                                       if object:get_luaentity().itemstring ~= "" then
-                                                              local titem = pipeworks.tube_item(pos, object:get_luaentity().itemstring)
-                                                              titem:get_luaentity().start_pos = {x = pos.x, y = pos.y-1, z = pos.z}
-                                                              titem:setvelocity({x = 0.01, y = 1, z = -0.01})
-                                                              titem:setacceleration({x = 0, y = 0, z = 0})
+                                                              pipeworks.tube_item(pos, pos, vector.new(0, 0, 0), object:get_luaentity().itemstring)
                                                       end
                                                       object:get_luaentity().itemstring = ""
                                                       object:remove()
@@ -589,26 +563,9 @@ if pipeworks.enable_mese_sand_tube then
        })
 end
 
-local function facedir_to_right_dir(facedir)
-       
-       --find the other directions
-       local backdir = minetest.facedir_to_dir(facedir)
-       local topdir = ({[0] = {x = 0, y = 1, z = 0},
-                        {x = 0, y = 0, z = 1},
-                        {x = 0, y = 0, z = -1},
-                        {x = 1, y = 0, z = 0},
-                        {x = -1, y = 0, z = 0},
-                        {x = 0, y = -1, z = 0}})[math.floor(facedir/4)]
-       
-       --return a cross product
-       return {x = topdir.y*backdir.z - backdir.y*topdir.z,
-               y = topdir.z*backdir.x - backdir.z*topdir.x,
-               z = topdir.x*backdir.y - backdir.x*topdir.y}
-end
-
 if pipeworks.enable_one_way_tube then
        minetest.register_node("pipeworks:one_way_tube", {
-               description = "One-way Pneumatic Tube Segment",
+               description = "One way tube",
                tiles = {"pipeworks_one_way_tube_top.png", "pipeworks_one_way_tube_top.png", "pipeworks_one_way_tube_output.png",
                        "pipeworks_one_way_tube_input.png", "pipeworks_one_way_tube_side.png", "pipeworks_one_way_tube_top.png"},
                paramtype2 = "facedir",
@@ -616,35 +573,19 @@ if pipeworks.enable_one_way_tube then
                paramtype = "light",
                node_box = {type = "fixed",
                        fixed = {{-1/2, -9/64, -9/64, 1/2, 9/64, 9/64}}},
-               groups = {snappy = 2, choppy = 2, oddly_breakable_by_hand = 2, tubedevice = 1, tubedevice_receiver = 1},
+               groups = {snappy = 2, choppy = 2, oddly_breakable_by_hand = 2, tubedevice = 1},
                legacy_facedir_simple = true,
                sounds = default.node_sound_wood_defaults(),
-               on_construct = function(pos)
-                       minetest.get_meta(pos):set_int("tubelike", 1)
-               end,
-               after_place_node = function(pos)
-                       pipeworks.scan_for_tube_objects(pos)
-               end,
-               after_dig_node = function(pos)
-                       pipeworks.scan_for_tube_objects(pos)
-               end,
-               tube = {connect_sides = {left = 1, right = 1},
+               tube = {
+                       connect_sides = {left = 1, right = 1},
                        can_go = function(pos, node, velocity, stack)
-                               return velocity
-                       end,
-                       insert_object = function(pos, node, stack, direction)
-                               item1 = pipeworks.tube_item(pos, stack)
-                               item1:get_luaentity().start_pos = pos
-                               item1:setvelocity({x = direction.x*direction.speed, y = direction.y*direction.speed, z = direction.z*direction.speed})
-                               item1:setacceleration({x = 0, y = 0, z = 0})
-                               return ItemStack("")
+                               return {velocity}
                        end,
                        can_insert = function(pos, node, stack, direction)
-                               local dir = facedir_to_right_dir(node.param2)
-                               if dir.x == direction.x and dir.y == direction.y and dir.z == direction.z then
-                                       return true
-                               end
-                               return false
-                       end},
+                               local dir = minetest.facedir_to_right_dir(node.param2)
+                               return vector.equals(dir, direction)
+                       end,
+                       priority = 75 -- Higher than normal tubes, but lower than receivers
+               },
        })
 end