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