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