Refactor node breaker
authorZefram <zefram@fysh.org>
Wed, 23 Jul 2014 00:47:40 +0000 (01:47 +0100)
committerVanessa Ezekowitz <vanessaezekowitz@gmail.com>
Wed, 23 Jul 2014 13:50:50 +0000 (09:50 -0400)
Merge node breaker into the generic wielder mechanism.  Center the wield
inventory in all wielders' formspecs.  Implement full auto-upgrade of
legacy node breakers, occurring upon use or punching of the node breaker.
Make node breaker respect on_dig hooks.

init.lua
legacy.lua
node_breaker.lua [deleted file]
wielder.lua

index 625a99c9386e687edff5c907a4768a0d1b0ad636..f78b41fde33db0e1c73793f4426937c7afbb54ee 100644 (file)
--- a/init.lua
+++ b/init.lua
@@ -122,11 +122,6 @@ if pipeworks.enable_pipe_devices then dofile(pipeworks.modpath.."/devices.lua")
 if pipeworks.enable_redefines then dofile(pipeworks.modpath.."/compat.lua") end
 if pipeworks.enable_autocrafter then dofile(pipeworks.modpath.."/autocrafter.lua") end
 
-if pipeworks.enable_node_breaker then
-       dofile(pipeworks.modpath.."/node_breaker.lua")
-       dofile(pipeworks.modpath.."/legacy.lua")
-end
-
 minetest.register_alias("pipeworks:pipe", "pipeworks:pipe_110000_empty")
 
 print("Pipeworks loaded!")
index 472eaca62f6877c28a3b16ec8d2123acf92f317d..84ae31d6dfb1626c28b1ccdf0df7b6008b8281a0 100644 (file)
@@ -14,6 +14,7 @@ if not minetest.get_modpath("auto_tree_tap") and
                        inv:set_size("ghost_pick", 1)
                        inv:set_size("main", 100)
                        minetest.set_node(pos, {name = "pipeworks:nodebreaker_off", param2 = fdir})
+                       minetest.registered_nodes["pipeworks:nodebreaker_off"].on_punch(pos, node)
                        inv:set_stack("pick", 1, ItemStack("technic:treetap"))
                end
        })
