]> git.lizzy.rs Git - dragonfireclient.git/blob - src/inventory.cpp
merged an old head into main branch
[dragonfireclient.git] / src / inventory.cpp
1 /*
2 Minetest-c55
3 Copyright (C) 2010 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 /*
21 (c) 2010 Perttu Ahola <celeron55@gmail.com>
22 */
23
24 #include "inventory.h"
25 #include "serialization.h"
26 #include "utility.h"
27 #include "debug.h"
28 #include <sstream>
29 #include "main.h"
30 #include "serverobject.h"
31
32 /*
33         InventoryItem
34 */
35
36 InventoryItem::InventoryItem(u16 count)
37 {
38         m_count = count;
39 }
40
41 InventoryItem::~InventoryItem()
42 {
43 }
44
45 InventoryItem* InventoryItem::deSerialize(std::istream &is)
46 {
47         DSTACK(__FUNCTION_NAME);
48
49         //is.imbue(std::locale("C"));
50         // Read name
51         std::string name;
52         std::getline(is, name, ' ');
53         
54         if(name == "MaterialItem")
55         {
56                 // u16 reads directly as a number (u8 doesn't)
57                 u16 material;
58                 is>>material;
59                 u16 count;
60                 is>>count;
61                 if(material > 255)
62                         throw SerializationError("Too large material number");
63                 return new MaterialItem(material, count);
64         }
65         else if(name == "MBOItem")
66         {
67                 std::string inventorystring;
68                 std::getline(is, inventorystring, '|');
69                 return new MapBlockObjectItem(inventorystring);
70         }
71         else if(name == "CraftItem")
72         {
73                 std::string subname;
74                 std::getline(is, subname, ' ');
75                 u16 count;
76                 is>>count;
77                 return new CraftItem(subname, count);
78         }
79         else if(name == "ToolItem")
80         {
81                 std::string toolname;
82                 std::getline(is, toolname, ' ');
83                 u16 wear;
84                 is>>wear;
85                 return new ToolItem(toolname, wear);
86         }
87         else
88         {
89                 dstream<<"Unknown InventoryItem name=\""<<name<<"\""<<std::endl;
90                 throw SerializationError("Unknown InventoryItem name");
91         }
92 }
93
94 ServerActiveObject* InventoryItem::createSAO(ServerEnvironment *env, u16 id, v3f pos)
95 {
96         /*
97                 Create an ItemSAO
98         */
99         // Get item string
100         std::ostringstream os(std::ios_base::binary);
101         serialize(os);
102         // Create object
103         ServerActiveObject *obj = new ItemSAO(env, 0, pos, os.str());
104         return obj;
105 }
106
107 /*
108         MaterialItem
109 */
110
111 bool MaterialItem::isCookable()
112 {
113         if(m_content == CONTENT_TREE)
114         {
115                 return true;
116         }
117         else if(m_content == CONTENT_COBBLE)
118         {
119                 return true;
120         }
121         else if(m_content == CONTENT_SAND)
122         {
123                 return true;
124         }
125         return false;
126 }
127
128 InventoryItem *MaterialItem::createCookResult()
129 {
130         if(m_content == CONTENT_TREE)
131         {
132                 return new CraftItem("lump_of_coal", 1);
133         }
134         else if(m_content == CONTENT_COBBLE)
135         {
136                 return new MaterialItem(CONTENT_STONE, 1);
137         }
138         else if(m_content == CONTENT_SAND)
139         {
140                 return new MaterialItem(CONTENT_GLASS, 1);
141         }
142         return NULL;
143 }
144
145 /*
146         CraftItem
147 */
148
149 #ifndef SERVER
150 video::ITexture * CraftItem::getImage()
151 {
152         if(g_texturesource == NULL)
153                 return NULL;
154         
155         std::string name;
156
157         if(m_subname == "Stick")
158                 name = "stick.png";
159         else if(m_subname == "lump_of_coal")
160                 name = "lump_of_coal.png";
161         else if(m_subname == "lump_of_iron")
162                 name = "lump_of_iron.png";
163         else if(m_subname == "steel_ingot")
164                 name = "steel_ingot.png";
165         else if(m_subname == "rat")
166                 name = "rat.png";
167         else
168                 name = "cloud.png";
169         
170         // Get such a texture
171         return g_texturesource->getTextureRaw(name);
172 }
173 #endif
174
175 ServerActiveObject* CraftItem::createSAO(ServerEnvironment *env, u16 id, v3f pos)
176 {
177         // Special cases
178         if(m_subname == "rat")
179         {
180                 ServerActiveObject *obj = new RatSAO(env, id, pos);
181                 return obj;
182         }
183         // Default
184         else
185         {
186                 return InventoryItem::createSAO(env, id, pos);
187         }
188 }
189
190 u16 CraftItem::getDropCount()
191 {
192         // Special cases
193         if(m_subname == "rat")
194                 return 1;
195         // Default
196         else
197                 return InventoryItem::getDropCount();
198 }
199
200 bool CraftItem::isCookable()
201 {
202         if(m_subname == "lump_of_iron")
203         {
204                 return true;
205         }
206         return false;
207 }
208
209 InventoryItem *CraftItem::createCookResult()
210 {
211         if(m_subname == "lump_of_iron")
212         {
213                 return new CraftItem("steel_ingot", 1);
214         }
215         return NULL;
216 }
217
218 /*
219         MapBlockObjectItem
220         TODO: Remove
221 */
222 #ifndef SERVER
223 video::ITexture * MapBlockObjectItem::getImage()
224 {
225         if(m_inventorystring.substr(0,3) == "Rat")
226                 return g_texturesource->getTextureRaw("rat.png");
227         
228         if(m_inventorystring.substr(0,4) == "Sign")
229                 return g_texturesource->getTextureRaw("sign.png");
230
231         return NULL;
232 }
233 #endif
234 std::string MapBlockObjectItem::getText()
235 {
236         if(m_inventorystring.substr(0,3) == "Rat")
237                 return "";
238         
239         if(m_inventorystring.substr(0,4) == "Sign")
240                 return "";
241
242         return "obj";
243 }
244
245 MapBlockObject * MapBlockObjectItem::createObject
246                 (v3f pos, f32 player_yaw, f32 player_pitch)
247 {
248         std::istringstream is(m_inventorystring);
249         std::string name;
250         std::getline(is, name, ' ');
251         
252         if(name == "None")
253         {
254                 return NULL;
255         }
256         else if(name == "Sign")
257         {
258                 std::string text;
259                 std::getline(is, text, '|');
260                 SignObject *obj = new SignObject(NULL, -1, pos);
261                 obj->setText(text);
262                 obj->setYaw(-player_yaw);
263                 return obj;
264         }
265         else if(name == "Rat")
266         {
267                 RatObject *obj = new RatObject(NULL, -1, pos);
268                 return obj;
269         }
270         else if(name == "ItemObj")
271         {
272                 /*
273                         Now we are an inventory item containing the serialization
274                         string of an object that contains the serialization
275                         string of an inventory item. Fuck this.
276                 */
277                 //assert(0);
278                 dstream<<__FUNCTION_NAME<<": WARNING: Ignoring ItemObj "
279                                 <<"because an item-object should never be inside "
280                                 <<"an object-item."<<std::endl;
281                 return NULL;
282         }
283         else
284         {
285                 return NULL;
286         }
287 }
288
289 /*
290         Inventory
291 */
292
293 InventoryList::InventoryList(std::string name, u32 size)
294 {
295         m_name = name;
296         m_size = size;
297         clearItems();
298         //m_dirty = false;
299 }
300
301 InventoryList::~InventoryList()
302 {
303         for(u32 i=0; i<m_items.size(); i++)
304         {
305                 if(m_items[i])
306                         delete m_items[i];
307         }
308 }
309
310 void InventoryList::clearItems()
311 {
312         for(u32 i=0; i<m_items.size(); i++)
313         {
314                 if(m_items[i])
315                         delete m_items[i];
316         }
317
318         m_items.clear();
319
320         for(u32 i=0; i<m_size; i++)
321         {
322                 m_items.push_back(NULL);
323         }
324
325         //setDirty(true);
326 }
327
328 void InventoryList::serialize(std::ostream &os)
329 {
330         //os.imbue(std::locale("C"));
331         
332         for(u32 i=0; i<m_items.size(); i++)
333         {
334                 InventoryItem *item = m_items[i];
335                 if(item != NULL)
336                 {
337                         os<<"Item ";
338                         item->serialize(os);
339                 }
340                 else
341                 {
342                         os<<"Empty";
343                 }
344                 os<<"\n";
345         }
346
347         os<<"EndInventoryList\n";
348 }
349
350 void InventoryList::deSerialize(std::istream &is)
351 {
352         //is.imbue(std::locale("C"));
353
354         clearItems();
355         u32 item_i = 0;
356
357         for(;;)
358         {
359                 std::string line;
360                 std::getline(is, line, '\n');
361
362                 std::istringstream iss(line);
363                 //iss.imbue(std::locale("C"));
364
365                 std::string name;
366                 std::getline(iss, name, ' ');
367
368                 if(name == "EndInventoryList")
369                 {
370                         break;
371                 }
372                 // This is a temporary backwards compatibility fix
373                 else if(name == "end")
374                 {
375                         break;
376                 }
377                 else if(name == "Item")
378                 {
379                         if(item_i > getSize() - 1)
380                                 throw SerializationError("too many items");
381                         InventoryItem *item = InventoryItem::deSerialize(iss);
382                         m_items[item_i++] = item;
383                 }
384                 else if(name == "Empty")
385                 {
386                         if(item_i > getSize() - 1)
387                                 throw SerializationError("too many items");
388                         m_items[item_i++] = NULL;
389                 }
390                 else
391                 {
392                         throw SerializationError("Unknown inventory identifier");
393                 }
394         }
395 }
396
397 InventoryList::InventoryList(const InventoryList &other)
398 {
399         /*
400                 Do this so that the items get cloned. Otherwise the pointers
401                 in the array will just get copied.
402         */
403         *this = other;
404 }
405
406 InventoryList & InventoryList::operator = (const InventoryList &other)
407 {
408         m_name = other.m_name;
409         m_size = other.m_size;
410         clearItems();
411         for(u32 i=0; i<other.m_items.size(); i++)
412         {
413                 InventoryItem *item = other.m_items[i];
414                 if(item != NULL)
415                 {
416                         m_items[i] = item->clone();
417                 }
418         }
419         //setDirty(true);
420
421         return *this;
422 }
423
424 std::string InventoryList::getName()
425 {
426         return m_name;
427 }
428
429 u32 InventoryList::getSize()
430 {
431         return m_items.size();
432 }
433
434 u32 InventoryList::getUsedSlots()
435 {
436         u32 num = 0;
437         for(u32 i=0; i<m_items.size(); i++)
438         {
439                 InventoryItem *item = m_items[i];
440                 if(item != NULL)
441                         num++;
442         }
443         return num;
444 }
445
446 u32 InventoryList::getFreeSlots()
447 {
448         return getSize() - getUsedSlots();
449 }
450
451 InventoryItem * InventoryList::getItem(u32 i)
452 {
453         if(i > m_items.size() - 1)
454                 return NULL;
455         return m_items[i];
456 }
457
458 InventoryItem * InventoryList::changeItem(u32 i, InventoryItem *newitem)
459 {
460         assert(i < m_items.size());
461
462         InventoryItem *olditem = m_items[i];
463         m_items[i] = newitem;
464         //setDirty(true);
465         return olditem;
466 }
467
468 void InventoryList::deleteItem(u32 i)
469 {
470         assert(i < m_items.size());
471         InventoryItem *item = changeItem(i, NULL);
472         if(item)
473                 delete item;
474 }
475
476 InventoryItem * InventoryList::addItem(InventoryItem *newitem)
477 {
478         if(newitem == NULL)
479                 return NULL;
480         
481         /*
482                 First try to find if it could be added to some existing items
483         */
484         for(u32 i=0; i<m_items.size(); i++)
485         {
486                 // Ignore empty slots
487                 if(m_items[i] == NULL)
488                         continue;
489                 // Try adding
490                 newitem = addItem(i, newitem);
491                 if(newitem == NULL)
492                         return NULL; // All was eaten
493         }
494
495         /*
496                 Then try to add it to empty slots
497         */
498         for(u32 i=0; i<m_items.size(); i++)
499         {
500                 // Ignore unempty slots
501                 if(m_items[i] != NULL)
502                         continue;
503                 // Try adding
504                 newitem = addItem(i, newitem);
505                 if(newitem == NULL)
506                         return NULL; // All was eaten
507         }
508
509         // Return leftover
510         return newitem;
511 }
512
513 InventoryItem * InventoryList::addItem(u32 i, InventoryItem *newitem)
514 {
515         if(newitem == NULL)
516                 return NULL;
517         
518         //setDirty(true);
519         
520         // If it is an empty position, it's an easy job.
521         InventoryItem *to_item = m_items[i];
522         if(to_item == NULL)
523         {
524                 m_items[i] = newitem;
525                 return NULL;
526         }
527         
528         // If not addable, return the item
529         if(newitem->addableTo(to_item) == false)
530                 return newitem;
531         
532         // If the item fits fully in the slot, add counter and delete it
533         if(newitem->getCount() <= to_item->freeSpace())
534         {
535                 to_item->add(newitem->getCount());
536                 delete newitem;
537                 return NULL;
538         }
539         // Else the item does not fit fully. Add all that fits and return
540         // the rest.
541         else
542         {
543                 u16 freespace = to_item->freeSpace();
544                 to_item->add(freespace);
545                 newitem->remove(freespace);
546                 return newitem;
547         }
548 }
549
550 bool InventoryList::itemFits(u32 i, InventoryItem *newitem)
551 {
552         // If it is an empty position, it's an easy job.
553         InventoryItem *to_item = m_items[i];
554         if(to_item == NULL)
555         {
556                 return true;
557         }
558         
559         // If not addable, return the item
560         if(newitem->addableTo(to_item) == false)
561                 return false;
562         
563         // If the item fits fully in the slot, add counter and delete it
564         if(newitem->getCount() <= to_item->freeSpace())
565         {
566                 return true;
567         }
568
569         return false;
570 }
571
572 InventoryItem * InventoryList::takeItem(u32 i, u32 count)
573 {
574         if(count == 0)
575                 return NULL;
576         
577         //setDirty(true);
578
579         InventoryItem *item = m_items[i];
580         // If it is an empty position, return NULL
581         if(item == NULL)
582                 return NULL;
583         
584         if(count >= item->getCount())
585         {
586                 // Get the item by swapping NULL to its place
587                 return changeItem(i, NULL);
588         }
589         else
590         {
591                 InventoryItem *item2 = item->clone();
592                 item->remove(count);
593                 item2->setCount(count);
594                 return item2;
595         }
596         
597         return false;
598 }
599
600 void InventoryList::decrementMaterials(u16 count)
601 {
602         for(u32 i=0; i<m_items.size(); i++)
603         {
604                 InventoryItem *item = takeItem(i, count);
605                 if(item)
606                         delete item;
607         }
608 }
609
610 void InventoryList::print(std::ostream &o)
611 {
612         o<<"InventoryList:"<<std::endl;
613         for(u32 i=0; i<m_items.size(); i++)
614         {
615                 InventoryItem *item = m_items[i];
616                 if(item != NULL)
617                 {
618                         o<<i<<": ";
619                         item->serialize(o);
620                         o<<"\n";
621                 }
622         }
623 }
624
625 /*
626         Inventory
627 */
628
629 Inventory::~Inventory()
630 {
631         clear();
632 }
633
634 void Inventory::clear()
635 {
636         for(u32 i=0; i<m_lists.size(); i++)
637         {
638                 delete m_lists[i];
639         }
640         m_lists.clear();
641 }
642
643 Inventory::Inventory()
644 {
645 }
646
647 Inventory::Inventory(const Inventory &other)
648 {
649         *this = other;
650 }
651
652 Inventory & Inventory::operator = (const Inventory &other)
653 {
654         clear();
655         for(u32 i=0; i<other.m_lists.size(); i++)
656         {
657                 m_lists.push_back(new InventoryList(*other.m_lists[i]));
658         }
659         return *this;
660 }
661
662 void Inventory::serialize(std::ostream &os)
663 {
664         for(u32 i=0; i<m_lists.size(); i++)
665         {
666                 InventoryList *list = m_lists[i];
667                 os<<"List "<<list->getName()<<" "<<list->getSize()<<"\n";
668                 list->serialize(os);
669         }
670
671         os<<"EndInventory\n";
672 }
673
674 void Inventory::deSerialize(std::istream &is)
675 {
676         clear();
677
678         for(;;)
679         {
680                 std::string line;
681                 std::getline(is, line, '\n');
682
683                 std::istringstream iss(line);
684
685                 std::string name;
686                 std::getline(iss, name, ' ');
687
688                 if(name == "EndInventory")
689                 {
690                         break;
691                 }
692                 // This is a temporary backwards compatibility fix
693                 else if(name == "end")
694                 {
695                         break;
696                 }
697                 else if(name == "List")
698                 {
699                         std::string listname;
700                         u32 listsize;
701
702                         std::getline(iss, listname, ' ');
703                         iss>>listsize;
704
705                         InventoryList *list = new InventoryList(listname, listsize);
706                         list->deSerialize(is);
707
708                         m_lists.push_back(list);
709                 }
710                 else
711                 {
712                         throw SerializationError("Unknown inventory identifier");
713                 }
714         }
715 }
716
717 InventoryList * Inventory::addList(const std::string &name, u32 size)
718 {
719         s32 i = getListIndex(name);
720         if(i != -1)
721         {
722                 if(m_lists[i]->getSize() != size)
723                 {
724                         delete m_lists[i];
725                         m_lists[i] = new InventoryList(name, size);
726                 }
727                 return m_lists[i];
728         }
729         else
730         {
731                 m_lists.push_back(new InventoryList(name, size));
732                 return m_lists.getLast();
733         }
734 }
735
736 InventoryList * Inventory::getList(const std::string &name)
737 {
738         s32 i = getListIndex(name);
739         if(i == -1)
740                 return NULL;
741         return m_lists[i];
742 }
743
744 s32 Inventory::getListIndex(const std::string &name)
745 {
746         for(u32 i=0; i<m_lists.size(); i++)
747         {
748                 if(m_lists[i]->getName() == name)
749                         return i;
750         }
751         return -1;
752 }
753
754 /*
755         InventoryAction
756 */
757
758 InventoryAction * InventoryAction::deSerialize(std::istream &is)
759 {
760         std::string type;
761         std::getline(is, type, ' ');
762
763         InventoryAction *a = NULL;
764
765         if(type == "Move")
766         {
767                 a = new IMoveAction(is);
768         }
769
770         return a;
771 }
772
773 void IMoveAction::apply(InventoryContext *c, InventoryManager *mgr)
774 {
775 #if 1
776
777         /*dstream<<"from_inv="<<from_inv<<" to_inv="<<to_inv<<std::endl;
778         dstream<<"from_list="<<from_list<<" to_list="<<to_list<<std::endl;
779         dstream<<"from_i="<<from_i<<" to_i="<<to_i<<std::endl;*/
780
781         Inventory *inv_from = mgr->getInventory(c, from_inv);
782         Inventory *inv_to = mgr->getInventory(c, to_inv);
783
784         if(!inv_from || !inv_to)
785         {
786                 dstream<<__FUNCTION_NAME<<": Operation not allowed "
787                                 <<"(inventories not found)"<<std::endl;
788                 return;
789         }
790
791         InventoryList *list_from = inv_from->getList(from_list);
792         InventoryList *list_to = inv_to->getList(to_list);
793
794         /*dstream<<"list_from="<<list_from<<" list_to="<<list_to
795                         <<std::endl;*/
796         /*if(list_from)
797                 dstream<<" list_from->getItem(from_i)="<<list_from->getItem(from_i)
798                                 <<std::endl;
799         if(list_to)
800                 dstream<<" list_to->getItem(to_i)="<<list_to->getItem(to_i)
801                                 <<std::endl;*/
802         
803         /*
804                 If a list doesn't exist or the source item doesn't exist
805         */
806         if(!list_from || !list_to)
807         {
808                 dstream<<__FUNCTION_NAME<<": Operation not allowed "
809                                 <<"(a list doesn't exist)"
810                                 <<std::endl;
811                 return;
812         }
813         if(list_from->getItem(from_i) == NULL)
814         {
815                 dstream<<__FUNCTION_NAME<<": Operation not allowed "
816                                 <<"(the source item doesn't exist)"
817                                 <<std::endl;
818                 return;
819         }
820         /*
821                 If the source and the destination slots are the same
822         */
823         if(inv_from == inv_to && list_from == list_to && from_i == to_i)
824         {
825                 dstream<<__FUNCTION_NAME<<": Operation not allowed "
826                                 <<"(source and the destination slots are the same)"<<std::endl;
827                 return;
828         }
829         
830         // Take item from source list
831         InventoryItem *item1 = NULL;
832         if(count == 0)
833                 item1 = list_from->changeItem(from_i, NULL);
834         else
835                 item1 = list_from->takeItem(from_i, count);
836
837         // Try to add the item to destination list
838         InventoryItem *olditem = item1;
839         item1 = list_to->addItem(to_i, item1);
840
841         // If something is returned, the item was not fully added
842         if(item1 != NULL)
843         {
844                 // If olditem is returned, nothing was added.
845                 bool nothing_added = (item1 == olditem);
846                 
847                 // If something else is returned, part of the item was left unadded.
848                 // Add the other part back to the source item
849                 list_from->addItem(from_i, item1);
850
851                 // If olditem is returned, nothing was added.
852                 // Swap the items
853                 if(nothing_added)
854                 {
855                         // Take item from source list
856                         item1 = list_from->changeItem(from_i, NULL);
857                         // Adding was not possible, swap the items.
858                         InventoryItem *item2 = list_to->changeItem(to_i, item1);
859                         // Put item from destination list to the source list
860                         list_from->changeItem(from_i, item2);
861                 }
862         }
863
864         mgr->inventoryModified(c, from_inv);
865         if(from_inv != to_inv)
866                 mgr->inventoryModified(c, to_inv);
867 #endif
868 }
869
870 /*
871         Craft checking system
872 */
873
874 bool ItemSpec::checkItem(InventoryItem *item)
875 {
876         if(type == ITEM_NONE)
877         {
878                 // Has to be no item
879                 if(item != NULL)
880                         return false;
881                 return true;
882         }
883         
884         // There should be an item
885         if(item == NULL)
886                 return false;
887
888         std::string itemname = item->getName();
889
890         if(type == ITEM_MATERIAL)
891         {
892                 if(itemname != "MaterialItem")
893                         return false;
894                 MaterialItem *mitem = (MaterialItem*)item;
895                 if(mitem->getMaterial() != num)
896                         return false;
897         }
898         else if(type == ITEM_CRAFT)
899         {
900                 if(itemname != "CraftItem")
901                         return false;
902                 CraftItem *mitem = (CraftItem*)item;
903                 if(mitem->getSubName() != name)
904                         return false;
905         }
906         else if(type == ITEM_TOOL)
907         {
908                 // Not supported yet
909                 assert(0);
910         }
911         else if(type == ITEM_MBO)
912         {
913                 // Not supported yet
914                 assert(0);
915         }
916         else
917         {
918                 // Not supported yet
919                 assert(0);
920         }
921         return true;
922 }
923
924 bool checkItemCombination(InventoryItem **items, ItemSpec *specs)
925 {
926         u16 items_min_x = 100;
927         u16 items_max_x = 100;
928         u16 items_min_y = 100;
929         u16 items_max_y = 100;
930         for(u16 y=0; y<3; y++)
931         for(u16 x=0; x<3; x++)
932         {
933                 if(items[y*3 + x] == NULL)
934                         continue;
935                 if(items_min_x == 100 || x < items_min_x)
936                         items_min_x = x;
937                 if(items_min_y == 100 || y < items_min_y)
938                         items_min_y = y;
939                 if(items_max_x == 100 || x > items_max_x)
940                         items_max_x = x;
941                 if(items_max_y == 100 || y > items_max_y)
942                         items_max_y = y;
943         }
944         // No items at all, just return false
945         if(items_min_x == 100)
946                 return false;
947         
948         u16 items_w = items_max_x - items_min_x + 1;
949         u16 items_h = items_max_y - items_min_y + 1;
950
951         u16 specs_min_x = 100;
952         u16 specs_max_x = 100;
953         u16 specs_min_y = 100;
954         u16 specs_max_y = 100;
955         for(u16 y=0; y<3; y++)
956         for(u16 x=0; x<3; x++)
957         {
958                 if(specs[y*3 + x].type == ITEM_NONE)
959                         continue;
960                 if(specs_min_x == 100 || x < specs_min_x)
961                         specs_min_x = x;
962                 if(specs_min_y == 100 || y < specs_min_y)
963                         specs_min_y = y;
964                 if(specs_max_x == 100 || x > specs_max_x)
965                         specs_max_x = x;
966                 if(specs_max_y == 100 || y > specs_max_y)
967                         specs_max_y = y;
968         }
969         // No specs at all, just return false
970         if(specs_min_x == 100)
971                 return false;
972
973         u16 specs_w = specs_max_x - specs_min_x + 1;
974         u16 specs_h = specs_max_y - specs_min_y + 1;
975
976         // Different sizes
977         if(items_w != specs_w || items_h != specs_h)
978                 return false;
979
980         for(u16 y=0; y<specs_h; y++)
981         for(u16 x=0; x<specs_w; x++)
982         {
983                 u16 items_x = items_min_x + x;
984                 u16 items_y = items_min_y + y;
985                 u16 specs_x = specs_min_x + x;
986                 u16 specs_y = specs_min_y + y;
987                 InventoryItem *item = items[items_y * 3 + items_x];
988                 ItemSpec &spec = specs[specs_y * 3 + specs_x];
989
990                 if(spec.checkItem(item) == false)
991                         return false;
992         }
993
994         return true;
995 }
996         
997 //END