diff --git a/inventory.lua b/inventory.lua index 88cb6be..86b1536 100644 --- a/inventory.lua +++ b/inventory.lua @@ -4,12 +4,16 @@ local pipeworks_enabled = minetest.get_modpath("pipeworks") ~= nil -- pos: the position of the Digilines chest node. -- action: the action string indicating what happened. -- stack: the ItemStack that the action acted on (optional). +-- from_slot: the slot number that is taken from (optional). +-- to_slot: the slot number that is put into (optional). -- side: which side of the chest the action occurred (optional). -local function send_message(pos, action, stack, side) +local function send_message(pos, action, stack, from_slot, to_slot, side) local channel = minetest.get_meta(pos):get_string("channel") local msg = { action = action, stack = stack and stack:to_table(), + from_slot = from_slot, + to_slot = to_slot, -- Duplicate the vector in case the caller expects it not to change. side = side and vector.new(side) } @@ -113,27 +117,88 @@ minetest.register_node("digilines:chest", { -- Here, direction = direction item is moving, which is into -- side. local side = vector.multiply(direction, -1) - send_message(pos, "toverflow", stack, side) + send_message(pos, "toverflow", stack, nil, nil, side) end return ret end, - insert_object = function(pos, _, stack, direction) + insert_object = function(pos, _, original_stack, direction) -- Here, direction = direction item is moving, which is into side. local side = vector.multiply(direction, -1) - local leftover = minetest.get_meta(pos):get_inventory():add_item("main", stack) - local leftover_count = leftover:get_count() + local inv = minetest.get_meta(pos):get_inventory() + local inv_contents = inv:get_list("main") + local any_put = false + local stack = original_stack + local stack_name = stack:get_name() local stack_count = stack:get_count() - if leftover_count ~= stack_count then - -- Some items were added. Report them. - stack:set_count(stack_count - leftover_count) - send_message(pos, "tput", stack, side) - check_full(pos, stack) + -- Walk the inventory, adding items to existing stacks of the same + -- type. + for i = 1, #inv_contents do + local existing_stack = inv_contents[i] + if not existing_stack:is_empty() and existing_stack:get_name() == stack_name then + local leftover = existing_stack:add_item(stack) + local leftover_count = leftover:get_count() + if leftover_count ~= stack_count then + -- We put some items into the slot. Update the slot in + -- the inventory, tell Digilines listeners about it, + -- and keep looking for the a place to put the + -- leftovers if any. + any_put = true + inv:set_stack("main", i, existing_stack) + local stack_that_was_put + if leftover_count == 0 then + stack_that_was_put = stack + else + stack_that_was_put = ItemStack(stack) + stack_that_was_put:set_count(stack_count - leftover_count) + end + send_message(pos, "tput", stack_that_was_put, nil, i, side) + stack = leftover + stack_count = leftover_count + if stack_count == 0 then + break + end + end + end end - if leftover_count ~= 0 then + if stack_count ~= 0 then + -- Walk the inventory, adding items to empty slots. + for i = 1, #inv_contents do + local existing_stack = inv_contents[i] + if existing_stack:is_empty() then + local leftover = existing_stack:add_item(stack) + local leftover_count = leftover:get_count() + if leftover_count ~= stack_count then + -- We put some items into the slot. Update the slot in + -- the inventory, tell Digilines listeners about it, + -- and keep looking for the a place to put the + -- leftovers if any. + any_put = true + inv:set_stack("main", i, existing_stack) + local stack_that_was_put + if leftover_count == 0 then + stack_that_was_put = stack + else + stack_that_was_put = ItemStack(stack) + stack_that_was_put:set_count(stack_count - leftover_count) + end + send_message(pos, "tput", stack_that_was_put, nil, i, side) + stack = leftover + stack_count = leftover_count + if stack_count == 0 then + break + end + end + end + end + end + if any_put then + check_full(pos, original_stack) + end + if stack_count ~= 0 then -- Some items could not be added and bounced back. Report them. - send_message(pos, "toverflow", leftover, side) + send_message(pos, "toverflow", stack, nil, nil, side) end - return leftover + return stack end, remove_items = function(pos, _, stack, dir, count) -- Here, stack is the ItemStack in our own inventory that is being @@ -147,7 +212,7 @@ minetest.register_node("digilines:chest", { -- index. local taken = stack:take_item(count) minetest.get_meta(pos):get_inventory():set_stack("main", last_inventory_take_index, stack) - send_message(pos, "ttake", taken, dir) + send_message(pos, "ttake", taken, last_inventory_take_index, nil, dir) check_empty(pos) return taken end, @@ -165,6 +230,42 @@ minetest.register_node("digilines:chest", { return stack:get_count() end, on_metadata_inventory_move = function(pos, from_list, from_index, to_list, to_index, count, player) + -- See what would happen if we were to move the items back from in the + -- opposite direction. In the event of a normal move, this must + -- succeed, because a normal move subtracts some items from the from + -- stack and adds them to the to stack; the two stacks naturally must + -- be compatible and so the reverse operation must succeed. However, if + -- the user *swaps* the two stacks instead, then due to issue + -- minetest/minetest#6534, this function is only called once; however, + -- when it is called, the stack that used to be in the to stack has + -- already been moved to the from stack, so we can detect the situation + -- by the fact that the reverse move will fail due to the from stack + -- being incompatible with its former contents. + local inv = minetest.get_meta(pos):get_inventory() + local from_stack = inv:get_stack("main", from_index) + local to_stack = inv:get_stack("main", to_index) + local reverse_move_stack = ItemStack(to_stack) + reverse_move_stack:set_count(count) + local swapped = from_stack:add_item(reverse_move_stack):get_count() == count + if swapped then + local channel = minetest.get_meta(pos):get_string("channel") + to_stack:set_count(count) + local msg = { + action = "uswap", + -- The slot and stack do not match because this function is + -- called after the action has taken place, but the Digilines + -- message is from the perspective of a viewer who hasn’t + -- observed the movement yet. + x_stack = to_stack:to_table(), + x_slot = from_index, + y_stack = from_stack:to_table(), + y_slot = to_index, + } + digilines.receptor_send(pos, digilines.rules.default, channel, msg) + else + to_stack:set_count(count) + send_message(pos, "umove", to_stack, from_index, to_index) + end minetest.log("action", player:get_player_name().." moves stuff in chest at "..minetest.pos_to_string(pos)) end, on_metadata_inventory_put = function(pos, _, index, stack, player) @@ -196,14 +297,14 @@ minetest.register_node("digilines:chest", { -- Digilines network as a utake followed by a uput. local leftovers = old_stack:add_item(stack) if leftovers:get_count() == stack:get_count() then - send_message(pos, "utake", old_stack) + send_message(pos, "utake", old_stack, index) end - send_message(pos, "uput", stack) + send_message(pos, "uput", stack, nil, index) check_full(pos, stack) minetest.log("action", player:get_player_name().." puts stuff into chest at "..minetest.pos_to_string(pos)) end, on_metadata_inventory_take = function(pos, _, index, stack, player) - send_message(pos, "utake", stack) + send_message(pos, "utake", stack, index) check_empty(pos) minetest.log("action", player:get_player_name().." takes stuff from chest at "..minetest.pos_to_string(pos)) end