More Blocks
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.

448 lines
14KB

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