From df5f8d837932dc70a812b850ef9b78ee3733111d Mon Sep 17 00:00:00 2001 From: Ajrat Makhmutov Date: Sun, 21 Jul 2024 04:16:35 +0300 Subject: [PATCH] Add multi-node containers support + remove trailing spaces + add new api for extra information about containers --- abms.lua | 12 ++--- api.lua | 130 ++++++++++++++++++++++++++++++++-------------- nodes/chute.lua | 12 ++--- nodes/hoppers.lua | 28 ++++++---- nodes/sorter.lua | 42 +++++++-------- utility.lua | 64 +++++++++++++++-------- 6 files changed, 184 insertions(+), 104 deletions(-) diff --git a/abms.lua b/abms.lua index edef8ed..a5536be 100644 --- a/abms.lua +++ b/abms.lua @@ -9,7 +9,7 @@ minetest.register_abm({ if active_object_count_wider == 0 then return end - + local inv = minetest.get_meta(pos):get_inventory() local posob @@ -96,21 +96,21 @@ minetest.register_abm({ source_pos = vector.subtract(pos, destination_dir) destination_pos = vector.add(pos, destination_dir) end - + local output_direction if destination_dir.y == 0 then output_direction = "horizontal" end - + local source_node = minetest.get_node(source_pos) local destination_node = minetest.get_node(destination_pos) - local registered_source_inventories = hopper.get_registered_inventories_for(source_node.name) + local registered_source_inventories = hopper.get_registered(source_node.name) if registered_source_inventories ~= nil then hopper.take_item_from(pos, source_pos, source_node, registered_source_inventories["top"]) end - - local registered_destination_inventories = hopper.get_registered_inventories_for(destination_node.name) + + local registered_destination_inventories = hopper.get_registered(destination_node.name) if registered_destination_inventories ~= nil then if output_direction == "horizontal" then hopper.send_item_to(pos, destination_pos, destination_node, registered_destination_inventories["side"]) diff --git a/api.lua b/api.lua index 4eb6282..6fb3a9c 100644 --- a/api.lua +++ b/api.lua @@ -2,60 +2,112 @@ hopper.containers = {} hopper.groups = {} hopper.neighbors = {} +local function parse_group(target_node) + local number + local identifier + + local equals_index = string.find(target_node, "=") + if equals_index ~= nil then + identifier = string.sub(target_node, 7, equals_index-1) + -- it's possible that the string was of the form "group:blah = 1", in which case we want to trim spaces off the end of the group identifier + local space_index = string.find(identifier, " ") + if space_index ~= nil then + identifier = string.sub(identifier, 1, space_index-1) + end + number = tonumber(string.sub(target_node, equals_index+1, -1)) + else + identifier = string.sub(target_node, 7, -1) + number = "all" -- special value to indicate no number was provided + end + + return identifier, number +end + +local function is_already_in_neighbors(neighbor_node) + for _, value in pairs(hopper.neighbors) do + if value == neighbor_node then + return true + end + end + return false +end + -- global function to add new containers function hopper:add_container(list) for _, entry in pairs(list) do - + local target_node = entry[2] local neighbor_node - + if string.sub(target_node, 1, 6) == "group:" then - local group_identifier, group_number - local equals_index = string.find(target_node, "=") - if equals_index ~= nil then - group_identifier = string.sub(target_node, 7, equals_index-1) - -- it's possible that the string was of the form "group:blah = 1", in which case we want to trim spaces off the end of the group identifier - local space_index = string.find(group_identifier, " ") - if space_index ~= nil then - group_identifier = string.sub(group_identifier, 1, space_index-1) - end - group_number = tonumber(string.sub(target_node, equals_index+1, -1)) - else - group_identifier = string.sub(target_node, 7, -1) - group_number = "all" -- special value to indicate no number was provided + local group_identifier, group_number = parse_group(target_node) + + if hopper.groups[group_identifier] == nil then + hopper.groups[group_identifier] = {} end - - local group_info = hopper.groups[group_identifier] - if group_info == nil then - group_info = {} + local group = hopper.groups[group_identifier][group_number] + if group == nil then + group = {} end - if group_info[group_number] == nil then - group_info[group_number] = {} + if group[entry[1]] == nil then + group[entry[1]] = {} end - group_info[group_number][entry[1]] = entry[3] - hopper.groups[group_identifier] = group_info + group[entry[1]]["inventory_name"] = entry[3] + if entry["get_inventory"] then + group[entry[1]]["get_inventory"] = entry["get_inventory"] + end + hopper.groups[group_identifier][group_number] = group neighbor_node = "group:"..group_identifier - -- result is a table of the form groups[group_identifier][group_number][relative_position][inventory_name] + -- result is a table of the form: + -- groups[group_identifier][group_number][relative_position]{"inventory_name","get_inventory"} else local node_info = hopper.containers[target_node] if node_info == nil then node_info = {} end - node_info[entry[1]] = entry[3] + if node_info[entry[1]] == nil then + node_info[entry[1]] = {} + end + node_info[entry[1]]["inventory_name"] = entry[3] + if entry["get_inventory"] then + node_info[entry[1]]["get_inventory"] = entry["get_inventory"] + end hopper.containers[target_node] = node_info neighbor_node = target_node - -- result is a table of the form containers[target_node_name][relative_position][inventory_name] + -- result is a table of the form: + -- containers[target_node_name][relative_position]{"inventory_name","get_inventory"} end - - local already_in_neighbors = false - for _, value in pairs(hopper.neighbors) do - if value == neighbor_node then - already_in_neighbors = true + + if not is_already_in_neighbors(neighbor_node) then + table.insert(hopper.neighbors, neighbor_node) + end + end +end + +-- global function for additional information about containers +function hopper:set_extra_container_info(list) + for _, entry in pairs(list) do + local target_node = entry[1] + table.remove(entry, 1) -- only extra information + if string.sub(target_node, 1, 6) == "group:" then + local group_identifier, group_number = parse_group(target_node) + if not is_already_in_neighbors("group:" .. group_identifier) then + minetest.log("error","An attempt to add extra information for " .. + target_node .. " in the absence of the main one") break end - end - if not already_in_neighbors then - table.insert(hopper.neighbors, neighbor_node) + hopper.groups[group_identifier][group_number].extra = entry + -- result is a table of the form: + -- groups[group_identifier][group_number]["extra"][list of extra information] + else + if not is_already_in_neighbors(target_node) then + minetest.log("error","An attempt to add extra information for " .. + target_node .. " in the absence of the main one") + break + end + hopper.containers[target_node].extra = entry + -- result is a table of the form: + -- containers[target_node_name]["extra"][list of extra information] end end end @@ -69,10 +121,10 @@ hopper:add_container({ {"bottom", "hopper:hopper", "main"}, {"side", "hopper:hopper", "main"}, {"side", "hopper:hopper_side", "main"}, - + {"bottom", "hopper:chute", "main"}, {"side", "hopper:chute", "main"}, - + {"bottom", "hopper:sorter", "main"}, {"side", "hopper:sorter", "main"}, }) @@ -82,15 +134,15 @@ if minetest.get_modpath("default") then {"top", "default:chest", "main"}, {"bottom", "default:chest", "main"}, {"side", "default:chest", "main"}, - + {"top", "default:furnace", "dst"}, {"bottom", "default:furnace", "src"}, {"side", "default:furnace", "fuel"}, - + {"top", "default:furnace_active", "dst"}, {"bottom", "default:furnace_active", "src"}, {"side", "default:furnace_active", "fuel"}, - + {"top", "default:chest_locked", "main"}, {"bottom", "default:chest_locked", "main"}, {"side", "default:chest_locked", "main"}, diff --git a/nodes/chute.lua b/nodes/chute.lua index 48d6b2d..3a93bf6 100644 --- a/nodes/chute.lua +++ b/nodes/chute.lua @@ -41,7 +41,7 @@ minetest.register_node("hopper:chute", { {-0.2, -0.2, 0.3, 0.2, 0.2, 0.7}, }, }, - + on_construct = function(pos) local inv = minetest.get_meta(pos):get_inventory() inv:set_size("main", 2*2) @@ -60,7 +60,7 @@ minetest.register_node("hopper:chute", { end return returned_stack end, - + can_dig = function(pos,player) local inv = minetest.get_meta(pos):get_inventory() return inv:is_empty("main") @@ -81,7 +81,7 @@ minetest.register_node("hopper:chute", { local timer = minetest.get_node_timer(pos) if not timer:is_started() then timer:start(1) - end + end end, on_timer = function(pos, elapsed) @@ -95,9 +95,9 @@ minetest.register_node("hopper:chute", { if dir.y == 0 then output_direction = "horizontal" end - + local destination_node = minetest.get_node(destination_pos) - local registered_inventories = hopper.get_registered_inventories_for(destination_node.name) + local registered_inventories = hopper.get_registered(destination_node.name) if registered_inventories ~= nil then if output_direction == "horizontal" then hopper.send_item_to(pos, destination_pos, destination_node, registered_inventories["side"]) @@ -107,7 +107,7 @@ minetest.register_node("hopper:chute", { else hopper.send_item_to(pos, destination_pos, destination_node) end - + if not inv:is_empty("main") then minetest.get_node_timer(pos):start(1) end diff --git a/nodes/hoppers.lua b/nodes/hoppers.lua index a5e53ae..fe57790 100644 --- a/nodes/hoppers.lua +++ b/nodes/hoppers.lua @@ -29,16 +29,22 @@ local hopper_on_place = function(itemstack, placer, pointed_thing, node_name) return returned_stack end - local pointed_pos = pointed_thing.under - local hopper_pos = pointed_thing.above + local pointed_pos = pointed_thing.under + local hopper_pos = pointed_thing.above - local param2_by_offset = { - [vector.new(-1, 0, 0):to_string()] = 0, - [vector.new( 0, 0, 1):to_string()] = 1, - [vector.new( 1, 0, 0):to_string()] = 2, - [vector.new( 0, 0,-1):to_string()] = 3, - } - local param2 = param2_by_offset[(pointed_pos - hopper_pos):to_string()] + local param2 + local pointed_registered = hopper.get_registered(minetest.get_node(pointed_pos).name) + if pointed_registered and pointed_registered.extra.set_hopper_param2 then + param2 = pointed_registered.extra.set_hopper_param2(hopper_pos, pointed_pos) + else + local param2_by_offset = { + [vector.new(-1, 0, 0):to_string()] = 0, + [vector.new( 0, 0, 1):to_string()] = 1, + [vector.new( 1, 0, 0):to_string()] = 2, + [vector.new( 0, 0,-1):to_string()] = 3, + } + param2 = param2_by_offset[(pointed_pos - hopper_pos):to_string()] + end if param2 then returned_stack, success = minetest.item_place_node(ItemStack("hopper:hopper_side"), placer, pointed_thing, param2) @@ -196,7 +202,7 @@ minetest.register_node("hopper:hopper_side", { {-0.7, -0.3, -0.15, 0.15, 0.0, 0.15}, }, }, - + on_construct = function(pos) local inv = minetest.get_meta(pos):get_inventory() inv:set_size("main", 4*4) @@ -205,7 +211,7 @@ minetest.register_node("hopper:hopper_side", { on_place = function(itemstack, placer, pointed_thing) return hopper_on_place(itemstack, placer, pointed_thing, "hopper:hopper_side") end, - + can_dig = function(pos,player) local inv = minetest.get_meta(pos):get_inventory() return inv:is_empty("main") diff --git a/nodes/sorter.lua b/nodes/sorter.lua index e0dc12b..26550bc 100644 --- a/nodes/sorter.lua +++ b/nodes/sorter.lua @@ -16,7 +16,7 @@ end local function get_sorter_formspec(pos) local spos = hopper.get_string_pos(pos) - + local filter_all = minetest.get_meta(pos):get_string("filter_all") == "true" local y_displace = 0 local filter_texture, filter_button_tooltip, filter_body @@ -30,11 +30,11 @@ local function get_sorter_formspec(pos) filter_button_tooltip = FS("This sorter is currently set to only send items listed\nin the filter list in the direction of the arrow.\nClick this button to set it to try sending all\nitems that way first.") y_displace = 1.6 end - + local formspec = "size[8," .. 7 + y_displace .. "]" .. hopper.formspec_bg - .. filter_body + .. filter_body .. "list[nodemeta:" .. spos .. ";main;3,".. tostring(0.3 + y_displace) .. ";2,2;]" .. ("image_button_exit[0,%g;1,1;%s;filter_all;]"):format(y_displace, filter_texture) .. "tooltip[filter_all;" .. filter_button_tooltip.. "]" @@ -72,14 +72,14 @@ minetest.register_node("hopper:sorter", { {-0.2, -0.3, -0.2, 0.2, -0.7, 0.2}, }, }, - + on_construct = function(pos) local meta = minetest.get_meta(pos) local inv = meta:get_inventory() inv:set_size("main", 2*2) inv:set_size("filter", 8) end, - + on_place = function(itemstack, placer, pointed_thing, node_name) local pos = pointed_thing.under local pos2 = pointed_thing.above @@ -93,13 +93,13 @@ minetest.register_node("hopper:sorter", { end return returned_stack end, - + can_dig = function(pos,player) local meta = minetest.get_meta(pos); local inv = meta:get_inventory() return inv:is_empty("main") end, - + on_rightclick = function(pos, node, clicker, itemstack) if minetest.is_protected(pos, clicker:get_player_name()) and not minetest.check_player_privs(clicker, "protection_bypass") then return @@ -107,7 +107,7 @@ minetest.register_node("hopper:sorter", { minetest.show_formspec(clicker:get_player_name(), "hopper_formspec:"..minetest.pos_to_string(pos), get_sorter_formspec(pos)) end, - + allow_metadata_inventory_put = function(pos, listname, index, stack, player) if listname == "filter" then local inv = minetest.get_inventory({type="node", pos=pos}) @@ -116,7 +116,7 @@ minetest.register_node("hopper:sorter", { end return stack:get_count() end, - + allow_metadata_inventory_take = function(pos, listname, index, stack, player) if listname == "filter" then local inv = minetest.get_inventory({type="node", pos=pos}) @@ -125,7 +125,7 @@ minetest.register_node("hopper:sorter", { end return stack:get_count() end, - + allow_metadata_inventory_move = function(pos, from_list, from_index, to_list, to_index, count, player) if to_list == "filter" then local inv = minetest.get_inventory({type="node", pos=pos}) @@ -135,11 +135,11 @@ minetest.register_node("hopper:sorter", { elseif from_list == "filter" then local inv = minetest.get_inventory({type="node", pos=pos}) inv:set_stack(from_list, from_index, ItemStack("")) - return 0 + return 0 end return count end, - + on_metadata_inventory_put = function(pos, listname, index, stack, player) hopper.log_inventory(("%s moves stuff to sorter at %s"):format( player:get_player_name(), minetest.pos_to_string(pos))) @@ -147,15 +147,15 @@ minetest.register_node("hopper:sorter", { local timer = minetest.get_node_timer(pos) if not timer:is_started() then timer:start(1) - end + end end, - + on_timer = function(pos, elapsed) local meta = minetest.get_meta(pos); local inv = meta:get_inventory() -- build a filter list - local filter_items = nil + local filter_items = nil if meta:get_string("filter_all") ~= "true" then filter_items = {} local filter_inv_size = inv:get_size("filter") @@ -167,7 +167,7 @@ minetest.register_node("hopper:sorter", { end end end - + local node = minetest.get_node(pos) local dir = minetest.facedir_to_dir(node.param2) local default_destination_pos = vector.add(pos, dir) @@ -184,9 +184,9 @@ minetest.register_node("hopper:sorter", { end local success = false - + local filter_destination_node = minetest.get_node(filter_destination_pos) - local registered_inventories = hopper.get_registered_inventories_for(filter_destination_node.name) + local registered_inventories = hopper.get_registered(filter_destination_node.name) if registered_inventories ~= nil then if filter_output_direction == "horizontal" then success = hopper.send_item_to(pos, filter_destination_pos, filter_destination_node, registered_inventories["side"], filter_items) @@ -196,10 +196,10 @@ minetest.register_node("hopper:sorter", { else success = hopper.send_item_to(pos, filter_destination_pos, filter_destination_node, nil, filter_items) end - + if not success then -- weren't able to put something in the filter destination, for whatever reason. Now we can start moving stuff forward to the default. local default_destination_node = minetest.get_node(default_destination_pos) - local registered_inventories = hopper.get_registered_inventories_for(default_destination_node.name) + local registered_inventories = hopper.get_registered(default_destination_node.name) if registered_inventories ~= nil then if default_output_direction == "horizontal" then hopper.send_item_to(pos, default_destination_pos, default_destination_node, registered_inventories["side"]) @@ -210,7 +210,7 @@ minetest.register_node("hopper:sorter", { hopper.send_item_to(pos, default_destination_pos, default_destination_node) end end - + if not inv:is_empty("main") then minetest.get_node_timer(pos):start(1) end diff --git a/utility.lua b/utility.lua index 0e8c646..ca35b9b 100644 --- a/utility.lua +++ b/utility.lua @@ -4,7 +4,7 @@ local FS = hopper.translator_escaped -- looks first for a registration matching the specific node name, then for a registration -- matching group and value, then for a registration matching a group and *any* value -hopper.get_registered_inventories_for = function(target_node_name) +hopper.get_registered = function(target_node_name) local output = hopper.containers[target_node_name] if output ~= nil then return output end @@ -70,38 +70,48 @@ local get_placer = function(player_name) end -- Used to remove items from the target block and put it into the hopper's inventory -hopper.take_item_from = function(hopper_pos, target_pos, target_node, target_inventory_name) - if target_inventory_name == nil then - return - end +hopper.take_item_from = function(hopper_pos, target_pos, target_node, target_inv_info) local target_def = minetest.registered_nodes[target_node.name] if not target_def then return end + local target_inv_name = target_inv_info["inventory_name"] + local target_get_inv = target_inv_info["get_inventory"] + --hopper inventory - local hopper_meta = minetest.get_meta(hopper_pos); + local hopper_meta = minetest.get_meta(hopper_pos) local hopper_inv = hopper_meta:get_inventory() local placer = get_placer(hopper_meta:get_string("placer")) --source inventory - local target_inv = minetest.get_meta(target_pos):get_inventory() - local target_inv_size = target_inv:get_size(target_inventory_name) - if target_inv:is_empty(target_inventory_name) == false then + local target_inv + if target_get_inv then + target_inv = target_get_inv(target_pos) + if not target_inv then + minetest.log("error","No inventory from api get_inventory function: " .. + target_node.name .. " on " .. vector.to_string(target_pos)) + return false + end + else + target_inv = minetest.get_meta(target_pos):get_inventory() + end + local target_inv_size = target_inv:get_size(target_inv_name) + if target_inv:is_empty(target_inv_name) == false then for i = 1,target_inv_size do - local stack = target_inv:get_stack(target_inventory_name, i) + local stack = target_inv:get_stack(target_inv_name, i) local item = stack:get_name() if item ~= "" then if hopper_inv:room_for_item("main", item) then local stack_to_take = stack:take_item(1) if target_def.allow_metadata_inventory_take == nil or placer == nil -- backwards compatibility, older versions of this mod didn't record who placed the hopper - or target_def.allow_metadata_inventory_take(target_pos, target_inventory_name, i, stack_to_take, placer) > 0 then - target_inv:set_stack(target_inventory_name, i, stack) + or target_def.allow_metadata_inventory_take(target_pos, target_inv_name, i, stack_to_take, placer) > 0 then + target_inv:set_stack(target_inv_name, i, stack) --add to hopper hopper_inv:add_item("main", stack_to_take) if target_def.on_metadata_inventory_take ~= nil and placer ~= nil then - target_def.on_metadata_inventory_take(target_pos, target_inventory_name, i, stack_to_take, placer) + target_def.on_metadata_inventory_take(target_pos, target_inv_name, i, stack_to_take, placer) end break end @@ -112,7 +122,7 @@ hopper.take_item_from = function(hopper_pos, target_pos, target_node, target_inv end -- Used to put items from the hopper inventory into the target block -hopper.send_item_to = function(hopper_pos, target_pos, target_node, target_inventory_name, filtered_items) +hopper.send_item_to = function(hopper_pos, target_pos, target_node, target_inv_info, filtered_items) local hopper_meta = minetest.get_meta(hopper_pos) local target_def = minetest.registered_nodes[target_node.name] if not target_def then @@ -121,7 +131,7 @@ hopper.send_item_to = function(hopper_pos, target_pos, target_node, target_inven local eject_item = hopper.config.eject_button_enabled and hopper_meta:get_string("eject") == "true" and target_def.buildable_to - if not eject_item and not target_inventory_name then + if not eject_item and not target_inv_info then return false end @@ -135,23 +145,35 @@ hopper.send_item_to = function(hopper_pos, target_pos, target_node, target_inven local placer = get_placer(hopper_meta:get_string("placer")) --target inventory - local target_inv = minetest.get_meta(target_pos):get_inventory() + local target_inv_name = target_inv_info["inventory_name"] + local target_get_inv = target_inv_info["get_inventory"] + local target_inv + if target_get_inv then + target_inv = target_get_inv(target_pos) + if not target_inv then + minetest.log("error","No inventory from api get_inventory function: " .. + target_node.name .. " on " .. vector.to_string(target_pos)) + return false + end + else + target_inv = minetest.get_meta(target_pos):get_inventory() + end for i = 1,hopper_inv_size do local stack = hopper_inv:get_stack("main", i) local item = stack:get_name() if item ~= "" and (filtered_items == nil or filtered_items[item]) then - if target_inventory_name then - if target_inv:room_for_item(target_inventory_name, item) then + if target_inv_name then + if target_inv:room_for_item(target_inv_name, item) then local stack_to_put = stack:take_item(1) if target_def.allow_metadata_inventory_put == nil or placer == nil -- backwards compatibility, older versions of this mod didn't record who placed the hopper - or target_def.allow_metadata_inventory_put(target_pos, target_inventory_name, i, stack_to_put, placer) > 0 then + or target_def.allow_metadata_inventory_put(target_pos, target_inv_name, i, stack_to_put, placer) > 0 then hopper_inv:set_stack("main", i, stack) --add to target node - target_inv:add_item(target_inventory_name, stack_to_put) + target_inv:add_item(target_inv_name, stack_to_put) if target_def.on_metadata_inventory_put ~= nil and placer ~= nil then - target_def.on_metadata_inventory_put(target_pos, target_inventory_name, i, stack_to_put, placer) + target_def.on_metadata_inventory_put(target_pos, target_inv_name, i, stack_to_put, placer) end return true end