Inventory: Fix order of callbacks when swapping items

This commit is contained in:
SmallJoker 2022-07-26 20:40:27 +02:00 committed by SmallJoker
parent d5d6e36ae0
commit 4245a7604b
6 changed files with 119 additions and 95 deletions

@ -3640,6 +3640,19 @@ Player Inventory lists
* Is not created automatically, use `InvRef:set_size`
* Is only used to enhance the empty hand's tool capabilities
ItemStack transaction order
---------------------------
This list describes the situation for non-empty ItemStacks in both slots
that cannot be stacked at all, hence triggering an ItemStack swap operation.
Put/take callbacks on empty ItemStack are not executed.
1. The "allow take" and "allow put" callbacks are each run once for the source
and destination inventory.
2. The allowed ItemStacks are exchanged.
3. The "on take" callbacks are run for the source and destination inventories
4. The "on put" callbacks are run for the source and destination inventories
Colors
======

@ -29,12 +29,16 @@ minetest.register_node("chest:chest", {
return inv:is_empty("main")
end,
allow_metadata_inventory_put = function(pos, listname, index, stack, player)
print_to_everything("Chest: ".. player:get_player_name() .. " triggered 'allow put' event for " .. stack:to_string())
return stack:get_count()
print_to_everything("Chest: ".. player:get_player_name() .. " triggered 'allow put' (10) event for " .. stack:to_string())
return 10
end,
allow_metadata_inventory_take = function(pos, listname, index, stack, player)
print_to_everything("Chest: ".. player:get_player_name() .. " triggered 'allow take' event for " .. stack:to_string())
return stack:get_count()
print_to_everything("Chest: ".. player:get_player_name() .. " triggered 'allow take' (20) event for " .. stack:to_string())
return 20
end,
allow_metadata_inventory_move = function(pos, from_list, from_index, to_list, to_index, count, player)
print_to_everything("Chest: ".. player:get_player_name() .. " triggered 'allow move' (30) event")
return 30
end,
on_metadata_inventory_put = function(pos, listname, index, stack, player)
print_to_everything("Chest: ".. player:get_player_name() .. " put " .. stack:to_string())
@ -42,4 +46,7 @@ minetest.register_node("chest:chest", {
on_metadata_inventory_take = function(pos, listname, index, stack, player)
print_to_everything("Chest: ".. player:get_player_name() .. " took " .. stack:to_string())
end,
on_metadata_inventory_move = function(pos, from_list, from_index, to_list, to_index, count, player)
print_to_everything("Chest: ".. player:get_player_name() .. " moved " .. count)
end,
})

@ -758,54 +758,53 @@ void InventoryList::moveItemSomewhere(u32 i, InventoryList *dest, u32 count)
if (!leftover.empty()) {
// Add the remaining part back to the source item
addItem(i, leftover);
ItemStack &source = getItem(i);
source.add(leftover.count); // do NOT use addItem to allow oversized stacks!
}
}
u32 InventoryList::moveItem(u32 i, InventoryList *dest, u32 dest_i,
ItemStack InventoryList::moveItem(u32 i, InventoryList *dest, u32 dest_i,
u32 count, bool swap_if_needed, bool *did_swap)
{
ItemStack moved;
if (this == dest && i == dest_i)
return count;
return moved;
// Take item from source list
ItemStack item1;
if (count == 0)
item1 = changeItem(i, ItemStack());
moved = changeItem(i, ItemStack());
else
item1 = takeItem(i, count);
if (item1.empty())
return 0;
moved = takeItem(i, count);
// Try to add the item to destination list
count = item1.count;
item1 = dest->addItem(dest_i, item1);
ItemStack leftover = dest->addItem(dest_i, moved);
// If something is returned, the item was not fully added
if (!item1.empty()) {
bool nothing_added = (item1.count == count);
if (!leftover.empty()) {
// Keep track of how many we actually moved
moved.remove(leftover.count);
// Add any leftover stack back to the source stack.
item1.add(getItem(i).count); // leftover + source count
changeItem(i, item1); // do NOT use addItem to allow oversized stacks!
leftover.add(getItem(i).count); // leftover + source count
changeItem(i, leftover); // do NOT use addItem to allow oversized stacks!
leftover.clear();
// Swap if no items could be moved
if (nothing_added && swap_if_needed) {
if (moved.empty() && swap_if_needed) {
// Tell that we swapped
if (did_swap != NULL) {
*did_swap = true;
}
// Take item from source list
item1 = changeItem(i, ItemStack());
moved = changeItem(i, ItemStack());
// Adding was not possible, swap the items.
ItemStack item2 = dest->changeItem(dest_i, item1);
ItemStack item2 = dest->changeItem(dest_i, moved);
// Put item from destination list to the source list
changeItem(i, item2);
item1.clear(); // no leftover
}
}
return (count - item1.count);
return moved;
}
void InventoryList::checkResizeLock()

@ -286,8 +286,8 @@ public:
// Move an item to a different list (or a different stack in the same list)
// count is the maximum number of items to move (0 for everything)
// returns number of moved items
u32 moveItem(u32 i, InventoryList *dest, u32 dest_i,
// returns the moved stack
ItemStack moveItem(u32 i, InventoryList *dest, u32 dest_i,
u32 count = 0, bool swap_if_needed = true, bool *did_swap = NULL);
// like moveItem, but without a fixed destination index

@ -162,7 +162,20 @@ void IMoveAction::swapDirections()
std::swap(from_i, to_i);
}
void IMoveAction::onPutAndOnTake(const ItemStack &src_item, ServerActiveObject *player) const
void IMoveAction::onTake(const ItemStack &src_item, ServerActiveObject *player) const
{
ServerScripting *sa = PLAYER_TO_SA(player);
if (from_inv.type == InventoryLocation::DETACHED)
sa->detached_inventory_OnTake(*this, src_item, player);
else if (from_inv.type == InventoryLocation::NODEMETA)
sa->nodemeta_inventory_OnTake(*this, src_item, player);
else if (from_inv.type == InventoryLocation::PLAYER)
sa->player_inventory_OnTake(*this, src_item, player);
else
assert(false);
}
void IMoveAction::onPut(const ItemStack &src_item, ServerActiveObject *player) const
{
ServerScripting *sa = PLAYER_TO_SA(player);
if (to_inv.type == InventoryLocation::DETACHED)
@ -173,15 +186,6 @@ void IMoveAction::onPutAndOnTake(const ItemStack &src_item, ServerActiveObject *
sa->player_inventory_OnPut(*this, src_item, player);
else
assert(false);
if (from_inv.type == InventoryLocation::DETACHED)
sa->detached_inventory_OnTake(*this, src_item, player);
else if (from_inv.type == InventoryLocation::NODEMETA)
sa->nodemeta_inventory_OnTake(*this, src_item, player);
else if (from_inv.type == InventoryLocation::PLAYER)
sa->player_inventory_OnTake(*this, src_item, player);
else
assert(false);
}
void IMoveAction::onMove(int count, ServerActiveObject *player) const
@ -244,6 +248,8 @@ int IMoveAction::allowMove(int try_take_count, ServerActiveObject *player) const
void IMoveAction::apply(InventoryManager *mgr, ServerActiveObject *player, IGameDef *gamedef)
{
/// Necessary for executing Lua callbacks which may manipulate the inventory,
/// hence invalidate pointers needed by IMoveAction::apply
auto get_borrow_checked_invlist = [mgr](const InventoryLocation &invloc,
const std::string &listname) -> InventoryList::ResizeLocked
{
@ -375,6 +381,8 @@ void IMoveAction::apply(InventoryManager *mgr, ServerActiveObject *player, IGame
bool allow_swap = !list_to->itemFits(to_i, src_item, &restitem)
&& restitem.count == src_item.count
&& !caused_by_move_somewhere;
// move_count : How many items that were moved at the end
// count : Total items "in the queue" of being moved. Do not touch.
move_count = src_item.count - restitem.count;
// Shift-click: Cannot fill this stack, proceed with next slot
@ -383,10 +391,11 @@ void IMoveAction::apply(InventoryManager *mgr, ServerActiveObject *player, IGame
}
if (allow_swap) {
// Swap will affect the entire stack if it can performed.
// Swap will affect the entire stack (= count) if it can performed.
src_item = list_from->getItem(from_i);
count = src_item.count;
move_count = src_item.count;
}
src_item.count = move_count; // Temporary movement stack
if (from_inv == to_inv) {
// Move action within the same inventory
@ -409,16 +418,9 @@ void IMoveAction::apply(InventoryManager *mgr, ServerActiveObject *player, IGame
src_can_take_count = dst_can_put_count = 0;
} else {
// Take from one inventory, put into another
int src_item_count = src_item.count;
if (caused_by_move_somewhere)
// When moving somewhere: temporarily use the actual movable stack
// size to ensure correct callback execution.
src_item.count = move_count;
dst_can_put_count = allowPut(src_item, player);
src_can_take_count = allowTake(src_item, player);
if (caused_by_move_somewhere)
// Reset source item count
src_item.count = src_item_count;
bool swap_expected = allow_swap;
allow_swap = allow_swap
&& (src_can_take_count == -1 || src_can_take_count >= src_item.count)
@ -440,25 +442,20 @@ void IMoveAction::apply(InventoryManager *mgr, ServerActiveObject *player, IGame
src_can_take_count = dst_can_put_count = 0;
}
int old_count = count;
int old_move_count = move_count;
/* Modify count according to collected data */
count = src_item.count;
if (src_can_take_count != -1 && count > src_can_take_count)
count = src_can_take_count;
if (dst_can_put_count != -1 && count > dst_can_put_count)
count = dst_can_put_count;
// Apply limits given by allow_* callbacks
if (src_can_take_count != -1)
move_count = (u32)std::min<s32>(src_can_take_count, move_count);
if (dst_can_put_count != -1)
move_count = (u32)std::min<s32>(dst_can_put_count, move_count);
/* Limit according to source item count */
if (count > list_from->getItem(from_i).count)
count = list_from->getItem(from_i).count;
// allow_* callbacks should not modify the stack - but if they do - handle that.
if (move_count > list_from->getItem(from_i).count)
move_count = list_from->getItem(from_i).count;
/* If no items will be moved, don't go further */
if (count == 0) {
if (caused_by_move_somewhere)
// Set move count to zero, as no items have been moved
move_count = 0;
if (move_count == 0) {
// Undo client prediction. See 'clientApply'
if (from_inv.type == InventoryLocation::PLAYER)
list_from->setModified();
@ -467,7 +464,7 @@ void IMoveAction::apply(InventoryManager *mgr, ServerActiveObject *player, IGame
list_to->setModified();
infostream<<"IMoveAction::apply(): move was completely disallowed:"
<<" count="<<old_count
<<" move_count="<<old_move_count
<<" from inv=\""<<from_inv.dump()<<"\""
<<" list=\""<<from_list<<"\""
<<" i="<<from_i
@ -479,8 +476,7 @@ void IMoveAction::apply(InventoryManager *mgr, ServerActiveObject *player, IGame
return;
}
src_item = list_from->getItem(from_i);
src_item.count = count;
// Backups stacks for infinite sources
ItemStack from_stack_was = list_from->getItem(from_i);
ItemStack to_stack_was = list_to->getItem(to_i);
@ -491,13 +487,13 @@ void IMoveAction::apply(InventoryManager *mgr, ServerActiveObject *player, IGame
same as source), nothing happens
*/
bool did_swap = false;
move_count = list_from->moveItem(from_i,
list_to.get(), to_i, count, allow_swap, &did_swap);
if (caused_by_move_somewhere)
count = old_count;
src_item = list_from->moveItem(from_i,
list_to.get(), to_i, move_count, allow_swap, &did_swap);
move_count = src_item.count;
assert(allow_swap == did_swap);
// If source is infinite, reset it's stack
// If source is infinite, reset its stack
if (src_can_take_count == -1) {
// For the caused_by_move_somewhere == true case we didn't force-put the item,
// which guarantees there is no leftover, and code below would duplicate the
@ -517,23 +513,23 @@ void IMoveAction::apply(InventoryManager *mgr, ServerActiveObject *player, IGame
}
}
if (move_count > 0 || did_swap) {
list_from->deleteItem(from_i);
list_from->addItem(from_i, from_stack_was);
list_from->changeItem(from_i, from_stack_was);
}
}
// If destination is infinite, reset it's stack and take count from source
// If destination is infinite, reset its stack and take count from source
if (dst_can_put_count == -1) {
list_to->deleteItem(to_i);
list_to->addItem(to_i, to_stack_was);
list_from->deleteItem(from_i);
list_from->addItem(from_i, from_stack_was);
list_from->takeItem(from_i, count);
list_to->changeItem(to_i, to_stack_was);
if (did_swap) {
// Undo swap result: set the expected stack + size
list_from->changeItem(from_i, from_stack_was);
list_from->takeItem(from_i, move_count);
}
}
infostream << "IMoveAction::apply(): moved"
<< " msom=" << move_somewhere
<< " caused=" << caused_by_move_somewhere
<< " count=" << count
<< " move_count=" << move_count
<< " from inv=\"" << from_inv.dump() << "\""
<< " list=\"" << from_list << "\""
<< " i=" << from_i
@ -589,9 +585,9 @@ void IMoveAction::apply(InventoryManager *mgr, ServerActiveObject *player, IGame
// Source = destination => move
if (from_inv == to_inv) {
onMove(count, player);
onMove(move_count, player);
if (did_swap) {
// Item is now placed in source list
// Already swapped. The other stack is now placed in "from" list
list_from = get_borrow_checked_invlist(from_inv, from_list);
if (list_from) {
src_item = list_from->getItem(from_i);
@ -603,26 +599,34 @@ void IMoveAction::apply(InventoryManager *mgr, ServerActiveObject *player, IGame
}
mgr->setInventoryModified(from_inv);
} else {
int src_item_count = src_item.count;
if (caused_by_move_somewhere)
// When moving somewhere: temporarily use the actual movable stack
// size to ensure correct callback execution.
src_item.count = move_count;
onPutAndOnTake(src_item, player);
if (caused_by_move_somewhere)
// Reset source item count
src_item.count = src_item_count;
ItemStack swap_item;
if (did_swap) {
// Item is now placed in source list
// Already swapped. The other stack is now placed in "from" list
list_from = get_borrow_checked_invlist(from_inv, from_list);
if (list_from) {
src_item = list_from->getItem(from_i);
swap_item = list_from->getItem(from_i);
list_from.reset();
swapDirections();
onPutAndOnTake(src_item, player);
swapDirections();
}
}
// 1. Take the ItemStack (visually: freely detached)
onTake(src_item, player);
if (!swap_item.empty() && get_borrow_checked_invlist(to_inv, to_list)) {
swapDirections();
onTake(swap_item, player);
swapDirections();
}
// 2. Put the ItemStack
if (get_borrow_checked_invlist(to_inv, to_list))
onPut(src_item, player);
if (!swap_item.empty() && get_borrow_checked_invlist(to_inv, to_list)) {
swapDirections();
onPut(swap_item, player);
swapDirections();
}
mgr->setInventoryModified(to_inv);
mgr->setInventoryModified(from_inv);
}

@ -191,7 +191,8 @@ struct IMoveAction : public InventoryAction, public MoveAction
void swapDirections();
void onPutAndOnTake(const ItemStack &src_item, ServerActiveObject *player) const;
void onTake(const ItemStack &src_item, ServerActiveObject *player) const;
void onPut(const ItemStack &src_item, ServerActiveObject *player) const;
void onMove(int count, ServerActiveObject *player) const;