diff --git a/node_breaker.lua b/node_breaker.lua
deleted file mode 100644 (file)
index a02324e..0000000
+++ /dev/null
@@ -1,413 +0,0 @@
-
---register aliases for when someone had technic installed, but then uninstalled it but not pipeworks
-minetest.register_alias("technic:nodebreaker_off", "pipeworks:nodebreaker_off")
-minetest.register_alias("technic:nodebreaker_on", "pipeworks:nodebreaker_on")
-minetest.register_alias("technic:node_breaker_off", "pipeworks:nodebreaker_off") --old name
-minetest.register_alias("technic:node_breaker_on", "pipeworks:nodebreaker_on") --old name
-
-minetest.register_craft({
-       output = 'pipeworks:nodebreaker_off 1',
-       recipe = {
-               {'group:wood', 'default:pick_mese','group:wood'},
-               {'default:stone', 'mesecons:piston','default:stone'},
-               {'default:stone', 'mesecons:mesecon','default:stone'},
-       }
-})
-
-local function swap_node(pos, name)
-    local node = minetest.get_node(pos)
-    if node.name == name then
-        return
-    end
-    node.name = name
-    minetest.swap_node(pos, node)
-end
-
---define the functions from https://github.com/minetest/minetest/pull/834 while waiting for the devs to notice it
-local function dir_to_facedir(dir, is6d)
-       --account for y if requested
-       if is6d and math.abs(dir.y) > math.abs(dir.x) and math.abs(dir.y) > math.abs(dir.z) then
-               
-               --from above
-               if dir.y < 0 then
-                       if math.abs(dir.x) > math.abs(dir.z) then
-                               if dir.x < 0 then
-                                       return 19
-                               else
-                                       return 13
-                               end
-                       else
-                               if dir.z < 0 then
-                                       return 10
-                               else
-                                       return 4
-                               end
-                       end
-               
-               --from below
-               else
-                       if math.abs(dir.x) > math.abs(dir.z) then
-                               if dir.x < 0 then
-                                       return 15
-                               else
-                                       return 17
-                               end
-                       else
-                               if dir.z < 0 then
-                                       return 6
-                               else
-                                       return 8
-                               end
-                       end
-               end
-       
-       --otherwise, place horizontally
-       elseif math.abs(dir.x) > math.abs(dir.z) then
-               if dir.x < 0 then
-                       return 3
-               else
-                       return 1
-               end
-       else
-               if dir.z < 0 then
-                       return 2
-               else
-                       return 0
-               end
-       end
-end
-
-local function delay(x)
-       return (function() return x end)
-end
-
-local function break_node (pos, facedir)
-       --locate the outgoing velocity, front, and back of the node via facedir_to_dir
-       if type(facedir) ~= "number" or facedir < 0 or facedir > 23 then return end
-
-       local vel = minetest.facedir_to_dir(facedir)
-       local front = {x=pos.x - vel.x, y=pos.y - vel.y, z=pos.z - vel.z}
-       
-       local node = minetest.get_node(front)
-       --if node.name == "air" or node.name == "ignore" then
-       --      return nil
-       --elseif minetest.registered_nodes[node.name] and minetest.registered_nodes[node.name].liquidtype ~= "none" then
-       --      return nil
-       --end
-       local meta = minetest.get_meta(pos)
-       local inv = meta:get_inventory()
-       
-       if inv:get_size("ghost_pick") ~= 1 then -- Legacy code
-               inv:set_size("ghost_pick", 1)
-               inv:set_size("main", 100)
-       end
-
-       local pick_inv = "pick"
-       local pick = inv:get_stack("pick", 1)
-       if pick:is_empty() then
-               pick = ItemStack("default:pick_mese")
-               inv:set_stack("ghost_pick", 1, pick)
-               pick_inv = "ghost_pick"
-       end
-       local pitch
-       local yaw
-       if vel.z < 0 then
-               yaw = 0
-               pitch = 0
-       elseif vel.z > 0 then
-               yaw = math.pi
-               pitch = 0
-       elseif vel.x < 0 then
-               yaw = 3*math.pi/2
-               pitch = 0
-       elseif vel.x > 0 then
-               yaw = math.pi/2
-               pitch = 0
-       elseif vel.y > 0 then
-               yaw = 0
-               pitch = -math.pi/2
-       else
-               yaw = 0
-               pitch = math.pi/2
-       end
-       local digger = {
-               get_inventory_formspec = delay(""),
-               get_look_dir = delay({x = -vel.x, y = -vel.y, z = -vel.z}),
-               get_look_pitch = delay(pitch),
-               get_look_yaw = delay(yaw),
-               get_player_control = delay({jump=false, right=false, left=false, LMB=false, RMB=false, sneak=false, aux1=false, down=false, up=false}),
-               get_player_control_bits = delay(0),
-               get_player_name = delay(meta:get_string("owner")),
-               is_player = delay(true),
-               is_fake_player = true,
-               set_inventory_formspec = delay(),
-               getpos = delay({x = pos.x, y = pos.y - 1.5, z = pos.z}), -- Player height
-               get_hp = delay(20),
-               get_inventory = delay(inv),
-               get_wielded_item = delay(pick),
-               get_wield_index = delay(1),
-               get_wield_list = delay(pick_inv),
-               moveto = delay(),
-               punch = delay(),
-               remove = delay(),
-               right_click = delay(),
-               setpos = delay(),
-               set_hp = delay(),
-               set_properties = delay(),
-               set_wielded_item = function(self, stack)
-                       if stack:get_name() == pick:get_name() then
-                               inv:set_stack(pick_inv, 1, stack)
-                       else
-                               inv:add_item("main", stack)
-                               inv:set_stack(pick_inv, 1, ItemStack(""))
-                       end
-               end,
-               set_animation = delay(),
-               set_attach = delay(),
-               set_detach = delay(),
-               set_bone_position = delay(),
-       }
-       
-       local pickdef = minetest.registered_items[pick:get_name()]
-       local pickcopy = ItemStack(pick)
-       if pick_inv == "pick" and pickdef and pickdef.on_use then
-               local pos_under, pos_above = {x = pos.x - vel.x, y = pos.y - vel.y, z = pos.z - vel.z}, {x = pos.x - 2*vel.x, y = pos.y - 2*vel.y, z = pos.z - 2*vel.z}
-               local pointed_thing = {type="node", under=pos_under, above=pos_above}
-               inv:set_stack(pick_inv, 1, pickdef.on_use(pick, digger, pointed_thing) or pick)
-       else
-               minetest.node_dig(front, node, digger)
-       end
-       
-       
-       local newpick = inv:get_stack(pick_inv, 1)
-       if newpick:get_name() == pickcopy:get_name() and newpick:get_count() == pickcopy:get_count() and newpick:get_metadata() == pickcopy:get_metadata() and pickdef and (not pickdef.wear_represents or pickdef.wear_represents == "mechanical_wear") then
-               inv:set_stack(pick_inv, 1, pickcopy) -- Do not wear pick out
-       end
-
-       for i = 1, 100 do
-               local dropped_item = inv:get_stack("main", i)
-               if not dropped_item:is_empty() then
-                       local item1 = pipeworks.tube_item({x=pos.x, y=pos.y, z=pos.z}, dropped_item)
-                       item1:get_luaentity().start_pos = {x=pos.x, y=pos.y, z=pos.z}
-                       item1:setvelocity(vel)
-                       item1:setacceleration({x=0, y=0, z=0})
-                       inv:set_stack("main", i, ItemStack(""))
-               end
-       end
-end
-
-local node_breaker_on = function(pos, node)
-       if node.name == "pipeworks:nodebreaker_off" then
-               swap_node(pos, "pipeworks:nodebreaker_on")
-               break_node(pos, node.param2)
-               nodeupdate(pos)
-       end
-end
-
-local node_breaker_off = function(pos, node)
-       if node.name == "pipeworks:nodebreaker_on" then
-               swap_node(pos, "pipeworks:nodebreaker_off")
-               nodeupdate(pos)
-       end
-end
-
-minetest.register_node("pipeworks:nodebreaker_off", {
-       description = "Node Breaker",
-       tile_images = {"pipeworks_nodebreaker_top_off.png","pipeworks_nodebreaker_bottom_off.png","pipeworks_nodebreaker_side2_off.png","pipeworks_nodebreaker_side1_off.png",
-                       "pipeworks_nodebreaker_back.png","pipeworks_nodebreaker_front_off.png"},
-       is_ground_content = true,
-       paramtype2 = "facedir",
-       groups = {snappy=2,choppy=2,oddly_breakable_by_hand=2, mesecon = 2,tubedevice=1, tubedevice_receiver=1},
-       mesecons= {effector={rules=pipeworks.rules_all,action_on=node_breaker_on, action_off=node_breaker_off}},
-       sounds = default.node_sound_stone_defaults(),
-       tube = {connect_sides = {left = 1, right = 1, back = 1, top = 1, bottom = 1},
-               input_inventory = "pick",
-               insert_object = function(pos, node, stack, direction)
-                       local vel = minetest.facedir_to_dir(node.param2)
-                       if math.abs(vel.x) == math.abs(direction.x) and math.abs(vel.y) == math.abs(direction.y) and math.abs(vel.z) == math.abs(direction.z) then
-                               return stack
-                       end
-                       local meta = minetest.get_meta(pos)
-                       local inv = meta:get_inventory()
-                       return inv:add_item("pick", stack)
-               end,
-               can_insert = function(pos, node, stack, direction)
-                       local vel = minetest.facedir_to_dir(node.param2)
-                       if math.abs(vel.x) == math.abs(direction.x) and math.abs(vel.y) == math.abs(direction.y) and math.abs(vel.z) == math.abs(direction.z) then
-                               return false
-                       end
-                       local meta = minetest.get_meta(pos)
-                       local inv = meta:get_inventory()
-                       return inv:room_for_item("pick", stack)
-               end,
-               can_remove = function(pos, node, stack, dir)
-                       return stack:get_count()
-               end},
-       on_construct = function(pos)
-               local meta = minetest.get_meta(pos)
-               local inv = meta:get_inventory()
-               inv:set_size("pick", 1)
-               inv:set_size("ghost_pick", 1)
-               inv:set_size("main", 100)
-               --inv:set_stack("pick", 1, ItemStack("default:pick_mese"))
-               meta:set_string("formspec",
-                               "invsize[8,6;]"..
-                               "label[0,0;Node breaker]"..
-                               "list[current_name;pick;3.5,0;1,1;]"..
-                               "list[current_player;main;0,2;8,4;]")
-               meta:set_string("infotext", "Node Breaker")
-       end,
-       after_place_node = function (pos, placer)
-               pipeworks.scan_for_tube_objects(pos, placer)
-               local placer_pos = placer:getpos()
-               
-               --correct for the player's height
-               if placer:is_player() then placer_pos.y = placer_pos.y + 1.5 end
-               
-               --correct for 6d facedir
-               if placer_pos then
-                       local dir = {
-                               x = pos.x - placer_pos.x,
-                               y = pos.y - placer_pos.y,
-                               z = pos.z - placer_pos.z
-                       }
-                       local node = minetest.get_node(pos)
-                       node.param2 = dir_to_facedir(dir, true)
-                       minetest.set_node(pos, node)
-                       minetest.log("action", "real (6d) facedir: " .. node.param2)
-               end
-               
-               minetest.get_meta(pos):set_string("owner", placer:get_player_name())
-       end,
-       after_dig_node = function(pos, oldnode, oldmetadata, digger)
-               if oldmetadata.inventory.pick and oldmetadata.fields.formspec then
-                       local stack = oldmetadata.inventory.pick[1]
-                       if not stack:is_empty() then
-                               minetest.add_item(pos, stack)
-                       end
-               end
-               pipeworks.scan_for_tube_objects(pos, oldnode, oldmetadata, digger)
-       end,
-       allow_metadata_inventory_move = function(pos, from_list, from_index, to_list, to_index, count, player)
-               local meta = minetest.get_meta(pos)
-               if player:get_player_name() ~= meta:get_string("owner") and meta:get_string("owner") ~= "" then
-                       return 0
-               end
-               return count
-       end,
-       allow_metadata_inventory_put = function(pos, listname, index, stack, player)
-               local meta = minetest.get_meta(pos)
-               if player:get_player_name() ~= meta:get_string("owner") and meta:get_string("owner") ~= "" then
-                       return 0
-               end
-               return stack:get_count()
-       end,
-       allow_metadata_inventory_take = function(pos, listname, index, stack, player)
-               local meta = minetest.get_meta(pos)
-               if player:get_player_name() ~= meta:get_string("owner") and meta:get_string("owner") ~= "" then
-                       return 0
-               end
-               return stack:get_count()
-       end
-})
-
-minetest.register_node("pipeworks:nodebreaker_on", {
-       description = "Node Breaker",
-       tile_images = {"pipeworks_nodebreaker_top_on.png","pipeworks_nodebreaker_bottom_on.png","pipeworks_nodebreaker_side2_on.png","pipeworks_nodebreaker_side1_on.png",
-                       "pipeworks_nodebreaker_back.png","pipeworks_nodebreaker_front_on.png"},
-       mesecons= {effector={rules=pipeworks.rules_all,action_on=node_breaker_on, action_off=node_breaker_off}},
-       is_ground_content = true,
-       paramtype2 = "facedir",
-       groups = {snappy=2,choppy=2,oddly_breakable_by_hand=2, mesecon = 2,tubedevice=1,not_in_creative_inventory=1, tubedevice_receiver=1},
-       sounds = default.node_sound_stone_defaults(),
-       drop = "pipeworks:nodebreaker_off",
-       tube = {connect_sides = {left = 1, right = 1, back = 1, top = 1, bottom = 1},
-               input_inventory = "pick",
-               insert_object = function(pos, node, stack, direction)
-                       local vel = minetest.facedir_to_dir(node.param2)
-                       if math.abs(vel.x) == math.abs(direction.x) and math.abs(vel.y) == math.abs(direction.y) and math.abs(vel.z) == math.abs(direction.z) then
-                               return stack
-                       end
-                       local meta = minetest.get_meta(pos)
-                       local inv = meta:get_inventory()
-                       return inv:add_item("pick", stack)
-               end,
-               can_insert = function(pos, node, stack, direction)
-                       local vel = minetest.facedir_to_dir(node.param2)
-                       if math.abs(vel.x) == math.abs(direction.x) and math.abs(vel.y) == math.abs(direction.y) and math.abs(vel.z) == math.abs(direction.z) then
-                               return false
-                       end
-                       local meta = minetest.get_meta(pos)
-                       local inv = meta:get_inventory()
-                       return inv:room_for_item("pick", stack)
-               end,
-               can_remove = function(pos, node, stack, dir)
-                       return stack:get_count()
-               end},
-       on_construct = function(pos)
-               local meta = minetest.get_meta(pos)
-               local inv = meta:get_inventory()
-               inv:set_size("pick", 1)
-               inv:set_size("ghost_pick", 1)
-               inv:set_size("main", 100)
-               meta:set_string("formspec",
-                               "invsize[8,6;]"..
-                               "label[0,0;Node breaker]"..
-                               "list[current_name;pick;3.5,0;1,1;]"..
-                               "list[current_player;main;0,2;8,4;]")
-               --inv:set_stack("pick", 1, ItemStack("default:pick_mese"))
-               meta:set_string("infotext", "Node Breaker")
-       end,
-       after_place_node = function (pos, placer)
-               pipeworks.scan_for_tube_objects(pos, placer)
-               local placer_pos = placer:getpos()
-               
-               --correct for the player's height
-               if placer:is_player() then placer_pos.y = placer_pos.y + 1.5 end
-               
-               --correct for 6d facedir
-               if placer_pos then
-                       local dir = {
-                               x = pos.x - placer_pos.x,
-                               y = pos.y - placer_pos.y,
-                               z = pos.z - placer_pos.z
-                       }
-                       local node = minetest.get_node(pos)
-                       node.param2 = dir_to_facedir(dir, true)
-                       minetest.set_node(pos, node)
-                       minetest.log("action", "real (6d) facedir: " .. node.param2)
-               end
-               
-               minetest.get_meta(pos):set_string("owner", placer:get_player_name())
-       end,
-       after_dig_node = function(pos, oldnode, oldmetadata, digger)
-               if oldmetadata.inventory.pick and oldmetadata.fields.formspec then
-                       local stack = oldmetadata.inventory.pick[1]
-                       if not stack:is_empty() then
-                               minetest.add_item(pos, stack)
-                       end
-               end
-               pipeworks.scan_for_tube_objects(pos, oldnode, oldmetadata, digger)
-       end,
-       allow_metadata_inventory_move = function(pos, from_list, from_index, to_list, to_index, count, player)
-               local meta = minetest.get_meta(pos)
-               if player:get_player_name() ~= meta:get_string("owner") and meta:get_string("owner") ~= "" then
-                       return 0
-               end
-               return count
-       end,
-       allow_metadata_inventory_put = function(pos, listname, index, stack, player)
-               local meta = minetest.get_meta(pos)
-               if player:get_player_name() ~= meta:get_string("owner") and meta:get_string("owner") ~= "" then
-                       return 0
-               end
-               return stack:get_count()
-       end,
-       allow_metadata_inventory_take = function(pos, listname, index, stack, player)
-               local meta = minetest.get_meta(pos)
-               if player:get_player_name() ~= meta:get_string("owner") and meta:get_string("owner") ~= "" then
-                       return 0
-               end
-               return stack:get_count()
-       end
-})
index 8c4cff98bc66b59ee7125f5e33561636701ef3fd..6d92a23e64857eb0b7ae74c01073a8a373859607 100644 (file)
@@ -1,26 +1,47 @@
 local assumed_eye_pos = vector.new(0, 1.5, 0)
 
