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