]> git.lizzy.rs Git - minetest.git/blob - src/inventory.cpp
Clean up serialization
[minetest.git] / src / inventory.cpp
1 /*
2 Minetest
3 Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
4
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.
9
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.
14
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.
18 */
19
20 #include "inventory.h"
21 #include "serialization.h"
22 #include "debug.h"
23 #include <algorithm>
24 #include <sstream>
25 #include "log.h"
26 #include "itemdef.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"
32
33 /*
34         ItemStack
35 */
36
37 static content_t content_translate_from_19_to_internal(content_t c_from)
38 {
39         for (const auto &tt : trans_table_19) {
40                 if(tt[1] == c_from) {
41                         return tt[0];
42                 }
43         }
44         return c_from;
45 }
46
47 ItemStack::ItemStack(const std::string &name_, u16 count_,
48                 u16 wear_, IItemDefManager *itemdef) :
49         name(itemdef->getAlias(name_)),
50         count(count_),
51         wear(wear_)
52 {
53         if (name.empty() || count == 0)
54                 clear();
55         else if (itemdef->get(name).type == ITEM_TOOL)
56                 count = 1;
57 }
58
59 void ItemStack::serialize(std::ostream &os, bool serialize_meta) const
60 {
61         if (empty())
62                 return;
63
64         // Check how many parts of the itemstring are needed
65         int parts = 1;
66         if (!metadata.empty())
67                 parts = 4;
68         else if (wear != 0)
69                 parts = 3;
70         else if (count != 1)
71                 parts = 2;
72
73         os << serializeJsonStringIfNeeded(name);
74         if (parts >= 2)
75                 os << " " << count;
76         if (parts >= 3)
77                 os << " " << wear;
78         if (parts >= 4) {
79                 os << " ";
80                 if (serialize_meta)
81                         metadata.serialize(os);
82                 else
83                         os << "<metadata size=" << metadata.size() << ">";
84         }
85 }
86
87 void ItemStack::deSerialize(std::istream &is, IItemDefManager *itemdef)
88 {
89         clear();
90
91         // Read name
92         name = deSerializeJsonStringIfNeeded(is);
93
94         // Skip space
95         std::string tmp;
96         std::getline(is, tmp, ' ');
97         if(!tmp.empty())
98                 throw SerializationError("Unexpected text after item name");
99
100         if(name == "MaterialItem")
101         {
102                 // Obsoleted on 2011-07-30
103
104                 u16 material;
105                 is>>material;
106                 u16 materialcount;
107                 is>>materialcount;
108                 // Convert old materials
109                 if(material <= 0xff)
110                         material = content_translate_from_19_to_internal(material);
111                 if(material > 0xfff)
112                         throw SerializationError("Too large material number");
113                 // Convert old id to name
114                 NameIdMapping legacy_nimap;
115                 content_mapnode_get_name_id_mapping(&legacy_nimap);
116                 legacy_nimap.getName(material, name);
117                 if(name.empty())
118                         name = "unknown_block";
119                 if (itemdef)
120                         name = itemdef->getAlias(name);
121                 count = materialcount;
122         }
123         else if(name == "MaterialItem2")
124         {
125                 // Obsoleted on 2011-11-16
126
127                 u16 material;
128                 is>>material;
129                 u16 materialcount;
130                 is>>materialcount;
131                 if(material > 0xfff)
132                         throw SerializationError("Too large material number");
133                 // Convert old id to name
134                 NameIdMapping legacy_nimap;
135                 content_mapnode_get_name_id_mapping(&legacy_nimap);
136                 legacy_nimap.getName(material, name);
137                 if(name.empty())
138                         name = "unknown_block";
139                 if (itemdef)
140                         name = itemdef->getAlias(name);
141                 count = materialcount;
142         }
143         else if(name == "node" || name == "NodeItem" || name == "MaterialItem3"
144                         || name == "craft" || name == "CraftItem")
145         {
146                 // Obsoleted on 2012-01-07
147
148                 std::string all;
149                 std::getline(is, all, '\n');
150                 // First attempt to read inside ""
151                 Strfnd fnd(all);
152                 fnd.next("\"");
153                 // If didn't skip to end, we have ""s
154                 if(!fnd.at_end()){
155                         name = fnd.next("\"");
156                 } else { // No luck, just read a word then
157                         fnd.start(all);
158                         name = fnd.next(" ");
159                 }
160                 fnd.skip_over(" ");
161                 if (itemdef)
162                         name = itemdef->getAlias(name);
163                 count = stoi(trim(fnd.next("")));
164                 if(count == 0)
165                         count = 1;
166         }
167         else if(name == "MBOItem")
168         {
169                 // Obsoleted on 2011-10-14
170                 throw SerializationError("MBOItem not supported anymore");
171         }
172         else if(name == "tool" || name == "ToolItem")
173         {
174                 // Obsoleted on 2012-01-07
175
176                 std::string all;
177                 std::getline(is, all, '\n');
178                 // First attempt to read inside ""
179                 Strfnd fnd(all);
180                 fnd.next("\"");
181                 // If didn't skip to end, we have ""s
182                 if(!fnd.at_end()){
183                         name = fnd.next("\"");
184                 } else { // No luck, just read a word then
185                         fnd.start(all);
186                         name = fnd.next(" ");
187                 }
188                 count = 1;
189                 // Then read wear
190                 fnd.skip_over(" ");
191                 if (itemdef)
192                         name = itemdef->getAlias(name);
193                 wear = stoi(trim(fnd.next("")));
194         }
195         else
196         {
197                 do  // This loop is just to allow "break;"
198                 {
199                         // The real thing
200
201                         // Apply item aliases
202                         if (itemdef)
203                                 name = itemdef->getAlias(name);
204
205                         // Read the count
206                         std::string count_str;
207                         std::getline(is, count_str, ' ');
208                         if (count_str.empty()) {
209                                 count = 1;
210                                 break;
211                         }
212
213                         count = stoi(count_str);
214
215                         // Read the wear
216                         std::string wear_str;
217                         std::getline(is, wear_str, ' ');
218                         if(wear_str.empty())
219                                 break;
220
221                         wear = stoi(wear_str);
222
223                         // Read metadata
224                         metadata.deSerialize(is);
225
226                         // In case fields are added after metadata, skip space here:
227                         //std::getline(is, tmp, ' ');
228                         //if(!tmp.empty())
229                         //      throw SerializationError("Unexpected text after metadata");
230
231                 } while(false);
232         }
233
234         if (name.empty() || count == 0)
235                 clear();
236         else if (itemdef && itemdef->get(name).type == ITEM_TOOL)
237                 count = 1;
238 }
239
240 void ItemStack::deSerialize(const std::string &str, IItemDefManager *itemdef)
241 {
242         std::istringstream is(str, std::ios::binary);
243         deSerialize(is, itemdef);
244 }
245
246 std::string ItemStack::getItemString(bool include_meta) const
247 {
248         std::ostringstream os(std::ios::binary);
249         serialize(os, include_meta);
250         return os.str();
251 }
252
253 std::string ItemStack::getDescription(IItemDefManager *itemdef) const
254 {
255         std::string desc = metadata.getString("description");
256         if (desc.empty())
257                 desc = getDefinition(itemdef).description;
258         return desc.empty() ? name : desc;
259 }
260
261
262 ItemStack ItemStack::addItem(ItemStack newitem, IItemDefManager *itemdef)
263 {
264         // If the item is empty or the position invalid, bail out
265         if(newitem.empty())
266         {
267                 // nothing can be added trivially
268         }
269         // If this is an empty item, it's an easy job.
270         else if(empty())
271         {
272                 *this = newitem;
273                 newitem.clear();
274         }
275         // If item name or metadata differs, bail out
276         else if (name != newitem.name
277                 || metadata != newitem.metadata)
278         {
279                 // cannot be added
280         }
281         // If the item fits fully, add counter and delete it
282         else if(newitem.count <= freeSpace(itemdef))
283         {
284                 add(newitem.count);
285                 newitem.clear();
286         }
287         // Else the item does not fit fully. Add all that fits and return
288         // the rest.
289         else
290         {
291                 u16 freespace = freeSpace(itemdef);
292                 add(freespace);
293                 newitem.remove(freespace);
294         }
295
296         return newitem;
297 }
298
299 bool ItemStack::itemFits(ItemStack newitem,
300                 ItemStack *restitem,
301                 IItemDefManager *itemdef) const
302 {
303
304         // If the item is empty or the position invalid, bail out
305         if(newitem.empty())
306         {
307                 // nothing can be added trivially
308         }
309         // If this is an empty item, it's an easy job.
310         else if(empty())
311         {
312                 newitem.clear();
313         }
314         // If item name or metadata differs, bail out
315         else if (name != newitem.name
316                 || metadata != newitem.metadata)
317         {
318                 // cannot be added
319         }
320         // If the item fits fully, delete it
321         else if(newitem.count <= freeSpace(itemdef))
322         {
323                 newitem.clear();
324         }
325         // Else the item does not fit fully. Return the rest.
326         else
327         {
328                 u16 freespace = freeSpace(itemdef);
329                 newitem.remove(freespace);
330         }
331
332         if(restitem)
333                 *restitem = newitem;
334
335         return newitem.empty();
336 }
337
338 ItemStack ItemStack::takeItem(u32 takecount)
339 {
340         if(takecount == 0 || count == 0)
341                 return ItemStack();
342
343         ItemStack result = *this;
344         if(takecount >= count)
345         {
346                 // Take all
347                 clear();
348         }
349         else
350         {
351                 // Take part
352                 remove(takecount);
353                 result.count = takecount;
354         }
355         return result;
356 }
357
358 ItemStack ItemStack::peekItem(u32 peekcount) const
359 {
360         if(peekcount == 0 || count == 0)
361                 return ItemStack();
362
363         ItemStack result = *this;
364         if(peekcount < count)
365                 result.count = peekcount;
366         return result;
367 }
368
369 /*
370         Inventory
371 */
372
373 InventoryList::InventoryList(const std::string &name, u32 size, IItemDefManager *itemdef):
374         m_name(name),
375         m_size(size),
376         m_itemdef(itemdef)
377 {
378         clearItems();
379 }
380
381 void InventoryList::clearItems()
382 {
383         m_items.clear();
384
385         for (u32 i=0; i < m_size; i++) {
386                 m_items.emplace_back();
387         }
388
389         setModified();
390 }
391
392 void InventoryList::setSize(u32 newsize)
393 {
394         if (newsize == m_items.size())
395                 return;
396
397         m_items.resize(newsize);
398         m_size = newsize;
399         setModified();
400 }
401
402 void InventoryList::setWidth(u32 newwidth)
403 {
404         m_width = newwidth;
405         setModified();
406 }
407
408 void InventoryList::setName(const std::string &name)
409 {
410         m_name = name;
411         setModified();
412 }
413
414 void InventoryList::serialize(std::ostream &os, bool incremental) const
415 {
416         //os.imbue(std::locale("C"));
417
418         os<<"Width "<<m_width<<"\n";
419
420         for (const auto &item : m_items) {
421                 if (item.empty()) {
422                         os<<"Empty";
423                 } else {
424                         os<<"Item ";
425                         item.serialize(os);
426                 }
427                 // TODO: Implement this:
428                 // if (!incremental || item.checkModified())
429                 // os << "Keep";
430                 os<<"\n";
431         }
432
433         os<<"EndInventoryList\n";
434 }
435
436 void InventoryList::deSerialize(std::istream &is)
437 {
438         //is.imbue(std::locale("C"));
439         setModified();
440
441         u32 item_i = 0;
442         m_width = 0;
443
444         while (is.good()) {
445                 std::string line;
446                 std::getline(is, line, '\n');
447
448                 std::istringstream iss(line);
449                 //iss.imbue(std::locale("C"));
450
451                 std::string name;
452                 std::getline(iss, name, ' ');
453
454                 if (name == "EndInventoryList" || name == "end") {
455                         // If partial incremental: Clear leftover items (should not happen!)
456                         for (size_t i = item_i; i < m_items.size(); ++i)
457                                 m_items[i].clear();
458                         return;
459                 }
460
461                 if (name == "Width") {
462                         iss >> m_width;
463                         if (iss.fail())
464                                 throw SerializationError("incorrect width property");
465                 }
466                 else if(name == "Item")
467                 {
468                         if(item_i > getSize() - 1)
469                                 throw SerializationError("too many items");
470                         ItemStack item;
471                         item.deSerialize(iss, m_itemdef);
472                         m_items[item_i++] = item;
473                 }
474                 else if(name == "Empty")
475                 {
476                         if(item_i > getSize() - 1)
477                                 throw SerializationError("too many items");
478                         m_items[item_i++].clear();
479                 } else if (name == "Keep") {
480                         ++item_i; // Unmodified item
481                 }
482         }
483
484         // Contents given to deSerialize() were not terminated properly: throw error.
485
486         std::ostringstream ss;
487         ss << "Malformatted inventory list. list="
488                 << m_name << ", read " << item_i << " of " << getSize()
489                 << " ItemStacks." << std::endl;
490         throw SerializationError(ss.str());
491 }
492
493 InventoryList::InventoryList(const InventoryList &other)
494 {
495         *this = other;
496 }
497
498 InventoryList & InventoryList::operator = (const InventoryList &other)
499 {
500         m_items = other.m_items;
501         m_size = other.m_size;
502         m_width = other.m_width;
503         m_name = other.m_name;
504         m_itemdef = other.m_itemdef;
505         //setDirty(true);
506
507         return *this;
508 }
509
510 bool InventoryList::operator == (const InventoryList &other) const
511 {
512         if(m_size != other.m_size)
513                 return false;
514         if(m_width != other.m_width)
515                 return false;
516         if(m_name != other.m_name)
517                 return false;
518         for (u32 i = 0; i < m_items.size(); i++)
519                 if (m_items[i] != other.m_items[i])
520                         return false;
521
522         return true;
523 }
524
525 const std::string &InventoryList::getName() const
526 {
527         return m_name;
528 }
529
530 u32 InventoryList::getSize() const
531 {
532         return m_items.size();
533 }
534
535 u32 InventoryList::getWidth() const
536 {
537         return m_width;
538 }
539
540 u32 InventoryList::getUsedSlots() const
541 {
542         u32 num = 0;
543         for (const auto &m_item : m_items) {
544                 if (!m_item.empty())
545                         num++;
546         }
547         return num;
548 }
549
550 u32 InventoryList::getFreeSlots() const
551 {
552         return getSize() - getUsedSlots();
553 }
554
555 const ItemStack& InventoryList::getItem(u32 i) const
556 {
557         assert(i < m_size); // Pre-condition
558         return m_items[i];
559 }
560
561 ItemStack& InventoryList::getItem(u32 i)
562 {
563         assert(i < m_size); // Pre-condition
564         return m_items[i];
565 }
566
567 ItemStack InventoryList::changeItem(u32 i, const ItemStack &newitem)
568 {
569         if(i >= m_items.size())
570                 return newitem;
571
572         ItemStack olditem = m_items[i];
573         m_items[i] = newitem;
574         setModified();
575         return olditem;
576 }
577
578 void InventoryList::deleteItem(u32 i)
579 {
580         assert(i < m_items.size()); // Pre-condition
581         m_items[i].clear();
582         setModified();
583 }
584
585 ItemStack InventoryList::addItem(const ItemStack &newitem_)
586 {
587         ItemStack newitem = newitem_;
588
589         if(newitem.empty())
590                 return newitem;
591
592         /*
593                 First try to find if it could be added to some existing items
594         */
595         for(u32 i=0; i<m_items.size(); i++)
596         {
597                 // Ignore empty slots
598                 if(m_items[i].empty())
599                         continue;
600                 // Try adding
601                 newitem = addItem(i, newitem);
602                 if(newitem.empty())
603                         return newitem; // All was eaten
604         }
605
606         /*
607                 Then try to add it to empty slots
608         */
609         for(u32 i=0; i<m_items.size(); i++)
610         {
611                 // Ignore unempty slots
612                 if(!m_items[i].empty())
613                         continue;
614                 // Try adding
615                 newitem = addItem(i, newitem);
616                 if(newitem.empty())
617                         return newitem; // All was eaten
618         }
619
620         // Return leftover
621         return newitem;
622 }
623
624 ItemStack InventoryList::addItem(u32 i, const ItemStack &newitem)
625 {
626         if(i >= m_items.size())
627                 return newitem;
628
629         ItemStack leftover = m_items[i].addItem(newitem, m_itemdef);
630         if (leftover != newitem)
631                 setModified();
632         return leftover;
633 }
634
635 bool InventoryList::itemFits(const u32 i, const ItemStack &newitem,
636                 ItemStack *restitem) const
637 {
638         if(i >= m_items.size())
639         {
640                 if(restitem)
641                         *restitem = newitem;
642                 return false;
643         }
644
645         return m_items[i].itemFits(newitem, restitem, m_itemdef);
646 }
647
648 bool InventoryList::roomForItem(const ItemStack &item_) const
649 {
650         ItemStack item = item_;
651         ItemStack leftover;
652         for(u32 i=0; i<m_items.size(); i++)
653         {
654                 if(itemFits(i, item, &leftover))
655                         return true;
656                 item = leftover;
657         }
658         return false;
659 }
660
661 bool InventoryList::containsItem(const ItemStack &item, bool match_meta) const
662 {
663         u32 count = item.count;
664         if (count == 0)
665                 return true;
666
667         for (auto i = m_items.rbegin(); i != m_items.rend(); ++i) {
668                 if (count == 0)
669                         break;
670                 if (i->name == item.name && (!match_meta || (i->metadata == item.metadata))) {
671                         if (i->count >= count)
672                                 return true;
673
674                         count -= i->count;
675                 }
676         }
677         return false;
678 }
679
680 ItemStack InventoryList::removeItem(const ItemStack &item)
681 {
682         ItemStack removed;
683         for (auto i = m_items.rbegin(); i != m_items.rend(); ++i) {
684                 if (i->name == item.name) {
685                         u32 still_to_remove = item.count - removed.count;
686                         ItemStack leftover = removed.addItem(i->takeItem(still_to_remove),
687                                         m_itemdef);
688                         // Allow oversized stacks
689                         removed.count += leftover.count;
690
691                         if (removed.count == item.count)
692                                 break;
693                 }
694         }
695         if (!removed.empty())
696                 setModified();
697         return removed;
698 }
699
700 ItemStack InventoryList::takeItem(u32 i, u32 takecount)
701 {
702         if(i >= m_items.size())
703                 return ItemStack();
704
705         ItemStack taken = m_items[i].takeItem(takecount);
706         if (!taken.empty())
707                 setModified();
708         return taken;
709 }
710
711 void InventoryList::moveItemSomewhere(u32 i, InventoryList *dest, u32 count)
712 {
713         // Take item from source list
714         ItemStack item1;
715         if (count == 0)
716                 item1 = changeItem(i, ItemStack());
717         else
718                 item1 = takeItem(i, count);
719
720         if (item1.empty())
721                 return;
722
723         ItemStack leftover;
724         leftover = dest->addItem(item1);
725
726         if (!leftover.empty()) {
727                 // Add the remaining part back to the source item
728                 addItem(i, leftover);
729         }
730 }
731
732 u32 InventoryList::moveItem(u32 i, InventoryList *dest, u32 dest_i,
733                 u32 count, bool swap_if_needed, bool *did_swap)
734 {
735         if (this == dest && i == dest_i)
736                 return count;
737
738         // Take item from source list
739         ItemStack item1;
740         if (count == 0)
741                 item1 = changeItem(i, ItemStack());
742         else
743                 item1 = takeItem(i, count);
744
745         if (item1.empty())
746                 return 0;
747
748         // Try to add the item to destination list
749         u32 oldcount = item1.count;
750         item1 = dest->addItem(dest_i, item1);
751
752         // If something is returned, the item was not fully added
753         if (!item1.empty()) {
754                 // If olditem is returned, nothing was added.
755                 bool nothing_added = (item1.count == oldcount);
756
757                 // If something else is returned, part of the item was left unadded.
758                 // Add the other part back to the source item
759                 addItem(i, item1);
760
761                 // If olditem is returned, nothing was added.
762                 // Swap the items
763                 if (nothing_added && swap_if_needed) {
764                         // Tell that we swapped
765                         if (did_swap != NULL) {
766                                 *did_swap = true;
767                         }
768                         // Take item from source list
769                         item1 = changeItem(i, ItemStack());
770                         // Adding was not possible, swap the items.
771                         ItemStack item2 = dest->changeItem(dest_i, item1);
772                         // Put item from destination list to the source list
773                         changeItem(i, item2);
774                 }
775         }
776         return (oldcount - item1.count);
777 }
778
779 /*
780         Inventory
781 */
782
783 Inventory::~Inventory()
784 {
785         clear();
786 }
787
788 void Inventory::clear()
789 {
790         for (auto &m_list : m_lists) {
791                 delete m_list;
792         }
793         m_lists.clear();
794         setModified();
795 }
796
797 Inventory::Inventory(IItemDefManager *itemdef)
798 {
799         m_itemdef = itemdef;
800         setModified();
801 }
802
803 Inventory::Inventory(const Inventory &other)
804 {
805         *this = other;
806 }
807
808 Inventory & Inventory::operator = (const Inventory &other)
809 {
810         // Gracefully handle self assignment
811         if(this != &other)
812         {
813                 clear();
814                 m_itemdef = other.m_itemdef;
815                 for (InventoryList *list : other.m_lists) {
816                         m_lists.push_back(new InventoryList(*list));
817                 }
818                 setModified();
819         }
820         return *this;
821 }
822
823 bool Inventory::operator == (const Inventory &other) const
824 {
825         if(m_lists.size() != other.m_lists.size())
826                 return false;
827
828         for(u32 i=0; i<m_lists.size(); i++)
829         {
830                 if(*m_lists[i] != *other.m_lists[i])
831                         return false;
832         }
833         return true;
834 }
835
836 void Inventory::serialize(std::ostream &os, bool incremental) const
837 {
838         //std::cout << "Serialize " << (int)incremental << ", n=" << m_lists.size() << std::endl;
839         for (const InventoryList *list : m_lists) {
840                 if (!incremental || list->checkModified()) {
841                         os << "List " << list->getName() << " " << list->getSize() << "\n";
842                         list->serialize(os, incremental);
843                 } else {
844                         os << "KeepList " << list->getName() << "\n";
845                 }
846         }
847
848         os<<"EndInventory\n";
849 }
850
851 void Inventory::deSerialize(std::istream &is)
852 {
853         std::vector<InventoryList *> new_lists;
854         new_lists.reserve(m_lists.size());
855
856         while (is.good()) {
857                 std::string line;
858                 std::getline(is, line, '\n');
859
860                 std::istringstream iss(line);
861
862                 std::string name;
863                 std::getline(iss, name, ' ');
864
865                 if (name == "EndInventory" || name == "end") {
866                         // Remove all lists that were not sent
867                         for (auto &list : m_lists) {
868                                 if (std::find(new_lists.begin(), new_lists.end(), list) != new_lists.end())
869                                         continue;
870
871                                 delete list;
872                                 list = nullptr;
873                                 setModified();
874                         }
875                         m_lists.erase(std::remove(m_lists.begin(), m_lists.end(),
876                                         nullptr), m_lists.end());
877                         return;
878                 }
879
880                 if (name == "List") {
881                         std::string listname;
882                         u32 listsize;
883
884                         std::getline(iss, listname, ' ');
885                         iss>>listsize;
886
887                         InventoryList *list = getList(listname);
888                         bool create_new = !list;
889                         if (create_new)
890                                 list = new InventoryList(listname, listsize, m_itemdef);
891                         else
892                                 list->setSize(listsize);
893                         list->deSerialize(is);
894
895                         new_lists.push_back(list);
896                         if (create_new)
897                                 m_lists.push_back(list);
898
899                 } else if (name == "KeepList") {
900                         // Incrementally sent list
901                         std::string listname;
902                         std::getline(iss, listname, ' ');
903
904                         InventoryList *list = getList(listname);
905                         if (list) {
906                                 new_lists.push_back(list);
907                         } else {
908                                 errorstream << "Inventory::deSerialize(): Tried to keep list '" <<
909                                         listname << "' which is non-existent." << std::endl;
910                         }
911                 }
912                 // Any additional fields will throw errors when received by a client
913                 // older than PROTOCOL_VERSION 38
914         }
915
916         // Contents given to deSerialize() were not terminated properly: throw error.
917
918         std::ostringstream ss;
919         ss << "Malformatted inventory (damaged?). "
920                 << m_lists.size() << " lists read." << std::endl;
921         throw SerializationError(ss.str());
922 }
923
924 InventoryList * Inventory::addList(const std::string &name, u32 size)
925 {
926         setModified();
927         s32 i = getListIndex(name);
928         if(i != -1)
929         {
930                 if(m_lists[i]->getSize() != size)
931                 {
932                         delete m_lists[i];
933                         m_lists[i] = new InventoryList(name, size, m_itemdef);
934                         m_lists[i]->setModified();
935                 }
936                 return m_lists[i];
937         }
938
939
940         //don't create list with invalid name
941         if (name.find(' ') != std::string::npos)
942                 return nullptr;
943
944         InventoryList *list = new InventoryList(name, size, m_itemdef);
945         list->setModified();
946         m_lists.push_back(list);
947         return list;
948 }
949
950 InventoryList * Inventory::getList(const std::string &name)
951 {
952         s32 i = getListIndex(name);
953         if(i == -1)
954                 return NULL;
955         return m_lists[i];
956 }
957
958 std::vector<const InventoryList*> Inventory::getLists()
959 {
960         std::vector<const InventoryList*> lists;
961         for (auto list : m_lists) {
962                 lists.push_back(list);
963         }
964         return lists;
965 }
966
967 bool Inventory::deleteList(const std::string &name)
968 {
969         s32 i = getListIndex(name);
970         if(i == -1)
971                 return false;
972
973         setModified();
974         delete m_lists[i];
975         m_lists.erase(m_lists.begin() + i);
976         return true;
977 }
978
979 const InventoryList * Inventory::getList(const std::string &name) const
980 {
981         s32 i = getListIndex(name);
982         if(i == -1)
983                 return NULL;
984         return m_lists[i];
985 }
986
987 const s32 Inventory::getListIndex(const std::string &name) const
988 {
989         for(u32 i=0; i<m_lists.size(); i++)
990         {
991                 if(m_lists[i]->getName() == name)
992                         return i;
993         }
994         return -1;
995 }
996
997 //END