+local function vector_copy(v)
+       return { x = v.x, y = v.y, z = v.z }
+end
+
 local function delay(x)
        return (function() return x end)
 end
 
+local function set_wielder_formspec(data, meta)
+       meta:set_string("formspec",
+                       "invsize[8,"..(6+data.wield_inv_height)..";]"..
+                       "item_image[0,0;1,1;"..data.name_base.."_off]"..
+                       "label[1,0;"..minetest.formspec_escape(data.description).."]"..
+                       "list[current_name;"..minetest.formspec_escape(data.wield_inv_name)..";"..((8-data.wield_inv_width)*0.5)..",1;"..data.wield_inv_width..","..data.wield_inv_height..";]"..
+                       "list[current_player;main;0,"..(2+data.wield_inv_height)..";8,4;]")
+       meta:set_string("infotext", data.description)
+end
+
 local function wielder_on(data, wielder_pos, wielder_node)
+       data.fixup_node(wielder_pos, wielder_node)
        if wielder_node.name ~= data.name_base.."_off" then return end
        wielder_node.name = data.name_base.."_on"
        minetest.swap_node(wielder_pos, wielder_node)
        nodeupdate(wielder_pos)
        local wielder_meta = minetest.get_meta(wielder_pos)
        local inv = wielder_meta:get_inventory()
