3 Copyright (C) 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 Lesser General Public License as published by
7 the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
15 You should have received a copy of the GNU Lesser 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.
22 #include "irrlichttypes.h"
27 #include "inventory.h"
28 #include "util/serialize.h"
30 // Check if input matches recipe
31 // Takes recipe groups into account
32 static bool inputItemMatchesRecipe(const std::string &inp_name,
33 const std::string &rec_name, IItemDefManager *idef)
36 if(inp_name == rec_name)
40 if(rec_name.substr(0,6) == "group:" && idef->isKnown(inp_name)){
41 std::string rec_group = rec_name.substr(6);
42 const struct ItemDefinition &def = idef->get(inp_name);
43 if(itemgroup_get(def.groups, rec_group) != 0)
51 // Deserialize an itemstring then return the name of the item
52 static std::string craftGetItemName(const std::string &itemstring, IGameDef *gamedef)
55 item.deSerialize(itemstring, gamedef->idef());
59 // (mapcar craftGetItemName itemstrings)
60 static std::vector<std::string> craftGetItemNames(
61 const std::vector<std::string> &itemstrings, IGameDef *gamedef)
63 std::vector<std::string> result;
64 for(std::vector<std::string>::const_iterator
65 i = itemstrings.begin();
66 i != itemstrings.end(); i++)
68 result.push_back(craftGetItemName(*i, gamedef));
73 // Get name of each item, and return them as a new list.
74 static std::vector<std::string> craftGetItemNames(
75 const std::vector<ItemStack> &items, IGameDef *gamedef)
77 std::vector<std::string> result;
78 for(std::vector<ItemStack>::const_iterator
80 i != items.end(); i++)
82 result.push_back(i->name);
87 // convert a list of item names, to ItemStacks.
88 static std::vector<ItemStack> craftGetItems(
89 const std::vector<std::string> &items, IGameDef *gamedef)
91 std::vector<ItemStack> result;
92 for(std::vector<std::string>::const_iterator
94 i != items.end(); i++)
96 result.push_back(ItemStack(std::string(*i),(u16)1,(u16)0,"",gamedef->getItemDefManager()));
101 // Compute bounding rectangle given a matrix of items
102 // Returns false if every item is ""
103 static bool craftGetBounds(const std::vector<std::string> &items, unsigned int width,
104 unsigned int &min_x, unsigned int &max_x,
105 unsigned int &min_y, unsigned int &max_y)
107 bool success = false;
110 for(std::vector<std::string>::const_iterator
112 i != items.end(); i++)
114 if(*i != "") // Is this an actual item?
118 // This is the first nonempty item
125 if(x < min_x) min_x = x;
126 if(x > max_x) max_x = x;
127 if(y < min_y) min_y = y;
128 if(y > max_y) max_y = y;
143 // Convert a list of item names to a multiset
144 static std::multiset<std::string> craftMakeMultiset(const std::vector<std::string> &names)
146 std::multiset<std::string> set;
147 for(std::vector<std::string>::const_iterator
149 i != names.end(); i++)
157 // Removes 1 from each item stack
158 static void craftDecrementInput(CraftInput &input, IGameDef *gamedef)
160 for(std::vector<ItemStack>::iterator
161 i = input.items.begin();
162 i != input.items.end(); i++)
169 // Removes 1 from each item stack with replacement support
170 // Example: if replacements contains the pair ("bucket:bucket_water", "bucket:bucket_empty"),
171 // a water bucket will not be removed but replaced by an empty bucket.
172 static void craftDecrementOrReplaceInput(CraftInput &input,
173 const CraftReplacements &replacements,
176 if(replacements.pairs.empty())
178 craftDecrementInput(input, gamedef);
182 // Make a copy of the replacements pair list
183 std::vector<std::pair<std::string, std::string> > pairs = replacements.pairs;
185 for(std::vector<ItemStack>::iterator
186 i = input.items.begin();
187 i != input.items.end(); i++)
191 // Find an appropriate replacement
192 bool found_replacement = false;
193 for(std::vector<std::pair<std::string, std::string> >::iterator
195 j != pairs.end(); j++)
198 from_item.deSerialize(j->first, gamedef->idef());
199 if(i->name == from_item.name)
201 i->deSerialize(j->second, gamedef->idef());
202 found_replacement = true;
207 // No replacement was found, simply decrement count to zero
208 if(!found_replacement)
211 else if(i->count >= 2)
213 // Ignore replacements for items with count >= 2
219 // Dump an itemstring matrix
220 static std::string craftDumpMatrix(const std::vector<std::string> &items,
223 std::ostringstream os(std::ios::binary);
226 for(std::vector<std::string>::const_iterator
228 i != items.end(); i++, x++)
239 os<<"\""<<(*i)<<"\"";
245 // Dump an item matrix
246 std::string craftDumpMatrix(const std::vector<ItemStack> &items,
249 std::ostringstream os(std::ios::binary);
252 for(std::vector<ItemStack>::const_iterator
254 i != items.end(); i++, x++)
265 os<<"\""<<(i->getItemString())<<"\"";
276 std::string CraftInput::dump() const
278 std::ostringstream os(std::ios::binary);
279 os<<"(method="<<((int)method)<<", items="<<craftDumpMatrix(items, width)<<")";
287 std::string CraftOutput::dump() const
289 std::ostringstream os(std::ios::binary);
290 os<<"(item=\""<<item<<"\", time="<<time<<")";
298 std::string CraftReplacements::dump() const
300 std::ostringstream os(std::ios::binary);
302 const char *sep = "";
303 for(std::vector<std::pair<std::string, std::string> >::const_iterator
305 i != pairs.end(); i++)
307 os<<sep<<"\""<<(i->first)<<"\"=>\""<<(i->second)<<"\"";
314 void CraftReplacements::serialize(std::ostream &os) const
316 writeU16(os, pairs.size());
317 for(u32 i=0; i<pairs.size(); i++)
319 os<<serializeString(pairs[i].first);
320 os<<serializeString(pairs[i].second);
324 void CraftReplacements::deSerialize(std::istream &is)
327 u32 count = readU16(is);
328 for(u32 i=0; i<count; i++)
330 std::string first = deSerializeString(is);
331 std::string second = deSerializeString(is);
332 pairs.push_back(std::make_pair(first, second));
340 void CraftDefinition::serialize(std::ostream &os) const
342 writeU8(os, 1); // version
343 os<<serializeString(getName());
347 CraftDefinition* CraftDefinition::deSerialize(std::istream &is)
349 int version = readU8(is);
350 if(version != 1) throw SerializationError(
351 "unsupported CraftDefinition version");
352 std::string name = deSerializeString(is);
353 CraftDefinition *def = NULL;
356 def = new CraftDefinitionShaped;
358 else if(name == "shapeless")
360 def = new CraftDefinitionShapeless;
362 else if(name == "toolrepair")
364 def = new CraftDefinitionToolRepair;
366 else if(name == "cooking")
368 def = new CraftDefinitionCooking;
370 else if(name == "fuel")
372 def = new CraftDefinitionFuel;
376 infostream<<"Unknown CraftDefinition name=\""<<name<<"\""<<std::endl;
377 throw SerializationError("Unknown CraftDefinition name");
379 def->deSerializeBody(is, version);
384 CraftDefinitionShaped
387 std::string CraftDefinitionShaped::getName() const
392 bool CraftDefinitionShaped::check(const CraftInput &input, IGameDef *gamedef) const
394 if(input.method != CRAFT_METHOD_NORMAL)
397 // Get input item matrix
398 std::vector<std::string> inp_names = craftGetItemNames(input.items, gamedef);
399 unsigned int inp_width = input.width;
402 while(inp_names.size() % inp_width != 0)
403 inp_names.push_back("");
406 unsigned int inp_min_x=0, inp_max_x=0, inp_min_y=0, inp_max_y=0;
407 if(!craftGetBounds(inp_names, inp_width, inp_min_x, inp_max_x, inp_min_y, inp_max_y))
408 return false; // it was empty
410 // Get recipe item matrix
411 std::vector<std::string> rec_names = craftGetItemNames(recipe, gamedef);
412 unsigned int rec_width = width;
415 while(rec_names.size() % rec_width != 0)
416 rec_names.push_back("");
419 unsigned int rec_min_x=0, rec_max_x=0, rec_min_y=0, rec_max_y=0;
420 if(!craftGetBounds(rec_names, rec_width, rec_min_x, rec_max_x, rec_min_y, rec_max_y))
421 return false; // it was empty
424 if(inp_max_x - inp_min_x != rec_max_x - rec_min_x)
426 if(inp_max_y - inp_min_y != rec_max_y - rec_min_y)
429 // Verify that all item names in the bounding box are equal
430 unsigned int w = inp_max_x - inp_min_x + 1;
431 unsigned int h = inp_max_y - inp_min_y + 1;
432 for(unsigned int y=0; y<h; y++)
433 for(unsigned int x=0; x<w; x++)
435 unsigned int inp_x = inp_min_x + x;
436 unsigned int inp_y = inp_min_y + y;
437 unsigned int rec_x = rec_min_x + x;
438 unsigned int rec_y = rec_min_y + y;
440 if(!inputItemMatchesRecipe(
441 inp_names[inp_y * inp_width + inp_x],
442 rec_names[rec_y * rec_width + rec_x], gamedef->idef())
451 CraftOutput CraftDefinitionShaped::getOutput(const CraftInput &input, IGameDef *gamedef) const
453 return CraftOutput(output, 0);
456 CraftInput CraftDefinitionShaped::getInput(const CraftOutput &output, IGameDef *gamedef) const
458 return CraftInput(CRAFT_METHOD_NORMAL,width,craftGetItems(recipe,gamedef));
461 void CraftDefinitionShaped::decrementInput(CraftInput &input, IGameDef *gamedef) const
463 craftDecrementOrReplaceInput(input, replacements, gamedef);
466 std::string CraftDefinitionShaped::dump() const
468 std::ostringstream os(std::ios::binary);
469 os<<"(shaped, output=\""<<output
470 <<"\", recipe="<<craftDumpMatrix(recipe, width)
471 <<", replacements="<<replacements.dump()<<")";
475 void CraftDefinitionShaped::serializeBody(std::ostream &os) const
477 os<<serializeString(output);
479 writeU16(os, recipe.size());
480 for(u32 i=0; i<recipe.size(); i++)
481 os<<serializeString(recipe[i]);
482 replacements.serialize(os);
485 void CraftDefinitionShaped::deSerializeBody(std::istream &is, int version)
487 if(version != 1) throw SerializationError(
488 "unsupported CraftDefinitionShaped version");
489 output = deSerializeString(is);
492 u32 count = readU16(is);
493 for(u32 i=0; i<count; i++)
494 recipe.push_back(deSerializeString(is));
495 replacements.deSerialize(is);
499 CraftDefinitionShapeless
502 std::string CraftDefinitionShapeless::getName() const
507 bool CraftDefinitionShapeless::check(const CraftInput &input, IGameDef *gamedef) const
509 if(input.method != CRAFT_METHOD_NORMAL)
512 // Get input item multiset
513 std::vector<std::string> inp_names = craftGetItemNames(input.items, gamedef);
514 std::multiset<std::string> inp_names_multiset = craftMakeMultiset(inp_names);
516 // Get recipe item multiset
517 std::vector<std::string> rec_names = craftGetItemNames(recipe, gamedef);
518 std::multiset<std::string> rec_names_multiset = craftMakeMultiset(rec_names);
520 // Recipe is matched when the multisets coincide
521 return inp_names_multiset == rec_names_multiset;
524 CraftOutput CraftDefinitionShapeless::getOutput(const CraftInput &input, IGameDef *gamedef) const
526 return CraftOutput(output, 0);
529 CraftInput CraftDefinitionShapeless::getInput(const CraftOutput &output, IGameDef *gamedef) const
531 return CraftInput(CRAFT_METHOD_NORMAL,0,craftGetItems(recipe,gamedef));
534 void CraftDefinitionShapeless::decrementInput(CraftInput &input, IGameDef *gamedef) const
536 craftDecrementOrReplaceInput(input, replacements, gamedef);
539 std::string CraftDefinitionShapeless::dump() const
541 std::ostringstream os(std::ios::binary);
542 os<<"(shapeless, output=\""<<output
543 <<"\", recipe="<<craftDumpMatrix(recipe, recipe.size())
544 <<", replacements="<<replacements.dump()<<")";
548 void CraftDefinitionShapeless::serializeBody(std::ostream &os) const
550 os<<serializeString(output);
551 writeU16(os, recipe.size());
552 for(u32 i=0; i<recipe.size(); i++)
553 os<<serializeString(recipe[i]);
554 replacements.serialize(os);
557 void CraftDefinitionShapeless::deSerializeBody(std::istream &is, int version)
559 if(version != 1) throw SerializationError(
560 "unsupported CraftDefinitionShapeless version");
561 output = deSerializeString(is);
563 u32 count = readU16(is);
564 for(u32 i=0; i<count; i++)
565 recipe.push_back(deSerializeString(is));
566 replacements.deSerialize(is);
570 CraftDefinitionToolRepair
573 static ItemStack craftToolRepair(
574 const ItemStack &item1,
575 const ItemStack &item2,
576 float additional_wear,
579 IItemDefManager *idef = gamedef->idef();
580 if(item1.count != 1 || item2.count != 1 || item1.name != item2.name
581 || idef->get(item1.name).type != ITEM_TOOL
582 || idef->get(item2.name).type != ITEM_TOOL)
588 s32 item1_uses = 65536 - (u32) item1.wear;
589 s32 item2_uses = 65536 - (u32) item2.wear;
590 s32 new_uses = item1_uses + item2_uses;
591 s32 new_wear = 65536 - new_uses + floor(additional_wear * 65536 + 0.5);
592 if(new_wear >= 65536)
597 ItemStack repaired = item1;
598 repaired.wear = new_wear;
602 std::string CraftDefinitionToolRepair::getName() const
607 bool CraftDefinitionToolRepair::check(const CraftInput &input, IGameDef *gamedef) const
609 if(input.method != CRAFT_METHOD_NORMAL)
614 for(std::vector<ItemStack>::const_iterator
615 i = input.items.begin();
616 i != input.items.end(); i++)
622 else if(item2.empty())
628 ItemStack repaired = craftToolRepair(item1, item2, additional_wear, gamedef);
629 return !repaired.empty();
632 CraftOutput CraftDefinitionToolRepair::getOutput(const CraftInput &input, IGameDef *gamedef) const
636 for(std::vector<ItemStack>::const_iterator
637 i = input.items.begin();
638 i != input.items.end(); i++)
644 else if(item2.empty())
648 ItemStack repaired = craftToolRepair(item1, item2, additional_wear, gamedef);
649 return CraftOutput(repaired.getItemString(), 0);
652 CraftInput CraftDefinitionToolRepair::getInput(const CraftOutput &output, IGameDef *gamedef) const
654 std::vector<ItemStack> stack;
655 stack.push_back(ItemStack());
656 return CraftInput(CRAFT_METHOD_COOKING,additional_wear,stack);
659 void CraftDefinitionToolRepair::decrementInput(CraftInput &input, IGameDef *gamedef) const
661 craftDecrementInput(input, gamedef);
664 std::string CraftDefinitionToolRepair::dump() const
666 std::ostringstream os(std::ios::binary);
667 os<<"(toolrepair, additional_wear="<<additional_wear<<")";
671 void CraftDefinitionToolRepair::serializeBody(std::ostream &os) const
673 writeF1000(os, additional_wear);
676 void CraftDefinitionToolRepair::deSerializeBody(std::istream &is, int version)
678 if(version != 1) throw SerializationError(
679 "unsupported CraftDefinitionToolRepair version");
680 additional_wear = readF1000(is);
684 CraftDefinitionCooking
687 std::string CraftDefinitionCooking::getName() const
692 bool CraftDefinitionCooking::check(const CraftInput &input, IGameDef *gamedef) const
694 if(input.method != CRAFT_METHOD_COOKING)
697 // Get input item multiset
698 std::vector<std::string> inp_names = craftGetItemNames(input.items, gamedef);
699 std::multiset<std::string> inp_names_multiset = craftMakeMultiset(inp_names);
701 // Get recipe item multiset
702 std::multiset<std::string> rec_names_multiset;
703 rec_names_multiset.insert(craftGetItemName(recipe, gamedef));
705 // Recipe is matched when the multisets coincide
706 return inp_names_multiset == rec_names_multiset;
709 CraftOutput CraftDefinitionCooking::getOutput(const CraftInput &input, IGameDef *gamedef) const
711 return CraftOutput(output, cooktime);
714 CraftInput CraftDefinitionCooking::getInput(const CraftOutput &output, IGameDef *gamedef) const
716 std::vector<std::string> rec;
717 rec.push_back(recipe);
718 return CraftInput(CRAFT_METHOD_COOKING,cooktime,craftGetItems(rec,gamedef));
721 void CraftDefinitionCooking::decrementInput(CraftInput &input, IGameDef *gamedef) const
723 craftDecrementOrReplaceInput(input, replacements, gamedef);
726 std::string CraftDefinitionCooking::dump() const
728 std::ostringstream os(std::ios::binary);
729 os<<"(cooking, output=\""<<output
730 <<"\", recipe=\""<<recipe
731 <<"\", cooktime="<<cooktime<<")"
732 <<", replacements="<<replacements.dump()<<")";
736 void CraftDefinitionCooking::serializeBody(std::ostream &os) const
738 os<<serializeString(output);
739 os<<serializeString(recipe);
740 writeF1000(os, cooktime);
741 replacements.serialize(os);
744 void CraftDefinitionCooking::deSerializeBody(std::istream &is, int version)
746 if(version != 1) throw SerializationError(
747 "unsupported CraftDefinitionCooking version");
748 output = deSerializeString(is);
749 recipe = deSerializeString(is);
750 cooktime = readF1000(is);
751 replacements.deSerialize(is);
758 std::string CraftDefinitionFuel::getName() const
763 bool CraftDefinitionFuel::check(const CraftInput &input, IGameDef *gamedef) const
765 if(input.method != CRAFT_METHOD_FUEL)
768 // Get input item multiset
769 std::vector<std::string> inp_names = craftGetItemNames(input.items, gamedef);
770 std::multiset<std::string> inp_names_multiset = craftMakeMultiset(inp_names);
772 // Get recipe item multiset
773 std::multiset<std::string> rec_names_multiset;
774 rec_names_multiset.insert(craftGetItemName(recipe, gamedef));
776 // Recipe is matched when the multisets coincide
777 return inp_names_multiset == rec_names_multiset;
780 CraftOutput CraftDefinitionFuel::getOutput(const CraftInput &input, IGameDef *gamedef) const
782 return CraftOutput("", burntime);
785 CraftInput CraftDefinitionFuel::getInput(const CraftOutput &output, IGameDef *gamedef) const
787 std::vector<std::string> rec;
788 rec.push_back(recipe);
789 return CraftInput(CRAFT_METHOD_COOKING,(int)burntime,craftGetItems(rec,gamedef));
792 void CraftDefinitionFuel::decrementInput(CraftInput &input, IGameDef *gamedef) const
794 craftDecrementOrReplaceInput(input, replacements, gamedef);
797 std::string CraftDefinitionFuel::dump() const
799 std::ostringstream os(std::ios::binary);
800 os<<"(fuel, recipe=\""<<recipe
801 <<"\", burntime="<<burntime<<")"
802 <<", replacements="<<replacements.dump()<<")";
806 void CraftDefinitionFuel::serializeBody(std::ostream &os) const
808 os<<serializeString(recipe);
809 writeF1000(os, burntime);
810 replacements.serialize(os);
813 void CraftDefinitionFuel::deSerializeBody(std::istream &is, int version)
815 if(version != 1) throw SerializationError(
816 "unsupported CraftDefinitionFuel version");
817 recipe = deSerializeString(is);
818 burntime = readF1000(is);
819 replacements.deSerialize(is);
823 Craft definition manager
826 class CCraftDefManager: public IWritableCraftDefManager
829 virtual ~CCraftDefManager()
833 virtual bool getCraftResult(CraftInput &input, CraftOutput &output,
834 bool decrementInput, IGameDef *gamedef) const
839 // If all input items are empty, abort.
840 bool all_empty = true;
841 for(std::vector<ItemStack>::const_iterator
842 i = input.items.begin();
843 i != input.items.end(); i++)
854 // Walk crafting definitions from back to front, so that later
855 // definitions can override earlier ones.
856 for(std::vector<CraftDefinition*>::const_reverse_iterator
857 i = m_craft_definitions.rbegin();
858 i != m_craft_definitions.rend(); i++)
860 CraftDefinition *def = *i;
862 /*infostream<<"Checking "<<input.dump()<<std::endl
863 <<" against "<<def->dump()<<std::endl;*/
866 if(def->check(input, gamedef))
868 // Get output, then decrement input (if requested)
869 output = def->getOutput(input, gamedef);
871 def->decrementInput(input, gamedef);
875 catch(SerializationError &e)
877 errorstream<<"getCraftResult: ERROR: "
878 <<"Serialization error in recipe "
879 <<def->dump()<<std::endl;
880 // then go on with the next craft definition
885 virtual bool getCraftRecipe(CraftInput &input, CraftOutput &output,
886 IGameDef *gamedef) const
892 // If output item is empty, abort.
893 if(output.item.empty())
896 // Walk crafting definitions from back to front, so that later
897 // definitions can override earlier ones.
898 for(std::vector<CraftDefinition*>::const_reverse_iterator
899 i = m_craft_definitions.rbegin();
900 i != m_craft_definitions.rend(); i++)
902 CraftDefinition *def = *i;
904 /*infostream<<"Checking "<<input.dump()<<std::endl
905 <<" against "<<def->dump()<<std::endl;*/
908 tmpout = def->getOutput(input, gamedef);
909 if(tmpout.item.substr(0,output.item.length()) == output.item)
911 // Get output, then decrement input (if requested)
912 input = def->getInput(output, gamedef);
916 catch(SerializationError &e)
918 errorstream<<"getCraftResult: ERROR: "
919 <<"Serialization error in recipe "
920 <<def->dump()<<std::endl;
921 // then go on with the next craft definition
926 virtual std::string dump() const
928 std::ostringstream os(std::ios::binary);
929 os<<"Crafting definitions:\n";
930 for(std::vector<CraftDefinition*>::const_iterator
931 i = m_craft_definitions.begin();
932 i != m_craft_definitions.end(); i++)
934 os<<(*i)->dump()<<"\n";
938 virtual void registerCraft(CraftDefinition *def)
940 verbosestream<<"registerCraft: registering craft definition: "
941 <<def->dump()<<std::endl;
942 m_craft_definitions.push_back(def);
946 for(std::vector<CraftDefinition*>::iterator
947 i = m_craft_definitions.begin();
948 i != m_craft_definitions.end(); i++){
951 m_craft_definitions.clear();
953 virtual void serialize(std::ostream &os) const
955 writeU8(os, 0); // version
956 u16 count = m_craft_definitions.size();
958 for(std::vector<CraftDefinition*>::const_iterator
959 i = m_craft_definitions.begin();
960 i != m_craft_definitions.end(); i++){
961 CraftDefinition *def = *i;
962 // Serialize wrapped in a string
963 std::ostringstream tmp_os(std::ios::binary);
964 def->serialize(tmp_os);
965 os<<serializeString(tmp_os.str());
968 virtual void deSerialize(std::istream &is)
973 int version = readU8(is);
974 if(version != 0) throw SerializationError(
975 "unsupported CraftDefManager version");
976 u16 count = readU16(is);
977 for(u16 i=0; i<count; i++){
978 // Deserialize a string and grab a CraftDefinition from it
979 std::istringstream tmp_is(deSerializeString(is), std::ios::binary);
980 CraftDefinition *def = CraftDefinition::deSerialize(tmp_is);
986 std::vector<CraftDefinition*> m_craft_definitions;
989 IWritableCraftDefManager* createCraftDefManager()
991 return new CCraftDefManager();