]> git.lizzy.rs Git - minetest.git/blob - src/inventory.cpp
ccd55a79f244c30795a7e995a35ec495db1fd893
[minetest.git] / src / inventory.cpp
1 /*
2 Minetest-c55
3 Copyright (C) 2010 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 /*
21 (c) 2010 Perttu Ahola <celeron55@gmail.com>
22 */
23
24 #include "inventory.h"
25 #include "serialization.h"
26 #include "utility.h"
27 #include "debug.h"
28 #include <sstream>
29 #include "main.h"
30
31 /*
32         InventoryItem
33 */
34
35 InventoryItem::InventoryItem(u16 count)
36 {
37         m_count = count;
38 }
39
40 InventoryItem::~InventoryItem()
41 {
42 }
43
44 InventoryItem* InventoryItem::deSerialize(std::istream &is)
45 {
46         DSTACK(__FUNCTION_NAME);
47
48         //is.imbue(std::locale("C"));
49         // Read name
50         std::string name;
51         std::getline(is, name, ' ');
52         
53         if(name == "MaterialItem")
54         {
55                 // u16 reads directly as a number (u8 doesn't)
56                 u16 material;
57                 is>>material;
58                 u16 count;
59                 is>>count;
60                 if(material > 255)
61                         throw SerializationError("Too large material number");
62                 return new MaterialItem(material, count);
63         }
64         else if(name == "MBOItem")
65         {
66                 std::string inventorystring;
67                 std::getline(is, inventorystring, '|');
68                 return new MapBlockObjectItem(inventorystring);
69         }
70         else if(name == "CraftItem")
71         {
72                 std::string subname;
73                 std::getline(is, subname, ' ');
74                 u16 count;
75                 is>>count;
76                 return new CraftItem(subname, count);
77         }
78         else if(name == "ToolItem")
79         {
80                 std::string toolname;
81                 std::getline(is, toolname, ' ');
82                 u16 wear;
83                 is>>wear;
84                 return new ToolItem(toolname, wear);
85         }
86         else
87         {
88                 dstream<<"Unknown InventoryItem name=\""<<name<<"\""<<std::endl;
89                 throw SerializationError("Unknown InventoryItem name");
90         }
91 }
92
93 /*
94         MaterialItem
95 */
96
97 InventoryItem *MaterialItem::createCookResult()
98 {
99         if(m_content == CONTENT_TREE)
100         {
101                 return new CraftItem("lump_of_coal", 1);
102         }
103         return NULL;
104 }
105
106 /*
107         MapBlockObjectItem
108 */
109 #ifndef SERVER
110 video::ITexture * MapBlockObjectItem::getImage()
111 {
112         if(m_inventorystring.substr(0,3) == "Rat")
113                 return g_texturesource->getTextureRaw("rat.png");
114         
115         if(m_inventorystring.substr(0,4) == "Sign")
116                 return g_texturesource->getTextureRaw("sign.png");
117
118         return NULL;
119 }
120 #endif
121 std::string MapBlockObjectItem::getText()
122 {
123         if(m_inventorystring.substr(0,3) == "Rat")
124                 return "";
125         
126         if(m_inventorystring.substr(0,4) == "Sign")
127                 return "";
128
129         return "obj";
130 }
131
132 MapBlockObject * MapBlockObjectItem::createObject
133                 (v3f pos, f32 player_yaw, f32 player_pitch)
134 {
135         std::istringstream is(m_inventorystring);
136         std::string name;
137         std::getline(is, name, ' ');
138         
139         if(name == "None")
140         {
141                 return NULL;
142         }
143         else if(name == "Sign")
144         {
145                 std::string text;
146                 std::getline(is, text, '|');
147                 SignObject *obj = new SignObject(NULL, -1, pos);
148                 obj->setText(text);
149                 obj->setYaw(-player_yaw);
150                 return obj;
151         }
152         else if(name == "Rat")
153         {
154                 RatObject *obj = new RatObject(NULL, -1, pos);
155                 return obj;
156         }
157         else if(name == "ItemObj")
158         {
159                 /*
160                         Now we are an inventory item containing the serialization
161                         string of an object that contains the serialization
162                         string of an inventory item. Fuck this.
163                 */
164                 //assert(0);
165                 dstream<<__FUNCTION_NAME<<": WARNING: Ignoring ItemObj "
166                                 <<"because an item-object should never be inside "
167                                 <<"an object-item."<<std::endl;
168                 return NULL;
169         }
170         else
171         {
172                 return NULL;
173         }
174 }
175
176 /*
177         Inventory
178 */
179
180 InventoryList::InventoryList(std::string name, u32 size)
181 {
182         m_name = name;
183         m_size = size;
184         clearItems();
185 }
186
187 InventoryList::~InventoryList()
188 {
189         for(u32 i=0; i<m_items.size(); i++)
190         {
191                 if(m_items[i])
192                         delete m_items[i];
193         }
194 }
195
196 void InventoryList::clearItems()
197 {
198         for(u32 i=0; i<m_items.size(); i++)
199         {
200                 if(m_items[i])
201                         delete m_items[i];
202         }
203
204         m_items.clear();
205
206         for(u32 i=0; i<m_size; i++)
207         {
208                 m_items.push_back(NULL);
209         }
210 }
211
212 void InventoryList::serialize(std::ostream &os)
213 {
214         //os.imbue(std::locale("C"));
215         
216         for(u32 i=0; i<m_items.size(); i++)
217         {
218                 InventoryItem *item = m_items[i];
219                 if(item != NULL)
220                 {
221                         os<<"Item ";
222                         item->serialize(os);
223                 }
224                 else
225                 {
226                         os<<"Empty";
227                 }
228                 os<<"\n";
229         }
230
231         os<<"EndInventoryList\n";
232 }
233
234 void InventoryList::deSerialize(std::istream &is)
235 {
236         //is.imbue(std::locale("C"));
237
238         clearItems();
239         u32 item_i = 0;
240
241         for(;;)
242         {
243                 std::string line;
244                 std::getline(is, line, '\n');
245
246                 std::istringstream iss(line);
247                 //iss.imbue(std::locale("C"));
248
249                 std::string name;
250                 std::getline(iss, name, ' ');
251
252                 if(name == "EndInventoryList")
253                 {
254                         break;
255                 }
256                 // This is a temporary backwards compatibility fix
257                 else if(name == "end")
258                 {
259                         break;
260                 }
261                 else if(name == "Item")
262                 {
263                         if(item_i > getSize() - 1)
264                                 throw SerializationError("too many items");
265                         InventoryItem *item = InventoryItem::deSerialize(iss);
266                         m_items[item_i++] = item;
267                 }
268                 else if(name == "Empty")
269                 {
270                         if(item_i > getSize() - 1)
271                                 throw SerializationError("too many items");
272                         m_items[item_i++] = NULL;
273                 }
274                 else
275                 {
276                         throw SerializationError("Unknown inventory identifier");
277                 }
278         }
279 }
280
281 InventoryList::InventoryList(const InventoryList &other)
282 {
283         /*
284                 Do this so that the items get cloned. Otherwise the pointers
285                 in the array will just get copied.
286         */
287         *this = other;
288 }
289
290 InventoryList & InventoryList::operator = (const InventoryList &other)
291 {
292         m_name = other.m_name;
293         m_size = other.m_size;
294         clearItems();
295         for(u32 i=0; i<other.m_items.size(); i++)
296         {
297                 InventoryItem *item = other.m_items[i];
298                 if(item != NULL)
299                 {
300                         m_items[i] = item->clone();
301                 }
302         }
303
304         return *this;
305 }
306
307 std::string InventoryList::getName()
308 {
309         return m_name;
310 }
311
312 u32 InventoryList::getSize()
313 {
314         return m_items.size();
315 }
316
317 u32 InventoryList::getUsedSlots()
318 {
319         u32 num = 0;
320         for(u32 i=0; i<m_items.size(); i++)
321         {
322                 InventoryItem *item = m_items[i];
323                 if(item != NULL)
324                         num++;
325         }
326         return num;
327 }
328
329 u32 InventoryList::getFreeSlots()
330 {
331         return getSize() - getUsedSlots();
332 }
333
334 InventoryItem * InventoryList::getItem(u32 i)
335 {
336         if(i > m_items.size() - 1)
337                 return NULL;
338         return m_items[i];
339 }
340
341 InventoryItem * InventoryList::changeItem(u32 i, InventoryItem *newitem)
342 {
343         assert(i < m_items.size());
344
345         InventoryItem *olditem = m_items[i];
346         m_items[i] = newitem;
347         return olditem;
348 }
349
350 void InventoryList::deleteItem(u32 i)
351 {
352         assert(i < m_items.size());
353         InventoryItem *item = changeItem(i, NULL);
354         if(item)
355                 delete item;
356 }
357
358 InventoryItem * InventoryList::addItem(InventoryItem *newitem)
359 {
360         /*
361                 First try to find if it could be added to some existing items
362         */
363         for(u32 i=0; i<m_items.size(); i++)
364         {
365                 // Ignore empty slots
366                 if(m_items[i] == NULL)
367                         continue;
368                 // Try adding
369                 newitem = addItem(i, newitem);
370                 if(newitem == NULL)
371                         return NULL; // All was eaten
372         }
373
374         /*
375                 Then try to add it to empty slots
376         */
377         for(u32 i=0; i<m_items.size(); i++)
378         {
379                 // Ignore unempty slots
380                 if(m_items[i] != NULL)
381                         continue;
382                 // Try adding
383                 newitem = addItem(i, newitem);
384                 if(newitem == NULL)
385                         return NULL; // All was eaten
386         }
387
388         // Return leftover
389         return newitem;
390 }
391
392 InventoryItem * InventoryList::addItem(u32 i, InventoryItem *newitem)
393 {
394         // If it is an empty position, it's an easy job.
395         InventoryItem *to_item = m_items[i];
396         if(to_item == NULL)
397         {
398                 m_items[i] = newitem;
399                 return NULL;
400         }
401         
402         // If not addable, return the item
403         if(newitem->addableTo(to_item) == false)
404                 return newitem;
405         
406         // If the item fits fully in the slot, add counter and delete it
407         if(newitem->getCount() <= to_item->freeSpace())
408         {
409                 to_item->add(newitem->getCount());
410                 delete newitem;
411                 return NULL;
412         }
413         // Else the item does not fit fully. Add all that fits and return
414         // the rest.
415         else
416         {
417                 u16 freespace = to_item->freeSpace();
418                 to_item->add(freespace);
419                 newitem->remove(freespace);
420                 return newitem;
421         }
422 }
423
424 bool InventoryList::itemFits(u32 i, InventoryItem *newitem)
425 {
426         // If it is an empty position, it's an easy job.
427         InventoryItem *to_item = m_items[i];
428         if(to_item == NULL)
429         {
430                 return true;
431         }
432         
433         // If not addable, return the item
434         if(newitem->addableTo(to_item) == false)
435                 return false;
436         
437         // If the item fits fully in the slot, add counter and delete it
438         if(newitem->getCount() <= to_item->freeSpace())
439         {
440                 return true;
441         }
442
443         return false;
444 }
445
446 InventoryItem * InventoryList::takeItem(u32 i, u32 count)
447 {
448         if(count == 0)
449                 return NULL;
450
451         InventoryItem *item = m_items[i];
452         // If it is an empty position, return NULL
453         if(item == NULL)
454                 return NULL;
455         
456         if(count >= item->getCount())
457         {
458                 // Get the item by swapping NULL to its place
459                 return changeItem(i, NULL);
460         }
461         else
462         {
463                 InventoryItem *item2 = item->clone();
464                 item->remove(count);
465                 item2->setCount(count);
466                 return item2;
467         }
468         
469         return false;
470 }
471
472 void InventoryList::decrementMaterials(u16 count)
473 {
474         for(u32 i=0; i<m_items.size(); i++)
475         {
476                 InventoryItem *item = takeItem(i, count);
477                 if(item)
478                         delete item;
479         }
480 }
481
482 void InventoryList::print(std::ostream &o)
483 {
484         o<<"InventoryList:"<<std::endl;
485         for(u32 i=0; i<m_items.size(); i++)
486         {
487                 InventoryItem *item = m_items[i];
488                 if(item != NULL)
489                 {
490                         o<<i<<": ";
491                         item->serialize(o);
492                         o<<"\n";
493                 }
494         }
495 }
496
497 /*
498         Inventory
499 */
500
501 Inventory::~Inventory()
502 {
503         clear();
504 }
505
506 void Inventory::clear()
507 {
508         for(u32 i=0; i<m_lists.size(); i++)
509         {
510                 delete m_lists[i];
511         }
512         m_lists.clear();
513 }
514
515 Inventory::Inventory()
516 {
517 }
518
519 Inventory::Inventory(const Inventory &other)
520 {
521         *this = other;
522 }
523
524 Inventory & Inventory::operator = (const Inventory &other)
525 {
526         clear();
527         for(u32 i=0; i<other.m_lists.size(); i++)
528         {
529                 m_lists.push_back(new InventoryList(*other.m_lists[i]));
530         }
531         return *this;
532 }
533
534 void Inventory::serialize(std::ostream &os)
535 {
536         for(u32 i=0; i<m_lists.size(); i++)
537         {
538                 InventoryList *list = m_lists[i];
539                 os<<"List "<<list->getName()<<" "<<list->getSize()<<"\n";
540                 list->serialize(os);
541         }
542
543         os<<"EndInventory\n";
544 }
545
546 void Inventory::deSerialize(std::istream &is)
547 {
548         clear();
549
550         for(;;)
551         {
552                 std::string line;
553                 std::getline(is, line, '\n');
554
555                 std::istringstream iss(line);
556
557                 std::string name;
558                 std::getline(iss, name, ' ');
559
560                 if(name == "EndInventory")
561                 {
562                         break;
563                 }
564                 // This is a temporary backwards compatibility fix
565                 else if(name == "end")
566                 {
567                         break;
568                 }
569                 else if(name == "List")
570                 {
571                         std::string listname;
572                         u32 listsize;
573
574                         std::getline(iss, listname, ' ');
575                         iss>>listsize;
576
577                         InventoryList *list = new InventoryList(listname, listsize);
578                         list->deSerialize(is);
579
580                         m_lists.push_back(list);
581                 }
582                 else
583                 {
584                         throw SerializationError("Unknown inventory identifier");
585                 }
586         }
587 }
588
589 InventoryList * Inventory::addList(const std::string &name, u32 size)
590 {
591         s32 i = getListIndex(name);
592         if(i != -1)
593         {
594                 if(m_lists[i]->getSize() != size)
595                 {
596                         delete m_lists[i];
597                         m_lists[i] = new InventoryList(name, size);
598                 }
599                 return m_lists[i];
600         }
601         else
602         {
603                 m_lists.push_back(new InventoryList(name, size));
604                 return m_lists.getLast();
605         }
606 }
607
608 InventoryList * Inventory::getList(const std::string &name)
609 {
610         s32 i = getListIndex(name);
611         if(i == -1)
612                 return NULL;
613         return m_lists[i];
614 }
615
616 s32 Inventory::getListIndex(const std::string &name)
617 {
618         for(u32 i=0; i<m_lists.size(); i++)
619         {
620                 if(m_lists[i]->getName() == name)
621                         return i;
622         }
623         return -1;
624 }
625
626 /*
627         InventoryAction
628 */
629
630 InventoryAction * InventoryAction::deSerialize(std::istream &is)
631 {
632         std::string type;
633         std::getline(is, type, ' ');
634
635         InventoryAction *a = NULL;
636
637         if(type == "Move")
638         {
639                 a = new IMoveAction(is);
640         }
641
642         return a;
643 }
644
645 void IMoveAction::apply(InventoryContext *c, InventoryManager *mgr)
646 {
647 #if 1
648
649         /*dstream<<"from_inv="<<from_inv<<" to_inv="<<to_inv<<std::endl;
650         dstream<<"from_list="<<from_list<<" to_list="<<to_list<<std::endl;
651         dstream<<"from_i="<<from_i<<" to_i="<<to_i<<std::endl;*/
652
653         Inventory *inv_from = mgr->getInventory(c, from_inv);
654         Inventory *inv_to = mgr->getInventory(c, to_inv);
655
656         if(!inv_from || !inv_to)
657         {
658                 dstream<<__FUNCTION_NAME<<": Operation not allowed "
659                                 <<"(inventories not found)"<<std::endl;
660                 return;
661         }
662
663         InventoryList *list_from = inv_from->getList(from_list);
664         InventoryList *list_to = inv_to->getList(to_list);
665
666         /*dstream<<"list_from="<<list_from<<" list_to="<<list_to
667                         <<std::endl;*/
668         /*if(list_from)
669                 dstream<<" list_from->getItem(from_i)="<<list_from->getItem(from_i)
670                                 <<std::endl;
671         if(list_to)
672                 dstream<<" list_to->getItem(to_i)="<<list_to->getItem(to_i)
673                                 <<std::endl;*/
674         
675         /*
676                 If a list doesn't exist or the source item doesn't exist
677         */
678         if(!list_from || !list_to)
679         {
680                 dstream<<__FUNCTION_NAME<<": Operation not allowed "
681                                 <<"(a list doesn't exist)"
682                                 <<std::endl;
683                 return;
684         }
685         if(list_from->getItem(from_i) == NULL)
686         {
687                 dstream<<__FUNCTION_NAME<<": Operation not allowed "
688                                 <<"(the source item doesn't exist)"
689                                 <<std::endl;
690                 return;
691         }
692         /*
693                 If the source and the destination slots are the same
694         */
695         if(inv_from == inv_to && list_from == list_to && from_i == to_i)
696         {
697                 dstream<<__FUNCTION_NAME<<": Operation not allowed "
698                                 <<"(source and the destination slots are the same)"<<std::endl;
699                 return;
700         }
701         
702         // Take item from source list
703         InventoryItem *item1 = NULL;
704         if(count == 0)
705                 item1 = list_from->changeItem(from_i, NULL);
706         else
707                 item1 = list_from->takeItem(from_i, count);
708
709         // Try to add the item to destination list
710         InventoryItem *olditem = item1;
711         item1 = list_to->addItem(to_i, item1);
712
713         // If something is returned, the item was not fully added
714         if(item1 != NULL)
715         {
716                 // If olditem is returned, nothing was added.
717                 bool nothing_added = (item1 == olditem);
718                 
719                 // If something else is returned, part of the item was left unadded.
720                 // Add the other part back to the source item
721                 list_from->addItem(from_i, item1);
722
723                 // If olditem is returned, nothing was added.
724                 // Swap the items
725                 if(nothing_added)
726                 {
727                         // Take item from source list
728                         item1 = list_from->changeItem(from_i, NULL);
729                         // Adding was not possible, swap the items.
730                         InventoryItem *item2 = list_to->changeItem(to_i, item1);
731                         // Put item from destination list to the source list
732                         list_from->changeItem(from_i, item2);
733                 }
734         }
735
736         mgr->inventoryModified(c, from_inv);
737         if(from_inv != to_inv)
738                 mgr->inventoryModified(c, to_inv);
739 #endif
740 }
741
742 /*
743         Craft checking system
744 */
745
746 bool ItemSpec::checkItem(InventoryItem *item)
747 {
748         if(type == ITEM_NONE)
749         {
750                 // Has to be no item
751                 if(item != NULL)
752                         return false;
753                 return true;
754         }
755         
756         // There should be an item
757         if(item == NULL)
758                 return false;
759
760         std::string itemname = item->getName();
761
762         if(type == ITEM_MATERIAL)
763         {
764                 if(itemname != "MaterialItem")
765                         return false;
766                 MaterialItem *mitem = (MaterialItem*)item;
767                 if(mitem->getMaterial() != num)
768                         return false;
769         }
770         else if(type == ITEM_CRAFT)
771         {
772                 if(itemname != "CraftItem")
773                         return false;
774                 CraftItem *mitem = (CraftItem*)item;
775                 if(mitem->getSubName() != name)
776                         return false;
777         }
778         else if(type == ITEM_TOOL)
779         {
780                 // Not supported yet
781                 assert(0);
782         }
783         else if(type == ITEM_MBO)
784         {
785                 // Not supported yet
786                 assert(0);
787         }
788         else
789         {
790                 // Not supported yet
791                 assert(0);
792         }
793         return true;
794 }
795
796 bool checkItemCombination(InventoryItem **items, ItemSpec *specs)
797 {
798         u16 items_min_x = 100;
799         u16 items_max_x = 100;
800         u16 items_min_y = 100;
801         u16 items_max_y = 100;
802         for(u16 y=0; y<3; y++)
803         for(u16 x=0; x<3; x++)
804         {
805                 if(items[y*3 + x] == NULL)
806                         continue;
807                 if(items_min_x == 100 || x < items_min_x)
808                         items_min_x = x;
809                 if(items_min_y == 100 || y < items_min_y)
810                         items_min_y = y;
811                 if(items_max_x == 100 || x > items_max_x)
812                         items_max_x = x;
813                 if(items_max_y == 100 || y > items_max_y)
814                         items_max_y = y;
815         }
816         // No items at all, just return false
817         if(items_min_x == 100)
818                 return false;
819         
820         u16 items_w = items_max_x - items_min_x + 1;
821         u16 items_h = items_max_y - items_min_y + 1;
822
823         u16 specs_min_x = 100;
824         u16 specs_max_x = 100;
825         u16 specs_min_y = 100;
826         u16 specs_max_y = 100;
827         for(u16 y=0; y<3; y++)
828         for(u16 x=0; x<3; x++)
829         {
830                 if(specs[y*3 + x].type == ITEM_NONE)
831                         continue;
832                 if(specs_min_x == 100 || x < specs_min_x)
833                         specs_min_x = x;
834                 if(specs_min_y == 100 || y < specs_min_y)
835                         specs_min_y = y;
836                 if(specs_max_x == 100 || x > specs_max_x)
837                         specs_max_x = x;
838                 if(specs_max_y == 100 || y > specs_max_y)
839                         specs_max_y = y;
840         }
841         // No specs at all, just return false
842         if(specs_min_x == 100)
843                 return false;
844
845         u16 specs_w = specs_max_x - specs_min_x + 1;
846         u16 specs_h = specs_max_y - specs_min_y + 1;
847
848         // Different sizes
849         if(items_w != specs_w || items_h != specs_h)
850                 return false;
851
852         for(u16 y=0; y<specs_h; y++)
853         for(u16 x=0; x<specs_w; x++)
854         {
855                 u16 items_x = items_min_x + x;
856                 u16 items_y = items_min_y + y;
857                 u16 specs_x = specs_min_x + x;
858                 u16 specs_y = specs_min_y + y;
859                 InventoryItem *item = items[items_y * 3 + items_x];
860                 ItemSpec &spec = specs[specs_y * 3 + specs_x];
861
862                 if(spec.checkItem(item) == false)
863                         return false;
864         }
865
866         return true;
867 }
868         
869 //END