-       local invlist = inv:get_list("main")
+       local wield_inv_name = data.wield_inv_name
        local wieldindex, wieldstack
-       for i, stack in ipairs(invlist) do
+       for i, stack in ipairs(inv:get_list(wield_inv_name)) do
                if not stack:is_empty() then
                        wieldindex = i
                        wieldstack = stack
                        break
                end
        end
-       if not wieldindex then return end
+       if not wieldindex then
+               if not data.ghost_inv_name then return end
+               wield_inv_name = data.ghost_inv_name
+               inv:set_stack(wield_inv_name, 1, ItemStack(data.ghost_tool))
+               wieldindex = 1
+               wieldstack = inv:get_stack(wield_inv_name, 1)
+       end
        local dir = minetest.facedir_to_dir(wielder_node.param2)
        local under_pos = vector.subtract(wielder_pos, dir)
        local above_pos = vector.subtract(under_pos, dir)
@@ -61,7 +82,7 @@ local function wielder_on(data, wielder_pos, wielder_node)
                get_inventory = delay(inv),
                get_wielded_item = delay(wieldstack),
                get_wield_index = delay(wieldindex),
-               get_wield_list = delay("main"),
+               get_wield_list = delay(wield_inv_name),
                moveto = delay(),
                punch = delay(),
                remove = delay(),
