/* 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 "log.h" #include "serverenvironment.h" #include "scripting_server.h" #include "serverobject.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:"<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=\""<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); 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 ((u16)to_i > list_to->getSize()) { infostream << "IMoveAction::apply(): FAIL: destination index out of bounds: " << "to_i=" << to_i << ", size=" << list_to->getSize() << std::endl; return; } /* Do not handle rollback if both inventories are that of the same player */ bool ignore_rollback = ( from_inv.type == InventoryLocation::PLAYER && to_inv.type == InventoryLocation::PLAYER && from_inv.name == to_inv.name); /* Collect information of endpoints */ int try_take_count = count; if(try_take_count == 0) try_take_count = list_from->getItem(from_i).count; int src_can_take_count = 0xffff; int dst_can_put_count = 0xffff; /* Query detached inventories */ // Move occurs in the same detached inventory if(from_inv.type == InventoryLocation::DETACHED && to_inv.type == InventoryLocation::DETACHED && from_inv.name == to_inv.name) { src_can_take_count = PLAYER_TO_SA(player)->detached_inventory_AllowMove( from_inv.name, from_list, from_i, to_list, to_i, 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( to_inv.name, to_list, to_i, 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( from_inv.name, from_list, from_i, src_item, player); } } /* Query node metadata inventories */ // Both endpoints are nodemeta // Move occurs in the same nodemeta inventory if(from_inv.type == InventoryLocation::NODEMETA && to_inv.type == InventoryLocation::NODEMETA && from_inv.p == to_inv.p) { src_can_take_count = PLAYER_TO_SA(player)->nodemeta_inventory_AllowMove( from_inv.p, from_list, from_i, to_list, to_i, 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( to_inv.p, to_list, to_i, src_item, player); } // 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( from_inv.p, from_list, from_i, src_item, player); } } int old_count = count; /* Modify count according to collected data */ count = try_take_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) { 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=\""<takeItem(from_i, actually_dropped_count); if(item2.count != actually_dropped_count) errorstream<<"Could not take dropped count of items"<setInventoryModified(from_inv, false); } } 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 (std::vector::iterator it = temp.begin(); it != temp.end(); ++it) { for (std::vector::iterator jt = output_replacements.begin(); jt != output_replacements.end(); ++jt) { if (it->name == jt->name) { *it = jt->addItem(*it, itemdef); if (it->empty()) continue; } } output_replacements.push_back(*it); } actionstream << player->getDescription() << " crafts " << crafted.getItemString() << std::endl; // Decrement counter if (count_remaining == 1) break; else if (count_remaining > 1) count_remaining--; // Get next crafting result found = 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 for (std::vector::iterator it = output_replacements.begin(); it != output_replacements.end(); ++it) { if (list_main) *it = list_main->addItem(*it); if (it->empty()) continue; u16 count = it->count; do { PLAYER_TO_SA(player)->item_OnDrop(*it, player, player->getBasePosition() + v3f(0,1,0)); if (count >= it->count) { errorstream << "Couldn't drop replacement stack " << it->getItemString() << " because drop loop didn't " "decrease count." << std::endl; break; } } while (!it->empty()); } infostream<<"ICraftAction::apply(): crafted " <<" craft_inv=\""< &output_replacements, bool decrementInput, IGameDef *gamedef) { DSTACK(FUNCTION_NAME); 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; igetSize(); 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; igetSize(); i++) { clist->changeItem(i, ci.items[i]); } } return found; }