multifurnace.lua 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517
  1. local MP = minetest.get_modpath(minetest.get_current_modname())
  2. local S, NS = dofile(MP.."/intllib.lua")
  3. -- multifurnace_def can have the following:
  4. --{
  5. -- show_guides = true or false,
  6. -- alphabetize_items = true or false,
  7. -- description = string,
  8. -- hopper_node_name = string,
  9. -- enable_pipeworks = true or false,
  10. -- protect_inventory = true or false
  11. -- crafting_time_multiplier = function(pos, recipe)
  12. -- active_node = string,
  13. -- lock_in_mode = "count" | "endless",
  14. -- append_to_formspec = string,
  15. --}
  16. local modpath_default = minetest.get_modpath("default")
  17. simplecrafting_lib.generate_multifurnace_functions = function(craft_type, fuel_type, multifurnace_def)
  18. if multifurnace_def == nil then
  19. multifurnace_def = {}
  20. end
  21. -- Hopper compatibility
  22. if multifurnace_def.hopper_node_name and minetest.get_modpath("hopper") and hopper ~= nil and hopper.add_container ~= nil then
  23. hopper:add_container({
  24. {"top", multifurnace_def.hopper_node_name, "output"},
  25. {"bottom", multifurnace_def.hopper_node_name, "input"},
  26. {"side", multifurnace_def.hopper_node_name, "fuel"},
  27. })
  28. if multifurnace_def.active_node then
  29. hopper:add_container({
  30. {"top", multifurnace_def.active_node, "output"},
  31. {"bottom", multifurnace_def.active_node, "input"},
  32. {"side", multifurnace_def.active_node, "fuel"},
  33. })
  34. end
  35. end
  36. local function get_count_mode(meta)
  37. if multifurnace_def.lock_in_mode == "endless" then
  38. return false
  39. elseif multifurnace_def.lock_in_mode == "count" then
  40. return true
  41. else
  42. return meta:get_string("count_mode") == "true"
  43. end
  44. end
  45. local function refresh_formspec(pos)
  46. local meta = minetest.get_meta(pos)
  47. local cook_time = meta:get_float("cook_time") or 0.0
  48. local total_cook_time = meta:get_float("total_cook_time") or 0.0
  49. local burn_time = meta:get_float("burn_time") or 0.0
  50. local total_burn_time = meta:get_float("total_burn_time") or 0.0
  51. local product_count = meta:get_int("product_count") or 0
  52. local count_mode = get_count_mode(meta)
  53. local item_percent
  54. if total_cook_time > 0 then item_percent = math.floor((math.min(cook_time, total_cook_time) / total_cook_time) * 100) else item_percent = 0 end
  55. local burn_percent
  56. if total_burn_time > 0 then burn_percent = math.floor((math.min(burn_time, total_burn_time) / total_burn_time) * 100) else burn_percent = 0 end
  57. local inventory = {
  58. "size[10,9.2]",
  59. "list[context;input;0,0.25;4,2;]",
  60. "list[context;fuel;0,2.75;4,2]",
  61. "image[4.5,0.7;1,1;gui_furnace_arrow_bg.png^[lowpart:"..(item_percent)..":gui_furnace_arrow_fg.png^[transformR270]",
  62. "image[4.5,3.3;1,1;default_furnace_fire_bg.png^[lowpart:"..(burn_percent)..":default_furnace_fire_fg.png]",
  63. "list[context;output;6,0.25;4,2;]",
  64. "list[current_player;main;1,5;8,1;0]",
  65. "list[current_player;main;1,6.2;8,3;8]",
  66. "listring[context;output]",
  67. "listring[current_player;main]",
  68. "listring[context;input]",
  69. "listring[current_player;main]",
  70. "listring[context;fuel]",
  71. "listring[current_player;main]",
  72. }
  73. if count_mode then
  74. inventory[#inventory+1] = "field[4.8,1.7;1,0.25;product_count;;"..product_count.."]"
  75. inventory[#inventory+1] = "field_close_on_enter[product_count;false]"
  76. if multifurnace_def.lock_in_mode == nil then
  77. inventory[#inventory+1] = "button[9,7.5;1,0.75;count_mode;"..S("Endless\nOutput").."]"
  78. end
  79. elseif multifurnace_def.lock_in_mode == nil then
  80. inventory[#inventory+1] = "button[9,7.5;1,0.75;count_mode;"..S("Counted\nOutput").."]"
  81. end
  82. if multifurnace_def.description then
  83. inventory[#inventory+1] = "label[4.5,0;"..multifurnace_def.description.."]"
  84. end
  85. if modpath_default then
  86. inventory[#inventory+1] = default.gui_bg
  87. inventory[#inventory+1] = default.gui_bg_img
  88. inventory[#inventory+1] = default.gui_slots
  89. end
  90. local target = meta:get_string("target_item")
  91. if target ~= "" then
  92. inventory[#inventory+1] = "item_image_button[4.5,2;1,1;" .. target .. ";target;]"
  93. else
  94. inventory[#inventory+1] = "item_image_button[4.5,2;1,1;;;]"
  95. end
  96. local product_x_dim = 4
  97. local product_y_dim = 2
  98. local corner_x = 6
  99. local corner_y = 2.75
  100. local product_count = product_x_dim * product_y_dim
  101. local product_list = minetest.deserialize(meta:get_string("product_list"))
  102. local product_page = meta:get_int("product_page") or 0
  103. local max_pages = math.floor((#product_list - 1) / product_count)
  104. if product_page > max_pages then
  105. product_page = max_pages
  106. meta:set_int("product_page", product_page)
  107. elseif product_page < 0 then
  108. product_page = 0
  109. meta:set_int("product_page", product_page)
  110. end
  111. local pages = false
  112. if product_page > 0 then
  113. inventory[#inventory+1] = "button[6.0,2.5;1,0.1;prev_page;<<]"
  114. end
  115. if product_page < max_pages then
  116. inventory[#inventory+1] = "button[9.0,2.5;1,0.1;next_page;>>]"
  117. end
  118. if pages then
  119. inventory[#inventory+1] = "label[9.3,2.5;" .. S("Page @1", tostring(product_page)) .. "]"
  120. end
  121. for i = 1, product_count do
  122. local current_item = product_list[i + product_page*product_count]
  123. if current_item then
  124. inventory[#inventory+1] = "item_image_button[" ..
  125. corner_x + (i-1)%product_x_dim .. "," .. corner_y + math.floor((i-1)/product_x_dim) ..
  126. ";1,1;" .. current_item.name .. ";product_".. i + product_page*product_count ..
  127. ";\n\n " .. current_item.count .. "]"
  128. else
  129. inventory[#inventory+1] = "item_image_button[" ..
  130. corner_x + (i-1)%product_x_dim .. "," .. corner_y + math.floor((i-1)/product_x_dim) ..
  131. ";1,1;;empty;]"
  132. end
  133. end
  134. if multifurnace_def.show_guides then
  135. inventory[#inventory+1] = "button[9.0,8.3;1,0.75;show_guide;"..S("Show\nGuide").."]"
  136. end
  137. if multifurnace_def.append_to_formspec then
  138. inventory[#inventory+1] = table_def.append_to_formspec
  139. end
  140. meta:set_string("formspec", table.concat(inventory))
  141. meta:set_string("infotext", multifurnace_def.get_infotext(pos))
  142. end
  143. local function refresh_products(meta)
  144. local inv = meta:get_inventory()
  145. local craftable = simplecrafting_lib.get_craftable_items(craft_type, inv:get_list("input"), false, multifurnace_def.alphabetize_items)
  146. local product_list = {}
  147. for _, craft in pairs(craftable) do
  148. table.insert(product_list, craft:to_table())
  149. end
  150. meta:set_string("product_list", minetest.serialize(product_list))
  151. end
  152. local function on_timer(pos, elapsed)
  153. local meta = minetest.get_meta(pos)
  154. local inv = meta:get_inventory()
  155. local cook_time = meta:get_float("cook_time") or 0.0
  156. local total_cook_time = meta:get_float("total_cook_time") or 0.0
  157. local burn_time = meta:get_float("burn_time") or 0.0
  158. local total_burn_time = meta:get_float("total_burn_time") or 0.0
  159. local product_count = meta:get_int("product_count") or 0
  160. local count_mode = get_count_mode(meta)
  161. local target_item = meta:get_string("target_item")
  162. local recipe
  163. local room_for_items = false
  164. local output
  165. if target_item ~= "" then
  166. recipe = simplecrafting_lib.get_crafting_result(craft_type, inv:get_list("input"), ItemStack({name=target_item, count=1}))
  167. if recipe then
  168. output = simplecrafting_lib.count_list_add({[recipe.output:get_name()]=recipe.output:get_count()}, recipe.returns)
  169. room_for_items = simplecrafting_lib.room_for_items(inv, "output", output)
  170. total_cook_time = recipe.input["simplecrafting_lib:heat"] or 1
  171. if multifurnace_def.crafting_time_multiplier then
  172. total_cook_time = total_cook_time * multifurnace_def.crafting_time_multiplier(pos, recipe)
  173. end
  174. end
  175. end
  176. cook_time = cook_time + elapsed
  177. burn_time = burn_time - elapsed
  178. if recipe == nil or not room_for_items or (product_count <= 0 and count_mode) then
  179. -- we're not cooking anything.
  180. cook_time = 0.0
  181. if burn_time < 0 then burn_time = 0 end
  182. minetest.get_node_timer(pos):stop()
  183. if multifurnace_def.active_node then -- only bother doing this if there's an active node
  184. local this_node = minetest.get_node(pos)
  185. this_node.name = meta:get_string("inactive_node")
  186. minetest.swap_node(pos, this_node)
  187. end
  188. else
  189. while true do
  190. if burn_time < 0 then
  191. -- burn some fuel, if possible.
  192. local fuel_recipes = simplecrafting_lib.get_fuels(fuel_type, inv:get_list("fuel"))
  193. local longest_burning
  194. for _, fuel_recipe in pairs(fuel_recipes) do
  195. local recipe_burntime = 0
  196. if fuel_recipe.output and fuel_recipe.output:get_name() == "simplecrafting_lib:heat" then
  197. recipe_burntime = fuel_recipe.output:get_count()
  198. end
  199. if longest_burning == nil or longest_burning.output:get_count() < recipe_burntime then
  200. longest_burning = fuel_recipe
  201. end
  202. end
  203. if longest_burning then
  204. total_burn_time = longest_burning.output:get_count()
  205. burn_time = burn_time + total_burn_time
  206. local success = true
  207. if longest_burning.returns then
  208. success = simplecrafting_lib.add_items_if_room(inv, "output", longest_burning.returns) and
  209. simplecrafting_lib.room_for_items(inv, "output", output)
  210. end
  211. if success then
  212. for item, count in pairs(longest_burning.input) do
  213. inv:remove_item("fuel", ItemStack({name = item, count = count}))
  214. end
  215. else
  216. --no room for both output and fuel reside
  217. cook_time = 0
  218. if burn_time < 0 then burn_time = 0 end
  219. break
  220. end
  221. else
  222. --out of fuel
  223. cook_time = 0
  224. if burn_time < 0 then burn_time = 0 end
  225. break
  226. end
  227. elseif cook_time >= total_cook_time then
  228. -- produce product
  229. if count_mode then
  230. product_count = product_count - recipe.output:get_count()
  231. meta:set_int("product_count", math.max(product_count, 0))
  232. end
  233. simplecrafting_lib.add_items(inv, "output", output)
  234. simplecrafting_lib.remove_items(inv, "input", recipe.input)
  235. simplecrafting_lib.execute_post_craft(craft_type, recipe, recipe.output, inv, "input", inv, "output")
  236. cook_time = cook_time - total_cook_time
  237. minetest.get_node_timer(pos):start(1)
  238. break
  239. else
  240. -- if we get here there's burning fuel but cook time hasn't reached recipe time yet.
  241. -- Do nothing this round.
  242. if multifurnace_def.active_node then
  243. local this_node = minetest.get_node(pos)
  244. this_node.name = multifurnace_def.active_node
  245. minetest.swap_node(pos, this_node)
  246. end
  247. minetest.get_node_timer(pos):start(1)
  248. break
  249. end
  250. end
  251. end
  252. meta:set_float("burn_time", burn_time)
  253. meta:set_float("total_burn_time", total_burn_time)
  254. meta:set_float("cook_time", cook_time)
  255. meta:set_float("total_cook_time", total_cook_time)
  256. refresh_formspec(pos)
  257. end
  258. local on_construct = function(pos)
  259. local meta = minetest.get_meta(pos)
  260. local inv = meta:get_inventory()
  261. inv:set_size("input", 2*4) -- materials that can be processed to outputs
  262. inv:set_size("fuel", 2*4) -- materials that can be burned for fuel
  263. inv:set_size("output", 2*4) -- holds output product
  264. meta:set_string("product_list", minetest.serialize({}))
  265. meta:set_string("target", "")
  266. if multifurnace_def.active_node then
  267. meta:set_string("inactive_node", minetest.get_node(pos).name) -- we only need this if there's an active node defined
  268. end
  269. refresh_formspec(pos)
  270. end
  271. local _pipeworks_override_player = {} -- Horrible hack. Pipeworks gets to insert stuff regardless of protection.
  272. local function allow_metadata_inventory_put(pos, listname, index, stack, player)
  273. if multifurnace_def.protect_inventory and
  274. player ~= _pipeworks_override_player and
  275. minetest.is_protected(pos, player:get_player_name())
  276. and not minetest.check_player_privs(player:get_name(), "protection_bypass") then
  277. return 0
  278. end
  279. if listname == "input" then
  280. if simplecrafting_lib.is_possible_input(craft_type, stack:get_name()) then
  281. return stack:get_count()
  282. else
  283. return 0
  284. end
  285. elseif listname == "fuel" then
  286. if simplecrafting_lib.is_fuel(fuel_type, stack:get_name()) then
  287. return stack:get_count()
  288. else
  289. return 0
  290. end
  291. elseif listname == "output" then
  292. -- not allowed to put items into the output
  293. return 0
  294. end
  295. return stack:get_count()
  296. end
  297. -- Pipeworks compatibility
  298. local tube = nil
  299. if multifurnace_def.enable_pipeworks and minetest.get_modpath("pipeworks") then
  300. tube = {
  301. insert_object = function(pos, node, stack, direction)
  302. local meta = minetest.get_meta(pos)
  303. local inv = meta:get_inventory()
  304. local timer = minetest.get_node_timer(pos)
  305. if not timer:is_started() then
  306. timer:start(1.0)
  307. end
  308. if direction.y == 1 then
  309. return inv:add_item("fuel", stack)
  310. else
  311. return inv:add_item("input", stack)
  312. end
  313. end,
  314. can_insert = function(pos,node,stack,direction)
  315. local meta = minetest.get_meta(pos)
  316. local inv = meta:get_inventory()
  317. if direction.y == 1 then
  318. return allow_metadata_inventory_put(pos, "fuel", 1, stack, _pipeworks_override_player) > 0
  319. and inv:room_for_item("fuel", stack)
  320. else
  321. return allow_metadata_inventory_put(pos, "input", 1, stack, _pipeworks_override_player) > 0
  322. and inv:room_for_item("input", stack)
  323. end
  324. end,
  325. input_inventory = "output",
  326. connect_sides = {left = 1, right = 1, back = 1, front = 1, bottom = 1, top = 1}
  327. }
  328. end
  329. local function allow_metadata_inventory_take(pos, listname, index, stack, player)
  330. if multifurnace_def.protect_inventory and
  331. minetest.is_protected(pos, player:get_player_name())
  332. and not minetest.check_player_privs(player:get_name(), "protection_bypass") then
  333. return 0
  334. end
  335. return stack:get_count()
  336. end
  337. local function allow_metadata_inventory_move(pos, from_list, from_index, to_list, to_index, count, player)
  338. local meta = minetest.get_meta(pos)
  339. local inv = meta:get_inventory()
  340. local stack = inv:get_stack(from_list, from_index)
  341. return math.min(allow_metadata_inventory_put(pos, to_list, to_index, stack, player),
  342. allow_metadata_inventory_take(pos, from_list, from_index, stack, player))
  343. end
  344. local on_metadata_inventory_move = function(pos, flist, fi, tlist, ti, no, player)
  345. local meta = minetest.get_meta(pos)
  346. if tlist == "input" then
  347. refresh_products(meta)
  348. end
  349. on_timer(pos, 0)
  350. end
  351. local on_metadata_inventory_take = function(pos, lname, i, stack, player)
  352. local meta = minetest.get_meta(pos)
  353. if lname == "input" then
  354. refresh_products(meta)
  355. refresh_formspec(pos)
  356. elseif lname == "output" then
  357. on_timer(pos, 0)
  358. end
  359. end
  360. local on_metadata_inventory_put = function(pos, lname, i, stack, player)
  361. local meta = minetest.get_meta(pos)
  362. if lname == "input" then
  363. refresh_products(meta)
  364. end
  365. on_timer(pos, 0)
  366. end
  367. local can_dig = function(pos, player)
  368. local meta = minetest.get_meta(pos)
  369. local inv = meta:get_inventory()
  370. return inv:is_empty("output") and inv:is_empty("fuel") and inv:is_empty("input")
  371. end
  372. local on_receive_fields = function(pos, formname, fields, sender)
  373. local meta = minetest.get_meta(pos)
  374. local product_list = minetest.deserialize(meta:get_string("product_list"))
  375. local refresh = false
  376. for field, _ in pairs(fields) do
  377. if field == "target" then
  378. meta:set_string("target_item", "")
  379. meta:set_float("cook_time", 0.0)
  380. meta:set_float("total_cook_time", 0.0)
  381. elseif string.sub(field, 1, 8) == "product_" then
  382. local product = product_list[tonumber(string.sub(field, 9))]
  383. if product then
  384. local new_target = product.name
  385. meta:set_string("target_item", new_target)
  386. meta:set_float("cook_time", 0.0)
  387. meta:set_string("last_selector_name", sender:get_player_name())
  388. refresh = true
  389. end
  390. end
  391. end
  392. if fields.show_guide and multifurnace_def.show_guides then
  393. simplecrafting_lib.show_crafting_guide(craft_type, sender)
  394. end
  395. if fields.product_count ~= nil then
  396. meta:set_int("product_count", math.max((tonumber(fields.product_count) or 0), 0))
  397. refresh = true
  398. end
  399. if fields.count_mode then
  400. if meta:get_string("count_mode") == "" then
  401. meta:set_string("count_mode", "true")
  402. else
  403. meta:set_string("count_mode", "")
  404. end
  405. refresh = true
  406. end
  407. if fields.next_page then
  408. meta:set_int("product_page", meta:get_int("product_page") + 1)
  409. refresh = true
  410. elseif fields.prev_page then
  411. meta:set_int("product_page", meta:get_int("product_page") - 1)
  412. refresh = true
  413. end
  414. if refresh then
  415. refresh_formspec(pos)
  416. end
  417. on_timer(pos, 0)
  418. end
  419. local function default_infotext(pos)
  420. local infotext = ""
  421. local meta = minetest.get_meta(pos)
  422. if multifurnace_def.description then
  423. infotext = infotext .. multifurnace_def.description
  424. end
  425. local target = meta:get_string("target_item")
  426. if target ~= "" then
  427. local craft_time = meta:get_float("cook_time") or 0.0
  428. local total_craft_time = meta:get_float("total_cook_time") or 0.0
  429. local item_percent
  430. if total_craft_time > 0 then item_percent = math.floor((math.min(craft_time, total_craft_time) / total_craft_time) * 100) else item_percent = 0 end
  431. infotext = infotext .. "\n" .. S("@1% done crafting @2", item_percent, minetest.registered_items[target].description or target)
  432. if get_count_mode(meta) then
  433. local product_count = meta:get_int("product_count") or 0
  434. infotext = infotext .. "\n" .. S("@1 remaining to do", product_count)
  435. end
  436. end
  437. return infotext
  438. end
  439. multifurnace_def.get_infotext = multifurnace_def.get_infotext or default_infotext
  440. return {
  441. allow_metadata_inventory_move = allow_metadata_inventory_move,
  442. allow_metadata_inventory_put = allow_metadata_inventory_put,
  443. allow_metadata_inventory_take = allow_metadata_inventory_take,
  444. can_dig = can_dig,
  445. on_construct = on_construct,
  446. on_metadata_inventory_move = on_metadata_inventory_move,
  447. on_metadata_inventory_put = on_metadata_inventory_put,
  448. on_metadata_inventory_take = on_metadata_inventory_take,
  449. on_receive_fields = on_receive_fields,
  450. on_timer = on_timer,
  451. tube = tube,
  452. }
  453. end