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