filter-injector.lua 19 KB

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