@@ -69,14 +90,25 @@ local function wielder_on(data, wielder_pos, wielder_node)
                setpos = delay(),
                set_hp = delay(),
                set_properties = delay(),
-               set_wielded_item = function(self, item) inv:set_stack("main", wieldindex, item) end,
+               set_wielded_item = function(self, item) inv:set_stack(wield_inv_name, wieldindex, item) end,
                set_animation = delay(),
                set_attach = delay(),
                set_detach = delay(),
                set_bone_position = delay(),
        }
        local pointed_thing = { type="node", under=under_pos, above=above_pos }
-       virtplayer:set_wielded_item(data.on_act(virtplayer, pointed_thing) or wieldstack)
+       data.act(virtplayer, pointed_thing)
+       if data.eject_drops then
+               for i, stack in ipairs(inv:get_list("main")) do
+                       if not stack:is_empty() then
+                               local tubeitem = pipeworks.tube_item(vector_copy(wielder_pos), stack)
+                               tubeitem:get_luaentity().start_pos = vector_copy(wielder_pos)
+                               tubeitem:setvelocity(vector_copy(dir))
+                               tubeitem:setacceleration(vector.new(0,0,0))
+                               inv:set_stack("main", i, ItemStack(""))
+                       end
+               end
+       end
 end
 
 local function wielder_off(data, pos, node)
