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