Fix conditional bug in inventory
[oweals/minetest.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 "main.h" // For tsrc, g_toolmanager
26 #include "serverobject.h"
27 #include "content_mapnode.h"
28 #include "content_sao.h"
29 #include "environment.h"
30 #include "mapblock.h"
31 #include "player.h"
32 #include "log.h"
33 #include "nodedef.h"
34 #include "tooldef.h"
35 #include "craftitemdef.h"
36 #include "gamedef.h"
37 #include "scriptapi.h"
38 #include "strfnd.h"
39
40 /*
41         InventoryItem
42 */
43
44 InventoryItem::InventoryItem(IGameDef *gamedef, u16 count):
45         m_gamedef(gamedef),
46         m_count(count)
47 {
48         assert(m_gamedef);
49 }
50
51 InventoryItem::~InventoryItem()
52 {
53 }
54
55 content_t content_translate_from_19_to_internal(content_t c_from)
56 {
57         for(u32 i=0; i<sizeof(trans_table_19)/sizeof(trans_table_19[0]); i++)
58         {
59                 if(trans_table_19[i][1] == c_from)
60                 {
61                         return trans_table_19[i][0];
62                 }
63         }
64         return c_from;
65 }
66
67 InventoryItem* InventoryItem::deSerialize(std::istream &is, IGameDef *gamedef)
68 {
69         DSTACK(__FUNCTION_NAME);
70
71         //is.imbue(std::locale("C"));
72         // Read name
73         std::string name;
74         std::getline(is, name, ' ');
75         
76         if(name == "MaterialItem")
77         {
78                 // u16 reads directly as a number (u8 doesn't)
79                 u16 material;
80                 is>>material;
81                 u16 count;
82                 is>>count;
83                 // Convert old materials
84                 if(material <= 0xff)
85                         material = content_translate_from_19_to_internal(material);
86                 if(material > MAX_CONTENT)
87                         throw SerializationError("Too large material number");
88                 return new MaterialItem(gamedef, material, count);
89         }
90         else if(name == "MaterialItem2")
91         {
92                 u16 material;
93                 is>>material;
94                 u16 count;
95                 is>>count;
96                 if(material > MAX_CONTENT)
97                         throw SerializationError("Too large material number");
98                 return new MaterialItem(gamedef, material, count);
99         }
100         else if(name == "NodeItem" || name == "MaterialItem3")
101         {
102                 std::string all;
103                 std::getline(is, all, '\n');
104                 std::string nodename;
105                 // First attempt to read inside ""
106                 Strfnd fnd(all);
107                 fnd.next("\"");
108                 // If didn't skip to end, we have ""s
109                 if(!fnd.atend()){
110                         nodename = fnd.next("\"");
111                 } else { // No luck, just read a word then
112                         fnd.start(all);
113                         nodename = fnd.next(" ");
114                 }
115                 fnd.skip_over(" ");
116                 u16 count = stoi(trim(fnd.next("")));
117                 if(count == 0)
118                         count = 1;
119                 return new MaterialItem(gamedef, nodename, count);
120         }
121         else if(name == "MBOItem")
122         {
123                 std::string inventorystring;
124                 std::getline(is, inventorystring, '|');
125                 throw SerializationError("MBOItem not supported anymore");
126         }
127         else if(name == "CraftItem")
128         {
129                 std::string all;
130                 std::getline(is, all, '\n');
131                 std::string subname;
132                 // First attempt to read inside ""
133                 Strfnd fnd(all);
134                 fnd.next("\"");
135                 // If didn't skip to end, we have ""s
136                 if(!fnd.atend()){
137                         subname = fnd.next("\"");
138                 } else { // No luck, just read a word then
139                         fnd.start(all);
140                         subname = fnd.next(" ");
141                 }
142                 // Then read count
143                 fnd.skip_over(" ");
144                 u16 count = stoi(trim(fnd.next("")));
145                 if(count == 0)
146                         count = 1;
147                 return new CraftItem(gamedef, subname, count);
148         }
149         else if(name == "ToolItem")
150         {
151                 std::string all;
152                 std::getline(is, all, '\n');
153                 std::string toolname;
154                 // First attempt to read inside ""
155                 Strfnd fnd(all);
156                 fnd.next("\"");
157                 // If didn't skip to end, we have ""s
158                 if(!fnd.atend()){
159                         toolname = fnd.next("\"");
160                 } else { // No luck, just read a word then
161                         fnd.start(all);
162                         toolname = fnd.next(" ");
163                 }
164                 // Then read wear
165                 fnd.skip_over(" ");
166                 u16 wear = stoi(trim(fnd.next("")));
167                 return new ToolItem(gamedef, toolname, wear);
168         }
169         else
170         {
171                 infostream<<"Unknown InventoryItem name=\""<<name<<"\""<<std::endl;
172                 throw SerializationError("Unknown InventoryItem name");
173         }
174 }
175
176 InventoryItem* InventoryItem::deSerialize(const std::string &str,
177                 IGameDef *gamedef)
178 {
179         std::istringstream is(str, std::ios_base::binary);
180         return deSerialize(is, gamedef);
181 }
182
183 std::string InventoryItem::getItemString() {
184         // Get item string
185         std::ostringstream os(std::ios_base::binary);
186         serialize(os);
187         return os.str();
188 }
189
190 bool InventoryItem::dropOrPlace(ServerEnvironment *env,
191                 ServerActiveObject *dropper,
192                 v3f pos, bool place, s16 count)
193 {
194         /*
195                 Ensure that the block is loaded so that the item
196                 can properly be added to the static list too
197         */
198         v3s16 blockpos = getNodeBlockPos(floatToInt(pos, BS));
199         MapBlock *block = env->getMap().emergeBlock(blockpos, false);
200         if(block==NULL)
201         {
202                 infostream<<"InventoryItem::dropOrPlace(): FAIL: block not found: "
203                                 <<blockpos.X<<","<<blockpos.Y<<","<<blockpos.Z
204                                 <<std::endl;
205                 return false;
206         }
207
208         /*
209                 Take specified number of items,
210                 but limit to getDropCount().
211         */
212         s16 dropcount = getDropCount();
213         if(count < 0 || count > dropcount)
214                 count = dropcount;
215         if(count < 0 || count > getCount())
216                 count = getCount();
217         if(count > 0)
218         {
219                 /*
220                         Create an ItemSAO
221                 */
222                 pos.Y -= BS*0.25; // let it drop a bit
223                 // Randomize a bit
224                 //pos.X += BS*0.2*(float)myrand_range(-1000,1000)/1000.0;
225                 //pos.Z += BS*0.2*(float)myrand_range(-1000,1000)/1000.0;
226                 // Create object
227                 ServerActiveObject *obj = new ItemSAO(env, pos, getItemString());
228                 // Add the object to the environment
229                 env->addActiveObject(obj);
230                 infostream<<"Dropped item"<<std::endl;
231
232                 setCount(getCount() - count);
233         }
234
235         return getCount() < 1; // delete the item?
236 }
237
238 /*
239         MaterialItem
240 */
241
242 MaterialItem::MaterialItem(IGameDef *gamedef, std::string nodename, u16 count):
243         InventoryItem(gamedef, count)
244 {
245         if(nodename == "")
246                 nodename = "unknown_block";
247         m_nodename = nodename;
248 }
249 // Legacy constructor
250 MaterialItem::MaterialItem(IGameDef *gamedef, content_t content, u16 count):
251         InventoryItem(gamedef, count)
252 {
253         INodeDefManager *ndef = m_gamedef->ndef();
254         std::string nodename = ndef->get(content).name;
255         if(nodename == "")
256                 nodename = "unknown_block";
257         m_nodename = nodename;
258 }
259
260 #ifndef SERVER
261 video::ITexture * MaterialItem::getImage() const
262 {
263         return m_gamedef->getNodeDefManager()->get(m_nodename).inventory_texture;
264 }
265 #endif
266
267 bool MaterialItem::isCookable() const
268 {
269         INodeDefManager *ndef = m_gamedef->ndef();
270         const ContentFeatures &f = ndef->get(m_nodename);
271         return (f.cookresult_item != "");
272 }
273
274 InventoryItem *MaterialItem::createCookResult() const
275 {
276         INodeDefManager *ndef = m_gamedef->ndef();
277         const ContentFeatures &f = ndef->get(m_nodename);
278         std::istringstream is(f.cookresult_item, std::ios::binary);
279         return InventoryItem::deSerialize(is, m_gamedef);
280 }
281
282 float MaterialItem::getCookTime() const
283 {
284         INodeDefManager *ndef = m_gamedef->ndef();
285         const ContentFeatures &f = ndef->get(m_nodename);
286         return f.furnace_cooktime;
287 }
288
289 float MaterialItem::getBurnTime() const
290 {
291         INodeDefManager *ndef = m_gamedef->ndef();
292         const ContentFeatures &f = ndef->get(m_nodename);
293         return f.furnace_burntime;
294 }
295
296 content_t MaterialItem::getMaterial() const
297 {
298         INodeDefManager *ndef = m_gamedef->ndef();
299         content_t id = CONTENT_IGNORE;
300         ndef->getId(m_nodename, id);
301         return id;
302 }
303
304 /*
305         ToolItem
306 */
307
308 std::string ToolItem::getImageBasename() const
309 {
310         return m_gamedef->getToolDefManager()->getImagename(m_toolname);
311 }
312
313 #ifndef SERVER
314 video::ITexture * ToolItem::getImage() const
315 {
316         ITextureSource *tsrc = m_gamedef->tsrc();
317
318         std::string basename = getImageBasename();
319         
320         /*
321                 Calculate a progress value with sane amount of
322                 maximum states
323         */
324         u32 maxprogress = 30;
325         u32 toolprogress = (65535-m_wear)/(65535/maxprogress);
326         
327         float value_f = (float)toolprogress / (float)maxprogress;
328         std::ostringstream os;
329         os<<basename<<"^[progressbar"<<value_f;
330
331         return tsrc->getTextureRaw(os.str());
332 }
333
334 video::ITexture * ToolItem::getImageRaw() const
335 {
336         ITextureSource *tsrc = m_gamedef->tsrc();
337         
338         return tsrc->getTextureRaw(getImageBasename());
339 }
340 #endif
341
342 /*
343         CraftItem
344 */
345
346 #ifndef SERVER
347 video::ITexture * CraftItem::getImage() const
348 {
349         ICraftItemDefManager *cidef = m_gamedef->cidef();
350         ITextureSource *tsrc = m_gamedef->tsrc();
351         std::string imagename = cidef->getImagename(m_subname);
352         return tsrc->getTextureRaw(imagename);
353 }
354 #endif
355
356 u16 CraftItem::getStackMax() const
357 {
358         ICraftItemDefManager *cidef = m_gamedef->cidef();
359         const CraftItemDefinition *def = cidef->getCraftItemDefinition(m_subname);
360         if(def == NULL)
361                 return InventoryItem::getStackMax();
362         return def->stack_max;
363 }
364
365 bool CraftItem::isUsable() const
366 {
367         ICraftItemDefManager *cidef = m_gamedef->cidef();
368         const CraftItemDefinition *def = cidef->getCraftItemDefinition(m_subname);
369         return def != NULL && def->usable;
370 }
371
372 bool CraftItem::isCookable() const
373 {
374         ICraftItemDefManager *cidef = m_gamedef->cidef();
375         const CraftItemDefinition *def = cidef->getCraftItemDefinition(m_subname);
376         return def != NULL && def->cookresult_item != "";
377 }
378
379 InventoryItem *CraftItem::createCookResult() const
380 {
381         ICraftItemDefManager *cidef = m_gamedef->cidef();
382         const CraftItemDefinition *def = cidef->getCraftItemDefinition(m_subname);
383         if(def == NULL)
384                 return InventoryItem::createCookResult();
385         std::istringstream is(def->cookresult_item, std::ios::binary);
386         return InventoryItem::deSerialize(is, m_gamedef);
387 }
388
389 float CraftItem::getCookTime() const
390 {
391         ICraftItemDefManager *cidef = m_gamedef->cidef();
392         const CraftItemDefinition *def = cidef->getCraftItemDefinition(m_subname);
393         if (def == NULL)
394                 return InventoryItem::getCookTime();
395         return def->furnace_cooktime;
396 }
397
398 float CraftItem::getBurnTime() const
399 {
400         ICraftItemDefManager *cidef = m_gamedef->cidef();
401         const CraftItemDefinition *def = cidef->getCraftItemDefinition(m_subname);
402         if (def == NULL)
403                 return InventoryItem::getBurnTime();
404         return def->furnace_burntime;
405 }
406
407 s16 CraftItem::getDropCount() const
408 {
409         // Special cases
410         ICraftItemDefManager *cidef = m_gamedef->cidef();
411         const CraftItemDefinition *def = cidef->getCraftItemDefinition(m_subname);
412         if(def != NULL && def->dropcount >= 0)
413                 return def->dropcount;
414         // Default
415         return InventoryItem::getDropCount();
416 }
417
418 bool CraftItem::areLiquidsPointable() const
419 {
420         ICraftItemDefManager *cidef = m_gamedef->cidef();
421         const CraftItemDefinition *def = cidef->getCraftItemDefinition(m_subname);
422         return def != NULL && def->liquids_pointable;
423 }
424
425 bool CraftItem::dropOrPlace(ServerEnvironment *env,
426                 ServerActiveObject *dropper,
427                 v3f pos, bool place, s16 count)
428 {
429         if(count == 0)
430                 return false;
431
432         bool callback_exists = false;
433         bool result = false;
434
435         if(place)
436         {
437                 result = scriptapi_craftitem_on_place_on_ground(
438                                 env->getLua(),
439                                 m_subname.c_str(), dropper, pos,
440                                 callback_exists);
441         }
442
443         // note: on_drop is fallback for on_place_on_ground
444
445         if(!callback_exists)
446         {
447                 result = scriptapi_craftitem_on_drop(
448                                 env->getLua(),
449                                 m_subname.c_str(), dropper, pos,
450                                 callback_exists);
451         }
452
453         if(callback_exists)
454         {
455                 // If the callback returned true, drop one item
456                 if(result)
457                         setCount(getCount() - 1);
458                 return getCount() < 1;
459         }
460         else
461         {
462                 // If neither on_place_on_ground (if place==true)
463                 // nor on_drop exists, call the base implementation
464                 return InventoryItem::dropOrPlace(env, dropper, pos, place, count);
465         }
466 }
467
468 bool CraftItem::use(ServerEnvironment *env,
469                 ServerActiveObject *user,
470                 const PointedThing& pointed)
471 {
472         bool callback_exists = false;
473         bool result = false;
474
475         result = scriptapi_craftitem_on_use(
476                         env->getLua(),
477                         m_subname.c_str(), user, pointed,
478                         callback_exists);
479
480         if(callback_exists)
481         {
482                 // If the callback returned true, drop one item
483                 if(result)
484                         setCount(getCount() - 1);
485                 return getCount() < 1;
486         }
487         else
488         {
489                 // If neither on_place_on_ground (if place==true)
490                 // nor on_drop exists, call the base implementation
491                 return InventoryItem::use(env, user, pointed);
492         }
493 }
494
495 /*
496         Inventory
497 */
498
499 InventoryList::InventoryList(std::string name, u32 size)
500 {
501         m_name = name;
502         m_size = size;
503         clearItems();
504         //m_dirty = false;
505 }
506
507 InventoryList::~InventoryList()
508 {
509         for(u32 i=0; i<m_items.size(); i++)
510         {
511                 if(m_items[i])
512                         delete m_items[i];
513         }
514 }
515
516 void InventoryList::clearItems()
517 {
518         for(u32 i=0; i<m_items.size(); i++)
519         {
520                 if(m_items[i])
521                         delete m_items[i];
522         }
523
524         m_items.clear();
525
526         for(u32 i=0; i<m_size; i++)
527         {
528                 m_items.push_back(NULL);
529         }
530
531         //setDirty(true);
532 }
533
534 void InventoryList::serialize(std::ostream &os) const
535 {
536         //os.imbue(std::locale("C"));
537         
538         for(u32 i=0; i<m_items.size(); i++)
539         {
540                 InventoryItem *item = m_items[i];
541                 if(item != NULL)
542                 {
543                         os<<"Item ";
544                         item->serialize(os);
545                 }
546                 else
547                 {
548                         os<<"Empty";
549                 }
550                 os<<"\n";
551         }
552
553         os<<"EndInventoryList\n";
554 }
555
556 void InventoryList::deSerialize(std::istream &is, IGameDef *gamedef)
557 {
558         //is.imbue(std::locale("C"));
559
560         clearItems();
561         u32 item_i = 0;
562
563         for(;;)
564         {
565                 std::string line;
566                 std::getline(is, line, '\n');
567
568                 std::istringstream iss(line);
569                 //iss.imbue(std::locale("C"));
570
571                 std::string name;
572                 std::getline(iss, name, ' ');
573
574                 if(name == "EndInventoryList")
575                 {
576                         break;
577                 }
578                 // This is a temporary backwards compatibility fix
579                 else if(name == "end")
580                 {
581                         break;
582                 }
583                 else if(name == "Item")
584                 {
585                         if(item_i > getSize() - 1)
586                                 throw SerializationError("too many items");
587                         InventoryItem *item = InventoryItem::deSerialize(iss, gamedef);
588                         m_items[item_i++] = item;
589                 }
590                 else if(name == "Empty")
591                 {
592                         if(item_i > getSize() - 1)
593                                 throw SerializationError("too many items");
594                         m_items[item_i++] = NULL;
595                 }
596                 else
597                 {
598                         throw SerializationError("Unknown inventory identifier");
599                 }
600         }
601 }
602
603 InventoryList::InventoryList(const InventoryList &other)
604 {
605         /*
606                 Do this so that the items get cloned. Otherwise the pointers
607                 in the array will just get copied.
608         */
609         *this = other;
610 }
611
612 InventoryList & InventoryList::operator = (const InventoryList &other)
613 {
614         m_name = other.m_name;
615         m_size = other.m_size;
616         clearItems();
617         for(u32 i=0; i<other.m_items.size(); i++)
618         {
619                 InventoryItem *item = other.m_items[i];
620                 if(item != NULL)
621                 {
622                         m_items[i] = item->clone();
623                 }
624         }
625         //setDirty(true);
626
627         return *this;
628 }
629
630 const std::string &InventoryList::getName() const
631 {
632         return m_name;
633 }
634
635 u32 InventoryList::getSize()
636 {
637         return m_items.size();
638 }
639
640 u32 InventoryList::getUsedSlots()
641 {
642         u32 num = 0;
643         for(u32 i=0; i<m_items.size(); i++)
644         {
645                 InventoryItem *item = m_items[i];
646                 if(item != NULL)
647                         num++;
648         }
649         return num;
650 }
651
652 u32 InventoryList::getFreeSlots()
653 {
654         return getSize() - getUsedSlots();
655 }
656
657 const InventoryItem * InventoryList::getItem(u32 i) const
658 {
659         if(i >= m_items.size())
660                 return NULL;
661         return m_items[i];
662 }
663
664 InventoryItem * InventoryList::getItem(u32 i)
665 {
666         if(i >= m_items.size())
667                 return NULL;
668         return m_items[i];
669 }
670
671 InventoryItem * InventoryList::changeItem(u32 i, InventoryItem *newitem)
672 {
673         if(i >= m_items.size())
674                 return newitem;
675
676         InventoryItem *olditem = m_items[i];
677         m_items[i] = newitem;
678         //setDirty(true);
679         return olditem;
680 }
681
682 void InventoryList::deleteItem(u32 i)
683 {
684         assert(i < m_items.size());
685         InventoryItem *item = changeItem(i, NULL);
686         if(item)
687                 delete item;
688 }
689
690 InventoryItem * InventoryList::addItem(InventoryItem *newitem)
691 {
692         if(newitem == NULL)
693                 return NULL;
694         
695         /*
696                 First try to find if it could be added to some existing items
697         */
698         for(u32 i=0; i<m_items.size(); i++)
699         {
700                 // Ignore empty slots
701                 if(m_items[i] == NULL)
702                         continue;
703                 // Try adding
704                 newitem = addItem(i, newitem);
705                 if(newitem == NULL)
706                         return NULL; // All was eaten
707         }
708
709         /*
710                 Then try to add it to empty slots
711         */
712         for(u32 i=0; i<m_items.size(); i++)
713         {
714                 // Ignore unempty slots
715                 if(m_items[i] != NULL)
716                         continue;
717                 // Try adding
718                 newitem = addItem(i, newitem);
719                 if(newitem == NULL)
720                         return NULL; // All was eaten
721         }
722
723         // Return leftover
724         return newitem;
725 }
726
727 InventoryItem * InventoryList::addItem(u32 i, InventoryItem *newitem)
728 {
729         if(newitem == NULL)
730                 return NULL;
731         if(i >= m_items.size())
732                 return newitem;
733         
734         //setDirty(true);
735         
736         // If it is an empty position, it's an easy job.
737         InventoryItem *to_item = getItem(i);
738         if(to_item == NULL)
739         {
740                 m_items[i] = newitem;
741                 return NULL;
742         }
743         
744         // If not addable, return the item
745         if(newitem->addableTo(to_item) == false)
746                 return newitem;
747         
748         // If the item fits fully in the slot, add counter and delete it
749         if(newitem->getCount() <= to_item->freeSpace())
750         {
751                 to_item->add(newitem->getCount());
752                 delete newitem;
753                 return NULL;
754         }
755         // Else the item does not fit fully. Add all that fits and return
756         // the rest.
757         else
758         {
759                 u16 freespace = to_item->freeSpace();
760                 to_item->add(freespace);
761                 newitem->remove(freespace);
762                 return newitem;
763         }
764 }
765
766 bool InventoryList::itemFits(const u32 i, const InventoryItem *newitem)
767 {
768         // If it is an empty position, it's an easy job.
769         const InventoryItem *to_item = getItem(i);
770         if(to_item == NULL)
771         {
772                 return true;
773         }
774         
775         // If not addable, fail
776         if(newitem->addableTo(to_item) == false)
777                 return false;
778         
779         // If the item fits fully in the slot, pass
780         if(newitem->getCount() <= to_item->freeSpace())
781         {
782                 return true;
783         }
784
785         return false;
786 }
787
788 bool InventoryList::roomForItem(const InventoryItem *item)
789 {
790         for(u32 i=0; i<m_items.size(); i++)
791                 if(itemFits(i, item))
792                         return true;
793         return false;
794 }
795
796 bool InventoryList::roomForCookedItem(const InventoryItem *item)
797 {
798         if(!item)
799                 return false;
800         const InventoryItem *cook = item->createCookResult();
801         if(!cook)
802                 return false;
803         bool room = roomForItem(cook);
804         delete cook;
805         return room;
806 }
807
808 InventoryItem * InventoryList::takeItem(u32 i, u32 count)
809 {
810         if(count == 0)
811                 return NULL;
812         
813         //setDirty(true);
814
815         InventoryItem *item = getItem(i);
816         // If it is an empty position, return NULL
817         if(item == NULL)
818                 return NULL;
819         
820         if(count >= item->getCount())
821         {
822                 // Get the item by swapping NULL to its place
823                 return changeItem(i, NULL);
824         }
825         else
826         {
827                 InventoryItem *item2 = item->clone();
828                 item->remove(count);
829                 item2->setCount(count);
830                 return item2;
831         }
832         
833         return false;
834 }
835
836 void InventoryList::decrementMaterials(u16 count)
837 {
838         for(u32 i=0; i<m_items.size(); i++)
839         {
840                 InventoryItem *item = takeItem(i, count);
841                 if(item)
842                         delete item;
843         }
844 }
845
846 void InventoryList::print(std::ostream &o)
847 {
848         o<<"InventoryList:"<<std::endl;
849         for(u32 i=0; i<m_items.size(); i++)
850         {
851                 InventoryItem *item = m_items[i];
852                 if(item != NULL)
853                 {
854                         o<<i<<": ";
855                         item->serialize(o);
856                         o<<"\n";
857                 }
858         }
859 }
860
861 /*
862         Inventory
863 */
864
865 Inventory::~Inventory()
866 {
867         clear();
868 }
869
870 void Inventory::clear()
871 {
872         for(u32 i=0; i<m_lists.size(); i++)
873         {
874                 delete m_lists[i];
875         }
876         m_lists.clear();
877 }
878
879 Inventory::Inventory()
880 {
881 }
882
883 Inventory::Inventory(const Inventory &other)
884 {
885         *this = other;
886 }
887
888 Inventory & Inventory::operator = (const Inventory &other)
889 {
890         clear();
891         for(u32 i=0; i<other.m_lists.size(); i++)
892         {
893                 m_lists.push_back(new InventoryList(*other.m_lists[i]));
894         }
895         return *this;
896 }
897
898 void Inventory::serialize(std::ostream &os) const
899 {
900         for(u32 i=0; i<m_lists.size(); i++)
901         {
902                 InventoryList *list = m_lists[i];
903                 os<<"List "<<list->getName()<<" "<<list->getSize()<<"\n";
904                 list->serialize(os);
905         }
906
907         os<<"EndInventory\n";
908 }
909
910 void Inventory::deSerialize(std::istream &is, IGameDef *gamedef)
911 {
912         clear();
913
914         for(;;)
915         {
916                 std::string line;
917                 std::getline(is, line, '\n');
918
919                 std::istringstream iss(line);
920
921                 std::string name;
922                 std::getline(iss, name, ' ');
923
924                 if(name == "EndInventory")
925                 {
926                         break;
927                 }
928                 // This is a temporary backwards compatibility fix
929                 else if(name == "end")
930                 {
931                         break;
932                 }
933                 else if(name == "List")
934                 {
935                         std::string listname;
936                         u32 listsize;
937
938                         std::getline(iss, listname, ' ');
939                         iss>>listsize;
940
941                         InventoryList *list = new InventoryList(listname, listsize);
942                         list->deSerialize(is, gamedef);
943
944                         m_lists.push_back(list);
945                 }
946                 else
947                 {
948                         throw SerializationError("Unknown inventory identifier");
949                 }
950         }
951 }
952
953 InventoryList * Inventory::addList(const std::string &name, u32 size)
954 {
955         s32 i = getListIndex(name);
956         if(i != -1)
957         {
958                 if(m_lists[i]->getSize() != size)
959                 {
960                         delete m_lists[i];
961                         m_lists[i] = new InventoryList(name, size);
962                 }
963                 return m_lists[i];
964         }
965         else
966         {
967                 m_lists.push_back(new InventoryList(name, size));
968                 return m_lists.getLast();
969         }
970 }
971
972 InventoryList * Inventory::getList(const std::string &name)
973 {
974         s32 i = getListIndex(name);
975         if(i == -1)
976                 return NULL;
977         return m_lists[i];
978 }
979
980 bool Inventory::deleteList(const std::string &name)
981 {
982         s32 i = getListIndex(name);
983         if(i == -1)
984                 return false;
985         delete m_lists[i];
986         m_lists.erase(i);
987         return true;
988 }
989
990 const InventoryList * Inventory::getList(const std::string &name) const
991 {
992         s32 i = getListIndex(name);
993         if(i == -1)
994                 return NULL;
995         return m_lists[i];
996 }
997
998 const s32 Inventory::getListIndex(const std::string &name) const
999 {
1000         for(u32 i=0; i<m_lists.size(); i++)
1001         {
1002                 if(m_lists[i]->getName() == name)
1003                         return i;
1004         }
1005         return -1;
1006 }
1007
1008 /*
1009         InventoryAction
1010 */
1011
1012 InventoryAction * InventoryAction::deSerialize(std::istream &is)
1013 {
1014         std::string type;
1015         std::getline(is, type, ' ');
1016
1017         InventoryAction *a = NULL;
1018
1019         if(type == "Move")
1020         {
1021                 a = new IMoveAction(is);
1022         }
1023         else if(type == "Drop")
1024         {
1025                 a = new IDropAction(is);
1026         }
1027
1028         return a;
1029 }
1030
1031 static std::string describeC(const struct InventoryContext *c)
1032 {
1033         if(c->current_player == NULL)
1034                 return "current_player=NULL";
1035         else
1036                 return std::string("current_player=") + c->current_player->getName();
1037 }
1038
1039 IMoveAction::IMoveAction(std::istream &is)
1040 {
1041         std::string ts;
1042
1043         std::getline(is, ts, ' ');
1044         count = stoi(ts);
1045
1046         std::getline(is, from_inv, ' ');
1047
1048         std::getline(is, from_list, ' ');
1049
1050         std::getline(is, ts, ' ');
1051         from_i = stoi(ts);
1052
1053         std::getline(is, to_inv, ' ');
1054
1055         std::getline(is, to_list, ' ');
1056
1057         std::getline(is, ts, ' ');
1058         to_i = stoi(ts);
1059 }
1060
1061 void IMoveAction::apply(InventoryContext *c, InventoryManager *mgr,
1062                 ServerEnvironment *env)
1063 {
1064         Inventory *inv_from = mgr->getInventory(c, from_inv);
1065         Inventory *inv_to = mgr->getInventory(c, to_inv);
1066         
1067         if(!inv_from){
1068                 infostream<<"IMoveAction::apply(): FAIL: source inventory not found: "
1069                                 <<"context=["<<describeC(c)<<"], from_inv=\""<<from_inv<<"\""
1070                                 <<", to_inv=\""<<to_inv<<"\""<<std::endl;
1071                 return;
1072         }
1073         if(!inv_to){
1074                 infostream<<"IMoveAction::apply(): FAIL: destination inventory not found: "
1075                                 "context=["<<describeC(c)<<"], from_inv=\""<<from_inv<<"\""
1076                                 <<", to_inv=\""<<to_inv<<"\""<<std::endl;
1077                 return;
1078         }
1079
1080         InventoryList *list_from = inv_from->getList(from_list);
1081         InventoryList *list_to = inv_to->getList(to_list);
1082
1083         /*
1084                 If a list doesn't exist or the source item doesn't exist
1085         */
1086         if(!list_from){
1087                 infostream<<"IMoveAction::apply(): FAIL: source list not found: "
1088                                 <<"context=["<<describeC(c)<<"], from_inv=\""<<from_inv<<"\""
1089                                 <<", from_list=\""<<from_list<<"\""<<std::endl;
1090                 return;
1091         }
1092         if(!list_to){
1093                 infostream<<"IMoveAction::apply(): FAIL: destination list not found: "
1094                                 <<"context=["<<describeC(c)<<"], to_inv=\""<<to_inv<<"\""
1095                                 <<", to_list=\""<<to_list<<"\""<<std::endl;
1096                 return;
1097         }
1098         if(list_from->getItem(from_i) == NULL)
1099         {
1100                 infostream<<"IMoveAction::apply(): FAIL: source item not found: "
1101                                 <<"context=["<<describeC(c)<<"], from_inv=\""<<from_inv<<"\""
1102                                 <<", from_list=\""<<from_list<<"\""
1103                                 <<" from_i="<<from_i<<std::endl;
1104                 return;
1105         }
1106         /*
1107                 If the source and the destination slots are the same
1108         */
1109         if(inv_from == inv_to && list_from == list_to && from_i == to_i)
1110         {
1111                 infostream<<"IMoveAction::apply(): FAIL: source and destination slots "
1112                                 <<"are the same: inv=\""<<from_inv<<"\" list=\""<<from_list
1113                                 <<"\" i="<<from_i<<std::endl;
1114                 return;
1115         }
1116         
1117         // Take item from source list
1118         InventoryItem *item1 = NULL;
1119         if(count == 0)
1120                 item1 = list_from->changeItem(from_i, NULL);
1121         else
1122                 item1 = list_from->takeItem(from_i, count);
1123
1124         // Try to add the item to destination list
1125         InventoryItem *olditem = item1;
1126         item1 = list_to->addItem(to_i, item1);
1127
1128         // If something is returned, the item was not fully added
1129         if(item1 != NULL)
1130         {
1131                 // If olditem is returned, nothing was added.
1132                 bool nothing_added = (item1 == olditem);
1133                 
1134                 // If something else is returned, part of the item was left unadded.
1135                 // Add the other part back to the source item
1136                 list_from->addItem(from_i, item1);
1137
1138                 // If olditem is returned, nothing was added.
1139                 // Swap the items
1140                 if(nothing_added)
1141                 {
1142                         // Take item from source list
1143                         item1 = list_from->changeItem(from_i, NULL);
1144                         // Adding was not possible, swap the items.
1145                         InventoryItem *item2 = list_to->changeItem(to_i, item1);
1146                         // Put item from destination list to the source list
1147                         list_from->changeItem(from_i, item2);
1148                 }
1149         }
1150
1151         mgr->inventoryModified(c, from_inv);
1152         if(from_inv != to_inv)
1153                 mgr->inventoryModified(c, to_inv);
1154         
1155         infostream<<"IMoveAction::apply(): moved at "
1156                         <<"["<<describeC(c)<<"]"
1157                         <<" from inv=\""<<from_inv<<"\""
1158                         <<" list=\""<<from_list<<"\""
1159                         <<" i="<<from_i
1160                         <<" to inv=\""<<to_inv<<"\""
1161                         <<" list=\""<<to_list<<"\""
1162                         <<" i="<<to_i
1163                         <<std::endl;
1164 }
1165
1166 IDropAction::IDropAction(std::istream &is)
1167 {
1168         std::string ts;
1169
1170         std::getline(is, ts, ' ');
1171         count = stoi(ts);
1172
1173         std::getline(is, from_inv, ' ');
1174
1175         std::getline(is, from_list, ' ');
1176
1177         std::getline(is, ts, ' ');
1178         from_i = stoi(ts);
1179 }
1180
1181 void IDropAction::apply(InventoryContext *c, InventoryManager *mgr,
1182                 ServerEnvironment *env)
1183 {
1184         if(c->current_player == NULL){
1185                 infostream<<"IDropAction::apply(): FAIL: current_player is NULL"<<std::endl;
1186                 return;
1187         }
1188
1189         // Do NOT cast directly to ServerActiveObject*, it breaks
1190         // because of multiple inheritance.
1191         ServerActiveObject *dropper =
1192                 static_cast<ServerActiveObject*>(
1193                 static_cast<ServerRemotePlayer*>(
1194                         c->current_player
1195                 ));
1196
1197         Inventory *inv_from = mgr->getInventory(c, from_inv);
1198         
1199         if(!inv_from){
1200                 infostream<<"IDropAction::apply(): FAIL: source inventory not found: "
1201                                 <<"context=["<<describeC(c)<<"], from_inv=\""<<from_inv<<"\""<<std::endl;
1202                 return;
1203         }
1204
1205         InventoryList *list_from = inv_from->getList(from_list);
1206
1207         /*
1208                 If a list doesn't exist or the source item doesn't exist
1209         */
1210         if(!list_from){
1211                 infostream<<"IDropAction::apply(): FAIL: source list not found: "
1212                                 <<"context=["<<describeC(c)<<"], from_inv=\""<<from_inv<<"\""
1213                                 <<", from_list=\""<<from_list<<"\""<<std::endl;
1214                 return;
1215         }
1216         InventoryItem *item = list_from->getItem(from_i);
1217         if(item == NULL)
1218         {
1219                 infostream<<"IDropAction::apply(): FAIL: source item not found: "
1220                                 <<"context=["<<describeC(c)<<"], from_inv=\""<<from_inv<<"\""
1221                                 <<", from_list=\""<<from_list<<"\""
1222                                 <<" from_i="<<from_i<<std::endl;
1223                 return;
1224         }
1225
1226         v3f pos = dropper->getBasePosition();
1227         pos.Y += 0.5*BS;
1228
1229         s16 count2 = count;
1230         if(count2 == 0)
1231                 count2 = -1;
1232
1233         /*
1234                 Drop the item
1235         */
1236         bool remove = item->dropOrPlace(env, dropper, pos, false, count2);
1237         if(remove)
1238                 list_from->deleteItem(from_i);
1239
1240         mgr->inventoryModified(c, from_inv);
1241
1242         infostream<<"IDropAction::apply(): dropped "
1243                         <<"["<<describeC(c)<<"]"
1244                         <<" from inv=\""<<from_inv<<"\""
1245                         <<" list=\""<<from_list<<"\""
1246                         <<" i="<<from_i
1247                         <<std::endl;
1248 }
1249
1250 /*
1251         Craft checking system
1252 */
1253
1254 bool ItemSpec::checkItem(const InventoryItem *item) const
1255 {
1256         if(type == ITEM_NONE)
1257         {
1258                 // Has to be no item
1259                 if(item != NULL)
1260                         return false;
1261                 return true;
1262         }
1263         
1264         // There should be an item
1265         if(item == NULL)
1266                 return false;
1267
1268         std::string itemname = item->getName();
1269
1270         if(type == ITEM_MATERIAL)
1271         {
1272                 if(itemname != "MaterialItem")
1273                         return false;
1274                 MaterialItem *mitem = (MaterialItem*)item;
1275                 if(num != 65535){
1276                         if(mitem->getMaterial() != num)
1277                                 return false;
1278                 } else {
1279                         if(mitem->getNodeName() != name)
1280                                 return false;
1281                 }
1282         }
1283         else if(type == ITEM_CRAFT)
1284         {
1285                 if(itemname != "CraftItem")
1286                         return false;
1287                 CraftItem *mitem = (CraftItem*)item;
1288                 if(mitem->getSubName() != name)
1289                         return false;
1290         }
1291         else if(type == ITEM_TOOL)
1292         {
1293                 // Not supported yet
1294                 assert(0);
1295         }
1296         else if(type == ITEM_MBO)
1297         {
1298                 // Not supported yet
1299                 assert(0);
1300         }
1301         else
1302         {
1303                 // Not supported yet
1304                 assert(0);
1305         }
1306         return true;
1307 }
1308
1309 bool checkItemCombination(InventoryItem const * const *items, const ItemSpec *specs)
1310 {
1311         u16 items_min_x = 100;
1312         u16 items_max_x = 100;
1313         u16 items_min_y = 100;
1314         u16 items_max_y = 100;
1315         for(u16 y=0; y<3; y++)
1316         for(u16 x=0; x<3; x++)
1317         {
1318                 if(items[y*3 + x] == NULL)
1319                         continue;
1320                 if(items_min_x == 100 || x < items_min_x)
1321                         items_min_x = x;
1322                 if(items_min_y == 100 || y < items_min_y)
1323                         items_min_y = y;
1324                 if(items_max_x == 100 || x > items_max_x)
1325                         items_max_x = x;
1326                 if(items_max_y == 100 || y > items_max_y)
1327                         items_max_y = y;
1328         }
1329         // No items at all, just return false
1330         if(items_min_x == 100)
1331                 return false;
1332         
1333         u16 items_w = items_max_x - items_min_x + 1;
1334         u16 items_h = items_max_y - items_min_y + 1;
1335
1336         u16 specs_min_x = 100;
1337         u16 specs_max_x = 100;
1338         u16 specs_min_y = 100;
1339         u16 specs_max_y = 100;
1340         for(u16 y=0; y<3; y++)
1341         for(u16 x=0; x<3; x++)
1342         {
1343                 if(specs[y*3 + x].type == ITEM_NONE)
1344                         continue;
1345                 if(specs_min_x == 100 || x < specs_min_x)
1346                         specs_min_x = x;
1347                 if(specs_min_y == 100 || y < specs_min_y)
1348                         specs_min_y = y;
1349                 if(specs_max_x == 100 || x > specs_max_x)
1350                         specs_max_x = x;
1351                 if(specs_max_y == 100 || y > specs_max_y)
1352                         specs_max_y = y;
1353         }
1354         // No specs at all, just return false
1355         if(specs_min_x == 100)
1356                 return false;
1357
1358         u16 specs_w = specs_max_x - specs_min_x + 1;
1359         u16 specs_h = specs_max_y - specs_min_y + 1;
1360
1361         // Different sizes
1362         if(items_w != specs_w || items_h != specs_h)
1363                 return false;
1364
1365         for(u16 y=0; y<specs_h; y++)
1366         for(u16 x=0; x<specs_w; x++)
1367         {
1368                 u16 items_x = items_min_x + x;
1369                 u16 items_y = items_min_y + y;
1370                 u16 specs_x = specs_min_x + x;
1371                 u16 specs_y = specs_min_y + y;
1372                 const InventoryItem *item = items[items_y * 3 + items_x];
1373                 const ItemSpec &spec = specs[specs_y * 3 + specs_x];
1374
1375                 if(spec.checkItem(item) == false)
1376                         return false;
1377         }
1378
1379         return true;
1380 }
1381
1382 bool checkItemCombination(const InventoryItem * const * items,
1383                 const InventoryItem * const * specs)
1384 {
1385         u16 items_min_x = 100;
1386         u16 items_max_x = 100;
1387         u16 items_min_y = 100;
1388         u16 items_max_y = 100;
1389         for(u16 y=0; y<3; y++)
1390         for(u16 x=0; x<3; x++)
1391         {
1392                 if(items[y*3 + x] == NULL)
1393                         continue;
1394                 if(items_min_x == 100 || x < items_min_x)
1395                         items_min_x = x;
1396                 if(items_min_y == 100 || y < items_min_y)
1397                         items_min_y = y;
1398                 if(items_max_x == 100 || x > items_max_x)
1399                         items_max_x = x;
1400                 if(items_max_y == 100 || y > items_max_y)
1401                         items_max_y = y;
1402         }
1403         // No items at all, just return false
1404         if(items_min_x == 100)
1405                 return false;
1406         
1407         u16 items_w = items_max_x - items_min_x + 1;
1408         u16 items_h = items_max_y - items_min_y + 1;
1409
1410         u16 specs_min_x = 100;
1411         u16 specs_max_x = 100;
1412         u16 specs_min_y = 100;
1413         u16 specs_max_y = 100;
1414         for(u16 y=0; y<3; y++)
1415         for(u16 x=0; x<3; x++)
1416         {
1417                 if(specs[y*3 + x] == NULL)
1418                         continue;
1419                 if(specs_min_x == 100 || x < specs_min_x)
1420                         specs_min_x = x;
1421                 if(specs_min_y == 100 || y < specs_min_y)
1422                         specs_min_y = y;
1423                 if(specs_max_x == 100 || x > specs_max_x)
1424                         specs_max_x = x;
1425                 if(specs_max_y == 100 || y > specs_max_y)
1426                         specs_max_y = y;
1427         }
1428         // No specs at all, just return false
1429         if(specs_min_x == 100)
1430                 return false;
1431
1432         u16 specs_w = specs_max_x - specs_min_x + 1;
1433         u16 specs_h = specs_max_y - specs_min_y + 1;
1434
1435         // Different sizes
1436         if(items_w != specs_w || items_h != specs_h)
1437                 return false;
1438
1439         for(u16 y=0; y<specs_h; y++)
1440         for(u16 x=0; x<specs_w; x++)
1441         {
1442                 u16 items_x = items_min_x + x;
1443                 u16 items_y = items_min_y + y;
1444                 u16 specs_x = specs_min_x + x;
1445                 u16 specs_y = specs_min_y + y;
1446                 const InventoryItem *item = items[items_y * 3 + items_x];
1447                 const InventoryItem *spec = specs[specs_y * 3 + specs_x];
1448                 
1449                 if(item == NULL && spec == NULL)
1450                         continue;
1451                 if(item == NULL && spec != NULL)
1452                         return false;
1453                 if(item != NULL && spec == NULL)
1454                         return false;
1455                 if(!spec->isSubsetOf(item))
1456                         return false;
1457         }
1458
1459         return true;
1460 }
1461
1462 //END