]> git.lizzy.rs Git - dragonfireclient.git/blobdiff - src/inventorymanager.cpp
Remove obsolete eye_height related workaround
[dragonfireclient.git] / src / inventorymanager.cpp
index 6c87255f3a2289ed6d4c0d75117ebf7c3541f970..ecdb56a972240b09e5245d007c7613b0ded38437 100644 (file)
@@ -22,7 +22,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include "log.h"
 #include "serverenvironment.h"
 #include "scripting_server.h"
-#include "serverobject.h"
+#include "server/serveractiveobject.h"
 #include "settings.h"
 #include "craftdef.h"
 #include "rollback_interface.h"
@@ -93,7 +93,7 @@ void InventoryLocation::deSerialize(std::istream &is)
        }
 }
 
-void InventoryLocation::deSerialize(std::string s)
+void InventoryLocation::deSerialize(const std::string &s)
 {
        std::istringstream is(s, std::ios::binary);
        deSerialize(is);
@@ -154,6 +154,93 @@ IMoveAction::IMoveAction(std::istream &is, bool somewhere) :
        }
 }
 
+void IMoveAction::swapDirections()
+{
+       std::swap(from_inv, to_inv);
+       std::swap(from_list, to_list);
+       std::swap(from_i, to_i);
+}
+
+void IMoveAction::onPutAndOnTake(const ItemStack &src_item, ServerActiveObject *player) const
+{
+       ServerScripting *sa = PLAYER_TO_SA(player);
+       if (to_inv.type == InventoryLocation::DETACHED)
+               sa->detached_inventory_OnPut(*this, src_item, player);
+       else if (to_inv.type == InventoryLocation::NODEMETA)
+               sa->nodemeta_inventory_OnPut(*this, src_item, player);
+       else if (to_inv.type == InventoryLocation::PLAYER)
+               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
+{
+       ServerScripting *sa = PLAYER_TO_SA(player);
+       if (from_inv.type == InventoryLocation::DETACHED)
+               sa->detached_inventory_OnMove(*this, count, player);
+       else if (from_inv.type == InventoryLocation::NODEMETA)
+               sa->nodemeta_inventory_OnMove(*this, count, player);
+       else if (from_inv.type == InventoryLocation::PLAYER)
+               sa->player_inventory_OnMove(*this, count, player);
+       else
+               assert(false);
+}
+
+int IMoveAction::allowPut(const ItemStack &dst_item, ServerActiveObject *player) const
+{
+       ServerScripting *sa = PLAYER_TO_SA(player);
+       int dst_can_put_count = 0xffff;
+       if (to_inv.type == InventoryLocation::DETACHED)
+               dst_can_put_count = sa->detached_inventory_AllowPut(*this, dst_item, player);
+       else if (to_inv.type == InventoryLocation::NODEMETA)
+               dst_can_put_count = sa->nodemeta_inventory_AllowPut(*this, dst_item, player);
+       else if (to_inv.type == InventoryLocation::PLAYER)
+               dst_can_put_count = sa->player_inventory_AllowPut(*this, dst_item, player);
+       else
+               assert(false);
+       return dst_can_put_count;
+}
+
+int IMoveAction::allowTake(const ItemStack &src_item, ServerActiveObject *player) const
+{
+       ServerScripting *sa = PLAYER_TO_SA(player);
+       int src_can_take_count = 0xffff;
+       if (from_inv.type == InventoryLocation::DETACHED)
+               src_can_take_count = sa->detached_inventory_AllowTake(*this, src_item, player);
+       else if (from_inv.type == InventoryLocation::NODEMETA)
+               src_can_take_count = sa->nodemeta_inventory_AllowTake(*this, src_item, player);
+       else if (from_inv.type == InventoryLocation::PLAYER)
+               src_can_take_count = sa->player_inventory_AllowTake(*this, src_item, player);
+       else
+               assert(false);
+       return src_can_take_count;
+}
+
+int IMoveAction::allowMove(int try_take_count, ServerActiveObject *player) const
+{
+       ServerScripting *sa = PLAYER_TO_SA(player);
+       int src_can_take_count = 0xffff;
+       if (from_inv.type == InventoryLocation::DETACHED)
+               src_can_take_count = sa->detached_inventory_AllowMove(*this, try_take_count, player);
+       else if (from_inv.type == InventoryLocation::NODEMETA)
+               src_can_take_count = sa->nodemeta_inventory_AllowMove(*this, try_take_count, player);
+       else if (from_inv.type == InventoryLocation::PLAYER)
+               src_can_take_count = sa->player_inventory_AllowMove(*this, try_take_count, player);
+       else
+               assert(false);
+       return src_can_take_count;
+}
+
 void IMoveAction::apply(InventoryManager *mgr, ServerActiveObject *player, IGameDef *gamedef)
 {
        Inventory *inv_from = mgr->getInventory(from_inv);
@@ -186,7 +273,7 @@ void IMoveAction::apply(InventoryManager *mgr, ServerActiveObject *player, IGame
        }
        if (!list_to) {
                infostream << "IMoveAction::apply(): FAIL: destination list not found: "
-                       << "to_inv=\""<<to_inv.dump() << "\""
+                       << "to_inv=\"" << to_inv.dump() << "\""
                        << ", to_list=\"" << to_list << "\"" << std::endl;
                return;
        }
@@ -214,6 +301,7 @@ void IMoveAction::apply(InventoryManager *mgr, ServerActiveObject *player, IGame
                        if (!list_to->getItem(dest_i).empty()) {
                                to_i = dest_i;
                                apply(mgr, player, gamedef);
+                               assert(move_count <= count);
                                count -= move_count;
                        }
                }
@@ -234,12 +322,20 @@ void IMoveAction::apply(InventoryManager *mgr, ServerActiveObject *player, IGame
                return;
        }
 
-       if ((u16)to_i > list_to->getSize()) {
+       if (from_i < 0 || list_from->getSize() <= (u32) from_i) {
+               infostream << "IMoveAction::apply(): FAIL: source index out of bounds: "
+                       << "size of from_list=\"" << list_from->getSize() << "\""
+                       << ", from_index=\"" << from_i << "\"" << std::endl;
+               return;
+       }
+
+       if (to_i < 0 || list_to->getSize() <= (u32) to_i) {
                infostream << "IMoveAction::apply(): FAIL: destination index out of bounds: "
-                       << "to_i=" << to_i
-                       << ", size=" << list_to->getSize() << std::endl;
+                       << "size of to_list=\"" << list_to->getSize() << "\""
+                       << ", to_index=\"" << to_i << "\"" << std::endl;
                return;
        }
+
        /*
                Do not handle rollback if both inventories are that of the same player
        */
@@ -251,103 +347,111 @@ void IMoveAction::apply(InventoryManager *mgr, ServerActiveObject *player, IGame
                Collect information of endpoints
        */
 
-       int try_take_count = count;
-       if (try_take_count == 0)
-               try_take_count = list_from->getItem(from_i).count;
+       ItemStack src_item = list_from->getItem(from_i);
+       if (count > 0 && count < src_item.count)
+               src_item.count = count;
+       if (src_item.empty())
+               return;
 
        int src_can_take_count = 0xffff;
        int dst_can_put_count = 0xffff;
 
-       /* Query detached inventories */
+       // this is needed for swapping items inside one inventory to work
+       ItemStack restitem;
+       bool allow_swap = !list_to->itemFits(to_i, src_item, &restitem)
+               && restitem.count == src_item.count
+               && !caused_by_move_somewhere;
+       move_count = src_item.count - restitem.count;
 
-       // Move occurs in the same detached inventory
-       if (from_inv.type == InventoryLocation::DETACHED &&
-                       from_inv == to_inv) {
-               src_can_take_count = PLAYER_TO_SA(player)->detached_inventory_AllowMove(
-                       *this, try_take_count, player);
-               dst_can_put_count = src_can_take_count;
-       } else {
-               // Destination is detached
-               if (to_inv.type == InventoryLocation::DETACHED) {
-                       ItemStack src_item = list_from->getItem(from_i);
-                       src_item.count = try_take_count;
-                       dst_can_put_count = PLAYER_TO_SA(player)->detached_inventory_AllowPut(
-                               *this, src_item, player);
-               }
-               // Source is detached
-               if (from_inv.type == InventoryLocation::DETACHED) {
-                       ItemStack src_item = list_from->getItem(from_i);
-                       src_item.count = try_take_count;
-                       src_can_take_count = PLAYER_TO_SA(player)->detached_inventory_AllowTake(
-                               *this, src_item, player);
-               }
+       // Shift-click: Cannot fill this stack, proceed with next slot
+       if (caused_by_move_somewhere && move_count == 0) {
+               return;
        }
 
-       /* Query node metadata inventories */
-
-       // Both endpoints are nodemeta
-       // Move occurs in the same nodemeta inventory
-       if (from_inv.type == InventoryLocation::NODEMETA &&
-                       from_inv == to_inv) {
-               src_can_take_count = PLAYER_TO_SA(player)->nodemeta_inventory_AllowMove(
-                       *this, try_take_count, player);
-               dst_can_put_count = src_can_take_count;
-       } else {
-               // Destination is nodemeta
-               if (to_inv.type == InventoryLocation::NODEMETA) {
-                       ItemStack src_item = list_from->getItem(from_i);
-                       src_item.count = try_take_count;
-                       dst_can_put_count = PLAYER_TO_SA(player)->nodemeta_inventory_AllowPut(
-                               *this, src_item, player);
+       if (allow_swap) {
+               // Swap will affect the entire stack if it can performed.
+               src_item = list_from->getItem(from_i);
+               count = src_item.count;
+       }
+
+       if (from_inv == to_inv) {
+               // Move action within the same inventory
+               src_can_take_count = allowMove(src_item.count, player);
+
+               bool swap_expected = allow_swap;
+               allow_swap = allow_swap
+                       && (src_can_take_count == -1 || src_can_take_count >= src_item.count);
+               if (allow_swap) {
+                       int try_put_count = list_to->getItem(to_i).count;
+                       swapDirections();
+                       dst_can_put_count = allowMove(try_put_count, player);
+                       allow_swap = allow_swap
+                               && (dst_can_put_count == -1 || dst_can_put_count >= try_put_count);
+                       swapDirections();
+               } else {
+                       dst_can_put_count = src_can_take_count;
                }
-               // Source is nodemeta
-               if (from_inv.type == InventoryLocation::NODEMETA) {
-                       ItemStack src_item = list_from->getItem(from_i);
-                       src_item.count = try_take_count;
-                       src_can_take_count = PLAYER_TO_SA(player)->nodemeta_inventory_AllowTake(
-                               *this, src_item, player);
-               }
-       }
-
-       // Query player inventories
-
-       // Move occurs in the same player inventory
-       if (from_inv.type == InventoryLocation::PLAYER &&
-                       from_inv == to_inv) {
-               src_can_take_count = PLAYER_TO_SA(player)->player_inventory_AllowMove(
-                       *this, try_take_count, player);
-               dst_can_put_count = src_can_take_count;
+               if (swap_expected != allow_swap)
+                       src_can_take_count = dst_can_put_count = 0;
        } else {
-               // Destination is a player
-               if (to_inv.type == InventoryLocation::PLAYER) {
-                       ItemStack src_item = list_from->getItem(from_i);
-                       src_item.count = try_take_count;
-                       dst_can_put_count = PLAYER_TO_SA(player)->player_inventory_AllowPut(
-                               *this, src_item, player);
-               }
-               // Source is a player
-               if (from_inv.type == InventoryLocation::PLAYER) {
-                       ItemStack src_item = list_from->getItem(from_i);
-                       src_item.count = try_take_count;
-                       src_can_take_count = PLAYER_TO_SA(player)->player_inventory_AllowTake(
-                               *this, src_item, player);
+               // 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)
+                       && (dst_can_put_count == -1 || dst_can_put_count >= src_item.count);
+               // A swap is expected, which means that we have to
+               // run the "allow" callbacks a second time with swapped inventories
+               if (allow_swap) {
+                       ItemStack dst_item = list_to->getItem(to_i);
+                       swapDirections();
+
+                       int src_can_take = allowPut(dst_item, player);
+                       int dst_can_put = allowTake(dst_item, player);
+                       allow_swap = allow_swap
+                               && (src_can_take == -1 || src_can_take >= dst_item.count)
+                               && (dst_can_put == -1 || dst_can_put >= dst_item.count);
+                       swapDirections();
                }
+               if (swap_expected != allow_swap)
+                       src_can_take_count = dst_can_put_count = 0;
        }
 
        int old_count = count;
 
        /* Modify count according to collected data */
-       count = try_take_count;
+       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;
+
        /* Limit according to source item count */
        if (count > list_from->getItem(from_i).count)
                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;
+
+               // Undo client prediction. See 'clientApply'
+               if (from_inv.type == InventoryLocation::PLAYER)
+                       list_from->setModified();
+
+               if (to_inv.type == InventoryLocation::PLAYER)
+                       list_to->setModified();
+
                infostream<<"IMoveAction::apply(): move was completely disallowed:"
                                <<" count="<<old_count
                                <<" from inv=\""<<from_inv.dump()<<"\""
@@ -357,10 +461,11 @@ void IMoveAction::apply(InventoryManager *mgr, ServerActiveObject *player, IGame
                                <<" list=\""<<to_list<<"\""
                                <<" i="<<to_i
                                <<std::endl;
+
                return;
        }
 
-       ItemStack src_item = list_from->getItem(from_i);
+       src_item = list_from->getItem(from_i);
        src_item.count = count;
        ItemStack from_stack_was = list_from->getItem(from_i);
        ItemStack to_stack_was = list_to->getItem(to_i);
@@ -373,7 +478,10 @@ void IMoveAction::apply(InventoryManager *mgr, ServerActiveObject *player, IGame
        */
        bool did_swap = false;
        move_count = list_from->moveItem(from_i,
-               list_to, to_i, count, !caused_by_move_somewhere, &did_swap);
+               list_to, to_i, count, allow_swap, &did_swap);
+       if (caused_by_move_somewhere)
+               count = old_count;
+       assert(allow_swap == did_swap);
 
        // If source is infinite, reset it's stack
        if (src_can_take_count == -1) {
@@ -421,8 +529,7 @@ void IMoveAction::apply(InventoryManager *mgr, ServerActiveObject *player, IGame
                        << std::endl;
 
        // If we are inside the move somewhere loop, we don't need to report
-       // anything if nothing happened (perhaps we don't need to report
-       // anything for caused_by_move_somewhere == true, but this way its safer)
+       // anything if nothing happened
        if (caused_by_move_somewhere && move_count == 0)
                return;
 
@@ -464,69 +571,37 @@ void IMoveAction::apply(InventoryManager *mgr, ServerActiveObject *player, IGame
                Report move to endpoints
        */
 
-       /* Detached inventories */
-
-       // Both endpoints are same detached
-       if (from_inv.type == InventoryLocation::DETACHED &&
-                       from_inv == to_inv) {
-               PLAYER_TO_SA(player)->detached_inventory_OnMove(
-                               *this, count, player);
-       } else {
-               // Destination is detached
-               if (to_inv.type == InventoryLocation::DETACHED) {
-                       PLAYER_TO_SA(player)->detached_inventory_OnPut(
-                               *this, src_item, player);
-               }
-               // Source is detached
-               if (from_inv.type == InventoryLocation::DETACHED) {
-                       PLAYER_TO_SA(player)->detached_inventory_OnTake(
-                               *this, src_item, player);
-               }
-       }
-
-       /* Node metadata inventories */
-
-       // Both endpoints are same nodemeta
-       if (from_inv.type == InventoryLocation::NODEMETA &&
-                       from_inv == to_inv) {
-               PLAYER_TO_SA(player)->nodemeta_inventory_OnMove(
-                       *this, count, player);
-       } else {
-               // Destination is nodemeta
-               if (to_inv.type == InventoryLocation::NODEMETA) {
-                       PLAYER_TO_SA(player)->nodemeta_inventory_OnPut(
-                               *this, src_item, player);
-               }
-               // Source is nodemeta
-               if (from_inv.type == InventoryLocation::NODEMETA) {
-                       PLAYER_TO_SA(player)->nodemeta_inventory_OnTake(
-                               *this, src_item, player);
+       // Source = destination => move
+       if (from_inv == to_inv) {
+               onMove(count, player);
+               if (did_swap) {
+                       // Item is now placed in source list
+                       src_item = list_from->getItem(from_i);
+                       swapDirections();
+                       onMove(src_item.count, player);
+                       swapDirections();
                }
-       }
-
-       // Player inventories
-
-       // Both endpoints are same player inventory
-       if (from_inv.type == InventoryLocation::PLAYER &&
-                       from_inv == to_inv) {
-               PLAYER_TO_SA(player)->player_inventory_OnMove(
-                       *this, count, player);
+               mgr->setInventoryModified(from_inv);
        } else {
-               // Destination is player inventory
-               if (to_inv.type == InventoryLocation::PLAYER) {
-                       PLAYER_TO_SA(player)->player_inventory_OnPut(
-                               *this, src_item, player);
-               }
-               // Source is player inventory
-               if (from_inv.type == InventoryLocation::PLAYER) {
-                       PLAYER_TO_SA(player)->player_inventory_OnTake(
-                               *this, src_item, player);
+               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;
+               if (did_swap) {
+                       // Item is now placed in source list
+                       src_item = list_from->getItem(from_i);
+                       swapDirections();
+                       onPutAndOnTake(src_item, player);
+                       swapDirections();
                }
+               mgr->setInventoryModified(to_inv);
+               mgr->setInventoryModified(from_inv);
        }
-
-       mgr->setInventoryModified(from_inv, false);
-       if (inv_from != inv_to)
-               mgr->setInventoryModified(to_inv, false);
 }
 
 void IMoveAction::clientApply(InventoryManager *mgr, IGameDef *gamedef)
@@ -646,8 +721,6 @@ void IDropAction::apply(InventoryManager *mgr, ServerActiveObject *player, IGame
        if (src_can_take_count != -1 && src_can_take_count < take_count)
                take_count = src_can_take_count;
 
-       int actually_dropped_count = 0;
-
        // Update item due executed callbacks
        src_item = list_from->getItem(from_i);
 
@@ -656,10 +729,14 @@ void IDropAction::apply(InventoryManager *mgr, ServerActiveObject *player, IGame
        item1.count = take_count;
        if(PLAYER_TO_SA(player)->item_OnDrop(item1, player,
                                player->getBasePosition())) {
-               actually_dropped_count = take_count - item1.count;
+               int actually_dropped_count = take_count - item1.count;
 
                if (actually_dropped_count == 0) {
                        infostream<<"Actually dropped no items"<<std::endl;
+
+                       // Revert client prediction. See 'clientApply'
+                       if (from_inv.type == InventoryLocation::PLAYER)
+                               list_from->setModified();
                        return;
                }
 
@@ -670,9 +747,10 @@ void IDropAction::apply(InventoryManager *mgr, ServerActiveObject *player, IGame
 
                        if (item2.count != actually_dropped_count)
                                errorstream<<"Could not take dropped count of items"<<std::endl;
-
-                       mgr->setInventoryModified(from_inv, false);
                }
+
+               src_item.count = actually_dropped_count;
+               mgr->setInventoryModified(from_inv);
        }
 
        infostream<<"IDropAction::apply(): dropped "
@@ -681,7 +759,6 @@ void IDropAction::apply(InventoryManager *mgr, ServerActiveObject *player, IGame
                        <<" i="<<from_i
                        <<std::endl;
 
-       src_item.count = actually_dropped_count;
 
        /*
                Report drop to endpoints
@@ -846,13 +923,13 @@ void ICraftAction::apply(InventoryManager *mgr,
                        count_remaining--;
 
                // Get next crafting result
-               found = getCraftingResult(inv_craft, crafted, temp, false, gamedef);
+               getCraftingResult(inv_craft, crafted, temp, false, gamedef);
                PLAYER_TO_SA(player)->item_CraftPredict(crafted, player, list_craft, craft_inv);
                found = !crafted.empty();
        }
 
        // Put the replacements in the inventory or drop them on the floor, if
-       // the invenotry is full
+       // the inventory is full
        for (auto &output_replacement : output_replacements) {
                if (list_main)
                        output_replacement = list_main->addItem(output_replacement);