chests.lua 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380
  1. default.chest = {}
  2. -- support for MT game translation.
  3. local S = default.get_translator
  4. function default.chest.get_chest_formspec(pos)
  5. local spos = pos.x .. "," .. pos.y .. "," .. pos.z
  6. local formspec =
  7. "size[8,9]" ..
  8. "list[nodemeta:" .. spos .. ";main;0,0.3;8,4;]" ..
  9. "list[current_player;main;0,4.85;8,1;]" ..
  10. "list[current_player;main;0,6.08;8,3;8]" ..
  11. "listring[nodemeta:" .. spos .. ";main]" ..
  12. "listring[current_player;main]" ..
  13. default.get_hotbar_bg(0,4.85)
  14. return formspec
  15. end
  16. function default.chest.chest_lid_obstructed(pos)
  17. local above = {x = pos.x, y = pos.y + 1, z = pos.z}
  18. local def = minetest.registered_nodes[minetest.get_node(above).name]
  19. -- allow ladders, signs, wallmounted things and torches to not obstruct
  20. if def and
  21. (def.drawtype == "airlike" or
  22. def.drawtype == "signlike" or
  23. def.drawtype == "torchlike" or
  24. (def.drawtype == "nodebox" and def.paramtype2 == "wallmounted")) then
  25. return false
  26. end
  27. return true
  28. end
  29. function default.chest.chest_lid_close(pn)
  30. local chest_open_info = default.chest.open_chests[pn]
  31. local pos = chest_open_info.pos
  32. local sound = chest_open_info.sound
  33. local swap = chest_open_info.swap
  34. default.chest.open_chests[pn] = nil
  35. for k, v in pairs(default.chest.open_chests) do
  36. if vector.equals(v.pos, pos) then
  37. -- another player is also looking at the chest
  38. return true
  39. end
  40. end
  41. local node = minetest.get_node(pos)
  42. minetest.after(0.2, function()
  43. local current_node = minetest.get_node(pos)
  44. if current_node.name ~= swap .. "_open" then
  45. -- the chest has already been replaced, don't try to replace what's there.
  46. return
  47. end
  48. minetest.swap_node(pos, {name = swap, param2 = node.param2})
  49. minetest.sound_play(sound, {gain = 0.3, pos = pos,
  50. max_hear_distance = 10}, true)
  51. end)
  52. end
  53. default.chest.open_chests = {}
  54. minetest.register_on_player_receive_fields(function(player, formname, fields)
  55. local pn = player:get_player_name()
  56. if formname ~= "default:chest" then
  57. if default.chest.open_chests[pn] then
  58. default.chest.chest_lid_close(pn)
  59. end
  60. return
  61. end
  62. if not (fields.quit and default.chest.open_chests[pn]) then
  63. return
  64. end
  65. default.chest.chest_lid_close(pn)
  66. return true
  67. end)
  68. minetest.register_on_leaveplayer(function(player)
  69. local pn = player:get_player_name()
  70. if default.chest.open_chests[pn] then
  71. default.chest.chest_lid_close(pn)
  72. end
  73. end)
  74. function default.chest.register_chest(prefixed_name, d)
  75. local name = prefixed_name:sub(1,1) == ':' and prefixed_name:sub(2,-1) or prefixed_name
  76. local def = table.copy(d)
  77. def.drawtype = "mesh"
  78. def.visual = "mesh"
  79. def.paramtype = "light"
  80. def.paramtype2 = "facedir"
  81. def.legacy_facedir_simple = true
  82. def.is_ground_content = false
  83. if def.protected then
  84. def.on_construct = function(pos)
  85. local meta = minetest.get_meta(pos)
  86. meta:set_string("infotext", S("Locked Chest"))
  87. meta:set_string("owner", "")
  88. local inv = meta:get_inventory()
  89. inv:set_size("main", 8*4)
  90. end
  91. def.after_place_node = function(pos, placer)
  92. local meta = minetest.get_meta(pos)
  93. meta:set_string("owner", placer:get_player_name() or "")
  94. meta:set_string("infotext", S("Locked Chest (owned by @1)", meta:get_string("owner")))
  95. end
  96. def.can_dig = function(pos,player)
  97. local meta = minetest.get_meta(pos);
  98. local inv = meta:get_inventory()
  99. return inv:is_empty("main") and
  100. default.can_interact_with_node(player, pos)
  101. end
  102. def.allow_metadata_inventory_move = function(pos, from_list, from_index,
  103. to_list, to_index, count, player)
  104. if not default.can_interact_with_node(player, pos) then
  105. return 0
  106. end
  107. return count
  108. end
  109. def.allow_metadata_inventory_put = function(pos, listname, index, stack, player)
  110. if not default.can_interact_with_node(player, pos) then
  111. return 0
  112. end
  113. return stack:get_count()
  114. end
  115. def.allow_metadata_inventory_take = function(pos, listname, index, stack, player)
  116. if not default.can_interact_with_node(player, pos) then
  117. return 0
  118. end
  119. return stack:get_count()
  120. end
  121. def.on_rightclick = function(pos, node, clicker, itemstack, pointed_thing)
  122. if not default.can_interact_with_node(clicker, pos) then
  123. return itemstack
  124. end
  125. local cn = clicker:get_player_name()
  126. if default.chest.open_chests[cn] then
  127. default.chest.chest_lid_close(cn)
  128. end
  129. minetest.sound_play(def.sound_open, {gain = 0.3,
  130. pos = pos, max_hear_distance = 10}, true)
  131. if not default.chest.chest_lid_obstructed(pos) then
  132. minetest.swap_node(pos,
  133. { name = name .. "_open",
  134. param2 = node.param2 })
  135. end
  136. minetest.after(0.2, minetest.show_formspec, cn,
  137. "default:chest", default.chest.get_chest_formspec(pos))
  138. default.chest.open_chests[cn] = { pos = pos,
  139. sound = def.sound_close, swap = name }
  140. end
  141. def.on_blast = function() end
  142. def.on_key_use = function(pos, player)
  143. local secret = minetest.get_meta(pos):get_string("key_lock_secret")
  144. local itemstack = player:get_wielded_item()
  145. local key_meta = itemstack:get_meta()
  146. if itemstack:get_metadata() == "" then
  147. return
  148. end
  149. if key_meta:get_string("secret") == "" then
  150. key_meta:set_string("secret", minetest.parse_json(itemstack:get_metadata()).secret)
  151. itemstack:set_metadata("")
  152. end
  153. if secret ~= key_meta:get_string("secret") then
  154. return
  155. end
  156. minetest.show_formspec(
  157. player:get_player_name(),
  158. "default:chest_locked",
  159. default.chest.get_chest_formspec(pos)
  160. )
  161. end
  162. def.on_skeleton_key_use = function(pos, player, newsecret)
  163. local meta = minetest.get_meta(pos)
  164. local owner = meta:get_string("owner")
  165. local pn = player:get_player_name()
  166. -- verify placer is owner of lockable chest
  167. if owner ~= pn then
  168. minetest.record_protection_violation(pos, pn)
  169. minetest.chat_send_player(pn, S("You do not own this chest."))
  170. return nil
  171. end
  172. local secret = meta:get_string("key_lock_secret")
  173. if secret == "" then
  174. secret = newsecret
  175. meta:set_string("key_lock_secret", secret)
  176. end
  177. return secret, S("a locked chest"), owner
  178. end
  179. else
  180. def.on_construct = function(pos)
  181. local meta = minetest.get_meta(pos)
  182. meta:set_string("infotext", S("Chest"))
  183. local inv = meta:get_inventory()
  184. inv:set_size("main", 8*4)
  185. end
  186. def.can_dig = function(pos,player)
  187. local meta = minetest.get_meta(pos);
  188. local inv = meta:get_inventory()
  189. return inv:is_empty("main")
  190. end
  191. def.on_rightclick = function(pos, node, clicker)
  192. local cn = clicker:get_player_name()
  193. if default.chest.open_chests[cn] then
  194. default.chest.chest_lid_close(cn)
  195. end
  196. minetest.sound_play(def.sound_open, {gain = 0.3, pos = pos,
  197. max_hear_distance = 10}, true)
  198. if not default.chest.chest_lid_obstructed(pos) then
  199. minetest.swap_node(pos, {
  200. name = name .. "_open",
  201. param2 = node.param2 })
  202. end
  203. minetest.after(0.2, minetest.show_formspec,
  204. cn,
  205. "default:chest", default.chest.get_chest_formspec(pos))
  206. default.chest.open_chests[cn] = { pos = pos,
  207. sound = def.sound_close, swap = name }
  208. end
  209. def.on_blast = function(pos)
  210. local drops = {}
  211. default.get_inventory_drops(pos, "main", drops)
  212. drops[#drops+1] = name
  213. minetest.remove_node(pos)
  214. return drops
  215. end
  216. end
  217. default.set_inventory_action_loggers(def, "chest")
  218. local def_opened = table.copy(def)
  219. local def_closed = table.copy(def)
  220. def_opened.mesh = "chest_open.obj"
  221. for i = 1, #def_opened.tiles do
  222. if type(def_opened.tiles[i]) == "string" then
  223. def_opened.tiles[i] = {name = def_opened.tiles[i], backface_culling = true}
  224. elseif def_opened.tiles[i].backface_culling == nil then
  225. def_opened.tiles[i].backface_culling = true
  226. end
  227. end
  228. def_opened.drop = name
  229. def_opened.groups.not_in_creative_inventory = 1
  230. def_opened.selection_box = {
  231. type = "fixed",
  232. fixed = { -1/2, -1/2, -1/2, 1/2, 3/16, 1/2 },
  233. }
  234. def_opened.can_dig = function()
  235. return false
  236. end
  237. def_opened.on_blast = function() end
  238. def_closed.mesh = nil
  239. def_closed.drawtype = nil
  240. def_closed.tiles[6] = def.tiles[5] -- swap textures around for "normal"
  241. def_closed.tiles[5] = def.tiles[3] -- drawtype to make them match the mesh
  242. def_closed.tiles[3] = def.tiles[3].."^[transformFX"
  243. minetest.register_node(prefixed_name, def_closed)
  244. minetest.register_node(prefixed_name .. "_open", def_opened)
  245. -- convert old chests to this new variant
  246. if name == "default:chest" or name == "default:chest_locked" then
  247. minetest.register_lbm({
  248. label = "update chests to opening chests",
  249. name = "default:upgrade_" .. name:sub(9,-1) .. "_v2",
  250. nodenames = {name},
  251. action = function(pos, node)
  252. local meta = minetest.get_meta(pos)
  253. meta:set_string("formspec", nil)
  254. local inv = meta:get_inventory()
  255. local list = inv:get_list("default:chest")
  256. if list then
  257. inv:set_size("main", 8*4)
  258. inv:set_list("main", list)
  259. inv:set_list("default:chest", nil)
  260. end
  261. end
  262. })
  263. end
  264. -- close opened chests on load
  265. local modname, chestname = prefixed_name:match("^(:?.-):(.*)$")
  266. minetest.register_lbm({
  267. label = "close opened chests on load",
  268. name = modname .. ":close_" .. chestname .. "_open",
  269. nodenames = {prefixed_name .. "_open"},
  270. run_at_every_load = true,
  271. action = function(pos, node)
  272. node.name = prefixed_name
  273. minetest.swap_node(pos, node)
  274. end
  275. })
  276. end
  277. default.chest.register_chest("default:chest", {
  278. description = S("Chest"),
  279. tiles = {
  280. "default_chest_top.png",
  281. "default_chest_top.png",
  282. "default_chest_side.png",
  283. "default_chest_side.png",
  284. "default_chest_front.png",
  285. "default_chest_inside.png"
  286. },
  287. sounds = default.node_sound_wood_defaults(),
  288. sound_open = "default_chest_open",
  289. sound_close = "default_chest_close",
  290. groups = {choppy = 2, oddly_breakable_by_hand = 2},
  291. })
  292. default.chest.register_chest("default:chest_locked", {
  293. description = S("Locked Chest"),
  294. tiles = {
  295. "default_chest_top.png",
  296. "default_chest_top.png",
  297. "default_chest_side.png",
  298. "default_chest_side.png",
  299. "default_chest_lock.png",
  300. "default_chest_inside.png"
  301. },
  302. sounds = default.node_sound_wood_defaults(),
  303. sound_open = "default_chest_open",
  304. sound_close = "default_chest_close",
  305. groups = {choppy = 2, oddly_breakable_by_hand = 2},
  306. protected = true,
  307. })
  308. minetest.register_craft({
  309. output = "default:chest",
  310. recipe = {
  311. {"group:wood", "group:wood", "group:wood"},
  312. {"group:wood", "", "group:wood"},
  313. {"group:wood", "group:wood", "group:wood"},
  314. }
  315. })
  316. minetest.register_craft({
  317. output = "default:chest_locked",
  318. recipe = {
  319. {"group:wood", "group:wood", "group:wood"},
  320. {"group:wood", "default:steel_ingot", "group:wood"},
  321. {"group:wood", "group:wood", "group:wood"},
  322. }
  323. })
  324. minetest.register_craft( {
  325. type = "shapeless",
  326. output = "default:chest_locked",
  327. recipe = {"default:chest", "default:steel_ingot"},
  328. })
  329. minetest.register_craft({
  330. type = "fuel",
  331. recipe = "default:chest",
  332. burntime = 30,
  333. })
  334. minetest.register_craft({
  335. type = "fuel",
  336. recipe = "default:chest_locked",
  337. burntime = 30,
  338. })