/* Minetest Copyright (C) 2010-2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "inventorymanager.h" #include "debug.h" #include "log.h" #include "serverenvironment.h" #include "scripting_server.h" #include "server/serveractiveobject.h" #include "settings.h" #include "craftdef.h" #include "rollback_interface.h" #include "util/strfnd.h" #include "util/basic_macros.h" #define PLAYER_TO_SA(p) p->getEnv()->getScriptIface() /* InventoryLocation */ std::string InventoryLocation::dump() const { std::ostringstream os(std::ios::binary); serialize(os); return os.str(); } void InventoryLocation::serialize(std::ostream &os) const { switch (type) { case InventoryLocation::UNDEFINED: os<<"undefined"; break; case InventoryLocation::CURRENT_PLAYER: os<<"current_player"; break; case InventoryLocation::PLAYER: os<<"player:"<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); Inventory *inv_to = mgr->getInventory(to_inv); if (!inv_from) { infostream << "IMoveAction::apply(): FAIL: source inventory not found: " << "from_inv=\""<getList(from_list); InventoryList *list_to = inv_to->getList(to_list); /* If a list doesn't exist or the source item doesn't exist */ if (!list_from) { infostream << "IMoveAction::apply(): FAIL: source list not found: " << "from_inv=\"" << from_inv.dump() << "\"" << ", from_list=\"" << from_list << "\"" << std::endl; return; } if (!list_to) { infostream << "IMoveAction::apply(): FAIL: destination list not found: " << "to_inv=\"" << to_inv.dump() << "\"" << ", to_list=\"" << to_list << "\"" << std::endl; return; } if (move_somewhere) { s16 old_to_i = to_i; u16 old_count = count; caused_by_move_somewhere = true; move_somewhere = false; infostream << "IMoveAction::apply(): moving item somewhere" << " msom=" << move_somewhere << " count=" << count << " from inv=\"" << from_inv.dump() << "\"" << " list=\"" << from_list << "\"" << " i=" << from_i << " to inv=\"" << to_inv.dump() << "\"" << " list=\"" << to_list << "\"" << std::endl; // Try to add the item to destination list s16 dest_size = list_to->getSize(); // First try all the non-empty slots for (s16 dest_i = 0; dest_i < dest_size && count > 0; dest_i++) { if (!list_to->getItem(dest_i).empty()) { to_i = dest_i; apply(mgr, player, gamedef); assert(move_count <= count); count -= move_count; } } // Then try all the empty ones for (s16 dest_i = 0; dest_i < dest_size && count > 0; dest_i++) { if (list_to->getItem(dest_i).empty()) { to_i = dest_i; apply(mgr, player, gamedef); count -= move_count; } } to_i = old_to_i; count = old_count; caused_by_move_somewhere = false; move_somewhere = true; return; } 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: " << "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 */ bool ignore_rollback = ( from_inv.type == InventoryLocation::PLAYER && from_inv == to_inv); /* Collect information of endpoints */ 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; // 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; // Shift-click: Cannot fill this stack, proceed with next slot if (caused_by_move_somewhere && move_count == 0) { return; } 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; } if (swap_expected != allow_swap) 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) && (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 = 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="<getList(from_list); /* If a list doesn't exist or the source item doesn't exist */ if (!list_from) { infostream<<"IDropAction::apply(): FAIL: source list not found: " <<"from_inv=\""<getItem(from_i).empty()) { infostream<<"IDropAction::apply(): FAIL: source item not found: " <<"from_inv=\""<setModified(); return; } // If source isn't infinite if (src_can_take_count != -1) { // Take item from source list ItemStack item2 = list_from->takeItem(from_i, actually_dropped_count); if (item2.count != actually_dropped_count) errorstream<<"Could not take dropped count of items"<setInventoryModified(from_inv); } infostream<<"IDropAction::apply(): dropped " <<" from inv=\""<getList("craft"); InventoryList *list_craftresult = inv_craft->getList("craftresult"); InventoryList *list_main = inv_craft->getList("main"); /* If a list doesn't exist or the source item doesn't exist */ if (!list_craft) { infostream << "ICraftAction::apply(): FAIL: craft list not found: " << "craft_inv=\"" << craft_inv.dump() << "\"" << std::endl; return; } if (!list_craftresult) { infostream << "ICraftAction::apply(): FAIL: craftresult list not found: " << "craft_inv=\"" << craft_inv.dump() << "\"" << std::endl; return; } if (list_craftresult->getSize() < 1) { infostream << "ICraftAction::apply(): FAIL: craftresult list too short: " << "craft_inv=\"" << craft_inv.dump() << "\"" << std::endl; return; } ItemStack crafted; ItemStack craftresultitem; int count_remaining = count; std::vector output_replacements; getCraftingResult(inv_craft, crafted, output_replacements, false, gamedef); PLAYER_TO_SA(player)->item_CraftPredict(crafted, player, list_craft, craft_inv); bool found = !crafted.empty(); while (found && list_craftresult->itemFits(0, crafted)) { InventoryList saved_craft_list = *list_craft; std::vector temp; // Decrement input and add crafting output getCraftingResult(inv_craft, crafted, temp, true, gamedef); PLAYER_TO_SA(player)->item_OnCraft(crafted, player, &saved_craft_list, craft_inv); list_craftresult->addItem(0, crafted); mgr->setInventoryModified(craft_inv); // Add the new replacements to the list IItemDefManager *itemdef = gamedef->getItemDefManager(); for (auto &itemstack : temp) { for (auto &output_replacement : output_replacements) { if (itemstack.name == output_replacement.name) { itemstack = output_replacement.addItem(itemstack, itemdef); if (itemstack.empty()) continue; } } output_replacements.push_back(itemstack); } actionstream << player->getDescription() << " crafts " << crafted.getItemString() << std::endl; // Decrement counter if (count_remaining == 1) break; if (count_remaining > 1) count_remaining--; // Get next crafting result 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 inventory is full for (auto &output_replacement : output_replacements) { if (list_main) output_replacement = list_main->addItem(output_replacement); if (output_replacement.empty()) continue; u16 count = output_replacement.count; do { PLAYER_TO_SA(player)->item_OnDrop(output_replacement, player, player->getBasePosition()); if (count >= output_replacement.count) { errorstream << "Couldn't drop replacement stack " << output_replacement.getItemString() << " because drop loop didn't " "decrease count." << std::endl; break; } } while (!output_replacement.empty()); } infostream<<"ICraftAction::apply(): crafted " <<" craft_inv=\""< &output_replacements, bool decrementInput, IGameDef *gamedef) { result.clear(); // Get the InventoryList in which we will operate InventoryList *clist = inv->getList("craft"); if (!clist) return false; // Mangle crafting grid to an another format CraftInput ci; ci.method = CRAFT_METHOD_NORMAL; ci.width = clist->getWidth() ? clist->getWidth() : 3; for (u16 i=0; i < clist->getSize(); i++) ci.items.push_back(clist->getItem(i)); // Find out what is crafted and add it to result item slot CraftOutput co; bool found = gamedef->getCraftDefManager()->getCraftResult( ci, co, output_replacements, decrementInput, gamedef); if (found) result.deSerialize(co.item, gamedef->getItemDefManager()); if (found && decrementInput) { // CraftInput has been changed, apply changes in clist for (u16 i=0; i < clist->getSize(); i++) { clist->changeItem(i, ci.items[i]); } } return found; }