pipeworks/item_transport.lua
Zefram 65baf218be Make non-node items in tubes look more solid
Use the "wielditem" visual type for non-node items, with a fixed 45
degree yaw.  This matches the recent change of dropped-item entities from
"sprite" visual to "wielditem".
2014-07-02 21:39:28 -04:00

541 lines
16 KiB
Lua

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
minetest.register_craftitem("pipeworks:filter", {
description = "Filter",
stack_max = 99,
})
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)
-- 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())
return obj
end
-- adding two tube functions
-- can_remove(pos,node,stack,dir) returns the maximum number of items of that stack that can be removed
-- remove_items(pos,node,stack,dir,count) removes count items and returns them
-- both optional w/ sensible defaults and fallback to normal allow_* function
-- XXX: possibly change insert_object to insert_item
-- sname = the current name to allow for, or nil if it allows anything
local function grabAndFire(frominv,frominvname,frompos,fromnode,sname,tube,idef,dir,all)
for spos,stack in ipairs(frominv:get_list(frominvname)) do
if (sname == nil and stack:get_name() ~= "") or stack:get_name() == sname then
local doRemove = stack:get_count()
if tube.can_remove then
doRemove = tube.can_remove(frompos, fromnode, stack, dir)
elseif idef.allow_metadata_inventory_take then
doRemove = idef.allow_metadata_inventory_take(frompos, frominvname,spos, stack, fakePlayer)
end
-- stupid lack of continue statements grumble
if doRemove > 0 then
local item
local count
if all then
count = math.min(stack:get_count(), doRemove)
else
count = 1
end
if tube.remove_items then
-- it could be the entire stack...
item = tube.remove_items(frompos, fromnode, stack, dir, count)
else
item = stack:take_item(count)
frominv:set_stack(frominvname, spos, stack)
if idef.on_metadata_inventory_take then
idef.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})
return true-- only fire one item, please
end
end
end
return false
end
minetest.register_node("pipeworks:filter", {
description = "Filter",
tiles = {"pipeworks_filter_top.png", "pipeworks_filter_top.png", "pipeworks_filter_output.png",
"pipeworks_filter_input.png", "pipeworks_filter_side.png", "pipeworks_filter_top.png"},
paramtype2 = "facedir",
groups = {snappy=2,choppy=2,oddly_breakable_by_hand=2,tubedevice=1,mesecon=2},
legacy_facedir_simple = true,
sounds = default.node_sound_wood_defaults(),
on_construct = function(pos)
local meta = minetest.get_meta(pos)
meta:set_string("formspec",
"invsize[8,6.5;]"..
"list[current_name;main;0,0;8,2;]"..
"list[current_player;main;0,2.5;8,4;]")
meta:set_string("infotext", "Filter")
local inv = meta:get_inventory()
inv:set_size("main", 8*2)
end,
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)
minetest.registered_nodes[node.name].on_punch(pos,node,nil)
end}},
tube={connect_sides={right=1}},
on_punch = function (pos, node, puncher)
local meta = minetest.get_meta(pos);
local inv = meta:get_inventory()
local dir = facedir_to_right_dir(node.param2)
local frompos = {x=pos.x - dir.x, y=pos.y - dir.y, z=pos.z - dir.z}
local fromnode=minetest.get_node(frompos)
if not fromnode then return end
local idef = minetest.registered_nodes[fromnode.name]
-- assert(idef)
local tube = idef.tube
if not (tube and tube.input_inventory) then
return
end
if tube.before_filter then
tube.before_filter(frompos)
end
local frommeta = minetest.get_meta(frompos)
local frominv = frommeta:get_inventory()
local function from_inventory(frominvname)
local sname
for _,filter in ipairs(inv:get_list("main")) do
sname = filter:get_name()
if sname ~= "" then
-- XXX: that's a lot of parameters
if grabAndFire(frominv, frominvname, frompos, fromnode, sname, tube, idef, dir) then
return true
end
end
end
if inv:is_empty("main") then
if grabAndFire(frominv, frominvname, frompos, fromnode, nil, tube, idef, dir) then
return true
end
end
return false
end
if type(tube.input_inventory) == "table" then
for _, i in ipairs(tube.input_inventory) do
if from_inventory(i) then -- fired an item
break
end
end
else
from_inventory(tube.input_inventory)
end
if tube.after_filter then
tube.after_filter(frompos)
end
end,
})
minetest.register_craftitem("pipeworks:mese_filter", {
description = "Mese filter",
stack_max = 99,
})
minetest.register_node("pipeworks:mese_filter", {
description = "Mese filter",
tiles = {"pipeworks_mese_filter_top.png", "pipeworks_mese_filter_top.png", "pipeworks_mese_filter_output.png",
"pipeworks_mese_filter_input.png", "pipeworks_mese_filter_side.png", "pipeworks_mese_filter_top.png"},
paramtype2 = "facedir",
groups = {snappy=2,choppy=2,oddly_breakable_by_hand=2,tubedevice=1,mesecon=2},
legacy_facedir_simple = true,
sounds = default.node_sound_wood_defaults(),
on_construct = function(pos)
local meta = minetest.get_meta(pos)
meta:set_string("formspec",
"invsize[8,6.5;]"..
"list[current_name;main;0,0;8,2;]"..
"list[current_player;main;0,2.5;8,4;]")
meta:set_string("infotext", "Mese filter")
local inv = meta:get_inventory()
inv:set_size("main", 8*2)
end,
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)
minetest.registered_nodes[node.name].on_punch(pos,node,nil)
end}},
tube={connect_sides={right=1}},
on_punch = function (pos, node, puncher)
local meta = minetest.get_meta(pos);
local inv = meta:get_inventory()
local dir = facedir_to_right_dir(node.param2)
local frompos = {x=pos.x - dir.x, y=pos.y - dir.y, z=pos.z - dir.z}
local fromnode=minetest.get_node(frompos)
local idef = minetest.registered_nodes[fromnode.name]
-- assert(idef)
local tube = idef.tube
if not (tube and tube.input_inventory) then
return
end
if tube.before_filter then
tube.before_filter(frompos)
end
local frommeta = minetest.get_meta(frompos)
local frominv = frommeta:get_inventory()
local function from_inventory(frominvname)
local sname
for _,filter in ipairs(inv:get_list("main")) do
sname = filter:get_name()
if sname ~= "" then
-- XXX: that's a lot of parameters
if grabAndFire(frominv, frominvname, frompos, fromnode, sname, tube, idef, dir, true) then
return true
end
end
end
if inv:is_empty("main") then
if grabAndFire(frominv, frominvname, frompos, fromnode, nil, tube, idef, dir, true) then
return true
end
end
return false
end
if type(tube.input_inventory) == "table" then
for _, i in ipairs(tube.input_inventory) do
if from_inventory(i) then -- fired an item
break
end
end
else
from_inventory(tube.input_inventory)
end
if tube.after_filter then
tube.after_filter(frompos)
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)
local tbl2={}
for _,val in ipairs(tbl) do
if val.x ~= -vel.x or val.y ~= -vel.y or val.z ~= -vel.z then table.insert(tbl2, val) end
end
return tbl2
end
local function go_next(pos, velocity, stack)
local chests = {}
local tubes = {}
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)
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
else
speed = 1
end
vel.speed = speed
if minetest.registered_nodes[cnode.name] and minetest.registered_nodes[cnode.name].tube and minetest.registered_nodes[cnode.name].tube.can_go then
can_go = minetest.registered_nodes[cnode.name].tube.can_go(pos, cnode, vel, stack)
else
can_go = pipeworks.notvel(adjlist, vel)
end
local meta = nil
for _,vect in ipairs(can_go) do
local npos = addVect(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
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
end
return 1
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},
textures = {""},
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
local stack = ItemStack(itemstring)
self.object:set_properties({
is_visible = true,
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
})
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
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)
end
self:set_item(item.itemstring)
end,
remove = function(self)
self.object:remove()
self.removed = true
self.itemstring = ''
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)
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 stack = ItemStack(self.itemstring)
local drop_pos = nil
local velocity = self.object:getvelocity()
if velocity == nil then return end
local velocitycopy = {x = velocity.x, y = velocity.y, z = velocity.z}
local moved = false
local speed = math.abs(velocity.x + velocity.y + velocity.z)
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
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)
if moved and minetest.get_item_group(node.name, "tubedevice_receiver") == 1 then
local leftover = nil
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
leftover = stack
end
if leftover:is_empty() then
self:remove()
return
end
velocity.x = -velocity.x
velocity.y = -velocity.y
velocity.z = -velocity.z
self.object:setvelocity(velocity)
self:set_item(leftover:to_string())
return
end
if moved then
if go_next (self.start_pos, velocity, stack) == 0 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()
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)
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
mesecon:register_mvps_unmov("pipeworks:tubed_item")
mesecon:register_on_mvps_move(function(moved_nodes)
local objects_to_move = {}
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
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)
end
end
end)
end