3 Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU Lesser General Public License as published by
7 the Free Software Foundation; either version 2.1 of the License, or
8 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU Lesser General Public License for more details.
15 You should have received a copy of the GNU Lesser General Public License along
16 with this program; if not, write to the Free Software Foundation, Inc.,
17 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20 #include "inventory.h"
21 #include "serialization.h"
27 #include "util/strfnd.h"
28 #include "content_mapnode.h" // For loading legacy MaterialItems
29 #include "nameidmapping.h" // For loading legacy MaterialItems
30 #include "util/serialize.h"
31 #include "util/string.h"
37 static content_t content_translate_from_19_to_internal(content_t c_from)
39 for (const auto &tt : trans_table_19) {
47 ItemStack::ItemStack(const std::string &name_, u16 count_,
48 u16 wear_, IItemDefManager *itemdef) :
49 name(itemdef->getAlias(name_)),
53 if (name.empty() || count == 0)
55 else if (itemdef->get(name).type == ITEM_TOOL)
59 void ItemStack::serialize(std::ostream &os) const
64 // Check how many parts of the itemstring are needed
70 if (!metadata.empty())
73 os<<serializeJsonStringIfNeeded(name);
80 metadata.serialize(os);
84 void ItemStack::deSerialize(std::istream &is, IItemDefManager *itemdef)
89 name = deSerializeJsonStringIfNeeded(is);
93 std::getline(is, tmp, ' ');
95 throw SerializationError("Unexpected text after item name");
97 if(name == "MaterialItem")
99 // Obsoleted on 2011-07-30
105 // Convert old materials
107 material = content_translate_from_19_to_internal(material);
109 throw SerializationError("Too large material number");
110 // Convert old id to name
111 NameIdMapping legacy_nimap;
112 content_mapnode_get_name_id_mapping(&legacy_nimap);
113 legacy_nimap.getName(material, name);
115 name = "unknown_block";
117 name = itemdef->getAlias(name);
118 count = materialcount;
120 else if(name == "MaterialItem2")
122 // Obsoleted on 2011-11-16
129 throw SerializationError("Too large material number");
130 // Convert old id to name
131 NameIdMapping legacy_nimap;
132 content_mapnode_get_name_id_mapping(&legacy_nimap);
133 legacy_nimap.getName(material, name);
135 name = "unknown_block";
137 name = itemdef->getAlias(name);
138 count = materialcount;
140 else if(name == "node" || name == "NodeItem" || name == "MaterialItem3"
141 || name == "craft" || name == "CraftItem")
143 // Obsoleted on 2012-01-07
146 std::getline(is, all, '\n');
147 // First attempt to read inside ""
150 // If didn't skip to end, we have ""s
152 name = fnd.next("\"");
153 } else { // No luck, just read a word then
155 name = fnd.next(" ");
159 name = itemdef->getAlias(name);
160 count = stoi(trim(fnd.next("")));
164 else if(name == "MBOItem")
166 // Obsoleted on 2011-10-14
167 throw SerializationError("MBOItem not supported anymore");
169 else if(name == "tool" || name == "ToolItem")
171 // Obsoleted on 2012-01-07
174 std::getline(is, all, '\n');
175 // First attempt to read inside ""
178 // If didn't skip to end, we have ""s
180 name = fnd.next("\"");
181 } else { // No luck, just read a word then
183 name = fnd.next(" ");
189 name = itemdef->getAlias(name);
190 wear = stoi(trim(fnd.next("")));
194 do // This loop is just to allow "break;"
198 // Apply item aliases
200 name = itemdef->getAlias(name);
203 std::string count_str;
204 std::getline(is, count_str, ' ');
205 if (count_str.empty()) {
210 count = stoi(count_str);
213 std::string wear_str;
214 std::getline(is, wear_str, ' ');
218 wear = stoi(wear_str);
221 metadata.deSerialize(is);
223 // In case fields are added after metadata, skip space here:
224 //std::getline(is, tmp, ' ');
226 // throw SerializationError("Unexpected text after metadata");
231 if (name.empty() || count == 0)
233 else if (itemdef && itemdef->get(name).type == ITEM_TOOL)
237 void ItemStack::deSerialize(const std::string &str, IItemDefManager *itemdef)
239 std::istringstream is(str, std::ios::binary);
240 deSerialize(is, itemdef);
243 std::string ItemStack::getItemString() const
245 std::ostringstream os(std::ios::binary);
250 std::string ItemStack::getDescription(IItemDefManager *itemdef) const
252 std::string desc = metadata.getString("description");
254 desc = getDefinition(itemdef).description;
255 return desc.empty() ? name : desc;
259 ItemStack ItemStack::addItem(ItemStack newitem, IItemDefManager *itemdef)
261 // If the item is empty or the position invalid, bail out
264 // nothing can be added trivially
266 // If this is an empty item, it's an easy job.
272 // If item name or metadata differs, bail out
273 else if (name != newitem.name
274 || metadata != newitem.metadata)
278 // If the item fits fully, add counter and delete it
279 else if(newitem.count <= freeSpace(itemdef))
284 // Else the item does not fit fully. Add all that fits and return
288 u16 freespace = freeSpace(itemdef);
290 newitem.remove(freespace);
296 bool ItemStack::itemFits(ItemStack newitem,
298 IItemDefManager *itemdef) const
301 // If the item is empty or the position invalid, bail out
304 // nothing can be added trivially
306 // If this is an empty item, it's an easy job.
311 // If item name or metadata differs, bail out
312 else if (name != newitem.name
313 || metadata != newitem.metadata)
317 // If the item fits fully, delete it
318 else if(newitem.count <= freeSpace(itemdef))
322 // Else the item does not fit fully. Return the rest.
325 u16 freespace = freeSpace(itemdef);
326 newitem.remove(freespace);
332 return newitem.empty();
335 ItemStack ItemStack::takeItem(u32 takecount)
337 if(takecount == 0 || count == 0)
340 ItemStack result = *this;
341 if(takecount >= count)
350 result.count = takecount;
355 ItemStack ItemStack::peekItem(u32 peekcount) const
357 if(peekcount == 0 || count == 0)
360 ItemStack result = *this;
361 if(peekcount < count)
362 result.count = peekcount;
370 InventoryList::InventoryList(const std::string &name, u32 size, IItemDefManager *itemdef):
378 void InventoryList::clearItems()
382 for (u32 i=0; i < m_size; i++) {
383 m_items.emplace_back();
389 void InventoryList::setSize(u32 newsize)
391 if (newsize == m_items.size())
394 m_items.resize(newsize);
399 void InventoryList::setWidth(u32 newwidth)
405 void InventoryList::setName(const std::string &name)
411 void InventoryList::serialize(std::ostream &os, bool incremental) const
413 //os.imbue(std::locale("C"));
415 os<<"Width "<<m_width<<"\n";
417 for (const auto &item : m_items) {
424 // TODO: Implement this:
425 // if (!incremental || item.checkModified())
430 os<<"EndInventoryList\n";
433 void InventoryList::deSerialize(std::istream &is)
435 //is.imbue(std::locale("C"));
443 std::getline(is, line, '\n');
445 std::istringstream iss(line);
446 //iss.imbue(std::locale("C"));
449 std::getline(iss, name, ' ');
451 if (name == "EndInventoryList" || name == "end") {
452 // If partial incremental: Clear leftover items (should not happen!)
453 for (size_t i = item_i; i < m_items.size(); ++i)
458 if (name == "Width") {
461 throw SerializationError("incorrect width property");
463 else if(name == "Item")
465 if(item_i > getSize() - 1)
466 throw SerializationError("too many items");
468 item.deSerialize(iss, m_itemdef);
469 m_items[item_i++] = item;
471 else if(name == "Empty")
473 if(item_i > getSize() - 1)
474 throw SerializationError("too many items");
475 m_items[item_i++].clear();
476 } else if (name == "Keep") {
477 ++item_i; // Unmodified item
481 // Contents given to deSerialize() were not terminated properly: throw error.
483 std::ostringstream ss;
484 ss << "Malformatted inventory list. list="
485 << m_name << ", read " << item_i << " of " << getSize()
486 << " ItemStacks." << std::endl;
487 throw SerializationError(ss.str());
490 InventoryList::InventoryList(const InventoryList &other)
495 InventoryList & InventoryList::operator = (const InventoryList &other)
497 m_items = other.m_items;
498 m_size = other.m_size;
499 m_width = other.m_width;
500 m_name = other.m_name;
501 m_itemdef = other.m_itemdef;
507 bool InventoryList::operator == (const InventoryList &other) const
509 if(m_size != other.m_size)
511 if(m_width != other.m_width)
513 if(m_name != other.m_name)
515 for (u32 i = 0; i < m_items.size(); i++)
516 if (m_items[i] != other.m_items[i])
522 const std::string &InventoryList::getName() const
527 u32 InventoryList::getSize() const
529 return m_items.size();
532 u32 InventoryList::getWidth() const
537 u32 InventoryList::getUsedSlots() const
540 for (const auto &m_item : m_items) {
547 u32 InventoryList::getFreeSlots() const
549 return getSize() - getUsedSlots();
552 const ItemStack& InventoryList::getItem(u32 i) const
554 assert(i < m_size); // Pre-condition
558 ItemStack& InventoryList::getItem(u32 i)
560 assert(i < m_size); // Pre-condition
564 ItemStack InventoryList::changeItem(u32 i, const ItemStack &newitem)
566 if(i >= m_items.size())
569 ItemStack olditem = m_items[i];
570 m_items[i] = newitem;
575 void InventoryList::deleteItem(u32 i)
577 assert(i < m_items.size()); // Pre-condition
582 ItemStack InventoryList::addItem(const ItemStack &newitem_)
584 ItemStack newitem = newitem_;
590 First try to find if it could be added to some existing items
592 for(u32 i=0; i<m_items.size(); i++)
594 // Ignore empty slots
595 if(m_items[i].empty())
598 newitem = addItem(i, newitem);
600 return newitem; // All was eaten
604 Then try to add it to empty slots
606 for(u32 i=0; i<m_items.size(); i++)
608 // Ignore unempty slots
609 if(!m_items[i].empty())
612 newitem = addItem(i, newitem);
614 return newitem; // All was eaten
621 ItemStack InventoryList::addItem(u32 i, const ItemStack &newitem)
623 if(i >= m_items.size())
626 ItemStack leftover = m_items[i].addItem(newitem, m_itemdef);
627 if (leftover != newitem)
632 bool InventoryList::itemFits(const u32 i, const ItemStack &newitem,
633 ItemStack *restitem) const
635 if(i >= m_items.size())
642 return m_items[i].itemFits(newitem, restitem, m_itemdef);
645 bool InventoryList::roomForItem(const ItemStack &item_) const
647 ItemStack item = item_;
649 for(u32 i=0; i<m_items.size(); i++)
651 if(itemFits(i, item, &leftover))
658 bool InventoryList::containsItem(const ItemStack &item, bool match_meta) const
660 u32 count = item.count;
664 for (auto i = m_items.rbegin(); i != m_items.rend(); ++i) {
667 if (i->name == item.name && (!match_meta || (i->metadata == item.metadata))) {
668 if (i->count >= count)
677 ItemStack InventoryList::removeItem(const ItemStack &item)
680 for (auto i = m_items.rbegin(); i != m_items.rend(); ++i) {
681 if (i->name == item.name) {
682 u32 still_to_remove = item.count - removed.count;
683 ItemStack leftover = removed.addItem(i->takeItem(still_to_remove),
685 // Allow oversized stacks
686 removed.count += leftover.count;
688 if (removed.count == item.count)
692 if (!removed.empty())
697 ItemStack InventoryList::takeItem(u32 i, u32 takecount)
699 if(i >= m_items.size())
702 ItemStack taken = m_items[i].takeItem(takecount);
708 void InventoryList::moveItemSomewhere(u32 i, InventoryList *dest, u32 count)
710 // Take item from source list
713 item1 = changeItem(i, ItemStack());
715 item1 = takeItem(i, count);
721 leftover = dest->addItem(item1);
723 if (!leftover.empty()) {
724 // Add the remaining part back to the source item
725 addItem(i, leftover);
729 u32 InventoryList::moveItem(u32 i, InventoryList *dest, u32 dest_i,
730 u32 count, bool swap_if_needed, bool *did_swap)
732 if(this == dest && i == dest_i)
735 // Take item from source list
738 item1 = changeItem(i, ItemStack());
740 item1 = takeItem(i, count);
745 // Try to add the item to destination list
746 u32 oldcount = item1.count;
747 item1 = dest->addItem(dest_i, item1);
749 // If something is returned, the item was not fully added
752 // If olditem is returned, nothing was added.
753 bool nothing_added = (item1.count == oldcount);
755 // If something else is returned, part of the item was left unadded.
756 // Add the other part back to the source item
759 // If olditem is returned, nothing was added.
761 if (nothing_added && swap_if_needed) {
762 // Tell that we swapped
763 if (did_swap != NULL) {
766 // Take item from source list
767 item1 = changeItem(i, ItemStack());
768 // Adding was not possible, swap the items.
769 ItemStack item2 = dest->changeItem(dest_i, item1);
770 // Put item from destination list to the source list
771 changeItem(i, item2);
774 return (oldcount - item1.count);
781 Inventory::~Inventory()
786 void Inventory::clear()
788 for (auto &m_list : m_lists) {
795 Inventory::Inventory(IItemDefManager *itemdef)
801 Inventory::Inventory(const Inventory &other)
806 Inventory & Inventory::operator = (const Inventory &other)
808 // Gracefully handle self assignment
812 m_itemdef = other.m_itemdef;
813 for (InventoryList *list : other.m_lists) {
814 m_lists.push_back(new InventoryList(*list));
821 bool Inventory::operator == (const Inventory &other) const
823 if(m_lists.size() != other.m_lists.size())
826 for(u32 i=0; i<m_lists.size(); i++)
828 if(*m_lists[i] != *other.m_lists[i])
834 void Inventory::serialize(std::ostream &os, bool incremental) const
836 //std::cout << "Serialize " << (int)incremental << ", n=" << m_lists.size() << std::endl;
837 for (const InventoryList *list : m_lists) {
838 if (!incremental || list->checkModified()) {
839 os << "List " << list->getName() << " " << list->getSize() << "\n";
840 list->serialize(os, incremental);
842 os << "KeepList " << list->getName() << "\n";
846 os<<"EndInventory\n";
849 void Inventory::deSerialize(std::istream &is)
851 std::vector<InventoryList *> new_lists;
852 new_lists.reserve(m_lists.size());
856 std::getline(is, line, '\n');
858 std::istringstream iss(line);
861 std::getline(iss, name, ' ');
863 if (name == "EndInventory" || name == "end") {
864 // Remove all lists that were not sent
865 for (auto &list : m_lists) {
866 if (std::find(new_lists.begin(), new_lists.end(), list) != new_lists.end())
873 m_lists.erase(std::remove(m_lists.begin(), m_lists.end(),
874 nullptr), m_lists.end());
878 if (name == "List") {
879 std::string listname;
882 std::getline(iss, listname, ' ');
885 InventoryList *list = getList(listname);
886 bool create_new = !list;
888 list = new InventoryList(listname, listsize, m_itemdef);
890 list->setSize(listsize);
891 list->deSerialize(is);
893 new_lists.push_back(list);
895 m_lists.push_back(list);
897 } else if (name == "KeepList") {
898 // Incrementally sent list
899 std::string listname;
900 std::getline(iss, listname, ' ');
902 InventoryList *list = getList(listname);
904 new_lists.push_back(list);
906 errorstream << "Inventory::deSerialize(): Tried to keep list '" <<
907 listname << "' which is non-existent." << std::endl;
910 // Any additional fields will throw errors when received by a client
911 // older than PROTOCOL_VERSION 38
914 // Contents given to deSerialize() were not terminated properly: throw error.
916 std::ostringstream ss;
917 ss << "Malformatted inventory (damaged?). "
918 << m_lists.size() << " lists read." << std::endl;
919 throw SerializationError(ss.str());
922 InventoryList * Inventory::addList(const std::string &name, u32 size)
925 s32 i = getListIndex(name);
928 if(m_lists[i]->getSize() != size)
931 m_lists[i] = new InventoryList(name, size, m_itemdef);
932 m_lists[i]->setModified();
938 //don't create list with invalid name
939 if (name.find(' ') != std::string::npos)
942 InventoryList *list = new InventoryList(name, size, m_itemdef);
944 m_lists.push_back(list);
948 InventoryList * Inventory::getList(const std::string &name)
950 s32 i = getListIndex(name);
956 std::vector<const InventoryList*> Inventory::getLists()
958 std::vector<const InventoryList*> lists;
959 for (auto list : m_lists) {
960 lists.push_back(list);
965 bool Inventory::deleteList(const std::string &name)
967 s32 i = getListIndex(name);
973 m_lists.erase(m_lists.begin() + i);
977 const InventoryList * Inventory::getList(const std::string &name) const
979 s32 i = getListIndex(name);
985 const s32 Inventory::getListIndex(const std::string &name) const
987 for(u32 i=0; i<m_lists.size(); i++)
989 if(m_lists[i]->getName() == name)