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 // Compute bounding rectangle given a matrix of items
88 // Returns false if every item is ""
89 static bool craftGetBounds(const std::vector<std::string> &items, unsigned int width,
90 unsigned int &min_x, unsigned int &max_x,
91 unsigned int &min_y, unsigned int &max_y)
96 for(std::vector<std::string>::const_iterator
98 i != items.end(); i++)
100 if(*i != "") // Is this an actual item?
104 // This is the first nonempty item
111 if(x < min_x) min_x = x;
112 if(x > max_x) max_x = x;
113 if(y < min_y) min_y = y;
114 if(y > max_y) max_y = y;
129 // Convert a list of item names to a multiset
130 static std::multiset<std::string> craftMakeMultiset(const std::vector<std::string> &names)
132 std::multiset<std::string> set;
133 for(std::vector<std::string>::const_iterator
135 i != names.end(); i++)
143 // Removes 1 from each item stack
144 static void craftDecrementInput(CraftInput &input, IGameDef *gamedef)
146 for(std::vector<ItemStack>::iterator
147 i = input.items.begin();
148 i != input.items.end(); i++)
155 // Removes 1 from each item stack with replacement support
156 // Example: if replacements contains the pair ("bucket:bucket_water", "bucket:bucket_empty"),
157 // a water bucket will not be removed but replaced by an empty bucket.
158 static void craftDecrementOrReplaceInput(CraftInput &input,
159 const CraftReplacements &replacements,
162 if(replacements.pairs.empty())
164 craftDecrementInput(input, gamedef);
168 // Make a copy of the replacements pair list
169 std::vector<std::pair<std::string, std::string> > pairs = replacements.pairs;
171 for(std::vector<ItemStack>::iterator
172 i = input.items.begin();
173 i != input.items.end(); i++)
177 // Find an appropriate replacement
178 bool found_replacement = false;
179 for(std::vector<std::pair<std::string, std::string> >::iterator
181 j != pairs.end(); j++)
184 from_item.deSerialize(j->first, gamedef->idef());
185 if(i->name == from_item.name)
187 i->deSerialize(j->second, gamedef->idef());
188 found_replacement = true;
193 // No replacement was found, simply decrement count to zero
194 if(!found_replacement)
197 else if(i->count >= 2)
199 // Ignore replacements for items with count >= 2
205 // Dump an itemstring matrix
206 static std::string craftDumpMatrix(const std::vector<std::string> &items,
209 std::ostringstream os(std::ios::binary);
212 for(std::vector<std::string>::const_iterator
214 i != items.end(); i++, x++)
225 os<<"\""<<(*i)<<"\"";
231 // Dump an item matrix
232 std::string craftDumpMatrix(const std::vector<ItemStack> &items,
235 std::ostringstream os(std::ios::binary);
238 for(std::vector<ItemStack>::const_iterator
240 i != items.end(); i++, x++)
251 os<<"\""<<(i->getItemString())<<"\"";
262 std::string CraftInput::dump() const
264 std::ostringstream os(std::ios::binary);
265 os<<"(method="<<((int)method)<<", items="<<craftDumpMatrix(items, width)<<")";
273 std::string CraftOutput::dump() const
275 std::ostringstream os(std::ios::binary);
276 os<<"(item=\""<<item<<"\", time="<<time<<")";
284 std::string CraftReplacements::dump() const
286 std::ostringstream os(std::ios::binary);
288 const char *sep = "";
289 for(std::vector<std::pair<std::string, std::string> >::const_iterator
291 i != pairs.end(); i++)
293 os<<sep<<"\""<<(i->first)<<"\"=>\""<<(i->second)<<"\"";
300 void CraftReplacements::serialize(std::ostream &os) const
302 writeU16(os, pairs.size());
303 for(u32 i=0; i<pairs.size(); i++)
305 os<<serializeString(pairs[i].first);
306 os<<serializeString(pairs[i].second);
310 void CraftReplacements::deSerialize(std::istream &is)
313 u32 count = readU16(is);
314 for(u32 i=0; i<count; i++)
316 std::string first = deSerializeString(is);
317 std::string second = deSerializeString(is);
318 pairs.push_back(std::make_pair(first, second));
326 void CraftDefinition::serialize(std::ostream &os) const
328 writeU8(os, 1); // version
329 os<<serializeString(getName());
333 CraftDefinition* CraftDefinition::deSerialize(std::istream &is)
335 int version = readU8(is);
336 if(version != 1) throw SerializationError(
337 "unsupported CraftDefinition version");
338 std::string name = deSerializeString(is);
339 CraftDefinition *def = NULL;
342 def = new CraftDefinitionShaped;
344 else if(name == "shapeless")
346 def = new CraftDefinitionShapeless;
348 else if(name == "toolrepair")
350 def = new CraftDefinitionToolRepair;
352 else if(name == "cooking")
354 def = new CraftDefinitionCooking;
356 else if(name == "fuel")
358 def = new CraftDefinitionFuel;
362 infostream<<"Unknown CraftDefinition name=\""<<name<<"\""<<std::endl;
363 throw SerializationError("Unknown CraftDefinition name");
365 def->deSerializeBody(is, version);
370 CraftDefinitionShaped
373 std::string CraftDefinitionShaped::getName() const
378 bool CraftDefinitionShaped::check(const CraftInput &input, IGameDef *gamedef) const
380 if(input.method != CRAFT_METHOD_NORMAL)
383 // Get input item matrix
384 std::vector<std::string> inp_names = craftGetItemNames(input.items, gamedef);
385 unsigned int inp_width = input.width;
388 while(inp_names.size() % inp_width != 0)
389 inp_names.push_back("");
392 unsigned int inp_min_x=0, inp_max_x=0, inp_min_y=0, inp_max_y=0;
393 if(!craftGetBounds(inp_names, inp_width, inp_min_x, inp_max_x, inp_min_y, inp_max_y))
394 return false; // it was empty
396 // Get recipe item matrix
397 std::vector<std::string> rec_names = craftGetItemNames(recipe, gamedef);
398 unsigned int rec_width = width;
401 while(rec_names.size() % rec_width != 0)
402 rec_names.push_back("");
405 unsigned int rec_min_x=0, rec_max_x=0, rec_min_y=0, rec_max_y=0;
406 if(!craftGetBounds(rec_names, rec_width, rec_min_x, rec_max_x, rec_min_y, rec_max_y))
407 return false; // it was empty
410 if(inp_max_x - inp_min_x != rec_max_x - rec_min_x)
412 if(inp_max_y - inp_min_y != rec_max_y - rec_min_y)
415 // Verify that all item names in the bounding box are equal
416 unsigned int w = inp_max_x - inp_min_x + 1;
417 unsigned int h = inp_max_y - inp_min_y + 1;
418 for(unsigned int y=0; y<h; y++)
419 for(unsigned int x=0; x<w; x++)
421 unsigned int inp_x = inp_min_x + x;
422 unsigned int inp_y = inp_min_y + y;
423 unsigned int rec_x = rec_min_x + x;
424 unsigned int rec_y = rec_min_y + y;
426 if(!inputItemMatchesRecipe(
427 inp_names[inp_y * inp_width + inp_x],
428 rec_names[rec_y * rec_width + rec_x], gamedef->idef())
437 CraftOutput CraftDefinitionShaped::getOutput(const CraftInput &input, IGameDef *gamedef) const
439 return CraftOutput(output, 0);
442 void CraftDefinitionShaped::decrementInput(CraftInput &input, IGameDef *gamedef) const
444 craftDecrementOrReplaceInput(input, replacements, gamedef);
447 std::string CraftDefinitionShaped::dump() const
449 std::ostringstream os(std::ios::binary);
450 os<<"(shaped, output=\""<<output
451 <<"\", recipe="<<craftDumpMatrix(recipe, width)
452 <<", replacements="<<replacements.dump()<<")";
456 void CraftDefinitionShaped::serializeBody(std::ostream &os) const
458 os<<serializeString(output);
460 writeU16(os, recipe.size());
461 for(u32 i=0; i<recipe.size(); i++)
462 os<<serializeString(recipe[i]);
463 replacements.serialize(os);
466 void CraftDefinitionShaped::deSerializeBody(std::istream &is, int version)
468 if(version != 1) throw SerializationError(
469 "unsupported CraftDefinitionShaped version");
470 output = deSerializeString(is);
473 u32 count = readU16(is);
474 for(u32 i=0; i<count; i++)
475 recipe.push_back(deSerializeString(is));
476 replacements.deSerialize(is);
480 CraftDefinitionShapeless
483 std::string CraftDefinitionShapeless::getName() const
488 bool CraftDefinitionShapeless::check(const CraftInput &input, IGameDef *gamedef) const
490 if(input.method != CRAFT_METHOD_NORMAL)
493 // Get input item multiset
494 std::vector<std::string> inp_names = craftGetItemNames(input.items, gamedef);
495 std::multiset<std::string> inp_names_multiset = craftMakeMultiset(inp_names);
497 // Get recipe item multiset
498 std::vector<std::string> rec_names = craftGetItemNames(recipe, gamedef);
499 std::multiset<std::string> rec_names_multiset = craftMakeMultiset(rec_names);
501 // Recipe is matched when the multisets coincide
502 return inp_names_multiset == rec_names_multiset;
505 CraftOutput CraftDefinitionShapeless::getOutput(const CraftInput &input, IGameDef *gamedef) const
507 return CraftOutput(output, 0);
510 void CraftDefinitionShapeless::decrementInput(CraftInput &input, IGameDef *gamedef) const
512 craftDecrementOrReplaceInput(input, replacements, gamedef);
515 std::string CraftDefinitionShapeless::dump() const
517 std::ostringstream os(std::ios::binary);
518 os<<"(shapeless, output=\""<<output
519 <<"\", recipe="<<craftDumpMatrix(recipe, recipe.size())
520 <<", replacements="<<replacements.dump()<<")";
524 void CraftDefinitionShapeless::serializeBody(std::ostream &os) const
526 os<<serializeString(output);
527 writeU16(os, recipe.size());
528 for(u32 i=0; i<recipe.size(); i++)
529 os<<serializeString(recipe[i]);
530 replacements.serialize(os);
533 void CraftDefinitionShapeless::deSerializeBody(std::istream &is, int version)
535 if(version != 1) throw SerializationError(
536 "unsupported CraftDefinitionShapeless version");
537 output = deSerializeString(is);
539 u32 count = readU16(is);
540 for(u32 i=0; i<count; i++)
541 recipe.push_back(deSerializeString(is));
542 replacements.deSerialize(is);
546 CraftDefinitionToolRepair
549 static ItemStack craftToolRepair(
550 const ItemStack &item1,
551 const ItemStack &item2,
552 float additional_wear,
555 IItemDefManager *idef = gamedef->idef();
556 if(item1.count != 1 || item2.count != 1 || item1.name != item2.name
557 || idef->get(item1.name).type != ITEM_TOOL
558 || idef->get(item2.name).type != ITEM_TOOL)
564 s32 item1_uses = 65536 - (u32) item1.wear;
565 s32 item2_uses = 65536 - (u32) item2.wear;
566 s32 new_uses = item1_uses + item2_uses;
567 s32 new_wear = 65536 - new_uses + floor(additional_wear * 65536 + 0.5);
568 if(new_wear >= 65536)
573 ItemStack repaired = item1;
574 repaired.wear = new_wear;
578 std::string CraftDefinitionToolRepair::getName() const
583 bool CraftDefinitionToolRepair::check(const CraftInput &input, IGameDef *gamedef) const
585 if(input.method != CRAFT_METHOD_NORMAL)
590 for(std::vector<ItemStack>::const_iterator
591 i = input.items.begin();
592 i != input.items.end(); i++)
598 else if(item2.empty())
604 ItemStack repaired = craftToolRepair(item1, item2, additional_wear, gamedef);
605 return !repaired.empty();
608 CraftOutput CraftDefinitionToolRepair::getOutput(const CraftInput &input, IGameDef *gamedef) const
612 for(std::vector<ItemStack>::const_iterator
613 i = input.items.begin();
614 i != input.items.end(); i++)
620 else if(item2.empty())
624 ItemStack repaired = craftToolRepair(item1, item2, additional_wear, gamedef);
625 return CraftOutput(repaired.getItemString(), 0);
628 void CraftDefinitionToolRepair::decrementInput(CraftInput &input, IGameDef *gamedef) const
630 craftDecrementInput(input, gamedef);
633 std::string CraftDefinitionToolRepair::dump() const
635 std::ostringstream os(std::ios::binary);
636 os<<"(toolrepair, additional_wear="<<additional_wear<<")";
640 void CraftDefinitionToolRepair::serializeBody(std::ostream &os) const
642 writeF1000(os, additional_wear);
645 void CraftDefinitionToolRepair::deSerializeBody(std::istream &is, int version)
647 if(version != 1) throw SerializationError(
648 "unsupported CraftDefinitionToolRepair version");
649 additional_wear = readF1000(is);
653 CraftDefinitionCooking
656 std::string CraftDefinitionCooking::getName() const
661 bool CraftDefinitionCooking::check(const CraftInput &input, IGameDef *gamedef) const
663 if(input.method != CRAFT_METHOD_COOKING)
666 // Get input item multiset
667 std::vector<std::string> inp_names = craftGetItemNames(input.items, gamedef);
668 std::multiset<std::string> inp_names_multiset = craftMakeMultiset(inp_names);
670 // Get recipe item multiset
671 std::multiset<std::string> rec_names_multiset;
672 rec_names_multiset.insert(craftGetItemName(recipe, gamedef));
674 // Recipe is matched when the multisets coincide
675 return inp_names_multiset == rec_names_multiset;
678 CraftOutput CraftDefinitionCooking::getOutput(const CraftInput &input, IGameDef *gamedef) const
680 return CraftOutput(output, cooktime);
683 void CraftDefinitionCooking::decrementInput(CraftInput &input, IGameDef *gamedef) const
685 craftDecrementOrReplaceInput(input, replacements, gamedef);
688 std::string CraftDefinitionCooking::dump() const
690 std::ostringstream os(std::ios::binary);
691 os<<"(cooking, output=\""<<output
692 <<"\", recipe=\""<<recipe
693 <<"\", cooktime="<<cooktime<<")"
694 <<", replacements="<<replacements.dump()<<")";
698 void CraftDefinitionCooking::serializeBody(std::ostream &os) const
700 os<<serializeString(output);
701 os<<serializeString(recipe);
702 writeF1000(os, cooktime);
703 replacements.serialize(os);
706 void CraftDefinitionCooking::deSerializeBody(std::istream &is, int version)
708 if(version != 1) throw SerializationError(
709 "unsupported CraftDefinitionCooking version");
710 output = deSerializeString(is);
711 recipe = deSerializeString(is);
712 cooktime = readF1000(is);
713 replacements.deSerialize(is);
720 std::string CraftDefinitionFuel::getName() const
725 bool CraftDefinitionFuel::check(const CraftInput &input, IGameDef *gamedef) const
727 if(input.method != CRAFT_METHOD_FUEL)
730 // Get input item multiset
731 std::vector<std::string> inp_names = craftGetItemNames(input.items, gamedef);
732 std::multiset<std::string> inp_names_multiset = craftMakeMultiset(inp_names);
734 // Get recipe item multiset
735 std::multiset<std::string> rec_names_multiset;
736 rec_names_multiset.insert(craftGetItemName(recipe, gamedef));
738 // Recipe is matched when the multisets coincide
739 return inp_names_multiset == rec_names_multiset;
742 CraftOutput CraftDefinitionFuel::getOutput(const CraftInput &input, IGameDef *gamedef) const
744 return CraftOutput("", burntime);
747 void CraftDefinitionFuel::decrementInput(CraftInput &input, IGameDef *gamedef) const
749 craftDecrementOrReplaceInput(input, replacements, gamedef);
752 std::string CraftDefinitionFuel::dump() const
754 std::ostringstream os(std::ios::binary);
755 os<<"(fuel, recipe=\""<<recipe
756 <<"\", burntime="<<burntime<<")"
757 <<", replacements="<<replacements.dump()<<")";
761 void CraftDefinitionFuel::serializeBody(std::ostream &os) const
763 os<<serializeString(recipe);
764 writeF1000(os, burntime);
765 replacements.serialize(os);
768 void CraftDefinitionFuel::deSerializeBody(std::istream &is, int version)
770 if(version != 1) throw SerializationError(
771 "unsupported CraftDefinitionFuel version");
772 recipe = deSerializeString(is);
773 burntime = readF1000(is);
774 replacements.deSerialize(is);
778 Craft definition manager
781 class CCraftDefManager: public IWritableCraftDefManager
784 virtual ~CCraftDefManager()
788 virtual bool getCraftResult(CraftInput &input, CraftOutput &output,
789 bool decrementInput, IGameDef *gamedef) const
794 // If all input items are empty, abort.
795 bool all_empty = true;
796 for(std::vector<ItemStack>::const_iterator
797 i = input.items.begin();
798 i != input.items.end(); i++)
809 // Walk crafting definitions from back to front, so that later
810 // definitions can override earlier ones.
811 for(std::vector<CraftDefinition*>::const_reverse_iterator
812 i = m_craft_definitions.rbegin();
813 i != m_craft_definitions.rend(); i++)
815 CraftDefinition *def = *i;
817 /*infostream<<"Checking "<<input.dump()<<std::endl
818 <<" against "<<def->dump()<<std::endl;*/
821 if(def->check(input, gamedef))
823 // Get output, then decrement input (if requested)
824 output = def->getOutput(input, gamedef);
826 def->decrementInput(input, gamedef);
830 catch(SerializationError &e)
832 errorstream<<"getCraftResult: ERROR: "
833 <<"Serialization error in recipe "
834 <<def->dump()<<std::endl;
835 // then go on with the next craft definition
840 virtual std::string dump() const
842 std::ostringstream os(std::ios::binary);
843 os<<"Crafting definitions:\n";
844 for(std::vector<CraftDefinition*>::const_iterator
845 i = m_craft_definitions.begin();
846 i != m_craft_definitions.end(); i++)
848 os<<(*i)->dump()<<"\n";
852 virtual void registerCraft(CraftDefinition *def)
854 verbosestream<<"registerCraft: registering craft definition: "
855 <<def->dump()<<std::endl;
856 m_craft_definitions.push_back(def);
860 for(std::vector<CraftDefinition*>::iterator
861 i = m_craft_definitions.begin();
862 i != m_craft_definitions.end(); i++){
865 m_craft_definitions.clear();
867 virtual void serialize(std::ostream &os) const
869 writeU8(os, 0); // version
870 u16 count = m_craft_definitions.size();
872 for(std::vector<CraftDefinition*>::const_iterator
873 i = m_craft_definitions.begin();
874 i != m_craft_definitions.end(); i++){
875 CraftDefinition *def = *i;
876 // Serialize wrapped in a string
877 std::ostringstream tmp_os(std::ios::binary);
878 def->serialize(tmp_os);
879 os<<serializeString(tmp_os.str());
882 virtual void deSerialize(std::istream &is)
887 int version = readU8(is);
888 if(version != 0) throw SerializationError(
889 "unsupported CraftDefManager version");
890 u16 count = readU16(is);
891 for(u16 i=0; i<count; i++){
892 // Deserialize a string and grab a CraftDefinition from it
893 std::istringstream tmp_is(deSerializeString(is), std::ios::binary);
894 CraftDefinition *def = CraftDefinition::deSerialize(tmp_is);
900 std::vector<CraftDefinition*> m_craft_definitions;
903 IWritableCraftDefManager* createCraftDefManager()
905 return new CCraftDefManager();