3 Copyright (C) 2010-2011 celeron55, Perttu Ahola <celeron55@gmail.com>
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.
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.
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.
20 #include "inventory.h"
21 #include "serialization.h"
25 #include "main.h" // For tsrc, g_toolmanager
26 #include "serverobject.h"
27 #include "content_mapnode.h"
28 #include "content_inventory.h"
29 #include "content_sao.h"
40 InventoryItem::InventoryItem(IGameDef *gamedef, u16 count):
47 InventoryItem::~InventoryItem()
51 content_t content_translate_from_19_to_internal(content_t c_from)
53 for(u32 i=0; i<sizeof(trans_table_19)/sizeof(trans_table_19[0]); i++)
55 if(trans_table_19[i][1] == c_from)
57 return trans_table_19[i][0];
63 InventoryItem* InventoryItem::deSerialize(std::istream &is, IGameDef *gamedef)
65 DSTACK(__FUNCTION_NAME);
67 //is.imbue(std::locale("C"));
70 std::getline(is, name, ' ');
72 if(name == "MaterialItem")
74 // u16 reads directly as a number (u8 doesn't)
79 // Convert old materials
82 material = content_translate_from_19_to_internal(material);
84 if(material > MAX_CONTENT)
85 throw SerializationError("Too large material number");
86 return new MaterialItem(gamedef, material, count);
88 else if(name == "MaterialItem2")
94 if(material > MAX_CONTENT)
95 throw SerializationError("Too large material number");
96 return new MaterialItem(gamedef, material, count);
98 else if(name == "MBOItem")
100 std::string inventorystring;
101 std::getline(is, inventorystring, '|');
102 throw SerializationError("MBOItem not supported anymore");
104 else if(name == "CraftItem")
107 std::getline(is, subname, ' ');
110 return new CraftItem(gamedef, subname, count);
112 else if(name == "ToolItem")
114 std::string toolname;
115 std::getline(is, toolname, ' ');
118 return new ToolItem(gamedef, toolname, wear);
122 infostream<<"Unknown InventoryItem name=\""<<name<<"\""<<std::endl;
123 throw SerializationError("Unknown InventoryItem name");
127 std::string InventoryItem::getItemString() {
129 std::ostringstream os(std::ios_base::binary);
134 ServerActiveObject* InventoryItem::createSAO(ServerEnvironment *env, u16 id, v3f pos)
139 pos.Y -= BS*0.25; // let it drop a bit
141 pos.X += BS*0.2*(float)myrand_range(-1000,1000)/1000.0;
142 pos.Z += BS*0.2*(float)myrand_range(-1000,1000)/1000.0;
144 ServerActiveObject *obj = new ItemSAO(env, pos, getItemString());
153 video::ITexture * MaterialItem::getImage(ITextureSource *tsrc) const
155 return m_gamedef->getNodeDefManager()->get(m_content).inventory_texture;
159 bool MaterialItem::isCookable() const
161 INodeDefManager *ndef = m_gamedef->ndef();
162 const ContentFeatures &f = ndef->get(m_content);
163 return (f.cookresult_item != "");
166 InventoryItem *MaterialItem::createCookResult() const
168 INodeDefManager *ndef = m_gamedef->ndef();
169 const ContentFeatures &f = ndef->get(m_content);
170 std::istringstream is(f.cookresult_item, std::ios::binary);
171 return InventoryItem::deSerialize(is, m_gamedef);
178 std::string ToolItem::getImageBasename() const
180 return m_gamedef->getToolDefManager()->getImagename(m_toolname);
184 video::ITexture * ToolItem::getImage(ITextureSource *tsrc) const
189 std::string basename = getImageBasename();
192 Calculate a progress value with sane amount of
195 u32 maxprogress = 30;
196 u32 toolprogress = (65535-m_wear)/(65535/maxprogress);
198 float value_f = (float)toolprogress / (float)maxprogress;
199 std::ostringstream os;
200 os<<basename<<"^[progressbar"<<value_f;
202 return tsrc->getTextureRaw(os.str());
205 video::ITexture * ToolItem::getImageRaw(ITextureSource *tsrc) const
210 return tsrc->getTextureRaw(getImageBasename());
219 video::ITexture * CraftItem::getImage(ITextureSource *tsrc) const
224 std::string name = item_craft_get_image_name(m_subname, m_gamedef);
226 // Get such a texture
227 return tsrc->getTextureRaw(name);
231 ServerActiveObject* CraftItem::createSAO(ServerEnvironment *env, u16 id, v3f pos)
234 ServerActiveObject *obj = item_craft_create_object(m_subname, env, pos);
238 return InventoryItem::createSAO(env, id, pos);
241 u16 CraftItem::getDropCount() const
244 s16 dc = item_craft_get_drop_count(m_subname, m_gamedef);
248 return InventoryItem::getDropCount();
251 bool CraftItem::isCookable() const
253 return item_craft_is_cookable(m_subname, m_gamedef);
256 InventoryItem *CraftItem::createCookResult() const
258 return item_craft_create_cook_result(m_subname, m_gamedef);
261 bool CraftItem::use(ServerEnvironment *env, ServerActiveObject *user)
263 if(!item_craft_is_eatable(m_subname, m_gamedef))
266 u16 result_count = getCount() - 1; // Eat one at a time
267 s16 hp_change = item_craft_eat_hp_change(m_subname, m_gamedef);
268 s16 hp = user->getHP();
277 setCount(result_count);
285 InventoryList::InventoryList(std::string name, u32 size)
293 InventoryList::~InventoryList()
295 for(u32 i=0; i<m_items.size(); i++)
302 void InventoryList::clearItems()
304 for(u32 i=0; i<m_items.size(); i++)
312 for(u32 i=0; i<m_size; i++)
314 m_items.push_back(NULL);
320 void InventoryList::serialize(std::ostream &os) const
322 //os.imbue(std::locale("C"));
324 for(u32 i=0; i<m_items.size(); i++)
326 InventoryItem *item = m_items[i];
339 os<<"EndInventoryList\n";
342 void InventoryList::deSerialize(std::istream &is, IGameDef *gamedef)
344 //is.imbue(std::locale("C"));
352 std::getline(is, line, '\n');
354 std::istringstream iss(line);
355 //iss.imbue(std::locale("C"));
358 std::getline(iss, name, ' ');
360 if(name == "EndInventoryList")
364 // This is a temporary backwards compatibility fix
365 else if(name == "end")
369 else if(name == "Item")
371 if(item_i > getSize() - 1)
372 throw SerializationError("too many items");
373 InventoryItem *item = InventoryItem::deSerialize(iss, gamedef);
374 m_items[item_i++] = item;
376 else if(name == "Empty")
378 if(item_i > getSize() - 1)
379 throw SerializationError("too many items");
380 m_items[item_i++] = NULL;
384 throw SerializationError("Unknown inventory identifier");
389 InventoryList::InventoryList(const InventoryList &other)
392 Do this so that the items get cloned. Otherwise the pointers
393 in the array will just get copied.
398 InventoryList & InventoryList::operator = (const InventoryList &other)
400 m_name = other.m_name;
401 m_size = other.m_size;
403 for(u32 i=0; i<other.m_items.size(); i++)
405 InventoryItem *item = other.m_items[i];
408 m_items[i] = item->clone();
416 const std::string &InventoryList::getName() const
421 u32 InventoryList::getSize()
423 return m_items.size();
426 u32 InventoryList::getUsedSlots()
429 for(u32 i=0; i<m_items.size(); i++)
431 InventoryItem *item = m_items[i];
438 u32 InventoryList::getFreeSlots()
440 return getSize() - getUsedSlots();
443 const InventoryItem * InventoryList::getItem(u32 i) const
445 if(i > m_items.size() - 1)
450 InventoryItem * InventoryList::getItem(u32 i)
452 if(i > m_items.size() - 1)
457 InventoryItem * InventoryList::changeItem(u32 i, InventoryItem *newitem)
459 assert(i < m_items.size());
461 InventoryItem *olditem = m_items[i];
462 m_items[i] = newitem;
467 void InventoryList::deleteItem(u32 i)
469 assert(i < m_items.size());
470 InventoryItem *item = changeItem(i, NULL);
475 InventoryItem * InventoryList::addItem(InventoryItem *newitem)
481 First try to find if it could be added to some existing items
483 for(u32 i=0; i<m_items.size(); i++)
485 // Ignore empty slots
486 if(m_items[i] == NULL)
489 newitem = addItem(i, newitem);
491 return NULL; // All was eaten
495 Then try to add it to empty slots
497 for(u32 i=0; i<m_items.size(); i++)
499 // Ignore unempty slots
500 if(m_items[i] != NULL)
503 newitem = addItem(i, newitem);
505 return NULL; // All was eaten
512 InventoryItem * InventoryList::addItem(u32 i, InventoryItem *newitem)
519 // If it is an empty position, it's an easy job.
520 InventoryItem *to_item = getItem(i);
523 m_items[i] = newitem;
527 // If not addable, return the item
528 if(newitem->addableTo(to_item) == false)
531 // If the item fits fully in the slot, add counter and delete it
532 if(newitem->getCount() <= to_item->freeSpace())
534 to_item->add(newitem->getCount());
538 // Else the item does not fit fully. Add all that fits and return
542 u16 freespace = to_item->freeSpace();
543 to_item->add(freespace);
544 newitem->remove(freespace);
549 bool InventoryList::itemFits(const u32 i, const InventoryItem *newitem)
551 // If it is an empty position, it's an easy job.
552 const InventoryItem *to_item = getItem(i);
558 // If not addable, fail
559 if(newitem->addableTo(to_item) == false)
562 // If the item fits fully in the slot, pass
563 if(newitem->getCount() <= to_item->freeSpace())
571 bool InventoryList::roomForItem(const InventoryItem *item)
573 for(u32 i=0; i<m_items.size(); i++)
574 if(itemFits(i, item))
579 bool InventoryList::roomForCookedItem(const InventoryItem *item)
583 const InventoryItem *cook = item->createCookResult();
586 bool room = roomForItem(cook);
591 InventoryItem * InventoryList::takeItem(u32 i, u32 count)
598 InventoryItem *item = getItem(i);
599 // If it is an empty position, return NULL
603 if(count >= item->getCount())
605 // Get the item by swapping NULL to its place
606 return changeItem(i, NULL);
610 InventoryItem *item2 = item->clone();
612 item2->setCount(count);
619 void InventoryList::decrementMaterials(u16 count)
621 for(u32 i=0; i<m_items.size(); i++)
623 InventoryItem *item = takeItem(i, count);
629 void InventoryList::print(std::ostream &o)
631 o<<"InventoryList:"<<std::endl;
632 for(u32 i=0; i<m_items.size(); i++)
634 InventoryItem *item = m_items[i];
648 Inventory::~Inventory()
653 void Inventory::clear()
655 for(u32 i=0; i<m_lists.size(); i++)
662 Inventory::Inventory()
666 Inventory::Inventory(const Inventory &other)
671 Inventory & Inventory::operator = (const Inventory &other)
674 for(u32 i=0; i<other.m_lists.size(); i++)
676 m_lists.push_back(new InventoryList(*other.m_lists[i]));
681 void Inventory::serialize(std::ostream &os) const
683 for(u32 i=0; i<m_lists.size(); i++)
685 InventoryList *list = m_lists[i];
686 os<<"List "<<list->getName()<<" "<<list->getSize()<<"\n";
690 os<<"EndInventory\n";
693 void Inventory::deSerialize(std::istream &is, IGameDef *gamedef)
700 std::getline(is, line, '\n');
702 std::istringstream iss(line);
705 std::getline(iss, name, ' ');
707 if(name == "EndInventory")
711 // This is a temporary backwards compatibility fix
712 else if(name == "end")
716 else if(name == "List")
718 std::string listname;
721 std::getline(iss, listname, ' ');
724 InventoryList *list = new InventoryList(listname, listsize);
725 list->deSerialize(is, gamedef);
727 m_lists.push_back(list);
731 throw SerializationError("Unknown inventory identifier");
736 InventoryList * Inventory::addList(const std::string &name, u32 size)
738 s32 i = getListIndex(name);
741 if(m_lists[i]->getSize() != size)
744 m_lists[i] = new InventoryList(name, size);
750 m_lists.push_back(new InventoryList(name, size));
751 return m_lists.getLast();
755 InventoryList * Inventory::getList(const std::string &name)
757 s32 i = getListIndex(name);
763 const InventoryList * Inventory::getList(const std::string &name) const
765 s32 i = getListIndex(name);
771 const s32 Inventory::getListIndex(const std::string &name) const
773 for(u32 i=0; i<m_lists.size(); i++)
775 if(m_lists[i]->getName() == name)
785 InventoryAction * InventoryAction::deSerialize(std::istream &is)
788 std::getline(is, type, ' ');
790 InventoryAction *a = NULL;
794 a = new IMoveAction(is);
800 static std::string describeC(const struct InventoryContext *c)
802 if(c->current_player == NULL)
803 return "current_player=NULL";
805 return std::string("current_player=") + c->current_player->getName();
808 IMoveAction::IMoveAction(std::istream &is)
812 std::getline(is, ts, ' ');
815 std::getline(is, from_inv, ' ');
817 std::getline(is, from_list, ' ');
819 std::getline(is, ts, ' ');
822 std::getline(is, to_inv, ' ');
824 std::getline(is, to_list, ' ');
826 std::getline(is, ts, ' ');
830 void IMoveAction::apply(InventoryContext *c, InventoryManager *mgr)
832 Inventory *inv_from = mgr->getInventory(c, from_inv);
833 Inventory *inv_to = mgr->getInventory(c, to_inv);
836 infostream<<"IMoveAction::apply(): FAIL: source inventory not found: "
837 <<"context=["<<describeC(c)<<"], from_inv=\""<<from_inv<<"\""
838 <<", to_inv=\""<<to_inv<<"\""<<std::endl;
842 infostream<<"IMoveAction::apply(): FAIL: destination inventory not found: "
843 "context=["<<describeC(c)<<"], from_inv=\""<<from_inv<<"\""
844 <<", to_inv=\""<<to_inv<<"\""<<std::endl;
848 InventoryList *list_from = inv_from->getList(from_list);
849 InventoryList *list_to = inv_to->getList(to_list);
852 If a list doesn't exist or the source item doesn't exist
855 infostream<<"IMoveAction::apply(): FAIL: source list not found: "
856 <<"context=["<<describeC(c)<<"], from_inv=\""<<from_inv<<"\""
857 <<", from_list=\""<<from_list<<"\""<<std::endl;
861 infostream<<"IMoveAction::apply(): FAIL: destination list not found: "
862 <<"context=["<<describeC(c)<<"], to_inv=\""<<to_inv<<"\""
863 <<", to_list=\""<<to_list<<"\""<<std::endl;
866 if(list_from->getItem(from_i) == NULL)
868 infostream<<"IMoveAction::apply(): FAIL: source item not found: "
869 <<"context=["<<describeC(c)<<"], from_inv=\""<<from_inv<<"\""
870 <<", from_list=\""<<from_list<<"\""
871 <<" from_i="<<from_i<<std::endl;
875 If the source and the destination slots are the same
877 if(inv_from == inv_to && list_from == list_to && from_i == to_i)
879 infostream<<"IMoveAction::apply(): FAIL: source and destination slots "
880 <<"are the same: inv=\""<<from_inv<<"\" list=\""<<from_list
881 <<"\" i="<<from_i<<std::endl;
885 // Take item from source list
886 InventoryItem *item1 = NULL;
888 item1 = list_from->changeItem(from_i, NULL);
890 item1 = list_from->takeItem(from_i, count);
892 // Try to add the item to destination list
893 InventoryItem *olditem = item1;
894 item1 = list_to->addItem(to_i, item1);
896 // If something is returned, the item was not fully added
899 // If olditem is returned, nothing was added.
900 bool nothing_added = (item1 == olditem);
902 // If something else is returned, part of the item was left unadded.
903 // Add the other part back to the source item
904 list_from->addItem(from_i, item1);
906 // If olditem is returned, nothing was added.
910 // Take item from source list
911 item1 = list_from->changeItem(from_i, NULL);
912 // Adding was not possible, swap the items.
913 InventoryItem *item2 = list_to->changeItem(to_i, item1);
914 // Put item from destination list to the source list
915 list_from->changeItem(from_i, item2);
919 mgr->inventoryModified(c, from_inv);
920 if(from_inv != to_inv)
921 mgr->inventoryModified(c, to_inv);
923 infostream<<"IMoveAction::apply(): moved at "
924 <<"["<<describeC(c)<<"]"
925 <<" from inv=\""<<from_inv<<"\""
926 <<" list=\""<<from_list<<"\""
928 <<" to inv=\""<<to_inv<<"\""
929 <<" list=\""<<to_list<<"\""
935 Craft checking system
938 bool ItemSpec::checkItem(const InventoryItem *item) const
940 if(type == ITEM_NONE)
948 // There should be an item
952 std::string itemname = item->getName();
954 if(type == ITEM_MATERIAL)
956 if(itemname != "MaterialItem")
958 MaterialItem *mitem = (MaterialItem*)item;
959 if(mitem->getMaterial() != num)
962 else if(type == ITEM_CRAFT)
964 if(itemname != "CraftItem")
966 CraftItem *mitem = (CraftItem*)item;
967 if(mitem->getSubName() != name)
970 else if(type == ITEM_TOOL)
975 else if(type == ITEM_MBO)
988 bool checkItemCombination(InventoryItem const * const *items, const ItemSpec *specs)
990 u16 items_min_x = 100;
991 u16 items_max_x = 100;
992 u16 items_min_y = 100;
993 u16 items_max_y = 100;
994 for(u16 y=0; y<3; y++)
995 for(u16 x=0; x<3; x++)
997 if(items[y*3 + x] == NULL)
999 if(items_min_x == 100 || x < items_min_x)
1001 if(items_min_y == 100 || y < items_min_y)
1003 if(items_max_x == 100 || x > items_max_x)
1005 if(items_max_y == 100 || y > items_max_y)
1008 // No items at all, just return false
1009 if(items_min_x == 100)
1012 u16 items_w = items_max_x - items_min_x + 1;
1013 u16 items_h = items_max_y - items_min_y + 1;
1015 u16 specs_min_x = 100;
1016 u16 specs_max_x = 100;
1017 u16 specs_min_y = 100;
1018 u16 specs_max_y = 100;
1019 for(u16 y=0; y<3; y++)
1020 for(u16 x=0; x<3; x++)
1022 if(specs[y*3 + x].type == ITEM_NONE)
1024 if(specs_min_x == 100 || x < specs_min_x)
1026 if(specs_min_y == 100 || y < specs_min_y)
1028 if(specs_max_x == 100 || x > specs_max_x)
1030 if(specs_max_y == 100 || y > specs_max_y)
1033 // No specs at all, just return false
1034 if(specs_min_x == 100)
1037 u16 specs_w = specs_max_x - specs_min_x + 1;
1038 u16 specs_h = specs_max_y - specs_min_y + 1;
1041 if(items_w != specs_w || items_h != specs_h)
1044 for(u16 y=0; y<specs_h; y++)
1045 for(u16 x=0; x<specs_w; x++)
1047 u16 items_x = items_min_x + x;
1048 u16 items_y = items_min_y + y;
1049 u16 specs_x = specs_min_x + x;
1050 u16 specs_y = specs_min_y + y;
1051 const InventoryItem *item = items[items_y * 3 + items_x];
1052 const ItemSpec &spec = specs[specs_y * 3 + specs_x];
1054 if(spec.checkItem(item) == false)