Fix dispensers placing useless shulker boxes
[MineClone/MineClone2.git] / mods / ITEMS / mcl_chests / init.lua
blob3bd80543d4c0475d4b7ffb72faed1f3241427394
1 local S = minetest.get_translator("mcl_chests")
2 local mod_doc = minetest.get_modpath("doc")
4 local no_rotate, simple_rotate
5 if minetest.get_modpath("screwdriver") then
6 no_rotate = screwdriver.disallow
7 simple_rotate = screwdriver.rotate_simple
8 end
10 --[[ List of open chests.
11 Key: Player name
12 Value:
13 If player is using a chest: { pos = <chest node position> }
14 Otherwise: nil ]]
15 local open_chests = {}
16 -- To be called if a player opened a chest
17 local player_chest_open = function(player, pos)
18 open_chests[player:get_player_name()] = { pos = pos }
19 end
21 -- Simple protection checking functions
22 local protection_check_move = function(pos, from_list, from_index, to_list, to_index, count, player)
23 local name = player:get_player_name()
24 if minetest.is_protected(pos, name) then
25 minetest.record_protection_violation(pos, name)
26 return 0
27 else
28 return count
29 end
30 end
31 local protection_check_put_take = function(pos, listname, index, stack, player)
32 local name = player:get_player_name()
33 if minetest.is_protected(pos, name) then
34 minetest.record_protection_violation(pos, name)
35 return 0
36 else
37 return stack:get_count()
38 end
39 end
41 local trapped_chest_mesecons_rules = mesecon.rules.pplate
43 -- To be called when a chest is closed (only relevant for trapped chest atm)
44 local chest_update_after_close = function(pos)
45 local node = minetest.get_node(pos)
47 if node.name == "mcl_chests:trapped_chest_on" then
48 minetest.swap_node(pos, {name="mcl_chests:trapped_chest", param2 = node.param2})
49 mesecon.receptor_off(pos, trapped_chest_mesecons_rules)
50 elseif node.name == "mcl_chests:trapped_chest_on_left" then
51 minetest.swap_node(pos, {name="mcl_chests:trapped_chest_left", param2 = node.param2})
52 mesecon.receptor_off(pos, trapped_chest_mesecons_rules)
54 local pos_other = mcl_util.get_double_container_neighbor_pos(pos, node.param2, "left")
55 minetest.swap_node(pos_other, {name="mcl_chests:trapped_chest_right", param2 = node.param2})
56 mesecon.receptor_off(pos_other, trapped_chest_mesecons_rules)
57 elseif node.name == "mcl_chests:trapped_chest_on_right" then
58 minetest.swap_node(pos, {name="mcl_chests:trapped_chest_right", param2 = node.param2})
59 mesecon.receptor_off(pos, trapped_chest_mesecons_rules)
61 local pos_other = mcl_util.get_double_container_neighbor_pos(pos, node.param2, "right")
62 minetest.swap_node(pos_other, {name="mcl_chests:trapped_chest_left", param2 = node.param2})
63 mesecon.receptor_off(pos_other, trapped_chest_mesecons_rules)
64 end
65 end
67 -- To be called if a player closed a chest
68 local player_chest_close = function(player)
69 local name = player:get_player_name()
70 if open_chests[name] == nil then
71 return
72 end
73 local pos = open_chests[name].pos
74 chest_update_after_close(pos)
76 open_chests[name] = nil
77 end
79 -- This is a helper function to register both chests and trapped chests. Trapped chests will make use of the additional parameters
80 local register_chest = function(basename, desc, longdesc, usagehelp, tt_help, tiles_table, hidden, mesecons, on_rightclick_addendum, on_rightclick_addendum_left, on_rightclick_addendum_right, drop, canonical_basename)
81 -- START OF register_chest FUNCTION BODY
82 if not drop then
83 drop = "mcl_chests:"..basename
84 else
85 drop = "mcl_chests:"..drop
86 end
87 -- The basename of the "canonical" version of the node, if set (e.g.: trapped_chest_on → trapped_chest).
88 -- Used to get a shared formspec ID and to swap the node back to the canonical version in on_construct.
89 if not canonical_basename then
90 canonical_basename = basename
91 end
93 local double_chest_add_item = function(top_inv, bottom_inv, listname, stack)
94 if not stack or stack:is_empty() then
95 return
96 end
98 local name = stack:get_name()
100 local top_off = function(inv, stack)
101 for c, chest_stack in ipairs(inv:get_list(listname)) do
102 if stack:is_empty() then
103 break
106 if chest_stack:get_name() == name and chest_stack:get_free_space() > 0 then
107 stack = chest_stack:add_item(stack)
108 inv:set_stack(listname, c, chest_stack)
112 return stack
115 stack = top_off(top_inv, stack)
116 stack = top_off(bottom_inv, stack)
118 if not stack:is_empty() then
119 stack = top_inv:add_item(listname, stack)
120 if not stack:is_empty() then
121 bottom_inv:add_item(listname, stack)
126 local drop_items_chest = function(pos, oldnode, oldmetadata)
127 local meta = minetest.get_meta(pos)
128 local meta2 = meta
129 if oldmetadata then
130 meta:from_table(oldmetadata)
132 local inv = meta:get_inventory()
133 for i=1,inv:get_size("main") do
134 local stack = inv:get_stack("main", i)
135 if not stack:is_empty() then
136 local p = {x=pos.x+math.random(0, 10)/10-0.5, y=pos.y, z=pos.z+math.random(0, 10)/10-0.5}
137 minetest.add_item(p, stack)
140 meta:from_table(meta2:to_table())
143 local on_chest_blast = function(pos)
144 local node = minetest.get_node(pos)
145 drop_items_chest(pos, node)
146 minetest.remove_node(pos)
149 minetest.register_node("mcl_chests:"..basename, {
150 description = desc,
151 _tt_help = tt_help,
152 _doc_items_longdesc = longdesc,
153 _doc_items_usagehelp = usagehelp,
154 _doc_items_hidden = hidden,
155 tiles = tiles_table.small,
156 paramtype = "light",
157 paramtype2 = "facedir",
158 stack_max = 64,
159 drop = drop,
160 groups = {handy=1,axey=1, container=2, deco_block=1, material_wood=1,flammable=-1},
161 is_ground_content = false,
162 sounds = mcl_sounds.node_sound_wood_defaults(),
163 on_construct = function(pos)
164 local param2 = minetest.get_node(pos).param2
165 local meta = minetest.get_meta(pos)
166 --[[ This is a workaround for Minetest issue 5894
167 <https://github.com/minetest/minetest/issues/5894>.
168 Apparently if we don't do this, large chests initially don't work when
169 placed at chunk borders, and some chests randomly don't work after
170 placing. ]]
171 -- FIXME: Remove this workaround when the bug has been fixed.
172 -- BEGIN OF WORKAROUND --
173 meta:set_string("workaround", "ignore_me")
174 meta:set_string("workaround", nil) -- Done to keep metadata clean
175 -- END OF WORKAROUND --
176 local inv = meta:get_inventory()
177 inv:set_size("main", 9*3)
178 --[[ The "input" list is *another* workaround (hahahaha!) around the fact that Minetest
179 does not support listrings to put items into an alternative list if the first one
180 happens to be full. See <https://github.com/minetest/minetest/issues/5343>.
181 This list is a hidden input-only list and immediately puts items into the appropriate chest.
182 It is only used for listrings and hoppers. This workaround is not that bad because it only
183 requires a simple “inventory allows” check for large chests.]]
184 -- FIXME: Refactor the listrings as soon Minetest supports alternative listrings
185 -- BEGIN OF LISTRING WORKAROUND
186 inv:set_size("input", 1)
187 -- END OF LISTRING WORKAROUND
188 if minetest.get_node(mcl_util.get_double_container_neighbor_pos(pos, param2, "right")).name == "mcl_chests:"..canonical_basename then
189 minetest.swap_node(pos, {name="mcl_chests:"..canonical_basename.."_right",param2=param2})
190 local p = mcl_util.get_double_container_neighbor_pos(pos, param2, "right")
191 minetest.swap_node(p, { name = "mcl_chests:"..canonical_basename.."_left", param2 = param2 })
192 elseif minetest.get_node(mcl_util.get_double_container_neighbor_pos(pos, param2, "left")).name == "mcl_chests:"..canonical_basename then
193 minetest.swap_node(pos, {name="mcl_chests:"..canonical_basename.."_left",param2=param2})
194 local p = mcl_util.get_double_container_neighbor_pos(pos, param2, "left")
195 minetest.swap_node(p, { name = "mcl_chests:"..canonical_basename.."_right", param2 = param2 })
196 else
197 minetest.swap_node(pos, { name = "mcl_chests:"..canonical_basename, param2 = param2 })
199 end,
200 after_dig_node = drop_items_chest,
201 on_blast = on_chest_blast,
202 allow_metadata_inventory_move = protection_check_move,
203 allow_metadata_inventory_take = protection_check_put_take,
204 allow_metadata_inventory_put = protection_check_put_take,
205 on_metadata_inventory_move = function(pos, from_list, from_index, to_list, to_index, count, player)
206 minetest.log("action", player:get_player_name()..
207 " moves stuff in chest at "..minetest.pos_to_string(pos))
208 end,
209 on_metadata_inventory_put = function(pos, listname, index, stack, player)
210 minetest.log("action", player:get_player_name()..
211 " moves stuff to chest at "..minetest.pos_to_string(pos))
212 -- BEGIN OF LISTRING WORKAROUND
213 if listname == "input" then
214 local inv = minetest.get_inventory({type="node", pos=pos})
215 inv:add_item("main", stack)
217 -- END OF LISTRING WORKAROUND
218 end,
219 on_metadata_inventory_take = function(pos, listname, index, stack, player)
220 minetest.log("action", player:get_player_name()..
221 " takes stuff from chest at "..minetest.pos_to_string(pos))
222 end,
223 _mcl_blast_resistance = 2.5,
224 _mcl_hardness = 2.5,
226 on_rightclick = function(pos, node, clicker)
227 minetest.show_formspec(clicker:get_player_name(),
228 "mcl_chests:"..canonical_basename.."_"..pos.x.."_"..pos.y.."_"..pos.z,
229 "size[9,8.75]"..
230 "label[0,0;"..minetest.formspec_escape(minetest.colorize("#313131", S("Chest"))).."]"..
231 "list[nodemeta:"..pos.x..","..pos.y..","..pos.z..";main;0,0.5;9,3;]"..
232 mcl_formspec.get_itemslot_bg(0,0.5,9,3)..
233 "label[0,4.0;"..minetest.formspec_escape(minetest.colorize("#313131", S("Inventory"))).."]"..
234 "list[current_player;main;0,4.5;9,3;9]"..
235 mcl_formspec.get_itemslot_bg(0,4.5,9,3)..
236 "list[current_player;main;0,7.74;9,1;]"..
237 mcl_formspec.get_itemslot_bg(0,7.74,9,1)..
238 "listring[nodemeta:"..pos.x..","..pos.y..","..pos.z..";main]"..
239 "listring[current_player;main]")
241 if on_rightclick_addendum then
242 on_rightclick_addendum(pos, node, clicker)
244 end,
246 on_destruct = function(pos)
247 local players = minetest.get_connected_players()
248 for p=1, #players do
249 minetest.close_formspec(players[p]:get_player_name(), "mcl_chests:"..canonical_basename.."_"..pos.x.."_"..pos.y.."_"..pos.z)
251 end,
252 mesecons = mesecons,
253 on_rotate = simple_rotate,
256 minetest.register_node("mcl_chests:"..basename.."_left", {
257 tiles = tiles_table.left,
258 paramtype = "light",
259 paramtype2 = "facedir",
260 groups = {handy=1,axey=1, container=5,not_in_creative_inventory=1, material_wood=1,flammable=-1},
261 drop = drop,
262 is_ground_content = false,
263 sounds = mcl_sounds.node_sound_wood_defaults(),
264 on_construct = function(pos)
265 local n = minetest.get_node(pos)
266 local param2 = n.param2
267 local p = mcl_util.get_double_container_neighbor_pos(pos, param2, "left")
268 if not p or minetest.get_node(p).name ~= "mcl_chests:"..canonical_basename.."_right" then
269 n.name = "mcl_chests:"..canonical_basename
270 minetest.swap_node(pos, n)
272 end,
273 on_destruct = function(pos)
274 local n = minetest.get_node(pos)
275 if n.name == "mcl_chests:"..basename then
276 return
279 local players = minetest.get_connected_players()
280 for p=1, #players do
281 minetest.close_formspec(players[p]:get_player_name(), "mcl_chests:"..canonical_basename.."_"..pos.x.."_"..pos.y.."_"..pos.z)
284 local param2 = n.param2
285 local p = mcl_util.get_double_container_neighbor_pos(pos, param2, "left")
286 if not p or minetest.get_node(p).name ~= "mcl_chests:"..basename.."_right" then
287 return
289 for pl=1, #players do
290 minetest.close_formspec(players[pl]:get_player_name(), "mcl_chests:"..canonical_basename.."_"..p.x.."_"..p.y.."_"..p.z)
292 minetest.swap_node(p, { name = "mcl_chests:"..basename, param2 = param2 })
293 end,
294 after_dig_node = drop_items_chest,
295 on_blast = on_chest_blast,
296 allow_metadata_inventory_move = protection_check_move,
297 allow_metadata_inventory_take = protection_check_put_take,
298 allow_metadata_inventory_put = function(pos, listname, index, stack, player)
299 local name = player:get_player_name()
300 if minetest.is_protected(pos, name) then
301 minetest.record_protection_violation(pos, name)
302 return 0
303 -- BEGIN OF LISTRING WORKAROUND
304 elseif listname == "input" then
305 local inv = minetest.get_inventory({type="node", pos=pos})
306 if inv:room_for_item("main", stack) then
307 return -1
308 else
309 local other_pos = mcl_util.get_double_container_neighbor_pos(pos, minetest.get_node(pos).param2, "left")
310 local other_inv = minetest.get_inventory({type="node", pos=other_pos})
311 if other_inv:room_for_item("main", stack) then
312 return -1
313 else
314 return 0
317 -- END OF LISTRING WORKAROUND
318 else
319 return stack:get_count()
321 end,
322 on_metadata_inventory_move = function(pos, from_list, from_index, to_list, to_index, count, player)
323 minetest.log("action", player:get_player_name()..
324 " moves stuff in chest at "..minetest.pos_to_string(pos))
325 end,
326 on_metadata_inventory_put = function(pos, listname, index, stack, player)
327 minetest.log("action", player:get_player_name()..
328 " moves stuff to chest at "..minetest.pos_to_string(pos))
329 -- BEGIN OF LISTRING WORKAROUND
330 if listname == "input" then
331 local inv = minetest.get_inventory({type="node", pos=pos})
332 local other_pos = mcl_util.get_double_container_neighbor_pos(pos, minetest.get_node(pos).param2, "left")
333 local other_inv = minetest.get_inventory({type="node", pos=other_pos})
335 double_chest_add_item(inv, other_inv, "main", stack)
337 -- END OF LISTRING WORKAROUND
338 end,
339 on_metadata_inventory_take = function(pos, listname, index, stack, player)
340 minetest.log("action", player:get_player_name()..
341 " takes stuff from chest at "..minetest.pos_to_string(pos))
342 end,
343 _mcl_blast_resistance = 2.5,
344 _mcl_hardness = 2.5,
346 on_rightclick = function(pos, node, clicker)
347 local pos_other = mcl_util.get_double_container_neighbor_pos(pos, node.param2, "left")
349 minetest.show_formspec(clicker:get_player_name(),
350 "mcl_chests:"..canonical_basename.."_"..pos.x.."_"..pos.y.."_"..pos.z,
351 "size[9,11.5]"..
352 "label[0,0;"..minetest.formspec_escape(minetest.colorize("#313131", S("Large Chest"))).."]"..
353 "list[nodemeta:"..pos.x..","..pos.y..","..pos.z..";main;0,0.5;9,3;]"..
354 mcl_formspec.get_itemslot_bg(0,0.5,9,3)..
355 "list[nodemeta:"..pos_other.x..","..pos_other.y..","..pos_other.z..";main;0,3.5;9,3;]"..
356 mcl_formspec.get_itemslot_bg(0,3.5,9,3)..
357 "label[0,7;"..minetest.formspec_escape(minetest.colorize("#313131", S("Inventory"))).."]"..
358 "list[current_player;main;0,7.5;9,3;9]"..
359 mcl_formspec.get_itemslot_bg(0,7.5,9,3)..
360 "list[current_player;main;0,10.75;9,1;]"..
361 mcl_formspec.get_itemslot_bg(0,10.75,9,1)..
362 -- BEGIN OF LISTRING WORKAROUND
363 "listring[current_player;main]"..
364 "listring[nodemeta:"..pos.x..","..pos.y..","..pos.z..";input]"..
365 -- END OF LISTRING WORKAROUND
366 "listring[current_player;main]"..
367 "listring[nodemeta:"..pos.x..","..pos.y..","..pos.z..";main]"..
368 "listring[current_player;main]"..
369 "listring[nodemeta:"..pos_other.x..","..pos_other.y..","..pos_other.z..";main]")
371 if on_rightclick_addendum_left then
372 on_rightclick_addendum_left(pos, node, clicker)
374 end,
375 mesecons = mesecons,
376 on_rotate = no_rotate,
379 minetest.register_node("mcl_chests:"..basename.."_right", {
380 tiles = tiles_table.right,
381 paramtype = "light",
382 paramtype2 = "facedir",
383 groups = {handy=1,axey=1, container=6,not_in_creative_inventory=1, material_wood=1,flammable=-1},
384 drop = drop,
385 is_ground_content = false,
386 sounds = mcl_sounds.node_sound_wood_defaults(),
387 on_construct = function(pos)
388 local n = minetest.get_node(pos)
389 local param2 = n.param2
390 local p = mcl_util.get_double_container_neighbor_pos(pos, param2, "right")
391 if not p or minetest.get_node(p).name ~= "mcl_chests:"..canonical_basename.."_left" then
392 n.name = "mcl_chests:"..canonical_basename
393 minetest.swap_node(pos, n)
395 end,
396 on_destruct = function(pos)
397 local n = minetest.get_node(pos)
398 if n.name == "mcl_chests:"..basename then
399 return
402 local players = minetest.get_connected_players()
403 for p=1, #players do
404 minetest.close_formspec(players[p]:get_player_name(), "mcl_chests:"..canonical_basename.."_"..pos.x.."_"..pos.y.."_"..pos.z)
407 local param2 = n.param2
408 local p = mcl_util.get_double_container_neighbor_pos(pos, param2, "right")
409 if not p or minetest.get_node(p).name ~= "mcl_chests:"..basename.."_left" then
410 return
412 for pl=1, #players do
413 minetest.close_formspec(players[pl]:get_player_name(), "mcl_chests:"..canonical_basename.."_"..p.x.."_"..p.y.."_"..p.z)
415 minetest.swap_node(p, { name = "mcl_chests:"..basename, param2 = param2 })
416 local meta = minetest.get_meta(pos)
417 end,
418 after_dig_node = drop_items_chest,
419 on_blast = on_chest_blast,
420 allow_metadata_inventory_move = protection_check_move,
421 allow_metadata_inventory_take = protection_check_put_take,
422 allow_metadata_inventory_put = function(pos, listname, index, stack, player)
423 local name = player:get_player_name()
424 if minetest.is_protected(pos, name) then
425 minetest.record_protection_violation(pos, name)
426 return 0
427 -- BEGIN OF LISTRING WORKAROUND
428 elseif listname == "input" then
429 local other_pos = mcl_util.get_double_container_neighbor_pos(pos, minetest.get_node(pos).param2, "right")
430 local other_inv = minetest.get_inventory({type="node", pos=other_pos})
431 if other_inv:room_for_item("main", stack) then
432 return -1
433 else
434 local inv = minetest.get_inventory({type="node", pos=pos})
435 if inv:room_for_item("main", stack) then
436 return -1
437 else
438 return 0
441 -- END OF LISTRING WORKAROUND
442 else
443 return stack:get_count()
445 end,
446 on_metadata_inventory_move = function(pos, from_list, from_index, to_list, to_index, count, player)
447 minetest.log("action", player:get_player_name()..
448 " moves stuff in chest at "..minetest.pos_to_string(pos))
449 end,
450 on_metadata_inventory_put = function(pos, listname, index, stack, player)
451 minetest.log("action", player:get_player_name()..
452 " moves stuff to chest at "..minetest.pos_to_string(pos))
453 -- BEGIN OF LISTRING WORKAROUND
454 if listname == "input" then
455 local other_pos = mcl_util.get_double_container_neighbor_pos(pos, minetest.get_node(pos).param2, "right")
456 local other_inv = minetest.get_inventory({type="node", pos=other_pos})
457 local inv = minetest.get_inventory({type="node", pos=pos})
459 double_chest_add_item(other_inv, inv, "main", stack)
461 -- END OF LISTRING WORKAROUND
462 end,
463 on_metadata_inventory_take = function(pos, listname, index, stack, player)
464 minetest.log("action", player:get_player_name()..
465 " takes stuff from chest at "..minetest.pos_to_string(pos))
466 end,
467 _mcl_blast_resistance = 2.5,
468 _mcl_hardness = 2.5,
470 on_rightclick = function(pos, node, clicker)
471 local pos_other = mcl_util.get_double_container_neighbor_pos(pos, node.param2, "right")
473 minetest.show_formspec(clicker:get_player_name(),
474 "mcl_chests:"..canonical_basename.."_"..pos.x.."_"..pos.y.."_"..pos.z,
476 "size[9,11.5]"..
477 "label[0,0;"..minetest.formspec_escape(minetest.colorize("#313131", S("Large Chest"))).."]"..
478 "list[nodemeta:"..pos_other.x..","..pos_other.y..","..pos_other.z..";main;0,0.5;9,3;]"..
479 mcl_formspec.get_itemslot_bg(0,0.5,9,3)..
480 "list[nodemeta:"..pos.x..","..pos.y..","..pos.z..";main;0,3.5;9,3;]"..
481 mcl_formspec.get_itemslot_bg(0,3.5,9,3)..
482 "label[0,7;"..minetest.formspec_escape(minetest.colorize("#313131", S("Inventory"))).."]"..
483 "list[current_player;main;0,7.5;9,3;9]"..
484 mcl_formspec.get_itemslot_bg(0,7.5,9,3)..
485 "list[current_player;main;0,10.75;9,1;]"..
486 mcl_formspec.get_itemslot_bg(0,10.75,9,1)..
487 -- BEGIN OF LISTRING WORKAROUND
488 "listring[current_player;main]"..
489 "listring[nodemeta:"..pos.x..","..pos.y..","..pos.z..";input]"..
490 -- END OF LISTRING WORKAROUND
491 "listring[current_player;main]"..
492 "listring[nodemeta:"..pos_other.x..","..pos_other.y..","..pos_other.z..";main]"..
493 "listring[current_player;main]"..
494 "listring[nodemeta:"..pos.x..","..pos.y..","..pos.z..";main]")
496 if on_rightclick_addendum_right then
497 on_rightclick_addendum_right(pos, node, clicker)
499 end,
500 mesecons = mesecons,
501 on_rotate = no_rotate,
504 if mod_doc then
505 doc.add_entry_alias("nodes", "mcl_chests:"..basename, "nodes", "mcl_chests:"..basename.."_left")
506 doc.add_entry_alias("nodes", "mcl_chests:"..basename, "nodes", "mcl_chests:"..basename.."_right")
509 -- END OF register_chest FUNCTION BODY
512 local chestusage = S("To access its inventory, rightclick it. When broken, the items will drop out.")
514 register_chest("chest",
515 S("Chest"),
516 S("Chests are containers which provide 27 inventory slots. Chests can be turned into large chests with double the capacity by placing two chests next to each other."),
517 chestusage,
518 S("27 inventory slots") .. "\n" .. S("Can be combined to a large chest"),
520 small = {"default_chest_top.png", "mcl_chests_chest_bottom.png",
521 "mcl_chests_chest_right.png", "mcl_chests_chest_left.png",
522 "mcl_chests_chest_back.png", "default_chest_front.png"},
523 left = {"default_chest_top_big.png", "default_chest_top_big.png",
524 "mcl_chests_chest_right.png", "mcl_chests_chest_left.png",
525 "default_chest_side_big.png^[transformFX", "default_chest_front_big.png"},
526 right = {"default_chest_top_big.png^[transformFX", "default_chest_top_big.png^[transformFX",
527 "mcl_chests_chest_right.png", "mcl_chests_chest_left.png",
528 "default_chest_side_big.png", "default_chest_front_big.png^[transformFX"},
530 false
533 local traptiles = {
534 small = {"mcl_chests_chest_trapped_top.png", "mcl_chests_chest_trapped_bottom.png",
535 "mcl_chests_chest_trapped_right.png", "mcl_chests_chest_trapped_left.png",
536 "mcl_chests_chest_trapped_back.png", "mcl_chests_chest_trapped_front.png"},
537 left = {"mcl_chests_chest_trapped_top_big.png", "mcl_chests_chest_trapped_top_big.png",
538 "mcl_chests_chest_trapped_right.png", "mcl_chests_chest_trapped_left.png",
539 "mcl_chests_chest_trapped_side_big.png^[transformFX", "mcl_chests_chest_trapped_front_big.png"},
540 right = {"mcl_chests_chest_trapped_top_big.png^[transformFX", "mcl_chests_chest_trapped_top_big.png^[transformFX",
541 "mcl_chests_chest_trapped_right.png", "mcl_chests_chest_trapped_left.png",
542 "mcl_chests_chest_trapped_side_big.png", "mcl_chests_chest_trapped_front_big.png^[transformFX"},
545 register_chest("trapped_chest",
546 S("Trapped Chest"),
547 S("A trapped chest is a container which provides 27 inventory slots. When it is opened, it sends a redstone signal to its adjacent blocks as long it stays open. Trapped chests can be turned into large trapped chests with double the capacity by placing two trapped chests next to each other."),
548 chestusage,
549 S("27 inventory slots") .. "\n" .. S("Can be combined to a large chest") .. "\n" .. S("Emits a redstone signal when opened"),
550 traptiles,
551 nil,
552 {receptor = {
553 state = mesecon.state.off,
554 rules = trapped_chest_mesecons_rules,
556 function(pos, node, clicker)
557 minetest.swap_node(pos, {name="mcl_chests:trapped_chest_on", param2 = node.param2})
558 mesecon.receptor_on(pos, trapped_chest_mesecons_rules)
559 player_chest_open(clicker, pos)
560 end,
561 function(pos, node, clicker)
562 local meta = minetest.get_meta(pos)
563 meta:set_int("players", 1)
565 minetest.swap_node(pos, {name="mcl_chests:trapped_chest_on_left", param2 = node.param2})
566 mesecon.receptor_on(pos, trapped_chest_mesecons_rules)
568 local pos_other = mcl_util.get_double_container_neighbor_pos(pos, node.param2, "left")
569 minetest.swap_node(pos_other, {name="mcl_chests:trapped_chest_on_right", param2 = node.param2})
570 mesecon.receptor_on(pos_other, trapped_chest_mesecons_rules)
572 player_chest_open(clicker, pos)
573 end,
574 function(pos, node, clicker)
575 local pos_other = mcl_util.get_double_container_neighbor_pos(pos, node.param2, "right")
577 minetest.swap_node(pos, {name="mcl_chests:trapped_chest_on_right", param2 = node.param2})
578 mesecon.receptor_on(pos, trapped_chest_mesecons_rules)
580 minetest.swap_node(pos_other, {name="mcl_chests:trapped_chest_on_left", param2 = node.param2})
581 mesecon.receptor_on(pos_other, trapped_chest_mesecons_rules)
583 player_chest_open(clicker, pos)
587 register_chest("trapped_chest_on",
588 nil, nil, nil, nil, traptiles, true,
589 {receptor = {
590 state = mesecon.state.on,
591 rules = trapped_chest_mesecons_rules,
593 function(pos, node, clicker)
594 player_chest_open(clicker, pos)
595 end,
596 function(pos, node, clicker)
597 player_chest_open(clicker, pos)
598 end,
599 function(pos, node, clicker)
600 player_chest_open(clicker, pos)
601 end,
602 "trapped_chest",
603 "trapped_chest"
606 local function close_if_trapped_chest(pos, player)
607 local node = minetest.get_node(pos)
609 if node.name == "mcl_chests:trapped_chest_on" then
610 minetest.swap_node(pos, {name="mcl_chests:trapped_chest", param2 = node.param2})
611 mesecon.receptor_off(pos, trapped_chest_mesecons_rules)
613 player_chest_close(player)
614 elseif node.name == "mcl_chests:trapped_chest_on_left" then
615 minetest.swap_node(pos, {name="mcl_chests:trapped_chest_left", param2 = node.param2})
616 mesecon.receptor_off(pos, trapped_chest_mesecons_rules)
618 local pos_other = mcl_util.get_double_container_neighbor_pos(pos, node.param2, "left")
619 minetest.swap_node(pos_other, {name="mcl_chests:trapped_chest_right", param2 = node.param2})
620 mesecon.receptor_off(pos_other, trapped_chest_mesecons_rules)
622 player_chest_close(player)
623 elseif node.name == "mcl_chests:trapped_chest_on_right" then
624 minetest.swap_node(pos, {name="mcl_chests:trapped_chest_right", param2 = node.param2})
625 mesecon.receptor_off(pos, trapped_chest_mesecons_rules)
627 local pos_other = mcl_util.get_double_container_neighbor_pos(pos, node.param2, "right")
628 minetest.swap_node(pos_other, {name="mcl_chests:trapped_chest_left", param2 = node.param2})
629 mesecon.receptor_off(pos_other, trapped_chest_mesecons_rules)
631 player_chest_close(player)
635 -- Disable trapped chest when it has been closed
636 minetest.register_on_player_receive_fields(function(player, formname, fields)
637 if formname:find("mcl_chests:trapped_chest_") == 1 then
638 if fields.quit then
639 player_chest_close(player)
642 end)
644 minetest.register_on_leaveplayer(function(player)
645 player_chest_close(player)
646 end)
648 minetest.register_craft({
649 output = 'mcl_chests:chest',
650 recipe = {
651 {'group:wood', 'group:wood', 'group:wood'},
652 {'group:wood', '', 'group:wood'},
653 {'group:wood', 'group:wood', 'group:wood'},
657 minetest.register_craft({
658 type = 'fuel',
659 recipe = 'mcl_chests:chest',
660 burntime = 15
663 minetest.register_craft({
664 type = 'fuel',
665 recipe = 'mcl_chests:trapped_chest',
666 burntime = 15
669 local formspec_ender_chest = "size[9,8.75]"..
670 "label[0,0;"..minetest.formspec_escape(minetest.colorize("#313131", S("Ender Chest"))).."]"..
671 "list[current_player;enderchest;0,0.5;9,3;]"..
672 mcl_formspec.get_itemslot_bg(0,0.5,9,3)..
673 "label[0,4.0;"..minetest.formspec_escape(minetest.colorize("#313131", S("Inventory"))).."]"..
674 "list[current_player;main;0,4.5;9,3;9]"..
675 mcl_formspec.get_itemslot_bg(0,4.5,9,3)..
676 "list[current_player;main;0,7.74;9,1;]"..
677 mcl_formspec.get_itemslot_bg(0,7.74,9,1)..
678 "listring[current_player;enderchest]"..
679 "listring[current_player;main]"
682 minetest.register_node("mcl_chests:ender_chest", {
683 description = S("Ender Chest"),
684 _tt_help = S("27 interdimensional inventory slots") .. "\n" .. S("Put items inside, retrieve them from any ender chest"),
685 _doc_items_longdesc = S("Ender chests grant you access to a single personal interdimensional inventory with 27 slots. This inventory is the same no matter from which ender chest you access it from. If you put one item into one ender chest, you will find it in all other ender chests. Each player will only see their own items, but not the items of other players."),
686 _doc_items_usagehelp = S("Rightclick the ender chest to access your personal interdimensional inventory."),
687 tiles = {"mcl_chests_ender_chest_top.png", "mcl_chests_ender_chest_bottom.png",
688 "mcl_chests_ender_chest_right.png", "mcl_chests_ender_chest_left.png",
689 "mcl_chests_ender_chest_back.png", "mcl_chests_ender_chest_front.png"},
690 -- Note: The “container” group is missing here because the ender chest does not
691 -- have an inventory on its own
692 groups = {pickaxey=1, deco_block=1, material_stone=1},
693 is_ground_content = false,
694 paramtype = "light",
695 light_source = 7,
696 paramtype2 = "facedir",
697 sounds = mcl_sounds.node_sound_stone_defaults(),
698 drop = "mcl_core:obsidian 8",
699 on_construct = function(pos)
700 local meta = minetest.get_meta(pos)
701 meta:set_string("formspec", formspec_ender_chest)
702 end,
703 _mcl_blast_resistance = 3000,
704 _mcl_hardness = 22.5,
705 on_rotate = simple_rotate,
708 minetest.register_lbm({
709 label = "Update ender chest + shulker box formspecs (0.51.0)",
710 name = "mcl_chests:update_formspecs_0_51_0",
711 nodenames = { "mcl_chests:ender_chest", "group:shulker_box" },
712 action = function(pos, node)
713 minetest.registered_nodes[node.name].on_construct(pos)
714 minetest.log("action", "[mcl_chests] Node formspec updated at "..minetest.pos_to_string(pos))
715 end,
718 minetest.register_on_joinplayer(function(player)
719 local inv = player:get_inventory()
720 inv:set_size("enderchest", 9*3)
721 end)
723 minetest.register_craft({
724 output = 'mcl_chests:ender_chest',
725 recipe = {
726 {'mcl_core:obsidian', 'mcl_core:obsidian', 'mcl_core:obsidian'},
727 {'mcl_core:obsidian', 'mcl_end:ender_eye', 'mcl_core:obsidian'},
728 {'mcl_core:obsidian', 'mcl_core:obsidian', 'mcl_core:obsidian'},
732 -- Shulker boxes
733 local boxtypes = {
734 white = S("White Shulker Box"),
735 grey = S("Light Grey Shulker Box"),
736 orange = S("Orange Shulker Box"),
737 cyan = S("Cyan Shulker Box"),
738 magenta = S("Magenta Shulker Box"),
739 violet = S("Purple Shulker Box"),
740 lightblue = S("Light Blue Shulker Box"),
741 blue = S("Blue Shulker Box"),
742 yellow = S("Yellow Shulker Box"),
743 brown = S("Brown Shulker Box"),
744 green = S("Lime Shulker Box"),
745 dark_green = S("Green Shulker Box"),
746 pink = S("Pink Shulker Box"),
747 red = S("Red Shulker Box"),
748 dark_grey = S("Grey Shulker Box"),
749 black = S("Black Shulker Box"),
752 local shulker_mob_textures = {
753 white = "mobs_mc_shulker_white.png",
754 grey = "mobs_mc_shulker_silver.png",
755 orange = "mobs_mc_shulker_orange.png",
756 cyan = "mobs_mc_shulker_cyan.png",
757 magenta = "mobs_mc_shulker_magenta.png",
758 violet = "mobs_mc_shulker_purple.png",
759 lightblue = "mobs_mc_shulker_light_blue.png",
760 blue = "mobs_mc_shulker_blue.png",
761 yellow = "mobs_mc_shulker_yellow.png",
762 brown = "mobs_mc_shulker_brown.png",
763 green = "mobs_mc_shulker_lime.png",
764 dark_green = "mobs_mc_shulker_green.png",
765 pink = "mobs_mc_shulker_pink.png",
766 red = "mobs_mc_shulker_red.png",
767 dark_grey = "mobs_mc_shulker_gray.png",
768 black = "mobs_mc_shulker_black.png",
770 local canonical_shulker_color = "violet"
772 local formspec_shulker_box = "size[9,8.75]"..
773 "label[0,0;"..minetest.formspec_escape(minetest.colorize("#313131", S("Shulker Box"))).."]"..
774 "list[current_name;main;0,0.5;9,3;]"..
775 mcl_formspec.get_itemslot_bg(0,0.5,9,3)..
776 "label[0,4.0;"..minetest.formspec_escape(minetest.colorize("#313131", S("Inventory"))).."]"..
777 "list[current_player;main;0,4.5;9,3;9]"..
778 mcl_formspec.get_itemslot_bg(0,4.5,9,3)..
779 "list[current_player;main;0,7.74;9,1;]"..
780 mcl_formspec.get_itemslot_bg(0,7.74,9,1)..
781 "listring[current_name;main]"..
782 "listring[current_player;main]"
784 for color, desc in pairs(boxtypes) do
785 local mob_texture = shulker_mob_textures[color]
786 local is_canonical = color == canonical_shulker_color
787 local longdesc, usagehelp, create_entry, entry_name
788 if mod_doc then
789 if is_canonical then
790 longdesc = S("A shulker box is a portable container which provides 27 inventory slots for any item except shulker boxes. Shulker boxes keep their inventory when broken, so shulker boxes as well as their contents can be taken as a single item. Shulker boxes come in many different colors.")
791 usagehelp = S("To access the inventory of a shulker box, place and right-click it. To take a shulker box and its contents with you, just break and collect it, the items will not fall out.")
792 entry_name = S("Shulker Box")
793 else
794 create_entry = false
798 minetest.register_node("mcl_chests:"..color.."_shulker_box", {
799 description = desc,
800 _tt_help = S("27 inventory slots") .. "\n" .. S("Can be carried around with its contents"),
801 _doc_items_create_entry = create_entry,
802 _doc_items_entry_name = entry_name,
803 _doc_items_longdesc = longdesc,
804 _doc_items_usagehelp = usagehelp,
805 tiles = {
806 "mcl_chests_"..color.."_shulker_box_top.png", -- top
807 "[combine:16x16:-32,-28="..mob_texture, -- bottom
808 "[combine:16x16:0,-36="..mob_texture..":0,-16="..mob_texture, -- side
809 "[combine:16x16:-32,-36="..mob_texture..":-32,-16="..mob_texture, -- side
810 "[combine:16x16:-16,-36="..mob_texture..":-16,-16="..mob_texture, -- side
811 "[combine:16x16:-48,-36="..mob_texture..":-48,-16="..mob_texture, -- side
813 groups = {handy=1,pickaxey=1, container=3, deco_block=1, dig_by_piston=1, shulker_box=1},
814 is_ground_content = false,
815 sounds = mcl_sounds.node_sound_stone_defaults(),
816 stack_max = 1,
817 drop = "",
818 paramtype = "light",
819 paramtype2 = "facedir",
820 -- TODO: Make shulker boxes rotatable
821 -- This doesn't work, it just destroys the inventory:
822 -- on_place = minetest.rotate_node,
823 on_construct = function(pos)
824 local meta = minetest.get_meta(pos)
825 meta:set_string("formspec", formspec_shulker_box)
826 local inv = meta:get_inventory()
827 inv:set_size("main", 9*3)
828 end,
829 _on_dispense = function(stack, pos, droppos, dropnode, dropdir)
830 -- Place shulker box as node
831 if minetest.registered_nodes[dropnode.name].buildable_to then
832 minetest.set_node(droppos, {name = stack:get_name(), param2 = minetest.dir_to_facedir(dropdir)})
833 local imeta = stack:get_metadata()
834 local iinv_main = minetest.deserialize(imeta)
835 local ninv = minetest.get_inventory({type="node", pos=droppos})
836 ninv:set_list("main", iinv_main)
837 ninv:set_size("main", 9*3)
838 stack:take_item()
840 return stack
841 end,
842 after_place_node = function(pos, placer, itemstack, pointed_thing)
843 local nmeta = minetest.get_meta(pos)
844 local ninv = nmeta:get_inventory()
845 local imeta = itemstack:get_metadata()
846 local iinv_main = minetest.deserialize(imeta)
847 ninv:set_list("main", iinv_main)
848 ninv:set_size("main", 9*3)
849 if minetest.is_creative_enabled(placer:get_player_name()) then
850 if not ninv:is_empty("main") then
851 return nil
852 else
853 return itemstack
855 else
856 return nil
858 end,
859 on_destruct = function(pos)
860 local meta = minetest.get_meta(pos)
861 local inv = meta:get_inventory()
862 local items = {}
863 for i=1, inv:get_size("main") do
864 local stack = inv:get_stack("main", i)
865 items[i] = stack:to_string()
867 local data = minetest.serialize(items)
868 local boxitem = ItemStack("mcl_chests:"..color.."_shulker_box")
869 boxitem:set_metadata(data)
871 if minetest.is_creative_enabled("") then
872 if not inv:is_empty("main") then
873 minetest.add_item(pos, boxitem)
875 else
876 minetest.add_item(pos, boxitem)
878 end,
879 allow_metadata_inventory_move = protection_check_move,
880 allow_metadata_inventory_take = protection_check_put_take,
881 allow_metadata_inventory_put = function(pos, listname, index, stack, player)
882 local name = player:get_player_name()
883 if minetest.is_protected(pos, name) then
884 minetest.record_protection_violation(pos, name)
885 return 0
887 -- Do not allow to place shulker boxes into shulker boxes
888 local group = minetest.get_item_group(stack:get_name(), "shulker_box")
889 if group == 0 or group == nil then
890 return stack:get_count()
891 else
892 return 0
894 end,
895 _mcl_blast_resistance = 6,
896 _mcl_hardness = 2,
899 if mod_doc and not is_canonical then
900 doc.add_entry_alias("nodes", "mcl_chests:"..canonical_shulker_color.."_shulker_box", "nodes", "mcl_chests:"..color.."_shulker_box")
903 minetest.register_craft({
904 type = "shapeless",
905 output = 'mcl_chests:'..color..'_shulker_box',
906 recipe = { 'group:shulker_box', 'mcl_dye:'..color }
910 minetest.register_craft({
911 output = 'mcl_chests:violet_shulker_box',
912 recipe = {
913 {'mcl_mobitems:shulker_shell'},
914 {'mcl_chests:chest'},
915 {'mcl_mobitems:shulker_shell'},
919 -- Save metadata of shulker box when used in crafting
920 minetest.register_on_craft(function(itemstack, player, old_craft_grid, craft_inv)
921 local new = itemstack:get_name()
922 if minetest.get_item_group(itemstack:get_name(), "shulker_box") ~= 1 then
923 return
925 local original
926 for i = 1, #old_craft_grid do
927 local item = old_craft_grid[i]:get_name()
928 if minetest.get_item_group(item, "shulker_box") == 1 then
929 original = old_craft_grid[i]
930 break
933 if original then
934 local ometa = original:get_meta():to_table()
935 local nmeta = itemstack:get_meta()
936 nmeta:from_table(ometa)
937 return itemstack
939 end)
942 minetest.register_lbm({
943 -- Disable active/open trapped chests when loaded because nobody could
944 -- have them open at loading time.
945 -- Fixes redstone weirdness.
946 label = "Disable active trapped chests",
947 name = "mcl_chests:reset_trapped_chests",
948 nodenames = { "mcl_chests:trapped_chest_on", "mcl_chests:trapped_chest_on_left", "mcl_chests:trapped_chest_on_right" },
949 run_at_every_load = true,
950 action = function(pos, node)
951 minetest.log("action", "[mcl_chests] Disabled active trapped chest on load: " ..minetest.pos_to_string(pos))
952 chest_update_after_close(pos)
953 end,
956 -- Legacy
957 minetest.register_lbm({
958 label = "Update ender chest formspecs (0.60.0)",
959 name = "mcl_chests:update_ender_chest_formspecs_0_60_0",
960 nodenames = { "mcl_chests:ender_chest" },
961 run_at_every_load = false,
962 action = function(pos, node)
963 local meta = minetest.get_meta(pos)
964 meta:set_string("formspec", formspec_ender_chest)
965 end,
967 minetest.register_lbm({
968 label = "Update shulker box formspecs (0.60.0)",
969 name = "mcl_chests:update_shulker_box_formspecs_0_60_0",
970 nodenames = { "group:shulker_box" },
971 run_at_every_load = false,
972 action = function(pos, node)
973 local meta = minetest.get_meta(pos)
974 meta:set_string("formspec", formspec_shulker_box)
975 end,