1
0
Fork 0
mirror of https://github.com/luanti-org/luanti.git synced 2025-06-27 16:36:03 +00:00

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

View file

@ -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);
}