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