@@ -88,6 +120,8 @@ local function wielder_off(data, pos, node)
 end
 
 local function register_wielder(data)
+       data.fixup_node = data.fixup_node or function (pos, node) end
+       data.fixup_oldmetadata = data.fixup_oldmetadata or function (m) return m end
        for _, state in ipairs({ "off", "on" }) do
                local groups = { snappy=2, choppy=2, oddly_breakable_by_hand=2, mesecon=2, tubedevice=1, tubedevice_receiver=1 }
                if state == "on" then groups.not_in_creative_inventory = 1 end
@@ -110,19 +144,31 @@ local function register_wielder(data)
                                },
                        },
                        tube = {
-                               insert_object = function(pos,node,stack,direction)
+                               can_insert = function(pos, node, stack, tubedir)
+                                       if not data.tube_permit_anteroposterior_insert then
+                                               local nodedir = minetest.facedir_to_dir(node.param2)
+                                               if vector.equals(tubedir, nodedir) or vector.equals(tubedir, vector.multiply(nodedir, -1)) then
+                                                       return false
+                                               end
+                                       end
                                        local meta = minetest.get_meta(pos)
                                        local inv = meta:get_inventory()
-                                       return inv:add_item("main",stack)
+                                       return inv:room_for_item(data.wield_inv_name, stack)
                                end,
-                               can_insert = function(pos,node,stack,direction)
+                               insert_object = function(pos, node, stack, tubedir)
+                                       if not data.tube_permit_anteroposterior_insert then
+                                               local nodedir = minetest.facedir_to_dir(node.param2)
+                                               if vector.equals(tubedir, nodedir) or vector.equals(tubedir, vector.multiply(nodedir, -1)) then
+                                                       return stack
+                                               end
+                                       end
                                        local meta = minetest.get_meta(pos)
                                        local inv = meta:get_inventory()
-                                       return inv:room_for_item("main",stack)
+                                       return inv:add_item(data.wield_inv_name, stack)
                                end,
-                               input_inventory = "main",
-                               connect_sides = {back=1},
-                               can_remove = function(pos, node, stack, dir)
+                               input_inventory = data.wield_inv_name,
+                               connect_sides = data.tube_connect_sides,
+                               can_remove = function(pos, node, stack, tubedir)
                                        return stack:get_count()
                                end,
                        },
@@ -134,23 +180,18 @@ local function register_wielder(data)
                        drop = data.name_base.."_off",
                        on_construct = function(pos)
                                local meta = minetest.get_meta(pos)
-                               meta:set_string("formspec",
-                                               "invsize[8,9;]"..
-                                               "item_image[0,0;1,1;"..data.name_base.."_off]"..
-                                               "label[1,0;"..minetest.formspec_escape(data.description).."]"..
-                                               "list[current_name;main;4,1;3,3;]"..
-                                               "list[current_player;main;0,5;8,4;]")
-                               meta:set_string("infotext", data.description)
-                               local inv = meta:get_inventory()
-                               inv:set_size("main", 3*3)
-                       end,
-                       can_dig = function(pos,player)
-                               local meta = minetest.get_meta(pos)
+                               set_wielder_formspec(data, meta)
                                local inv = meta:get_inventory()
-                               return inv:is_empty("main")
+                               inv:set_size(data.wield_inv_name, data.wield_inv_width*data.wield_inv_height)
+                               if data.ghost_inv_name then
+                                       inv:set_size(data.ghost_inv_name, 1)
+                               end
+                               if data.eject_drops then
+                                       inv:set_size("main", 100)
+                               end
                        end,
                        after_place_node = function (pos, placer)
-                               pipeworks.scan_for_tube_objects(pos, placer)
+                               pipeworks.scan_for_tube_objects(pos)
                                local placer_pos = placer:getpos()
                                if placer_pos and placer:is_player() then placer_pos = vector.add(placer_pos, assumed_eye_pos) end
                                if placer_pos then
