circular_saw.lua 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467
  1. --[[
  2. More Blocks: circular saw
  3. Copyright © 2011-2020 Hugo Locurcio, Sokomine and contributors.
  4. Licensed under the zlib license. See LICENSE.md for more information.
  5. --]]
  6. local S = moreblocks.S
  7. local F = minetest.formspec_escape
  8. circular_saw = {}
  9. circular_saw.known_stairs = setmetatable({}, {
  10. __newindex = function(k, v)
  11. local modname = minetest.get_current_modname()
  12. print(("WARNING: mod %s tried to add node %s to the circular saw"
  13. .. " manually."):format(modname, v))
  14. end,
  15. })
  16. -- This is populated by stairsplus:register_all:
  17. circular_saw.known_nodes = {}
  18. -- How many microblocks does this shape at the output inventory cost:
  19. -- It may cause slight loss, but no gain.
  20. circular_saw.cost_in_microblocks = {
  21. 1, 1, 1, 1, 1, 1, 1, 2,
  22. 2, 3, 2, 4, 2, 4, 5, 6,
  23. 7, 1, 1, 2, 4, 6, 7, 8,
  24. 1, 2, 2, 3, 1, 1, 2, 4,
  25. 4, 2, 6, 7, 3, 7, 7, 4,
  26. 8, 3, 2, 6, 2, 1, 3, 4
  27. }
  28. circular_saw.names = {
  29. {"micro", "_1"},
  30. {"panel", "_1"},
  31. {"micro", "_2"},
  32. {"panel", "_2"},
  33. {"micro", "_4"},
  34. {"panel", "_4"},
  35. {"micro", ""},
  36. {"panel", ""},
  37. {"micro", "_12"},
  38. {"panel", "_12"},
  39. {"micro", "_14"},
  40. {"panel", "_14"},
  41. {"micro", "_15"},
  42. {"panel", "_15"},
  43. {"stair", "_outer"},
  44. {"stair", ""},
  45. {"stair", "_inner"},
  46. {"slab", "_1"},
  47. {"slab", "_2"},
  48. {"slab", "_quarter"},
  49. {"slab", ""},
  50. {"slab", "_three_quarter"},
  51. {"slab", "_14"},
  52. {"slab", "_15"},
  53. {"slab", "_two_sides"},
  54. {"slab", "_three_sides"},
  55. {"slab", "_three_sides_u"},
  56. {"stair", "_half"},
  57. {"stair", "_alt_1"},
  58. {"stair", "_alt_2"},
  59. {"stair", "_alt_4"},
  60. {"stair", "_alt"},
  61. {"slope", ""},
  62. {"slope", "_half"},
  63. {"slope", "_half_raised"},
  64. {"slope", "_inner"},
  65. {"slope", "_inner_half"},
  66. {"slope", "_inner_half_raised"},
  67. {"slope", "_inner_cut"},
  68. {"slope", "_inner_cut_half"},
  69. {"slope", "_inner_cut_half_raised"},
  70. {"slope", "_outer"},
  71. {"slope", "_outer_half"},
  72. {"slope", "_outer_half_raised"},
  73. {"slope", "_outer_cut"},
  74. {"slope", "_outer_cut_half"},
  75. {"slope", "_outer_cut_half_raised"},
  76. {"slope", "_cut"},
  77. }
  78. function circular_saw:get_cost(inv, stackname)
  79. for i, item in pairs(inv:get_list("output")) do
  80. if item:get_name() == stackname then
  81. return circular_saw.cost_in_microblocks[i]
  82. end
  83. end
  84. end
  85. function circular_saw:get_output_inv(modname, material, amount, max)
  86. if (not max or max < 1 or max > 99) then max = 99 end
  87. local list = {}
  88. local pos = #list
  89. -- If there is nothing inside, display empty inventory:
  90. if amount < 1 then
  91. return list
  92. end
  93. for i = 1, #circular_saw.names do
  94. local t = circular_saw.names[i]
  95. local cost = circular_saw.cost_in_microblocks[i]
  96. local balance = math.min(math.floor(amount/cost), max)
  97. local nodename = modname .. ":" .. t[1] .. "_" .. material .. t[2]
  98. if minetest.registered_nodes[nodename] then
  99. pos = pos + 1
  100. list[pos] = nodename .. " " .. balance
  101. end
  102. end
  103. return list
  104. end
  105. -- Reset empty circular_saw after last full block has been taken out
  106. -- (or the circular_saw has been placed the first time)
  107. -- Note: max_offered is not reset:
  108. function circular_saw:reset(pos)
  109. local meta = minetest.get_meta(pos)
  110. local inv = meta:get_inventory()
  111. local owned_by = meta:get_string("owner")
  112. if owned_by and owned_by ~= "" then
  113. owned_by = (" (%s)"):format(S("owned by @1", meta:get_string("owner")))
  114. else
  115. owned_by = ""
  116. end
  117. inv:set_list("input", {})
  118. inv:set_list("micro", {})
  119. inv:set_list("output", {})
  120. meta:set_int("anz", 0)
  121. meta:set_string("infotext", "Table Saw is empty" .. owned_by)
  122. end
  123. -- Player has taken something out of the box or placed something inside
  124. -- that amounts to count microblocks:
  125. function circular_saw:update_inventory(pos, amount)
  126. local meta = minetest.get_meta(pos)
  127. local inv = meta:get_inventory()
  128. amount = meta:get_int("anz") + amount
  129. -- The material is recycled automatically.
  130. inv:set_list("recycle", {})
  131. if amount < 1 then -- If the last block is taken out.
  132. self:reset(pos)
  133. return
  134. end
  135. local stack = inv:get_stack("input", 1)
  136. -- At least one "normal" block is necessary to see what kind of stairs are requested.
  137. if stack:is_empty() then
  138. -- Any microblocks not taken out yet are now lost.
  139. -- (covers material loss in the machine)
  140. self:reset(pos)
  141. return
  142. end
  143. local node_name = stack:get_name() or ""
  144. local node_def = stack:get_definition()
  145. local name_parts = circular_saw.known_nodes[node_name] or ""
  146. local modname = name_parts[1] or ""
  147. local material = name_parts[2] or ""
  148. local owned_by = meta:get_string("owner")
  149. if owned_by and owned_by ~= "" then
  150. owned_by = (" (%s)"):format(S("owned by @1", meta:get_string("owner")))
  151. else
  152. owned_by = ""
  153. end
  154. inv:set_list("input", { -- Display as many full blocks as possible:
  155. node_name.. " " .. math.floor(amount / 8)
  156. })
  157. -- The stairnodes made of default nodes use moreblocks namespace, other mods keep own:
  158. if modname == "default" then
  159. modname = "moreblocks"
  160. end
  161. -- print("circular_saw set to " .. modname .. " : "
  162. -- .. material .. " with " .. (amount) .. " microblocks.")
  163. -- 0-7 microblocks may remain left-over:
  164. inv:set_list("micro", {
  165. modname .. ":micro_" .. material .. " " .. (amount % 8)
  166. })
  167. -- Display:
  168. inv:set_list("output",
  169. self:get_output_inv(modname, material, amount,
  170. meta:get_int("max_offered")))
  171. -- Store how many microblocks are available:
  172. meta:set_int("anz", amount)
  173. meta:set_string("infotext",
  174. S("Table Saw is working on @1",
  175. node_def and node_def.description or material
  176. ) .. owned_by
  177. )
  178. end
  179. -- The amount of items offered per shape can be configured:
  180. function circular_saw.on_receive_fields(pos, formname, fields, sender)
  181. local meta = minetest.get_meta(pos)
  182. local max = tonumber(fields.max_offered)
  183. if max and max > 0 then
  184. meta:set_string("max_offered", max)
  185. -- Update to show the correct number of items:
  186. circular_saw:update_inventory(pos, 0)
  187. end
  188. end
  189. -- Moving the inventory of the circular_saw around is not allowed because it
  190. -- is a fictional inventory. Moving inventory around would be rather
  191. -- impractical and make things more difficult to calculate:
  192. function circular_saw.allow_metadata_inventory_move(
  193. pos, from_list, from_index, to_list, to_index, count, player)
  194. return 0
  195. end
  196. -- Only input- and recycle-slot are intended as input slots:
  197. function circular_saw.allow_metadata_inventory_put(
  198. pos, listname, index, stack, player)
  199. -- The player is not allowed to put something in there:
  200. if listname == "output" or listname == "micro" then
  201. return 0
  202. end
  203. local meta = minetest.get_meta(pos)
  204. local inv = meta:get_inventory()
  205. local stackname = stack:get_name()
  206. local count = stack:get_count()
  207. -- Only allow those items that are offered in the output inventory to be recycled:
  208. if listname == "recycle" then
  209. if not inv:contains_item("output", stackname) then
  210. return 0
  211. end
  212. local stackmax = stack:get_stack_max()
  213. local instack = inv:get_stack("input", 1)
  214. local microstack = inv:get_stack("micro", 1)
  215. local incount = instack:get_count()
  216. local incost = (incount * 8) + microstack:get_count()
  217. local maxcost = (stackmax * 8) + 7
  218. local cost = circular_saw:get_cost(inv, stackname)
  219. if (incost + cost) > maxcost then
  220. return math.max((maxcost - incost) / cost, 0)
  221. end
  222. return count
  223. end
  224. -- Only accept certain blocks as input which are known to be craftable into stairs:
  225. if listname == "input" then
  226. if not inv:is_empty("input") then
  227. if inv:get_stack("input", index):get_name() ~= stackname then
  228. return 0
  229. end
  230. end
  231. if not inv:is_empty("micro") then
  232. local microstackname = inv:get_stack("micro", 1):get_name():gsub("^.+:micro_", "", 1)
  233. local cutstackname = stackname:gsub("^.+:", "", 1)
  234. if microstackname ~= cutstackname then
  235. return 0
  236. end
  237. end
  238. for name, t in pairs(circular_saw.known_nodes) do
  239. if name == stackname and inv:room_for_item("input", stack) then
  240. return count
  241. end
  242. end
  243. return 0
  244. end
  245. end
  246. -- Taking is allowed from all slots (even the internal microblock slot).
  247. -- Putting something in is slightly more complicated than taking anything
  248. -- because we have to make sure it is of a suitable material:
  249. function circular_saw.on_metadata_inventory_put(
  250. pos, listname, index, stack, player)
  251. -- We need to find out if the circular_saw is already set to a
  252. -- specific material or not:
  253. local meta = minetest.get_meta(pos)
  254. local inv = meta:get_inventory()
  255. local stackname = stack:get_name()
  256. local count = stack:get_count()
  257. -- Putting something into the input slot is only possible if that had
  258. -- been empty before or did contain something of the same material:
  259. if listname == "input" then
  260. -- Each new block is worth 8 microblocks:
  261. circular_saw:update_inventory(pos, 8 * count)
  262. elseif listname == "recycle" then
  263. -- Lets look which shape this represents:
  264. local cost = circular_saw:get_cost(inv, stackname)
  265. local input_stack = inv:get_stack("input", 1)
  266. -- check if this would not exceed input itemstack max_stacks
  267. if input_stack:get_count() + ((cost * count) / 8) <= input_stack:get_stack_max() then
  268. circular_saw:update_inventory(pos, cost * count)
  269. end
  270. end
  271. end
  272. function circular_saw.allow_metadata_inventory_take(pos, listname, index, stack, player)
  273. local meta = minetest.get_meta(pos)
  274. local inv = meta:get_inventory()
  275. local input_stack = inv:get_stack(listname, index)
  276. local player_inv = player:get_inventory()
  277. if not player_inv:room_for_item("main", input_stack) then
  278. return 0
  279. else return stack:get_count()
  280. end
  281. end
  282. function circular_saw.on_metadata_inventory_take(
  283. pos, listname, index, stack, player)
  284. -- Prevent (inbuilt) swapping between inventories with different blocks
  285. -- corrupting player inventory or Saw with 'unknown' items.
  286. local meta = minetest.get_meta(pos)
  287. local inv = meta:get_inventory()
  288. local input_stack = inv:get_stack(listname, index)
  289. if not input_stack:is_empty() and input_stack:get_name()~=stack:get_name() then
  290. local player_inv = player:get_inventory()
  291. if player_inv:room_for_item("main", input_stack) then
  292. player_inv:add_item("main", input_stack)
  293. end
  294. circular_saw:reset(pos)
  295. return
  296. end
  297. -- If it is one of the offered stairs: find out how many
  298. -- microblocks have to be subtracted:
  299. if listname == "output" then
  300. -- We do know how much each block at each position costs:
  301. local cost = circular_saw.cost_in_microblocks[index]
  302. * stack:get_count()
  303. circular_saw:update_inventory(pos, -cost)
  304. elseif listname == "micro" then
  305. -- Each microblock costs 1 microblock:
  306. circular_saw:update_inventory(pos, -stack:get_count())
  307. elseif listname == "input" then
  308. -- Each normal (= full) block taken costs 8 microblocks:
  309. circular_saw:update_inventory(pos, 8 * -stack:get_count())
  310. end
  311. -- The recycle field plays no role here since it is processed immediately.
  312. end
  313. function circular_saw.on_construct(pos)
  314. local meta = minetest.get_meta(pos)
  315. local fancy_inv = default.gui_bg..default.gui_bg_img..default.gui_slots
  316. meta:set_string(
  317. "formspec", "size[11,10]"..fancy_inv..
  318. "label[0,0;" ..F(S("Input\nmaterial")).. "]" ..
  319. "list[current_name;input;1.5,0;1,1;]" ..
  320. "label[0,1;" ..F(S("Left-over")).. "]" ..
  321. "list[current_name;micro;1.5,1;1,1;]" ..
  322. "label[0,2;" ..F(S("Recycle\noutput")).. "]" ..
  323. "list[current_name;recycle;1.5,2;1,1;]" ..
  324. "field[0.3,3.5;1,1;max_offered;" ..F(S("Max")).. ":;${max_offered}]" ..
  325. "button[1,3.2;1,1;Set;" ..F(S("Set")).. "]" ..
  326. "list[current_name;output;2.8,0;8,6;]" ..
  327. "list[current_player;main;1.5,6.25;8,4;]" ..
  328. "listring[current_name;output]" ..
  329. "listring[current_player;main]" ..
  330. "listring[current_name;input]" ..
  331. "listring[current_player;main]" ..
  332. "listring[current_name;micro]" ..
  333. "listring[current_player;main]" ..
  334. "listring[current_name;recycle]" ..
  335. "listring[current_player;main]"
  336. )
  337. meta:set_int("anz", 0) -- No microblocks inside yet.
  338. meta:set_string("max_offered", 99) -- How many items of this kind are offered by default?
  339. meta:set_string("infotext", S("Table Saw is empty"))
  340. local inv = meta:get_inventory()
  341. inv:set_size("input", 1) -- Input slot for full blocks of material x.
  342. inv:set_size("micro", 1) -- Storage for 1-7 surplus microblocks.
  343. inv:set_size("recycle", 1) -- Surplus partial blocks can be placed here.
  344. inv:set_size("output", 6*8) -- 6x8 versions of stair-parts of material x.
  345. circular_saw:reset(pos)
  346. end
  347. function circular_saw.can_dig(pos,player)
  348. local meta = minetest.get_meta(pos)
  349. local inv = meta:get_inventory()
  350. if not inv:is_empty("input") or
  351. not inv:is_empty("micro") or
  352. not inv:is_empty("recycle") then
  353. return false
  354. end
  355. -- Can be dug by anyone when empty, not only by the owner:
  356. return true
  357. end
  358. minetest.register_node("moreblocks:circular_saw", {
  359. description = "Table Saw",
  360. drawtype = "nodebox",
  361. node_box = {
  362. type = "fixed",
  363. fixed = {
  364. {-0.4, -0.5, -0.4, -0.25, 0.25, -0.25}, -- Leg
  365. {0.25, -0.5, 0.25, 0.4, 0.25, 0.4}, -- Leg
  366. {-0.4, -0.5, 0.25, -0.25, 0.25, 0.4}, -- Leg
  367. {0.25, -0.5, -0.4, 0.4, 0.25, -0.25}, -- Leg
  368. {-0.5, 0.25, -0.5, 0.5, 0.375, 0.5}, -- Tabletop
  369. {-0.01, 0.4375, -0.125, 0.01, 0.5, 0.125}, -- Saw blade (top)
  370. {-0.01, 0.375, -0.1875, 0.01, 0.4375, 0.1875}, -- Saw blade (bottom)
  371. {-0.25, -0.0625, -0.25, 0.25, 0.25, 0.25}, -- Motor case
  372. },
  373. },
  374. tiles = {"moreblocks_circular_saw_top.png",
  375. "moreblocks_circular_saw_bottom.png",
  376. "moreblocks_circular_saw_side.png"},
  377. paramtype = "light",
  378. sunlight_propagates = true,
  379. paramtype2 = "facedir",
  380. groups = {choppy = 2,oddly_breakable_by_hand = 2},
  381. sounds = default.node_sound_wood_defaults(),
  382. on_construct = circular_saw.on_construct,
  383. can_dig = circular_saw.can_dig,
  384. -- Set the owner of this circular saw.
  385. after_place_node = function(pos, placer)
  386. local meta = minetest.get_meta(pos)
  387. local owner = placer and placer:get_player_name() or ""
  388. local owned_by = owner
  389. if owner ~= "" then
  390. owned_by = (" (%s)"):format(S("owned by @1", owner))
  391. end
  392. meta:set_string("owner", owner)
  393. meta:set_string("infotext", "Table Saw is empty" .. owned_by)
  394. end,
  395. -- The amount of items offered per shape can be configured:
  396. on_receive_fields = circular_saw.on_receive_fields,
  397. allow_metadata_inventory_move = circular_saw.allow_metadata_inventory_move,
  398. -- Only input- and recycle-slot are intended as input slots:
  399. allow_metadata_inventory_put = circular_saw.allow_metadata_inventory_put,
  400. allow_metadata_inventory_take = circular_saw.allow_metadata_inventory_take,
  401. -- Taking is allowed from all slots (even the internal microblock slot). Moving is forbidden.
  402. -- Putting something in is slightly more complicated than taking anything because we have to make sure it is of a suitable material:
  403. on_metadata_inventory_put = circular_saw.on_metadata_inventory_put,
  404. on_metadata_inventory_take = circular_saw.on_metadata_inventory_take,
  405. })