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