summaryrefslogtreecommitdiff
path: root/digilines/inventory.lua
diff options
context:
space:
mode:
Diffstat (limited to 'digilines/inventory.lua')
-rw-r--r--digilines/inventory.lua283
1 files changed, 223 insertions, 60 deletions
diff --git a/digilines/inventory.lua b/digilines/inventory.lua
index 693f882..86b1536 100644
--- a/digilines/inventory.lua
+++ b/digilines/inventory.lua
@@ -1,33 +1,56 @@
local pipeworks_enabled = minetest.get_modpath("pipeworks") ~= nil
-local function sendMessage(pos, msg, channel)
- if channel == nil then
- channel = minetest.get_meta(pos):get_string("channel")
- end
- digilines.receptor_send(pos,digilines.rules.default,channel,msg)
+-- Sends a message onto the Digilines network.
+-- 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, 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)
+ }
+ digilines.receptor_send(pos, digilines.rules.default, channel, msg)
end
-local function maybeString(stack)
- if type(stack)=='string' then return stack
- elseif type(stack)=='table' then return dump(stack)
- else return stack:to_string()
+-- Checks if the inventory has become empty and, if so, sends an empty message.
+local function check_empty(pos)
+ if minetest.get_meta(pos):get_inventory():is_empty("main") then
+ send_message(pos, "empty")
end
end
-local function can_insert(pos, stack)
- local can = minetest.get_meta(pos):get_inventory():room_for_item("main", stack)
- if can then
- sendMessage(pos,"put "..maybeString(stack))
- else
- -- overflow and lost means that items are gonna be out as entities :/
- sendMessage(pos,"lost "..maybeString(stack))
+-- Checks if the inventory has become full for a particular type of item and,
+-- if so, sends a full message.
+local function check_full(pos, stack)
+ local one_item_stack = ItemStack(stack)
+ one_item_stack:set_count(1)
+ if not minetest.get_meta(pos):get_inventory():room_for_item("main", one_item_stack) then
+ send_message(pos, "full", one_item_stack)
end
- return can
end
local tubeconn = pipeworks_enabled and "^pipeworks_tube_connection_wooden.png" or ""
local tubescan = pipeworks_enabled and function(pos) pipeworks.scan_for_tube_objects(pos) end or nil
+-- A place to remember things from allow_metadata_inventory_put to
+-- on_metadata_inventory_put. This is a hack due to issue
+-- minetest/minetest#6534 that should be removed once that’s fixed.
+local last_inventory_put_index
+local last_inventory_put_stack
+
+-- A place to remember things from allow_metadata_inventory_take to
+-- tube.remove_items. This is a hack due to issue minetest-mods/pipeworks#205
+-- that should be removed once that’s fixed.
+local last_inventory_take_index
+
minetest.register_alias("digilines_inventory:chest", "digilines:chest")
minetest.register_node("digilines:chest", {
description = "Digiline Chest",
@@ -86,63 +109,203 @@ minetest.register_node("digilines:chest", {
return not pipeworks.connects.facingFront(i,param2)
end,
input_inventory = "main",
- can_insert = function(pos, _, stack)
- return can_insert(pos, stack)
+ can_insert = function(pos, _, stack, direction)
+ local ret = minetest.get_meta(pos):get_inventory():room_for_item("main", stack)
+ if not ret then
+ -- The stack cannot be accepted. It will never be passed to
+ -- insert_object, but it should be reported as a toverflow.
+ -- Here, direction = direction item is moving, which is into
+ -- side.
+ local side = vector.multiply(direction, -1)
+ send_message(pos, "toverflow", stack, nil, nil, side)
+ end
+ return ret
end,
- insert_object = function(pos, _, stack)
+ insert_object = function(pos, _, original_stack, direction)
+ -- Here, direction = direction item is moving, which is into side.
+ local side = vector.multiply(direction, -1)
local inv = minetest.get_meta(pos):get_inventory()
- local leftover = inv:add_item("main", stack)
- local count = leftover:get_count()
- if count == 0 then
- local derpstack = stack:get_name()..' 1'
- if not inv:room_for_item("main", derpstack) then
- -- when you can't put a single more of whatever you just put,
- -- you'll get a put for it, then a full
- sendMessage(pos,"full "..maybeString(stack)..' '..tostring(count))
+ 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()
+ -- 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 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
- else
- -- this happens when the chest has received two stacks in a row and
- -- filled up exactly with the first one.
- -- You get a put for the first stack, a put for the second
- -- and then a overflow with the first in stack and the second in leftover
- -- and NO full?
- sendMessage(pos,"overflow "..maybeString(stack)..' '..tostring(count))
end
- return leftover
+ 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", stack, nil, nil, side)
+ end
+ return stack
+ end,
+ remove_items = function(pos, _, stack, dir, count)
+ -- Here, stack is the ItemStack in our own inventory that is being
+ -- pulled from, NOT the stack that is actually pulled out.
+ -- Combining it with count gives the stack that is pulled out.
+ -- Also, note that Pipeworks doesn’t pass the index to this
+ -- function, so we use the one recorded in
+ -- allow_metadata_inventory_take; because we don’t implement
+ -- tube.can_remove, Pipeworks will call
+ -- allow_metadata_inventory_take instead and will pass it the
+ -- 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, last_inventory_take_index, nil, dir)
+ check_empty(pos)
+ return taken
end,
},
- allow_metadata_inventory_put = function(pos, _, _, stack)
- if not can_insert(pos, stack) then
- sendMessage(pos,"uoverflow "..maybeString(stack))
- end
+ allow_metadata_inventory_put = function(pos, _, index, stack)
+ -- Remember what was in the target slot before the put; see
+ -- on_metadata_inventory_put for why we care.
+ last_inventory_put_index = index
+ last_inventory_put_stack = minetest.get_meta(pos):get_inventory():get_stack("main", index)
return stack:get_count()
end,
- on_metadata_inventory_move = function(pos, _, _, _, _, _, player)
- minetest.log("action", player:get_player_name().." moves stuff in chest at "..minetest.pos_to_string(pos))
+ allow_metadata_inventory_take = function(_, _, index, stack)
+ -- Remember the index value; see tube.remove_items for why we care.
+ last_inventory_take_index = index
+ return stack:get_count()
end,
- on_metadata_inventory_put = function(pos, _, _, stack, player)
- local channel = minetest.get_meta(pos):get_string("channel")
- local send = function(msg)
- sendMessage(pos,msg,channel)
+ 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
- -- direction is only for furnaces
- -- as the item has already been put, can_insert should return false if the chest is now full.
- local derpstack = stack:get_name()..' 1'
- if can_insert(pos,derpstack) then
- send("uput "..maybeString(stack))
+ 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)
+ -- Get what was in the target slot before the put; it has disappeared
+ -- by now (been replaced by the result of the put action) but we saved
+ -- it in allow_metadata_inventory_put. This should always work
+ -- (allow_metadata_inventory_put should AFAICT always be called
+ -- immediately before on_metadata_inventory_put), but in case of
+ -- something weird happening, just fall back to using an empty
+ -- ItemStack rather than crashing.
+ local old_stack
+ if last_inventory_put_index == index then
+ old_stack = last_inventory_put_stack
+ last_inventory_put_index = nil
+ last_inventory_put_stack = nil
else
- send("ufull "..maybeString(stack))
+ old_stack = ItemStack(nil)
end
+ -- If the player tries to place a stack into an inventory, there’s
+ -- already a stack there, and the existing stack is either of a
+ -- different item type or full, then obviously the stacks can’t be
+ -- merged; instead the stacks are swapped. This information is not
+ -- reported to mods (Minetest core neither tells us that a particular
+ -- action was a swap, nor tells us a take followed by a put). In core,
+ -- the condition for swapping is that you try to add the new stack to
+ -- the existing stack and the leftovers are as big as the original
+ -- stack to put. Replicate that logic here using the old stack saved in
+ -- allow_metadata_inventory_put. If a swap happened, report it to the
+ -- 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, index)
+ end
+ 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, listname, _, stack, player)
- local meta = minetest.get_meta(pos)
- local channel = meta:get_string("channel")
- local inv = meta:get_inventory()
- if inv:is_empty(listname) then
- sendMessage(pos, "empty", channel)
- end
- sendMessage(pos,"utake "..maybeString(stack))
+ on_metadata_inventory_take = function(pos, _, index, stack, player)
+ 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
})