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
10 --[[ List of open chests.
13 If player is using a chest: { pos = <chest node position> }
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
}
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
)
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
)
37 return stack
:get_count()
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
)
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
73 local pos
= open_chests
[name
].pos
74 chest_update_after_close(pos
)
76 open_chests
[name
] = nil
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
83 drop
= "mcl_chests:"..basename
85 drop
= "mcl_chests:"..drop
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
93 local double_chest_add_item
= function(top_inv
, bottom_inv
, listname
, stack
)
94 if not stack
or stack
:is_empty() then
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
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
)
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
)
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
, {
152 _doc_items_longdesc
= longdesc
,
153 _doc_items_usagehelp
= usagehelp
,
154 _doc_items_hidden
= hidden
,
155 tiles
= tiles_table
.small
,
157 paramtype2
= "facedir",
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
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
})
197 minetest
.swap_node(pos
, { name
= "mcl_chests:"..canonical_basename
, param2
= param2
})
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
))
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
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
))
223 _mcl_blast_resistance
= 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
,
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
)
246 on_destruct
= function(pos
)
247 local players
= minetest
.get_connected_players()
249 minetest
.close_formspec(players
[p
]:get_player_name(), "mcl_chests:"..canonical_basename
.."_"..pos
.x
.."_"..pos
.y
.."_"..pos
.z
)
253 on_rotate
= simple_rotate
,
256 minetest
.register_node("mcl_chests:"..basename
.."_left", {
257 tiles
= tiles_table
.left
,
259 paramtype2
= "facedir",
260 groups
= {handy
=1,axey
=1, container
=5,not_in_creative_inventory
=1, material_wood
=1,flammable
=-1},
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
)
273 on_destruct
= function(pos
)
274 local n
= minetest
.get_node(pos
)
275 if n
.name
== "mcl_chests:"..basename
then
279 local players
= minetest
.get_connected_players()
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
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
})
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
)
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
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
317 -- END OF LISTRING WORKAROUND
319 return stack
:get_count()
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
))
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
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
))
343 _mcl_blast_resistance
= 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
,
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
)
376 on_rotate
= no_rotate
,
379 minetest
.register_node("mcl_chests:"..basename
.."_right", {
380 tiles
= tiles_table
.right
,
382 paramtype2
= "facedir",
383 groups
= {handy
=1,axey
=1, container
=6,not_in_creative_inventory
=1, material_wood
=1,flammable
=-1},
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
)
396 on_destruct
= function(pos
)
397 local n
= minetest
.get_node(pos
)
398 if n
.name
== "mcl_chests:"..basename
then
402 local players
= minetest
.get_connected_players()
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
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
)
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
)
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
434 local inv
= minetest
.get_inventory({type="node", pos
=pos
})
435 if inv
:room_for_item("main", stack
) then
441 -- END OF LISTRING WORKAROUND
443 return stack
:get_count()
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
))
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
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
))
467 _mcl_blast_resistance
= 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
,
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
)
501 on_rotate
= no_rotate
,
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",
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."),
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"},
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",
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."),
549 S("27 inventory slots") .. "\n" .. S("Can be combined to a large chest") .. "\n" .. S("Emits a redstone signal when opened"),
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
)
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
)
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,
590 state
= mesecon
.state
.on
,
591 rules
= trapped_chest_mesecons_rules
,
593 function(pos
, node
, clicker
)
594 player_chest_open(clicker
, pos
)
596 function(pos
, node
, clicker
)
597 player_chest_open(clicker
, pos
)
599 function(pos
, node
, clicker
)
600 player_chest_open(clicker
, pos
)
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
639 player_chest_close(player
)
644 minetest
.register_on_leaveplayer(function(player
)
645 player_chest_close(player
)
648 minetest
.register_craft({
649 output
= 'mcl_chests:chest',
651 {'group:wood', 'group:wood', 'group:wood'},
652 {'group:wood', '', 'group:wood'},
653 {'group:wood', 'group:wood', 'group:wood'},
657 minetest
.register_craft({
659 recipe
= 'mcl_chests:chest',
663 minetest
.register_craft({
665 recipe
= 'mcl_chests:trapped_chest',
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,
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
)
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
))
718 minetest
.register_on_joinplayer(function(player
)
719 local inv
= player
:get_inventory()
720 inv
:set_size("enderchest", 9*3)
723 minetest
.register_craft({
724 output
= 'mcl_chests:ender_chest',
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'},
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
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")
798 minetest
.register_node("mcl_chests:"..color
.."_shulker_box", {
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
,
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(),
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)
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)
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
859 on_destruct
= function(pos
)
860 local meta
= minetest
.get_meta(pos
)
861 local inv
= meta
:get_inventory()
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
)
876 minetest
.add_item(pos
, boxitem
)
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
)
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()
895 _mcl_blast_resistance
= 6,
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({
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',
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
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
]
934 local ometa
= original
:get_meta():to_table()
935 local nmeta
= itemstack
:get_meta()
936 nmeta
:from_table(ometa
)
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
)
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
)
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
)