@@ -162,7 +203,27 @@ local function register_wielder(data)
                                end
                                minetest.get_meta(pos):set_string("owner", placer:get_player_name())
                        end,
-                       after_dig_node = pipeworks.scan_for_tube_objects,
+                       can_dig = (data.can_dig_nonempty_wield_inv and delay(true) or function(pos, player)
+                               local meta = minetest.get_meta(pos)
+                               local inv = meta:get_inventory()
+                               return inv:is_empty(data.wield_inv_name)
+                       end),
+                       after_dig_node = function(pos, oldnode, oldmetadata, digger)
+                               -- The legacy-node fixup is done here in a
+                               -- different form from the standard fixup,
+                               -- rather than relying on a standard fixup
+                               -- in an on_dig callback, because some
+                               -- non-standard diggers (such as technic's
+                               -- mining drill) don't respect on_dig.
+                               oldmetadata = data.fixup_oldmetadata(oldmetadata)
+                               for _, stack in ipairs(oldmetadata.inventory[data.wield_inv_name] or {}) do
+                                       if not stack:is_empty() then
+                                               minetest.add_item(pos, stack)
+                                       end
+                               end
+                               pipeworks.scan_for_tube_objects(pos)
+                       end,
+                       on_punch = data.fixup_node,
                        allow_metadata_inventory_move = function(pos, from_list, from_index, to_list, to_index, count, player)
                                local meta = minetest.get_meta(pos)
                                if player:get_player_name() ~= meta:get_string("owner") and meta:get_string("owner") ~= "" then
@@ -188,18 +249,134 @@ local function register_wielder(data)
        end
 end
 
