remove_detached_inventory: Fix segfault during mod load
[oweals/minetest.git] / src / inventory.cpp
index 4e897d9ff343e26827cf96462c913453784465be..40dc602d0e855883a1d0a42549bdedb117c1fcda 100644 (file)
 /*
-Minetest-c55
-Copyright (C) 2010-2011 celeron55, Perttu Ahola <celeron55@gmail.com>
+Minetest
+Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
 
 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 <sstream>
-#include "main.h" // For tsrc, g_toolmanager
-#include "serverobject.h"
-#include "content_mapnode.h"
-#include "content_inventory.h"
-#include "content_sao.h"
-#include "environment.h"
-#include "mapblock.h"
-#include "player.h"
 #include "log.h"
-#include "nodedef.h"
-#include "tooldef.h"
-#include "gamedef.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 "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<sizeof(trans_table_19)/sizeof(trans_table_19[0]); i++)
-       {
-               if(trans_table_19[i][1] == c_from)
-               {
-                       return trans_table_19[i][0];
-               }
+       if (empty())
+               return;
+
+       // Check how many parts of the itemstring are needed
+       int parts = 1;
+       if(count != 1)
+               parts = 2;
+       if(wear != 0)
+               parts = 3;
+       if (!metadata.empty())
+               parts = 4;
+
+       os<<serializeJsonStringIfNeeded(name);
+       if(parts >= 2)
+               os<<" "<<count;
+       if(parts >= 3)
+               os<<" "<<wear;
+       if (parts >= 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 == "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("")));
-               return new MaterialItem(gamedef, nodename, count);
+               if (itemdef)
+                       name = itemdef->getAlias(name);
+               count = stoi(trim(fnd.next("")));
+               if(count == 0)
+                       count = 1;
        }
        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 == "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("")));
-               return new CraftItem(gamedef, subname, count);
-       }
-       else if(name == "ToolItem")
+       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=\""<<name<<"\""<<std::endl;
-               throw SerializationError("Unknown InventoryItem name");
-       }
-}
+               do  // This loop is just to allow "break;"
+               {
+                       // The real thing
 
-InventoryItem* InventoryItem::deSerialize(const std::string &str,
-               IGameDef *gamedef)
-{
-       std::istringstream is(str, std::ios_base::binary);
-       return deSerialize(is, gamedef);
-}
+                       // Apply item aliases
+                       if (itemdef)
+                               name = itemdef->getAlias(name);
 
-std::string InventoryItem::getItemString() {
-       // Get item string
-       std::ostringstream os(std::ios_base::binary);
-       serialize(os);
-       return os.str();
-}
+                       // Read the count
+                       std::string count_str;
+                       std::getline(is, count_str, ' ');
+                       if (count_str.empty()) {
+                               count = 1;
+                               break;
+                       }
 
-ServerActiveObject* InventoryItem::createSAO(ServerEnvironment *env, v3f pos)
-{
-       /*
-               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());
-       return obj;
-}
+                       count = stoi(count_str);
 
-/*
-       MaterialItem
-*/
+                       // Read the wear
+                       std::string wear_str;
+                       std::getline(is, wear_str, ' ');
+                       if(wear_str.empty())
+                               break;
 
-MaterialItem::MaterialItem(IGameDef *gamedef, std::string nodename, u16 count):
-       InventoryItem(gamedef, count)
-{
-       if(nodename == "")
-               nodename = "unknown_block";
-       m_nodename = nodename;
-}
-// Legacy constructor
-MaterialItem::MaterialItem(IGameDef *gamedef, content_t content, u16 count):
-       InventoryItem(gamedef, count)
-{
-       INodeDefManager *ndef = m_gamedef->ndef();
-       std::string nodename = ndef->get(content).name;
-       if(nodename == "")
-               nodename = "unknown_block";
-       m_nodename = nodename;
-}
+                       wear = stoi(wear_str);
 
-#ifndef SERVER
-video::ITexture * MaterialItem::getImage() const
-{
-       return m_gamedef->getNodeDefManager()->get(m_nodename).inventory_texture;
-}
-#endif
+                       // Read metadata
+                       metadata.deSerialize(is);
 
