Pipeworks
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

561 lines
19KB

  1. local fs_helpers = pipeworks.fs_helpers
  2. local function delay(x)
  3. return (function() return x end)
  4. end
  5. local function set_filter_infotext(data, meta)
  6. local infotext = data.wise_desc.." Filter-Injector"
  7. if meta:get_int("slotseq_mode") == 2 then
  8. infotext = infotext .. " (slot #"..meta:get_int("slotseq_index").." next)"
  9. end
  10. meta:set_string("infotext", infotext)
  11. end
  12. local function set_filter_formspec(data, meta)
  13. local itemname = data.wise_desc.." Filter-Injector"
  14. local formspec
  15. if data.digiline then
  16. formspec = "size[8,2.7]"..
  17. "item_image[0,0;1,1;pipeworks:"..data.name.."]"..
  18. "label[1,0;"..minetest.formspec_escape(itemname).."]"..
  19. "field[0.3,1.5;8.0,1;channel;Channel;${channel}]"..
  20. fs_helpers.cycling_button(meta, "button[0,2;4,1", "slotseq_mode",
  21. {"Sequence slots by Priority",
  22. "Sequence slots Randomly",
  23. "Sequence slots by Rotation"})..
  24. fs_helpers.cycling_button(meta, "button[4,2;4,1", "exmatch_mode",
  25. {"Exact match - off",
  26. "Exact match - on "})
  27. else
  28. local exmatch_button = ""
  29. if data.stackwise then
  30. exmatch_button =
  31. fs_helpers.cycling_button(meta, "button[4,3.5;4,1", "exmatch_mode",
  32. {"Exact match - off",
  33. "Exact match - on "})
  34. end
  35. formspec = "size[8,8.5]"..
  36. "item_image[0,0;1,1;pipeworks:"..data.name.."]"..
  37. "label[1,0;"..minetest.formspec_escape(itemname).."]"..
  38. "label[0,1;Prefer item types:]"..
  39. "list[context;main;0,1.5;8,2;]"..
  40. fs_helpers.cycling_button(meta, "button[0,3.5;4,1", "slotseq_mode",
  41. {"Sequence slots by Priority",
  42. "Sequence slots Randomly",
  43. "Sequence slots by Rotation"})..
  44. exmatch_button..
  45. "list[current_player;main;0,4.5;8,4;]" ..
  46. "listring[]"
  47. end
  48. meta:set_string("formspec", formspec)
  49. end
  50. -- todo SOON: this function has *way too many* parameters
  51. local function grabAndFire(data,slotseq_mode,exmatch_mode,filtmeta,frominv,frominvname,frompos,fromnode,filterfor,fromtube,fromdef,dir,fakePlayer,all,digiline)
  52. local sposes = {}
  53. if not frominvname or not frominv:get_list(frominvname) then return end
  54. for spos,stack in ipairs(frominv:get_list(frominvname)) do
  55. local matches
  56. if filterfor == "" then
  57. matches = stack:get_name() ~= ""
  58. else
  59. local fname = filterfor.name
  60. local fgroup = filterfor.group
  61. local fwear = filterfor.wear
  62. local fmetadata = filterfor.metadata
  63. matches = (not fname -- If there's a name filter,
  64. or stack:get_name() == fname) -- it must match.
  65. and (not fgroup -- If there's a group filter,
  66. or (type(fgroup) == "string" -- it must be a string
  67. and minetest.get_item_group( -- and it must match.
  68. stack:get_name(), fgroup) ~= 0))
  69. and (not fwear -- If there's a wear filter:
  70. or (type(fwear) == "number" -- If it's a number,
  71. and stack:get_wear() == fwear) -- it must match.
  72. or (type(fwear) == "table" -- If it's a table:
  73. and (not fwear[1] -- If there's a lower bound,
  74. or (type(fwear[1]) == "number" -- it must be a number
  75. and fwear[1] <= stack:get_wear())) -- and it must be <= the actual wear.
  76. and (not fwear[2] -- If there's an upper bound
  77. or (type(fwear[2]) == "number" -- it must be a number
  78. and stack:get_wear() < fwear[2])))) -- and it must be > the actual wear.
  79. -- If the wear filter is of any other type, fail.
  80. --
  81. and (not fmetadata -- If there's a matadata filter,
  82. or (type(fmetadata) == "string" -- it must be a string
  83. and stack:get_metadata() == fmetadata)) -- and it must match.
  84. end
  85. if matches then table.insert(sposes, spos) end
  86. end
  87. if #sposes == 0 then return false end
  88. if slotseq_mode == 1 then
  89. for i = #sposes, 2, -1 do
  90. local j = math.random(i)
  91. local t = sposes[j]
  92. sposes[j] = sposes[i]
  93. sposes[i] = t
  94. end
  95. elseif slotseq_mode == 2 then
  96. local headpos = filtmeta:get_int("slotseq_index")
  97. table.sort(sposes, function (a, b)
  98. if a >= headpos then
  99. if b < headpos then return true end
  100. else
  101. if b >= headpos then return false end
  102. end
  103. return a < b
  104. end)
  105. end
  106. for _, spos in ipairs(sposes) do
  107. local stack = frominv:get_stack(frominvname, spos)
  108. local doRemove = stack:get_count()
  109. if fromtube.can_remove then
  110. doRemove = fromtube.can_remove(frompos, fromnode, stack, dir, frominvname, spos)
  111. elseif fromdef.allow_metadata_inventory_take then
  112. doRemove = fromdef.allow_metadata_inventory_take(frompos, frominvname,spos, stack, fakePlayer)
  113. end
  114. -- stupid lack of continue statements grumble
  115. if doRemove > 0 then
  116. if slotseq_mode == 2 then
  117. local nextpos = spos + 1
  118. if nextpos > frominv:get_size(frominvname) then
  119. nextpos = 1
  120. end
  121. filtmeta:set_int("slotseq_index", nextpos)
  122. set_filter_infotext(data, filtmeta)
  123. end
  124. local item
  125. local count
  126. if all then
  127. count = math.min(stack:get_count(), doRemove)
  128. if filterfor.count and (filterfor.count > 1 or digiline) then
  129. if exmatch_mode ~= 0 and filterfor.count > count then
  130. return false -- not enough, fail
  131. else
  132. -- limit quantity to filter amount
  133. count = math.min(filterfor.count, count)
  134. end
  135. end
  136. else
  137. count = 1
  138. end
  139. if fromtube.remove_items then
  140. -- it could be the entire stack...
  141. item = fromtube.remove_items(frompos, fromnode, stack, dir, count, frominvname, spos)
  142. else
  143. item = stack:take_item(count)
  144. frominv:set_stack(frominvname, spos, stack)
  145. if fromdef.on_metadata_inventory_take then
  146. fromdef.on_metadata_inventory_take(frompos, frominvname, spos, item, fakePlayer)
  147. end
  148. end
  149. local pos = vector.add(frompos, vector.multiply(dir, 1.4))
  150. local start_pos = vector.add(frompos, dir)
  151. local item1 = pipeworks.tube_inject_item(pos, start_pos, dir, item, fakePlayer:get_player_name())
  152. return true-- only fire one item, please
  153. end
  154. end
  155. return false
  156. end
  157. local function punch_filter(data, filtpos, filtnode, msg)
  158. local filtmeta = minetest.get_meta(filtpos)
  159. local filtinv = filtmeta:get_inventory()
  160. local owner = filtmeta:get_string("owner")
  161. local fakePlayer = pipeworks.create_fake_player({
  162. name = owner
  163. })
  164. local dir = pipeworks.facedir_to_right_dir(filtnode.param2)
  165. local frompos = vector.subtract(filtpos, dir)
  166. local fromnode = minetest.get_node(frompos)
  167. if not fromnode then return end
  168. local fromdef = minetest.registered_nodes[fromnode.name]
  169. if not fromdef then return end
  170. local fromtube = fromdef.tube
  171. local input_special_cases = {
  172. ["technic:mv_furnace"] = "dst",
  173. ["technic:mv_furnace_active"] = "dst",
  174. ["technic:mv_electric_furnace"] = "dst",
  175. ["technic:mv_electric_furnace_active"] = "dst",
  176. ["technic:mv_alloy_furnace"] = "dst",
  177. ["technic:mv_alloy_furnace_active"] = "dst",
  178. ["technic:mv_centrifuge"] = "dst",
  179. ["technic:mv_centrifuge_active"] = "dst",
  180. ["technic:mv_compressor"] = "dst",
  181. ["technic:mv_compressor_active"] = "dst",
  182. ["technic:mv_extractor"] = "dst",
  183. ["technic:mv_extractor_active"] = "dst",
  184. ["technic:mv_grinder"] = "dst",
  185. ["technic:mv_grinder_active"] = "dst",
  186. ["technic:tool_workshop"] = "src",
  187. }
  188. -- make sure there's something appropriate to inject the item into
  189. local todir = pipeworks.facedir_to_right_dir(filtnode.param2)
  190. local topos = vector.add(filtpos, todir)
  191. local tonode = minetest.get_node(topos)
  192. local todef = minetest.registered_nodes[tonode.name]
  193. if not todef
  194. or not (minetest.get_item_group(tonode.name, "tube") == 1
  195. or minetest.get_item_group(tonode.name, "tubedevice") == 1
  196. or minetest.get_item_group(tonode.name, "tubedevice_receiver") == 1) then
  197. return
  198. end
  199. if fromtube then fromtube.input_inventory = input_special_cases[fromnode.name] or fromtube.input_inventory end
  200. if not (fromtube and fromtube.input_inventory) then return end
  201. local slotseq_mode
  202. local exact_match
  203. local filters = {}
  204. if data.digiline then
  205. local function add_filter(name, group, count, wear, metadata)
  206. table.insert(filters, {name = name, group = group, count = tonumber(count), wear = wear, metadata = metadata})
  207. end
  208. local function add_itemstring_filter(filter)
  209. local filterstack = ItemStack(filter)
  210. local filtername = filterstack:get_name()
  211. local filtercount = filterstack:get_count()
  212. local filterwear = string.match(filter, "%S*:%S*%s%d%s(%d)") and filterstack:get_wear()
  213. local filtermetadata = string.match(filter, "%S*:%S*%s%d%s%d(%s.*)") and filterstack:get_metadata()
  214. add_filter(filtername, nil, filtercount, filterwear, filtermetadata)
  215. end
  216. local t_msg = type(msg)
  217. if t_msg == "table" then
  218. local slotseq = msg.slotseq
  219. local t_slotseq = type(slotseq)
  220. if t_slotseq == "number" and slotseq >= 0 and slotseq <= 2 then
  221. slotseq_mode = slotseq
  222. elseif t_slotseq == "string" then
  223. slotseq = string.lower(slotseq)
  224. if slotseq == "priority" then
  225. slotseq_mode = 0
  226. elseif slotseq == "random" then
  227. slotseq_mode = 1
  228. elseif slotseq == "rotation" then
  229. slotseq_mode = 2
  230. end
  231. end
  232. local exmatch = msg.exmatch
  233. local t_exmatch = type(exmatch)
  234. if t_exmatch == "number" and exmatch >= 0 and exmatch <= 1 then
  235. exact_match = exmatch
  236. elseif t_exmatch == "boolean" then
  237. exact_match = exmatch and 1 or 0
  238. end
  239. local slotseq_index = msg.slotseq_index
  240. if type(slotseq_index) == "number" then
  241. -- This should allow any valid index, but I'm not completely sure what
  242. -- constitutes a valid index, so I'm only allowing resetting it to 1.
  243. if slotseq_index == 1 then
  244. filtmeta:set_int("slotseq_index", slotseq_index)
  245. set_filter_infotext(data, filtmeta)
  246. end
  247. end
  248. if slotseq_mode ~= nil then
  249. filtmeta:set_int("slotseq_mode", slotseq_mode)
  250. end
  251. if exact_match ~= nil then
  252. filtmeta:set_int("exmatch_mode", exact_match)
  253. end
  254. if slotseq_mode ~= nil or exact_match ~= nil then
  255. set_filter_formspec(data, filtmeta)
  256. end
  257. if msg.nofire then
  258. return
  259. end
  260. if msg.name or msg.group or msg.count or msg.wear or msg.metadata then
  261. add_filter(msg.name, msg.group, msg.count, msg.wear, msg.metadata)
  262. else
  263. for _, filter in ipairs(msg) do
  264. local t_filter = type(filter)
  265. if t_filter == "table" then
  266. if filter.name or filter.group or filter.count or filter.wear or filter.metadata then
  267. add_filter(filter.name, filter.group, filter.count, filter.wear, filter.metadata)
  268. end
  269. elseif t_filter == "string" then
  270. add_itemstring_filter(filter)
  271. end
  272. end
  273. end
  274. elseif t_msg == "string" then
  275. add_itemstring_filter(msg)
  276. end
  277. else
  278. for _, filterstack in ipairs(filtinv:get_list("main")) do
  279. local filtername = filterstack:get_name()
  280. local filtercount = filterstack:get_count()
  281. if filtername ~= "" then table.insert(filters, {name = filtername, count = filtercount}) end
  282. end
  283. end
  284. if #filters == 0 then table.insert(filters, "") end
  285. if slotseq_mode == nil then
  286. slotseq_mode = filtmeta:get_int("slotseq_mode")
  287. end
  288. if exact_match == nil then
  289. exact_match = filtmeta:get_int("exmatch_mode")
  290. end
  291. local frominv
  292. if fromtube.return_input_invref then
  293. frominv = fromtube.return_input_invref(frompos, fromnode, dir, owner)
  294. if not frominv then
  295. return
  296. end
  297. else
  298. local frommeta = minetest.get_meta(frompos)
  299. frominv = frommeta:get_inventory()
  300. end
  301. if fromtube.before_filter then fromtube.before_filter(frompos) end
  302. for _, frominvname in ipairs(type(fromtube.input_inventory) == "table" and fromtube.input_inventory or {fromtube.input_inventory}) do
  303. local done = false
  304. for _, filterfor in ipairs(filters) do
  305. if grabAndFire(data, slotseq_mode, exact_match, filtmeta, frominv, frominvname, frompos, fromnode, filterfor, fromtube, fromdef, dir, fakePlayer, data.stackwise, data.digiline) then
  306. done = true
  307. break
  308. end
  309. end
  310. if done then break end
  311. end
  312. if fromtube.after_filter then fromtube.after_filter(frompos) end
  313. end
  314. for _, data in ipairs({
  315. {
  316. name = "filter",
  317. wise_desc = "Itemwise",
  318. stackwise = false,
  319. },
  320. {
  321. name = "mese_filter",
  322. wise_desc = "Stackwise",
  323. stackwise = true,
  324. },
  325. { -- register even if no digilines
  326. name = "digiline_filter",
  327. wise_desc = "Digiline",
  328. stackwise = true,
  329. digiline = true,
  330. },
  331. }) do
  332. local node = {
  333. description = data.wise_desc.." Filter-Injector",
  334. tiles = {
  335. "pipeworks_"..data.name.."_top.png",
  336. "pipeworks_"..data.name.."_top.png",
  337. "pipeworks_"..data.name.."_output.png",
  338. "pipeworks_"..data.name.."_input.png",
  339. "pipeworks_"..data.name.."_side.png",
  340. "pipeworks_"..data.name.."_top.png",
  341. },
  342. paramtype2 = "facedir",
  343. groups = {snappy = 2, choppy = 2, oddly_breakable_by_hand = 2, mesecon = 2},
  344. legacy_facedir_simple = true,
  345. sounds = default.node_sound_wood_defaults(),
  346. on_construct = function(pos)
  347. local meta = minetest.get_meta(pos)
  348. set_filter_formspec(data, meta)
  349. set_filter_infotext(data, meta)
  350. local inv = meta:get_inventory()
  351. inv:set_size("main", 8*2)
  352. end,
  353. after_place_node = function (pos, placer)
  354. minetest.get_meta(pos):set_string("owner", placer:get_player_name())
  355. pipeworks.after_place(pos)
  356. end,
  357. after_dig_node = pipeworks.after_dig,
  358. allow_metadata_inventory_put = function(pos, listname, index, stack, player)
  359. if not pipeworks.may_configure(pos, player) then
  360. return 0
  361. end
  362. local inv = minetest.get_meta(pos):get_inventory()
  363. inv:set_stack("main", index, stack)
  364. return 0
  365. end,
  366. allow_metadata_inventory_take = function(pos, listname, index, stack, player)
  367. if not pipeworks.may_configure(pos, player) then
  368. return 0
  369. end
  370. local inv = minetest.get_meta(pos):get_inventory()
  371. local fake_stack = inv:get_stack("main", index)
  372. fake_stack:take_item(stack:get_count())
  373. inv:set_stack("main", index, fake_stack)
  374. return 0
  375. end,
  376. allow_metadata_inventory_move = function(pos, from_list, from_index, to_list, to_index, count, player)
  377. if not pipeworks.may_configure(pos, player) then return 0 end
  378. return count
  379. end,
  380. can_dig = function(pos, player)
  381. local meta = minetest.get_meta(pos)
  382. local inv = meta:get_inventory()
  383. return inv:is_empty("main")
  384. end,
  385. tube = {connect_sides = {right = 1}},
  386. }
  387. if data.digiline then
  388. node.groups.mesecon = nil
  389. if not minetest.get_modpath("digilines") then
  390. node.groups.not_in_creative_inventory = 1
  391. end
  392. node.on_receive_fields = function(pos, formname, fields, sender)
  393. if not pipeworks.may_configure(pos, sender) then return end
  394. fs_helpers.on_receive_fields(pos, fields)
  395. if fields.channel then
  396. minetest.get_meta(pos):set_string("channel", fields.channel)
  397. end
  398. local meta = minetest.get_meta(pos)
  399. --meta:set_int("slotseq_index", 1)
  400. set_filter_formspec(data, meta)
  401. set_filter_infotext(data, meta)
  402. end
  403. node.digiline = {
  404. effector = {
  405. action = function(pos, node, channel, msg)
  406. local meta = minetest.get_meta(pos)
  407. local setchan = meta:get_string("channel")
  408. if setchan ~= channel then return end
  409. punch_filter(data, pos, node, msg)
  410. end,
  411. },
  412. }
  413. else
  414. node.on_receive_fields = function(pos, formname, fields, sender)
  415. if not pipeworks.may_configure(pos, sender) then return end
  416. fs_helpers.on_receive_fields(pos, fields)
  417. local meta = minetest.get_meta(pos)
  418. meta:set_int("slotseq_index", 1)
  419. set_filter_formspec(data, meta)
  420. set_filter_infotext(data, meta)
  421. end
  422. node.mesecons = {
  423. effector = {
  424. action_on = function(pos, node)
  425. punch_filter(data, pos, node)
  426. end,
  427. },
  428. }
  429. node.on_punch = function (pos, node, puncher)
  430. punch_filter(data, pos, node)
  431. end
  432. end
  433. minetest.register_node("pipeworks:"..data.name, node)
  434. end
  435. minetest.register_craft( {
  436. output = "pipeworks:filter 2",
  437. recipe = {
  438. { "default:steel_ingot", "default:steel_ingot", "homedecor:plastic_sheeting" },
  439. { "group:stick", "default:mese_crystal", "homedecor:plastic_sheeting" },
  440. { "default:steel_ingot", "default:steel_ingot", "homedecor:plastic_sheeting" }
  441. },
  442. })
  443. minetest.register_craft( {
  444. output = "pipeworks:mese_filter 2",
  445. recipe = {
  446. { "default:steel_ingot", "default:steel_ingot", "homedecor:plastic_sheeting" },
  447. { "group:stick", "default:mese", "homedecor:plastic_sheeting" },
  448. { "default:steel_ingot", "default:steel_ingot", "homedecor:plastic_sheeting" }
  449. },
  450. })
  451. if minetest.get_modpath("digilines") then
  452. minetest.register_craft( {
  453. output = "pipeworks:digiline_filter 2",
  454. recipe = {
  455. { "default:steel_ingot", "default:steel_ingot", "homedecor:plastic_sheeting" },
  456. { "group:stick", "digilines:wire_std_00000000", "homedecor:plastic_sheeting" },
  457. { "default:steel_ingot", "default:steel_ingot", "homedecor:plastic_sheeting" }
  458. },
  459. })
  460. end
  461. --[[
  462. In the past the filter-injectors had real items in their inventories. This code
  463. puts them to the input to the filter-injector if possible. Else the items are
  464. dropped.
  465. ]]
  466. local function put_to_inputinv(pos, node, filtmeta, list)
  467. local dir = pipeworks.facedir_to_right_dir(node.param2)
  468. local frompos = vector.subtract(pos, dir)
  469. local fromnode = minetest.get_node(frompos)
  470. local fromdef = minetest.registered_nodes[fromnode.name]
  471. if not fromdef or not fromdef.tube then
  472. return
  473. end
  474. local fromtube = fromdef.tube
  475. local frominv
  476. if fromtube.return_input_invref then
  477. local owner = filtmeta:get_string("owner")
  478. frominv = fromtube.return_input_invref(frompos, fromnode, dir, owner)
  479. if not frominv then
  480. return
  481. end
  482. else
  483. frominv = minetest.get_meta(frompos):get_inventory()
  484. end
  485. local listname = type(fromtube.input_inventory) == "table" and
  486. fromtube.input_inventory[1] or fromtube.input_inventory
  487. if not listname then
  488. return
  489. end
  490. for i = 1, #list do
  491. local item = list[i]
  492. if not item:is_empty() then
  493. local leftover = frominv:add_item(listname, item)
  494. if not leftover:is_empty() then
  495. minetest.add_item(pos, leftover)
  496. end
  497. end
  498. end
  499. return true
  500. end
  501. minetest.register_lbm({
  502. label = "Give back items of old filters that had real inventories",
  503. name = "pipeworks:give_back_old_filter_items",
  504. nodenames = {"pipeworks:filter", "pipeworks:mese_filter"},
  505. run_at_every_load = false,
  506. action = function(pos, node)
  507. local meta = minetest.get_meta(pos)
  508. local list = meta:get_inventory():get_list("main")
  509. if put_to_inputinv(pos, node, meta, list) then
  510. return
  511. end
  512. pos.y = pos.y + 1
  513. for i = 1, #list do
  514. local item = list[i]
  515. if not item:is_empty() then
  516. minetest.add_item(pos, item)
  517. end
  518. end
  519. end,
  520. })