+if pipeworks.enable_node_breaker then
+       local data
+       data = {
+               name_base = "pipeworks:nodebreaker",
+               description = "Node Breaker",
+               texture_base = "pipeworks_nodebreaker",
+               texture_stateful = { top = true, bottom = true, side2 = true, side1 = true, front = true },
+               tube_connect_sides = { top=1, bottom=1, left=1, right=1, back=1 },
+               tube_permit_anteroposterior_insert = false,
+               wield_inv_name = "pick",
+               wield_inv_width = 1,
+               wield_inv_height = 1,
+               can_dig_nonempty_wield_inv = true,
+               ghost_inv_name = "ghost_pick",
+               ghost_tool = "default:pick_mese",
+               fixup_node = function (pos, node)
+                       local meta = minetest.get_meta(pos)
+                       local inv = meta:get_inventory()
+                       -- Node breakers predating the visible pick slot
+                       -- may have been partially updated.  This code
+                       -- fully updates them.  Originally, they had a
+                       -- ghost pick in a "pick" inventory, no other
+                       -- inventory, and no form.  The partial update of
+                       -- early with-form node breaker code gives them
+                       -- "ghost_pick" and "main" inventories, but leaves
+                       -- the old ghost pick in the "pick" inventory,
+                       -- and doesn't add a form.  First perform that
+                       -- partial update.
+                       if inv:get_size("ghost_pick") ~= 1 then
+                               inv:set_size("ghost_pick", 1)
+                               inv:set_size("main", 100)
+                       end
+                       -- If the node breaker predates the visible pick
+                       -- slot, which we can detect by it not having a
+                       -- form, then the pick slot needs to be cleared
+                       -- of the old ghost pick.
+                       if (meta:get_string("formspec") or "") == "" then
+                               inv:set_stack("pick", 1, ItemStack(""))
+                       end
+                       -- Finally, unconditionally set the formspec
+                       -- and infotext.  This not only makes the
+                       -- pick slot visible for node breakers where
+                       -- it wasn't before; it also updates the form
+                       -- for node breakers that had an older version
+                       -- of the form, and sets infotext where it was
+                       -- missing for early with-form node breakers.
+                       set_wielder_formspec(data, meta)
+               end,
+               fixup_oldmetadata = function (oldmetadata)
+                       -- Node breakers predating the visible pick slot,
+                       -- with node form, kept their ghost pick in an
+                       -- inventory named "pick", the same name as the
+                       -- later visible pick slot.  The pick must be
+                       -- removed to avoid spilling it.
+                       if not oldmetadata.fields.formspec then
+                               return { inventory = { pick = {} }, fields = oldmetadata.fields }
+                       else
+                               return oldmetadata
+                       end
+               end,
+               masquerade_as_owner = true,
+               sneak = false,
+               act = function(virtplayer, pointed_thing)
+                       local wieldstack = virtplayer:get_wielded_item()
+                       local oldwieldstack = ItemStack(wieldstack)
+                       local on_use = (minetest.registered_items[wieldstack:get_name()] or {}).on_use
+                       if on_use then
+                               virtplayer:set_wielded_item(on_use(wieldstack, virtplayer, pointed_thing) or wieldstack)
+                       else
+                               local under_node = minetest.get_node(pointed_thing.under)
+                               local on_dig = (minetest.registered_nodes[under_node.name] or {on_dig=minetest.node_dig}).on_dig
+                               on_dig(pointed_thing.under, under_node, virtplayer)
+                       end
+                       wieldstack = virtplayer:get_wielded_item()
+                       local wieldname = wieldstack:get_name()
+                       if wieldname == oldwieldstack:get_name() then
+                               -- don't mechanically wear out tool
+                               if wieldstack:get_count() == oldwieldstack:get_count() and
+                                               wieldstack:get_metadata() == oldwieldstack:get_metadata() and
+                                               ((minetest.registered_items[wieldstack:get_name()] or {}).wear_represents or "mechanical_wear") == "mechanical_wear" then
+                                       virtplayer:set_wielded_item(oldwieldstack)
+                               end
+                       elseif wieldname ~= "" then
+                               -- tool got replaced by something else:
+                               -- treat it as a drop
+                               virtplayer:get_inventory():add_item("main", wieldstack)
+                               virtplayer:set_wielded_item(ItemStack(""))
+                       end
+               end,
+               eject_drops = true,
+       }
+       register_wielder(data)
+       minetest.register_craft({
+               output = "pipeworks:nodebreaker_off",
+               recipe = {
+                       { "group:wood",    "default:pick_mese", "group:wood"    },
+                       { "default:stone", "mesecons:piston",   "default:stone" },
+                       { "default:stone", "mesecons:mesecon",  "default:stone" },
+               }
+       })
+       -- aliases for when someone had technic installed, but then uninstalled it but not pipeworks
+       minetest.register_alias("technic:nodebreaker_off", "pipeworks:nodebreaker_off")
+       minetest.register_alias("technic:nodebreaker_on", "pipeworks:nodebreaker_on")
+       minetest.register_alias("technic:node_breaker_off", "pipeworks:nodebreaker_off")
+       minetest.register_alias("technic:node_breaker_on", "pipeworks:nodebreaker_on")
+       -- turn legacy auto-tree-taps into node breakers
+       dofile(pipeworks.modpath.."/legacy.lua")
+end
+
 if pipeworks.enable_deployer then
        register_wielder({
                name_base = "pipeworks:deployer",
                description = "Deployer",
                texture_base = "pipeworks_deployer",
                texture_stateful = { front = true },
+               tube_connect_sides = { back=1 },
+               tube_permit_anteroposterior_insert = true,
+               wield_inv_name = "main",
+               wield_inv_width = 3,
+               wield_inv_height = 3,
+               can_dig_nonempty_wield_inv = false,
                masquerade_as_owner = true,
                sneak = false,
-               on_act = function(virtplayer, pointed_thing)
+               act = function(virtplayer, pointed_thing)
                        local wieldstack = virtplayer:get_wielded_item()
-                       return (minetest.registered_items[wieldstack:get_name()] or {on_place=minetest.item_place}).on_place(wieldstack, virtplayer, pointed_thing)
+                       virtplayer:set_wielded_item((minetest.registered_items[wieldstack:get_name()] or {on_place=minetest.item_place}).on_place(wieldstack, virtplayer, pointed_thing) or wieldstack)
                end,
+               eject_drops = false,
        })
        minetest.register_craft({
                output = "pipeworks:deployer_off",
@@ -220,12 +397,19 @@ if pipeworks.enable_dispenser then
                description = "Dispenser",
                texture_base = "pipeworks_dispenser",
                texture_stateful = { front = true },
+               tube_connect_sides = { back=1 },
+               tube_permit_anteroposterior_insert = true,
+               wield_inv_name = "main",
+               wield_inv_width = 3,
+               wield_inv_height = 3,
+               can_dig_nonempty_wield_inv = false,
                masquerade_as_owner = false,
                sneak = true,
-               on_act = function(virtplayer, pointed_thing)
+               act = function(virtplayer, pointed_thing)
                        local wieldstack = virtplayer:get_wielded_item()
-                       return (minetest.registered_items[wieldstack:get_name()] or {on_drop=minetest.item_drop}).on_drop(wieldstack, virtplayer, virtplayer:getpos())
+                       virtplayer:set_wielded_item((minetest.registered_items[wieldstack:get_name()] or {on_drop=minetest.item_drop}).on_drop(wieldstack, virtplayer, virtplayer:getpos()) or wieldstack)
                end,
+               eject_drops = false,
        })
        minetest.register_craft({
                output = "pipeworks:dispenser_off",