-bool MaterialItem::isCookable() const
-{
-       INodeDefManager *ndef = m_gamedef->ndef();
-       const ContentFeatures &f = ndef->get(m_nodename);
-       return (f.cookresult_item != "");
-}
+                       // In case fields are added after metadata, skip space here:
+                       //std::getline(is, tmp, ' ');
+                       //if(!tmp.empty())
+                       //      throw SerializationError("Unexpected text after metadata");
 
-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);
-}
+               } while(false);
+       }
 
-float MaterialItem::getCookTime() const
-{
-       INodeDefManager *ndef = m_gamedef->ndef();
-       const ContentFeatures &f = ndef->get(m_nodename);
-       return f.furnace_cooktime;
+       if (name.empty() || count == 0)
+               clear();
+       else if (itemdef && itemdef->get(name).type == ITEM_TOOL)
+               count = 1;
 }
 
-float MaterialItem::getBurnTime() const
+void ItemStack::deSerialize(const std::string &str, IItemDefManager *itemdef)
 {
-       INodeDefManager *ndef = m_gamedef->ndef();
-       const ContentFeatures &f = ndef->get(m_nodename);
-       return f.furnace_burntime;
+       std::istringstream is(str, std::ios::binary);
+       deSerialize(is, itemdef);
 }
 
-content_t MaterialItem::getMaterial() const
+std::string ItemStack::getItemString() const
 {
-       INodeDefManager *ndef = m_gamedef->ndef();
-       content_t id = CONTENT_IGNORE;
-       ndef->getId(m_nodename, id);
-       return id;
+       std::ostringstream os(std::ios::binary);
+       serialize(os);
+       return os.str();
 }
 
-/*
-       ToolItem
-*/
-
-std::string ToolItem::getImageBasename() const
-{
-       return m_gamedef->getToolDefManager()->getImagename(m_toolname);
-}
 
-#ifndef SERVER
-video::ITexture * ToolItem::getImage() const
+ItemStack ItemStack::addItem(ItemStack newitem, IItemDefManager *itemdef)
 {
-       ITextureSource *tsrc = m_gamedef->tsrc();
-
-       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<<basename<<"^[progressbar"<<value_f;
-
-       return tsrc->getTextureRaw(os.str());
-}
+       // 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);
+       }
 
-video::ITexture * ToolItem::getImageRaw() const
-{
-       ITextureSource *tsrc = m_gamedef->tsrc();
-       
-       return tsrc->getTextureRaw(getImageBasename());
+       return newitem;
 }
-#endif
-
-/*
-       CraftItem
-*/
 
-#ifndef SERVER
-video::ITexture * CraftItem::getImage() const
+bool ItemStack::itemFits(ItemStack newitem,
+               ItemStack *restitem,
+               IItemDefManager *itemdef) const
 {
-       ITextureSource *tsrc = m_gamedef->tsrc();
-
-       std::string name = item_craft_get_image_name(m_subname, m_gamedef);
-
-       // Get such a texture
-       return tsrc->getTextureRaw(name);
-}
-#endif
 
