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