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