item_transport.lua 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407
  1. local luaentity = pipeworks.luaentity
  2. local enable_max_limit = minetest.settings:get("pipeworks_enable_items_per_tube_limit")
  3. local max_tube_limit = tonumber(minetest.settings:get("pipeworks_max_items_per_tube")) or 30
  4. if enable_max_limit == nil then enable_max_limit = true end
  5. function pipeworks.tube_item(pos, item)
  6. error("obsolete pipeworks.tube_item() called; change caller to use pipeworks.tube_inject_item() instead")
  7. end
  8. function pipeworks.tube_inject_item(pos, start_pos, velocity, item, owner)
  9. -- Take item in any format
  10. local stack = ItemStack(item)
  11. local obj = luaentity.add_entity(pos, "pipeworks:tubed_item")
  12. obj:set_item(stack:to_string())
  13. obj.start_pos = vector.new(start_pos)
  14. obj:set_velocity(velocity)
  15. obj.owner = owner
  16. --obj:set_color("red") -- todo: this is test-only code
  17. return obj
  18. end
  19. -- adding two tube functions
  20. -- can_remove(pos,node,stack,dir) returns the maximum number of items of that stack that can be removed
  21. -- remove_items(pos,node,stack,dir,count) removes count items and returns them
  22. -- both optional w/ sensible defaults and fallback to normal allow_* function
  23. -- XXX: possibly change insert_object to insert_item
  24. 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}}
  25. function pipeworks.notvel(tbl, vel)
  26. local tbl2={}
  27. for _,val in ipairs(tbl) do
  28. if val.x ~= -vel.x or val.y ~= -vel.y or val.z ~= -vel.z then table.insert(tbl2, val) end
  29. end
  30. return tbl2
  31. end
  32. local tube_item_count = {}
  33. minetest.register_globalstep(function(dtime)
  34. if not luaentity.entities then
  35. return
  36. end
  37. tube_item_count = {}
  38. for id, entity in pairs(luaentity.entities) do
  39. if entity.name == "pipeworks:tubed_item" then
  40. local h = minetest.hash_node_position(vector.round(entity._pos))
  41. tube_item_count[h] = (tube_item_count[h] or 0) + 1
  42. end
  43. end
  44. end)
  45. -- tube overload mechanism:
  46. -- when the tube's item count (tracked in the above tube_item_count table)
  47. -- exceeds the limit configured per tube, replace it with a broken one.
  48. local crunch_tube = function(pos, cnode, cmeta)
  49. if enable_max_limit then
  50. local h = minetest.hash_node_position(pos)
  51. local itemcount = tube_item_count[h] or 0
  52. if itemcount > max_tube_limit then
  53. cmeta:set_string("the_tube_was", minetest.serialize(cnode))
  54. pipeworks.logger("Warning - a tube at "..minetest.pos_to_string(pos).." broke due to too many items ("..itemcount..")")
  55. minetest.swap_node(pos, {name = "pipeworks:broken_tube_1"})
  56. pipeworks.scan_for_tube_objects(pos)
  57. end
  58. end
  59. end
  60. -- compatibility behaviour for the existing can_go() callbacks,
  61. -- which can only specify a list of possible positions.
  62. local function go_next_compat(pos, cnode, cmeta, cycledir, vel, stack, owner)
  63. local next_positions = {}
  64. local max_priority = 0
  65. local can_go
  66. if minetest.registered_nodes[cnode.name] and minetest.registered_nodes[cnode.name].tube and minetest.registered_nodes[cnode.name].tube.can_go then
  67. can_go = minetest.registered_nodes[cnode.name].tube.can_go(pos, cnode, vel, stack)
  68. else
  69. local adjlist_string = minetest.get_meta(pos):get_string("adjlist")
  70. local adjlist = minetest.deserialize(adjlist_string) or default_adjlist -- backward compat: if not found, use old behavior: all directions
  71. can_go = pipeworks.notvel(adjlist, vel)
  72. end
  73. -- can_go() is expected to return an array-like table of candidate offsets.
  74. -- for each one, look at the node at that offset and determine if it can accept the item.
  75. -- also note the prioritisation:
  76. -- if any tube is found with a greater priority than previously discovered,
  77. -- then the valid positions are reset and and subsequent positions under this are skipped.
  78. -- this has the effect of allowing only equal priorities to co-exist.
  79. for _, vect in ipairs(can_go) do
  80. local npos = vector.add(pos, vect)
  81. pipeworks.load_position(npos)
  82. local node = minetest.get_node(npos)
  83. local reg_node = minetest.registered_nodes[node.name]
  84. if reg_node then
  85. local tube_def = reg_node.tube
  86. local tubedevice = minetest.get_item_group(node.name, "tubedevice")
  87. local tube_priority = (tube_def and tube_def.priority) or 100
  88. if tubedevice > 0 and tube_priority >= max_priority then
  89. if not tube_def or not tube_def.can_insert or
  90. tube_def.can_insert(npos, node, stack, vect, owner) then
  91. if tube_priority > max_priority then
  92. max_priority = tube_priority
  93. next_positions = {}
  94. end
  95. next_positions[#next_positions + 1] = {pos = npos, vect = vect}
  96. end
  97. end
  98. end
  99. end
  100. -- indicate not found if no valid rules were picked up,
  101. -- and don't change the counter.
  102. if not next_positions[1] then
  103. return cycledir, false, nil, nil
  104. end
  105. -- otherwise rotate to the next output direction and return that
  106. local n = (cycledir % (#next_positions)) + 1
  107. local new_velocity = vector.multiply(next_positions[n].vect, vel.speed)
  108. return n, true, new_velocity, nil
  109. end
  110. -- function called by the on_step callback of the pipeworks tube luaentity.
  111. -- the routine is passed the current node position, velocity, itemstack,
  112. -- and owner name.
  113. -- returns three values:
  114. -- * a boolean "found destination" status;
  115. -- * a new velocity vector that the tubed item should use, or nil if not found;
  116. -- * a "multi-mode" data table (or nil if N/A) where a stack was split apart.
  117. -- if this is not nil, the luaentity spawns new tubed items for each new fragment stack,
  118. -- then deletes itself (i.e. the original item stack).
  119. local function go_next(pos, velocity, stack, owner)
  120. local cnode = minetest.get_node(pos)
  121. local cmeta = minetest.get_meta(pos)
  122. local speed = math.abs(velocity.x + velocity.y + velocity.z)
  123. if speed == 0 then
  124. speed = 1
  125. end
  126. local vel = {x = velocity.x/speed, y = velocity.y/speed, z = velocity.z/speed,speed=speed}
  127. if speed >= 4.1 then
  128. speed = 4
  129. elseif speed >= 1.1 then
  130. speed = speed - 0.1
  131. else
  132. speed = 1
  133. end
  134. vel.speed = speed
  135. crunch_tube(pos, cnode, cmeta)
  136. -- cycling of outputs:
  137. -- an integer counter is kept in each pipe's metadata,
  138. -- which allows tracking which output was previously chosen.
  139. -- note reliance on get_int returning 0 for uninitialised.
  140. local cycledir = cmeta:get_int("tubedir")
  141. -- pulled out and factored out into go_next_compat() above.
  142. -- n is the new value of the cycle counter.
  143. -- XXX: this probably needs cleaning up after being split out,
  144. -- seven args is a bit too many
  145. local n, found, new_velocity, multimode = go_next_compat(pos, cnode, cmeta, cycledir, vel, stack, owner)
  146. -- if not using output cycling,
  147. -- don't update the field so it stays the same for the next item.
  148. if pipeworks.enable_cyclic_mode then
  149. cmeta:set_int("tubedir", n)
  150. end
  151. return found, new_velocity, multimode
  152. end
  153. minetest.register_entity("pipeworks:tubed_item", {
  154. initial_properties = {
  155. hp_max = 1,
  156. physical = false,
  157. collisionbox = {0.1, 0.1, 0.1, 0.1, 0.1, 0.1},
  158. visual = "wielditem",
  159. visual_size = {x = 0.15, y = 0.15},
  160. textures = {""},
  161. spritediv = {x = 1, y = 1},
  162. initial_sprite_basepos = {x = 0, y = 0},
  163. is_visible = false,
  164. },
  165. physical_state = false,
  166. from_data = function(self, itemstring)
  167. local stack = ItemStack(itemstring)
  168. local itemtable = stack:to_table()
  169. local itemname = nil
  170. if itemtable then
  171. itemname = stack:to_table().name
  172. end
  173. local item_texture = nil
  174. local item_type = ""
  175. if minetest.registered_items[itemname] then
  176. item_texture = minetest.registered_items[itemname].inventory_image
  177. item_type = minetest.registered_items[itemname].type
  178. end
  179. self.object:set_properties({
  180. is_visible = true,
  181. textures = {stack:get_name()}
  182. })
  183. local def = stack:get_definition()
  184. self.object:set_yaw((def and def.type == "node") and 0 or math.pi * 0.25)
  185. end,
  186. get_staticdata = luaentity.get_staticdata,
  187. on_activate = function(self, staticdata) -- Legacy code, should be replaced later by luaentity.on_activate
  188. if staticdata == "" or staticdata == nil then
  189. return
  190. end
  191. if staticdata == "toremove" then
  192. self.object:remove()
  193. return
  194. end
  195. local item = minetest.deserialize(staticdata)
  196. pipeworks.tube_inject_item(self.object:get_pos(), item.start_pos, item.velocity, item.itemstring)
  197. self.object:remove()
  198. end,
  199. })
  200. minetest.register_entity("pipeworks:color_entity", {
  201. initial_properties = {
  202. hp_max = 1,
  203. physical = false,
  204. collisionbox = {0.1, 0.1, 0.1, 0.1, 0.1, 0.1},
  205. visual = "cube",
  206. visual_size = {x = 3.5, y = 3.5, z = 3.5}, -- todo: find correct size
  207. textures = {""},
  208. is_visible = false,
  209. },
  210. physical_state = false,
  211. from_data = function(self, color)
  212. local t = "pipeworks_color_"..color..".png"
  213. local prop = {
  214. is_visible = true,
  215. visual = "cube",
  216. textures = {t, t, t, t, t, t} -- todo: textures
  217. }
  218. self.object:set_properties(prop)
  219. end,
  220. get_staticdata = luaentity.get_staticdata,
  221. on_activate = luaentity.on_activate,
  222. })
  223. -- see below for usage:
  224. -- determine if go_next returned a multi-mode set.
  225. local is_multimode = function(v)
  226. return (type(v) == "table") and (v.__multimode)
  227. end
  228. luaentity.register_entity("pipeworks:tubed_item", {
  229. itemstring = '',
  230. item_entity = nil,
  231. color_entity = nil,
  232. color = nil,
  233. start_pos = nil,
  234. set_item = function(self, item)
  235. local itemstring = ItemStack(item):to_string() -- Accept any input format
  236. if self.itemstring == itemstring then
  237. return
  238. end
  239. if self.item_entity then
  240. self:remove_attached_entity(self.item_entity)
  241. end
  242. self.itemstring = itemstring
  243. self.item_entity = self:add_attached_entity("pipeworks:tubed_item", itemstring)
  244. end,
  245. set_color = function(self, color)
  246. if self.color == color then
  247. return
  248. end
  249. self.color = color
  250. if self.color_entity then
  251. self:remove_attached_entity(self.color_entity)
  252. end
  253. if color then
  254. self.color_entity = self:add_attached_entity("pipeworks:color_entity", color)
  255. else
  256. self.color_entity = nil
  257. end
  258. end,
  259. on_step = function(self, dtime)
  260. local pos = self:get_pos()
  261. if self.start_pos == nil then
  262. self.start_pos = vector.round(pos)
  263. self:set_pos(pos)
  264. end
  265. local stack = ItemStack(self.itemstring)
  266. local velocity = self:get_velocity()
  267. local moved = false
  268. local speed = math.abs(velocity.x + velocity.y + velocity.z)
  269. if speed == 0 then
  270. speed = 1
  271. moved = true
  272. end
  273. local vel = {x = velocity.x / speed, y = velocity.y / speed, z = velocity.z / speed, speed = speed}
  274. local moved_by = vector.distance(pos, self.start_pos)
  275. if moved_by >= 1 then
  276. self.start_pos = vector.add(self.start_pos, vel)
  277. moved = true
  278. end
  279. pipeworks.load_position(self.start_pos)
  280. local node = minetest.get_node(self.start_pos)
  281. if moved and minetest.get_item_group(node.name, "tubedevice_receiver") == 1 then
  282. local leftover
  283. if minetest.registered_nodes[node.name].tube and minetest.registered_nodes[node.name].tube.insert_object then
  284. leftover = minetest.registered_nodes[node.name].tube.insert_object(self.start_pos, node, stack, vel, self.owner)
  285. else
  286. leftover = stack
  287. end
  288. if leftover:is_empty() then
  289. self:remove()
  290. return
  291. end
  292. velocity = vector.multiply(velocity, -1)
  293. self:set_pos(vector.subtract(self.start_pos, vector.multiply(vel, moved_by - 1)))
  294. self:set_velocity(velocity)
  295. self:set_item(leftover:to_string())
  296. return
  297. end
  298. if moved then
  299. local found_next, new_velocity, multimode = go_next(self.start_pos, velocity, stack, self.owner) -- todo: color
  300. local rev_vel = vector.multiply(velocity, -1)
  301. local rev_dir = vector.direction(self.start_pos,vector.add(self.start_pos,rev_vel))
  302. local rev_node = minetest.get_node(vector.round(vector.add(self.start_pos,rev_dir)))
  303. local tube_present = minetest.get_item_group(rev_node.name,"tubedevice") == 1
  304. if not found_next then
  305. if pipeworks.drop_on_routing_fail or not tube_present or
  306. minetest.get_item_group(rev_node.name,"tube") ~= 1 then
  307. -- Using add_item instead of item_drop since this makes pipeworks backward
  308. -- compatible with Minetest 0.4.13.
  309. -- Using item_drop here makes Minetest 0.4.13 crash.
  310. local dropped_item = minetest.add_item(self.start_pos, stack)
  311. if dropped_item then
  312. dropped_item:set_velocity(vector.multiply(velocity, 5))
  313. self:remove()
  314. end
  315. return
  316. else
  317. velocity = vector.multiply(velocity, -1)
  318. self:set_pos(vector.subtract(self.start_pos, vector.multiply(vel, moved_by - 1)))
  319. self:set_velocity(velocity)
  320. end
  321. elseif is_multimode(multimode) then
  322. -- create new stacks according to returned data.
  323. local s = self.start_pos
  324. for _, split in ipairs(multimode) do
  325. pipeworks.tube_inject_item(s, s, split.velocity, split.itemstack, self.owner)
  326. end
  327. -- remove ourself now the splits are sent
  328. self:remove()
  329. return
  330. end
  331. if new_velocity and not vector.equals(velocity, new_velocity) then
  332. local nvelr = math.abs(new_velocity.x + new_velocity.y + new_velocity.z)
  333. self:set_pos(vector.add(self.start_pos, vector.multiply(new_velocity, (moved_by - 1) / nvelr)))
  334. self:set_velocity(new_velocity)
  335. end
  336. end
  337. end
  338. })
  339. if minetest.get_modpath("mesecons_mvps") then
  340. mesecon.register_mvps_unmov("pipeworks:tubed_item")
  341. mesecon.register_mvps_unmov("pipeworks:color_entity")
  342. mesecon.register_on_mvps_move(function(moved_nodes)
  343. local moved = {}
  344. for _, n in ipairs(moved_nodes) do
  345. moved[minetest.hash_node_position(n.oldpos)] = vector.subtract(n.pos, n.oldpos)
  346. end
  347. for id, entity in pairs(luaentity.entities) do
  348. if entity.name == "pipeworks:tubed_item" then
  349. local pos = entity:get_pos()
  350. local rpos = vector.round(pos)
  351. local dir = moved[minetest.hash_node_position(rpos)]
  352. if dir then
  353. entity:set_pos(vector.add(pos, dir))
  354. entity.start_pos = vector.add(entity.start_pos, dir)
  355. end
  356. end
  357. end
  358. end)
  359. end