From 1719c3e4e13c591111c3626ef0d8dc7a80fccc18 Mon Sep 17 00:00:00 2001 From: Wuzzy Date: Thu, 21 Mar 2019 16:08:29 +0100 Subject: [PATCH] Add LBMs to update formspecs of 4 nodes Dispenser, dropper, ender chest, shulker box. --- mods/ITEMS/REDSTONE/mcl_dispensers/init.lua | 10 + .../REDSTONE/mcl_dispensers/init_new.lua | 372 ++++++++ mods/ITEMS/REDSTONE/mcl_droppers/init.lua | 10 + mods/ITEMS/REDSTONE/mcl_droppers/init_new.lua | 219 +++++ mods/ITEMS/mcl_chests/init.lua | 10 + mods/ITEMS/mcl_chests/init_new.lua | 878 ++++++++++++++++++ 6 files changed, 1499 insertions(+) create mode 100644 mods/ITEMS/REDSTONE/mcl_dispensers/init_new.lua create mode 100644 mods/ITEMS/REDSTONE/mcl_droppers/init_new.lua create mode 100644 mods/ITEMS/mcl_chests/init_new.lua diff --git a/mods/ITEMS/REDSTONE/mcl_dispensers/init.lua b/mods/ITEMS/REDSTONE/mcl_dispensers/init.lua index 1575c3fc6..2e5e3c6a1 100644 --- a/mods/ITEMS/REDSTONE/mcl_dispensers/init.lua +++ b/mods/ITEMS/REDSTONE/mcl_dispensers/init.lua @@ -359,3 +359,13 @@ if minetest.get_modpath("doc") then doc.add_entry_alias("nodes", "mcl_dispensers:dispenser", "nodes", "mcl_dispensers:dispenser_down") doc.add_entry_alias("nodes", "mcl_dispensers:dispenser", "nodes", "mcl_dispensers:dispenser_up") end + +minetest.register_lbm({ + label = "Update dispenser formspecs (0.51.0)", + name = "mcl_dispensers:update_formspecs_0_51_0", + nodenames = { "mcl_dispensers:dispenser", "mcl_dispensers:dispenser_down", "mcl_dispensers:dispenser_up" }, + action = function(pos, node) + setup_dispenser(pos) + minetest.log("action", "[mcl_dispenser] Node formspec updated at "..minetest.pos_to_string(pos)) + end, +}) diff --git a/mods/ITEMS/REDSTONE/mcl_dispensers/init_new.lua b/mods/ITEMS/REDSTONE/mcl_dispensers/init_new.lua new file mode 100644 index 000000000..dbe802c2c --- /dev/null +++ b/mods/ITEMS/REDSTONE/mcl_dispensers/init_new.lua @@ -0,0 +1,372 @@ +--[[ This mod registers 3 nodes: +- One node for the horizontal-facing dispensers (mcl_dispensers:dispenser) +- One node for the upwards-facing dispensers (mcl_dispenser:dispenser_up) +- One node for the downwards-facing dispensers (mcl_dispenser:dispenser_down) + +3 node definitions are needed because of the way the textures are defined. +All node definitions share a lot of code, so this is the reason why there +are so many weird tables below. +]] +local S = minetest.get_translator("mcl_dispensers") + +-- For after_place_node +local setup_dispenser = function(pos) + -- Set formspec and inventory + local form = "size[9,8.75]".. + "background[-0.19,-0.25;9.41,9.49;crafting_inventory_9_slots.png]".. + mcl_vars.inventory_header.. + "label[0,4.0;"..minetest.formspec_escape(minetest.colorize("#313131", S("Inventory"))).."]".. + "list[current_player;main;0,4.5;9,3;9]".. + "list[current_player;main;0,7.74;9,1;]".. + "label[3,0;"..minetest.formspec_escape(minetest.colorize("#313131", S("Dispenser"))).."]".. + "list[current_name;main;3,0.5;3,3;]".. + "listring[current_name;main]".. + "listring[current_player;main]" + local meta = minetest.get_meta(pos) + meta:set_string("formspec", form) + local inv = meta:get_inventory() + inv:set_size("main", 9) +end + +local orientate_dispenser = function(pos, placer) + -- Not placed by player + if not placer then return end + + -- Pitch in degrees + local pitch = placer:get_look_vertical() * (180 / math.pi) + + local node = minetest.get_node(pos) + if pitch > 55 then + minetest.swap_node(pos, {name="mcl_dispensers:dispenser_up", param2 = node.param2}) + elseif pitch < -55 then + minetest.swap_node(pos, {name="mcl_dispensers:dispenser_down", param2 = node.param2}) + end +end + +local on_rotate +if minetest.get_modpath("screwdriver") then + on_rotate = screwdriver.rotate_simple +end + +-- Shared core definition table +local dispenserdef = { + is_ground_content = false, + sounds = mcl_sounds.node_sound_stone_defaults(), + allow_metadata_inventory_move = function(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, + allow_metadata_inventory_take = 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 + else + return stack:get_count() + end + end, + 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 + else + return stack:get_count() + end + end, + after_dig_node = function(pos, oldnode, oldmetadata, digger) + local meta = minetest.get_meta(pos) + local meta2 = meta + meta:from_table(oldmetadata) + local inv = meta:get_inventory() + for i=1, inv:get_size("main") do + local stack = inv:get_stack("main", i) + if not stack:is_empty() then + local p = {x=pos.x+math.random(0, 10)/10-0.5, y=pos.y, z=pos.z+math.random(0, 10)/10-0.5} + minetest.add_item(p, stack) + end + end + meta:from_table(meta2:to_table()) + end, + _mcl_blast_resistance = 17.5, + _mcl_hardness = 3.5, + mesecons = {effector = { + -- Dispense random item when triggered + action_on = function (pos, node) + local meta = minetest.get_meta(pos) + local inv = meta:get_inventory() + local droppos, dropdir + if node.name == "mcl_dispensers:dispenser" then + dropdir = vector.multiply(minetest.facedir_to_dir(node.param2), -1) + droppos = vector.add(pos, dropdir) + elseif node.name == "mcl_dispensers:dispenser_up" then + dropdir = {x=0, y=1, z=0} + droppos = {x=pos.x, y=pos.y+1, z=pos.z} + elseif node.name == "mcl_dispensers:dispenser_down" then + dropdir = {x=0, y=-1, z=0} + droppos = {x=pos.x, y=pos.y-1, z=pos.z} + end + local dropnode = minetest.get_node(droppos) + local dropnodedef = minetest.registered_nodes[dropnode.name] + local stacks = {} + for i=1,inv:get_size("main") do + local stack = inv:get_stack("main", i) + if not stack:is_empty() then + table.insert(stacks, {stack = stack, stackpos = i}) + end + end + if #stacks >= 1 then + local r = math.random(1, #stacks) + local stack = stacks[r].stack + local dropitem = ItemStack(stack) + dropitem:set_count(1) + local stack_id = stacks[r].stackpos + local stackdef = stack:get_definition() + local iname = stack:get_name() + local igroups = minetest.registered_items[iname].groups + + --[===[ Dispense item ]===] + + -- Hardcoded dispensions -- + + -- Armor, mob heads and pumpkins + if igroups.armor_head or igroups.armor_torso or igroups.armor_legs or igroups.armor_feet then + local armor_type, armor_slot + local armor_dispensed = false + if igroups.armor_head then + armor_type = "armor_head" + armor_slot = 2 + elseif igroups.armor_torso then + armor_type = "armor_torso" + armor_slot = 3 + elseif igroups.armor_legs then + armor_type = "armor_legs" + armor_slot = 4 + elseif igroups.armor_feet then + armor_type = "armor_feet" + armor_slot = 5 + end + + local droppos_below = {x=droppos.x, y=droppos.y-1, z=droppos.z} + local dropnode_below = minetest.get_node(droppos_below) + -- Put armor on player or armor stand + local standpos + if dropnode.name == "3d_armor_stand:armor_stand" then + standpos = droppos + elseif dropnode_below.name == "3d_armor_stand:armor_stand" then + standpos = droppos_below + end + if standpos then + local dropmeta = minetest.get_meta(standpos) + local dropinv = dropmeta:get_inventory() + if dropinv:room_for_item(armor_type, dropitem) then + dropinv:add_item(armor_type, dropitem) + --[[ FIXME: For some reason, this function is not called after calling add_item, + so we call it manually to update the armor stand entity. + This may need investigation and the following line may be a small hack. ]] + minetest.registered_nodes["3d_armor_stand:armor_stand"].on_metadata_inventory_put(standpos) + stack:take_item() + inv:set_stack("main", stack_id, stack) + armor_dispensed = true + end + else + -- Put armor on nearby player + -- First search for player in front of dispenser (check 2 nodes) + local objs1 = minetest.get_objects_inside_radius(droppos, 1) + local objs2 = minetest.get_objects_inside_radius(droppos_below, 1) + local objs_table = {objs1, objs2} + local player + for oi=1, #objs_table do + local objs_inner = objs_table[oi] + for o=1, #objs_inner do + --[[ First player in list is the lucky one. The other player get nothing :-( + If multiple players are close to the dispenser, it can be a bit + -- unpredictable on who gets the armor. ]] + if objs_inner[o]:is_player() then + player = objs_inner[o] + break + end + end + if player then + break + end + end + -- If player found, add armor + if player then + local ainv = minetest.get_inventory({type="detached", name=player:get_player_name().."_armor"}) + local pinv = player:get_inventory() + if ainv:get_stack("armor", armor_slot):is_empty() and pinv:get_stack("armor", armor_slot):is_empty() then + ainv:set_stack("armor", armor_slot, dropitem) + pinv:set_stack("armor", armor_slot, dropitem) + armor:set_player_armor(player) + armor:update_inventory(player) + + stack:take_item() + inv:set_stack("main", stack_id, stack) + armor_dispensed = true + end + end + + -- Place head or pumpkin as node, if equipping it as armor has failed + if not armor_dispensed then + if igroups.head or iname == "mcl_farming:pumpkin_face" then + if dropnodedef.buildable_to then + minetest.set_node(droppos, {name = iname, param2 = node.param2}) + stack:take_item() + inv:set_stack("main", stack_id, stack) + end + end + end + end + + -- Spawn Egg + elseif igroups.spawn_egg then + -- Spawn mob + if not dropnodedef.walkable then + pointed_thing = { above = droppos, under = { x=droppos.x, y=droppos.y-1, z=droppos.z } } + minetest.add_entity(droppos, stack:get_name()) + + stack:take_item() + inv:set_stack("main", stack_id, stack) + end + + -- Generalized dispension + elseif (not dropnodedef.walkable or stackdef._dispense_into_walkable) then + --[[ _on_dispense(stack, pos, droppos, dropnode, dropdir) + * stack: Itemstack which is dispense + * pos: Position of dispenser + * droppos: Position to which to dispense item + * dropnode: Node of droppos + * dropdir: Drop direction + + _dispense_into_walkable: If true, can dispense into walkable nodes + ]] + if stackdef._on_dispense then + -- Item-specific dispension (if defined) + local od_ret = stackdef._on_dispense(dropitem, pos, droppos, dropnode, dropdir) + if od_ret then + local newcount = stack:get_count() - 1 + stack:set_count(newcount) + inv:set_stack("main", stack_id, stack) + if newcount == 0 then + inv:set_stack("main", stack_id, od_ret) + elseif inv:room_for_item("main", od_ret) then + inv:add_item("main", od_ret) + else + minetest.add_item(droppos, dropitem) + end + else + stack:take_item() + inv:set_stack("main", stack_id, stack) + end + else + -- Drop item otherwise + minetest.add_item(droppos, dropitem) + stack:take_item() + inv:set_stack("main", stack_id, stack) + end + end + + + end + end, + rules = mesecon.rules.alldirs, + }}, + on_rotate = on_rotate, +} + +-- Horizontal dispenser + +local horizontal_def = table.copy(dispenserdef) +horizontal_def.description = S("Dispenser") +horizontal_def._doc_items_longdesc = S("A dispenser is a block which acts as a redstone component which, when powered with redstone power, dispenses an item. It has a container with 9 inventory slots.") +horizontal_def._doc_items_usagehelp = S("Place the dispenser in one of 6 possible directions. The “hole” is where items will fly out of the dispenser. Use the dispenser to access its inventory. Insert the items you wish to dispense. Supply the dispenser with redstone energy once to dispense a random item.").."\n\n".. + +S("The dispenser will do different things, depending on the dispensed item:").."\n\n".. + +S("• Arrows: Are launched").."\n".. +S("• Eggs and snowballs: Are thrown").."\n".. +S("• Fire charges: Are fired in a straight line").."\n".. +S("• Armor: Will be equipped to players and armor stands").."\n".. +S("• Boats: Are placed on water or are dropped").."\n".. +S("• Minecart: Are placed on rails or are dropped").."\n".. +S("• Bone meal: Is applied on the block it is facing").."\n".. +S("• Empty buckets: Are used to collect a liquid source").."\n".. +S("• Filled buckets: Are used to place a liquid source").."\n".. +S("• Heads, pumpkins: Equipped to players and armor stands, or placed as a block").."\n".. +S("• Shulker boxes: Are placed as a block").."\n".. +S("• TNT: Is placed and ignited").."\n".. +S("• Flint and steel: Is used to ignite a fire in air and to ignite TNT").."\n".. +S("• Spawn eggs: Will summon the mob they contain").."\n".. +S("• Other items: Are simply dropped") + +horizontal_def.after_place_node = function(pos, placer, itemstack, pointed_thing) + setup_dispenser(pos) + orientate_dispenser(pos, placer) +end +horizontal_def.tiles = { + "default_furnace_top.png", "default_furnace_bottom.png", + "default_furnace_side.png", "default_furnace_side.png", + "default_furnace_side.png", "mcl_dispensers_dispenser_front_horizontal.png" +} +horizontal_def.paramtype2 = "facedir" +horizontal_def.groups = {pickaxey=1, container=2, material_stone=1} + +minetest.register_node("mcl_dispensers:dispenser", horizontal_def) + +-- Down dispenser +local down_def = table.copy(dispenserdef) +down_def.description = S("Downwards-Facing Dispenser") +down_def.after_place_node = setup_dispenser +down_def.tiles = { + "default_furnace_top.png", "mcl_dispensers_dispenser_front_vertical.png", + "default_furnace_side.png", "default_furnace_side.png", + "default_furnace_side.png", "default_furnace_side.png" +} +down_def.groups = {pickaxey=1, container=2,not_in_creative_inventory=1, material_stone=1} +down_def._doc_items_create_entry = false +down_def.drop = "mcl_dispensers:dispenser" +minetest.register_node("mcl_dispensers:dispenser_down", down_def) + +-- Up dispenser +-- The up dispenser is almost identical to the down dispenser , it only differs in textures +local up_def = table.copy(down_def) +up_def.description = S("Upwards-Facing Dispenser") +up_def.tiles = { + "mcl_dispensers_dispenser_front_vertical.png", "default_furnace_bottom.png", + "default_furnace_side.png", "default_furnace_side.png", + "default_furnace_side.png", "default_furnace_side.png" +} +minetest.register_node("mcl_dispensers:dispenser_up", up_def) + + +minetest.register_craft({ + output = 'mcl_dispensers:dispenser', + recipe = { + {"mcl_core:cobble", "mcl_core:cobble", "mcl_core:cobble",}, + {"mcl_core:cobble", "mcl_bows:bow", "mcl_core:cobble",}, + {"mcl_core:cobble", "mesecons:redstone", "mcl_core:cobble",}, + } +}) + +-- Add entry aliases for the Help +if minetest.get_modpath("doc") then + doc.add_entry_alias("nodes", "mcl_dispensers:dispenser", "nodes", "mcl_dispensers:dispenser_down") + doc.add_entry_alias("nodes", "mcl_dispensers:dispenser", "nodes", "mcl_dispensers:dispenser_up") +end + +minetest.register_lbm({ + label = "Update dispenser formspecs (0.51.0)", + name = "mcl_dispensers:update_formspecs_0_51_0", + nodenames = { "mcl_dispensers:dispenser", "mcl_dispensers:dispenser_down", "mcl_dispensers:dispenser_up" }, + action = function(pos, node) + minetest.registered_nodes[node.name].on_construct(pos) + minetest.log("action", "[mcl_dispenser] Node formspec updated at "..minetest.pos_to_string(pos)) + end, +}) + diff --git a/mods/ITEMS/REDSTONE/mcl_droppers/init.lua b/mods/ITEMS/REDSTONE/mcl_droppers/init.lua index 157ce6801..7167de19f 100644 --- a/mods/ITEMS/REDSTONE/mcl_droppers/init.lua +++ b/mods/ITEMS/REDSTONE/mcl_droppers/init.lua @@ -206,3 +206,13 @@ if minetest.get_modpath("doc") then doc.add_entry_alias("nodes", "mcl_droppers:dropper", "nodes", "mcl_droppers:dropper_down") doc.add_entry_alias("nodes", "mcl_droppers:dropper", "nodes", "mcl_droppers:dropper_up") end + +minetest.register_lbm({ + label = "Update dropper formspecs (0.51.0)", + name = "mcl_droppers:update_formspecs_0_51_0", + nodenames = { "mcl_droppers:dropper", "mcl_droppers:dropper_down", "mcl_droppers:dropper_up" }, + action = function(pos, node) + setup_dropper(pos) + minetest.log("action", "[mcl_droppers] Node formspec updated at "..minetest.pos_to_string(pos)) + end, +}) diff --git a/mods/ITEMS/REDSTONE/mcl_droppers/init_new.lua b/mods/ITEMS/REDSTONE/mcl_droppers/init_new.lua new file mode 100644 index 000000000..a36a2dbe9 --- /dev/null +++ b/mods/ITEMS/REDSTONE/mcl_droppers/init_new.lua @@ -0,0 +1,219 @@ +--[[ This mod registers 3 nodes: +- One node for the horizontal-facing dropper (mcl_droppers:dropper) +- One node for the upwards-facing droppers (mcl_droppers:dropper_up) +- One node for the downwards-facing droppers (mcl_droppers:dropper_down) + +3 node definitions are needed because of the way the textures are defined. +All node definitions share a lot of code, so this is the reason why there +are so many weird tables below. +]] + +local S = minetest.get_translator("mcl_droppers") + +-- For after_place_node +local setup_dropper = function(pos) + -- Set formspec and inventory + local form = "size[9,8.75]".. + "background[-0.19,-0.25;9.41,9.49;crafting_inventory_9_slots.png]".. + mcl_vars.inventory_header.. + "label[0,4.0;"..minetest.formspec_escape(minetest.colorize("#313131", S("Inventory"))).."]".. + "list[current_player;main;0,4.5;9,3;9]".. + "list[current_player;main;0,7.74;9,1;]".. + "label[3,0;"..minetest.formspec_escape(minetest.colorize("#313131", S("Dropper"))).."]".. + "list[current_name;main;3,0.5;3,3;]".. + "listring[current_name;main]".. + "listring[current_player;main]" + local meta = minetest.get_meta(pos) + meta:set_string("formspec", form) + local inv = meta:get_inventory() + inv:set_size("main", 9) +end + +local orientate_dropper = function(pos, placer) + -- Not placed by player + if not placer then return end + + -- Pitch in degrees + local pitch = placer:get_look_vertical() * (180 / math.pi) + + if pitch > 55 then + minetest.swap_node(pos, {name="mcl_droppers:dropper_up"}) + elseif pitch < -55 then + minetest.swap_node(pos, {name="mcl_droppers:dropper_down"}) + end +end + +local on_rotate +if minetest.get_modpath("screwdriver") then + on_rotate = screwdriver.rotate_simple +end + +-- Shared core definition table +local dropperdef = { + is_ground_content = false, + sounds = mcl_sounds.node_sound_stone_defaults(), + after_dig_node = function(pos, oldnode, oldmetadata, digger) + local meta = minetest.get_meta(pos) + local meta2 = meta + meta:from_table(oldmetadata) + local inv = meta:get_inventory() + for i=1, inv:get_size("main") do + local stack = inv:get_stack("main", i) + if not stack:is_empty() then + local p = {x=pos.x+math.random(0, 10)/10-0.5, y=pos.y, z=pos.z+math.random(0, 10)/10-0.5} + minetest.add_item(p, stack) + end + end + meta:from_table(meta2:to_table()) + end, + allow_metadata_inventory_move = function(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, + allow_metadata_inventory_take = 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 + else + return stack:get_count() + end + end, + 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 + else + return stack:get_count() + end + end, + _mcl_blast_resistance = 17.5, + _mcl_hardness = 3.5, + mesecons = {effector = { + -- Drop random item when triggered + action_on = function (pos, node) + local meta = minetest.get_meta(pos) + local inv = meta:get_inventory() + local droppos + if node.name == "mcl_droppers:dropper" then + droppos = vector.subtract(pos, minetest.facedir_to_dir(node.param2)) + elseif node.name == "mcl_droppers:dropper_up" then + droppos = {x=pos.x, y=pos.y+1, z=pos.z} + elseif node.name == "mcl_droppers:dropper_down" then + droppos = {x=pos.x, y=pos.y-1, z=pos.z} + end + local dropnode = minetest.get_node(droppos) + -- Do not drop into solid nodes, unless they are containers + local dropnodedef = minetest.registered_nodes[dropnode.name] + if dropnodedef.walkable and not dropnodedef.groups.container then + return + end + local stacks = {} + for i=1,inv:get_size("main") do + local stack = inv:get_stack("main", i) + if not stack:is_empty() then + table.insert(stacks, {stack = stack, stackpos = i}) + end + end + if #stacks >= 1 then + local r = math.random(1, #stacks) + local stack = stacks[r].stack + local dropitem = ItemStack(stack) + dropitem:set_count(1) + local stack_id = stacks[r].stackpos + + -- If it's a container, attempt to put it into the container + local dropped = mcl_util.move_item_container(pos, droppos, nil, stack_id) + -- No container? + if not dropped and not dropnodedef.groups.container then + -- Drop item normally + minetest.add_item(droppos, dropitem) + stack:take_item() + inv:set_stack("main", stack_id, stack) + end + end + end, + rules = mesecon.rules.alldirs, + }}, + on_rotate = on_rotate, +} + +-- Horizontal dropper + +local horizontal_def = table.copy(dropperdef) +horizontal_def.description = S("Dropper") +horizontal_def._doc_items_longdesc = S("A dropper is a redstone component and a container with 9 inventory slots which, when supplied with redstone power, drops an item or puts it into a container in front of it.") +horizontal_def._doc_items_usagehelp = S("Droppers can be placed in 6 possible directions, items will be dropped out of the hole. Use the dropper to access its inventory. Supply it with redstone energy once to make the dropper drop or transfer a random item.") +horizontal_def.after_place_node = function(pos, placer, itemstack, pointed_thing) + setup_dropper(pos) + orientate_dropper(pos, placer) +end +horizontal_def.tiles = { + "default_furnace_top.png", "default_furnace_bottom.png", + "default_furnace_side.png", "default_furnace_side.png", + "default_furnace_side.png", "mcl_droppers_dropper_front_horizontal.png" +} +horizontal_def.paramtype2 = "facedir" +horizontal_def.groups = {pickaxey=1, container=2, material_stone=1} + +minetest.register_node("mcl_droppers:dropper", horizontal_def) + +-- Down dropper +local down_def = table.copy(dropperdef) +down_def.description = S("Downwards-Facing Dropper") +down_def.after_place_node = setup_dropper +down_def.tiles = { + "default_furnace_top.png", "mcl_droppers_dropper_front_vertical.png", + "default_furnace_side.png", "default_furnace_side.png", + "default_furnace_side.png", "default_furnace_side.png" +} +down_def.groups = {pickaxey=1, container=2,not_in_creative_inventory=1, material_stone=1} +down_def._doc_items_create_entry = false +down_def.drop = "mcl_droppers:dropper" +minetest.register_node("mcl_droppers:dropper_down", down_def) + +-- Up dropper +-- The up dropper is almost identical to the down dropper, it only differs in textures +local up_def = table.copy(down_def) +up_def.description = S("Upwards-Facing Dropper") +up_def.tiles = { + "mcl_droppers_dropper_front_vertical.png", "default_furnace_bottom.png", + "default_furnace_side.png", "default_furnace_side.png", + "default_furnace_side.png", "default_furnace_side.png" +} +minetest.register_node("mcl_droppers:dropper_up", up_def) + + + +-- Ladies and gentlemen, I present to you: the crafting recipe! +minetest.register_craft({ + output = 'mcl_droppers:dropper', + recipe = { + {"mcl_core:cobble", "mcl_core:cobble", "mcl_core:cobble",}, + {"mcl_core:cobble", "", "mcl_core:cobble",}, + {"mcl_core:cobble", "mesecons:redstone", "mcl_core:cobble",}, + } +}) + +-- Add entry aliases for the Help +if minetest.get_modpath("doc") then + doc.add_entry_alias("nodes", "mcl_droppers:dropper", "nodes", "mcl_droppers:dropper_down") + doc.add_entry_alias("nodes", "mcl_droppers:dropper", "nodes", "mcl_droppers:dropper_up") +end + +minetest.register_lbm({ + label = "Update dropper formspecs (0.51.0)", + name = "mcl_droppers:update_formspecs_0_51_0", + nodenames = { "mcl_droppers:dropper", "mcl_droppers:dropper_down", "mcl_droppers:dropper_up" }, + action = function(pos, node) + minetest.registered_nodes[node.name].on_construct(pos) + minetest.log("action", "[mcl_droppers] Node formspec updated at "..minetest.pos_to_string(pos)) + end, +}) + diff --git a/mods/ITEMS/mcl_chests/init.lua b/mods/ITEMS/mcl_chests/init.lua index f04fb6527..496740b5b 100644 --- a/mods/ITEMS/mcl_chests/init.lua +++ b/mods/ITEMS/mcl_chests/init.lua @@ -675,6 +675,16 @@ minetest.register_node("mcl_chests:ender_chest", { on_rotate = simple_rotate, }) +minetest.register_lbm({ + label = "Update ender chest + shulker box formspecs (0.51.0)", + name = "mcl_chests:update_formspecs_0_51_0", + nodenames = { "mcl_chests:ender_chest", "group:shulker_box" }, + action = function(pos, node) + minetest.registered_nodes[node.name].on_construct(pos) + minetest.log("action", "[mcl_chests] Node formspec updated at "..minetest.pos_to_string(pos)) + end, +}) + minetest.register_on_joinplayer(function(player) local inv = player:get_inventory() inv:set_size("enderchest", 9*3) diff --git a/mods/ITEMS/mcl_chests/init_new.lua b/mods/ITEMS/mcl_chests/init_new.lua new file mode 100644 index 000000000..496740b5b --- /dev/null +++ b/mods/ITEMS/mcl_chests/init_new.lua @@ -0,0 +1,878 @@ +local S = minetest.get_translator("mcl_chests") + +local no_rotate, simple_rotate +if minetest.get_modpath("screwdriver") then + no_rotate = screwdriver.disallow + simple_rotate = screwdriver.rotate_simple +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 player_chest_open = function(player, pos) + open_chests[player:get_player_name()] = { pos = pos } +end + +-- Simple protection checking functions +local protection_check_move = function(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 protection_check_put_take = 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 + 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 chest_update_after_close = function(pos) + local node = minetest.get_node(pos) + + if node.name == "mcl_chests:trapped_chest_on" then + minetest.swap_node(pos, {name="mcl_chests:trapped_chest", param2 = node.param2}) + 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}) + mesecon.receptor_off(pos, trapped_chest_mesecons_rules) + + local pos_other = mcl_util.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 = mcl_util.get_double_container_neighbor_pos(pos, node.param2, "right") + minetest.swap_node(pos_other, {name="mcl_chests:trapped_chest_left", param2 = node.param2}) + mesecon.receptor_off(pos_other, trapped_chest_mesecons_rules) + end +end + +-- To be called if a player closed a chest +local player_chest_close = function(player) + local name = player:get_player_name() + if open_chests[name] == nil then + return + end + local pos = open_chests[name].pos + chest_update_after_close(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 +local register_chest = function(basename, desc, longdesc, usagehelp, tiles_table, hidden, mesecons, on_rightclick_addendum, on_rightclick_addendum_left, on_rightclick_addendum_right, drop, canonical_basename) +-- START OF register_chest FUNCTION BODY +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 + +minetest.register_node("mcl_chests:"..basename, { + description = desc, + _doc_items_longdesc = longdesc, + _doc_items_usagehelp = usagehelp, + _doc_items_hidden = hidden, + tiles = tiles_table.small, + paramtype = "light", + paramtype2 = "facedir", + stack_max = 64, + drop = drop, + groups = {handy=1,axey=1, container=2, deco_block=1, material_wood=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, double 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 + if minetest.get_node(mcl_util.get_double_container_neighbor_pos(pos, param2, "right")).name == "mcl_chests:"..canonical_basename then + minetest.swap_node(pos, {name="mcl_chests:"..canonical_basename.."_right",param2=param2}) + local p = mcl_util.get_double_container_neighbor_pos(pos, param2, "right") + minetest.swap_node(p, { name = "mcl_chests:"..canonical_basename.."_left", param2 = param2 }) + elseif minetest.get_node(mcl_util.get_double_container_neighbor_pos(pos, param2, "left")).name == "mcl_chests:"..canonical_basename then + minetest.swap_node(pos, {name="mcl_chests:"..canonical_basename.."_left",param2=param2}) + local p = mcl_util.get_double_container_neighbor_pos(pos, param2, "left") + minetest.swap_node(p, { name = "mcl_chests:"..canonical_basename.."_right", param2 = param2 }) + else + minetest.swap_node(pos, { name = "mcl_chests:"..canonical_basename, param2 = param2 }) + end + end, + after_dig_node = function(pos, oldnode, oldmetadata, digger) + local meta = minetest.get_meta(pos) + local meta2 = meta + meta:from_table(oldmetadata) + local inv = meta:get_inventory() + for i=1,inv:get_size("main") do + local stack = inv:get_stack("main", i) + if not stack:is_empty() then + local p = {x=pos.x+math.random(0, 10)/10-0.5, y=pos.y, z=pos.z+math.random(0, 10)/10-0.5} + minetest.add_item(p, stack) + end + end + meta:from_table(meta2:to_table()) + end, + 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) + minetest.show_formspec(clicker:get_player_name(), + "mcl_chests:"..canonical_basename.."_"..pos.x.."_"..pos.y.."_"..pos.z, + "size[9,8.75]".. + mcl_vars.inventory_header.. + "background[-0.19,-0.25;9.41,10.48;mcl_chests_inventory_chest.png]".. + "label[0,0;"..minetest.formspec_escape(minetest.colorize("#313131", S("Chest"))).."]".. + "list[nodemeta:"..pos.x..","..pos.y..","..pos.z..";main;0,0.5;9,3;]".. + "label[0,4.0;"..minetest.formspec_escape(minetest.colorize("#313131", S("Inventory"))).."]".. + "list[current_player;main;0,4.5;9,3;9]".. + "list[current_player;main;0,7.74;9,1;]".. + "listring[nodemeta:"..pos.x..","..pos.y..","..pos.z..";main]".. + "listring[current_player;main]") + + if on_rightclick_addendum then + on_rightclick_addendum(pos, node, clicker) + end + end, + + on_destruct = function(pos) + local players = minetest.get_connected_players() + for p=1, #players do + minetest.close_formspec(players[p]:get_player_name(), "mcl_chests:"..canonical_basename.."_"..pos.x.."_"..pos.y.."_"..pos.z) + end + end, + mesecons = mesecons, + on_rotate = simple_rotate, +}) + +minetest.register_node("mcl_chests:"..basename.."_left", { + tiles = tiles_table.left, + paramtype = "light", + paramtype2 = "facedir", + groups = {handy=1,axey=1, container=5,not_in_creative_inventory=1, material_wood=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 = mcl_util.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 + minetest.swap_node(pos, n) + end + end, + on_destruct = function(pos) + local n = minetest.get_node(pos) + if n.name == "mcl_chests:"..basename then + return + end + + local players = minetest.get_connected_players() + for p=1, #players do + minetest.close_formspec(players[p]:get_player_name(), "mcl_chests:"..canonical_basename.."_"..pos.x.."_"..pos.y.."_"..pos.z) + end + + local param2 = n.param2 + local p = mcl_util.get_double_container_neighbor_pos(pos, param2, "left") + if not p or minetest.get_node(p).name ~= "mcl_chests:"..basename.."_right" then + return + end + for pl=1, #players do + minetest.close_formspec(players[pl]:get_player_name(), "mcl_chests:"..canonical_basename.."_"..p.x.."_"..p.y.."_"..p.z) + end + minetest.swap_node(p, { name = "mcl_chests:"..basename, param2 = param2 }) + end, + after_dig_node = function(pos, oldnode, oldmetadata, digger) + local meta = minetest.get_meta(pos) + local meta2 = meta + meta:from_table(oldmetadata) + local inv = meta:get_inventory() + for i=1,inv:get_size("main") do + local stack = inv:get_stack("main", i) + if not stack:is_empty() then + local p = {x=pos.x+math.random(0, 10)/10-0.5, y=pos.y, z=pos.z+math.random(0, 10)/10-0.5} + minetest.add_item(p, stack) + end + end + meta:from_table(meta2:to_table()) + 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 + -- BEGIN OF LISTRING WORKAROUND + elseif listname == "input" then + local inv = minetest.get_inventory({type="node", pos=pos}) + if inv:room_for_item("main", stack) then + return -1 + else + local other_pos = mcl_util.get_double_container_neighbor_pos(pos, minetest.get_node(pos).param2, "left") + local other_inv = minetest.get_inventory({type="node", pos=other_pos}) + if other_inv:room_for_item("main", stack) then + return -1 + else + return 0 + end + end + -- 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 leftover = inv:add_item("main", stack) + if not leftover:is_empty() then + local other_pos = mcl_util.get_double_container_neighbor_pos(pos, minetest.get_node(pos).param2, "left") + local other_inv = minetest.get_inventory({type="node", pos=other_pos}) + other_inv:add_item("main", leftover) + end + 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 = mcl_util.get_double_container_neighbor_pos(pos, node.param2, "left") + + minetest.show_formspec(clicker:get_player_name(), + "mcl_chests:"..canonical_basename.."_"..pos.x.."_"..pos.y.."_"..pos.z, + "size[9,11.5]".. + "background[-0.19,-0.25;9.41,12.5;mcl_chests_inventory_chest_large.png]".. + mcl_vars.inventory_header.. + "label[0,0;"..minetest.formspec_escape(minetest.colorize("#313131", S("Large Chest"))).."]".. + "list[nodemeta:"..pos.x..","..pos.y..","..pos.z..";main;0,0.5;9,3;]".. + "list[nodemeta:"..pos_other.x..","..pos_other.y..","..pos_other.z..";main;0,3.5;9,3;]".. + "label[0,7;"..minetest.formspec_escape(minetest.colorize("#313131", S("Inventory"))).."]".. + "list[current_player;main;0,7.5;9,3;9]".. + "list[current_player;main;0,10.75;9,1;]".. + -- BEGIN OF LISTRING WORKAROUND + "listring[current_player;main]".. + "listring[nodemeta:"..pos.x..","..pos.y..","..pos.z..";input]".. + -- END OF LISTRING WORKAROUND + "listring[current_player;main]".. + "listring[nodemeta:"..pos.x..","..pos.y..","..pos.z..";main]".. + "listring[current_player;main]".. + "listring[nodemeta:"..pos_other.x..","..pos_other.y..","..pos_other.z..";main]") + + if on_rightclick_addendum_left then + on_rightclick_addendum_left(pos, node, clicker) + end + end, + mesecons = mesecons, + on_rotate = no_rotate, +}) + +minetest.register_node("mcl_chests:"..basename.."_right", { + tiles = tiles_table.right, + paramtype = "light", + paramtype2 = "facedir", + groups = {handy=1,axey=1, container=6,not_in_creative_inventory=1, material_wood=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 = mcl_util.get_double_container_neighbor_pos(pos, param2, "right") + if not p or minetest.get_node(p).name ~= "mcl_chests:"..canonical_basename.."_left" then + n.name = "mcl_chests:"..canonical_basename + minetest.swap_node(pos, n) + end + end, + on_destruct = function(pos) + local n = minetest.get_node(pos) + if n.name == "mcl_chests:"..basename then + return + end + + local players = minetest.get_connected_players() + for p=1, #players do + minetest.close_formspec(players[p]:get_player_name(), "mcl_chests:"..canonical_basename.."_"..pos.x.."_"..pos.y.."_"..pos.z) + end + + local param2 = n.param2 + local p = mcl_util.get_double_container_neighbor_pos(pos, param2, "right") + if not p or minetest.get_node(p).name ~= "mcl_chests:"..basename.."_left" then + return + end + for pl=1, #players do + minetest.close_formspec(players[pl]:get_player_name(), "mcl_chests:"..canonical_basename.."_"..p.x.."_"..p.y.."_"..p.z) + end + minetest.swap_node(p, { name = "mcl_chests:"..basename, param2 = param2 }) + end, + after_dig_node = function(pos, oldnode, oldmetadata, digger) + local meta = minetest.get_meta(pos) + local meta2 = meta + meta:from_table(oldmetadata) + local inv = meta:get_inventory() + for i=1,inv:get_size("main") do + local stack = inv:get_stack("main", i) + if not stack:is_empty() then + local p = {x=pos.x+math.random(0, 10)/10-0.5, y=pos.y, z=pos.z+math.random(0, 10)/10-0.5} + minetest.add_item(p, stack) + end + end + meta:from_table(meta2:to_table()) + 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 + -- BEGIN OF LISTRING WORKAROUND + elseif listname == "input" then + local other_pos = mcl_util.get_double_container_neighbor_pos(pos, minetest.get_node(pos).param2, "right") + local other_inv = minetest.get_inventory({type="node", pos=other_pos}) + if other_inv:room_for_item("main", stack) then + return -1 + else + local inv = minetest.get_inventory({type="node", pos=pos}) + if inv:room_for_item("main", stack) then + return -1 + else + return 0 + end + end + -- 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 = mcl_util.get_double_container_neighbor_pos(pos, minetest.get_node(pos).param2, "right") + local other_inv = minetest.get_inventory({type="node", pos=other_pos}) + local leftover = other_inv:add_item("main", stack) + if not leftover:is_empty() then + local inv = minetest.get_inventory({type="node", pos=pos}) + inv:add_item("main", leftover) + end + 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 = mcl_util.get_double_container_neighbor_pos(pos, node.param2, "right") + + minetest.show_formspec(clicker:get_player_name(), + "mcl_chests:"..canonical_basename.."_"..pos.x.."_"..pos.y.."_"..pos.z, + + "size[9,11.5]".. + "background[-0.19,-0.25;9.41,12.5;mcl_chests_inventory_chest_large.png]".. + mcl_vars.inventory_header.. + "label[0,0;"..minetest.formspec_escape(minetest.colorize("#313131", S("Large Chest"))).."]".. + "list[nodemeta:"..pos_other.x..","..pos_other.y..","..pos_other.z..";main;0,0.5;9,3;]".. + "list[nodemeta:"..pos.x..","..pos.y..","..pos.z..";main;0,3.5;9,3;]".. + "label[0,7;"..minetest.formspec_escape(minetest.colorize("#313131", S("Inventory"))).."]".. + "list[current_player;main;0,7.5;9,3;9]".. + "list[current_player;main;0,10.75;9,1;]".. + -- BEGIN OF LISTRING WORKAROUND + "listring[current_player;main]".. + "listring[nodemeta:"..pos.x..","..pos.y..","..pos.z..";input]".. + -- END OF LISTRING WORKAROUND + "listring[current_player;main]".. + "listring[nodemeta:"..pos_other.x..","..pos_other.y..","..pos_other.z..";main]".. + "listring[current_player;main]".. + "listring[nodemeta:"..pos.x..","..pos.y..","..pos.z..";main]") + + if on_rightclick_addendum_right then + on_rightclick_addendum_right(pos, node, clicker) + end + end, + mesecons = mesecons, + on_rotate = no_rotate, +}) + +if minetest.get_modpath("doc") then + doc.add_entry_alias("nodes", "mcl_chests:"..basename, "nodes", "mcl_chests:"..basename.."_left") + doc.add_entry_alias("nodes", "mcl_chests:"..basename, "nodes", "mcl_chests:"..basename.."_right") +end + +-- END OF register_chest FUNCTION BODY +end + +local chestusage = S("To access its inventory, rightclick it. When broken, the items will drop out.") + +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, + { + small = {"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"}, + left = {"default_chest_top_big.png", "default_chest_top_big.png", + "mcl_chests_chest_right.png", "mcl_chests_chest_left.png", + "default_chest_side_big.png^[transformFX", "default_chest_front_big.png"}, + right = {"default_chest_top_big.png^[transformFX", "default_chest_top_big.png^[transformFX", + "mcl_chests_chest_right.png", "mcl_chests_chest_left.png", + "default_chest_side_big.png", "default_chest_front_big.png^[transformFX"}, + }, + false +) + +local traptiles = { + small = {"mcl_chests_chest_trapped_top.png", "mcl_chests_chest_trapped_bottom.png", + "mcl_chests_chest_trapped_right.png", "mcl_chests_chest_trapped_left.png", + "mcl_chests_chest_trapped_back.png", "mcl_chests_chest_trapped_front.png"}, + left = {"mcl_chests_chest_trapped_top_big.png", "mcl_chests_chest_trapped_top_big.png", + "mcl_chests_chest_trapped_right.png", "mcl_chests_chest_trapped_left.png", + "mcl_chests_chest_trapped_side_big.png^[transformFX", "mcl_chests_chest_trapped_front_big.png"}, + right = {"mcl_chests_chest_trapped_top_big.png^[transformFX", "mcl_chests_chest_trapped_top_big.png^[transformFX", + "mcl_chests_chest_trapped_right.png", "mcl_chests_chest_trapped_left.png", + "mcl_chests_chest_trapped_side_big.png", "mcl_chests_chest_trapped_front_big.png^[transformFX"}, +} + +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, + 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", param2 = node.param2}) + mesecon.receptor_on(pos, trapped_chest_mesecons_rules) + player_chest_open(clicker, pos) + 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}) + mesecon.receptor_on(pos, trapped_chest_mesecons_rules) + + local pos_other = mcl_util.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) + + player_chest_open(clicker, pos) + end, + function(pos, node, clicker) + local pos_other = mcl_util.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}) + mesecon.receptor_on(pos_other, trapped_chest_mesecons_rules) + + player_chest_open(clicker, pos) + end +) + +register_chest("trapped_chest_on", + nil, nil, nil, traptiles, true, + {receptor = { + state = mesecon.state.on, + rules = trapped_chest_mesecons_rules, + }}, + function(pos, node, clicker) + player_chest_open(clicker, pos) + end, + function(pos, node, clicker) + player_chest_open(clicker, pos) + end, + function(pos, node, clicker) + player_chest_open(clicker, pos) + end, + "trapped_chest", + "trapped_chest" +) + +local function close_if_trapped_chest(pos, player) + local node = minetest.get_node(pos) + + if node.name == "mcl_chests:trapped_chest_on" then + minetest.swap_node(pos, {name="mcl_chests:trapped_chest", param2 = node.param2}) + mesecon.receptor_off(pos, trapped_chest_mesecons_rules) + + player_chest_close(player) + elseif node.name == "mcl_chests:trapped_chest_on_left" then + minetest.swap_node(pos, {name="mcl_chests:trapped_chest_left", param2 = node.param2}) + mesecon.receptor_off(pos, trapped_chest_mesecons_rules) + + local pos_other = mcl_util.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) + + player_chest_close(player) + 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 = mcl_util.get_double_container_neighbor_pos(pos, node.param2, "right") + minetest.swap_node(pos_other, {name="mcl_chests:trapped_chest_left", param2 = node.param2}) + mesecon.receptor_off(pos_other, trapped_chest_mesecons_rules) + + player_chest_close(player) + end +end + +-- Disable trapped chest when it has been closed +minetest.register_on_player_receive_fields(function(player, formname, fields) + if formname:find("mcl_chests:trapped_chest_") == 1 then + if fields.quit then + player_chest_close(player) + end + end +end) + +minetest.register_on_leaveplayer(function(player) + player_chest_close(player) +end) + +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 +}) + +minetest.register_node("mcl_chests:ender_chest", { + description = S("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."), + tiles = {"mcl_chests_ender_chest_top.png", "mcl_chests_ender_chest_bottom.png", + "mcl_chests_ender_chest_right.png", "mcl_chests_ender_chest_left.png", + "mcl_chests_ender_chest_back.png", "mcl_chests_ender_chest_front.png"}, + -- 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}, + 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) + local meta = minetest.get_meta(pos) + meta:set_string("formspec", + "size[9,8.75]".. + mcl_vars.inventory_header.. + "background[-0.19,-0.25;9.41,10.48;mcl_chests_inventory_chest.png]".. + "label[0,0;"..minetest.formspec_escape(minetest.colorize("#313131", S("Ender Chest"))).."]".. + "list[current_player;enderchest;0,0.5;9,3;]".. + "label[0,4.0;"..minetest.formspec_escape(minetest.colorize("#313131", S("Inventory"))).."]".. + "list[current_player;main;0,4.5;9,3;9]".. + "list[current_player;main;0,7.74;9,1;]".. + "listring[current_player;enderchest]".. + "listring[current_player;main]") + end, + _mcl_blast_resistance = 3000, + _mcl_hardness = 22.5, + on_rotate = simple_rotate, +}) + +minetest.register_lbm({ + label = "Update ender chest + shulker box formspecs (0.51.0)", + name = "mcl_chests:update_formspecs_0_51_0", + nodenames = { "mcl_chests:ender_chest", "group:shulker_box" }, + action = function(pos, node) + minetest.registered_nodes[node.name].on_construct(pos) + minetest.log("action", "[mcl_chests] Node formspec updated at "..minetest.pos_to_string(pos)) + end, +}) + +minetest.register_on_joinplayer(function(player) + local inv = player:get_inventory() + inv:set_size("enderchest", 9*3) +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'}, + } +}) + +-- 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", +} + +for color, desc in pairs(boxtypes) do + local mob_texture = shulker_mob_textures[color] + minetest.register_node("mcl_chests:"..color.."_shulker_box", { + description = desc, + _doc_items_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."), + _doc_items_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."), + tiles = { + "mcl_chests_"..color.."_shulker_box_top.png", -- top + "[combine:16x16:-32,-28="..mob_texture, -- bottom + "[combine:16x16:0,-36="..mob_texture..":0,-16="..mob_texture, -- side + "[combine:16x16:-32,-36="..mob_texture..":-32,-16="..mob_texture, -- side + "[combine:16x16:-16,-36="..mob_texture..":-16,-16="..mob_texture, -- side + "[combine:16x16:-48,-36="..mob_texture..":-48,-16="..mob_texture, -- side + }, + groups = {handy=1,pickaxey=1, container=3, deco_block=1, dig_by_piston=1, shulker_box=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", + "size[9,8.75]".. + mcl_vars.inventory_header.. + "background[-0.19,-0.25;9.41,10.48;mcl_chests_inventory_chest.png]".. + "label[0,0;"..minetest.formspec_escape(minetest.colorize("#313131", S("Shulker Box"))).."]".. + "list[current_name;main;0,0.5;9,3;]".. + "label[0,4.0;"..minetest.formspec_escape(minetest.colorize("#313131", S("Inventory"))).."]".. + "list[current_player;main;0,4.5;9,3;9]".. + "list[current_player;main;0,7.74;9,1;]".. + "listring[current_name;main]".. + "listring[current_player;main]") + local inv = meta:get_inventory() + inv:set_size("main", 9*3) + 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 = stack:get_name(), param2 = minetest.dir_to_facedir(dropdir)}) + local imeta = stack:get_metadata() + local iinv_main = minetest.deserialize(imeta) + local ninv = minetest.get_inventory({type="node", pos=droppos}) + ninv:set_list("main", iinv_main) + stack:take_item() + end + return stack + end, + after_place_node = function(pos, placer, itemstack, pointed_thing) + local nmeta = minetest.get_meta(pos) + local ninv = nmeta:get_inventory() + local imeta = itemstack:get_metadata() + local iinv_main = minetest.deserialize(imeta) + ninv:set_list("main", iinv_main) + ninv:set_size("main", 9*3) + if minetest.settings:get_bool("creative_mode") then + if not ninv:is_empty("main") then + return nil + else + return itemstack + end + else + return nil + 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") + boxitem:set_metadata(data) + + if minetest.settings:get_bool("creative_mode") 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 = 30, + _mcl_hardness = 6, + }) + + minetest.register_craft({ + type = "shapeless", + output = 'mcl_chests:'..color..'_shulker_box', + 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'}, + } +}) + +minetest.register_lbm({ + -- Disable active/open trapped chests when loaded because nobody could + -- have them open at loading time. + -- Fixes redstone weirdness. + label = "Disable active trapped chests", + name = "mcl_chests:reset_trapped_chests", + nodenames = { "mcl_chests:trapped_chest_on", "mcl_chests:trapped_chest_on_left", "mcl_chests:trapped_chest_on_right" }, + run_at_every_load = true, + action = function(pos, node) + minetest.log("error", "lbm!" ..minetest.pos_to_string(pos)) + chest_update_after_close(pos) + end, +})