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