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