Add multi-node containers support

+ remove trailing spaces
+ add new api for extra information about containers
This commit is contained in:
Ajrat Makhmutov 2024-07-21 04:16:35 +03:00 committed by SmallJoker
parent d0c8e18c5a
commit df5f8d8379
6 changed files with 184 additions and 104 deletions

@ -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"])

130
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"},

@ -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

@ -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")

@ -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

@ -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