X-Git-Url: https://git.librecmc.org/?a=blobdiff_plain;f=src%2Finventory.cpp;h=77ecf5876bf36500206ba8b7eb5697c0e7a2c033;hb=a462181e5fe631b816cb67a4e2cb8a870ed71840;hp=a913430e6a060d2ebf028e7d7286405dac6d07e2;hpb=e494b5d422609b9ed9f79cc39b401f40b6a3ec9a;p=oweals%2Fminetest.git diff --git a/src/inventory.cpp b/src/inventory.cpp index a913430e6..77ecf5876 100644 --- a/src/inventory.cpp +++ b/src/inventory.cpp @@ -1,585 +1,444 @@ /* -Minetest-c55 -Copyright (C) 2010-2011 celeron55, Perttu Ahola +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 General Public License as published by -the Free Software Foundation; either version 2 of the License, or +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 General Public License for more details. +GNU Lesser General Public License for more details. -You should have received a copy of the GNU General Public License along +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 "inventory.h" #include "serialization.h" -#include "utility.h" #include "debug.h" +#include #include -#include "main.h" // For tsrc, g_toolmanager -#include "serverobject.h" -#include "content_mapnode.h" -#include "content_sao.h" -#include "environment.h" -#include "mapblock.h" -#include "player.h" #include "log.h" -#include "nodedef.h" -#include "tooldef.h" -#include "craftitemdef.h" -#include "gamedef.h" -#include "scriptapi.h" -#include "strfnd.h" +#include "itemdef.h" +#include "util/strfnd.h" +#include "content_mapnode.h" // For loading legacy MaterialItems #include "nameidmapping.h" // For loading legacy MaterialItems -#include "serverremoteplayer.h" +#include "util/serialize.h" +#include "util/string.h" /* - InventoryItem + ItemStack */ -InventoryItem::InventoryItem(IGameDef *gamedef, u16 count): - m_gamedef(gamedef), - m_count(count) +static content_t content_translate_from_19_to_internal(content_t c_from) { - assert(m_gamedef); + for (const auto &tt : trans_table_19) { + if(tt[1] == c_from) { + return tt[0]; + } + } + return c_from; } -InventoryItem::~InventoryItem() +ItemStack::ItemStack(const std::string &name_, u16 count_, + u16 wear_, IItemDefManager *itemdef) : + name(itemdef->getAlias(name_)), + count(count_), + wear(wear_) { + if (name.empty() || count == 0) + clear(); + else if (itemdef->get(name).type == ITEM_TOOL) + count = 1; } -content_t content_translate_from_19_to_internal(content_t c_from) +void ItemStack::serialize(std::ostream &os) const { - for(u32 i=0; i= 2) + os<<" "<= 3) + os<<" "<= 4) { + os << " "; + metadata.serialize(os); } - return c_from; } -InventoryItem* InventoryItem::deSerialize(std::istream &is, IGameDef *gamedef) +void ItemStack::deSerialize(std::istream &is, IItemDefManager *itemdef) { - DSTACK(__FUNCTION_NAME); + clear(); - //is.imbue(std::locale("C")); // Read name - std::string name; - std::getline(is, name, ' '); - + name = deSerializeJsonStringIfNeeded(is); + + // Skip space + std::string tmp; + std::getline(is, tmp, ' '); + if(!tmp.empty()) + throw SerializationError("Unexpected text after item name"); + if(name == "MaterialItem") { - // u16 reads directly as a number (u8 doesn't) + // Obsoleted on 2011-07-30 + u16 material; is>>material; - u16 count; - is>>count; + u16 materialcount; + is>>materialcount; // Convert old materials if(material <= 0xff) material = content_translate_from_19_to_internal(material); - if(material > MAX_CONTENT) + if(material > 0xfff) throw SerializationError("Too large material number"); - return new MaterialItem(gamedef, material, count); + // Convert old id to name + NameIdMapping legacy_nimap; + content_mapnode_get_name_id_mapping(&legacy_nimap); + legacy_nimap.getName(material, name); + if(name.empty()) + name = "unknown_block"; + if (itemdef) + name = itemdef->getAlias(name); + count = materialcount; } else if(name == "MaterialItem2") { + // Obsoleted on 2011-11-16 + u16 material; is>>material; - u16 count; - is>>count; - if(material > MAX_CONTENT) + u16 materialcount; + is>>materialcount; + if(material > 0xfff) throw SerializationError("Too large material number"); - return new MaterialItem(gamedef, material, count); + // Convert old id to name + NameIdMapping legacy_nimap; + content_mapnode_get_name_id_mapping(&legacy_nimap); + legacy_nimap.getName(material, name); + if(name.empty()) + name = "unknown_block"; + if (itemdef) + name = itemdef->getAlias(name); + count = materialcount; } - else if(name == "node" || name == "NodeItem" || name == "MaterialItem3") + else if(name == "node" || name == "NodeItem" || name == "MaterialItem3" + || name == "craft" || name == "CraftItem") { + // Obsoleted on 2012-01-07 + std::string all; std::getline(is, all, '\n'); - std::string nodename; // First attempt to read inside "" Strfnd fnd(all); fnd.next("\""); // If didn't skip to end, we have ""s - if(!fnd.atend()){ - nodename = fnd.next("\""); + if(!fnd.at_end()){ + name = fnd.next("\""); } else { // No luck, just read a word then fnd.start(all); - nodename = fnd.next(" "); + name = fnd.next(" "); } fnd.skip_over(" "); - u16 count = stoi(trim(fnd.next(""))); + if (itemdef) + name = itemdef->getAlias(name); + count = stoi(trim(fnd.next(""))); if(count == 0) count = 1; - return new MaterialItem(gamedef, nodename, count); } else if(name == "MBOItem") { - std::string inventorystring; - std::getline(is, inventorystring, '|'); + // Obsoleted on 2011-10-14 throw SerializationError("MBOItem not supported anymore"); } - else if(name == "craft" || name == "CraftItem") - { - std::string all; - std::getline(is, all, '\n'); - std::string subname; - // First attempt to read inside "" - Strfnd fnd(all); - fnd.next("\""); - // If didn't skip to end, we have ""s - if(!fnd.atend()){ - subname = fnd.next("\""); - } else { // No luck, just read a word then - fnd.start(all); - subname = fnd.next(" "); - } - // Then read count - fnd.skip_over(" "); - u16 count = stoi(trim(fnd.next(""))); - if(count == 0) - count = 1; - return new CraftItem(gamedef, subname, count); - } else if(name == "tool" || name == "ToolItem") { + // Obsoleted on 2012-01-07 + std::string all; std::getline(is, all, '\n'); - std::string toolname; // First attempt to read inside "" Strfnd fnd(all); fnd.next("\""); // If didn't skip to end, we have ""s - if(!fnd.atend()){ - toolname = fnd.next("\""); + if(!fnd.at_end()){ + name = fnd.next("\""); } else { // No luck, just read a word then fnd.start(all); - toolname = fnd.next(" "); + name = fnd.next(" "); } + count = 1; // Then read wear fnd.skip_over(" "); - u16 wear = stoi(trim(fnd.next(""))); - return new ToolItem(gamedef, toolname, wear); + if (itemdef) + name = itemdef->getAlias(name); + wear = stoi(trim(fnd.next(""))); } else { - infostream<<"Unknown InventoryItem name=\""<getMap().emergeBlock(blockpos, false); - if(block==NULL) - { - infostream<<"InventoryItem::dropOrPlace(): FAIL: block not found: " - < dropcount) - count = dropcount; - if(count < 0 || count > getCount()) - count = getCount(); - if(count > 0) - { - /* - Create an ItemSAO - */ - pos.Y -= BS*0.25; // let it drop a bit - // Randomize a bit - //pos.X += BS*0.2*(float)myrand_range(-1000,1000)/1000.0; - //pos.Z += BS*0.2*(float)myrand_range(-1000,1000)/1000.0; - // Create object - ServerActiveObject *obj = new ItemSAO(env, pos, getItemString()); - // Add the object to the environment - env->addActiveObject(obj); - infostream<<"Dropped item"<getNodeDefManager()->get(m_nodename).inventory_texture; -} -#endif - -bool MaterialItem::isCookable() const -{ - INodeDefManager *ndef = m_gamedef->ndef(); - const ContentFeatures &f = ndef->get(m_nodename); - return (f.cookresult_item != ""); -} - -InventoryItem *MaterialItem::createCookResult() const -{ - INodeDefManager *ndef = m_gamedef->ndef(); - const ContentFeatures &f = ndef->get(m_nodename); - std::istringstream is(f.cookresult_item, std::ios::binary); - return InventoryItem::deSerialize(is, m_gamedef); -} - -float MaterialItem::getCookTime() const -{ - INodeDefManager *ndef = m_gamedef->ndef(); - const ContentFeatures &f = ndef->get(m_nodename); - return f.furnace_cooktime; -} - -float MaterialItem::getBurnTime() const -{ - INodeDefManager *ndef = m_gamedef->ndef(); - const ContentFeatures &f = ndef->get(m_nodename); - return f.furnace_burntime; -} - -content_t MaterialItem::getMaterial() const -{ - INodeDefManager *ndef = m_gamedef->ndef(); - content_t id = CONTENT_IGNORE; - ndef->getId(m_nodename, id); - return id; -} + do // This loop is just to allow "break;" + { + // The real thing -/* - ToolItem -*/ + // Apply item aliases + if (itemdef) + name = itemdef->getAlias(name); -std::string ToolItem::getImageBasename() const -{ - return m_gamedef->getToolDefManager()->getImagename(m_toolname); -} + // Read the count + std::string count_str; + std::getline(is, count_str, ' '); + if (count_str.empty()) { + count = 1; + break; + } -#ifndef SERVER -video::ITexture * ToolItem::getImage() const -{ - ITextureSource *tsrc = m_gamedef->tsrc(); + count = stoi(count_str); - std::string basename = getImageBasename(); - - /* - Calculate a progress value with sane amount of - maximum states - */ - u32 maxprogress = 30; - u32 toolprogress = (65535-m_wear)/(65535/maxprogress); - - float value_f = (float)toolprogress / (float)maxprogress; - std::ostringstream os; - os<getTextureRaw(os.str()); -} + // Read the wear + std::string wear_str; + std::getline(is, wear_str, ' '); + if(wear_str.empty()) + break; -video::ITexture * ToolItem::getImageRaw() const -{ - ITextureSource *tsrc = m_gamedef->tsrc(); - - return tsrc->getTextureRaw(getImageBasename()); -} -#endif + wear = stoi(wear_str); -bool ToolItem::isKnown() const -{ - IToolDefManager *tdef = m_gamedef->tdef(); - const ToolDefinition *def = tdef->getToolDefinition(m_toolname); - return (def != NULL); -} + // Read metadata + metadata.deSerialize(is); -/* - CraftItem -*/ + // In case fields are added after metadata, skip space here: + //std::getline(is, tmp, ' '); + //if(!tmp.empty()) + // throw SerializationError("Unexpected text after metadata"); -#ifndef SERVER -video::ITexture * CraftItem::getImage() const -{ - ICraftItemDefManager *cidef = m_gamedef->cidef(); - ITextureSource *tsrc = m_gamedef->tsrc(); - std::string imagename = cidef->getImagename(m_subname); - return tsrc->getTextureRaw(imagename); -} -#endif - -bool CraftItem::isKnown() const -{ - ICraftItemDefManager *cidef = m_gamedef->cidef(); - const CraftItemDefinition *def = cidef->getCraftItemDefinition(m_subname); - return (def != NULL); -} - -u16 CraftItem::getStackMax() const -{ - ICraftItemDefManager *cidef = m_gamedef->cidef(); - const CraftItemDefinition *def = cidef->getCraftItemDefinition(m_subname); - if(def == NULL) - return InventoryItem::getStackMax(); - return def->stack_max; -} + } while(false); + } -bool CraftItem::isUsable() const -{ - ICraftItemDefManager *cidef = m_gamedef->cidef(); - const CraftItemDefinition *def = cidef->getCraftItemDefinition(m_subname); - return def != NULL && def->usable; + if (name.empty() || count == 0) + clear(); + else if (itemdef && itemdef->get(name).type == ITEM_TOOL) + count = 1; } -bool CraftItem::isCookable() const +void ItemStack::deSerialize(const std::string &str, IItemDefManager *itemdef) { - ICraftItemDefManager *cidef = m_gamedef->cidef(); - const CraftItemDefinition *def = cidef->getCraftItemDefinition(m_subname); - return def != NULL && def->cookresult_item != ""; + std::istringstream is(str, std::ios::binary); + deSerialize(is, itemdef); } -InventoryItem *CraftItem::createCookResult() const +std::string ItemStack::getItemString() const { - ICraftItemDefManager *cidef = m_gamedef->cidef(); - const CraftItemDefinition *def = cidef->getCraftItemDefinition(m_subname); - if(def == NULL) - return InventoryItem::createCookResult(); - std::istringstream is(def->cookresult_item, std::ios::binary); - return InventoryItem::deSerialize(is, m_gamedef); + std::ostringstream os(std::ios::binary); + serialize(os); + return os.str(); } -float CraftItem::getCookTime() const +std::string ItemStack::getDescription(IItemDefManager *itemdef) const { - ICraftItemDefManager *cidef = m_gamedef->cidef(); - const CraftItemDefinition *def = cidef->getCraftItemDefinition(m_subname); - if (def == NULL) - return InventoryItem::getCookTime(); - return def->furnace_cooktime; + std::string desc = metadata.getString("description"); + if (desc.empty()) + desc = getDefinition(itemdef).description; + return desc.empty() ? name : desc; } -float CraftItem::getBurnTime() const -{ - ICraftItemDefManager *cidef = m_gamedef->cidef(); - const CraftItemDefinition *def = cidef->getCraftItemDefinition(m_subname); - if (def == NULL) - return InventoryItem::getBurnTime(); - return def->furnace_burntime; -} -s16 CraftItem::getDropCount() const +ItemStack ItemStack::addItem(ItemStack newitem, IItemDefManager *itemdef) { - // Special cases - ICraftItemDefManager *cidef = m_gamedef->cidef(); - const CraftItemDefinition *def = cidef->getCraftItemDefinition(m_subname); - if(def != NULL && def->dropcount >= 0) - return def->dropcount; - // Default - return InventoryItem::getDropCount(); -} + // If the item is empty or the position invalid, bail out + if(newitem.empty()) + { + // nothing can be added trivially + } + // If this is an empty item, it's an easy job. + else if(empty()) + { + *this = newitem; + newitem.clear(); + } + // If item name or metadata differs, bail out + else if (name != newitem.name + || metadata != newitem.metadata) + { + // cannot be added + } + // If the item fits fully, add counter and delete it + else if(newitem.count <= freeSpace(itemdef)) + { + add(newitem.count); + newitem.clear(); + } + // Else the item does not fit fully. Add all that fits and return + // the rest. + else + { + u16 freespace = freeSpace(itemdef); + add(freespace); + newitem.remove(freespace); + } -bool CraftItem::areLiquidsPointable() const -{ - ICraftItemDefManager *cidef = m_gamedef->cidef(); - const CraftItemDefinition *def = cidef->getCraftItemDefinition(m_subname); - return def != NULL && def->liquids_pointable; + return newitem; } -bool CraftItem::dropOrPlace(ServerEnvironment *env, - ServerActiveObject *dropper, - v3f pos, bool place, s16 count) +bool ItemStack::itemFits(ItemStack newitem, + ItemStack *restitem, + IItemDefManager *itemdef) const { - if(count == 0) - return false; - - bool callback_exists = false; - bool result = false; - if(place) + // If the item is empty or the position invalid, bail out + if(newitem.empty()) { - result = scriptapi_craftitem_on_place_on_ground( - env->getLua(), - m_subname.c_str(), dropper, pos, - callback_exists); + // nothing can be added trivially } - - // note: on_drop is fallback for on_place_on_ground - - if(!callback_exists) + // If this is an empty item, it's an easy job. + else if(empty()) { - result = scriptapi_craftitem_on_drop( - env->getLua(), - m_subname.c_str(), dropper, pos, - callback_exists); + newitem.clear(); } - - if(callback_exists) + // If item name or metadata differs, bail out + else if (name != newitem.name + || metadata != newitem.metadata) + { + // cannot be added + } + // If the item fits fully, delete it + else if(newitem.count <= freeSpace(itemdef)) { - // If the callback returned true, drop one item - if(result) - setCount(getCount() - 1); - return getCount() < 1; + newitem.clear(); } + // Else the item does not fit fully. Return the rest. else { - // If neither on_place_on_ground (if place==true) - // nor on_drop exists, call the base implementation - return InventoryItem::dropOrPlace(env, dropper, pos, place, count); + u16 freespace = freeSpace(itemdef); + newitem.remove(freespace); } + + if(restitem) + *restitem = newitem; + + return newitem.empty(); } -bool CraftItem::use(ServerEnvironment *env, - ServerActiveObject *user, - const PointedThing& pointed) +ItemStack ItemStack::takeItem(u32 takecount) { - bool callback_exists = false; - bool result = false; - - result = scriptapi_craftitem_on_use( - env->getLua(), - m_subname.c_str(), user, pointed, - callback_exists); + if(takecount == 0 || count == 0) + return ItemStack(); - if(callback_exists) + ItemStack result = *this; + if(takecount >= count) { - // If the callback returned true, drop one item - if(result) - setCount(getCount() - 1); - return getCount() < 1; + // Take all + clear(); } else { - // If neither on_place_on_ground (if place==true) - // nor on_drop exists, call the base implementation - return InventoryItem::use(env, user, pointed); + // Take part + remove(takecount); + result.count = takecount; } + return result; +} + +ItemStack ItemStack::peekItem(u32 peekcount) const +{ + if(peekcount == 0 || count == 0) + return ItemStack(); + + ItemStack result = *this; + if(peekcount < count) + result.count = peekcount; + return result; } /* Inventory */ -InventoryList::InventoryList(std::string name, u32 size) +InventoryList::InventoryList(const std::string &name, u32 size, IItemDefManager *itemdef): + m_name(name), + m_size(size), + m_itemdef(itemdef) { - m_name = name; - m_size = size; clearItems(); - //m_dirty = false; } -InventoryList::~InventoryList() +void InventoryList::clearItems() { - for(u32 i=0; iserialize(os); - } - else - { + + os<<"Width "<> m_width; + if (iss.fail()) + throw SerializationError("incorrect width property"); } else if(name == "Item") { if(item_i > getSize() - 1) throw SerializationError("too many items"); - InventoryItem *item = InventoryItem::deSerialize(iss, gamedef); + ItemStack item; + item.deSerialize(iss, m_itemdef); m_items[item_i++] = item; } else if(name == "Empty") { if(item_i > getSize() - 1) throw SerializationError("too many items"); - m_items[item_i++] = NULL; - } - else - { - throw SerializationError("Unknown inventory identifier"); + m_items[item_i++].clear(); + } else if (name == "Keep") { + ++item_i; // Unmodified item } } + + // Contents given to deSerialize() were not terminated properly: throw error. + + std::ostringstream ss; + ss << "Malformatted inventory list. list=" + << m_name << ", read " << item_i << " of " << getSize() + << " ItemStacks." << std::endl; + throw SerializationError(ss.str()); } InventoryList::InventoryList(const InventoryList &other) { - /* - Do this so that the items get cloned. Otherwise the pointers - in the array will just get copied. - */ *this = other; } InventoryList & InventoryList::operator = (const InventoryList &other) { - m_name = other.m_name; + m_items = other.m_items; m_size = other.m_size; - clearItems(); - for(u32 i=0; iclone(); - } - } + m_width = other.m_width; + m_name = other.m_name; + m_itemdef = other.m_itemdef; //setDirty(true); return *this; } +bool InventoryList::operator == (const InventoryList &other) const +{ + if(m_size != other.m_size) + return false; + if(m_width != other.m_width) + return false; + if(m_name != other.m_name) + return false; + for (u32 i = 0; i < m_items.size(); i++) + if (m_items[i] != other.m_items[i]) + return false; + + return true; +} + const std::string &InventoryList::getName() const { return m_name; } -u32 InventoryList::getSize() +u32 InventoryList::getSize() const { return m_items.size(); } -u32 InventoryList::getUsedSlots() +u32 InventoryList::getWidth() const +{ + return m_width; +} + +u32 InventoryList::getUsedSlots() const { u32 num = 0; - for(u32 i=0; i= m_items.size()) - return NULL; + assert(i < m_size); // Pre-condition return m_items[i]; } -InventoryItem * InventoryList::getItem(u32 i) +ItemStack& InventoryList::getItem(u32 i) { - if(i >= m_items.size()) - return NULL; + assert(i < m_size); // Pre-condition return m_items[i]; } -InventoryItem * InventoryList::changeItem(u32 i, InventoryItem *newitem) +ItemStack InventoryList::changeItem(u32 i, const ItemStack &newitem) { if(i >= m_items.size()) return newitem; - InventoryItem *olditem = m_items[i]; + ItemStack olditem = m_items[i]; m_items[i] = newitem; - //setDirty(true); + setModified(); return olditem; } void InventoryList::deleteItem(u32 i) { - assert(i < m_items.size()); - InventoryItem *item = changeItem(i, NULL); - if(item) - delete item; + assert(i < m_items.size()); // Pre-condition + m_items[i].clear(); + setModified(); } -InventoryItem * InventoryList::addItem(InventoryItem *newitem) +ItemStack InventoryList::addItem(const ItemStack &newitem_) { - if(newitem == NULL) - return NULL; - + ItemStack newitem = newitem_; + + if(newitem.empty()) + return newitem; + /* First try to find if it could be added to some existing items */ for(u32 i=0; i= m_items.size()) return newitem; - - //setDirty(true); - - // If it is an empty position, it's an easy job. - InventoryItem *to_item = getItem(i); - if(to_item == NULL) - { - m_items[i] = newitem; - return NULL; - } - - // If not addable, return the item - if(newitem->addableTo(to_item) == false) - return newitem; - - // If the item fits fully in the slot, add counter and delete it - if(newitem->getCount() <= to_item->freeSpace()) - { - to_item->add(newitem->getCount()); - delete newitem; - return NULL; - } - // Else the item does not fit fully. Add all that fits and return - // the rest. - else - { - u16 freespace = to_item->freeSpace(); - to_item->add(freespace); - newitem->remove(freespace); - return newitem; - } + + ItemStack leftover = m_items[i].addItem(newitem, m_itemdef); + if (leftover != newitem) + setModified(); + return leftover; } -bool InventoryList::itemFits(const u32 i, const InventoryItem *newitem) +bool InventoryList::itemFits(const u32 i, const ItemStack &newitem, + ItemStack *restitem) const { - // If it is an empty position, it's an easy job. - const InventoryItem *to_item = getItem(i); - if(to_item == NULL) + if(i >= m_items.size()) { - return true; - } - - // If not addable, fail - if(newitem->addableTo(to_item) == false) + if(restitem) + *restitem = newitem; return false; - - // If the item fits fully in the slot, pass - if(newitem->getCount() <= to_item->freeSpace()) - { - return true; } - return false; + return m_items[i].itemFits(newitem, restitem, m_itemdef); } -bool InventoryList::roomForItem(const InventoryItem *item) +bool InventoryList::roomForItem(const ItemStack &item_) const { + ItemStack item = item_; + ItemStack leftover; for(u32 i=0; icreateCookResult(); - if(!cook) - return false; - bool room = roomForItem(cook); - delete cook; - return room; + u32 count = item.count; + if (count == 0) + return true; + + for (auto i = m_items.rbegin(); i != m_items.rend(); ++i) { + if (count == 0) + break; + if (i->name == item.name && (!match_meta || (i->metadata == item.metadata))) { + if (i->count >= count) + return true; + + count -= i->count; + } + } + return false; } -InventoryItem * InventoryList::takeItem(u32 i, u32 count) +ItemStack InventoryList::removeItem(const ItemStack &item) { - if(count == 0) - return NULL; - - //setDirty(true); + ItemStack removed; + for (auto i = m_items.rbegin(); i != m_items.rend(); ++i) { + if (i->name == item.name) { + u32 still_to_remove = item.count - removed.count; + ItemStack leftover = removed.addItem(i->takeItem(still_to_remove), + m_itemdef); + // Allow oversized stacks + removed.count += leftover.count; - InventoryItem *item = getItem(i); - // If it is an empty position, return NULL - if(item == NULL) - return NULL; - - if(count >= item->getCount()) - { - // Get the item by swapping NULL to its place - return changeItem(i, NULL); - } - else - { - InventoryItem *item2 = item->clone(); - item->remove(count); - item2->setCount(count); - return item2; + if (removed.count == item.count) + break; + } } - - return false; + if (!removed.empty()) + setModified(); + return removed; } -void InventoryList::decrementMaterials(u16 count) +ItemStack InventoryList::takeItem(u32 i, u32 takecount) { - for(u32 i=0; i= m_items.size()) + return ItemStack(); + + ItemStack taken = m_items[i].takeItem(takecount); + if (!taken.empty()) + setModified(); + return taken; +} + +void InventoryList::moveItemSomewhere(u32 i, InventoryList *dest, u32 count) +{ + // Take item from source list + ItemStack item1; + if (count == 0) + item1 = changeItem(i, ItemStack()); + else + item1 = takeItem(i, count); + + if (item1.empty()) + return; + + ItemStack leftover; + leftover = dest->addItem(item1); + + if (!leftover.empty()) { + // Add the remaining part back to the source item + addItem(i, leftover); } } -void InventoryList::print(std::ostream &o) +u32 InventoryList::moveItem(u32 i, InventoryList *dest, u32 dest_i, + u32 count, bool swap_if_needed, bool *did_swap) { - o<<"InventoryList:"<addItem(dest_i, item1); + + // If something is returned, the item was not fully added + if(!item1.empty()) { - InventoryItem *item = m_items[i]; - if(item != NULL) - { - o<serialize(o); - o<<"\n"; + // If olditem is returned, nothing was added. + bool nothing_added = (item1.count == oldcount); + + // If something else is returned, part of the item was left unadded. + // Add the other part back to the source item + addItem(i, item1); + + // If olditem is returned, nothing was added. + // Swap the items + if (nothing_added && swap_if_needed) { + // Tell that we swapped + if (did_swap != NULL) { + *did_swap = true; + } + // Take item from source list + item1 = changeItem(i, ItemStack()); + // Adding was not possible, swap the items. + ItemStack item2 = dest->changeItem(dest_i, item1); + // Put item from destination list to the source list + changeItem(i, item2); } } + return (oldcount - item1.count); } /* @@ -887,15 +785,17 @@ Inventory::~Inventory() void Inventory::clear() { - for(u32 i=0; igetName()<<" "<getSize()<<"\n"; - list->serialize(os); + if(*m_lists[i] != *other.m_lists[i]) + return false; + } + return true; +} + +void Inventory::serialize(std::ostream &os, bool incremental) const +{ + //std::cout << "Serialize " << (int)incremental << ", n=" << m_lists.size() << std::endl; + for (const InventoryList *list : m_lists) { + if (!incremental || list->checkModified()) { + os << "List " << list->getName() << " " << list->getSize() << "\n"; + list->serialize(os, incremental); + } else { + os << "KeepList " << list->getName() << "\n"; + } } os<<"EndInventory\n"; } -void Inventory::deSerialize(std::istream &is, IGameDef *gamedef) +void Inventory::deSerialize(std::istream &is) { - clear(); + std::vector new_lists; + new_lists.reserve(m_lists.size()); - for(;;) - { + while (is.good()) { std::string line; std::getline(is, line, '\n'); @@ -939,52 +860,89 @@ void Inventory::deSerialize(std::istream &is, IGameDef *gamedef) std::string name; std::getline(iss, name, ' '); - if(name == "EndInventory") - { - break; + if (name == "EndInventory" || name == "end") { + // Remove all lists that were not sent + for (auto &list : m_lists) { + if (std::find(new_lists.begin(), new_lists.end(), list) != new_lists.end()) + continue; + + delete list; + list = nullptr; + setModified(); + } + m_lists.erase(std::remove(m_lists.begin(), m_lists.end(), + nullptr), m_lists.end()); + return; } - // This is a temporary backwards compatibility fix - else if(name == "end") - { - break; - } - else if(name == "List") - { + + if (name == "List") { std::string listname; u32 listsize; std::getline(iss, listname, ' '); iss>>listsize; - InventoryList *list = new InventoryList(listname, listsize); - list->deSerialize(is, gamedef); + InventoryList *list = getList(listname); + bool create_new = !list; + if (create_new) + list = new InventoryList(listname, listsize, m_itemdef); + else + list->setSize(listsize); + list->deSerialize(is); - m_lists.push_back(list); - } - else - { - throw SerializationError("Unknown inventory identifier"); + new_lists.push_back(list); + if (create_new) + m_lists.push_back(list); + + } else if (name == "KeepList") { + // Incrementally sent list + std::string listname; + std::getline(iss, listname, ' '); + + InventoryList *list = getList(listname); + if (list) { + new_lists.push_back(list); + } else { + errorstream << "Inventory::deSerialize(): Tried to keep list '" << + listname << "' which is non-existent." << std::endl; + } } + // Any additional fields will throw errors when received by a client + // older than PROTOCOL_VERSION 38 } + + // Contents given to deSerialize() were not terminated properly: throw error. + + std::ostringstream ss; + ss << "Malformatted inventory (damaged?). " + << m_lists.size() << " lists read." << std::endl; + throw SerializationError(ss.str()); } InventoryList * Inventory::addList(const std::string &name, u32 size) { + setModified(); s32 i = getListIndex(name); if(i != -1) { if(m_lists[i]->getSize() != size) { delete m_lists[i]; - m_lists[i] = new InventoryList(name, size); + m_lists[i] = new InventoryList(name, size, m_itemdef); + m_lists[i]->setModified(); } return m_lists[i]; } - else - { - m_lists.push_back(new InventoryList(name, size)); - return m_lists.getLast(); - } + + + //don't create list with invalid name + if (name.find(' ') != std::string::npos) + return nullptr; + + InventoryList *list = new InventoryList(name, size, m_itemdef); + list->setModified(); + m_lists.push_back(list); + return list; } InventoryList * Inventory::getList(const std::string &name) @@ -995,13 +953,24 @@ InventoryList * Inventory::getList(const std::string &name) return m_lists[i]; } +std::vector Inventory::getLists() +{ + std::vector lists; + for (auto list : m_lists) { + lists.push_back(list); + } + return lists; +} + bool Inventory::deleteList(const std::string &name) { s32 i = getListIndex(name); if(i == -1) return false; + + setModified(); delete m_lists[i]; - m_lists.erase(i); + m_lists.erase(m_lists.begin() + i); return true; } @@ -1023,458 +992,4 @@ const s32 Inventory::getListIndex(const std::string &name) const return -1; } -/* - InventoryAction -*/ - -InventoryAction * InventoryAction::deSerialize(std::istream &is) -{ - std::string type; - std::getline(is, type, ' '); - - InventoryAction *a = NULL; - - if(type == "Move") - { - a = new IMoveAction(is); - } - else if(type == "Drop") - { - a = new IDropAction(is); - } - - return a; -} - -static std::string describeC(const struct InventoryContext *c) -{ - if(c->current_player == NULL) - return "current_player=NULL"; - else - return std::string("current_player=") + c->current_player->getName(); -} - -IMoveAction::IMoveAction(std::istream &is) -{ - std::string ts; - - std::getline(is, ts, ' '); - count = stoi(ts); - - std::getline(is, from_inv, ' '); - - std::getline(is, from_list, ' '); - - std::getline(is, ts, ' '); - from_i = stoi(ts); - - std::getline(is, to_inv, ' '); - - std::getline(is, to_list, ' '); - - std::getline(is, ts, ' '); - to_i = stoi(ts); -} - -void IMoveAction::apply(InventoryContext *c, InventoryManager *mgr, - ServerEnvironment *env) -{ - Inventory *inv_from = mgr->getInventory(c, from_inv); - Inventory *inv_to = mgr->getInventory(c, to_inv); - - if(!inv_from){ - infostream<<"IMoveAction::apply(): FAIL: source inventory not found: " - <<"context=["<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: " - <<"context=["<getItem(from_i) == NULL) - { - infostream<<"IMoveAction::apply(): FAIL: source item not found: " - <<"context=["<( - static_cast( - c->current_player - )); - - Inventory *inv_from = mgr->getInventory(c, from_inv); - - if(!inv_from){ - infostream<<"IDropAction::apply(): FAIL: source inventory not found: " - <<"context=["<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: " - <<"context=["<getItem(from_i); - if(item == NULL) - { - infostream<<"IDropAction::apply(): FAIL: source item not found: " - <<"context=["<getMaterial() != num) - return false; - } else { - if(mitem->getNodeName() != name) - return false; - } - } - else if(type == ITEM_CRAFT) - { - if(itemname != "CraftItem") - return false; - CraftItem *mitem = (CraftItem*)item; - if(mitem->getSubName() != name) - return false; - } - else if(type == ITEM_TOOL) - { - // Not supported yet - assert(0); - } - else if(type == ITEM_MBO) - { - // Not supported yet - assert(0); - } - else - { - // Not supported yet - assert(0); - } - return true; -} - -bool checkItemCombination(InventoryItem const * const *items, const ItemSpec *specs) -{ - u16 items_min_x = 100; - u16 items_max_x = 100; - u16 items_min_y = 100; - u16 items_max_y = 100; - for(u16 y=0; y<3; y++) - for(u16 x=0; x<3; x++) - { - if(items[y*3 + x] == NULL) - continue; - if(items_min_x == 100 || x < items_min_x) - items_min_x = x; - if(items_min_y == 100 || y < items_min_y) - items_min_y = y; - if(items_max_x == 100 || x > items_max_x) - items_max_x = x; - if(items_max_y == 100 || y > items_max_y) - items_max_y = y; - } - // No items at all, just return false - if(items_min_x == 100) - return false; - - u16 items_w = items_max_x - items_min_x + 1; - u16 items_h = items_max_y - items_min_y + 1; - - u16 specs_min_x = 100; - u16 specs_max_x = 100; - u16 specs_min_y = 100; - u16 specs_max_y = 100; - for(u16 y=0; y<3; y++) - for(u16 x=0; x<3; x++) - { - if(specs[y*3 + x].type == ITEM_NONE) - continue; - if(specs_min_x == 100 || x < specs_min_x) - specs_min_x = x; - if(specs_min_y == 100 || y < specs_min_y) - specs_min_y = y; - if(specs_max_x == 100 || x > specs_max_x) - specs_max_x = x; - if(specs_max_y == 100 || y > specs_max_y) - specs_max_y = y; - } - // No specs at all, just return false - if(specs_min_x == 100) - return false; - - u16 specs_w = specs_max_x - specs_min_x + 1; - u16 specs_h = specs_max_y - specs_min_y + 1; - - // Different sizes - if(items_w != specs_w || items_h != specs_h) - return false; - - for(u16 y=0; y items_max_x) - items_max_x = x; - if(items_max_y == 100 || y > items_max_y) - items_max_y = y; - } - // No items at all, just return false - if(items_min_x == 100) - return false; - - u16 items_w = items_max_x - items_min_x + 1; - u16 items_h = items_max_y - items_min_y + 1; - - u16 specs_min_x = 100; - u16 specs_max_x = 100; - u16 specs_min_y = 100; - u16 specs_max_y = 100; - for(u16 y=0; y<3; y++) - for(u16 x=0; x<3; x++) - { - if(specs[y*3 + x] == NULL) - continue; - if(specs_min_x == 100 || x < specs_min_x) - specs_min_x = x; - if(specs_min_y == 100 || y < specs_min_y) - specs_min_y = y; - if(specs_max_x == 100 || x > specs_max_x) - specs_max_x = x; - if(specs_max_y == 100 || y > specs_max_y) - specs_max_y = y; - } - // No specs at all, just return false - if(specs_min_x == 100) - return false; - - u16 specs_w = specs_max_x - specs_min_x + 1; - u16 specs_h = specs_max_y - specs_min_y + 1; - - // Different sizes - if(items_w != specs_w || items_h != specs_h) - return false; - - for(u16 y=0; yisSubsetOf(item)) - return false; - } - - return true; -} - //END