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_sao.h"
29 #include "environment.h"
35 #include "craftitemdef.h"
37 #include "scriptapi.h"
39 #include "nameidmapping.h" // For loading legacy MaterialItems
45 InventoryItem::InventoryItem(IGameDef *gamedef, u16 count):
52 InventoryItem::~InventoryItem()
56 content_t content_translate_from_19_to_internal(content_t c_from)
58 for(u32 i=0; i<sizeof(trans_table_19)/sizeof(trans_table_19[0]); i++)
60 if(trans_table_19[i][1] == c_from)
62 return trans_table_19[i][0];
68 InventoryItem* InventoryItem::deSerialize(std::istream &is, IGameDef *gamedef)
70 DSTACK(__FUNCTION_NAME);
72 //is.imbue(std::locale("C"));
75 std::getline(is, name, ' ');
77 if(name == "MaterialItem")
79 // u16 reads directly as a number (u8 doesn't)
84 // Convert old materials
86 material = content_translate_from_19_to_internal(material);
87 if(material > MAX_CONTENT)
88 throw SerializationError("Too large material number");
89 return new MaterialItem(gamedef, material, count);
91 else if(name == "MaterialItem2")
97 if(material > MAX_CONTENT)
98 throw SerializationError("Too large material number");
99 return new MaterialItem(gamedef, material, count);
101 else if(name == "node" || name == "NodeItem" || name == "MaterialItem3")
104 std::getline(is, all, '\n');
105 std::string nodename;
106 // First attempt to read inside ""
109 // If didn't skip to end, we have ""s
111 nodename = fnd.next("\"");
112 } else { // No luck, just read a word then
114 nodename = fnd.next(" ");
117 u16 count = stoi(trim(fnd.next("")));
120 return new MaterialItem(gamedef, nodename, count);
122 else if(name == "MBOItem")
124 std::string inventorystring;
125 std::getline(is, inventorystring, '|');
126 throw SerializationError("MBOItem not supported anymore");
128 else if(name == "craft" || name == "CraftItem")
131 std::getline(is, all, '\n');
133 // First attempt to read inside ""
136 // If didn't skip to end, we have ""s
138 subname = fnd.next("\"");
139 } else { // No luck, just read a word then
141 subname = fnd.next(" ");
145 u16 count = stoi(trim(fnd.next("")));
148 return new CraftItem(gamedef, subname, count);
150 else if(name == "tool" || name == "ToolItem")
153 std::getline(is, all, '\n');
154 std::string toolname;
155 // First attempt to read inside ""
158 // If didn't skip to end, we have ""s
160 toolname = fnd.next("\"");
161 } else { // No luck, just read a word then
163 toolname = fnd.next(" ");
167 u16 wear = stoi(trim(fnd.next("")));
168 return new ToolItem(gamedef, toolname, wear);
172 infostream<<"Unknown InventoryItem name=\""<<name<<"\""<<std::endl;
173 throw SerializationError("Unknown InventoryItem name");
177 InventoryItem* InventoryItem::deSerialize(const std::string &str,
180 std::istringstream is(str, std::ios_base::binary);
181 return deSerialize(is, gamedef);
184 std::string InventoryItem::getItemString() {
186 std::ostringstream os(std::ios_base::binary);
191 bool InventoryItem::dropOrPlace(ServerEnvironment *env,
192 ServerActiveObject *dropper,
193 v3f pos, bool place, s16 count)
196 Ensure that the block is loaded so that the item
197 can properly be added to the static list too
199 v3s16 blockpos = getNodeBlockPos(floatToInt(pos, BS));
200 MapBlock *block = env->getMap().emergeBlock(blockpos, false);
203 infostream<<"InventoryItem::dropOrPlace(): FAIL: block not found: "
204 <<blockpos.X<<","<<blockpos.Y<<","<<blockpos.Z
210 Take specified number of items,
211 but limit to getDropCount().
213 s16 dropcount = getDropCount();
214 if(count < 0 || count > dropcount)
216 if(count < 0 || count > getCount())
223 pos.Y -= BS*0.25; // let it drop a bit
225 //pos.X += BS*0.2*(float)myrand_range(-1000,1000)/1000.0;
226 //pos.Z += BS*0.2*(float)myrand_range(-1000,1000)/1000.0;
228 ServerActiveObject *obj = new ItemSAO(env, pos, getItemString());
229 // Add the object to the environment
230 env->addActiveObject(obj);
231 infostream<<"Dropped item"<<std::endl;
233 setCount(getCount() - count);
236 return getCount() < 1; // delete the item?
243 MaterialItem::MaterialItem(IGameDef *gamedef, std::string nodename, u16 count):
244 InventoryItem(gamedef, count)
247 nodename = "unknown_block";
249 // Convert directly to the correct name through aliases
250 m_nodename = gamedef->ndef()->getAlias(nodename);
252 // Legacy constructor
253 MaterialItem::MaterialItem(IGameDef *gamedef, content_t content, u16 count):
254 InventoryItem(gamedef, count)
256 NameIdMapping legacy_nimap;
257 content_mapnode_get_name_id_mapping(&legacy_nimap);
258 std::string nodename;
259 legacy_nimap.getName(content, nodename);
261 nodename = "unknown_block";
262 m_nodename = nodename;
266 video::ITexture * MaterialItem::getImage() const
268 return m_gamedef->getNodeDefManager()->get(m_nodename).inventory_texture;
272 bool MaterialItem::isCookable() const
274 INodeDefManager *ndef = m_gamedef->ndef();
275 const ContentFeatures &f = ndef->get(m_nodename);
276 return (f.cookresult_item != "");
279 InventoryItem *MaterialItem::createCookResult() const
281 INodeDefManager *ndef = m_gamedef->ndef();
282 const ContentFeatures &f = ndef->get(m_nodename);
283 std::istringstream is(f.cookresult_item, std::ios::binary);
284 return InventoryItem::deSerialize(is, m_gamedef);
287 float MaterialItem::getCookTime() const
289 INodeDefManager *ndef = m_gamedef->ndef();
290 const ContentFeatures &f = ndef->get(m_nodename);
291 return f.furnace_cooktime;
294 float MaterialItem::getBurnTime() const
296 INodeDefManager *ndef = m_gamedef->ndef();
297 const ContentFeatures &f = ndef->get(m_nodename);
298 return f.furnace_burntime;
301 content_t MaterialItem::getMaterial() const
303 INodeDefManager *ndef = m_gamedef->ndef();
304 content_t id = CONTENT_IGNORE;
305 ndef->getId(m_nodename, id);
313 ToolItem::ToolItem(IGameDef *gamedef, std::string toolname, u16 wear):
314 InventoryItem(gamedef, 1)
316 // Convert directly to the correct name through aliases
317 m_toolname = gamedef->tdef()->getAlias(toolname);
322 std::string ToolItem::getImageBasename() const
324 return m_gamedef->getToolDefManager()->getImagename(m_toolname);
328 video::ITexture * ToolItem::getImage() const
330 ITextureSource *tsrc = m_gamedef->tsrc();
332 std::string basename = getImageBasename();
335 Calculate a progress value with sane amount of
338 u32 maxprogress = 30;
339 u32 toolprogress = (65535-m_wear)/(65535/maxprogress);
341 float value_f = (float)toolprogress / (float)maxprogress;
342 std::ostringstream os;
343 os<<basename<<"^[progressbar"<<value_f;
345 return tsrc->getTextureRaw(os.str());
348 video::ITexture * ToolItem::getImageRaw() const
350 ITextureSource *tsrc = m_gamedef->tsrc();
352 return tsrc->getTextureRaw(getImageBasename());
356 bool ToolItem::isKnown() const
358 IToolDefManager *tdef = m_gamedef->tdef();
359 const ToolDefinition *def = tdef->getToolDefinition(m_toolname);
360 return (def != NULL);
367 CraftItem::CraftItem(IGameDef *gamedef, std::string subname, u16 count):
368 InventoryItem(gamedef, count)
370 // Convert directly to the correct name through aliases
371 m_subname = gamedef->cidef()->getAlias(subname);
375 video::ITexture * CraftItem::getImage() const
377 ICraftItemDefManager *cidef = m_gamedef->cidef();
378 ITextureSource *tsrc = m_gamedef->tsrc();
379 std::string imagename = cidef->getImagename(m_subname);
380 return tsrc->getTextureRaw(imagename);
384 bool CraftItem::isKnown() const
386 ICraftItemDefManager *cidef = m_gamedef->cidef();
387 const CraftItemDefinition *def = cidef->getCraftItemDefinition(m_subname);
388 return (def != NULL);
391 u16 CraftItem::getStackMax() const
393 ICraftItemDefManager *cidef = m_gamedef->cidef();
394 const CraftItemDefinition *def = cidef->getCraftItemDefinition(m_subname);
396 return InventoryItem::getStackMax();
397 return def->stack_max;
400 bool CraftItem::isUsable() const
402 ICraftItemDefManager *cidef = m_gamedef->cidef();
403 const CraftItemDefinition *def = cidef->getCraftItemDefinition(m_subname);
404 return def != NULL && def->usable;
407 bool CraftItem::isCookable() const
409 ICraftItemDefManager *cidef = m_gamedef->cidef();
410 const CraftItemDefinition *def = cidef->getCraftItemDefinition(m_subname);
411 return def != NULL && def->cookresult_item != "";
414 InventoryItem *CraftItem::createCookResult() const
416 ICraftItemDefManager *cidef = m_gamedef->cidef();
417 const CraftItemDefinition *def = cidef->getCraftItemDefinition(m_subname);
419 return InventoryItem::createCookResult();
420 std::istringstream is(def->cookresult_item, std::ios::binary);
421 return InventoryItem::deSerialize(is, m_gamedef);
424 float CraftItem::getCookTime() const
426 ICraftItemDefManager *cidef = m_gamedef->cidef();
427 const CraftItemDefinition *def = cidef->getCraftItemDefinition(m_subname);
429 return InventoryItem::getCookTime();
430 return def->furnace_cooktime;
433 float CraftItem::getBurnTime() const
435 ICraftItemDefManager *cidef = m_gamedef->cidef();
436 const CraftItemDefinition *def = cidef->getCraftItemDefinition(m_subname);
438 return InventoryItem::getBurnTime();
439 return def->furnace_burntime;
442 s16 CraftItem::getDropCount() const
445 ICraftItemDefManager *cidef = m_gamedef->cidef();
446 const CraftItemDefinition *def = cidef->getCraftItemDefinition(m_subname);
447 if(def != NULL && def->dropcount >= 0)
448 return def->dropcount;
450 return InventoryItem::getDropCount();
453 bool CraftItem::areLiquidsPointable() const
455 ICraftItemDefManager *cidef = m_gamedef->cidef();
456 const CraftItemDefinition *def = cidef->getCraftItemDefinition(m_subname);
457 return def != NULL && def->liquids_pointable;
460 bool CraftItem::dropOrPlace(ServerEnvironment *env,
461 ServerActiveObject *dropper,
462 v3f pos, bool place, s16 count)
467 bool callback_exists = false;
472 result = scriptapi_craftitem_on_place_on_ground(
474 m_subname.c_str(), dropper, pos,
478 // note: on_drop is fallback for on_place_on_ground
482 result = scriptapi_craftitem_on_drop(
484 m_subname.c_str(), dropper, pos,
490 // If the callback returned true, drop one item
492 setCount(getCount() - 1);
493 return getCount() < 1;
497 // If neither on_place_on_ground (if place==true)
498 // nor on_drop exists, call the base implementation
499 return InventoryItem::dropOrPlace(env, dropper, pos, place, count);
503 bool CraftItem::use(ServerEnvironment *env,
504 ServerActiveObject *user,
505 const PointedThing& pointed)
507 bool callback_exists = false;
510 result = scriptapi_craftitem_on_use(
512 m_subname.c_str(), user, pointed,
517 // If the callback returned true, drop one item
519 setCount(getCount() - 1);
520 return getCount() < 1;
524 // If neither on_place_on_ground (if place==true)
525 // nor on_drop exists, call the base implementation
526 return InventoryItem::use(env, user, pointed);
534 InventoryList::InventoryList(std::string name, u32 size)
542 InventoryList::~InventoryList()
544 for(u32 i=0; i<m_items.size(); i++)
551 void InventoryList::clearItems()
553 for(u32 i=0; i<m_items.size(); i++)
561 for(u32 i=0; i<m_size; i++)
563 m_items.push_back(NULL);
569 void InventoryList::setSize(u32 newsize)
571 if(newsize < m_items.size()){
572 for(u32 i=newsize; i<m_items.size(); i++){
576 m_items.erase(newsize, m_items.size() - newsize);
578 for(u32 i=m_items.size(); i<newsize; i++){
579 m_items.push_back(NULL);
585 void InventoryList::serialize(std::ostream &os) const
587 //os.imbue(std::locale("C"));
589 for(u32 i=0; i<m_items.size(); i++)
591 InventoryItem *item = m_items[i];
604 os<<"EndInventoryList\n";
607 void InventoryList::deSerialize(std::istream &is, IGameDef *gamedef)
609 //is.imbue(std::locale("C"));
617 std::getline(is, line, '\n');
619 std::istringstream iss(line);
620 //iss.imbue(std::locale("C"));
623 std::getline(iss, name, ' ');
625 if(name == "EndInventoryList")
629 // This is a temporary backwards compatibility fix
630 else if(name == "end")
634 else if(name == "Item")
636 if(item_i > getSize() - 1)
637 throw SerializationError("too many items");
638 InventoryItem *item = InventoryItem::deSerialize(iss, gamedef);
639 m_items[item_i++] = item;
641 else if(name == "Empty")
643 if(item_i > getSize() - 1)
644 throw SerializationError("too many items");
645 m_items[item_i++] = NULL;
649 throw SerializationError("Unknown inventory identifier");
654 InventoryList::InventoryList(const InventoryList &other)
657 Do this so that the items get cloned. Otherwise the pointers
658 in the array will just get copied.
663 InventoryList & InventoryList::operator = (const InventoryList &other)
665 m_name = other.m_name;
666 m_size = other.m_size;
668 for(u32 i=0; i<other.m_items.size(); i++)
670 InventoryItem *item = other.m_items[i];
673 m_items[i] = item->clone();
681 const std::string &InventoryList::getName() const
686 u32 InventoryList::getSize()
688 return m_items.size();
691 u32 InventoryList::getUsedSlots()
694 for(u32 i=0; i<m_items.size(); i++)
696 InventoryItem *item = m_items[i];
703 u32 InventoryList::getFreeSlots()
705 return getSize() - getUsedSlots();
708 const InventoryItem * InventoryList::getItem(u32 i) const
710 if(i >= m_items.size())
715 InventoryItem * InventoryList::getItem(u32 i)
717 if(i >= m_items.size())
722 InventoryItem * InventoryList::changeItem(u32 i, InventoryItem *newitem)
724 if(i >= m_items.size())
727 InventoryItem *olditem = m_items[i];
728 m_items[i] = newitem;
733 void InventoryList::deleteItem(u32 i)
735 assert(i < m_items.size());
736 InventoryItem *item = changeItem(i, NULL);
741 InventoryItem * InventoryList::addItem(InventoryItem *newitem)
747 First try to find if it could be added to some existing items
749 for(u32 i=0; i<m_items.size(); i++)
751 // Ignore empty slots
752 if(m_items[i] == NULL)
755 newitem = addItem(i, newitem);
757 return NULL; // All was eaten
761 Then try to add it to empty slots
763 for(u32 i=0; i<m_items.size(); i++)
765 // Ignore unempty slots
766 if(m_items[i] != NULL)
769 newitem = addItem(i, newitem);
771 return NULL; // All was eaten
778 InventoryItem * InventoryList::addItem(u32 i, InventoryItem *newitem)
782 if(i >= m_items.size())
787 // If it is an empty position, it's an easy job.
788 InventoryItem *to_item = getItem(i);
791 m_items[i] = newitem;
795 // If not addable, return the item
796 if(newitem->addableTo(to_item) == false)
799 // If the item fits fully in the slot, add counter and delete it
800 if(newitem->getCount() <= to_item->freeSpace())
802 to_item->add(newitem->getCount());
806 // Else the item does not fit fully. Add all that fits and return
810 u16 freespace = to_item->freeSpace();
811 to_item->add(freespace);
812 newitem->remove(freespace);
817 bool InventoryList::itemFits(const u32 i, const InventoryItem *newitem)
819 // If it is an empty position, it's an easy job.
820 const InventoryItem *to_item = getItem(i);
826 // If not addable, fail
827 if(newitem->addableTo(to_item) == false)
830 // If the item fits fully in the slot, pass
831 if(newitem->getCount() <= to_item->freeSpace())
839 bool InventoryList::roomForItem(const InventoryItem *item)
841 for(u32 i=0; i<m_items.size(); i++)
842 if(itemFits(i, item))
847 bool InventoryList::roomForCookedItem(const InventoryItem *item)
851 const InventoryItem *cook = item->createCookResult();
854 bool room = roomForItem(cook);
859 InventoryItem * InventoryList::takeItem(u32 i, u32 count)
866 InventoryItem *item = getItem(i);
867 // If it is an empty position, return NULL
871 if(count >= item->getCount())
873 // Get the item by swapping NULL to its place
874 return changeItem(i, NULL);
878 InventoryItem *item2 = item->clone();
880 item2->setCount(count);
887 void InventoryList::decrementMaterials(u16 count)
889 for(u32 i=0; i<m_items.size(); i++)
891 InventoryItem *item = takeItem(i, count);
897 void InventoryList::print(std::ostream &o)
899 o<<"InventoryList:"<<std::endl;
900 for(u32 i=0; i<m_items.size(); i++)
902 InventoryItem *item = m_items[i];
916 Inventory::~Inventory()
921 void Inventory::clear()
923 for(u32 i=0; i<m_lists.size(); i++)
930 Inventory::Inventory()
934 Inventory::Inventory(const Inventory &other)
939 Inventory & Inventory::operator = (const Inventory &other)
942 for(u32 i=0; i<other.m_lists.size(); i++)
944 m_lists.push_back(new InventoryList(*other.m_lists[i]));
949 void Inventory::serialize(std::ostream &os) const
951 for(u32 i=0; i<m_lists.size(); i++)
953 InventoryList *list = m_lists[i];
954 os<<"List "<<list->getName()<<" "<<list->getSize()<<"\n";
958 os<<"EndInventory\n";
961 void Inventory::deSerialize(std::istream &is, IGameDef *gamedef)
968 std::getline(is, line, '\n');
970 std::istringstream iss(line);
973 std::getline(iss, name, ' ');
975 if(name == "EndInventory")
979 // This is a temporary backwards compatibility fix
980 else if(name == "end")
984 else if(name == "List")
986 std::string listname;
989 std::getline(iss, listname, ' ');
992 InventoryList *list = new InventoryList(listname, listsize);
993 list->deSerialize(is, gamedef);
995 m_lists.push_back(list);
999 throw SerializationError("Unknown inventory identifier");
1004 InventoryList * Inventory::addList(const std::string &name, u32 size)
1006 s32 i = getListIndex(name);
1009 if(m_lists[i]->getSize() != size)
1012 m_lists[i] = new InventoryList(name, size);
1018 m_lists.push_back(new InventoryList(name, size));
1019 return m_lists.getLast();
1023 InventoryList * Inventory::getList(const std::string &name)
1025 s32 i = getListIndex(name);
1031 bool Inventory::deleteList(const std::string &name)
1033 s32 i = getListIndex(name);
1041 const InventoryList * Inventory::getList(const std::string &name) const
1043 s32 i = getListIndex(name);
1049 const s32 Inventory::getListIndex(const std::string &name) const
1051 for(u32 i=0; i<m_lists.size(); i++)
1053 if(m_lists[i]->getName() == name)