Add InvRef and InvStack (currently untested and unusable)
[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
41 /*
42         InventoryItem
43 */
44
45 InventoryItem::InventoryItem(IGameDef *gamedef, u16 count):
46         m_gamedef(gamedef),
47         m_count(count)
48 {
49         assert(m_gamedef);
50 }
51
52 InventoryItem::~InventoryItem()
53 {
54 }
55
56 content_t content_translate_from_19_to_internal(content_t c_from)
57 {
58         for(u32 i=0; i<sizeof(trans_table_19)/sizeof(trans_table_19[0]); i++)
59         {
60                 if(trans_table_19[i][1] == c_from)
61                 {
62                         return trans_table_19[i][0];
63                 }
64         }
65         return c_from;
66 }
67
68 InventoryItem* InventoryItem::deSerialize(std::istream &is, IGameDef *gamedef)
69 {
70         DSTACK(__FUNCTION_NAME);
71
72         //is.imbue(std::locale("C"));
73         // Read name
74         std::string name;
75         std::getline(is, name, ' ');
76         
77         if(name == "MaterialItem")
78         {
79                 // u16 reads directly as a number (u8 doesn't)
80                 u16 material;
81                 is>>material;
82                 u16 count;
83                 is>>count;
84                 // Convert old materials
85                 if(material <= 0xff)
86                         material = content_translate_from_19_to_internal(material);
87                 if(material > MAX_CONTENT)
88                         throw SerializationError("Too large material number");
89                 return new MaterialItem(gamedef, material, count);
90         }
91         else if(name == "MaterialItem2")
92         {
93                 u16 material;
94                 is>>material;
95                 u16 count;
96                 is>>count;
97                 if(material > MAX_CONTENT)
98                         throw SerializationError("Too large material number");
99                 return new MaterialItem(gamedef, material, count);
100         }
101         else if(name == "node" || name == "NodeItem" || name == "MaterialItem3")
102         {
103                 std::string all;
104                 std::getline(is, all, '\n');
105                 std::string nodename;
106                 // First attempt to read inside ""
107                 Strfnd fnd(all);
108                 fnd.next("\"");
109                 // If didn't skip to end, we have ""s
110                 if(!fnd.atend()){
111                         nodename = fnd.next("\"");
112                 } else { // No luck, just read a word then
113                         fnd.start(all);
114                         nodename = fnd.next(" ");
115                 }
116                 fnd.skip_over(" ");
117                 u16 count = stoi(trim(fnd.next("")));
118                 if(count == 0)
119                         count = 1;
120                 return new MaterialItem(gamedef, nodename, count);
121         }
122         else if(name == "MBOItem")
123         {
124                 std::string inventorystring;
125                 std::getline(is, inventorystring, '|');
126                 throw SerializationError("MBOItem not supported anymore");
127         }
128         else if(name == "craft" || name == "CraftItem")
129         {
130                 std::string all;
131                 std::getline(is, all, '\n');
132                 std::string subname;
133                 // First attempt to read inside ""
134                 Strfnd fnd(all);
135                 fnd.next("\"");
136                 // If didn't skip to end, we have ""s
137                 if(!fnd.atend()){
138                         subname = fnd.next("\"");
139                 } else { // No luck, just read a word then
140                         fnd.start(all);
141                         subname = fnd.next(" ");
142                 }
143                 // Then read count
144                 fnd.skip_over(" ");
145                 u16 count = stoi(trim(fnd.next("")));
146                 if(count == 0)
147                         count = 1;
148                 return new CraftItem(gamedef, subname, count);
149         }
150         else if(name == "tool" || name == "ToolItem")
151         {
152                 std::string all;
153                 std::getline(is, all, '\n');
154                 std::string toolname;
155                 // First attempt to read inside ""
156                 Strfnd fnd(all);
157                 fnd.next("\"");
158                 // If didn't skip to end, we have ""s
159                 if(!fnd.atend()){
160                         toolname = fnd.next("\"");
161                 } else { // No luck, just read a word then
162                         fnd.start(all);
163                         toolname = fnd.next(" ");
164                 }
165                 // Then read wear
166                 fnd.skip_over(" ");
167                 u16 wear = stoi(trim(fnd.next("")));
168                 return new ToolItem(gamedef, toolname, wear);
169         }
170         else
171         {
172                 infostream<<"Unknown InventoryItem name=\""<<name<<"\""<<std::endl;
173                 throw SerializationError("Unknown InventoryItem name");
174         }
175 }
176
177 InventoryItem* InventoryItem::deSerialize(const std::string &str,
178                 IGameDef *gamedef)
179 {
180         std::istringstream is(str, std::ios_base::binary);
181         return deSerialize(is, gamedef);
182 }
183
184 std::string InventoryItem::getItemString() {
185         // Get item string
186         std::ostringstream os(std::ios_base::binary);
187         serialize(os);
188         return os.str();
189 }
190
191 bool InventoryItem::dropOrPlace(ServerEnvironment *env,
192                 ServerActiveObject *dropper,
193                 v3f pos, bool place, s16 count)
194 {
195         /*
196                 Ensure that the block is loaded so that the item
197                 can properly be added to the static list too
198         */
199         v3s16 blockpos = getNodeBlockPos(floatToInt(pos, BS));
200         MapBlock *block = env->getMap().emergeBlock(blockpos, false);
201         if(block==NULL)
202         {
203                 infostream<<"InventoryItem::dropOrPlace(): FAIL: block not found: "
204                                 <<blockpos.X<<","<<blockpos.Y<<","<<blockpos.Z
205                                 <<std::endl;
206                 return false;
207         }
208
209         /*
210                 Take specified number of items,
211                 but limit to getDropCount().
212         */
213         s16 dropcount = getDropCount();
214         if(count < 0 || count > dropcount)
215                 count = dropcount;
216         if(count < 0 || count > getCount())
217                 count = getCount();
218         if(count > 0)
219         {
220                 /*
221                         Create an ItemSAO
222                 */
223                 pos.Y -= BS*0.25; // let it drop a bit
224                 // Randomize a bit
225                 //pos.X += BS*0.2*(float)myrand_range(-1000,1000)/1000.0;
226                 //pos.Z += BS*0.2*(float)myrand_range(-1000,1000)/1000.0;
227                 // Create object
228                 ServerActiveObject *obj = new ItemSAO(env, pos, getItemString());
229                 // Add the object to the environment
230                 env->addActiveObject(obj);
231                 infostream<<"Dropped item"<<std::endl;
232
233                 setCount(getCount() - count);
234         }
235
236         return getCount() < 1; // delete the item?
237 }
238
239 /*
240         MaterialItem
241 */
242
243 MaterialItem::MaterialItem(IGameDef *gamedef, std::string nodename, u16 count):
244         InventoryItem(gamedef, count)
245 {
246         if(nodename == "")
247                 nodename = "unknown_block";
248
249         // Convert directly to the correct name through aliases
250         m_nodename = gamedef->ndef()->getAlias(nodename);
251 }
252 // Legacy constructor
253 MaterialItem::MaterialItem(IGameDef *gamedef, content_t content, u16 count):
254         InventoryItem(gamedef, count)
255 {
256         NameIdMapping legacy_nimap;
257         content_mapnode_get_name_id_mapping(&legacy_nimap);
258         std::string nodename;
259         legacy_nimap.getName(content, nodename);
260         if(nodename == "")
261                 nodename = "unknown_block";
262         m_nodename = nodename;
263 }
264
265 #ifndef SERVER
266 video::ITexture * MaterialItem::getImage() const
267 {
268         return m_gamedef->getNodeDefManager()->get(m_nodename).inventory_texture;
269 }
270 #endif
271
272 bool MaterialItem::isCookable() const
273 {
274         INodeDefManager *ndef = m_gamedef->ndef();
275         const ContentFeatures &f = ndef->get(m_nodename);
276         return (f.cookresult_item != "");
277 }
278
279 InventoryItem *MaterialItem::createCookResult() const
280 {
281         INodeDefManager *ndef = m_gamedef->ndef();
282         const ContentFeatures &f = ndef->get(m_nodename);
283         std::istringstream is(f.cookresult_item, std::ios::binary);
284         return InventoryItem::deSerialize(is, m_gamedef);
285 }
286
287 float MaterialItem::getCookTime() const
288 {
289         INodeDefManager *ndef = m_gamedef->ndef();
290         const ContentFeatures &f = ndef->get(m_nodename);
291         return f.furnace_cooktime;
292 }
293
294 float MaterialItem::getBurnTime() const
295 {
296         INodeDefManager *ndef = m_gamedef->ndef();
297         const ContentFeatures &f = ndef->get(m_nodename);
298         return f.furnace_burntime;
299 }
300
301 content_t MaterialItem::getMaterial() const
302 {
303         INodeDefManager *ndef = m_gamedef->ndef();
304         content_t id = CONTENT_IGNORE;
305         ndef->getId(m_nodename, id);
306         return id;
307 }
308
309 /*
310         ToolItem
311 */
312
313 ToolItem::ToolItem(IGameDef *gamedef, std::string toolname, u16 wear):
314         InventoryItem(gamedef, 1)
315 {
316         // Convert directly to the correct name through aliases
317         m_toolname = gamedef->tdef()->getAlias(toolname);
318         
319         m_wear = wear;
320 }
321
322 std::string ToolItem::getImageBasename() const
323 {
324         return m_gamedef->getToolDefManager()->getImagename(m_toolname);
325 }
326
327 #ifndef SERVER
328 video::ITexture * ToolItem::getImage() const
329 {
330         ITextureSource *tsrc = m_gamedef->tsrc();
331
332         std::string basename = getImageBasename();
333         
334         /*
335                 Calculate a progress value with sane amount of
336                 maximum states
337         */
338         u32 maxprogress = 30;
339         u32 toolprogress = (65535-m_wear)/(65535/maxprogress);
340         
341         float value_f = (float)toolprogress / (float)maxprogress;
342         std::ostringstream os;
343         os<<basename<<"^[progressbar"<<value_f;
344
345         return tsrc->getTextureRaw(os.str());
346 }
347
348 video::ITexture * ToolItem::getImageRaw() const
349 {
350         ITextureSource *tsrc = m_gamedef->tsrc();
351         
352         return tsrc->getTextureRaw(getImageBasename());
353 }
354 #endif
355
356 bool ToolItem::isKnown() const
357 {
358         IToolDefManager *tdef = m_gamedef->tdef();
359         const ToolDefinition *def = tdef->getToolDefinition(m_toolname);
360         return (def != NULL);
361 }
362
363 /*
364         CraftItem
365 */
366
367 CraftItem::CraftItem(IGameDef *gamedef, std::string subname, u16 count):
368         InventoryItem(gamedef, count)
369 {
370         // Convert directly to the correct name through aliases
371         m_subname = gamedef->cidef()->getAlias(subname);
372 }
373
374 #ifndef SERVER
375 video::ITexture * CraftItem::getImage() const
376 {
377         ICraftItemDefManager *cidef = m_gamedef->cidef();
378         ITextureSource *tsrc = m_gamedef->tsrc();
379         std::string imagename = cidef->getImagename(m_subname);
380         return tsrc->getTextureRaw(imagename);
381 }
382 #endif
383
384 bool CraftItem::isKnown() const
385 {
386         ICraftItemDefManager *cidef = m_gamedef->cidef();
387         const CraftItemDefinition *def = cidef->getCraftItemDefinition(m_subname);
388         return (def != NULL);
389 }
390
391 u16 CraftItem::getStackMax() const
392 {
393         ICraftItemDefManager *cidef = m_gamedef->cidef();
394         const CraftItemDefinition *def = cidef->getCraftItemDefinition(m_subname);
395         if(def == NULL)
396                 return InventoryItem::getStackMax();
397         return def->stack_max;
398 }
399
400 bool CraftItem::isUsable() const
401 {
402         ICraftItemDefManager *cidef = m_gamedef->cidef();
403         const CraftItemDefinition *def = cidef->getCraftItemDefinition(m_subname);
404         return def != NULL && def->usable;
405 }
406
407 bool CraftItem::isCookable() const
408 {
409         ICraftItemDefManager *cidef = m_gamedef->cidef();
410         const CraftItemDefinition *def = cidef->getCraftItemDefinition(m_subname);
411         return def != NULL && def->cookresult_item != "";
412 }
413
414 InventoryItem *CraftItem::createCookResult() const
415 {
416         ICraftItemDefManager *cidef = m_gamedef->cidef();
417         const CraftItemDefinition *def = cidef->getCraftItemDefinition(m_subname);
418         if(def == NULL)
419                 return InventoryItem::createCookResult();
420         std::istringstream is(def->cookresult_item, std::ios::binary);
421         return InventoryItem::deSerialize(is, m_gamedef);
422 }
423
424 float CraftItem::getCookTime() const
425 {
426         ICraftItemDefManager *cidef = m_gamedef->cidef();
427         const CraftItemDefinition *def = cidef->getCraftItemDefinition(m_subname);
428         if (def == NULL)
429                 return InventoryItem::getCookTime();
430         return def->furnace_cooktime;
431 }
432
433 float CraftItem::getBurnTime() const
434 {
435         ICraftItemDefManager *cidef = m_gamedef->cidef();
436         const CraftItemDefinition *def = cidef->getCraftItemDefinition(m_subname);
437         if (def == NULL)
438                 return InventoryItem::getBurnTime();
439         return def->furnace_burntime;
440 }
441
442 s16 CraftItem::getDropCount() const
443 {
444         // Special cases
445         ICraftItemDefManager *cidef = m_gamedef->cidef();
446         const CraftItemDefinition *def = cidef->getCraftItemDefinition(m_subname);
447         if(def != NULL && def->dropcount >= 0)
448                 return def->dropcount;
449         // Default
450         return InventoryItem::getDropCount();
451 }
452
453 bool CraftItem::areLiquidsPointable() const
454 {
455         ICraftItemDefManager *cidef = m_gamedef->cidef();
456         const CraftItemDefinition *def = cidef->getCraftItemDefinition(m_subname);
457         return def != NULL && def->liquids_pointable;
458 }
459
460 bool CraftItem::dropOrPlace(ServerEnvironment *env,
461                 ServerActiveObject *dropper,
462                 v3f pos, bool place, s16 count)
463 {
464         if(count == 0)
465                 return false;
466
467         bool callback_exists = false;
468         bool result = false;
469
470         if(place)
471         {
472                 result = scriptapi_craftitem_on_place_on_ground(
473                                 env->getLua(),
474                                 m_subname.c_str(), dropper, pos,
475                                 callback_exists);
476         }
477
478         // note: on_drop is fallback for on_place_on_ground
479
480         if(!callback_exists)
481         {
482                 result = scriptapi_craftitem_on_drop(
483                                 env->getLua(),
484                                 m_subname.c_str(), dropper, pos,
485                                 callback_exists);
486         }
487
488         if(callback_exists)
489         {
490                 // If the callback returned true, drop one item
491                 if(result)
492                         setCount(getCount() - 1);
493                 return getCount() < 1;
494         }
495         else
496         {
497                 // If neither on_place_on_ground (if place==true)
498                 // nor on_drop exists, call the base implementation
499                 return InventoryItem::dropOrPlace(env, dropper, pos, place, count);
500         }
501 }
502
503 bool CraftItem::use(ServerEnvironment *env,
504                 ServerActiveObject *user,
505                 const PointedThing& pointed)
506 {
507         bool callback_exists = false;
508         bool result = false;
509
510         result = scriptapi_craftitem_on_use(
511                         env->getLua(),
512                         m_subname.c_str(), user, pointed,
513                         callback_exists);
514
515         if(callback_exists)
516         {
517                 // If the callback returned true, drop one item
518                 if(result)
519                         setCount(getCount() - 1);
520                 return getCount() < 1;
521         }
522         else
523         {
524                 // If neither on_place_on_ground (if place==true)
525                 // nor on_drop exists, call the base implementation
526                 return InventoryItem::use(env, user, pointed);
527         }
528 }
529
530 /*
531         Inventory
532 */
533
534 InventoryList::InventoryList(std::string name, u32 size)
535 {
536         m_name = name;
537         m_size = size;
538         clearItems();
539         //m_dirty = false;
540 }
541
542 InventoryList::~InventoryList()
543 {
544         for(u32 i=0; i<m_items.size(); i++)
545         {
546                 if(m_items[i])
547                         delete m_items[i];
548         }
549 }
550
551 void InventoryList::clearItems()
552 {
553         for(u32 i=0; i<m_items.size(); i++)
554         {
555                 if(m_items[i])
556                         delete m_items[i];
557         }
558
559         m_items.clear();
560
561         for(u32 i=0; i<m_size; i++)
562         {
563                 m_items.push_back(NULL);
564         }
565
566         //setDirty(true);
567 }
568
569 void InventoryList::setSize(u32 newsize)
570 {
571         if(newsize < m_items.size()){
572                 for(u32 i=newsize; i<m_items.size(); i++){
573                         if(m_items[i])
574                                 delete m_items[i];
575                 }
576                 m_items.erase(newsize, m_items.size() - newsize);
577         } else {
578                 for(u32 i=m_items.size(); i<newsize; i++){
579                         m_items.push_back(NULL);
580                 }
581         }
582         m_size = newsize;
583 }
584
585 void InventoryList::serialize(std::ostream &os) const
586 {
587         //os.imbue(std::locale("C"));
588         
589         for(u32 i=0; i<m_items.size(); i++)
590         {
591                 InventoryItem *item = m_items[i];
592                 if(item != NULL)
593                 {
594                         os<<"Item ";
595                         item->serialize(os);
596                 }
597                 else
598                 {
599                         os<<"Empty";
600                 }
601                 os<<"\n";
602         }
603
604         os<<"EndInventoryList\n";
605 }
606
607 void InventoryList::deSerialize(std::istream &is, IGameDef *gamedef)
608 {
609         //is.imbue(std::locale("C"));
610
611         clearItems();
612         u32 item_i = 0;
613
614         for(;;)
615         {
616                 std::string line;
617                 std::getline(is, line, '\n');
618
619                 std::istringstream iss(line);
620                 //iss.imbue(std::locale("C"));
621
622                 std::string name;
623                 std::getline(iss, name, ' ');
624
625                 if(name == "EndInventoryList")
626                 {
627                         break;
628                 }
629                 // This is a temporary backwards compatibility fix
630                 else if(name == "end")
631                 {
632                         break;
633                 }
634                 else if(name == "Item")
635                 {
636                         if(item_i > getSize() - 1)
637                                 throw SerializationError("too many items");
638                         InventoryItem *item = InventoryItem::deSerialize(iss, gamedef);
639                         m_items[item_i++] = item;
640                 }
641                 else if(name == "Empty")
642                 {
643                         if(item_i > getSize() - 1)
644                                 throw SerializationError("too many items");
645                         m_items[item_i++] = NULL;
646                 }
647                 else
648                 {
649                         throw SerializationError("Unknown inventory identifier");
650                 }
651         }
652 }
653
654 InventoryList::InventoryList(const InventoryList &other)
655 {
656         /*
657                 Do this so that the items get cloned. Otherwise the pointers
658                 in the array will just get copied.
659         */
660         *this = other;
661 }
662
663 InventoryList & InventoryList::operator = (const InventoryList &other)
664 {
665         m_name = other.m_name;
666         m_size = other.m_size;
667         clearItems();
668         for(u32 i=0; i<other.m_items.size(); i++)
669         {
670                 InventoryItem *item = other.m_items[i];
671                 if(item != NULL)
672                 {
673                         m_items[i] = item->clone();
674                 }
675         }
676         //setDirty(true);
677
678         return *this;
679 }
680
681 const std::string &InventoryList::getName() const
682 {
683         return m_name;
684 }
685
686 u32 InventoryList::getSize()
687 {
688         return m_items.size();
689 }
690
691 u32 InventoryList::getUsedSlots()
692 {
693         u32 num = 0;
694         for(u32 i=0; i<m_items.size(); i++)
695         {
696                 InventoryItem *item = m_items[i];
697                 if(item != NULL)
698                         num++;
699         }
700         return num;
701 }
702
703 u32 InventoryList::getFreeSlots()
704 {
705         return getSize() - getUsedSlots();
706 }
707
708 const InventoryItem * InventoryList::getItem(u32 i) const
709 {
710         if(i >= m_items.size())
711                 return NULL;
712         return m_items[i];
713 }
714
715 InventoryItem * InventoryList::getItem(u32 i)
716 {
717         if(i >= m_items.size())
718                 return NULL;
719         return m_items[i];
720 }
721
722 InventoryItem * InventoryList::changeItem(u32 i, InventoryItem *newitem)
723 {
724         if(i >= m_items.size())
725                 return newitem;
726
727         InventoryItem *olditem = m_items[i];
728         m_items[i] = newitem;
729         //setDirty(true);
730         return olditem;
731 }
732
733 void InventoryList::deleteItem(u32 i)
734 {
735         assert(i < m_items.size());
736         InventoryItem *item = changeItem(i, NULL);
737         if(item)
738                 delete item;
739 }
740
741 InventoryItem * InventoryList::addItem(InventoryItem *newitem)
742 {
743         if(newitem == NULL)
744                 return NULL;
745         
746         /*
747                 First try to find if it could be added to some existing items
748         */
749         for(u32 i=0; i<m_items.size(); i++)
750         {
751                 // Ignore empty slots
752                 if(m_items[i] == NULL)
753                         continue;
754                 // Try adding
755                 newitem = addItem(i, newitem);
756                 if(newitem == NULL)
757                         return NULL; // All was eaten
758         }
759
760         /*
761                 Then try to add it to empty slots
762         */
763         for(u32 i=0; i<m_items.size(); i++)
764         {
765                 // Ignore unempty slots
766                 if(m_items[i] != NULL)
767                         continue;
768                 // Try adding
769                 newitem = addItem(i, newitem);
770                 if(newitem == NULL)
771                         return NULL; // All was eaten
772         }
773
774         // Return leftover
775         return newitem;
776 }
777
778 InventoryItem * InventoryList::addItem(u32 i, InventoryItem *newitem)
779 {
780         if(newitem == NULL)
781                 return NULL;
782         if(i >= m_items.size())
783                 return newitem;
784         
785         //setDirty(true);
786         
787         // If it is an empty position, it's an easy job.
788         InventoryItem *to_item = getItem(i);
789         if(to_item == NULL)
790         {
791                 m_items[i] = newitem;
792                 return NULL;
793         }
794         
795         // If not addable, return the item
796         if(newitem->addableTo(to_item) == false)
797                 return newitem;
798         
799         // If the item fits fully in the slot, add counter and delete it
800         if(newitem->getCount() <= to_item->freeSpace())
801         {
802                 to_item->add(newitem->getCount());
803                 delete newitem;
804                 return NULL;
805         }
806         // Else the item does not fit fully. Add all that fits and return
807         // the rest.
808         else
809         {
810                 u16 freespace = to_item->freeSpace();
811                 to_item->add(freespace);
812                 newitem->remove(freespace);
813                 return newitem;
814         }
815 }
816
817 bool InventoryList::itemFits(const u32 i, const InventoryItem *newitem)
818 {
819         // If it is an empty position, it's an easy job.
820         const InventoryItem *to_item = getItem(i);
821         if(to_item == NULL)
822         {
823                 return true;
824         }
825         
826         // If not addable, fail
827         if(newitem->addableTo(to_item) == false)
828                 return false;
829         
830         // If the item fits fully in the slot, pass
831         if(newitem->getCount() <= to_item->freeSpace())
832         {
833                 return true;
834         }
835
836         return false;
837 }
838
839 bool InventoryList::roomForItem(const InventoryItem *item)
840 {
841         for(u32 i=0; i<m_items.size(); i++)
842                 if(itemFits(i, item))
843                         return true;
844         return false;
845 }
846
847 bool InventoryList::roomForCookedItem(const InventoryItem *item)
848 {
849         if(!item)
850                 return false;
851         const InventoryItem *cook = item->createCookResult();
852         if(!cook)
853                 return false;
854         bool room = roomForItem(cook);
855         delete cook;
856         return room;
857 }
858
859 InventoryItem * InventoryList::takeItem(u32 i, u32 count)
860 {
861         if(count == 0)
862                 return NULL;
863         
864         //setDirty(true);
865
866         InventoryItem *item = getItem(i);
867         // If it is an empty position, return NULL
868         if(item == NULL)
869                 return NULL;
870         
871         if(count >= item->getCount())
872         {
873                 // Get the item by swapping NULL to its place
874                 return changeItem(i, NULL);
875         }
876         else
877         {
878                 InventoryItem *item2 = item->clone();
879                 item->remove(count);
880                 item2->setCount(count);
881                 return item2;
882         }
883         
884         return false;
885 }
886
887 void InventoryList::decrementMaterials(u16 count)
888 {
889         for(u32 i=0; i<m_items.size(); i++)
890         {
891                 InventoryItem *item = takeItem(i, count);
892                 if(item)
893                         delete item;
894         }
895 }
896
897 void InventoryList::print(std::ostream &o)
898 {
899         o<<"InventoryList:"<<std::endl;
900         for(u32 i=0; i<m_items.size(); i++)
901         {
902                 InventoryItem *item = m_items[i];
903                 if(item != NULL)
904                 {
905                         o<<i<<": ";
906                         item->serialize(o);
907                         o<<"\n";
908                 }
909         }
910 }
911
912 /*
913         Inventory
914 */
915
916 Inventory::~Inventory()
917 {
918         clear();
919 }
920
921 void Inventory::clear()
922 {
923         for(u32 i=0; i<m_lists.size(); i++)
924         {
925                 delete m_lists[i];
926         }
927         m_lists.clear();
928 }
929
930 Inventory::Inventory()
931 {
932 }
933
934 Inventory::Inventory(const Inventory &other)
935 {
936         *this = other;
937 }
938
939 Inventory & Inventory::operator = (const Inventory &other)
940 {
941         clear();
942         for(u32 i=0; i<other.m_lists.size(); i++)
943         {
944                 m_lists.push_back(new InventoryList(*other.m_lists[i]));
945         }
946         return *this;
947 }
948
949 void Inventory::serialize(std::ostream &os) const
950 {
951         for(u32 i=0; i<m_lists.size(); i++)
952         {
953                 InventoryList *list = m_lists[i];
954                 os<<"List "<<list->getName()<<" "<<list->getSize()<<"\n";
955                 list->serialize(os);
956         }
957
958         os<<"EndInventory\n";
959 }
960
961 void Inventory::deSerialize(std::istream &is, IGameDef *gamedef)
962 {
963         clear();
964
965         for(;;)
966         {
967                 std::string line;
968                 std::getline(is, line, '\n');
969
970                 std::istringstream iss(line);
971
972                 std::string name;
973                 std::getline(iss, name, ' ');
974
975                 if(name == "EndInventory")
976                 {
977                         break;
978                 }
979                 // This is a temporary backwards compatibility fix
980                 else if(name == "end")
981                 {
982                         break;
983                 }
984                 else if(name == "List")
985                 {
986                         std::string listname;
987                         u32 listsize;
988
989                         std::getline(iss, listname, ' ');
990                         iss>>listsize;
991
992                         InventoryList *list = new InventoryList(listname, listsize);
993                         list->deSerialize(is, gamedef);
994
995                         m_lists.push_back(list);
996                 }
997                 else
998                 {
999                         throw SerializationError("Unknown inventory identifier");
1000                 }
1001         }
1002 }
1003
1004 InventoryList * Inventory::addList(const std::string &name, u32 size)
1005 {
1006         s32 i = getListIndex(name);
1007         if(i != -1)
1008         {
1009                 if(m_lists[i]->getSize() != size)
1010                 {
1011                         delete m_lists[i];
1012                         m_lists[i] = new InventoryList(name, size);
1013                 }
1014                 return m_lists[i];
1015         }
1016         else
1017         {
1018                 m_lists.push_back(new InventoryList(name, size));
1019                 return m_lists.getLast();
1020         }
1021 }
1022
1023 InventoryList * Inventory::getList(const std::string &name)
1024 {
1025         s32 i = getListIndex(name);
1026         if(i == -1)
1027                 return NULL;
1028         return m_lists[i];
1029 }
1030
1031 bool Inventory::deleteList(const std::string &name)
1032 {
1033         s32 i = getListIndex(name);
1034         if(i == -1)
1035                 return false;
1036         delete m_lists[i];
1037         m_lists.erase(i);
1038         return true;
1039 }
1040
1041 const InventoryList * Inventory::getList(const std::string &name) const
1042 {
1043         s32 i = getListIndex(name);
1044         if(i == -1)
1045                 return NULL;
1046         return m_lists[i];
1047 }
1048
1049 const s32 Inventory::getListIndex(const std::string &name) const
1050 {
1051         for(u32 i=0; i<m_lists.size(); i++)
1052         {
1053                 if(m_lists[i]->getName() == name)
1054                         return i;
1055         }
1056         return -1;
1057 }
1058
1059 //END