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