-ServerActiveObject* CraftItem::createSAO(ServerEnvironment *env, v3f pos)
-{
-       // Special cases
-       ServerActiveObject *obj = item_craft_create_object(m_subname, env, pos);
-       if(obj)
-               return obj;
-       // Default
-       return InventoryItem::createSAO(env, pos);
-}
+       // 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())
+       {
+               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, delete it
+       else if(newitem.count <= freeSpace(itemdef))
+       {
+               newitem.clear();
+       }
+       // Else the item does not fit fully. Return the rest.
+       else
+       {
+               u16 freespace = freeSpace(itemdef);
+               newitem.remove(freespace);
+       }
 
-u16 CraftItem::getDropCount() const
-{
-       // Special cases
-       s16 dc = item_craft_get_drop_count(m_subname, m_gamedef);
-       if(dc != -1)
-               return dc;
-       // Default
-       return InventoryItem::getDropCount();
-}
+       if(restitem)
+               *restitem = newitem;
 
-bool CraftItem::isCookable() const
-{
-       return item_craft_is_cookable(m_subname, m_gamedef);
+       return newitem.empty();
 }
 
-InventoryItem *CraftItem::createCookResult() const
+ItemStack ItemStack::takeItem(u32 takecount)
 {
-       return item_craft_create_cook_result(m_subname, m_gamedef);
-}
+       if(takecount == 0 || count == 0)
+               return ItemStack();
 
-float CraftItem::getCookTime() const
-{
-       return 3.0;
+       ItemStack result = *this;
+       if(takecount >= count)
+       {
+               // Take all
+               clear();
+       }
+       else
+       {
+               // Take part
+               remove(takecount);
+               result.count = takecount;
+       }
+       return result;
 }
 
-float CraftItem::getBurnTime() const
+ItemStack ItemStack::peekItem(u32 peekcount) const
 {
-       if(m_subname == "lump_of_coal")
-               return 40;
-       return -1;
-}
+       if(peekcount == 0 || count == 0)
+               return ItemStack();
 
-bool CraftItem::use(ServerEnvironment *env, ServerActiveObject *user)
-{
-       if(!item_craft_is_eatable(m_subname, m_gamedef))
-               return false;
-       
-       u16 result_count = getCount() - 1; // Eat one at a time
-       s16 hp_change = item_craft_eat_hp_change(m_subname, m_gamedef);
-       s16 hp = user->getHP();
-       hp += hp_change;
-       if(hp < 0)
-               hp = 0;
-       user->setHP(hp);
-       
-       if(result_count < 1)
-               return true;
-               
-       setCount(result_count);
-       return false;
+       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()
-{
-       for(u32 i=0; i<m_items.size(); i++)
-       {
-               if(m_items[i])
-                       delete m_items[i];
-       }
 }
 
 void InventoryList::clearItems()
 {
-       for(u32 i=0; i<m_items.size(); i++)
-       {
-               if(m_items[i])
-                       delete m_items[i];
-       }
-
        m_items.clear();
 
-       for(u32 i=0; i<m_size; i++)
-       {
-               m_items.push_back(NULL);
+       for (u32 i=0; i < m_size; i++) {
+               m_items.emplace_back();
        }
 
        //setDirty(true);
 }
 
+void InventoryList::setSize(u32 newsize)
+{
+       if(newsize != m_items.size())
+               m_items.resize(newsize);
+       m_size = newsize;
+}
+
+void InventoryList::setWidth(u32 newwidth)
+{
+       m_width = newwidth;
+}
+
+void InventoryList::setName(const std::string &name)
+{
+       m_name = name;
+}
+
 void InventoryList::serialize(std::ostream &os) const
 {
        //os.imbue(std::locale("C"));
-       
-       for(u32 i=0; i<m_items.size(); i++)
-       {
-               InventoryItem *item = m_items[i];
-               if(item != NULL)
-               {
-                       os<<"Item ";
-                       item->serialize(os);
-               }
-               else
-               {
+
+       os<<"Width "<<m_width<<"\n";
+
+       for (const auto &item : m_items) {
+               if (item.empty()) {
                        os<<"Empty";
+               } else {
+                       os<<"Item ";
+                       item.serialize(os);
                }
                os<<"\n";
        }
@@ -439,15 +413,15 @@ void InventoryList::serialize(std::ostream &os) const
        os<<"EndInventoryList\n";
 }
 
-void InventoryList::deSerialize(std::istream &is, IGameDef *gamedef)
+void InventoryList::deSerialize(std::istream &is)
 {
        //is.imbue(std::locale("C"));
 
        clearItems();
        u32 item_i = 0;
+       m_width = 0;
 
-       for(;;)
-       {
+       while (is.good()) {
                std::string line;
                std::getline(is, line, '\n');
 
@@ -457,109 +431,123 @@ void InventoryList::deSerialize(std::istream &is, IGameDef *gamedef)
                std::string name;
                std::getline(iss, name, ' ');
 
-               if(name == "EndInventoryList")
-               {
-                       break;
-               }
+               if (name == "EndInventoryList")
+                       return;
+
                // This is a temporary backwards compatibility fix
-               else if(name == "end")
-               {
-                       break;
+               if (name == "end")
+                       return;
+
+               if (name == "Width") {
+                       iss >> 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();
                }
        }
+
+       // 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; i<other.m_items.size(); i++)
-       {
-               InventoryItem *item = other.m_items[i];
-               if(item != NULL)
-               {
-                       m_items[i] = item->clone();
-               }
-       }
+       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(); i++)
-       {
-               InventoryItem *item = m_items[i];
-               if(item != NULL)
+       for (const auto &m_item : m_items) {
+               if (!m_item.empty())
                        num++;
        }
        return num;
 }
 
-u32 InventoryList::getFreeSlots()
+u32 InventoryList::getFreeSlots() const
 {
        return getSize() - getUsedSlots();
 }
 
-const InventoryItem * InventoryList::getItem(u32 i) const
+const ItemStack& InventoryList::getItem(u32 i) const
 {
-       if(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);
        return olditem;
@@ -567,29 +555,29 @@ InventoryItem * InventoryList::changeItem(u32 i, InventoryItem *newitem)
 
 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();
 }
 
-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(); i++)
        {
                // Ignore empty slots
-               if(m_items[i] == NULL)
+               if(m_items[i].empty())
                        continue;
                // Try adding
                newitem = addItem(i, newitem);
-               if(newitem == NULL)
-                       return NULL; // All was eaten
+               if(newitem.empty())
+                       return newitem; // All was eaten
        }
 
        /*
@@ -598,150 +586,180 @@ InventoryItem * InventoryList::addItem(InventoryItem *newitem)
        for(u32 i=0; i<m_items.size(); i++)
        {
                // Ignore unempty slots
-               if(m_items[i] != NULL)
+               if(!m_items[i].empty())
                        continue;
                // Try adding
                newitem = addItem(i, newitem);
-               if(newitem == NULL)
-                       return NULL; // All was eaten
+               if(newitem.empty())
+                       return newitem; // All was eaten
        }
 
        // Return leftover
        return newitem;
 }
 
-InventoryItem * InventoryList::addItem(u32 i, InventoryItem *newitem)
+ItemStack InventoryList::addItem(u32 i, const ItemStack &newitem)
 {
-       if(newitem == NULL)
-               return NULL;
        if(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)
+       //      setDirty(true);
+       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; i<m_items.size(); i++)
-               if(itemFits(i, item))
+       {
+               if(itemFits(i, item, &leftover))
                        return true;
+               item = leftover;
+       }
        return false;
 }
 
-bool InventoryList::roomForCookedItem(const InventoryItem *item)
+bool InventoryList::containsItem(const ItemStack &item, bool match_meta) const
 {
-       if(!item)
-               return false;
-       const InventoryItem *cook = item->createCookResult();
-       if(!cook)
-               return false;
-       bool room = roomForItem(cook);
-       delete cook;
-       return room;
-}
+       u32 count = item.count;
+       if (count == 0)
+               return true;
 
-InventoryItem * InventoryList::takeItem(u32 i, u32 count)
-{
-       if(count == 0)
-               return NULL;
-       
-       //setDirty(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;
 
-       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;
+                       count -= i->count;
+               }
        }
-       
        return false;
 }
 
-void InventoryList::decrementMaterials(u16 count)
+ItemStack InventoryList::removeItem(const ItemStack &item)
 {
-       for(u32 i=0; i<m_items.size(); i++)
-       {
-               InventoryItem *item = takeItem(i, count);
-               if(item)
-                       delete item;
+       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;
+                       removed.addItem(i->takeItem(still_to_remove), m_itemdef);
+                       if (removed.count == item.count)
+                               break;
+               }
        }
+       return removed;
 }
 
-void InventoryList::print(std::ostream &o)
+ItemStack InventoryList::takeItem(u32 i, u32 takecount)
 {
-       o<<"InventoryList:"<<std::endl;
-       for(u32 i=0; i<m_items.size(); i++)
+       if(i >= m_items.size())
+               return ItemStack();
+
+       ItemStack taken = m_items[i].takeItem(takecount);
+       //if(!taken.empty())
+       //      setDirty(true);
+       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;
+
+       // Try to add the item to destination list
+       u32 dest_size = dest->getSize();
+       // First try all the non-empty slots
+       for (u32 dest_i = 0; dest_i < dest_size; dest_i++) {
+               if (!m_items[dest_i].empty()) {
+                       item1 = dest->addItem(dest_i, item1);
+                       if (item1.empty()) return;
+               }
+       }
+
+       // Then try all the empty ones
+       for (u32 dest_i = 0; dest_i < dest_size; dest_i++) {
+               if (m_items[dest_i].empty()) {
+                       item1 = dest->addItem(dest_i, item1);
+                       if (item1.empty()) return;
+               }
+       }
+
+       // If we reach this, the item was not fully added
+       // Add the remaining part back to the source item
+       addItem(i, item1);
+}
+
+u32 InventoryList::moveItem(u32 i, InventoryList *dest, u32 dest_i,
+               u32 count, bool swap_if_needed, bool *did_swap)
+{
+       if(this == dest && i == dest_i)
+               return count;
+
+       // Take item from source list
+       ItemStack item1;
+       if(count == 0)
+               item1 = changeItem(i, ItemStack());
+       else
+               item1 = takeItem(i, count);
+
+       if(item1.empty())
+               return 0;
+
+       // Try to add the item to destination list
+       u32 oldcount = item1.count;
+       item1 = dest->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<<i<<": ";
-                       item->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);
 }
 
 /*
@@ -755,37 +773,66 @@ Inventory::~Inventory()
 
 void Inventory::clear()
 {
-       for(u32 i=0; i<m_lists.size(); i++)
-       {
-               delete m_lists[i];
+       m_dirty = true;
+       for (auto &m_list : m_lists) {
+               delete m_list;
        }
        m_lists.clear();
 }
 
-Inventory::Inventory()
+void Inventory::clearContents()
 {
+       m_dirty = true;
+       for (InventoryList *list : m_lists) {
+               for (u32 j=0; j<list->getSize(); j++) {
+                       list->deleteItem(j);
+               }
+       }
+}
+
+Inventory::Inventory(IItemDefManager *itemdef)
+{
+       m_dirty = false;
+       m_itemdef = itemdef;
 }
 
 Inventory::Inventory(const Inventory &other)
 {
        *this = other;
+       m_dirty = false;
 }
 
 Inventory & Inventory::operator = (const Inventory &other)
 {
-       clear();
-       for(u32 i=0; i<other.m_lists.size(); i++)
+       // Gracefully handle self assignment
+       if(this != &other)
        {
-               m_lists.push_back(new InventoryList(*other.m_lists[i]));
+               m_dirty = true;
+               clear();
+               m_itemdef = other.m_itemdef;
+               for (InventoryList *list : other.m_lists) {
+                       m_lists.push_back(new InventoryList(*list));
+               }
        }
        return *this;
 }
 
-void Inventory::serialize(std::ostream &os) const
+bool Inventory::operator == (const Inventory &other) const
 {
+       if(m_lists.size() != other.m_lists.size())
+               return false;
+
        for(u32 i=0; i<m_lists.size(); i++)
        {
-               InventoryList *list = m_lists[i];
+               if(*m_lists[i] != *other.m_lists[i])
+                       return false;
+       }
+       return true;
+}
+
+void Inventory::serialize(std::ostream &os) const
+{
+       for (InventoryList *list : m_lists) {
                os<<"List "<<list->getName()<<" "<<list->getSize()<<"\n";
                list->serialize(os);
        }
@@ -793,12 +840,11 @@ void Inventory::serialize(std::ostream &os) const
        os<<"EndInventory\n";
 }
 
-void Inventory::deSerialize(std::istream &is, IGameDef *gamedef)
+void Inventory::deSerialize(std::istream &is)
 {
        clear();
 
-       for(;;)
-       {
+       while (is.good()) {
                std::string line;
                std::getline(is, line, '\n');
 
@@ -807,52 +853,60 @@ void Inventory::deSerialize(std::istream &is, IGameDef *gamedef)
                std::string name;
                std::getline(iss, name, ' ');
 
-               if(name == "EndInventory")
-               {
-                       break;
-               }
+               if (name == "EndInventory")
+                       return;
+
                // This is a temporary backwards compatibility fix
-               else if(name == "end")
-               {
-                       break;
-               }
-               else if(name == "List")
-               {
+               if (name == "end")
+                       return;
+
+               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 = new InventoryList(listname, listsize, m_itemdef);
+                       list->deSerialize(is);
 
                        m_lists.push_back(list);
                }
                else
                {
-                       throw SerializationError("Unknown inventory identifier");
+                       throw SerializationError("invalid inventory specifier: " + name);
                }
        }
+
+       // 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)
 {
+       m_dirty = true;
        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);
                }
                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 NULL;
+
+       InventoryList *list = new InventoryList(name, size, m_itemdef);
+       m_lists.push_back(list);
+       return list;
 }
 
 InventoryList * Inventory::getList(const std::string &name)
@@ -863,13 +917,23 @@ InventoryList * Inventory::getList(const std::string &name)
        return m_lists[i];
 }
 
+std::vector<const InventoryList*> Inventory::getLists()
+{
+       std::vector<const InventoryList*> 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;
+       m_dirty = true;
        delete m_lists[i];
-       m_lists.erase(i);
+       m_lists.erase(m_lists.begin() + i);
        return true;
 }
 
@@ -891,468 +955,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=["<<describeC(c)<<"], from_inv=\""<<from_inv<<"\""
-                               <<", to_inv=\""<<to_inv<<"\""<<std::endl;
-               return;
-       }
-       if(!inv_to){
-               infostream<<"IMoveAction::apply(): FAIL: destination inventory not found: "
-                               "context=["<<describeC(c)<<"], from_inv=\""<<from_inv<<"\""
-                               <<", to_inv=\""<<to_inv<<"\""<<std::endl;
-               return;
-       }
-
-       InventoryList *list_from = inv_from->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=["<<describeC(c)<<"], from_inv=\""<<from_inv<<"\""
-                               <<", from_list=\""<<from_list<<"\""<<std::endl;
-               return;
-       }
-       if(!list_to){
-               infostream<<"IMoveAction::apply(): FAIL: destination list not found: "
-                               <<"context=["<<describeC(c)<<"], to_inv=\""<<to_inv<<"\""
-                               <<", to_list=\""<<to_list<<"\""<<std::endl;
-               return;
-       }
-       if(list_from->getItem(from_i) == NULL)
-       {
-               infostream<<"IMoveAction::apply(): FAIL: source item not found: "
-                               <<"context=["<<describeC(c)<<"], from_inv=\""<<from_inv<<"\""
-                               <<", from_list=\""<<from_list<<"\""
-                               <<" from_i="<<from_i<<std::endl;
-               return;
-       }
-       /*
-               If the source and the destination slots are the same
-       */
-       if(inv_from == inv_to && list_from == list_to && from_i == to_i)
-       {
-               infostream<<"IMoveAction::apply(): FAIL: source and destination slots "
-                               <<"are the same: inv=\""<<from_inv<<"\" list=\""<<from_list
-                               <<"\" i="<<from_i<<std::endl;
-               return;
-       }
-       
-       // Take item from source list
-       InventoryItem *item1 = NULL;
-       if(count == 0)
-               item1 = list_from->changeItem(from_i, NULL);
-       else
-               item1 = list_from->takeItem(from_i, count);
-
-       // Try to add the item to destination list
-       InventoryItem *olditem = item1;
-       item1 = list_to->addItem(to_i, item1);
-
-       // If something is returned, the item was not fully added
-       if(item1 != NULL)
-       {
-               // If olditem is returned, nothing was added.
-               bool nothing_added = (item1 == olditem);
-               
-               // If something else is returned, part of the item was left unadded.
-               // Add the other part back to the source item
-               list_from->addItem(from_i, item1);
-
-               // If olditem is returned, nothing was added.
-               // Swap the items
-               if(nothing_added)
-               {
-                       // Take item from source list
-                       item1 = list_from->changeItem(from_i, NULL);
-                       // Adding was not possible, swap the items.
-                       InventoryItem *item2 = list_to->changeItem(to_i, item1);
-                       // Put item from destination list to the source list
-                       list_from->changeItem(from_i, item2);
-               }
-       }
-
-       mgr->inventoryModified(c, from_inv);
-       if(from_inv != to_inv)
-               mgr->inventoryModified(c, to_inv);
-       
-       infostream<<"IMoveAction::apply(): moved at "
-                       <<"["<<describeC(c)<<"]"
-                       <<" from inv=\""<<from_inv<<"\""
-                       <<" list=\""<<from_list<<"\""
-                       <<" i="<<from_i
-                       <<" to inv=\""<<to_inv<<"\""
-                       <<" list=\""<<to_list<<"\""
-                       <<" i="<<to_i
-                       <<std::endl;
-}
-
-IDropAction::IDropAction(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);
-}
-
-void IDropAction::apply(InventoryContext *c, InventoryManager *mgr,
-               ServerEnvironment *env)
-{
-       Inventory *inv_from = mgr->getInventory(c, from_inv);
-       
-       if(!inv_from){
-               infostream<<"IDropAction::apply(): FAIL: source inventory not found: "
-                               <<"context=["<<describeC(c)<<"], from_inv=\""<<from_inv<<"\""<<std::endl;
-               return;
-       }
-
-       InventoryList *list_from = inv_from->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=["<<describeC(c)<<"], from_inv=\""<<from_inv<<"\""
-                               <<", from_list=\""<<from_list<<"\""<<std::endl;
-               return;
-       }
-       if(list_from->getItem(from_i) == NULL)
-       {
-               infostream<<"IDropAction::apply(): FAIL: source item not found: "
-                               <<"context=["<<describeC(c)<<"], from_inv=\""<<from_inv<<"\""
-                               <<", from_list=\""<<from_list<<"\""
-                               <<" from_i="<<from_i<<std::endl;
-               return;
-       }
-
-       v3f pos = c->current_player->getPosition();
-       pos.Y += 0.5*BS;
-       v3s16 blockpos = getNodeBlockPos(floatToInt(pos, BS));
-
-       /*
-               Ensure that the block is loaded so that the item
-               can properly be added to the static list too
-       */
-       MapBlock *block = env->getMap().emergeBlock(blockpos, false);
-       if(block==NULL)
-       {
-               infostream<<"IDropAction::apply(): FAIL: block not found: "
-                               <<blockpos.X<<","<<blockpos.Y<<","<<blockpos.Z
-                               <<std::endl;
-               return;
-       }
-
-       // Take item from source list
-       if(count == 0)
-               count = list_from->getItem(from_i)->getDropCount();
-       InventoryItem *item1 = list_from->takeItem(from_i, count);
-
-       // Create an active object
-       ServerActiveObject *obj = item1->createSAO(env, pos);
-       if(obj == NULL)
-       {
-               infostream<<"IDropAction::apply(): item resulted in NULL object, "
-                       <<"not placing onto map"
-                       <<std::endl;
-       }
-       else
-       {
-               // Add the object to the environment
-               env->addActiveObject(obj);
-
-               infostream<<"Dropped object"<<std::endl;
-       }
-
-       mgr->inventoryModified(c, from_inv);
-
-       infostream<<"IDropAction::apply(): dropped "
-                       <<"["<<describeC(c)<<"]"
-                       <<" from inv=\""<<from_inv<<"\""
-                       <<" list=\""<<from_list<<"\""
-                       <<" i="<<from_i
-                       <<std::endl;
-}
-
-/*
-       Craft checking system
-*/
-
-bool ItemSpec::checkItem(const InventoryItem *item) const
-{
-       if(type == ITEM_NONE)
-       {
-               // Has to be no item
-               if(item != NULL)
-                       return false;
-               return true;
-       }
-       
-       // There should be an item
-       if(item == NULL)
-               return false;
-
-       std::string itemname = item->getName();
-
-       if(type == ITEM_MATERIAL)
-       {
-               if(itemname != "MaterialItem")
-                       return false;
-               MaterialItem *mitem = (MaterialItem*)item;
-               if(num != 65535){
-                       if(mitem->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<specs_h; y++)
-       for(u16 x=0; x<specs_w; x++)
-       {
-               u16 items_x = items_min_x + x;
-               u16 items_y = items_min_y + y;
-               u16 specs_x = specs_min_x + x;
-               u16 specs_y = specs_min_y + y;
-               const InventoryItem *item = items[items_y * 3 + items_x];
-               const ItemSpec &spec = specs[specs_y * 3 + specs_x];
-
-               if(spec.checkItem(item) == false)
-                       return false;
-       }
-
-       return true;
-}
-
-bool checkItemCombination(const InventoryItem * const * items,
-               const InventoryItem * const * 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] == 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; y<specs_h; y++)
-       for(u16 x=0; x<specs_w; x++)
-       {
-               u16 items_x = items_min_x + x;
-               u16 items_y = items_min_y + y;
-               u16 specs_x = specs_min_x + x;
-               u16 specs_y = specs_min_y + y;
-               const InventoryItem *item = items[items_y * 3 + items_x];
-               const InventoryItem *spec = specs[specs_y * 3 + specs_x];
-               
-               if(item == NULL && spec == NULL)
-                       continue;
-               if(item == NULL && spec != NULL)
-                       return false;
-               if(item != NULL && spec == NULL)
-                       return false;
-               if(!spec->isSubsetOf(item))
-                       return false;
-       }
-
-       return true;
-}
-
 //END