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"
26 #include "util/strfnd.h"
27 #include "content_mapnode.h" // For loading legacy MaterialItems
28 #include "nameidmapping.h" // For loading legacy MaterialItems
29 #include "util/serialize.h"
30 #include "util/string.h"
36 static content_t content_translate_from_19_to_internal(content_t c_from)
38 for(u32 i=0; i<sizeof(trans_table_19)/sizeof(trans_table_19[0]); i++)
40 if(trans_table_19[i][1] == c_from)
42 return trans_table_19[i][0];
48 ItemStack::ItemStack(const std::string &name_, u16 count_,
49 u16 wear_, IItemDefManager *itemdef)
51 name = itemdef->getAlias(name_);
55 if (name.empty() || count == 0)
57 else if (itemdef->get(name).type == ITEM_TOOL)
61 void ItemStack::serialize(std::ostream &os) const
63 DSTACK(FUNCTION_NAME);
68 // Check how many parts of the itemstring are needed
74 if (!metadata.empty())
77 os<<serializeJsonStringIfNeeded(name);
84 metadata.serialize(os);
88 void ItemStack::deSerialize(std::istream &is, IItemDefManager *itemdef)
90 DSTACK(FUNCTION_NAME);
95 name = deSerializeJsonStringIfNeeded(is);
99 std::getline(is, tmp, ' ');
101 throw SerializationError("Unexpected text after item name");
103 if(name == "MaterialItem")
105 // Obsoleted on 2011-07-30
111 // Convert old materials
113 material = content_translate_from_19_to_internal(material);
115 throw SerializationError("Too large material number");
116 // Convert old id to name
117 NameIdMapping legacy_nimap;
118 content_mapnode_get_name_id_mapping(&legacy_nimap);
119 legacy_nimap.getName(material, name);
121 name = "unknown_block";
123 name = itemdef->getAlias(name);
124 count = materialcount;
126 else if(name == "MaterialItem2")
128 // Obsoleted on 2011-11-16
135 throw SerializationError("Too large material number");
136 // Convert old id to name
137 NameIdMapping legacy_nimap;
138 content_mapnode_get_name_id_mapping(&legacy_nimap);
139 legacy_nimap.getName(material, name);
141 name = "unknown_block";
143 name = itemdef->getAlias(name);
144 count = materialcount;
146 else if(name == "node" || name == "NodeItem" || name == "MaterialItem3"
147 || name == "craft" || name == "CraftItem")
149 // Obsoleted on 2012-01-07
152 std::getline(is, all, '\n');
153 // First attempt to read inside ""
156 // If didn't skip to end, we have ""s
158 name = fnd.next("\"");
159 } else { // No luck, just read a word then
161 name = fnd.next(" ");
165 name = itemdef->getAlias(name);
166 count = stoi(trim(fnd.next("")));
170 else if(name == "MBOItem")
172 // Obsoleted on 2011-10-14
173 throw SerializationError("MBOItem not supported anymore");
175 else if(name == "tool" || name == "ToolItem")
177 // Obsoleted on 2012-01-07
180 std::getline(is, all, '\n');
181 // First attempt to read inside ""
184 // If didn't skip to end, we have ""s
186 name = fnd.next("\"");
187 } else { // No luck, just read a word then
189 name = fnd.next(" ");
195 name = itemdef->getAlias(name);
196 wear = stoi(trim(fnd.next("")));
200 do // This loop is just to allow "break;"
204 // Apply item aliases
206 name = itemdef->getAlias(name);
209 std::string count_str;
210 std::getline(is, count_str, ' ');
211 if(count_str.empty())
217 count = stoi(count_str);
220 std::string wear_str;
221 std::getline(is, wear_str, ' ');
225 wear = stoi(wear_str);
228 metadata.deSerialize(is);
230 // In case fields are added after metadata, skip space here:
231 //std::getline(is, tmp, ' ');
233 // throw SerializationError("Unexpected text after metadata");
238 if (name.empty() || count == 0)
240 else if (itemdef && itemdef->get(name).type == ITEM_TOOL)
244 void ItemStack::deSerialize(const std::string &str, IItemDefManager *itemdef)
246 std::istringstream is(str, std::ios::binary);
247 deSerialize(is, itemdef);
250 std::string ItemStack::getItemString() const
252 std::ostringstream os(std::ios::binary);
258 ItemStack ItemStack::addItem(const ItemStack &newitem_,
259 IItemDefManager *itemdef)
261 ItemStack newitem = newitem_;
263 // If the item is empty or the position invalid, bail out
266 // nothing can be added trivially
268 // If this is an empty item, it's an easy job.
274 // If item name or metadata differs, bail out
275 else if (name != newitem.name
276 || metadata != newitem.metadata)
280 // If the item fits fully, add counter and delete it
281 else if(newitem.count <= freeSpace(itemdef))
286 // Else the item does not fit fully. Add all that fits and return
290 u16 freespace = freeSpace(itemdef);
292 newitem.remove(freespace);
298 bool ItemStack::itemFits(const ItemStack &newitem_,
300 IItemDefManager *itemdef) const
302 ItemStack newitem = newitem_;
304 // If the item is empty or the position invalid, bail out
307 // nothing can be added trivially
309 // If this is an empty item, it's an easy job.
314 // If item name or metadata differs, bail out
315 else if (name != newitem.name
316 || metadata != newitem.metadata)
320 // If the item fits fully, delete it
321 else if(newitem.count <= freeSpace(itemdef))
325 // Else the item does not fit fully. Return the rest.
329 u16 freespace = freeSpace(itemdef);
330 newitem.remove(freespace);
335 return newitem.empty();
338 ItemStack ItemStack::takeItem(u32 takecount)
340 if(takecount == 0 || count == 0)
343 ItemStack result = *this;
344 if(takecount >= count)
353 result.count = takecount;
358 ItemStack ItemStack::peekItem(u32 peekcount) const
360 if(peekcount == 0 || count == 0)
363 ItemStack result = *this;
364 if(peekcount < count)
365 result.count = peekcount;
373 InventoryList::InventoryList(std::string name, u32 size, IItemDefManager *itemdef)
383 InventoryList::~InventoryList()
387 void InventoryList::clearItems()
391 for(u32 i=0; i<m_size; i++)
393 m_items.push_back(ItemStack());
399 void InventoryList::setSize(u32 newsize)
401 if(newsize != m_items.size())
402 m_items.resize(newsize);
406 void InventoryList::setWidth(u32 newwidth)
411 void InventoryList::setName(const std::string &name)
416 void InventoryList::serialize(std::ostream &os) const
418 //os.imbue(std::locale("C"));
420 os<<"Width "<<m_width<<"\n";
422 for(u32 i=0; i<m_items.size(); i++)
424 const ItemStack &item = m_items[i];
437 os<<"EndInventoryList\n";
440 void InventoryList::deSerialize(std::istream &is)
442 //is.imbue(std::locale("C"));
451 std::getline(is, line, '\n');
453 std::istringstream iss(line);
454 //iss.imbue(std::locale("C"));
457 std::getline(iss, name, ' ');
459 if(name == "EndInventoryList")
463 // This is a temporary backwards compatibility fix
464 else if(name == "end")
468 else if(name == "Width")
472 throw SerializationError("incorrect width property");
474 else if(name == "Item")
476 if(item_i > getSize() - 1)
477 throw SerializationError("too many items");
479 item.deSerialize(iss, m_itemdef);
480 m_items[item_i++] = item;
482 else if(name == "Empty")
484 if(item_i > getSize() - 1)
485 throw SerializationError("too many items");
486 m_items[item_i++].clear();
491 InventoryList::InventoryList(const InventoryList &other)
496 InventoryList & InventoryList::operator = (const InventoryList &other)
498 m_items = other.m_items;
499 m_size = other.m_size;
500 m_width = other.m_width;
501 m_name = other.m_name;
502 m_itemdef = other.m_itemdef;
508 bool InventoryList::operator == (const InventoryList &other) const
510 if(m_size != other.m_size)
512 if(m_width != other.m_width)
514 if(m_name != other.m_name)
516 for(u32 i=0; i<m_items.size(); i++)
518 ItemStack s1 = m_items[i];
519 ItemStack s2 = other.m_items[i];
520 if(s1.name != s2.name || s1.wear!= s2.wear || s1.count != s2.count ||
521 s1.metadata != s2.metadata)
528 const std::string &InventoryList::getName() const
533 u32 InventoryList::getSize() const
535 return m_items.size();
538 u32 InventoryList::getWidth() const
543 u32 InventoryList::getUsedSlots() const
546 for(u32 i=0; i<m_items.size(); i++)
548 if(!m_items[i].empty())
554 u32 InventoryList::getFreeSlots() const
556 return getSize() - getUsedSlots();
559 const ItemStack& InventoryList::getItem(u32 i) const
561 assert(i < m_size); // Pre-condition
565 ItemStack& InventoryList::getItem(u32 i)
567 assert(i < m_size); // Pre-condition
571 ItemStack InventoryList::changeItem(u32 i, const ItemStack &newitem)
573 if(i >= m_items.size())
576 ItemStack olditem = m_items[i];
577 m_items[i] = newitem;
582 void InventoryList::deleteItem(u32 i)
584 assert(i < m_items.size()); // Pre-condition
588 ItemStack InventoryList::addItem(const ItemStack &newitem_)
590 ItemStack newitem = newitem_;
596 First try to find if it could be added to some existing items
598 for(u32 i=0; i<m_items.size(); i++)
600 // Ignore empty slots
601 if(m_items[i].empty())
604 newitem = addItem(i, newitem);
606 return newitem; // All was eaten
610 Then try to add it to empty slots
612 for(u32 i=0; i<m_items.size(); i++)
614 // Ignore unempty slots
615 if(!m_items[i].empty())
618 newitem = addItem(i, newitem);
620 return newitem; // All was eaten
627 ItemStack InventoryList::addItem(u32 i, const ItemStack &newitem)
629 if(i >= m_items.size())
632 ItemStack leftover = m_items[i].addItem(newitem, m_itemdef);
633 //if(leftover != newitem)
638 bool InventoryList::itemFits(const u32 i, const ItemStack &newitem,
639 ItemStack *restitem) const
641 if(i >= m_items.size())
648 return m_items[i].itemFits(newitem, restitem, m_itemdef);
651 bool InventoryList::roomForItem(const ItemStack &item_) const
653 ItemStack item = item_;
655 for(u32 i=0; i<m_items.size(); i++)
657 if(itemFits(i, item, &leftover))
664 bool InventoryList::containsItem(const ItemStack &item) const
666 u32 count = item.count;
669 for(std::vector<ItemStack>::const_reverse_iterator
670 i = m_items.rbegin();
671 i != m_items.rend(); ++i)
675 if(i->name == item.name)
677 if(i->count >= count)
686 ItemStack InventoryList::removeItem(const ItemStack &item)
689 for(std::vector<ItemStack>::reverse_iterator
690 i = m_items.rbegin();
691 i != m_items.rend(); ++i)
693 if(i->name == item.name)
695 u32 still_to_remove = item.count - removed.count;
696 removed.addItem(i->takeItem(still_to_remove), m_itemdef);
697 if(removed.count == item.count)
704 ItemStack InventoryList::takeItem(u32 i, u32 takecount)
706 if(i >= m_items.size())
709 ItemStack taken = m_items[i].takeItem(takecount);
715 ItemStack InventoryList::peekItem(u32 i, u32 peekcount) const
717 if(i >= m_items.size())
720 return m_items[i].peekItem(peekcount);
723 void InventoryList::moveItemSomewhere(u32 i, InventoryList *dest, u32 count)
725 // Take item from source list
728 item1 = changeItem(i, ItemStack());
730 item1 = takeItem(i, count);
735 // Try to add the item to destination list
736 u32 dest_size = dest->getSize();
737 // First try all the non-empty slots
738 for (u32 dest_i = 0; dest_i < dest_size; dest_i++) {
739 if (!m_items[dest_i].empty()) {
740 item1 = dest->addItem(dest_i, item1);
741 if (item1.empty()) return;
745 // Then try all the empty ones
746 for (u32 dest_i = 0; dest_i < dest_size; dest_i++) {
747 if (m_items[dest_i].empty()) {
748 item1 = dest->addItem(dest_i, item1);
749 if (item1.empty()) return;
753 // If we reach this, the item was not fully added
754 // Add the remaining part back to the source item
758 u32 InventoryList::moveItem(u32 i, InventoryList *dest, u32 dest_i,
759 u32 count, bool swap_if_needed, bool *did_swap)
761 if(this == dest && i == dest_i)
764 // Take item from source list
767 item1 = changeItem(i, ItemStack());
769 item1 = takeItem(i, count);
774 // Try to add the item to destination list
775 u32 oldcount = item1.count;
776 item1 = dest->addItem(dest_i, item1);
778 // If something is returned, the item was not fully added
781 // If olditem is returned, nothing was added.
782 bool nothing_added = (item1.count == oldcount);
784 // If something else is returned, part of the item was left unadded.
785 // Add the other part back to the source item
788 // If olditem is returned, nothing was added.
790 if (nothing_added && swap_if_needed) {
791 // Tell that we swapped
792 if (did_swap != NULL) {
795 // Take item from source list
796 item1 = changeItem(i, ItemStack());
797 // Adding was not possible, swap the items.
798 ItemStack item2 = dest->changeItem(dest_i, item1);
799 // Put item from destination list to the source list
800 changeItem(i, item2);
803 return (oldcount - item1.count);
810 Inventory::~Inventory()
815 void Inventory::clear()
818 for(u32 i=0; i<m_lists.size(); i++)
825 void Inventory::clearContents()
828 for(u32 i=0; i<m_lists.size(); i++)
830 InventoryList *list = m_lists[i];
831 for(u32 j=0; j<list->getSize(); j++)
838 Inventory::Inventory(IItemDefManager *itemdef)
844 Inventory::Inventory(const Inventory &other)
850 Inventory & Inventory::operator = (const Inventory &other)
852 // Gracefully handle self assignment
857 m_itemdef = other.m_itemdef;
858 for(u32 i=0; i<other.m_lists.size(); i++)
860 m_lists.push_back(new InventoryList(*other.m_lists[i]));
866 bool Inventory::operator == (const Inventory &other) const
868 if(m_lists.size() != other.m_lists.size())
871 for(u32 i=0; i<m_lists.size(); i++)
873 if(*m_lists[i] != *other.m_lists[i])
879 void Inventory::serialize(std::ostream &os) const
881 for(u32 i=0; i<m_lists.size(); i++)
883 InventoryList *list = m_lists[i];
884 os<<"List "<<list->getName()<<" "<<list->getSize()<<"\n";
888 os<<"EndInventory\n";
891 void Inventory::deSerialize(std::istream &is)
898 std::getline(is, line, '\n');
900 std::istringstream iss(line);
903 std::getline(iss, name, ' ');
905 if(name == "EndInventory")
909 // This is a temporary backwards compatibility fix
910 else if(name == "end")
914 else if(name == "List")
916 std::string listname;
919 std::getline(iss, listname, ' ');
922 InventoryList *list = new InventoryList(listname, listsize, m_itemdef);
923 list->deSerialize(is);
925 m_lists.push_back(list);
929 throw SerializationError("invalid inventory specifier: " + name);
934 InventoryList * Inventory::addList(const std::string &name, u32 size)
937 s32 i = getListIndex(name);
940 if(m_lists[i]->getSize() != size)
943 m_lists[i] = new InventoryList(name, size, m_itemdef);
949 //don't create list with invalid name
950 if (name.find(" ") != std::string::npos) return NULL;
952 InventoryList *list = new InventoryList(name, size, m_itemdef);
953 m_lists.push_back(list);
958 InventoryList * Inventory::getList(const std::string &name)
960 s32 i = getListIndex(name);
966 std::vector<const InventoryList*> Inventory::getLists()
968 std::vector<const InventoryList*> lists;
969 for(u32 i=0; i<m_lists.size(); i++)
971 InventoryList *list = m_lists[i];
972 lists.push_back(list);
977 bool Inventory::deleteList(const std::string &name)
979 s32 i = getListIndex(name);
984 m_lists.erase(m_lists.begin() + i);
988 const InventoryList * Inventory::getList(const std::string &name) const
990 s32 i = getListIndex(name);
996 const s32 Inventory::getListIndex(const std::string &name) const
998 for(u32 i=0; i<m_lists.size(); i++)
1000 if(m_lists[i]->getName() == name)