pipeworks/item_transport.lua

454 lines
14 KiB
Lua
Raw Permalink Normal View History

local luaentity = pipeworks.luaentity
local enable_max_limit = minetest.settings:get_bool("pipeworks_enable_items_per_tube_limit")
local max_tube_limit = tonumber(minetest.settings:get("pipeworks_max_items_per_tube")) or 30
if enable_max_limit == nil then enable_max_limit = true end
if pipeworks.enable_item_tags then
local max_tag_length = tonumber(minetest.settings:get("pipeworks_max_item_tag_length")) or 32
local max_tags = tonumber(minetest.settings:get("pipeworks_max_item_tags")) or 16
function pipeworks.sanitize_tags(tags)
if type(tags) == "string" then
tags = tags:split(",")
end
local sanitized = {}
for i, tag in ipairs(tags) do
if type(tag) == "string" then
tag = tag:gsub("[%s,]", "") -- Remove whitespace and commas
tag = tag:gsub("%$%b%{%}", "") -- Remove special ${key} values
if tag ~= "" then
table.insert(sanitized, tag:sub(1, max_tag_length))
end
end
if #sanitized >= max_tags then
break
end
end
return sanitized
end
end
function pipeworks.tube_item(pos, item)
error("obsolete pipeworks.tube_item() called; change caller to use pipeworks.tube_inject_item() instead")
end
function pipeworks.tube_inject_item(pos, start_pos, velocity, item, owner, tags)
2013-12-15 11:35:11 +01:00
-- Take item in any format
local stack = ItemStack(item)
2014-08-14 16:22:03 +02:00
local obj = luaentity.add_entity(pos, "pipeworks:tubed_item")
obj:set_item(stack:to_string())
obj.start_pos = vector.new(start_pos)
2018-07-03 22:30:44 +02:00
obj:set_velocity(velocity)
obj.owner = owner
obj.tags = tags
2014-08-14 16:22:03 +02:00
--obj:set_color("red") -- todo: this is test-only code
2013-12-15 11:35:11 +01:00
return obj
end
Multiple updates: 1) Refactor autoplace, There was a lot of redundant code and like a dozen unneccessary string scans for every node next to every tube placed! I put it all into indexed tables and loops instead of bizarre and unexplainable variable names and copy and pasted code. There was also no support for notifying a chest when an item has been taken from it by a filter, so I added something for that. I also thought it prudent to fall back on the allow_metadata_inventory_take function should a special can_remove not exist. In fact if can_insert doesn't exist, it calls allow_metadata_inventory_put instead. I also added a thing for allowing pipes to attach to nodes of other modules, without having to hard code type all those node names into autoplace.lua. Basically node.tube.collects(i,param2) and i is the direction from the pipe and param2 is the param2 of what it's pointing at. I also abstracted the inscrutable correlation between i and param2 by trial and error (and the paramwand mod) into understandable functions. There was no pipeworks namespace so I created it, and put these functions into pipeworks.collects (as distinguished from a node.tube.collects function, which uses those functions) And now it's too late to cart my old clothes to the thrift store, dangit. 2) My "node.tube.collects" idea might be redundant with the node.tube.connect_sides thing, though possibly more versatile so I'll leave it in. 3) I was using node.tube.connects and fancy functions for checking if it's the sides or top or whatnot, and this connect_side thing came in. This should make both my way and the way using connect_side work. Also removed some debugging cruft
2013-10-15 05:45:07 +02:00
-- adding two tube functions
-- can_remove(pos,node,stack,dir) returns the maximum number of items of that stack that can be removed
Multiple updates: 1) Refactor autoplace, There was a lot of redundant code and like a dozen unneccessary string scans for every node next to every tube placed! I put it all into indexed tables and loops instead of bizarre and unexplainable variable names and copy and pasted code. There was also no support for notifying a chest when an item has been taken from it by a filter, so I added something for that. I also thought it prudent to fall back on the allow_metadata_inventory_take function should a special can_remove not exist. In fact if can_insert doesn't exist, it calls allow_metadata_inventory_put instead. I also added a thing for allowing pipes to attach to nodes of other modules, without having to hard code type all those node names into autoplace.lua. Basically node.tube.collects(i,param2) and i is the direction from the pipe and param2 is the param2 of what it's pointing at. I also abstracted the inscrutable correlation between i and param2 by trial and error (and the paramwand mod) into understandable functions. There was no pipeworks namespace so I created it, and put these functions into pipeworks.collects (as distinguished from a node.tube.collects function, which uses those functions) And now it's too late to cart my old clothes to the thrift store, dangit. 2) My "node.tube.collects" idea might be redundant with the node.tube.connect_sides thing, though possibly more versatile so I'll leave it in. 3) I was using node.tube.connects and fancy functions for checking if it's the sides or top or whatnot, and this connect_side thing came in. This should make both my way and the way using connect_side work. Also removed some debugging cruft
2013-10-15 05:45:07 +02:00
-- 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
local default_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}}
2013-12-15 11:35:11 +01:00
2013-12-21 10:16:58 +01:00
function pipeworks.notvel(tbl, vel)
2013-12-15 11:35:11 +01:00
local tbl2={}
for _,val in ipairs(tbl) do
2013-12-21 10:16:58 +01:00
if val.x ~= -vel.x or val.y ~= -vel.y or val.z ~= -vel.z then table.insert(tbl2, val) end
2013-12-15 11:35:11 +01:00
end
return tbl2
end
local tube_item_count = {}
minetest.register_globalstep(function(dtime)
if not luaentity.entities then
return
end
tube_item_count = {}
for _, entity in pairs(luaentity.entities) do
if entity.name == "pipeworks:tubed_item" then
local h = minetest.hash_node_position(vector.round(entity._pos))
tube_item_count[h] = (tube_item_count[h] or 0) + 1
end
end
end)
-- tube overload mechanism:
-- when the tube's item count (tracked in the above tube_item_count table)
-- exceeds the limit configured per tube, replace it with a broken one.
function pipeworks.break_tube(pos)
local node = minetest.get_node(pos)
local meta = minetest.get_meta(pos)
meta:set_string("the_tube_was", minetest.serialize(node))
minetest.swap_node(pos, {name = "pipeworks:broken_tube_1"})
pipeworks.scan_for_tube_objects(pos)
end
local crunch_tube = function(pos, cnode, cmeta)
if enable_max_limit then
local h = minetest.hash_node_position(pos)
local itemcount = tube_item_count[h] or 0
if itemcount > max_tube_limit then
pipeworks.logger("Warning - a tube at "..minetest.pos_to_string(pos).." broke due to too many items ("..itemcount..")")
pipeworks.break_tube(pos)
end
end
end
-- compatibility behaviour for the existing can_go() callbacks,
-- which can only specify a list of possible positions.
local function go_next_compat(pos, cnode, cmeta, cycledir, vel, stack, owner, tags)
2014-08-14 16:22:03 +02:00
local next_positions = {}
local max_priority = 0
2013-12-15 11:35:11 +01:00
local can_go
local def = minetest.registered_nodes[cnode.name]
if def and def.tube and def.tube.can_go then
can_go = def.tube.can_go(pos, cnode, vel, stack, tags)
2013-12-15 11:35:11 +01:00
else
local adjlist_string = minetest.get_meta(pos):get_string("adjlist")
local adjlist = minetest.deserialize(adjlist_string) or default_adjlist -- backward compat: if not found, use old behavior: all directions
2013-12-21 10:16:58 +01:00
can_go = pipeworks.notvel(adjlist, vel)
2013-12-15 11:35:11 +01:00
end
-- can_go() is expected to return an array-like table of candidate offsets.
-- for each one, look at the node at that offset and determine if it can accept the item.
-- also note the prioritisation:
-- if any tube is found with a greater priority than previously discovered,
-- then the valid positions are reset and and subsequent positions under this are skipped.
-- this has the effect of allowing only equal priorities to co-exist.
2014-08-14 16:22:03 +02:00
for _, vect in ipairs(can_go) do
local npos = vector.add(pos, vect)
pipeworks.load_position(npos)
2013-12-21 08:46:55 +01:00
local node = minetest.get_node(npos)
2014-08-24 17:51:43 +02:00
local reg_node = minetest.registered_nodes[node.name]
if reg_node then
local tube_def = reg_node.tube
local tubedevice = minetest.get_item_group(node.name, "tubedevice")
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
tube_def.can_insert(npos, node, stack, vect, owner) then
2014-08-24 17:51:43 +02:00
if tube_priority > max_priority then
max_priority = tube_priority
next_positions = {}
end
next_positions[#next_positions + 1] = {pos = npos, vect = vect}
2014-08-14 16:22:03 +02:00
end
2013-12-15 11:35:11 +01:00
end
end
end
2014-08-14 16:22:03 +02:00
-- indicate not found if no valid rules were picked up,
-- and don't change the counter.
2014-08-14 16:22:03 +02:00
if not next_positions[1] then
return cycledir, false, nil, nil
2014-08-14 16:22:03 +02:00
end
-- otherwise rotate to the next output direction and return that
local n = (cycledir % (#next_positions)) + 1
local new_velocity = vector.multiply(next_positions[n].vect, vel.speed)
return n, true, new_velocity, nil
end
-- function called by the on_step callback of the pipeworks tube luaentity.
-- the routine is passed the current node position, velocity, itemstack,
-- and owner name.
-- returns three values:
-- * a boolean "found destination" status;
-- * a new velocity vector that the tubed item should use, or nil if not found;
-- * a "multi-mode" data table (or nil if N/A) where a stack was split apart.
-- if this is not nil, the luaentity spawns new tubed items for each new fragment stack,
-- then deletes itself (i.e. the original item stack).
local function go_next(pos, velocity, stack, owner, tags)
local cnode = minetest.get_node(pos)
local cmeta = minetest.get_meta(pos)
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
else
speed = 1
end
vel.speed = speed
crunch_tube(pos, cnode, cmeta)
-- cycling of outputs:
-- an integer counter is kept in each pipe's metadata,
-- which allows tracking which output was previously chosen.
-- note reliance on get_int returning 0 for uninitialised.
local cycledir = cmeta:get_int("tubedir")
-- pulled out and factored out into go_next_compat() above.
-- n is the new value of the cycle counter.
-- XXX: this probably needs cleaning up after being split out,
-- seven args is a bit too many
local n, found, new_velocity, multimode = go_next_compat(pos, cnode, cmeta, cycledir, vel, stack, owner, tags)
-- if not using output cycling,
-- don't update the field so it stays the same for the next item.
2014-08-14 16:22:03 +02:00
if pipeworks.enable_cyclic_mode then
cmeta:set_int("tubedir", n)
2013-12-15 11:35:11 +01:00
end
return found, new_velocity, multimode
2013-12-15 11:35:11 +01:00
end
minetest.register_entity("pipeworks:tubed_item", {
initial_properties = {
hp_max = 1,
physical = false,
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,
},
2014-08-14 16:22:03 +02:00
physical_state = false,
2014-08-14 16:22:03 +02:00
from_data = function(self, itemstring)
local stack = ItemStack(itemstring)
--[[
2014-08-14 16:22:03 +02:00
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,
2014-08-14 16:22:03 +02:00
textures = {stack:get_name()}
})
local def = stack:get_definition()
2018-07-03 22:30:44 +02:00
self.object:set_yaw((def and def.type == "node") and 0 or math.pi * 0.25)
end,
2014-08-14 16:22:03 +02:00
get_staticdata = luaentity.get_staticdata,
2014-08-14 21:18:48 +02:00
on_activate = function(self, staticdata) -- Legacy code, should be replaced later by luaentity.on_activate
if staticdata == "" or staticdata == nil then
return
end
if staticdata == "toremove" then
self.object:remove()
return
end
local item = minetest.deserialize(staticdata)
2018-07-03 22:30:44 +02:00
pipeworks.tube_inject_item(self.object:get_pos(), item.start_pos, item.velocity, item.itemstring)
2014-08-14 21:18:48 +02:00
self.object:remove()
end,
2014-08-14 16:22:03 +02:00
})
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,
2014-08-14 16:22:03 +02:00
get_staticdata = luaentity.get_staticdata,
on_activate = luaentity.on_activate,
})
-- see below for usage:
-- determine if go_next returned a multi-mode set.
local is_multimode = function(v)
return (type(v) == "table") and (v.__multimode)
end
2014-08-14 16:22:03 +02:00
luaentity.register_entity("pipeworks:tubed_item", {
itemstring = '',
item_entity = nil,
color_entity = nil,
color = nil,
start_pos = nil,
tags = nil,
2014-08-14 16:22:03 +02:00
set_item = function(self, item)
local itemstring = ItemStack(item):to_string() -- Accept any input format
if self.itemstring == itemstring then
return
end
2014-08-14 16:22:03 +02:00
if self.item_entity then
self:remove_attached_entity(self.item_entity)
end
2014-08-14 16:22:03 +02:00
self.itemstring = itemstring
self.item_entity = self:add_attached_entity("pipeworks:tubed_item", itemstring)
end,
2014-08-14 16:22:03 +02:00
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)
2018-07-03 22:30:44 +02:00
local pos = self:get_pos()
if self.start_pos == nil then
2014-08-14 16:22:03 +02:00
self.start_pos = vector.round(pos)
2018-07-03 22:30:44 +02:00
self:set_pos(pos)
2014-04-13 09:25:17 +02:00
end
2018-07-03 22:30:44 +02:00
local velocity = self:get_velocity()
2014-04-13 09:25:17 +02:00
local moved = false
local speed = math.abs(velocity.x + velocity.y + velocity.z)
2014-08-14 16:22:03 +02:00
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}
local moved_by = vector.distance(pos, self.start_pos)
if moved_by >= 1 then
2014-08-14 16:22:03 +02:00
self.start_pos = vector.add(self.start_pos, vel)
moved = true
end
2022-05-15 16:58:35 +02:00
if not moved then
return
end
local stack = ItemStack(self.itemstring)
pipeworks.load_position(self.start_pos)
2014-08-14 16:22:03 +02:00
local node = minetest.get_node(self.start_pos)
2022-05-15 16:58:35 +02:00
if minetest.get_item_group(node.name, "tubedevice_receiver") == 1 then
2014-08-14 16:22:03 +02:00
local leftover
local def = minetest.registered_nodes[node.name]
if def.tube and def.tube.insert_object then
leftover = def.tube.insert_object(self.start_pos, node, stack, vel, self.owner)
2014-04-13 09:25:17 +02:00
else
leftover = stack
end
if leftover:is_empty() then
self:remove()
2014-04-13 09:25:17 +02:00
return
end
2014-08-14 16:22:03 +02:00
velocity = vector.multiply(velocity, -1)
2018-07-03 22:30:44 +02:00
self:set_pos(vector.subtract(self.start_pos, vector.multiply(vel, moved_by - 1)))
self:set_velocity(velocity)
2014-04-13 09:25:17 +02:00
self:set_item(leftover:to_string())
return
end
local tags
if pipeworks.enable_item_tags then
tags = self.tags or {}
end
local found_next, new_velocity, multimode = go_next(self.start_pos, velocity, stack, self.owner, tags) -- todo: color
if pipeworks.enable_item_tags then
self.tags = #tags > 0 and tags or nil
end
2022-05-15 16:58:35 +02:00
local rev_vel = vector.multiply(velocity, -1)
local rev_dir = vector.direction(self.start_pos,vector.add(self.start_pos,rev_vel))
local rev_node = minetest.get_node(vector.round(vector.add(self.start_pos,rev_dir)))
local tube_present = minetest.get_item_group(rev_node.name,"tubedevice") == 1
if not found_next then
if pipeworks.drop_on_routing_fail or not tube_present or
minetest.get_item_group(rev_node.name,"tube") ~= 1 then
-- Using add_item instead of item_drop since this makes pipeworks backward
-- compatible with Minetest 0.4.13.
-- Using item_drop here makes Minetest 0.4.13 crash.
local dropped_item = minetest.add_item(self.start_pos, stack)
if dropped_item then
dropped_item:set_velocity(vector.multiply(velocity, 5))
self:remove()
2014-04-13 09:25:17 +02:00
end
return
2022-05-15 16:58:35 +02:00
else
velocity = vector.multiply(velocity, -1)
self:set_pos(vector.subtract(self.start_pos, vector.multiply(vel, moved_by - 1)))
self:set_velocity(velocity)
end
2022-05-15 16:58:35 +02:00
elseif is_multimode(multimode) then
-- create new stacks according to returned data.
local s = self.start_pos
for _, split in ipairs(multimode) do
pipeworks.tube_inject_item(s, s, split.velocity, split.itemstack, self.owner)
2014-08-14 16:22:03 +02:00
end
2022-05-15 16:58:35 +02:00
-- remove ourself now the splits are sent
self:remove()
return
end
if new_velocity and not vector.equals(velocity, new_velocity) then
local nvelr = math.abs(new_velocity.x + new_velocity.y + new_velocity.z)
self:set_pos(vector.add(self.start_pos, vector.multiply(new_velocity, (moved_by - 1) / nvelr)))
self:set_velocity(new_velocity)
2014-04-13 09:25:17 +02:00
end
end
})
2014-08-14 16:22:03 +02:00
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)
2014-08-14 16:22:03 +02:00
local moved = {}
2014-01-03 13:29:38 +01:00
for _, n in ipairs(moved_nodes) do
2014-08-14 16:22:03 +02:00
moved[minetest.hash_node_position(n.oldpos)] = vector.subtract(n.pos, n.oldpos)
2014-01-03 13:29:38 +01:00
end
for _, entity in pairs(luaentity.entities) do
2014-08-14 16:22:03 +02:00
if entity.name == "pipeworks:tubed_item" then
2018-07-03 22:30:44 +02:00
local pos = entity:get_pos()
2014-08-14 16:22:03 +02:00
local rpos = vector.round(pos)
local dir = moved[minetest.hash_node_position(rpos)]
if dir then
2018-07-03 22:30:44 +02:00
entity:set_pos(vector.add(pos, dir))
2014-08-14 16:22:03 +02:00
entity.start_pos = vector.add(entity.start_pos, dir)
end
2014-01-03 13:29:38 +01:00
end
end
end)
end