diff --git a/mods/ITEMS/mcl_chests/api.lua b/mods/ITEMS/mcl_chests/api.lua new file mode 100644 index 000000000..6b602b5c7 --- /dev/null +++ b/mods/ITEMS/mcl_chests/api.lua @@ -0,0 +1,953 @@ +local S = minetest.get_translator(minetest.get_current_modname()) +local F = minetest.formspec_escape +local C = minetest.colorize + +local get_double_container_neighbor_pos = mcl_util.get_double_container_neighbor_pos + +local string = string +local table = table +local math = math + +local sf = string.format + +-- Chest Entity +-- ============ +-- This is necessary to show the chest as an animated mesh, as Minetest doesn't +-- support assigning animated meshes to nodes directly. We're bypassing this +-- limitation by giving each chest its own entity, and making the chest node +-- itself fully transparent. +local animated_chests = (minetest.settings:get_bool("animated_chests") ~= false) +local entity_animations = { + shulker = { + speed = 50, + open = { x = 45, y = 95 }, + close = { x = 95, y = 145 }, + }, + chest = { + speed = 25, + open = { x = 0, y = 7 }, + close = { x = 13, y = 20 }, + }, +} + +minetest.register_entity("mcl_chests:chest", { + initial_properties = { + visual = "mesh", + pointable = false, + physical = false, + static_save = false, + }, + + set_animation = function(self, animname) + local anim_table = entity_animations[self.animation_type] + local anim = anim_table[animname] + if not anim then return end + self.object:set_animation(anim, anim_table.speed, 0, false) + end, + + open = function(self, playername) + self.players[playername] = true + if not self.is_open then + self:set_animation("open") + minetest.sound_play(self.sound_prefix .. "_open", { pos = self.node_pos, gain = 0.5, max_hear_distance = 16 }, + true) + self.is_open = true + end + end, + + close = function(self, playername) + local playerlist = self.players + playerlist[playername] = nil + if self.is_open then + if next(playerlist) then + return + end + self:set_animation("close") + minetest.sound_play(self.sound_prefix .. "_close", + { pos = self.node_pos, gain = 0.3, max_hear_distance = 16 }, + true) + self.is_open = false + end + end, + + initialize = function(self, node_pos, node_name, textures, dir, double, sound_prefix, mesh_prefix, animation_type) + self.node_pos = node_pos + self.node_name = node_name + self.sound_prefix = sound_prefix + self.animation_type = animation_type + self.object:set_properties({ + textures = textures, + mesh = mesh_prefix .. (double and "_double" or "") .. ".b3d", + }) + self:set_yaw(dir) + end, + + reinitialize = function(self, node_name) + self.node_name = node_name + end, + + set_yaw = function(self, dir) + self.object:set_yaw(minetest.dir_to_yaw(dir)) + end, + + check = function(self) + local node_pos, node_name = self.node_pos, self.node_name + if not node_pos or not node_name then + return false + end + local node = minetest.get_node(node_pos) + if node.name ~= node_name then + return false + end + return true + end, + + on_activate = function(self) + self.object:set_armor_groups({ immortal = 1 }) + self.players = {} + end, + + on_step = function(self, dtime) + if not self:check() then + self.object:remove() + end + end +}) + +local function get_entity_pos(pos, dir, double) + pos = vector.copy(pos) + if double then + local add, mul, vec, cross = vector.add, vector.multiply, vector.new, vector.cross + pos = add(pos, mul(cross(dir, vec(0, 1, 0)), -0.5)) + end + return pos +end + +local function find_entity(pos) + for _, obj in pairs(minetest.get_objects_inside_radius(pos, 0)) do + local luaentity = obj:get_luaentity() + if luaentity and luaentity.name == "mcl_chests:chest" then + return luaentity + end + end +end + +local function get_entity_info(pos, param2, double, dir, entity_pos) + dir = dir or minetest.facedir_to_dir(param2) + return dir, get_entity_pos(pos, dir, double) +end + +local function create_entity(pos, node_name, textures, param2, double, sound_prefix, mesh_prefix, animation_type, dir, + entity_pos) + dir, entity_pos = get_entity_info(pos, param2, double, dir, entity_pos) + local obj = minetest.add_entity(entity_pos, "mcl_chests:chest") + local luaentity = obj:get_luaentity() + luaentity:initialize(pos, node_name, textures, dir, double, sound_prefix, mesh_prefix, animation_type) + return luaentity +end +mcl_chests.create_entity = create_entity + +local function find_or_create_entity(pos, node_name, textures, param2, double, sound_prefix, mesh_prefix, + animation_type, dir, entity_pos) + dir, entity_pos = get_entity_info(pos, param2, double, dir, entity_pos) + return find_entity(entity_pos) or + create_entity(pos, node_name, textures, param2, double, sound_prefix, mesh_prefix, animation_type, dir, + entity_pos) +end +mcl_chests.find_or_create_entity = find_or_create_entity + +local no_rotate, simple_rotate +if screwdriver then + no_rotate = screwdriver.disallow + simple_rotate = function(pos, node, user, mode, new_param2) + if screwdriver.rotate_simple(pos, node, user, mode, new_param2) ~= false then + local nodename = node.name + local nodedef = minetest.registered_nodes[nodename] + local dir = minetest.facedir_to_dir(new_param2) + find_or_create_entity(pos, nodename, nodedef._chest_entity_textures, new_param2, false, + nodedef._chest_entity_sound, + nodedef._chest_entity_mesh, nodedef._chest_entity_animation_type, dir):set_yaw(dir) + else + return false + end + end +end +mcl_chests.no_rotate, mcl_chests.simple_rotate = no_rotate, simple_rotate + +--[[ List of open chests. +Key: Player name +Value: + If player is using a chest: { pos = } + Otherwise: nil ]] +local open_chests = {} +mcl_chests.open_chests = open_chests + +-- To be called if a player opened a chest +local function player_chest_open(player, pos, node_name, textures, param2, double, sound, mesh, shulker) + local name = player:get_player_name() + open_chests[name] = { + pos = pos, + node_name = node_name, + textures = textures, + param2 = param2, + double = double, + sound = sound, + mesh = mesh, + shulker = shulker + } + if animated_chests then + local dir = minetest.facedir_to_dir(param2) + find_or_create_entity(pos, node_name, textures, param2, double, sound, mesh, shulker and "shulker" or "chest", + dir): + open(name) + end +end +mcl_chests.player_chest_open = player_chest_open + +-- Simple protection checking functions +local function protection_check_move(pos, from_list, from_index, to_list, to_index, count, player) + local name = player:get_player_name() + if minetest.is_protected(pos, name) then + minetest.record_protection_violation(pos, name) + return 0 + else + return count + end +end +mcl_chests.protection_check_move = protection_check_move + +local function protection_check_put_take(pos, listname, index, stack, player) + local name = player:get_player_name() + if minetest.is_protected(pos, name) then + minetest.record_protection_violation(pos, name) + return 0 + else + return stack:get_count() + end +end +mcl_chests.protection_check_put_take = protection_check_put_take + +local trapped_chest_mesecons_rules = mesecon.rules.pplate + +-- To be called when a chest is closed (only relevant for trapped chest atm) +local function chest_update_after_close(pos) + local node = minetest.get_node(pos) + + if node.name == "mcl_chests:trapped_chest_on_small" then + minetest.swap_node(pos, { name = "mcl_chests:trapped_chest_small", param2 = node.param2 }) + find_or_create_entity(pos, "mcl_chests:trapped_chest_small", { "mcl_chests_trapped.png" }, node.param2, false, + "default_chest", "mcl_chests_chest", "chest"):reinitialize("mcl_chests:trapped_chest_small") + mesecon.receptor_off(pos, trapped_chest_mesecons_rules) + elseif node.name == "mcl_chests:trapped_chest_on_left" then + minetest.swap_node(pos, { name = "mcl_chests:trapped_chest_left", param2 = node.param2 }) + find_or_create_entity(pos, "mcl_chests:trapped_chest_left", mcl_chests.tiles.chest_trapped_double, node.param2, true, + "default_chest", "mcl_chests_chest", "chest"):reinitialize("mcl_chests:trapped_chest_left") + mesecon.receptor_off(pos, trapped_chest_mesecons_rules) + + local pos_other = get_double_container_neighbor_pos(pos, node.param2, "left") + minetest.swap_node(pos_other, { name = "mcl_chests:trapped_chest_right", param2 = node.param2 }) + mesecon.receptor_off(pos_other, trapped_chest_mesecons_rules) + elseif node.name == "mcl_chests:trapped_chest_on_right" then + minetest.swap_node(pos, { name = "mcl_chests:trapped_chest_right", param2 = node.param2 }) + mesecon.receptor_off(pos, trapped_chest_mesecons_rules) + + local pos_other = get_double_container_neighbor_pos(pos, node.param2, "right") + minetest.swap_node(pos_other, { name = "mcl_chests:trapped_chest_left", param2 = node.param2 }) + find_or_create_entity(pos_other, "mcl_chests:trapped_chest_left", mcl_chests.tiles.chest_trapped_double, node.param2, true, + "default_chest", "mcl_chests_chest", "chest"):reinitialize("mcl_chests:trapped_chest_left") + mesecon.receptor_off(pos_other, trapped_chest_mesecons_rules) + end +end +mcl_chests.chest_update_after_close = chest_update_after_close + +-- To be called if a player closed a chest +local function player_chest_close(player) + local name = player:get_player_name() + local open_chest = open_chests[name] + if open_chest == nil then + return + end + if animated_chests then + find_or_create_entity(open_chest.pos, open_chest.node_name, open_chest.textures, open_chest.param2, + open_chest.double, + open_chest.sound, open_chest.mesh, open_chest.shulker and "shulker" or "chest"):close(name) + end + chest_update_after_close(open_chest.pos) + + open_chests[name] = nil +end +mcl_chests.player_chest_close = player_chest_close + +-- This is a helper function to register both chests and trapped chests. Trapped chests will make use of the additional parameters +function mcl_chests.register_chest(basename, desc, longdesc, usagehelp, tt_help, tiles_table, hidden, mesecons, + on_rightclick_addendum, on_rightclick_addendum_left, + on_rightclick_addendum_right, drop, canonical_basename) + if not drop then + drop = "mcl_chests:" .. basename + else + drop = "mcl_chests:" .. drop + end + -- The basename of the "canonical" version of the node, if set (e.g.: trapped_chest_on → trapped_chest). + -- Used to get a shared formspec ID and to swap the node back to the canonical version in on_construct. + if not canonical_basename then + canonical_basename = basename + end + + local function double_chest_add_item(top_inv, bottom_inv, listname, stack) + if not stack or stack:is_empty() then return end + + local name = stack:get_name() + + local function top_off(inv, stack) + for c, chest_stack in ipairs(inv:get_list(listname)) do + if stack:is_empty() then + break + end + + if chest_stack:get_name() == name and chest_stack:get_free_space() > 0 then + stack = chest_stack:add_item(stack) + inv:set_stack(listname, c, chest_stack) + end + end + + return stack + end + + stack = top_off(top_inv, stack) + stack = top_off(bottom_inv, stack) + + if not stack:is_empty() then + stack = top_inv:add_item(listname, stack) + if not stack:is_empty() then + bottom_inv:add_item(listname, stack) + end + end + end + + local drop_items_chest = mcl_util.drop_items_from_meta_container("main") + + local function on_chest_blast(pos) + local node = minetest.get_node(pos) + drop_items_chest(pos, node) + minetest.remove_node(pos) + end + + local function limit_put_list(stack, list) + for _, other in ipairs(list) do + stack = other:add_item(stack) + if stack:is_empty() then + break + end + end + return stack + end + + local function limit_put(stack, inv1, inv2) + local leftover = ItemStack(stack) + leftover = limit_put_list(leftover, inv1:get_list("main")) + leftover = limit_put_list(leftover, inv2:get_list("main")) + return stack:get_count() - leftover:get_count() + end + + local small_name = "mcl_chests:" .. basename .. "_small" + local small_textures = tiles_table.small + local left_name = "mcl_chests:" .. basename .. "_left" + local right_name = "mcl_chests:" .. basename .. "_right" + local double_textures = tiles_table.double + + minetest.register_node("mcl_chests:" .. basename, { + description = desc, + _tt_help = tt_help, + _doc_items_longdesc = longdesc, + _doc_items_usagehelp = usagehelp, + _doc_items_hidden = hidden, + drawtype = "mesh", + mesh = "mcl_chests_chest.b3d", + tiles = small_textures, + use_texture_alpha = "opaque", + paramtype = "light", + paramtype2 = "facedir", + sounds = mcl_sounds.node_sound_wood_defaults(), + groups = { deco_block = 1 }, + on_construct = function(pos, node) + local node = minetest.get_node(pos) + node.name = small_name + minetest.set_node(pos, node) + end, + after_place_node = function(pos, placer, itemstack, pointed_thing) + minetest.get_meta(pos):set_string("name", itemstack:get_meta():get_string("name")) + end, + }) + + local function close_forms(canonical_basename, pos) + local players = minetest.get_connected_players() + for p = 1, #players do + if vector.distance(players[p]:get_pos(), pos) <= 30 then + minetest.close_formspec(players[p]:get_player_name(), + "mcl_chests:" .. canonical_basename .. "_" .. pos.x .. "_" .. pos.y .. "_" .. pos.z) + end + end + end + + minetest.register_node(small_name, { + description = desc, + _tt_help = tt_help, + _doc_items_longdesc = longdesc, + _doc_items_usagehelp = usagehelp, + _doc_items_hidden = hidden, + drawtype = "nodebox", + node_box = { + type = "fixed", + fixed = { -0.4375, -0.5, -0.4375, 0.4375, 0.375, 0.4375 }, + }, + tiles = { "blank.png^[resize:16x16" }, + use_texture_alpha = "clip", + _chest_entity_textures = small_textures, + _chest_entity_sound = "default_chest", + _chest_entity_mesh = "mcl_chests_chest", + _chest_entity_animation_type = "chest", + paramtype = "light", + paramtype2 = "facedir", + drop = drop, + groups = { + handy = 1, + axey = 1, + container = 2, + deco_block = 1, + material_wood = 1, + flammable = -1, + chest_entity = 1, + not_in_creative_inventory = 1 + }, + is_ground_content = false, + sounds = mcl_sounds.node_sound_wood_defaults(), + on_construct = function(pos) + local param2 = minetest.get_node(pos).param2 + local meta = minetest.get_meta(pos) + + --[[ This is a workaround for Minetest issue 5894 + . + Apparently if we don't do this, large chests initially don't work when + placed at chunk borders, and some chests randomly don't work after + placing. ]] + -- FIXME: Remove this workaround when the bug has been fixed. + -- BEGIN OF WORKAROUND -- + meta:set_string("workaround", "ignore_me") + meta:set_string("workaround", nil) -- Done to keep metadata clean + -- END OF WORKAROUND -- + + local inv = meta:get_inventory() + inv:set_size("main", 9 * 3) + + --[[ The "input" list is *another* workaround (hahahaha!) around the fact that Minetest + does not support listrings to put items into an alternative list if the first one + happens to be full. See . + This list is a hidden input-only list and immediately puts items into the appropriate chest. + It is only used for listrings and hoppers. This workaround is not that bad because it only + requires a simple “inventory allows” check for large chests.]] + -- FIXME: Refactor the listrings as soon Minetest supports alternative listrings + -- BEGIN OF LISTRING WORKAROUND + inv:set_size("input", 1) + -- END OF LISTRING WORKAROUND + + -- Combine into a double chest if neighbouring another small chest + if minetest.get_node(get_double_container_neighbor_pos(pos, param2, "right")).name == + small_name then + minetest.swap_node(pos, { name = right_name, param2 = param2 }) + local p = get_double_container_neighbor_pos(pos, param2, "right") + minetest.swap_node(p, { name = left_name, param2 = param2 }) + create_entity(p, left_name, double_textures, param2, true, "default_chest", + "mcl_chests_chest", "chest") + elseif minetest.get_node(get_double_container_neighbor_pos(pos, param2, "left")).name == + small_name then + minetest.swap_node(pos, { name = left_name, param2 = param2 }) + create_entity(pos, left_name, double_textures, param2, true, "default_chest", + "mcl_chests_chest", "chest") + local p = get_double_container_neighbor_pos(pos, param2, "left") + minetest.swap_node(p, { name = right_name, param2 = param2 }) + else + minetest.swap_node(pos, { name = small_name, param2 = param2 }) + create_entity(pos, small_name, small_textures, param2, false, "default_chest", + "mcl_chests_chest", "chest") + end + end, + after_place_node = function(pos, placer, itemstack, pointed_thing) + minetest.get_meta(pos):set_string("name", itemstack:get_meta():get_string("name")) + end, + after_dig_node = drop_items_chest, + on_blast = on_chest_blast, + allow_metadata_inventory_move = protection_check_move, + allow_metadata_inventory_take = protection_check_put_take, + allow_metadata_inventory_put = protection_check_put_take, + on_metadata_inventory_move = function(pos, from_list, from_index, to_list, to_index, count, player) + minetest.log("action", player:get_player_name() .. + " moves stuff in chest at " .. minetest.pos_to_string(pos)) + end, + on_metadata_inventory_put = function(pos, listname, index, stack, player) + minetest.log("action", player:get_player_name() .. + " moves stuff to chest at " .. minetest.pos_to_string(pos)) + -- BEGIN OF LISTRING WORKAROUND + if listname == "input" then + local inv = minetest.get_inventory({ type = "node", pos = pos }) + inv:add_item("main", stack) + end + -- END OF LISTRING WORKAROUND + end, + on_metadata_inventory_take = function(pos, listname, index, stack, player) + minetest.log("action", player:get_player_name() .. + " takes stuff from chest at " .. minetest.pos_to_string(pos)) + end, + _mcl_blast_resistance = 2.5, + _mcl_hardness = 2.5, + + on_rightclick = function(pos, node, clicker) + local topnode = minetest.get_node({ x = pos.x, y = pos.y + 1, z = pos.z }) + if topnode and topnode.name and minetest.registered_nodes[topnode.name] then + if minetest.registered_nodes[topnode.name].groups.opaque == 1 then + -- won't open if there is no space from the top + return false + end + end + local name = minetest.get_meta(pos):get_string("name") + if name == "" then + name = S("Chest") + end + + minetest.show_formspec(clicker:get_player_name(), + sf("mcl_chests:%s_%s_%s_%s", canonical_basename, pos.x, pos.y, pos.z), + table.concat({ + "formspec_version[4]", + "size[11.75,10.425]", + + "label[0.375,0.375;" .. F(C(mcl_formspec.label_color, name)) .. "]", + mcl_formspec.get_itemslot_bg_v4(0.375, 0.75, 9, 3), + sf("list[nodemeta:%s,%s,%s;main;0.375,0.75;9,3;]", pos.x, pos.y, pos.z), + "label[0.375,4.7;" .. F(C(mcl_formspec.label_color, S("Inventory"))) .. "]", + mcl_formspec.get_itemslot_bg_v4(0.375, 5.1, 9, 3), + "list[current_player;main;0.375,5.1;9,3;9]", + + mcl_formspec.get_itemslot_bg_v4(0.375, 9.05, 9, 1), + "list[current_player;main;0.375,9.05;9,1;]", + sf("listring[nodemeta:%s,%s,%s;main]", pos.x, pos.y, pos.z), + "listring[current_player;main]", + }) + ) + + if on_rightclick_addendum then + on_rightclick_addendum(pos, node, clicker) + end + + player_chest_open(clicker, pos, small_name, small_textures, node.param2, false, "default_chest", + "mcl_chests_chest") + end, + + on_destruct = function(pos) + close_forms(canonical_basename, pos) + end, + mesecons = mesecons, + on_rotate = simple_rotate, + }) + + minetest.register_node(left_name, { + drawtype = "nodebox", + node_box = { + type = "fixed", + fixed = { -0.4375, -0.5, -0.4375, 0.5, 0.375, 0.4375 }, + }, + tiles = { "blank.png^[resize:16x16" }, + use_texture_alpha = "clip", + _chest_entity_textures = double_textures, + _chest_entity_sound = "default_chest", + _chest_entity_mesh = "mcl_chests_chest", + _chest_entity_animation_type = "chest", + paramtype = "light", + paramtype2 = "facedir", + groups = { + handy = 1, + axey = 1, + container = 2, + not_in_creative_inventory = 1, + material_wood = 1, + flammable = -1, + chest_entity = 1, + double_chest = 1 + }, + drop = drop, + is_ground_content = false, + sounds = mcl_sounds.node_sound_wood_defaults(), + on_construct = function(pos) + local n = minetest.get_node(pos) + local param2 = n.param2 + local p = get_double_container_neighbor_pos(pos, param2, "left") + if not p or minetest.get_node(p).name ~= "mcl_chests:" .. canonical_basename .. "_right" then + n.name = "mcl_chests:" .. canonical_basename .. "_small" + minetest.swap_node(pos, n) + end + create_entity(pos, left_name, double_textures, param2, true, "default_chest", "mcl_chests_chest", "chest") + end, + after_place_node = function(pos, placer, itemstack, pointed_thing) + minetest.get_meta(pos):set_string("name", itemstack:get_meta():get_string("name")) + end, + on_destruct = function(pos) + local n = minetest.get_node(pos) + if n.name == small_name then + return + end + + close_forms(canonical_basename, pos) + + local param2 = n.param2 + local p = get_double_container_neighbor_pos(pos, param2, "left") + if not p or minetest.get_node(p).name ~= "mcl_chests:" .. basename .. "_right" then + return + end + close_forms(canonical_basename, p) + + minetest.swap_node(p, { name = small_name, param2 = param2 }) + create_entity(p, small_name, small_textures, param2, false, "default_chest", "mcl_chests_chest", "chest") + end, + after_dig_node = drop_items_chest, + on_blast = on_chest_blast, + allow_metadata_inventory_move = protection_check_move, + allow_metadata_inventory_take = protection_check_put_take, + allow_metadata_inventory_put = function(pos, listname, index, stack, player) + local name = player:get_player_name() + if minetest.is_protected(pos, name) then + minetest.record_protection_violation(pos, name) + return 0 + -- BEGIN OF LISTRING WORKAROUND + elseif listname == "input" then + local inv = minetest.get_inventory({ type = "node", pos = pos }) + local other_pos = get_double_container_neighbor_pos(pos, minetest.get_node(pos).param2, "left") + local other_inv = minetest.get_inventory({ type = "node", pos = other_pos }) + return limit_put(stack, inv, other_inv) + -- END OF LISTRING WORKAROUND + else + return stack:get_count() + end + end, + on_metadata_inventory_move = function(pos, from_list, from_index, to_list, to_index, count, player) + minetest.log("action", player:get_player_name() .. + " moves stuff in chest at " .. minetest.pos_to_string(pos)) + end, + on_metadata_inventory_put = function(pos, listname, index, stack, player) + minetest.log("action", player:get_player_name() .. + " moves stuff to chest at " .. minetest.pos_to_string(pos)) + -- BEGIN OF LISTRING WORKAROUND + if listname == "input" then + local inv = minetest.get_inventory({ type = "node", pos = pos }) + local other_pos = get_double_container_neighbor_pos(pos, minetest.get_node(pos).param2, "left") + local other_inv = minetest.get_inventory({ type = "node", pos = other_pos }) + + inv:set_stack("input", 1, nil) + + double_chest_add_item(inv, other_inv, "main", stack) + end + -- END OF LISTRING WORKAROUND + end, + on_metadata_inventory_take = function(pos, listname, index, stack, player) + minetest.log("action", player:get_player_name() .. + " takes stuff from chest at " .. minetest.pos_to_string(pos)) + end, + _mcl_blast_resistance = 2.5, + _mcl_hardness = 2.5, + + on_rightclick = function(pos, node, clicker) + local pos_other = get_double_container_neighbor_pos(pos, node.param2, "left") + local above_def = minetest.registered_nodes[ + minetest.get_node({ x = pos.x, y = pos.y + 1, z = pos.z }).name + ] + local above_def_other = minetest.registered_nodes[ + minetest.get_node({ x = pos_other.x, y = pos_other.y + 1, z = pos_other.z }).name + ] + + if (not above_def or above_def.groups.opaque == 1 or not above_def_other + or above_def_other.groups.opaque == 1) then + -- won't open if there is no space from the top + return false + end + + local name = minetest.get_meta(pos):get_string("name") + if name == "" then -- if empty after that ^ + name = minetest.get_meta(pos_other):get_string("name") + end if name == "" then -- if STILL empty after that ^ + name = S("Large Chest") + end + + minetest.show_formspec(clicker:get_player_name(), + sf("mcl_chests:%s_%s_%s_%s", canonical_basename, pos.x, pos.y, pos.z), + table.concat({ + "formspec_version[4]", + "size[11.75,14.15]", + + "label[0.375,0.375;" .. F(C(mcl_formspec.label_color, name)) .. "]", + mcl_formspec.get_itemslot_bg_v4(0.375, 0.75, 9, 3), + sf("list[nodemeta:%s,%s,%s;main;0.375,0.75;9,3;]", pos.x, pos.y, pos.z), + mcl_formspec.get_itemslot_bg_v4(0.375, 4.5, 9, 3), + sf("list[nodemeta:%s,%s,%s;main;0.375,4.5;9,3;]", pos_other.x, pos_other.y, pos_other.z), + "label[0.375,8.45;" .. F(C(mcl_formspec.label_color, S("Inventory"))) .. "]", + mcl_formspec.get_itemslot_bg_v4(0.375, 8.825, 9, 3), + "list[current_player;main;0.375,8.825;9,3;9]", + + mcl_formspec.get_itemslot_bg_v4(0.375, 12.775, 9, 1), + "list[current_player;main;0.375,12.775;9,1;]", + + --BEGIN OF LISTRING WORKAROUND + "listring[current_player;main]", + sf("listring[nodemeta:%s,%s,%s;input]", pos.x, pos.y, pos.z), + --END OF LISTRING WORKAROUND + + "listring[current_player;main]" .. + sf("listring[nodemeta:%s,%s,%s;main]", pos.x, pos.y, pos.z), + "listring[current_player;main]", + sf("listring[nodemeta:%s,%s,%s;main]", pos_other.x, pos_other.y, pos_other.z), + }) + ) + + if on_rightclick_addendum_left then + on_rightclick_addendum_left(pos, node, clicker) + end + + player_chest_open(clicker, pos, left_name, double_textures, node.param2, true, "default_chest", + "mcl_chests_chest") + end, + mesecons = mesecons, + on_rotate = no_rotate, + _mcl_hoppers_on_try_pull = function(pos, hop_pos, hop_inv, hop_list) + local meta = minetest.get_meta(pos) + local inv = meta:get_inventory() + + local stack_id = mcl_util.select_stack(inv, "main", hop_inv, hop_list) + if stack_id ~= nil then + return inv, "main", stack_id + end + + local node = minetest.get_node(pos) + local pos_other = get_double_container_neighbor_pos(pos, node.param2, "left") + local meta_other = minetest.get_meta(pos_other) + local inv_other = meta_other:get_inventory() + stack_id = mcl_util.select_stack(inv_other, "main", hop_inv, hop_list) + return inv_other, "main", stack_id + end, + _mcl_hoppers_on_try_push = function(pos, hop_pos, hop_inv, hop_list) + local meta = minetest.get_meta(pos) + local inv = meta:get_inventory() + + local stack_id = mcl_util.select_stack(hop_inv, hop_list, inv, "main", nil, 1) + if stack_id ~= nil then + return inv, "main", stack_id + end + + local node = minetest.get_node(pos) + local pos_other = get_double_container_neighbor_pos(pos, node.param2, "left") + local meta_other = minetest.get_meta(pos_other) + local inv_other = meta_other:get_inventory() + stack_id = mcl_util.select_stack(hop_inv, hop_list, inv_other, "main", nil, 1) + return inv_other, "main", stack_id + end, + }) + + minetest.register_node(right_name, { + drawtype = "nodebox", + paramtype = "light", + paramtype2 = "facedir", + node_box = { + type = "fixed", + fixed = { -0.5, -0.5, -0.4375, 0.4375, 0.375, 0.4375 }, + }, + tiles = { "blank.png^[resize:16x16" }, + use_texture_alpha = "clip", + groups = { + handy = 1, + axey = 1, + container = 2, + not_in_creative_inventory = 1, + material_wood = 1, + flammable = -1, + double_chest = 2 + }, + drop = drop, + is_ground_content = false, + sounds = mcl_sounds.node_sound_wood_defaults(), + on_construct = function(pos) + local n = minetest.get_node(pos) + local param2 = n.param2 + local p = get_double_container_neighbor_pos(pos, param2, "right") + if not p or minetest.get_node(p).name ~= left_name then + n.name = small_name + minetest.swap_node(pos, n) + end + end, + after_place_node = function(pos, placer, itemstack, pointed_thing) + minetest.get_meta(pos):set_string("name", itemstack:get_meta():get_string("name")) + end, + on_destruct = function(pos) + local n = minetest.get_node(pos) + if n.name == small_name then + return + end + + close_forms(canonical_basename, pos) + + local param2 = n.param2 + local p = get_double_container_neighbor_pos(pos, param2, "right") + if not p or minetest.get_node(p).name ~= left_name then + return + end + close_forms(canonical_basename, p) + + minetest.swap_node(p, { name = small_name, param2 = param2 }) + create_entity(p, small_name, small_textures, param2, false, "default_chest", "mcl_chests_chest", "chest") + end, + after_dig_node = drop_items_chest, + on_blast = on_chest_blast, + allow_metadata_inventory_move = protection_check_move, + allow_metadata_inventory_take = protection_check_put_take, + allow_metadata_inventory_put = function(pos, listname, index, stack, player) + local name = player:get_player_name() + if minetest.is_protected(pos, name) then + minetest.record_protection_violation(pos, name) + return 0 + -- BEGIN OF LISTRING WORKAROUND + elseif listname == "input" then + local other_pos = get_double_container_neighbor_pos(pos, minetest.get_node(pos).param2, "right") + local other_inv = minetest.get_inventory({ type = "node", pos = other_pos }) + local inv = minetest.get_inventory({ type = "node", pos = pos }) + return limit_put(stack, other_inv, inv) + -- END OF LISTRING WORKAROUND + else + return stack:get_count() + end + end, + on_metadata_inventory_move = function(pos, from_list, from_index, to_list, to_index, count, player) + minetest.log("action", player:get_player_name() .. + " moves stuff in chest at " .. minetest.pos_to_string(pos)) + end, + on_metadata_inventory_put = function(pos, listname, index, stack, player) + minetest.log("action", player:get_player_name() .. + " moves stuff to chest at " .. minetest.pos_to_string(pos)) + -- BEGIN OF LISTRING WORKAROUND + if listname == "input" then + local other_pos = get_double_container_neighbor_pos(pos, minetest.get_node(pos).param2, "right") + local other_inv = minetest.get_inventory({ type = "node", pos = other_pos }) + local inv = minetest.get_inventory({ type = "node", pos = pos }) + + inv:set_stack("input", 1, nil) + + double_chest_add_item(other_inv, inv, "main", stack) + end + -- END OF LISTRING WORKAROUND + end, + on_metadata_inventory_take = function(pos, listname, index, stack, player) + minetest.log("action", player:get_player_name() .. + " takes stuff from chest at " .. minetest.pos_to_string(pos)) + end, + _mcl_blast_resistance = 2.5, + _mcl_hardness = 2.5, + + on_rightclick = function(pos, node, clicker) + local pos_other = get_double_container_neighbor_pos(pos, node.param2, "right") + local above_def = minetest.registered_nodes[ + minetest.get_node({ x = pos.x, y = pos.y + 1, z = pos.z }).name + ] + local above_def_other = minetest.registered_nodes[ + minetest.get_node({ x = pos_other.x, y = pos_other.y + 1, z = pos_other.z }).name + ] + + if (not above_def or above_def.groups.opaque == 1 or not above_def_other + or above_def_other.groups.opaque == 1) then + -- won't open if there is no space from the top + return false + end + + local name = minetest.get_meta(pos):get_string("name") + if name == "" then -- if empty after that ^ + name = minetest.get_meta(pos_other):get_string("name") + end if name == "" then -- if STILL empty after that ^ + name = S("Large Chest") + end + + minetest.show_formspec(clicker:get_player_name(), + sf("mcl_chests:%s_%s_%s_%s", canonical_basename, pos.x, pos.y, pos.z), + table.concat({ + "formspec_version[4]", + "size[11.75,14.15]", + + "label[0.375,0.375;" .. F(C(mcl_formspec.label_color, name)) .. "]", + mcl_formspec.get_itemslot_bg_v4(0.375, 0.75, 9, 3), + sf("list[nodemeta:%s,%s,%s;main;0.375,0.75;9,3;]", pos_other.x, pos_other.y, pos_other.z), + mcl_formspec.get_itemslot_bg_v4(0.375, 4.5, 9, 3), + sf("list[nodemeta:%s,%s,%s;main;0.375,4.5;9,3;]", pos.x, pos.y, pos.z), + "label[0.375,8.45;" .. F(C(mcl_formspec.label_color, S("Inventory"))) .. "]", + mcl_formspec.get_itemslot_bg_v4(0.375, 8.825, 9, 3), + "list[current_player;main;0.375,8.825;9,3;9]", + + mcl_formspec.get_itemslot_bg_v4(0.375, 12.775, 9, 1), + "list[current_player;main;0.375,12.775;9,1;]", + + --BEGIN OF LISTRING WORKAROUND + "listring[current_player;main]", + sf("listring[nodemeta:%s,%s,%s;input]", pos.x, pos.y, pos.z), + --END OF LISTRING WORKAROUND + + "listring[current_player;main]" .. + sf("listring[nodemeta:%s,%s,%s;main]", pos_other.x, pos_other.y, pos_other.z), + "listring[current_player;main]", + sf("listring[nodemeta:%s,%s,%s;main]", pos.x, pos.y, pos.z), + }) + ) + + if on_rightclick_addendum_right then + on_rightclick_addendum_right(pos, node, clicker) + end + + player_chest_open(clicker, pos_other, left_name, double_textures, node.param2, true, "default_chest", + "mcl_chests_chest") + end, + mesecons = mesecons, + on_rotate = no_rotate, + _mcl_hoppers_on_try_pull = function(pos, hop_pos, hop_inv, hop_list) + local node = minetest.get_node(pos) + local pos_other = get_double_container_neighbor_pos(pos, node.param2, "right") + local meta_other = minetest.get_meta(pos_other) + local inv_other = meta_other:get_inventory() + + local stack_id = mcl_util.select_stack(inv_other, "main", hop_inv, hop_list) + if stack_id ~= nil then + return inv_other, "main", stack_id + end + + local meta = minetest.get_meta(pos) + local inv = meta:get_inventory() + stack_id = mcl_util.select_stack(inv, "main", hop_inv, hop_list) + return inv, "main", stack_id + end, + _mcl_hoppers_on_try_push = function(pos, hop_pos, hop_inv, hop_list) + local node = minetest.get_node(pos) + local pos_other = get_double_container_neighbor_pos(pos, node.param2, "right") + local meta_other = minetest.get_meta(pos_other) + local inv_other = meta_other:get_inventory() + + local stack_id = mcl_util.select_stack(hop_inv, hop_list, inv_other, "main", nil, 1) + if stack_id ~= nil then + return inv_other, "main", stack_id + end + + local meta = minetest.get_meta(pos) + local inv = meta:get_inventory() + stack_id = mcl_util.select_stack(hop_inv, hop_list, inv, "main", nil, 1) + return inv, "main", stack_id + end, + }) + + if doc then + doc.add_entry_alias("nodes", small_name, "nodes", left_name) + doc.add_entry_alias("nodes", small_name, "nodes", right_name) + end +end + +-- Returns false if itemstack is a shulker box +function mcl_chests.is_not_shulker_box(stack) + local g = minetest.get_item_group(stack:get_name(), "shulker_box") + return g == 0 or g == nil +end diff --git a/mods/ITEMS/mcl_chests/chests.lua b/mods/ITEMS/mcl_chests/chests.lua new file mode 100644 index 000000000..c66f336da --- /dev/null +++ b/mods/ITEMS/mcl_chests/chests.lua @@ -0,0 +1,108 @@ +local S = minetest.get_translator(minetest.get_current_modname()) +local F = minetest.formspec_escape +local C = minetest.colorize +local get_double_container_neighbor_pos = mcl_util.get_double_container_neighbor_pos +local trapped_chest_mesecons_rules = mesecon.rules.pplate + +local chestusage = S("To access its inventory, rightclick it. When broken, the items will drop out.") + +mcl_chests.register_chest("chest", + S("Chest"), + 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."), + chestusage, + S("27 inventory slots") .. "\n" .. S("Can be combined to a large chest"), + { + small = mcl_chests.tiles.chest_normal_small, + double = mcl_chests.tiles.chest_normal_double, + inv = { "default_chest_top.png", "mcl_chests_chest_bottom.png", + "mcl_chests_chest_right.png", "mcl_chests_chest_left.png", + "mcl_chests_chest_back.png", "default_chest_front.png" }, + }, + false +) + +local traptiles = { + small = mcl_chests.tiles.chest_trapped_small, + double = mcl_chests.tiles.chest_trapped_double, +} + +mcl_chests.register_chest("trapped_chest", + S("Trapped Chest"), + 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."), + chestusage, + S("27 inventory slots") .. + "\n" .. S("Can be combined to a large chest") .. "\n" .. S("Emits a redstone signal when opened"), + traptiles, + nil, + { + receptor = { + state = mesecon.state.off, + rules = trapped_chest_mesecons_rules, + }, + }, + function(pos, node, clicker) + minetest.swap_node(pos, { name = "mcl_chests:trapped_chest_on_small", param2 = node.param2 }) + mcl_chests.find_or_create_entity(pos, "mcl_chests:trapped_chest_on_small", { "mcl_chests_trapped.png" }, node.param2, false, + "default_chest", "mcl_chests_chest", "chest"):reinitialize("mcl_chests:trapped_chest_on_small") + mesecon.receptor_on(pos, trapped_chest_mesecons_rules) + end, + function(pos, node, clicker) + local meta = minetest.get_meta(pos) + meta:set_int("players", 1) + + minetest.swap_node(pos, { name = "mcl_chests:trapped_chest_on_left", param2 = node.param2 }) + mcl_chests.find_or_create_entity(pos, "mcl_chests:trapped_chest_on_left", mcl_chests.tiles.chest_trapped_double, node.param2, true, + "default_chest", "mcl_chests_chest", "chest"):reinitialize("mcl_chests:trapped_chest_on_left") + mesecon.receptor_on(pos, trapped_chest_mesecons_rules) + + local pos_other = get_double_container_neighbor_pos(pos, node.param2, "left") + minetest.swap_node(pos_other, { name = "mcl_chests:trapped_chest_on_right", param2 = node.param2 }) + mesecon.receptor_on(pos_other, trapped_chest_mesecons_rules) + end, + function(pos, node, clicker) + local pos_other = get_double_container_neighbor_pos(pos, node.param2, "right") + + minetest.swap_node(pos, { name = "mcl_chests:trapped_chest_on_right", param2 = node.param2 }) + mesecon.receptor_on(pos, trapped_chest_mesecons_rules) + + minetest.swap_node(pos_other, { name = "mcl_chests:trapped_chest_on_left", param2 = node.param2 }) + mcl_chests.find_or_create_entity(pos_other, "mcl_chests:trapped_chest_on_left", mcl_chests.tiles.chest_trapped_double, node.param2, + true, + "default_chest", "mcl_chests_chest", "chest"):reinitialize("mcl_chests:trapped_chest_on_left") + mesecon.receptor_on(pos_other, trapped_chest_mesecons_rules) + end +) + +mcl_chests.register_chest("trapped_chest_on", + nil, nil, nil, nil, traptiles, true, + { + receptor = { + state = mesecon.state.on, + rules = trapped_chest_mesecons_rules, + }, + }, + nil, nil, nil, + "trapped_chest", + "trapped_chest" +) + +minetest.register_craft({ + output = "mcl_chests:chest", + recipe = { + { "group:wood", "group:wood", "group:wood" }, + { "group:wood", "", "group:wood" }, + { "group:wood", "group:wood", "group:wood" }, + }, +}) + +minetest.register_craft({ + type = "fuel", + recipe = "mcl_chests:chest", + burntime = 15, +}) + +minetest.register_craft({ + type = "fuel", + recipe = "mcl_chests:trapped_chest", + burntime = 15, +}) diff --git a/mods/ITEMS/mcl_chests/ender.lua b/mods/ITEMS/mcl_chests/ender.lua new file mode 100644 index 000000000..c126563ca --- /dev/null +++ b/mods/ITEMS/mcl_chests/ender.lua @@ -0,0 +1,128 @@ +local S = minetest.get_translator(minetest.get_current_modname()) +local F = minetest.formspec_escape +local C = minetest.colorize + +local 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. +]]) + +minetest.register_node("mcl_chests:ender_chest", { + description = S("Ender Chest"), + _tt_help = S("27 interdimensional inventory slots") .. + "\n" .. S("Put items inside, retrieve them from any ender chest"), + _doc_items_longdesc = longdesc, + _doc_items_usagehelp = S("Rightclick the ender chest to access your personal interdimensional inventory."), + drawtype = "mesh", + mesh = "mcl_chests_chest.b3d", + tiles = mcl_chests.tiles.chest_ender_small, + use_texture_alpha = "opaque", + paramtype = "light", + paramtype2 = "facedir", + groups = { deco_block = 1 }, + sounds = mcl_sounds.node_sound_stone_defaults(), + on_construct = function(pos) + local node = minetest.get_node(pos) + node.name = "mcl_chests:ender_chest_small" + minetest.set_node(pos, node) + end, +}) + +local formspec_ender_chest = table.concat({ + "formspec_version[4]", + "size[11.75,10.425]", + + "label[0.375,0.375;" .. F(C(mcl_formspec.label_color, S("Ender Chest"))) .. "]", + mcl_formspec.get_itemslot_bg_v4(0.375, 0.75, 9, 3), + "list[current_player;enderchest;0.375,0.75;9,3;]", + "label[0.375,4.7;" .. F(C(mcl_formspec.label_color, S("Inventory"))) .. "]", + mcl_formspec.get_itemslot_bg_v4(0.375, 5.1, 9, 3), + "list[current_player;main;0.375,5.1;9,3;9]", + + mcl_formspec.get_itemslot_bg_v4(0.375, 9.05, 9, 1), + "list[current_player;main;0.375,9.05;9,1;]", + + "listring[current_player;enderchest]", + "listring[current_player;main]", +}) + +minetest.register_node("mcl_chests:ender_chest_small", { + description = S("Ender Chest"), + _tt_help = S("27 interdimensional inventory slots") .. + "\n" .. S("Put items inside, retrieve them from any ender chest"), + _doc_items_longdesc = longdesc, + _doc_items_usagehelp = S("Rightclick the ender chest to access your personal interdimensional inventory."), + drawtype = "nodebox", + node_box = { + type = "fixed", + fixed = { -0.4375, -0.5, -0.4375, 0.4375, 0.375, 0.4375 }, + }, + _chest_entity_textures = mcl_chests.tiles.ender_chest_texture, + _chest_entity_sound = "mcl_chests_enderchest", + _chest_entity_mesh = "mcl_chests_chest", + _chest_entity_animation_type = "chest", + tiles = { "blank.png^[resize:16x16" }, + use_texture_alpha = "clip", + -- Note: The “container” group is missing here because the ender chest does not + -- have an inventory on its own + groups = { pickaxey = 1, deco_block = 1, material_stone = 1, chest_entity = 1, not_in_creative_inventory = 1 }, + is_ground_content = false, + paramtype = "light", + light_source = 7, + paramtype2 = "facedir", + sounds = mcl_sounds.node_sound_stone_defaults(), + drop = "mcl_core:obsidian 8", + on_construct = function(pos) + mcl_chests.create_entity(pos, "mcl_chests:ender_chest_small", mcl_chests.tiles.ender_chest_texture, + minetest.get_node(pos).param2, false, "mcl_chests_enderchest", "mcl_chests_chest", "chest") + end, + on_rightclick = function(pos, node, clicker) + if minetest.registered_nodes[minetest.get_node(vector.offset(pos, 0, 1, 0)).name].groups.opaque == 1 then + -- won't open if there is no space from the top + return false + end + minetest.show_formspec(clicker:get_player_name(), "mcl_chests:ender_chest_" .. clicker:get_player_name(), + formspec_ender_chest) + mcl_chests.player_chest_open(clicker, pos, "mcl_chests:ender_chest_small", + mcl_chests.tiles.ender_chest_texture, node.param2, false, "mcl_chests_enderchest", + "mcl_chests_chest") + end, + on_receive_fields = function(pos, formname, fields, sender) + if fields.quit then + mcl_chests.player_chest_close(sender) + end + end, + _mcl_blast_resistance = 3000, + _mcl_hardness = 22.5, + _mcl_silk_touch_drop = { "mcl_chests:ender_chest" }, + on_rotate = simple_rotate, +}) + +minetest.register_on_joinplayer(function(player) + local inv = player:get_inventory() + inv:set_size("enderchest", 9 * 3) +end) + +minetest.register_allow_player_inventory_action(function(player, action, inv, info) + if inv:get_location().type == "player" and ( + action == "move" and (info.from_list == "enderchest" or info.to_list == "enderchest") + or action == "put" and info.listname == "enderchest" + or action == "take" and info.listname == "enderchest") then + local def = player:get_wielded_item():get_definition() + local range = (def and def.range or player:get_inventory():get_stack("hand", 1):get_definition().range) + 1 + if not minetest.find_node_near(player:get_pos(), range, "mcl_chests:ender_chest_small", true) then + return 0 + end + end +end) + +minetest.register_craft({ + output = "mcl_chests:ender_chest", + recipe = { + { "mcl_core:obsidian", "mcl_core:obsidian", "mcl_core:obsidian" }, + { "mcl_core:obsidian", "mcl_end:ender_eye", "mcl_core:obsidian" }, + { "mcl_core:obsidian", "mcl_core:obsidian", "mcl_core:obsidian" }, + }, +}) diff --git a/mods/ITEMS/mcl_chests/init.lua b/mods/ITEMS/mcl_chests/init.lua index f7186eea4..3ca1cf4da 100644 --- a/mods/ITEMS/mcl_chests/init.lua +++ b/mods/ITEMS/mcl_chests/init.lua @@ -1,32 +1,11 @@ --- TODO --- ==== --- [x] Take another full look at this code and clean-up even more. --- [ ] Expose more functions that are currently local. --- [ ] Split this giant 1.6k-line file into: --- - init.lua (dofiles and LBMs) --- - api.lua (functions) --- - chests.lua (normal/double and trapped chests) --- - ender.lua (ender chest registration) --- - shulkers.lua (self-explanatory) --- [ ] Take a new look into , maybe it can be fixed here. --- [ ] File a Pull Request for this branch. --- ==== - -local S = minetest.get_translator(minetest.get_current_modname()) -local F = minetest.formspec_escape -local C = minetest.colorize - local string = string local table = table local math = math -local sf = string.format local sm = string.match mcl_chests = {} -local get_double_container_neighbor_pos = mcl_util.get_double_container_neighbor_pos - -- Christmas chest setup local it_is_christmas = mcl_util.is_it_christmas() @@ -55,1036 +34,13 @@ for k,v in pairs(tiles) do end end +mcl_chests.tiles = tiles - --- ======= -- --- api.lua -- --- ======= -- - - - --- Chest Entity --- ============ --- This is necessary to show the chest as an animated mesh, as Minetest doesn't --- support assigning animated meshes to nodes directly. We're bypassing this --- limitation by giving each chest its own entity, and making the chest node --- itself fully transparent. -local animated_chests = (minetest.settings:get_bool("animated_chests") ~= false) -local entity_animations = { - shulker = { - speed = 50, - open = { x = 45, y = 95 }, - close = { x = 95, y = 145 }, - }, - chest = { - speed = 25, - open = { x = 0, y = 7 }, - close = { x = 13, y = 20 }, - }, -} - -minetest.register_entity("mcl_chests:chest", { - initial_properties = { - visual = "mesh", - pointable = false, - physical = false, - static_save = false, - }, - - set_animation = function(self, animname) - local anim_table = entity_animations[self.animation_type] - local anim = anim_table[animname] - if not anim then return end - self.object:set_animation(anim, anim_table.speed, 0, false) - end, - - open = function(self, playername) - self.players[playername] = true - if not self.is_open then - self:set_animation("open") - minetest.sound_play(self.sound_prefix .. "_open", { pos = self.node_pos, gain = 0.5, max_hear_distance = 16 }, - true) - self.is_open = true - end - end, - - close = function(self, playername) - local playerlist = self.players - playerlist[playername] = nil - if self.is_open then - if next(playerlist) then - return - end - self:set_animation("close") - minetest.sound_play(self.sound_prefix .. "_close", - { pos = self.node_pos, gain = 0.3, max_hear_distance = 16 }, - true) - self.is_open = false - end - end, - - initialize = function(self, node_pos, node_name, textures, dir, double, sound_prefix, mesh_prefix, animation_type) - self.node_pos = node_pos - self.node_name = node_name - self.sound_prefix = sound_prefix - self.animation_type = animation_type - self.object:set_properties({ - textures = textures, - mesh = mesh_prefix .. (double and "_double" or "") .. ".b3d", - }) - self:set_yaw(dir) - end, - - reinitialize = function(self, node_name) - self.node_name = node_name - end, - - set_yaw = function(self, dir) - self.object:set_yaw(minetest.dir_to_yaw(dir)) - end, - - check = function(self) - local node_pos, node_name = self.node_pos, self.node_name - if not node_pos or not node_name then - return false - end - local node = minetest.get_node(node_pos) - if node.name ~= node_name then - return false - end - return true - end, - - on_activate = function(self) - self.object:set_armor_groups({ immortal = 1 }) - self.players = {} - end, - - on_step = function(self, dtime) - if not self:check() then - self.object:remove() - end - end -}) - -local function get_entity_pos(pos, dir, double) - pos = vector.copy(pos) - if double then - local add, mul, vec, cross = vector.add, vector.multiply, vector.new, vector.cross - pos = add(pos, mul(cross(dir, vec(0, 1, 0)), -0.5)) - end - return pos -end - -local function find_entity(pos) - for _, obj in pairs(minetest.get_objects_inside_radius(pos, 0)) do - local luaentity = obj:get_luaentity() - if luaentity and luaentity.name == "mcl_chests:chest" then - return luaentity - end - end -end - -local function get_entity_info(pos, param2, double, dir, entity_pos) - dir = dir or minetest.facedir_to_dir(param2) - return dir, get_entity_pos(pos, dir, double) -end - -local function create_entity(pos, node_name, textures, param2, double, sound_prefix, mesh_prefix, animation_type, dir, - entity_pos) - dir, entity_pos = get_entity_info(pos, param2, double, dir, entity_pos) - local obj = minetest.add_entity(entity_pos, "mcl_chests:chest") - local luaentity = obj:get_luaentity() - luaentity:initialize(pos, node_name, textures, dir, double, sound_prefix, mesh_prefix, animation_type) - return luaentity -end - -local function find_or_create_entity(pos, node_name, textures, param2, double, sound_prefix, mesh_prefix, - animation_type, dir, entity_pos) - dir, entity_pos = get_entity_info(pos, param2, double, dir, entity_pos) - return find_entity(entity_pos) or - create_entity(pos, node_name, textures, param2, double, sound_prefix, mesh_prefix, animation_type, dir, - entity_pos) -end - -local no_rotate, simple_rotate -if screwdriver then - no_rotate = screwdriver.disallow - simple_rotate = function(pos, node, user, mode, new_param2) - if screwdriver.rotate_simple(pos, node, user, mode, new_param2) ~= false then - local nodename = node.name - local nodedef = minetest.registered_nodes[nodename] - local dir = minetest.facedir_to_dir(new_param2) - find_or_create_entity(pos, nodename, nodedef._chest_entity_textures, new_param2, false, - nodedef._chest_entity_sound, - nodedef._chest_entity_mesh, nodedef._chest_entity_animation_type, dir):set_yaw(dir) - else - return false - end - end -end - ---[[ List of open chests. -Key: Player name -Value: - If player is using a chest: { pos = } - Otherwise: nil ]] -local open_chests = {} - --- To be called if a player opened a chest -local function player_chest_open(player, pos, node_name, textures, param2, double, sound, mesh, shulker) - local name = player:get_player_name() - open_chests[name] = { - pos = pos, - node_name = node_name, - textures = textures, - param2 = param2, - double = double, - sound = sound, - mesh = mesh, - shulker = shulker - } - if animated_chests then - local dir = minetest.facedir_to_dir(param2) - find_or_create_entity(pos, node_name, textures, param2, double, sound, mesh, shulker and "shulker" or "chest", - dir): - open(name) - end -end - --- Simple protection checking functions -local function protection_check_move(pos, from_list, from_index, to_list, to_index, count, player) - local name = player:get_player_name() - if minetest.is_protected(pos, name) then - minetest.record_protection_violation(pos, name) - return 0 - else - return count - end -end - -local function protection_check_put_take(pos, listname, index, stack, player) - local name = player:get_player_name() - if minetest.is_protected(pos, name) then - minetest.record_protection_violation(pos, name) - return 0 - else - return stack:get_count() - end -end - -local trapped_chest_mesecons_rules = mesecon.rules.pplate - --- To be called when a chest is closed (only relevant for trapped chest atm) -local function chest_update_after_close(pos) - local node = minetest.get_node(pos) - - if node.name == "mcl_chests:trapped_chest_on_small" then - minetest.swap_node(pos, { name = "mcl_chests:trapped_chest_small", param2 = node.param2 }) - find_or_create_entity(pos, "mcl_chests:trapped_chest_small", { "mcl_chests_trapped.png" }, node.param2, false, - "default_chest", "mcl_chests_chest", "chest"):reinitialize("mcl_chests:trapped_chest_small") - mesecon.receptor_off(pos, trapped_chest_mesecons_rules) - elseif node.name == "mcl_chests:trapped_chest_on_left" then - minetest.swap_node(pos, { name = "mcl_chests:trapped_chest_left", param2 = node.param2 }) - find_or_create_entity(pos, "mcl_chests:trapped_chest_left", tiles.chest_trapped_double, node.param2, true, - "default_chest", "mcl_chests_chest", "chest"):reinitialize("mcl_chests:trapped_chest_left") - mesecon.receptor_off(pos, trapped_chest_mesecons_rules) - - local pos_other = get_double_container_neighbor_pos(pos, node.param2, "left") - minetest.swap_node(pos_other, { name = "mcl_chests:trapped_chest_right", param2 = node.param2 }) - mesecon.receptor_off(pos_other, trapped_chest_mesecons_rules) - elseif node.name == "mcl_chests:trapped_chest_on_right" then - minetest.swap_node(pos, { name = "mcl_chests:trapped_chest_right", param2 = node.param2 }) - mesecon.receptor_off(pos, trapped_chest_mesecons_rules) - - local pos_other = get_double_container_neighbor_pos(pos, node.param2, "right") - minetest.swap_node(pos_other, { name = "mcl_chests:trapped_chest_left", param2 = node.param2 }) - find_or_create_entity(pos_other, "mcl_chests:trapped_chest_left", tiles.chest_trapped_double, node.param2, true, - "default_chest", "mcl_chests_chest", "chest"):reinitialize("mcl_chests:trapped_chest_left") - mesecon.receptor_off(pos_other, trapped_chest_mesecons_rules) - end -end - --- To be called if a player closed a chest -local function player_chest_close(player) - local name = player:get_player_name() - local open_chest = open_chests[name] - if open_chest == nil then - return - end - if animated_chests then - find_or_create_entity(open_chest.pos, open_chest.node_name, open_chest.textures, open_chest.param2, - open_chest.double, - open_chest.sound, open_chest.mesh, open_chest.shulker and "shulker" or "chest"):close(name) - end - chest_update_after_close(open_chest.pos) - - open_chests[name] = nil -end - --- This is a helper function to register both chests and trapped chests. Trapped chests will make use of the additional parameters -function mcl_chests.register_chest(basename, desc, longdesc, usagehelp, tt_help, tiles_table, hidden, mesecons, - on_rightclick_addendum, on_rightclick_addendum_left, - on_rightclick_addendum_right, drop, canonical_basename) - if not drop then - drop = "mcl_chests:" .. basename - else - drop = "mcl_chests:" .. drop - end - -- The basename of the "canonical" version of the node, if set (e.g.: trapped_chest_on → trapped_chest). - -- Used to get a shared formspec ID and to swap the node back to the canonical version in on_construct. - if not canonical_basename then - canonical_basename = basename - end - - local function double_chest_add_item(top_inv, bottom_inv, listname, stack) - if not stack or stack:is_empty() then return end - - local name = stack:get_name() - - local function top_off(inv, stack) - for c, chest_stack in ipairs(inv:get_list(listname)) do - if stack:is_empty() then - break - end - - if chest_stack:get_name() == name and chest_stack:get_free_space() > 0 then - stack = chest_stack:add_item(stack) - inv:set_stack(listname, c, chest_stack) - end - end - - return stack - end - - stack = top_off(top_inv, stack) - stack = top_off(bottom_inv, stack) - - if not stack:is_empty() then - stack = top_inv:add_item(listname, stack) - if not stack:is_empty() then - bottom_inv:add_item(listname, stack) - end - end - end - - local drop_items_chest = mcl_util.drop_items_from_meta_container("main") - - local function on_chest_blast(pos) - local node = minetest.get_node(pos) - drop_items_chest(pos, node) - minetest.remove_node(pos) - end - - local function limit_put_list(stack, list) - for _, other in ipairs(list) do - stack = other:add_item(stack) - if stack:is_empty() then - break - end - end - return stack - end - - local function limit_put(stack, inv1, inv2) - local leftover = ItemStack(stack) - leftover = limit_put_list(leftover, inv1:get_list("main")) - leftover = limit_put_list(leftover, inv2:get_list("main")) - return stack:get_count() - leftover:get_count() - end - - local small_name = "mcl_chests:" .. basename .. "_small" - local small_textures = tiles_table.small - local left_name = "mcl_chests:" .. basename .. "_left" - local right_name = "mcl_chests:" .. basename .. "_right" - local double_textures = tiles_table.double - - minetest.register_node("mcl_chests:" .. basename, { - description = desc, - _tt_help = tt_help, - _doc_items_longdesc = longdesc, - _doc_items_usagehelp = usagehelp, - _doc_items_hidden = hidden, - drawtype = "mesh", - mesh = "mcl_chests_chest.b3d", - tiles = small_textures, - use_texture_alpha = "opaque", - paramtype = "light", - paramtype2 = "facedir", - sounds = mcl_sounds.node_sound_wood_defaults(), - groups = { deco_block = 1 }, - on_construct = function(pos, node) - local node = minetest.get_node(pos) - node.name = small_name - minetest.set_node(pos, node) - end, - after_place_node = function(pos, placer, itemstack, pointed_thing) - minetest.get_meta(pos):set_string("name", itemstack:get_meta():get_string("name")) - end, - }) - - local function close_forms(canonical_basename, pos) - local players = minetest.get_connected_players() - for p = 1, #players do - if vector.distance(players[p]:get_pos(), pos) <= 30 then - minetest.close_formspec(players[p]:get_player_name(), - "mcl_chests:" .. canonical_basename .. "_" .. pos.x .. "_" .. pos.y .. "_" .. pos.z) - end - end - end - - minetest.register_node(small_name, { - description = desc, - _tt_help = tt_help, - _doc_items_longdesc = longdesc, - _doc_items_usagehelp = usagehelp, - _doc_items_hidden = hidden, - drawtype = "nodebox", - node_box = { - type = "fixed", - fixed = { -0.4375, -0.5, -0.4375, 0.4375, 0.375, 0.4375 }, - }, - tiles = { "blank.png^[resize:16x16" }, - use_texture_alpha = "clip", - _chest_entity_textures = small_textures, - _chest_entity_sound = "default_chest", - _chest_entity_mesh = "mcl_chests_chest", - _chest_entity_animation_type = "chest", - paramtype = "light", - paramtype2 = "facedir", - drop = drop, - groups = { - handy = 1, - axey = 1, - container = 2, - deco_block = 1, - material_wood = 1, - flammable = -1, - chest_entity = 1, - not_in_creative_inventory = 1 - }, - is_ground_content = false, - sounds = mcl_sounds.node_sound_wood_defaults(), - on_construct = function(pos) - local param2 = minetest.get_node(pos).param2 - local meta = minetest.get_meta(pos) - - --[[ This is a workaround for Minetest issue 5894 - . - Apparently if we don't do this, large chests initially don't work when - placed at chunk borders, and some chests randomly don't work after - placing. ]] - -- FIXME: Remove this workaround when the bug has been fixed. - -- BEGIN OF WORKAROUND -- - meta:set_string("workaround", "ignore_me") - meta:set_string("workaround", nil) -- Done to keep metadata clean - -- END OF WORKAROUND -- - - local inv = meta:get_inventory() - inv:set_size("main", 9 * 3) - - --[[ The "input" list is *another* workaround (hahahaha!) around the fact that Minetest - does not support listrings to put items into an alternative list if the first one - happens to be full. See . - This list is a hidden input-only list and immediately puts items into the appropriate chest. - It is only used for listrings and hoppers. This workaround is not that bad because it only - requires a simple “inventory allows” check for large chests.]] - -- FIXME: Refactor the listrings as soon Minetest supports alternative listrings - -- BEGIN OF LISTRING WORKAROUND - inv:set_size("input", 1) - -- END OF LISTRING WORKAROUND - - -- Combine into a double chest if neighbouring another small chest - if minetest.get_node(get_double_container_neighbor_pos(pos, param2, "right")).name == - small_name then - minetest.swap_node(pos, { name = right_name, param2 = param2 }) - local p = get_double_container_neighbor_pos(pos, param2, "right") - minetest.swap_node(p, { name = left_name, param2 = param2 }) - create_entity(p, left_name, double_textures, param2, true, "default_chest", - "mcl_chests_chest", "chest") - elseif minetest.get_node(get_double_container_neighbor_pos(pos, param2, "left")).name == - small_name then - minetest.swap_node(pos, { name = left_name, param2 = param2 }) - create_entity(pos, left_name, double_textures, param2, true, "default_chest", - "mcl_chests_chest", "chest") - local p = get_double_container_neighbor_pos(pos, param2, "left") - minetest.swap_node(p, { name = right_name, param2 = param2 }) - else - minetest.swap_node(pos, { name = small_name, param2 = param2 }) - create_entity(pos, small_name, small_textures, param2, false, "default_chest", - "mcl_chests_chest", "chest") - end - end, - after_place_node = function(pos, placer, itemstack, pointed_thing) - minetest.get_meta(pos):set_string("name", itemstack:get_meta():get_string("name")) - end, - after_dig_node = drop_items_chest, - on_blast = on_chest_blast, - allow_metadata_inventory_move = protection_check_move, - allow_metadata_inventory_take = protection_check_put_take, - allow_metadata_inventory_put = protection_check_put_take, - on_metadata_inventory_move = function(pos, from_list, from_index, to_list, to_index, count, player) - minetest.log("action", player:get_player_name() .. - " moves stuff in chest at " .. minetest.pos_to_string(pos)) - end, - on_metadata_inventory_put = function(pos, listname, index, stack, player) - minetest.log("action", player:get_player_name() .. - " moves stuff to chest at " .. minetest.pos_to_string(pos)) - -- BEGIN OF LISTRING WORKAROUND - if listname == "input" then - local inv = minetest.get_inventory({ type = "node", pos = pos }) - inv:add_item("main", stack) - end - -- END OF LISTRING WORKAROUND - end, - on_metadata_inventory_take = function(pos, listname, index, stack, player) - minetest.log("action", player:get_player_name() .. - " takes stuff from chest at " .. minetest.pos_to_string(pos)) - end, - _mcl_blast_resistance = 2.5, - _mcl_hardness = 2.5, - - on_rightclick = function(pos, node, clicker) - local topnode = minetest.get_node({ x = pos.x, y = pos.y + 1, z = pos.z }) - if topnode and topnode.name and minetest.registered_nodes[topnode.name] then - if minetest.registered_nodes[topnode.name].groups.opaque == 1 then - -- won't open if there is no space from the top - return false - end - end - local name = minetest.get_meta(pos):get_string("name") - if name == "" then - name = S("Chest") - end - - minetest.show_formspec(clicker:get_player_name(), - sf("mcl_chests:%s_%s_%s_%s", canonical_basename, pos.x, pos.y, pos.z), - table.concat({ - "formspec_version[4]", - "size[11.75,10.425]", - - "label[0.375,0.375;" .. F(C(mcl_formspec.label_color, name)) .. "]", - mcl_formspec.get_itemslot_bg_v4(0.375, 0.75, 9, 3), - sf("list[nodemeta:%s,%s,%s;main;0.375,0.75;9,3;]", pos.x, pos.y, pos.z), - "label[0.375,4.7;" .. F(C(mcl_formspec.label_color, S("Inventory"))) .. "]", - mcl_formspec.get_itemslot_bg_v4(0.375, 5.1, 9, 3), - "list[current_player;main;0.375,5.1;9,3;9]", - - mcl_formspec.get_itemslot_bg_v4(0.375, 9.05, 9, 1), - "list[current_player;main;0.375,9.05;9,1;]", - sf("listring[nodemeta:%s,%s,%s;main]", pos.x, pos.y, pos.z), - "listring[current_player;main]", - }) - ) - - if on_rightclick_addendum then - on_rightclick_addendum(pos, node, clicker) - end - - player_chest_open(clicker, pos, small_name, small_textures, node.param2, false, "default_chest", - "mcl_chests_chest") - end, - - on_destruct = function(pos) - close_forms(canonical_basename, pos) - end, - mesecons = mesecons, - on_rotate = simple_rotate, - }) - - minetest.register_node(left_name, { - drawtype = "nodebox", - node_box = { - type = "fixed", - fixed = { -0.4375, -0.5, -0.4375, 0.5, 0.375, 0.4375 }, - }, - tiles = { "blank.png^[resize:16x16" }, - use_texture_alpha = "clip", - _chest_entity_textures = double_textures, - _chest_entity_sound = "default_chest", - _chest_entity_mesh = "mcl_chests_chest", - _chest_entity_animation_type = "chest", - paramtype = "light", - paramtype2 = "facedir", - groups = { - handy = 1, - axey = 1, - container = 2, - not_in_creative_inventory = 1, - material_wood = 1, - flammable = -1, - chest_entity = 1, - double_chest = 1 - }, - drop = drop, - is_ground_content = false, - sounds = mcl_sounds.node_sound_wood_defaults(), - on_construct = function(pos) - local n = minetest.get_node(pos) - local param2 = n.param2 - local p = get_double_container_neighbor_pos(pos, param2, "left") - if not p or minetest.get_node(p).name ~= "mcl_chests:" .. canonical_basename .. "_right" then - n.name = "mcl_chests:" .. canonical_basename .. "_small" - minetest.swap_node(pos, n) - end - create_entity(pos, left_name, double_textures, param2, true, "default_chest", "mcl_chests_chest", "chest") - end, - after_place_node = function(pos, placer, itemstack, pointed_thing) - minetest.get_meta(pos):set_string("name", itemstack:get_meta():get_string("name")) - end, - on_destruct = function(pos) - local n = minetest.get_node(pos) - if n.name == small_name then - return - end - - close_forms(canonical_basename, pos) - - local param2 = n.param2 - local p = get_double_container_neighbor_pos(pos, param2, "left") - if not p or minetest.get_node(p).name ~= "mcl_chests:" .. basename .. "_right" then - return - end - close_forms(canonical_basename, p) - - minetest.swap_node(p, { name = small_name, param2 = param2 }) - create_entity(p, small_name, small_textures, param2, false, "default_chest", "mcl_chests_chest", "chest") - end, - after_dig_node = drop_items_chest, - on_blast = on_chest_blast, - allow_metadata_inventory_move = protection_check_move, - allow_metadata_inventory_take = protection_check_put_take, - allow_metadata_inventory_put = function(pos, listname, index, stack, player) - local name = player:get_player_name() - if minetest.is_protected(pos, name) then - minetest.record_protection_violation(pos, name) - return 0 - -- BEGIN OF LISTRING WORKAROUND - elseif listname == "input" then - local inv = minetest.get_inventory({ type = "node", pos = pos }) - local other_pos = get_double_container_neighbor_pos(pos, minetest.get_node(pos).param2, "left") - local other_inv = minetest.get_inventory({ type = "node", pos = other_pos }) - return limit_put(stack, inv, other_inv) - -- END OF LISTRING WORKAROUND - else - return stack:get_count() - end - end, - on_metadata_inventory_move = function(pos, from_list, from_index, to_list, to_index, count, player) - minetest.log("action", player:get_player_name() .. - " moves stuff in chest at " .. minetest.pos_to_string(pos)) - end, - on_metadata_inventory_put = function(pos, listname, index, stack, player) - minetest.log("action", player:get_player_name() .. - " moves stuff to chest at " .. minetest.pos_to_string(pos)) - -- BEGIN OF LISTRING WORKAROUND - if listname == "input" then - local inv = minetest.get_inventory({ type = "node", pos = pos }) - local other_pos = get_double_container_neighbor_pos(pos, minetest.get_node(pos).param2, "left") - local other_inv = minetest.get_inventory({ type = "node", pos = other_pos }) - - inv:set_stack("input", 1, nil) - - double_chest_add_item(inv, other_inv, "main", stack) - end - -- END OF LISTRING WORKAROUND - end, - on_metadata_inventory_take = function(pos, listname, index, stack, player) - minetest.log("action", player:get_player_name() .. - " takes stuff from chest at " .. minetest.pos_to_string(pos)) - end, - _mcl_blast_resistance = 2.5, - _mcl_hardness = 2.5, - - on_rightclick = function(pos, node, clicker) - local pos_other = get_double_container_neighbor_pos(pos, node.param2, "left") - local above_def = minetest.registered_nodes[ - minetest.get_node({ x = pos.x, y = pos.y + 1, z = pos.z }).name - ] - local above_def_other = minetest.registered_nodes[ - minetest.get_node({ x = pos_other.x, y = pos_other.y + 1, z = pos_other.z }).name - ] - - if (not above_def or above_def.groups.opaque == 1 or not above_def_other - or above_def_other.groups.opaque == 1) then - -- won't open if there is no space from the top - return false - end - - local name = minetest.get_meta(pos):get_string("name") - if name == "" then -- if empty after that ^ - name = minetest.get_meta(pos_other):get_string("name") - end if name == "" then -- if STILL empty after that ^ - name = S("Large Chest") - end - - minetest.show_formspec(clicker:get_player_name(), - sf("mcl_chests:%s_%s_%s_%s", canonical_basename, pos.x, pos.y, pos.z), - table.concat({ - "formspec_version[4]", - "size[11.75,14.15]", - - "label[0.375,0.375;" .. F(C(mcl_formspec.label_color, name)) .. "]", - mcl_formspec.get_itemslot_bg_v4(0.375, 0.75, 9, 3), - sf("list[nodemeta:%s,%s,%s;main;0.375,0.75;9,3;]", pos.x, pos.y, pos.z), - mcl_formspec.get_itemslot_bg_v4(0.375, 4.5, 9, 3), - sf("list[nodemeta:%s,%s,%s;main;0.375,4.5;9,3;]", pos_other.x, pos_other.y, pos_other.z), - "label[0.375,8.45;" .. F(C(mcl_formspec.label_color, S("Inventory"))) .. "]", - mcl_formspec.get_itemslot_bg_v4(0.375, 8.825, 9, 3), - "list[current_player;main;0.375,8.825;9,3;9]", - - mcl_formspec.get_itemslot_bg_v4(0.375, 12.775, 9, 1), - "list[current_player;main;0.375,12.775;9,1;]", - - --BEGIN OF LISTRING WORKAROUND - "listring[current_player;main]", - sf("listring[nodemeta:%s,%s,%s;input]", pos.x, pos.y, pos.z), - --END OF LISTRING WORKAROUND - - "listring[current_player;main]" .. - sf("listring[nodemeta:%s,%s,%s;main]", pos.x, pos.y, pos.z), - "listring[current_player;main]", - sf("listring[nodemeta:%s,%s,%s;main]", pos_other.x, pos_other.y, pos_other.z), - }) - ) - - if on_rightclick_addendum_left then - on_rightclick_addendum_left(pos, node, clicker) - end - - player_chest_open(clicker, pos, left_name, double_textures, node.param2, true, "default_chest", - "mcl_chests_chest") - end, - mesecons = mesecons, - on_rotate = no_rotate, - _mcl_hoppers_on_try_pull = function(pos, hop_pos, hop_inv, hop_list) - local meta = minetest.get_meta(pos) - local inv = meta:get_inventory() - - local stack_id = mcl_util.select_stack(inv, "main", hop_inv, hop_list) - if stack_id ~= nil then - return inv, "main", stack_id - end - - local node = minetest.get_node(pos) - local pos_other = get_double_container_neighbor_pos(pos, node.param2, "left") - local meta_other = minetest.get_meta(pos_other) - local inv_other = meta_other:get_inventory() - stack_id = mcl_util.select_stack(inv_other, "main", hop_inv, hop_list) - return inv_other, "main", stack_id - end, - _mcl_hoppers_on_try_push = function(pos, hop_pos, hop_inv, hop_list) - local meta = minetest.get_meta(pos) - local inv = meta:get_inventory() - - local stack_id = mcl_util.select_stack(hop_inv, hop_list, inv, "main", nil, 1) - if stack_id ~= nil then - return inv, "main", stack_id - end - - local node = minetest.get_node(pos) - local pos_other = get_double_container_neighbor_pos(pos, node.param2, "left") - local meta_other = minetest.get_meta(pos_other) - local inv_other = meta_other:get_inventory() - stack_id = mcl_util.select_stack(hop_inv, hop_list, inv_other, "main", nil, 1) - return inv_other, "main", stack_id - end, - }) - - minetest.register_node(right_name, { - drawtype = "nodebox", - paramtype = "light", - paramtype2 = "facedir", - node_box = { - type = "fixed", - fixed = { -0.5, -0.5, -0.4375, 0.4375, 0.375, 0.4375 }, - }, - tiles = { "blank.png^[resize:16x16" }, - use_texture_alpha = "clip", - groups = { - handy = 1, - axey = 1, - container = 2, - not_in_creative_inventory = 1, - material_wood = 1, - flammable = -1, - double_chest = 2 - }, - drop = drop, - is_ground_content = false, - sounds = mcl_sounds.node_sound_wood_defaults(), - on_construct = function(pos) - local n = minetest.get_node(pos) - local param2 = n.param2 - local p = get_double_container_neighbor_pos(pos, param2, "right") - if not p or minetest.get_node(p).name ~= left_name then - n.name = small_name - minetest.swap_node(pos, n) - end - end, - after_place_node = function(pos, placer, itemstack, pointed_thing) - minetest.get_meta(pos):set_string("name", itemstack:get_meta():get_string("name")) - end, - on_destruct = function(pos) - local n = minetest.get_node(pos) - if n.name == small_name then - return - end - - close_forms(canonical_basename, pos) - - local param2 = n.param2 - local p = get_double_container_neighbor_pos(pos, param2, "right") - if not p or minetest.get_node(p).name ~= left_name then - return - end - close_forms(canonical_basename, p) - - minetest.swap_node(p, { name = small_name, param2 = param2 }) - create_entity(p, small_name, small_textures, param2, false, "default_chest", "mcl_chests_chest", "chest") - end, - after_dig_node = drop_items_chest, - on_blast = on_chest_blast, - allow_metadata_inventory_move = protection_check_move, - allow_metadata_inventory_take = protection_check_put_take, - allow_metadata_inventory_put = function(pos, listname, index, stack, player) - local name = player:get_player_name() - if minetest.is_protected(pos, name) then - minetest.record_protection_violation(pos, name) - return 0 - -- BEGIN OF LISTRING WORKAROUND - elseif listname == "input" then - local other_pos = get_double_container_neighbor_pos(pos, minetest.get_node(pos).param2, "right") - local other_inv = minetest.get_inventory({ type = "node", pos = other_pos }) - local inv = minetest.get_inventory({ type = "node", pos = pos }) - return limit_put(stack, other_inv, inv) - -- END OF LISTRING WORKAROUND - else - return stack:get_count() - end - end, - on_metadata_inventory_move = function(pos, from_list, from_index, to_list, to_index, count, player) - minetest.log("action", player:get_player_name() .. - " moves stuff in chest at " .. minetest.pos_to_string(pos)) - end, - on_metadata_inventory_put = function(pos, listname, index, stack, player) - minetest.log("action", player:get_player_name() .. - " moves stuff to chest at " .. minetest.pos_to_string(pos)) - -- BEGIN OF LISTRING WORKAROUND - if listname == "input" then - local other_pos = get_double_container_neighbor_pos(pos, minetest.get_node(pos).param2, "right") - local other_inv = minetest.get_inventory({ type = "node", pos = other_pos }) - local inv = minetest.get_inventory({ type = "node", pos = pos }) - - inv:set_stack("input", 1, nil) - - double_chest_add_item(other_inv, inv, "main", stack) - end - -- END OF LISTRING WORKAROUND - end, - on_metadata_inventory_take = function(pos, listname, index, stack, player) - minetest.log("action", player:get_player_name() .. - " takes stuff from chest at " .. minetest.pos_to_string(pos)) - end, - _mcl_blast_resistance = 2.5, - _mcl_hardness = 2.5, - - on_rightclick = function(pos, node, clicker) - local pos_other = get_double_container_neighbor_pos(pos, node.param2, "right") - local above_def = minetest.registered_nodes[ - minetest.get_node({ x = pos.x, y = pos.y + 1, z = pos.z }).name - ] - local above_def_other = minetest.registered_nodes[ - minetest.get_node({ x = pos_other.x, y = pos_other.y + 1, z = pos_other.z }).name - ] - - if (not above_def or above_def.groups.opaque == 1 or not above_def_other - or above_def_other.groups.opaque == 1) then - -- won't open if there is no space from the top - return false - end - - local name = minetest.get_meta(pos):get_string("name") - if name == "" then -- if empty after that ^ - name = minetest.get_meta(pos_other):get_string("name") - end if name == "" then -- if STILL empty after that ^ - name = S("Large Chest") - end - - minetest.show_formspec(clicker:get_player_name(), - sf("mcl_chests:%s_%s_%s_%s", canonical_basename, pos.x, pos.y, pos.z), - table.concat({ - "formspec_version[4]", - "size[11.75,14.15]", - - "label[0.375,0.375;" .. F(C(mcl_formspec.label_color, name)) .. "]", - mcl_formspec.get_itemslot_bg_v4(0.375, 0.75, 9, 3), - sf("list[nodemeta:%s,%s,%s;main;0.375,0.75;9,3;]", pos_other.x, pos_other.y, pos_other.z), - mcl_formspec.get_itemslot_bg_v4(0.375, 4.5, 9, 3), - sf("list[nodemeta:%s,%s,%s;main;0.375,4.5;9,3;]", pos.x, pos.y, pos.z), - "label[0.375,8.45;" .. F(C(mcl_formspec.label_color, S("Inventory"))) .. "]", - mcl_formspec.get_itemslot_bg_v4(0.375, 8.825, 9, 3), - "list[current_player;main;0.375,8.825;9,3;9]", - - mcl_formspec.get_itemslot_bg_v4(0.375, 12.775, 9, 1), - "list[current_player;main;0.375,12.775;9,1;]", - - --BEGIN OF LISTRING WORKAROUND - "listring[current_player;main]", - sf("listring[nodemeta:%s,%s,%s;input]", pos.x, pos.y, pos.z), - --END OF LISTRING WORKAROUND - - "listring[current_player;main]" .. - sf("listring[nodemeta:%s,%s,%s;main]", pos_other.x, pos_other.y, pos_other.z), - "listring[current_player;main]", - sf("listring[nodemeta:%s,%s,%s;main]", pos.x, pos.y, pos.z), - }) - ) - - if on_rightclick_addendum_right then - on_rightclick_addendum_right(pos, node, clicker) - end - - player_chest_open(clicker, pos_other, left_name, double_textures, node.param2, true, "default_chest", - "mcl_chests_chest") - end, - mesecons = mesecons, - on_rotate = no_rotate, - _mcl_hoppers_on_try_pull = function(pos, hop_pos, hop_inv, hop_list) - local node = minetest.get_node(pos) - local pos_other = get_double_container_neighbor_pos(pos, node.param2, "right") - local meta_other = minetest.get_meta(pos_other) - local inv_other = meta_other:get_inventory() - - local stack_id = mcl_util.select_stack(inv_other, "main", hop_inv, hop_list) - if stack_id ~= nil then - return inv_other, "main", stack_id - end - - local meta = minetest.get_meta(pos) - local inv = meta:get_inventory() - stack_id = mcl_util.select_stack(inv, "main", hop_inv, hop_list) - return inv, "main", stack_id - end, - _mcl_hoppers_on_try_push = function(pos, hop_pos, hop_inv, hop_list) - local node = minetest.get_node(pos) - local pos_other = get_double_container_neighbor_pos(pos, node.param2, "right") - local meta_other = minetest.get_meta(pos_other) - local inv_other = meta_other:get_inventory() - - local stack_id = mcl_util.select_stack(hop_inv, hop_list, inv_other, "main", nil, 1) - if stack_id ~= nil then - return inv_other, "main", stack_id - end - - local meta = minetest.get_meta(pos) - local inv = meta:get_inventory() - stack_id = mcl_util.select_stack(hop_inv, hop_list, inv, "main", nil, 1) - return inv, "main", stack_id - end, - }) - - if doc then - doc.add_entry_alias("nodes", small_name, "nodes", left_name) - doc.add_entry_alias("nodes", small_name, "nodes", right_name) - end -end - - - --- ========== -- --- chests.lua -- --- ========== -- - - - -local chestusage = S("To access its inventory, rightclick it. When broken, the items will drop out.") - -mcl_chests.register_chest("chest", - S("Chest"), - 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."), - chestusage, - S("27 inventory slots") .. "\n" .. S("Can be combined to a large chest"), - { - small = tiles.chest_normal_small, - double = tiles.chest_normal_double, - inv = { "default_chest_top.png", "mcl_chests_chest_bottom.png", - "mcl_chests_chest_right.png", "mcl_chests_chest_left.png", - "mcl_chests_chest_back.png", "default_chest_front.png" }, - }, - false -) - -local traptiles = { - small = tiles.chest_trapped_small, - double = tiles.chest_trapped_double, -} - -mcl_chests.register_chest("trapped_chest", - S("Trapped Chest"), - 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."), - chestusage, - S("27 inventory slots") .. - "\n" .. S("Can be combined to a large chest") .. "\n" .. S("Emits a redstone signal when opened"), - traptiles, - nil, - { - receptor = { - state = mesecon.state.off, - rules = trapped_chest_mesecons_rules, - }, - }, - function(pos, node, clicker) - minetest.swap_node(pos, { name = "mcl_chests:trapped_chest_on_small", param2 = node.param2 }) - find_or_create_entity(pos, "mcl_chests:trapped_chest_on_small", { "mcl_chests_trapped.png" }, node.param2, false, - "default_chest", "mcl_chests_chest", "chest"):reinitialize("mcl_chests:trapped_chest_on_small") - mesecon.receptor_on(pos, trapped_chest_mesecons_rules) - end, - function(pos, node, clicker) - local meta = minetest.get_meta(pos) - meta:set_int("players", 1) - - minetest.swap_node(pos, { name = "mcl_chests:trapped_chest_on_left", param2 = node.param2 }) - find_or_create_entity(pos, "mcl_chests:trapped_chest_on_left", tiles.chest_trapped_double, node.param2, true, - "default_chest", "mcl_chests_chest", "chest"):reinitialize("mcl_chests:trapped_chest_on_left") - mesecon.receptor_on(pos, trapped_chest_mesecons_rules) - - local pos_other = get_double_container_neighbor_pos(pos, node.param2, "left") - minetest.swap_node(pos_other, { name = "mcl_chests:trapped_chest_on_right", param2 = node.param2 }) - mesecon.receptor_on(pos_other, trapped_chest_mesecons_rules) - end, - function(pos, node, clicker) - local pos_other = get_double_container_neighbor_pos(pos, node.param2, "right") - - minetest.swap_node(pos, { name = "mcl_chests:trapped_chest_on_right", param2 = node.param2 }) - mesecon.receptor_on(pos, trapped_chest_mesecons_rules) - - minetest.swap_node(pos_other, { name = "mcl_chests:trapped_chest_on_left", param2 = node.param2 }) - find_or_create_entity(pos_other, "mcl_chests:trapped_chest_on_left", tiles.chest_trapped_double, node.param2, - true, - "default_chest", "mcl_chests_chest", "chest"):reinitialize("mcl_chests:trapped_chest_on_left") - mesecon.receptor_on(pos_other, trapped_chest_mesecons_rules) - end -) - -mcl_chests.register_chest("trapped_chest_on", - nil, nil, nil, nil, traptiles, true, - { - receptor = { - state = mesecon.state.on, - rules = trapped_chest_mesecons_rules, - }, - }, - nil, nil, nil, - "trapped_chest", - "trapped_chest" -) - - - --- ================= -- --- CONTINUE init.lua -- --- ================= -- +local modpath = minetest.get_modpath("mcl_chests") +dofile(modpath .. "/api.lua") +dofile(modpath .. "/chests.lua") +dofile(modpath .. "/ender.lua") +dofile(modpath .. "/shulkers.lua") @@ -1092,538 +48,22 @@ mcl_chests.register_chest("trapped_chest_on", minetest.register_on_player_receive_fields(function(player, formname, fields) if formname:find("mcl_chests:") == 1 then if fields.quit then - player_chest_close(player) + mcl_chests.player_chest_close(player) end end end) minetest.register_on_leaveplayer(function(player) - player_chest_close(player) + mcl_chests.player_chest_close(player) end) --- =================== -- --- CONTINUE chests.lua -- --- =================== -- - - - -minetest.register_craft({ - output = "mcl_chests:chest", - recipe = { - { "group:wood", "group:wood", "group:wood" }, - { "group:wood", "", "group:wood" }, - { "group:wood", "group:wood", "group:wood" }, - }, -}) - -minetest.register_craft({ - type = "fuel", - recipe = "mcl_chests:chest", - burntime = 15, -}) - -minetest.register_craft({ - type = "fuel", - recipe = "mcl_chests:trapped_chest", - burntime = 15, -}) - - - --- ========= -- --- ender.lua -- --- ========= -- - - - -minetest.register_node("mcl_chests:ender_chest", { - description = S("Ender Chest"), - _tt_help = S("27 interdimensional inventory slots") .. - "\n" .. S("Put items inside, retrieve them from any ender chest"), - _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."), - _doc_items_usagehelp = S("Rightclick the ender chest to access your personal interdimensional inventory."), - drawtype = "mesh", - mesh = "mcl_chests_chest.b3d", - tiles = tiles.chest_ender_small, - use_texture_alpha = "opaque", - paramtype = "light", - paramtype2 = "facedir", - groups = { deco_block = 1 }, - sounds = mcl_sounds.node_sound_stone_defaults(), - on_construct = function(pos) - local node = minetest.get_node(pos) - node.name = "mcl_chests:ender_chest_small" - minetest.set_node(pos, node) - end, -}) - -local formspec_ender_chest = table.concat({ - "formspec_version[4]", - "size[11.75,10.425]", - - "label[0.375,0.375;" .. F(C(mcl_formspec.label_color, S("Ender Chest"))) .. "]", - mcl_formspec.get_itemslot_bg_v4(0.375, 0.75, 9, 3), - "list[current_player;enderchest;0.375,0.75;9,3;]", - "label[0.375,4.7;" .. F(C(mcl_formspec.label_color, S("Inventory"))) .. "]", - mcl_formspec.get_itemslot_bg_v4(0.375, 5.1, 9, 3), - "list[current_player;main;0.375,5.1;9,3;9]", - - mcl_formspec.get_itemslot_bg_v4(0.375, 9.05, 9, 1), - "list[current_player;main;0.375,9.05;9,1;]", - - "listring[current_player;enderchest]", - "listring[current_player;main]", -}) - - -minetest.register_node("mcl_chests:ender_chest_small", { - description = S("Ender Chest"), - _tt_help = S("27 interdimensional inventory slots") .. - "\n" .. S("Put items inside, retrieve them from any ender chest"), - _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."), - _doc_items_usagehelp = S("Rightclick the ender chest to access your personal interdimensional inventory."), - drawtype = "nodebox", - node_box = { - type = "fixed", - fixed = { -0.4375, -0.5, -0.4375, 0.4375, 0.375, 0.4375 }, - }, - _chest_entity_textures = tiles.ender_chest_texture, - _chest_entity_sound = "mcl_chests_enderchest", - _chest_entity_mesh = "mcl_chests_chest", - _chest_entity_animation_type = "chest", - tiles = { "blank.png^[resize:16x16" }, - use_texture_alpha = "clip", - -- Note: The “container” group is missing here because the ender chest does not - -- have an inventory on its own - groups = { pickaxey = 1, deco_block = 1, material_stone = 1, chest_entity = 1, not_in_creative_inventory = 1 }, - is_ground_content = false, - paramtype = "light", - light_source = 7, - paramtype2 = "facedir", - sounds = mcl_sounds.node_sound_stone_defaults(), - drop = "mcl_core:obsidian 8", - on_construct = function(pos) - create_entity(pos, "mcl_chests:ender_chest_small", tiles.ender_chest_texture, minetest.get_node(pos).param2, false, - "mcl_chests_enderchest", "mcl_chests_chest", "chest") - end, - on_rightclick = function(pos, node, clicker) - if minetest.registered_nodes[minetest.get_node(vector.offset(pos, 0, 1, 0)).name].groups.opaque == 1 then - -- won't open if there is no space from the top - return false - end - minetest.show_formspec(clicker:get_player_name(), "mcl_chests:ender_chest_" .. clicker:get_player_name(), - formspec_ender_chest) - player_chest_open(clicker, pos, "mcl_chests:ender_chest_small", tiles.ender_chest_texture, node.param2, false, - "mcl_chests_enderchest", "mcl_chests_chest") - end, - on_receive_fields = function(pos, formname, fields, sender) - if fields.quit then - player_chest_close(sender) - end - end, - _mcl_blast_resistance = 3000, - _mcl_hardness = 22.5, - _mcl_silk_touch_drop = { "mcl_chests:ender_chest" }, - on_rotate = simple_rotate, -}) - -minetest.register_on_joinplayer(function(player) - local inv = player:get_inventory() - inv:set_size("enderchest", 9 * 3) -end) - -minetest.register_allow_player_inventory_action(function(player, action, inv, info) - if inv:get_location().type == "player" and ( - action == "move" and (info.from_list == "enderchest" or info.to_list == "enderchest") - or action == "put" and info.listname == "enderchest" - or action == "take" and info.listname == "enderchest" - ) then - local def = player:get_wielded_item():get_definition() - local range = (def and def.range or player:get_inventory():get_stack("hand", 1):get_definition().range) + 1 - if not minetest.find_node_near(player:get_pos(), range, "mcl_chests:ender_chest_small", true) then - return 0 - end - end -end) - -minetest.register_craft({ - output = "mcl_chests:ender_chest", - recipe = { - { "mcl_core:obsidian", "mcl_core:obsidian", "mcl_core:obsidian" }, - { "mcl_core:obsidian", "mcl_end:ender_eye", "mcl_core:obsidian" }, - { "mcl_core:obsidian", "mcl_core:obsidian", "mcl_core:obsidian" }, - }, -}) - - - --- ============ -- --- shulkers.lua -- --- ============ -- - - - --- Shulker boxes -local boxtypes = { - white = S("White Shulker Box"), - grey = S("Light Grey Shulker Box"), - orange = S("Orange Shulker Box"), - cyan = S("Cyan Shulker Box"), - magenta = S("Magenta Shulker Box"), - violet = S("Purple Shulker Box"), - lightblue = S("Light Blue Shulker Box"), - blue = S("Blue Shulker Box"), - yellow = S("Yellow Shulker Box"), - brown = S("Brown Shulker Box"), - green = S("Lime Shulker Box"), - dark_green = S("Green Shulker Box"), - pink = S("Pink Shulker Box"), - red = S("Red Shulker Box"), - dark_grey = S("Grey Shulker Box"), - black = S("Black Shulker Box"), -} - -local shulker_mob_textures = { - white = "mobs_mc_shulker_white.png", - grey = "mobs_mc_shulker_silver.png", - orange = "mobs_mc_shulker_orange.png", - cyan = "mobs_mc_shulker_cyan.png", - magenta = "mobs_mc_shulker_magenta.png", - violet = "mobs_mc_shulker_purple.png", - lightblue = "mobs_mc_shulker_light_blue.png", - blue = "mobs_mc_shulker_blue.png", - yellow = "mobs_mc_shulker_yellow.png", - brown = "mobs_mc_shulker_brown.png", - green = "mobs_mc_shulker_lime.png", - dark_green = "mobs_mc_shulker_green.png", - pink = "mobs_mc_shulker_pink.png", - red = "mobs_mc_shulker_red.png", - dark_grey = "mobs_mc_shulker_gray.png", - black = "mobs_mc_shulker_black.png", -} -local canonical_shulker_color = "violet" -local normal_canonical_name = "mcl_chests:" .. canonical_shulker_color .. "_shulker_box" -local small_canonical_name = normal_canonical_name .. "_small" - ---WARNING: after formspec v4 update, old shulker boxes will need to be placed again to get the new formspec -local function formspec_shulker_box(name) - if not name or name == "" then - name = S("Shulker Box") - end - - return table.concat({ - "formspec_version[4]", - "size[11.75,10.425]", - - "label[0.375,0.375;" .. F(C(mcl_formspec.label_color, name)) .. "]", - mcl_formspec.get_itemslot_bg_v4(0.375, 0.75, 9, 3), - "list[context;main;0.375,0.75;9,3;]", - "label[0.375,4.7;" .. F(C(mcl_formspec.label_color, S("Inventory"))) .. "]", - mcl_formspec.get_itemslot_bg_v4(0.375, 5.1, 9, 3), - "list[current_player;main;0.375,5.1;9,3;9]", - - mcl_formspec.get_itemslot_bg_v4(0.375, 9.05, 9, 1), - "list[current_player;main;0.375,9.05;9,1;]", - - "listring[context;main]", - "listring[current_player;main]", - }) -end - -local function set_shulkerbox_meta(nmeta, imeta) - local name = imeta:get_string("name") - nmeta:set_string("description", imeta:get_string("description")) - nmeta:set_string("name", name) - nmeta:set_string("formspec", formspec_shulker_box(name)) -end - -for color, desc in pairs(boxtypes) do - local mob_texture = shulker_mob_textures[color] - local is_canonical = color == canonical_shulker_color - local longdesc, usagehelp, create_entry, entry_name - if doc then - if is_canonical then - 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.") - 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. Place the shulker box again to be able to retrieve its contents.") - entry_name = S("Shulker Box") - else - create_entry = false - end - end - - local normal_name = "mcl_chests:" .. color .. "_shulker_box" - local small_name = normal_name .. "_small" - - minetest.register_node(normal_name, { - description = desc, - _tt_help = S("27 inventory slots") .. "\n" .. S("Can be carried around with its contents"), - _doc_items_create_entry = create_entry, - _doc_items_entry_name = entry_name, - _doc_items_longdesc = longdesc, - _doc_items_usagehelp = usagehelp, - tiles = { mob_texture }, - use_texture_alpha = "opaque", - drawtype = "mesh", - mesh = "mcl_chests_shulker.b3d", - groups = { - handy = 1, - pickaxey = 1, - container = 2, - deco_block = 1, - dig_by_piston = 1, - shulker_box = 1, - old_shulker_box_node = 1 - }, - is_ground_content = false, - sounds = mcl_sounds.node_sound_stone_defaults(), - stack_max = 1, - drop = "", - paramtype = "light", - paramtype2 = "facedir", - on_construct = function(pos) - local node = minetest.get_node(pos) - node.name = small_name - minetest.set_node(pos, node) - end, - after_place_node = function(pos, placer, itemstack, pointed_thing) - local nmeta = minetest.get_meta(pos) - local imetadata = itemstack:get_metadata() - local iinv_main = minetest.deserialize(imetadata) - local ninv = nmeta:get_inventory() - ninv:set_list("main", iinv_main) - ninv:set_size("main", 9 * 3) - set_shulkerbox_meta(nmeta, itemstack:get_meta()) - - if minetest.is_creative_enabled(placer:get_player_name()) then - if not ninv:is_empty("main") then - return nil - else - return itemstack - end - else - return nil - end - end, - _on_dispense = function(stack, pos, droppos, dropnode, dropdir) - -- Place shulker box as node - if minetest.registered_nodes[dropnode.name].buildable_to then - minetest.set_node(droppos, { name = small_name, param2 = minetest.dir_to_facedir(dropdir) }) - local ninv = minetest.get_inventory({ type = "node", pos = droppos }) - local imetadata = stack:get_metadata() - local iinv_main = minetest.deserialize(imetadata) - ninv:set_list("main", iinv_main) - ninv:set_size("main", 9 * 3) - set_shulkerbox_meta(minetest.get_meta(droppos), stack:get_meta()) - stack:take_item() - end - return stack - end, - }) - - minetest.register_node(small_name, { - description = desc, - _tt_help = S("27 inventory slots") .. "\n" .. S("Can be carried around with its contents"), - _doc_items_create_entry = create_entry, - _doc_items_entry_name = entry_name, - _doc_items_longdesc = longdesc, - _doc_items_usagehelp = usagehelp, - drawtype = "nodebox", - node_box = { - type = "fixed", - fixed = { -0.48, -0.5, -0.48, 0.48, 0.489, 0.48 }, - }, - tiles = { "blank.png^[resize:16x16" }, - use_texture_alpha = "clip", - _chest_entity_textures = { mob_texture }, - _chest_entity_sound = "mcl_chests_shulker", - _chest_entity_mesh = "mcl_chests_shulker", - _chest_entity_animation_type = "shulker", - groups = { - handy = 1, - pickaxey = 1, - container = 2, - deco_block = 1, - dig_by_piston = 1, - shulker_box = 1, - chest_entity = 1, - not_in_creative_inventory = 1 - }, - is_ground_content = false, - sounds = mcl_sounds.node_sound_stone_defaults(), - stack_max = 1, - drop = "", - paramtype = "light", - paramtype2 = "facedir", - -- TODO: Make shulker boxes rotatable - -- This doesn't work, it just destroys the inventory: - -- on_place = minetest.rotate_node, - on_construct = function(pos) - local meta = minetest.get_meta(pos) - meta:set_string("formspec", formspec_shulker_box(nil)) - local inv = meta:get_inventory() - inv:set_size("main", 9 * 3) - create_entity(pos, small_name, { mob_texture }, minetest.get_node(pos).param2, false, "mcl_chests_shulker", - "mcl_chests_shulker", "shulker") - end, - after_place_node = function(pos, placer, itemstack, pointed_thing) - local nmeta = minetest.get_meta(pos) - local imetadata = itemstack:get_metadata() - local iinv_main = minetest.deserialize(imetadata) - local ninv = nmeta:get_inventory() - ninv:set_list("main", iinv_main) - ninv:set_size("main", 9 * 3) - set_shulkerbox_meta(nmeta, itemstack:get_meta()) - - if minetest.is_creative_enabled(placer:get_player_name()) then - if not ninv:is_empty("main") then - return nil - else - return itemstack - end - else - return nil - end - end, - on_rightclick = function(pos, node, clicker) - player_chest_open(clicker, pos, small_name, { mob_texture }, node.param2, false, "mcl_chests_shulker", - "mcl_chests_shulker", true) - end, - on_receive_fields = function(pos, formname, fields, sender) - if fields.quit then - player_chest_close(sender) - end - end, - on_destruct = function(pos) - local meta = minetest.get_meta(pos) - local inv = meta:get_inventory() - local items = {} - for i = 1, inv:get_size("main") do - local stack = inv:get_stack("main", i) - items[i] = stack:to_string() - end - local data = minetest.serialize(items) - local boxitem = ItemStack("mcl_chests:" .. color .. "_shulker_box") - local boxitem_meta = boxitem:get_meta() - boxitem_meta:set_string("description", meta:get_string("description")) - boxitem_meta:set_string("name", meta:get_string("name")) - boxitem:set_metadata(data) - - if minetest.is_creative_enabled("") then - if not inv:is_empty("main") then - minetest.add_item(pos, boxitem) - end - else - minetest.add_item(pos, boxitem) - end - end, - allow_metadata_inventory_move = protection_check_move, - allow_metadata_inventory_take = protection_check_put_take, - allow_metadata_inventory_put = function(pos, listname, index, stack, player) - local name = player:get_player_name() - if minetest.is_protected(pos, name) then - minetest.record_protection_violation(pos, name) - return 0 - end - -- Do not allow to place shulker boxes into shulker boxes - local group = minetest.get_item_group(stack:get_name(), "shulker_box") - if group == 0 or group == nil then - return stack:get_count() - else - return 0 - end - end, - _mcl_blast_resistance = 6, - _mcl_hardness = 2, - _mcl_hoppers_on_try_push = function(pos, hop_pos, hop_inv, hop_list) - local meta = minetest.get_meta(pos) - local inv = meta:get_inventory() - return inv, "main", mcl_util.select_stack(hop_inv, hop_list, inv, "main", mcl_chests.is_not_shulker_box, 1) - end, - }) - - if doc and not is_canonical then - doc.add_entry_alias("nodes", normal_canonical_name, "nodes", normal_name) - doc.add_entry_alias("nodes", small_canonical_name, "nodes", small_name) - end - - minetest.register_craft({ - type = "shapeless", - output = normal_name, - recipe = { "group:shulker_box", "mcl_dye:" .. color }, - }) -end - - - --- ================ -- --- CONTINUE api.lua -- --- ================ -- - - - --- Returns false if itemstack is a shulker box -function mcl_chests.is_not_shulker_box(stack) - local g = minetest.get_item_group(stack:get_name(), "shulker_box") - return g == 0 or g == nil -end - - - --- ===================== -- --- CONTINUE shulkers.lua -- --- ===================== -- - - - -minetest.register_craft({ - output = "mcl_chests:violet_shulker_box", - recipe = { - { "mcl_mobitems:shulker_shell" }, - { "mcl_chests:chest" }, - { "mcl_mobitems:shulker_shell" }, - }, -}) - --- Save metadata of shulker box when used in crafting -minetest.register_on_craft(function(itemstack, player, old_craft_grid, craft_inv) - if minetest.get_item_group(itemstack:get_name(), "shulker_box") ~= 1 then - return - end - local original - for i = 1, #old_craft_grid do - local item = old_craft_grid[i]:get_name() - if minetest.get_item_group(item, "shulker_box") == 1 then - original = old_craft_grid[i] - break - end - end - if original then - local ometa = original:get_meta():to_table() - local nmeta = itemstack:get_meta() - nmeta:from_table(ometa) - return itemstack - end -end) - - - --- ================= -- --- CONTINUE init.lua -- --- ================= -- - - - local function select_and_spawn_entity(pos, node) local node_name = node.name local node_def = minetest.registered_nodes[node_name] local double_chest = minetest.get_item_group(node_name, "double_chest") > 0 - find_or_create_entity(pos, node_name, node_def._chest_entity_textures, node.param2, double_chest, + mcl_chests.find_or_create_entity(pos, node_name, node_def._chest_entity_textures, node.param2, double_chest, node_def._chest_entity_sound, node_def._chest_entity_mesh, node_def._chest_entity_animation_type) end @@ -1649,7 +89,7 @@ minetest.register_lbm({ select_and_spawn_entity(pos, node) if node_name == "mcl_chests:trapped_chest_on" then minetest.log("action", "[mcl_chests] Disabled active trapped chest on load: " .. minetest.pos_to_string(pos)) - chest_update_after_close(pos) + mcl_chests.chest_update_after_close(pos) elseif node_name == "mcl_chests:ender_chest" then local meta = minetest.get_meta(pos) meta:set_string("formspec", formspec_ender_chest) @@ -1667,18 +107,7 @@ minetest.register_lbm({ run_at_every_load = true, action = function(pos, node) minetest.log("action", "[mcl_chests] Disabled active trapped chest on load: " .. minetest.pos_to_string(pos)) - chest_update_after_close(pos) - end, -}) - -minetest.register_lbm({ - label = "Update shulker box formspecs (0.72.0)", - name = "mcl_chests:update_shulker_box_formspecs_0_72_0", - nodenames = { "group:shulker_box" }, - run_at_every_load = false, - action = function(pos, node) - local meta = minetest.get_meta(pos) - meta:set_string("formspec", formspec_shulker_box(meta:get_string("name"))) + mcl_chests.chest_update_after_close(pos) end, }) diff --git a/mods/ITEMS/mcl_chests/shulkers.lua b/mods/ITEMS/mcl_chests/shulkers.lua new file mode 100644 index 000000000..0323c6fc9 --- /dev/null +++ b/mods/ITEMS/mcl_chests/shulkers.lua @@ -0,0 +1,342 @@ +local S = minetest.get_translator(minetest.get_current_modname()) +local F = minetest.formspec_escape +local C = minetest.colorize + +-- Shulker boxes +local boxtypes = { + white = S("White Shulker Box"), + grey = S("Light Grey Shulker Box"), + orange = S("Orange Shulker Box"), + cyan = S("Cyan Shulker Box"), + magenta = S("Magenta Shulker Box"), + violet = S("Purple Shulker Box"), + lightblue = S("Light Blue Shulker Box"), + blue = S("Blue Shulker Box"), + yellow = S("Yellow Shulker Box"), + brown = S("Brown Shulker Box"), + green = S("Lime Shulker Box"), + dark_green = S("Green Shulker Box"), + pink = S("Pink Shulker Box"), + red = S("Red Shulker Box"), + dark_grey = S("Grey Shulker Box"), + black = S("Black Shulker Box"), +} + +local shulker_mob_textures = { + white = "mobs_mc_shulker_white.png", + grey = "mobs_mc_shulker_silver.png", + orange = "mobs_mc_shulker_orange.png", + cyan = "mobs_mc_shulker_cyan.png", + magenta = "mobs_mc_shulker_magenta.png", + violet = "mobs_mc_shulker_purple.png", + lightblue = "mobs_mc_shulker_light_blue.png", + blue = "mobs_mc_shulker_blue.png", + yellow = "mobs_mc_shulker_yellow.png", + brown = "mobs_mc_shulker_brown.png", + green = "mobs_mc_shulker_lime.png", + dark_green = "mobs_mc_shulker_green.png", + pink = "mobs_mc_shulker_pink.png", + red = "mobs_mc_shulker_red.png", + dark_grey = "mobs_mc_shulker_gray.png", + black = "mobs_mc_shulker_black.png", +} + +local canonical_shulker_color = "violet" +local normal_canonical_name = "mcl_chests:" .. canonical_shulker_color .. "_shulker_box" +local small_canonical_name = normal_canonical_name .. "_small" + +--WARNING: after formspec v4 update, old shulker boxes will need to be placed again to get the new formspec +local function formspec_shulker_box(name) + if not name or name == "" then + name = S("Shulker Box") + end + + return table.concat({ + "formspec_version[4]", + "size[11.75,10.425]", + + "label[0.375,0.375;" .. F(C(mcl_formspec.label_color, name)) .. "]", + mcl_formspec.get_itemslot_bg_v4(0.375, 0.75, 9, 3), + "list[context;main;0.375,0.75;9,3;]", + "label[0.375,4.7;" .. F(C(mcl_formspec.label_color, S("Inventory"))) .. "]", + mcl_formspec.get_itemslot_bg_v4(0.375, 5.1, 9, 3), + "list[current_player;main;0.375,5.1;9,3;9]", + + mcl_formspec.get_itemslot_bg_v4(0.375, 9.05, 9, 1), + "list[current_player;main;0.375,9.05;9,1;]", + + "listring[context;main]", + "listring[current_player;main]", + }) +end + +local function set_shulkerbox_meta(nmeta, imeta) + local name = imeta:get_string("name") + nmeta:set_string("description", imeta:get_string("description")) + nmeta:set_string("name", name) + nmeta:set_string("formspec", formspec_shulker_box(name)) +end + +for color, desc in pairs(boxtypes) do + local mob_texture = shulker_mob_textures[color] + local is_canonical = color == canonical_shulker_color + local longdesc, usagehelp, create_entry, entry_name + if doc then + if is_canonical then + 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. + ]]) + 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. + Place the shulker box again to be able to retrieve its contents. + ]]) + entry_name = S("Shulker Box") + else + create_entry = false + end + end + + local normal_name = "mcl_chests:" .. color .. "_shulker_box" + local small_name = normal_name .. "_small" + + minetest.register_node(normal_name, { + description = desc, + _tt_help = S("27 inventory slots") .. "\n" .. S("Can be carried around with its contents"), + _doc_items_create_entry = create_entry, + _doc_items_entry_name = entry_name, + _doc_items_longdesc = longdesc, + _doc_items_usagehelp = usagehelp, + tiles = { mob_texture }, + use_texture_alpha = "opaque", + drawtype = "mesh", + mesh = "mcl_chests_shulker.b3d", + groups = { + handy = 1, + pickaxey = 1, + container = 2, + deco_block = 1, + dig_by_piston = 1, + shulker_box = 1, + old_shulker_box_node = 1 + }, + is_ground_content = false, + sounds = mcl_sounds.node_sound_stone_defaults(), + stack_max = 1, + drop = "", + paramtype = "light", + paramtype2 = "facedir", + on_construct = function(pos) + local node = minetest.get_node(pos) + node.name = small_name + minetest.set_node(pos, node) + end, + after_place_node = function(pos, placer, itemstack, pointed_thing) + local nmeta = minetest.get_meta(pos) + local imetadata = itemstack:get_metadata() + local iinv_main = minetest.deserialize(imetadata) + local ninv = nmeta:get_inventory() + ninv:set_list("main", iinv_main) + ninv:set_size("main", 9 * 3) + set_shulkerbox_meta(nmeta, itemstack:get_meta()) + + if minetest.is_creative_enabled(placer:get_player_name()) then + if not ninv:is_empty("main") then + return nil + else + return itemstack + end + else + return nil + end + end, + _on_dispense = function(stack, pos, droppos, dropnode, dropdir) + -- Place shulker box as node + if minetest.registered_nodes[dropnode.name].buildable_to then + minetest.set_node(droppos, { name = small_name, param2 = minetest.dir_to_facedir(dropdir) }) + local ninv = minetest.get_inventory({ type = "node", pos = droppos }) + local imetadata = stack:get_metadata() + local iinv_main = minetest.deserialize(imetadata) + ninv:set_list("main", iinv_main) + ninv:set_size("main", 9 * 3) + set_shulkerbox_meta(minetest.get_meta(droppos), stack:get_meta()) + stack:take_item() + end + return stack + end, + }) + + minetest.register_node(small_name, { + description = desc, + _tt_help = S("27 inventory slots") .. "\n" .. S("Can be carried around with its contents"), + _doc_items_create_entry = create_entry, + _doc_items_entry_name = entry_name, + _doc_items_longdesc = longdesc, + _doc_items_usagehelp = usagehelp, + drawtype = "nodebox", + node_box = { + type = "fixed", + fixed = { -0.48, -0.5, -0.48, 0.48, 0.489, 0.48 }, + }, + tiles = { "blank.png^[resize:16x16" }, + use_texture_alpha = "clip", + _chest_entity_textures = { mob_texture }, + _chest_entity_sound = "mcl_chests_shulker", + _chest_entity_mesh = "mcl_chests_shulker", + _chest_entity_animation_type = "shulker", + groups = { + handy = 1, + pickaxey = 1, + container = 2, + deco_block = 1, + dig_by_piston = 1, + shulker_box = 1, + chest_entity = 1, + not_in_creative_inventory = 1 + }, + is_ground_content = false, + sounds = mcl_sounds.node_sound_stone_defaults(), + stack_max = 1, + drop = "", + paramtype = "light", + paramtype2 = "facedir", + on_construct = function(pos) + local meta = minetest.get_meta(pos) + meta:set_string("formspec", formspec_shulker_box(nil)) + local inv = meta:get_inventory() + inv:set_size("main", 9 * 3) + mcl_chests.create_entity(pos, small_name, { mob_texture }, minetest.get_node(pos).param2, false, + "mcl_chests_shulker", "mcl_chests_shulker", "shulker") + end, + after_place_node = function(pos, placer, itemstack, pointed_thing) + local nmeta = minetest.get_meta(pos) + local imetadata = itemstack:get_metadata() + local iinv_main = minetest.deserialize(imetadata) + local ninv = nmeta:get_inventory() + ninv:set_list("main", iinv_main) + ninv:set_size("main", 9 * 3) + set_shulkerbox_meta(nmeta, itemstack:get_meta()) + + if minetest.is_creative_enabled(placer:get_player_name()) then + if not ninv:is_empty("main") then + return nil + else + return itemstack + end + else + return nil + end + end, + on_rightclick = function(pos, node, clicker) + mcl_chests.player_chest_open(clicker, pos, small_name, { mob_texture }, node.param2, false, + "mcl_chests_shulker", "mcl_chests_shulker", true) + end, + on_receive_fields = function(pos, formname, fields, sender) + if fields.quit then + mcl_chests.player_chest_close(sender) + end + end, + on_destruct = function(pos) + local meta = minetest.get_meta(pos) + local inv = meta:get_inventory() + local items = {} + for i = 1, inv:get_size("main") do + local stack = inv:get_stack("main", i) + items[i] = stack:to_string() + end + local data = minetest.serialize(items) + local boxitem = ItemStack("mcl_chests:" .. color .. "_shulker_box") + local boxitem_meta = boxitem:get_meta() + boxitem_meta:set_string("description", meta:get_string("description")) + boxitem_meta:set_string("name", meta:get_string("name")) + boxitem:set_metadata(data) + + if minetest.is_creative_enabled("") then + if not inv:is_empty("main") then + minetest.add_item(pos, boxitem) + end + else + minetest.add_item(pos, boxitem) + end + end, + allow_metadata_inventory_move = mcl_chests.protection_check_move, + allow_metadata_inventory_take = mcl_chests.protection_check_put_take, + allow_metadata_inventory_put = function(pos, listname, index, stack, player) + local name = player:get_player_name() + if minetest.is_protected(pos, name) then + minetest.record_protection_violation(pos, name) + return 0 + end + -- Do not allow to place shulker boxes into shulker boxes + local group = minetest.get_item_group(stack:get_name(), "shulker_box") + if group == 0 or group == nil then + return stack:get_count() + else + return 0 + end + end, + on_rotate = mcl_chests.simple_rotate, + _mcl_blast_resistance = 6, + _mcl_hardness = 2, + _mcl_hoppers_on_try_push = function(pos, hop_pos, hop_inv, hop_list) + local meta = minetest.get_meta(pos) + local inv = meta:get_inventory() + return inv, "main", + mcl_util.select_stack(hop_inv, hop_list, inv, "main", mcl_chests.is_not_shulker_box, 1) + end, + }) + + if doc and not is_canonical then + doc.add_entry_alias("nodes", normal_canonical_name, "nodes", normal_name) + doc.add_entry_alias("nodes", small_canonical_name, "nodes", small_name) + end + + minetest.register_craft({ + type = "shapeless", + output = normal_name, + recipe = { "group:shulker_box", "mcl_dye:" .. color }, + }) +end + +minetest.register_craft({ + output = "mcl_chests:violet_shulker_box", + recipe = { + { "mcl_mobitems:shulker_shell" }, + { "mcl_chests:chest" }, + { "mcl_mobitems:shulker_shell" }, + }, +}) + +-- Save metadata of shulker box when used in crafting +minetest.register_on_craft(function(itemstack, player, old_craft_grid, craft_inv) + if minetest.get_item_group(itemstack:get_name(), "shulker_box") ~= 1 then return end + + local original + for i = 1, #old_craft_grid do + local item = old_craft_grid[i]:get_name() + if minetest.get_item_group(item, "shulker_box") == 1 then + original = old_craft_grid[i] + break + end + end + if original then + local ometa = original:get_meta():to_table() + local nmeta = itemstack:get_meta() + nmeta:from_table(ometa) + return itemstack + end +end) + +minetest.register_lbm({ + label = "Update shulker box formspecs (0.72.0)", + name = "mcl_chests:update_shulker_box_formspecs_0_72_0", + nodenames = { "group:shulker_box" }, + run_at_every_load = false, + action = function(pos, node) + local meta = minetest.get_meta(pos) + meta:set_string("formspec", formspec_shulker_box(meta:get_string("name"))) + end, +})