+bool CraftDefinitionShaped::check(const CraftInput &input, IGameDef *gamedef) const
+{
+ if (input.method != CRAFT_METHOD_NORMAL)
+ return false;
+
+ // Get input item matrix
+ std::vector<std::string> inp_names = craftGetItemNames(input.items, gamedef);
+ unsigned int inp_width = input.width;
+ if (inp_width == 0)
+ return false;
+ while (inp_names.size() % inp_width != 0)
+ inp_names.emplace_back("");
+
+ // Get input bounds
+ unsigned int inp_min_x = 0, inp_max_x = 0, inp_min_y = 0, inp_max_y = 0;
+ if (!craftGetBounds(inp_names, inp_width, inp_min_x, inp_max_x,
+ inp_min_y, inp_max_y))
+ return false; // it was empty
+
+ std::vector<std::string> rec_names;
+ if (hash_inited)
+ rec_names = recipe_names;
+ else
+ rec_names = craftGetItemNames(recipe, gamedef);
+
+ // Get recipe item matrix
+ unsigned int rec_width = width;
+ if (rec_width == 0)
+ return false;
+ while (rec_names.size() % rec_width != 0)
+ rec_names.emplace_back("");
+
+ // Get recipe bounds
+ unsigned int rec_min_x=0, rec_max_x=0, rec_min_y=0, rec_max_y=0;
+ if (!craftGetBounds(rec_names, rec_width, rec_min_x, rec_max_x,
+ rec_min_y, rec_max_y))
+ return false; // it was empty
+
+ // Different sizes?
+ if (inp_max_x - inp_min_x != rec_max_x - rec_min_x ||
+ inp_max_y - inp_min_y != rec_max_y - rec_min_y)
+ return false;
+
+ // Verify that all item names in the bounding box are equal
+ unsigned int w = inp_max_x - inp_min_x + 1;
+ unsigned int h = inp_max_y - inp_min_y + 1;
+
+ for (unsigned int y=0; y < h; y++) {
+ unsigned int inp_y = (inp_min_y + y) * inp_width;
+ unsigned int rec_y = (rec_min_y + y) * rec_width;
+
+ for (unsigned int x=0; x < w; x++) {
+ unsigned int inp_x = inp_min_x + x;
+ unsigned int rec_x = rec_min_x + x;
+
+ if (!inputItemMatchesRecipe(
+ inp_names[inp_y + inp_x],
+ rec_names[rec_y + rec_x], gamedef->idef())) {
+ return false;
+ }
+ }
+ }
+
+ return true;
+}
+
+CraftOutput CraftDefinitionShaped::getOutput(const CraftInput &input, IGameDef *gamedef) const
+{
+ return CraftOutput(output, 0);
+}
+
+CraftInput CraftDefinitionShaped::getInput(const CraftOutput &output, IGameDef *gamedef) const
+{
+ return CraftInput(CRAFT_METHOD_NORMAL,width,craftGetItems(recipe,gamedef));
+}
+
+void CraftDefinitionShaped::decrementInput(CraftInput &input, std::vector<ItemStack> &output_replacements,
+ IGameDef *gamedef) const
+{
+ craftDecrementOrReplaceInput(input, output_replacements, replacements, gamedef);
+}
+
+u64 CraftDefinitionShaped::getHash(CraftHashType type) const
+{
+ assert(hash_inited); // Pre-condition
+ assert((type == CRAFT_HASH_TYPE_ITEM_NAMES)
+ || (type == CRAFT_HASH_TYPE_COUNT)); // Pre-condition
+
+ std::vector<std::string> rec_names = recipe_names;
+ std::sort(rec_names.begin(), rec_names.end());
+ return getHashForGrid(type, rec_names);
+}
+
+void CraftDefinitionShaped::initHash(IGameDef *gamedef)
+{
+ if (hash_inited)
+ return;
+ hash_inited = true;
+ recipe_names = craftGetItemNames(recipe, gamedef);
+
+ if (hasGroupItem(recipe_names))
+ hash_type = CRAFT_HASH_TYPE_COUNT;
+ else
+ hash_type = CRAFT_HASH_TYPE_ITEM_NAMES;
+}
+
+std::string CraftDefinitionShaped::dump() const
+{
+ std::ostringstream os(std::ios::binary);
+ os << "(shaped, output=\"" << output
+ << "\", recipe=" << craftDumpMatrix(recipe, width)
+ << ", replacements=" << replacements.dump() << ")";
+ return os.str();
+}
+
+/*
+ CraftDefinitionShapeless
+*/
+
+CraftDefinitionShapeless::CraftDefinitionShapeless(
+ const std::string &output_,
+ const std::vector<std::string> &recipe_,
+ const CraftReplacements &replacements_):
+ output(output_), recipe(recipe_), replacements(replacements_)
+{
+ if (hasGroupItem(recipe))
+ priority = PRIORITY_SHAPELESS_AND_GROUPS;
+ else
+ priority = PRIORITY_SHAPELESS;
+}
+
+std::string CraftDefinitionShapeless::getName() const
+{
+ return "shapeless";
+}
+
+bool CraftDefinitionShapeless::check(const CraftInput &input, IGameDef *gamedef) const
+{
+ if (input.method != CRAFT_METHOD_NORMAL)
+ return false;
+
+ // Filter empty items out of input
+ std::vector<std::string> input_filtered;
+ for (const auto &item : input.items) {
+ if (!item.name.empty())
+ input_filtered.push_back(item.name);
+ }
+
+ // If there is a wrong number of items in input, no match
+ if (input_filtered.size() != recipe.size()) {
+ /*dstream<<"Number of input items ("<<input_filtered.size()
+ <<") does not match recipe size ("<<recipe.size()<<") "
+ <<"of recipe with output="<<output<<std::endl;*/
+ return false;
+ }
+
+ std::vector<std::string> recipe_copy;
+ if (hash_inited)
+ recipe_copy = recipe_names;
+ else {
+ recipe_copy = craftGetItemNames(recipe, gamedef);
+ std::sort(recipe_copy.begin(), recipe_copy.end());
+ }
+
+ // Try with all permutations of the recipe,
+ // start from the lexicographically first permutation (=sorted),
+ // recipe_names is pre-sorted
+ do {
+ // If all items match, the recipe matches
+ bool all_match = true;
+ //dstream<<"Testing recipe (output="<<output<<"):";
+ for (size_t i=0; i<recipe.size(); i++) {
+ //dstream<<" ("<<input_filtered[i]<<" == "<<recipe_copy[i]<<")";
+ if (!inputItemMatchesRecipe(input_filtered[i], recipe_copy[i],
+ gamedef->idef())) {
+ all_match = false;
+ break;
+ }
+ }
+ //dstream<<" -> match="<<all_match<<std::endl;
+ if (all_match)
+ return true;
+ } while (std::next_permutation(recipe_copy.begin(), recipe_copy.end()));
+
+ return false;
+}
+
+CraftOutput CraftDefinitionShapeless::getOutput(const CraftInput &input, IGameDef *gamedef) const
+{
+ return CraftOutput(output, 0);
+}
+
+CraftInput CraftDefinitionShapeless::getInput(const CraftOutput &output, IGameDef *gamedef) const
+{
+ return CraftInput(CRAFT_METHOD_NORMAL, 0, craftGetItems(recipe, gamedef));
+}
+
+void CraftDefinitionShapeless::decrementInput(CraftInput &input, std::vector<ItemStack> &output_replacements,
+ IGameDef *gamedef) const
+{
+ craftDecrementOrReplaceInput(input, output_replacements, replacements, gamedef);
+}
+
+u64 CraftDefinitionShapeless::getHash(CraftHashType type) const
+{
+ assert(hash_inited); // Pre-condition
+ assert(type == CRAFT_HASH_TYPE_ITEM_NAMES
+ || type == CRAFT_HASH_TYPE_COUNT); // Pre-condition
+ return getHashForGrid(type, recipe_names);
+}
+
+void CraftDefinitionShapeless::initHash(IGameDef *gamedef)
+{
+ if (hash_inited)
+ return;
+ hash_inited = true;
+ recipe_names = craftGetItemNames(recipe, gamedef);
+ std::sort(recipe_names.begin(), recipe_names.end());
+
+ if (hasGroupItem(recipe_names))
+ hash_type = CRAFT_HASH_TYPE_COUNT;
+ else
+ hash_type = CRAFT_HASH_TYPE_ITEM_NAMES;
+}
+
+std::string CraftDefinitionShapeless::dump() const
+{
+ std::ostringstream os(std::ios::binary);
+ os << "(shapeless, output=\"" << output
+ << "\", recipe=" << craftDumpMatrix(recipe, recipe.size())
+ << ", replacements=" << replacements.dump() << ")";
+ return os.str();
+}
+
+/*
+ CraftDefinitionToolRepair
+*/
+
+CraftDefinitionToolRepair::CraftDefinitionToolRepair(float additional_wear_):
+ additional_wear(additional_wear_)
+{
+ priority = PRIORITY_TOOLREPAIR;
+}
+
+static ItemStack craftToolRepair(
+ const ItemStack &item1,
+ const ItemStack &item2,
+ float additional_wear,
+ IGameDef *gamedef)
+{
+ IItemDefManager *idef = gamedef->idef();
+ if (item1.count != 1 || item2.count != 1 || item1.name != item2.name
+ || idef->get(item1.name).type != ITEM_TOOL
+ || itemgroup_get(idef->get(item1.name).groups, "disable_repair") == 1) {
+ // Failure
+ return ItemStack();
+ }
+
+ s32 item1_uses = 65536 - (u32) item1.wear;
+ s32 item2_uses = 65536 - (u32) item2.wear;
+ s32 new_uses = item1_uses + item2_uses;
+ s32 new_wear = 65536 - new_uses + floor(additional_wear * 65536 + 0.5);
+ if (new_wear >= 65536)
+ return ItemStack();
+ if (new_wear < 0)
+ new_wear = 0;
+
+ ItemStack repaired = item1;
+ repaired.wear = new_wear;
+ return repaired;
+}
+
+std::string CraftDefinitionToolRepair::getName() const
+{
+ return "toolrepair";
+}
+
+bool CraftDefinitionToolRepair::check(const CraftInput &input, IGameDef *gamedef) const
+{
+ if (input.method != CRAFT_METHOD_NORMAL)
+ return false;
+
+ ItemStack item1;
+ ItemStack item2;
+ for (const auto &item : input.items) {
+ if (!item.empty()) {
+ if (item1.empty())
+ item1 = item;
+ else if (item2.empty())
+ item2 = item;
+ else
+ return false;
+ }
+ }
+ ItemStack repaired = craftToolRepair(item1, item2, additional_wear, gamedef);
+ return !repaired.empty();
+}
+
+CraftOutput CraftDefinitionToolRepair::getOutput(const CraftInput &input, IGameDef *gamedef) const
+{
+ ItemStack item1;
+ ItemStack item2;
+ for (const auto &item : input.items) {
+ if (!item.empty()) {
+ if (item1.empty())
+ item1 = item;
+ else if (item2.empty())
+ item2 = item;
+ }
+ }
+ ItemStack repaired = craftToolRepair(item1, item2, additional_wear, gamedef);
+ return CraftOutput(repaired.getItemString(), 0);
+}
+
+CraftInput CraftDefinitionToolRepair::getInput(const CraftOutput &output, IGameDef *gamedef) const
+{
+ std::vector<ItemStack> stack;
+ stack.emplace_back();
+ return CraftInput(CRAFT_METHOD_COOKING, additional_wear, stack);
+}
+
+void CraftDefinitionToolRepair::decrementInput(CraftInput &input, std::vector<ItemStack> &output_replacements,
+ IGameDef *gamedef) const
+{
+ craftDecrementInput(input, gamedef);
+}
+
+std::string CraftDefinitionToolRepair::dump() const
+{
+ std::ostringstream os(std::ios::binary);
+ os << "(toolrepair, additional_wear=" << additional_wear << ")";
+ return os.str();
+}
+
+/*
+ CraftDefinitionCooking
+*/
+
+CraftDefinitionCooking::CraftDefinitionCooking(
+ const std::string &output_,
+ const std::string &recipe_,
+ float cooktime_,
+ const CraftReplacements &replacements_):
+ output(output_), recipe(recipe_), cooktime(cooktime_), replacements(replacements_)
+{
+ if (isGroupRecipeStr(recipe))
+ priority = PRIORITY_SHAPELESS_AND_GROUPS;
+ else
+ priority = PRIORITY_SHAPELESS;
+}
+
+std::string CraftDefinitionCooking::getName() const
+{
+ return "cooking";
+}
+
+bool CraftDefinitionCooking::check(const CraftInput &input, IGameDef *gamedef) const
+{
+ if (input.method != CRAFT_METHOD_COOKING)
+ return false;
+
+ // Filter empty items out of input
+ std::vector<std::string> input_filtered;
+ for (const auto &item : input.items) {
+ const std::string &name = item.name;
+ if (!name.empty())
+ input_filtered.push_back(name);
+ }
+
+ // If there is a wrong number of items in input, no match
+ if (input_filtered.size() != 1) {
+ /*dstream<<"Number of input items ("<<input_filtered.size()
+ <<") does not match recipe size (1) "
+ <<"of cooking recipe with output="<<output<<std::endl;*/
+ return false;
+ }
+
+ // Check the single input item
+ return inputItemMatchesRecipe(input_filtered[0], recipe, gamedef->idef());
+}
+
+CraftOutput CraftDefinitionCooking::getOutput(const CraftInput &input, IGameDef *gamedef) const
+{
+ return CraftOutput(output, cooktime);
+}
+
+CraftInput CraftDefinitionCooking::getInput(const CraftOutput &output, IGameDef *gamedef) const
+{
+ std::vector<std::string> rec;
+ rec.push_back(recipe);
+ return CraftInput(CRAFT_METHOD_COOKING,cooktime,craftGetItems(rec,gamedef));
+}
+
+void CraftDefinitionCooking::decrementInput(CraftInput &input, std::vector<ItemStack> &output_replacements,
+ IGameDef *gamedef) const
+{
+ craftDecrementOrReplaceInput(input, output_replacements, replacements, gamedef);
+}
+
+u64 CraftDefinitionCooking::getHash(CraftHashType type) const
+{
+ if (type == CRAFT_HASH_TYPE_ITEM_NAMES) {
+ return getHashForString(recipe_name);
+ }
+
+ if (type == CRAFT_HASH_TYPE_COUNT) {
+ return 1;
+ }
+
+ // illegal hash type for this CraftDefinition (pre-condition)
+ assert(false);
+ return 0;
+}
+
+void CraftDefinitionCooking::initHash(IGameDef *gamedef)
+{
+ if (hash_inited)
+ return;
+ hash_inited = true;
+ recipe_name = craftGetItemName(recipe, gamedef);
+
+ if (isGroupRecipeStr(recipe_name))
+ hash_type = CRAFT_HASH_TYPE_COUNT;
+ else
+ hash_type = CRAFT_HASH_TYPE_ITEM_NAMES;
+}
+
+std::string CraftDefinitionCooking::dump() const
+{
+ std::ostringstream os(std::ios::binary);
+ os << "(cooking, output=\"" << output
+ << "\", recipe=\"" << recipe
+ << "\", cooktime=" << cooktime << ")"
+ << ", replacements=" << replacements.dump() << ")";
+ return os.str();
+}
+
+/*
+ CraftDefinitionFuel
+*/
+
+CraftDefinitionFuel::CraftDefinitionFuel(
+ const std::string &recipe_,
+ float burntime_,
+ const CraftReplacements &replacements_):
+ recipe(recipe_), burntime(burntime_), replacements(replacements_)
+{
+ if (isGroupRecipeStr(recipe_name))
+ priority = PRIORITY_SHAPELESS_AND_GROUPS;
+ else
+ priority = PRIORITY_SHAPELESS;
+}
+
+std::string CraftDefinitionFuel::getName() const
+{
+ return "fuel";
+}
+
+bool CraftDefinitionFuel::check(const CraftInput &input, IGameDef *gamedef) const
+{
+ if (input.method != CRAFT_METHOD_FUEL)
+ return false;
+
+ // Filter empty items out of input
+ std::vector<std::string> input_filtered;
+ for (const auto &item : input.items) {
+ const std::string &name = item.name;
+ if (!name.empty())
+ input_filtered.push_back(name);
+ }
+
+ // If there is a wrong number of items in input, no match
+ if (input_filtered.size() != 1) {
+ /*dstream<<"Number of input items ("<<input_filtered.size()
+ <<") does not match recipe size (1) "
+ <<"of fuel recipe with burntime="<<burntime<<std::endl;*/
+ return false;
+ }
+
+ // Check the single input item
+ return inputItemMatchesRecipe(input_filtered[0], recipe, gamedef->idef());
+}
+
+CraftOutput CraftDefinitionFuel::getOutput(const CraftInput &input, IGameDef *gamedef) const
+{
+ return CraftOutput("", burntime);
+}
+
+CraftInput CraftDefinitionFuel::getInput(const CraftOutput &output, IGameDef *gamedef) const
+{
+ std::vector<std::string> rec;
+ rec.push_back(recipe);
+ return CraftInput(CRAFT_METHOD_COOKING,(int)burntime,craftGetItems(rec,gamedef));
+}
+
+void CraftDefinitionFuel::decrementInput(CraftInput &input, std::vector<ItemStack> &output_replacements,
+ IGameDef *gamedef) const
+{
+ craftDecrementOrReplaceInput(input, output_replacements, replacements, gamedef);
+}
+
+u64 CraftDefinitionFuel::getHash(CraftHashType type) const
+{
+ if (type == CRAFT_HASH_TYPE_ITEM_NAMES) {
+ return getHashForString(recipe_name);
+ }
+
+ if (type == CRAFT_HASH_TYPE_COUNT) {
+ return 1;
+ }
+
+ // illegal hash type for this CraftDefinition (pre-condition)
+ assert(false);
+ return 0;
+}
+
+void CraftDefinitionFuel::initHash(IGameDef *gamedef)
+{
+ if (hash_inited)
+ return;
+ hash_inited = true;
+ recipe_name = craftGetItemName(recipe, gamedef);
+
+ if (isGroupRecipeStr(recipe_name))
+ hash_type = CRAFT_HASH_TYPE_COUNT;
+ else
+ hash_type = CRAFT_HASH_TYPE_ITEM_NAMES;
+}
+
+std::string CraftDefinitionFuel::dump() const
+{
+ std::ostringstream os(std::ios::binary);
+ os << "(fuel, recipe=\"" << recipe
+ << "\", burntime=" << burntime << ")"
+ << ", replacements=" << replacements.dump() << ")";
+ return os.str();
+}
+
+/*
+ Craft definition manager
+*/
+