Translated using Weblate (Chinese (Simplified))
[oweals/minetest.git] / src / craftdef.cpp
1 /*
2 Minetest
3 Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
4
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.
9
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.
14
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.
18 */
19
20 #include "craftdef.h"
21
22 #include "irrlichttypes.h"
23 #include "log.h"
24 #include <sstream>
25 #include <set>
26 #include <algorithm>
27 #include "gamedef.h"
28 #include "inventory.h"
29 #include "util/serialize.h"
30 #include "util/string.h"
31 #include "util/numeric.h"
32 #include "util/strfnd.h"
33 #include "exceptions.h"
34
35 inline bool isGroupRecipeStr(const std::string &rec_name)
36 {
37         return str_starts_with(rec_name, std::string("group:"));
38 }
39
40 static bool hasGroupItem(const std::vector<std::string> &recipe)
41 {
42         for (const auto &item : recipe) {
43                 if (isGroupRecipeStr(item))
44                         return true;
45         }
46         return false;
47 }
48
49 inline u64 getHashForString(const std::string &recipe_str)
50 {
51         /*errorstream << "Hashing craft string  \"" << recipe_str << '"';*/
52         return murmur_hash_64_ua(recipe_str.data(), recipe_str.length(), 0xdeadbeef);
53 }
54
55 static u64 getHashForGrid(CraftHashType type, const std::vector<std::string> &grid_names)
56 {
57         switch (type) {
58                 case CRAFT_HASH_TYPE_ITEM_NAMES: {
59                         std::ostringstream os;
60                         bool is_first = true;
61                         for (const std::string &grid_name : grid_names) {
62                                 if (!grid_name.empty()) {
63                                         os << (is_first ? "" : "\n") << grid_name;
64                                         is_first = false;
65                                 }
66                         }
67                         return getHashForString(os.str());
68                 } case CRAFT_HASH_TYPE_COUNT: {
69                         u64 cnt = 0;
70                         for (const std::string &grid_name : grid_names)
71                                 if (!grid_name.empty())
72                                         cnt++;
73                         return cnt;
74                 } case CRAFT_HASH_TYPE_UNHASHED:
75                         return 0;
76         }
77         // invalid CraftHashType
78         assert(false);
79         return 0;
80 }
81
82 // Check if input matches recipe
83 // Takes recipe groups into account
84 static bool inputItemMatchesRecipe(const std::string &inp_name,
85                 const std::string &rec_name, IItemDefManager *idef)
86 {
87         // Exact name
88         if (inp_name == rec_name)
89                 return true;
90
91         // Group
92         if (isGroupRecipeStr(rec_name) && idef->isKnown(inp_name)) {
93                 const struct ItemDefinition &def = idef->get(inp_name);
94                 Strfnd f(rec_name.substr(6));
95                 bool all_groups_match = true;
96                 do {
97                         std::string check_group = f.next(",");
98                         if (itemgroup_get(def.groups, check_group) == 0) {
99                                 all_groups_match = false;
100                                 break;
101                         }
102                 } while (!f.at_end());
103                 if (all_groups_match)
104                         return true;
105         }
106
107         // Didn't match
108         return false;
109 }
110
111 // Deserialize an itemstring then return the name of the item
112 static std::string craftGetItemName(const std::string &itemstring, IGameDef *gamedef)
113 {
114         ItemStack item;
115         item.deSerialize(itemstring, gamedef->idef());
116         return item.name;
117 }
118
119 // (mapcar craftGetItemName itemstrings)
120 static std::vector<std::string> craftGetItemNames(
121                 const std::vector<std::string> &itemstrings, IGameDef *gamedef)
122 {
123         std::vector<std::string> result;
124         result.reserve(itemstrings.size());
125         for (const auto &itemstring : itemstrings) {
126                 result.push_back(craftGetItemName(itemstring, gamedef));
127         }
128         return result;
129 }
130
131 // Get name of each item, and return them as a new list.
132 static std::vector<std::string> craftGetItemNames(
133                 const std::vector<ItemStack> &items, IGameDef *gamedef)
134 {
135         std::vector<std::string> result;
136         result.reserve(items.size());
137         for (const auto &item : items) {
138                 result.push_back(item.name);
139         }
140         return result;
141 }
142
143 // convert a list of item names, to ItemStacks.
144 static std::vector<ItemStack> craftGetItems(
145                 const std::vector<std::string> &items, IGameDef *gamedef)
146 {
147         std::vector<ItemStack> result;
148         result.reserve(items.size());
149         for (const auto &item : items) {
150                 result.emplace_back(std::string(item), (u16)1,
151                         (u16)0, gamedef->getItemDefManager());
152         }
153         return result;
154 }
155
156 // Compute bounding rectangle given a matrix of items
157 // Returns false if every item is ""
158 static bool craftGetBounds(const std::vector<std::string> &items, unsigned int width,
159                 unsigned int &min_x, unsigned int &max_x,
160                 unsigned int &min_y, unsigned int &max_y)
161 {
162         bool success = false;
163         unsigned int x = 0;
164         unsigned int y = 0;
165         for (const std::string &item : items) {
166                 // Is this an actual item?
167                 if (!item.empty()) {
168                         if (!success) {
169                                 // This is the first nonempty item
170                                 min_x = max_x = x;
171                                 min_y = max_y = y;
172                                 success = true;
173                         } else {
174                                 if (x < min_x) min_x = x;
175                                 if (x > max_x) max_x = x;
176                                 if (y < min_y) min_y = y;
177                                 if (y > max_y) max_y = y;
178                         }
179                 }
180
181                 // Step coordinate
182                 x++;
183                 if (x == width) {
184                         x = 0;
185                         y++;
186                 }
187         }
188         return success;
189 }
190
191 // Removes 1 from each item stack
192 static void craftDecrementInput(CraftInput &input, IGameDef *gamedef)
193 {
194         for (auto &item : input.items) {
195                 if (item.count != 0)
196                         item.remove(1);
197         }
198 }
199
200 // Removes 1 from each item stack with replacement support
201 // Example: if replacements contains the pair ("bucket:bucket_water", "bucket:bucket_empty"),
202 //   a water bucket will not be removed but replaced by an empty bucket.
203 static void craftDecrementOrReplaceInput(CraftInput &input,
204                 std::vector<ItemStack> &output_replacements,
205                 const CraftReplacements &replacements,
206                 IGameDef *gamedef)
207 {
208         if (replacements.pairs.empty()) {
209                 craftDecrementInput(input, gamedef);
210                 return;
211         }
212
213         // Make a copy of the replacements pair list
214         std::vector<std::pair<std::string, std::string> > pairs = replacements.pairs;
215
216         for (auto &item : input.items) {
217                 // Find an appropriate replacement
218                 bool found_replacement = false;
219                 for (auto j = pairs.begin(); j != pairs.end(); ++j) {
220                         if (inputItemMatchesRecipe(item.name, j->first, gamedef->idef())) {
221                                 if (item.count == 1) {
222                                         item.deSerialize(j->second, gamedef->idef());
223                                         found_replacement = true;
224                                         pairs.erase(j);
225                                         break;
226                                 }
227
228                                 ItemStack rep;
229                                 rep.deSerialize(j->second, gamedef->idef());
230                                 item.remove(1);
231                                 found_replacement = true;
232                                 output_replacements.push_back(rep);
233                                 break;
234
235                         }
236                 }
237                 // No replacement was found, simply decrement count by one
238                 if (!found_replacement && item.count > 0)
239                         item.remove(1);
240         }
241 }
242
243 // Dump an itemstring matrix
244 static std::string craftDumpMatrix(const std::vector<std::string> &items,
245                 unsigned int width)
246 {
247         std::ostringstream os(std::ios::binary);
248         os << "{ ";
249         unsigned int x = 0;
250         for(std::vector<std::string>::size_type i = 0;
251                         i < items.size(); i++, x++) {
252                 if (x == width) {
253                         os << "; ";
254                         x = 0;
255                 } else if (x != 0) {
256                         os << ",";
257                 }
258                 os << '"' << items[i] << '"';
259         }
260         os << " }";
261         return os.str();
262 }
263
264 // Dump an item matrix
265 std::string craftDumpMatrix(const std::vector<ItemStack> &items,
266                 unsigned int width)
267 {
268         std::ostringstream os(std::ios::binary);
269         os << "{ ";
270         unsigned int x = 0;
271         for (std::vector<ItemStack>::size_type i = 0;
272                         i < items.size(); i++, x++) {
273                 if (x == width) {
274                         os << "; ";
275                         x = 0;
276                 } else if (x != 0) {
277                         os << ",";
278                 }
279                 os << '"' << (items[i].getItemString()) << '"';
280         }
281         os << " }";
282         return os.str();
283 }
284
285
286 /*
287         CraftInput
288 */
289
290 bool CraftInput::empty() const
291 {
292         for (const auto &item : items) {
293                 if (!item.empty())
294                         return false;
295         }
296         return true;
297 }
298
299 std::string CraftInput::dump() const
300 {
301         std::ostringstream os(std::ios::binary);
302         os << "(method=" << ((int)method) << ", items="
303                 << craftDumpMatrix(items, width) << ")";
304         return os.str();
305 }
306
307 /*
308         CraftOutput
309 */
310
311 std::string CraftOutput::dump() const
312 {
313         std::ostringstream os(std::ios::binary);
314         os << "(item=\"" << item << "\", time=" << time << ")";
315         return os.str();
316 }
317
318 /*
319         CraftReplacements
320 */
321
322 std::string CraftReplacements::dump() const
323 {
324         std::ostringstream os(std::ios::binary);
325         os<<"{";
326         const char *sep = "";
327         for (const auto &repl_p : pairs) {
328                 os << sep
329                         << '"' << (repl_p.first)
330                         << "\"=>\"" << (repl_p.second) << '"';
331                 sep = ",";
332         }
333         os << "}";
334         return os.str();
335 }
336
337 /*
338         CraftDefinitionShaped
339 */
340
341 CraftDefinitionShaped::CraftDefinitionShaped(
342                 const std::string &output_,
343                 unsigned int width_,
344                 const std::vector<std::string> &recipe_,
345                 const CraftReplacements &replacements_):
346         output(output_), width(width_), recipe(recipe_), replacements(replacements_)
347 {
348         if (hasGroupItem(recipe))
349                 priority = PRIORITY_SHAPED_AND_GROUPS;
350         else
351                 priority = PRIORITY_SHAPED;
352 }
353
354 std::string CraftDefinitionShaped::getName() const
355 {
356         return "shaped";
357 }
358
359 bool CraftDefinitionShaped::check(const CraftInput &input, IGameDef *gamedef) const
360 {
361         if (input.method != CRAFT_METHOD_NORMAL)
362                 return false;
363
364         // Get input item matrix
365         std::vector<std::string> inp_names = craftGetItemNames(input.items, gamedef);
366         unsigned int inp_width = input.width;
367         if (inp_width == 0)
368                 return false;
369         while (inp_names.size() % inp_width != 0)
370                 inp_names.emplace_back("");
371
372         // Get input bounds
373         unsigned int inp_min_x = 0, inp_max_x = 0, inp_min_y = 0, inp_max_y = 0;
374         if (!craftGetBounds(inp_names, inp_width, inp_min_x, inp_max_x,
375                         inp_min_y, inp_max_y))
376                 return false;  // it was empty
377
378         std::vector<std::string> rec_names;
379         if (hash_inited)
380                 rec_names = recipe_names;
381         else
382                 rec_names = craftGetItemNames(recipe, gamedef);
383
384         // Get recipe item matrix
385         unsigned int rec_width = width;
386         if (rec_width == 0)
387                 return false;
388         while (rec_names.size() % rec_width != 0)
389                 rec_names.emplace_back("");
390
391         // Get recipe bounds
392         unsigned int rec_min_x=0, rec_max_x=0, rec_min_y=0, rec_max_y=0;
393         if (!craftGetBounds(rec_names, rec_width, rec_min_x, rec_max_x,
394                         rec_min_y, rec_max_y))
395                 return false;  // it was empty
396
397         // Different sizes?
398         if (inp_max_x - inp_min_x != rec_max_x - rec_min_x ||
399                         inp_max_y - inp_min_y != rec_max_y - rec_min_y)
400                 return false;
401
402         // Verify that all item names in the bounding box are equal
403         unsigned int w = inp_max_x - inp_min_x + 1;
404         unsigned int h = inp_max_y - inp_min_y + 1;
405
406         for (unsigned int y=0; y < h; y++) {
407                 unsigned int inp_y = (inp_min_y + y) * inp_width;
408                 unsigned int rec_y = (rec_min_y + y) * rec_width;
409
410                 for (unsigned int x=0; x < w; x++) {
411                         unsigned int inp_x = inp_min_x + x;
412                         unsigned int rec_x = rec_min_x + x;
413
414                         if (!inputItemMatchesRecipe(
415                                         inp_names[inp_y + inp_x],
416                                         rec_names[rec_y + rec_x], gamedef->idef())) {
417                                 return false;
418                         }
419                 }
420         }
421
422         return true;
423 }
424
425 CraftOutput CraftDefinitionShaped::getOutput(const CraftInput &input, IGameDef *gamedef) const
426 {
427         return CraftOutput(output, 0);
428 }
429
430 CraftInput CraftDefinitionShaped::getInput(const CraftOutput &output, IGameDef *gamedef) const
431 {
432         return CraftInput(CRAFT_METHOD_NORMAL,width,craftGetItems(recipe,gamedef));
433 }
434
435 void CraftDefinitionShaped::decrementInput(CraftInput &input, std::vector<ItemStack> &output_replacements,
436          IGameDef *gamedef) const
437 {
438         craftDecrementOrReplaceInput(input, output_replacements, replacements, gamedef);
439 }
440
441 u64 CraftDefinitionShaped::getHash(CraftHashType type) const
442 {
443         assert(hash_inited); // Pre-condition
444         assert((type == CRAFT_HASH_TYPE_ITEM_NAMES)
445                 || (type == CRAFT_HASH_TYPE_COUNT)); // Pre-condition
446
447         std::vector<std::string> rec_names = recipe_names;
448         std::sort(rec_names.begin(), rec_names.end());
449         return getHashForGrid(type, rec_names);
450 }
451
452 void CraftDefinitionShaped::initHash(IGameDef *gamedef)
453 {
454         if (hash_inited)
455                 return;
456         hash_inited = true;
457         recipe_names = craftGetItemNames(recipe, gamedef);
458
459         if (hasGroupItem(recipe_names))
460                 hash_type = CRAFT_HASH_TYPE_COUNT;
461         else
462                 hash_type = CRAFT_HASH_TYPE_ITEM_NAMES;
463 }
464
465 std::string CraftDefinitionShaped::dump() const
466 {
467         std::ostringstream os(std::ios::binary);
468         os << "(shaped, output=\"" << output
469                 << "\", recipe=" << craftDumpMatrix(recipe, width)
470                 << ", replacements=" << replacements.dump() << ")";
471         return os.str();
472 }
473
474 /*
475         CraftDefinitionShapeless
476 */
477
478 CraftDefinitionShapeless::CraftDefinitionShapeless(
479                 const std::string &output_,
480                 const std::vector<std::string> &recipe_,
481                 const CraftReplacements &replacements_):
482         output(output_), recipe(recipe_), replacements(replacements_)
483 {
484         if (hasGroupItem(recipe))
485                 priority = PRIORITY_SHAPELESS_AND_GROUPS;
486         else
487                 priority = PRIORITY_SHAPELESS;
488 }
489
490 std::string CraftDefinitionShapeless::getName() const
491 {
492         return "shapeless";
493 }
494
495 bool CraftDefinitionShapeless::check(const CraftInput &input, IGameDef *gamedef) const
496 {
497         if (input.method != CRAFT_METHOD_NORMAL)
498                 return false;
499
500         // Filter empty items out of input
501         std::vector<std::string> input_filtered;
502         for (const auto &item : input.items) {
503                 if (!item.name.empty())
504                         input_filtered.push_back(item.name);
505         }
506
507         // If there is a wrong number of items in input, no match
508         if (input_filtered.size() != recipe.size()) {
509                 /*dstream<<"Number of input items ("<<input_filtered.size()
510                                 <<") does not match recipe size ("<<recipe.size()<<") "
511                                 <<"of recipe with output="<<output<<std::endl;*/
512                 return false;
513         }
514
515         std::vector<std::string> recipe_copy;
516         if (hash_inited)
517                 recipe_copy = recipe_names;
518         else {
519                 recipe_copy = craftGetItemNames(recipe, gamedef);
520                 std::sort(recipe_copy.begin(), recipe_copy.end());
521         }
522
523         // Try with all permutations of the recipe,
524         // start from the lexicographically first permutation (=sorted),
525         // recipe_names is pre-sorted
526         do {
527                 // If all items match, the recipe matches
528                 bool all_match = true;
529                 //dstream<<"Testing recipe (output="<<output<<"):";
530                 for (size_t i=0; i<recipe.size(); i++) {
531                         //dstream<<" ("<<input_filtered[i]<<" == "<<recipe_copy[i]<<")";
532                         if (!inputItemMatchesRecipe(input_filtered[i], recipe_copy[i],
533                                         gamedef->idef())) {
534                                 all_match = false;
535                                 break;
536                         }
537                 }
538                 //dstream<<" -> match="<<all_match<<std::endl;
539                 if (all_match)
540                         return true;
541         } while (std::next_permutation(recipe_copy.begin(), recipe_copy.end()));
542
543         return false;
544 }
545
546 CraftOutput CraftDefinitionShapeless::getOutput(const CraftInput &input, IGameDef *gamedef) const
547 {
548         return CraftOutput(output, 0);
549 }
550
551 CraftInput CraftDefinitionShapeless::getInput(const CraftOutput &output, IGameDef *gamedef) const
552 {
553         return CraftInput(CRAFT_METHOD_NORMAL, 0, craftGetItems(recipe, gamedef));
554 }
555
556 void CraftDefinitionShapeless::decrementInput(CraftInput &input, std::vector<ItemStack> &output_replacements,
557         IGameDef *gamedef) const
558 {
559         craftDecrementOrReplaceInput(input, output_replacements, replacements, gamedef);
560 }
561
562 u64 CraftDefinitionShapeless::getHash(CraftHashType type) const
563 {
564         assert(hash_inited); // Pre-condition
565         assert(type == CRAFT_HASH_TYPE_ITEM_NAMES
566                 || type == CRAFT_HASH_TYPE_COUNT); // Pre-condition
567         return getHashForGrid(type, recipe_names);
568 }
569
570 void CraftDefinitionShapeless::initHash(IGameDef *gamedef)
571 {
572         if (hash_inited)
573                 return;
574         hash_inited = true;
575         recipe_names = craftGetItemNames(recipe, gamedef);
576         std::sort(recipe_names.begin(), recipe_names.end());
577
578         if (hasGroupItem(recipe_names))
579                 hash_type = CRAFT_HASH_TYPE_COUNT;
580         else
581                 hash_type = CRAFT_HASH_TYPE_ITEM_NAMES;
582 }
583
584 std::string CraftDefinitionShapeless::dump() const
585 {
586         std::ostringstream os(std::ios::binary);
587         os << "(shapeless, output=\"" << output
588                 << "\", recipe=" << craftDumpMatrix(recipe, recipe.size())
589                 << ", replacements=" << replacements.dump() << ")";
590         return os.str();
591 }
592
593 /*
594         CraftDefinitionToolRepair
595 */
596
597 CraftDefinitionToolRepair::CraftDefinitionToolRepair(float additional_wear_):
598         additional_wear(additional_wear_)
599 {
600         priority = PRIORITY_TOOLREPAIR;
601 }
602
603 static ItemStack craftToolRepair(
604                 const ItemStack &item1,
605                 const ItemStack &item2,
606                 float additional_wear,
607                 IGameDef *gamedef)
608 {
609         IItemDefManager *idef = gamedef->idef();
610         if (item1.count != 1 || item2.count != 1 || item1.name != item2.name
611                         || idef->get(item1.name).type != ITEM_TOOL
612                         || itemgroup_get(idef->get(item1.name).groups, "disable_repair") == 1) {
613                 // Failure
614                 return ItemStack();
615         }
616
617         s32 item1_uses = 65536 - (u32) item1.wear;
618         s32 item2_uses = 65536 - (u32) item2.wear;
619         s32 new_uses = item1_uses + item2_uses;
620         s32 new_wear = 65536 - new_uses + floor(additional_wear * 65536 + 0.5);
621         if (new_wear >= 65536)
622                 return ItemStack();
623         if (new_wear < 0)
624                 new_wear = 0;
625
626         ItemStack repaired = item1;
627         repaired.wear = new_wear;
628         return repaired;
629 }
630
631 std::string CraftDefinitionToolRepair::getName() const
632 {
633         return "toolrepair";
634 }
635
636 bool CraftDefinitionToolRepair::check(const CraftInput &input, IGameDef *gamedef) const
637 {
638         if (input.method != CRAFT_METHOD_NORMAL)
639                 return false;
640
641         ItemStack item1;
642         ItemStack item2;
643         for (const auto &item : input.items) {
644                 if (!item.empty()) {
645                         if (item1.empty())
646                                 item1 = item;
647                         else if (item2.empty())
648                                 item2 = item;
649                         else
650                                 return false;
651                 }
652         }
653         ItemStack repaired = craftToolRepair(item1, item2, additional_wear, gamedef);
654         return !repaired.empty();
655 }
656
657 CraftOutput CraftDefinitionToolRepair::getOutput(const CraftInput &input, IGameDef *gamedef) const
658 {
659         ItemStack item1;
660         ItemStack item2;
661         for (const auto &item : input.items) {
662                 if (!item.empty()) {
663                         if (item1.empty())
664                                 item1 = item;
665                         else if (item2.empty())
666                                 item2 = item;
667                 }
668         }
669         ItemStack repaired = craftToolRepair(item1, item2, additional_wear, gamedef);
670         return CraftOutput(repaired.getItemString(), 0);
671 }
672
673 CraftInput CraftDefinitionToolRepair::getInput(const CraftOutput &output, IGameDef *gamedef) const
674 {
675         std::vector<ItemStack> stack;
676         stack.emplace_back();
677         return CraftInput(CRAFT_METHOD_COOKING, additional_wear, stack);
678 }
679
680 void CraftDefinitionToolRepair::decrementInput(CraftInput &input, std::vector<ItemStack> &output_replacements,
681         IGameDef *gamedef) const
682 {
683         craftDecrementInput(input, gamedef);
684 }
685
686 std::string CraftDefinitionToolRepair::dump() const
687 {
688         std::ostringstream os(std::ios::binary);
689         os << "(toolrepair, additional_wear=" << additional_wear << ")";
690         return os.str();
691 }
692
693 /*
694         CraftDefinitionCooking
695 */
696
697 CraftDefinitionCooking::CraftDefinitionCooking(
698                 const std::string &output_,
699                 const std::string &recipe_,
700                 float cooktime_,
701                 const CraftReplacements &replacements_):
702         output(output_), recipe(recipe_), cooktime(cooktime_), replacements(replacements_)
703 {
704         if (isGroupRecipeStr(recipe))
705                 priority = PRIORITY_SHAPELESS_AND_GROUPS;
706         else
707                 priority = PRIORITY_SHAPELESS;
708 }
709
710 std::string CraftDefinitionCooking::getName() const
711 {
712         return "cooking";
713 }
714
715 bool CraftDefinitionCooking::check(const CraftInput &input, IGameDef *gamedef) const
716 {
717         if (input.method != CRAFT_METHOD_COOKING)
718                 return false;
719
720         // Filter empty items out of input
721         std::vector<std::string> input_filtered;
722         for (const auto &item : input.items) {
723                 const std::string &name = item.name;
724                 if (!name.empty())
725                         input_filtered.push_back(name);
726         }
727
728         // If there is a wrong number of items in input, no match
729         if (input_filtered.size() != 1) {
730                 /*dstream<<"Number of input items ("<<input_filtered.size()
731                                 <<") does not match recipe size (1) "
732                                 <<"of cooking recipe with output="<<output<<std::endl;*/
733                 return false;
734         }
735
736         // Check the single input item
737         return inputItemMatchesRecipe(input_filtered[0], recipe, gamedef->idef());
738 }
739
740 CraftOutput CraftDefinitionCooking::getOutput(const CraftInput &input, IGameDef *gamedef) const
741 {
742         return CraftOutput(output, cooktime);
743 }
744
745 CraftInput CraftDefinitionCooking::getInput(const CraftOutput &output, IGameDef *gamedef) const
746 {
747         std::vector<std::string> rec;
748         rec.push_back(recipe);
749         return CraftInput(CRAFT_METHOD_COOKING,cooktime,craftGetItems(rec,gamedef));
750 }
751
752 void CraftDefinitionCooking::decrementInput(CraftInput &input, std::vector<ItemStack> &output_replacements,
753         IGameDef *gamedef) const
754 {
755         craftDecrementOrReplaceInput(input, output_replacements, replacements, gamedef);
756 }
757
758 u64 CraftDefinitionCooking::getHash(CraftHashType type) const
759 {
760         if (type == CRAFT_HASH_TYPE_ITEM_NAMES) {
761                 return getHashForString(recipe_name);
762         }
763
764         if (type == CRAFT_HASH_TYPE_COUNT) {
765                 return 1;
766         }
767
768         // illegal hash type for this CraftDefinition (pre-condition)
769         assert(false);
770         return 0;
771 }
772
773 void CraftDefinitionCooking::initHash(IGameDef *gamedef)
774 {
775         if (hash_inited)
776                 return;
777         hash_inited = true;
778         recipe_name = craftGetItemName(recipe, gamedef);
779
780         if (isGroupRecipeStr(recipe_name))
781                 hash_type = CRAFT_HASH_TYPE_COUNT;
782         else
783                 hash_type = CRAFT_HASH_TYPE_ITEM_NAMES;
784 }
785
786 std::string CraftDefinitionCooking::dump() const
787 {
788         std::ostringstream os(std::ios::binary);
789         os << "(cooking, output=\"" << output
790                 << "\", recipe=\"" << recipe
791                 << "\", cooktime=" << cooktime << ")"
792                 << ", replacements=" << replacements.dump() << ")";
793         return os.str();
794 }
795
796 /*
797         CraftDefinitionFuel
798 */
799
800 CraftDefinitionFuel::CraftDefinitionFuel(
801                 const std::string &recipe_,
802                 float burntime_,
803                 const CraftReplacements &replacements_):
804         recipe(recipe_), burntime(burntime_), replacements(replacements_)
805 {
806         if (isGroupRecipeStr(recipe_name))
807                 priority = PRIORITY_SHAPELESS_AND_GROUPS;
808         else
809                 priority = PRIORITY_SHAPELESS;
810 }
811
812 std::string CraftDefinitionFuel::getName() const
813 {
814         return "fuel";
815 }
816
817 bool CraftDefinitionFuel::check(const CraftInput &input, IGameDef *gamedef) const
818 {
819         if (input.method != CRAFT_METHOD_FUEL)
820                 return false;
821
822         // Filter empty items out of input
823         std::vector<std::string> input_filtered;
824         for (const auto &item : input.items) {
825                 const std::string &name = item.name;
826                 if (!name.empty())
827                         input_filtered.push_back(name);
828         }
829
830         // If there is a wrong number of items in input, no match
831         if (input_filtered.size() != 1) {
832                 /*dstream<<"Number of input items ("<<input_filtered.size()
833                                 <<") does not match recipe size (1) "
834                                 <<"of fuel recipe with burntime="<<burntime<<std::endl;*/
835                 return false;
836         }
837
838         // Check the single input item
839         return inputItemMatchesRecipe(input_filtered[0], recipe, gamedef->idef());
840 }
841
842 CraftOutput CraftDefinitionFuel::getOutput(const CraftInput &input, IGameDef *gamedef) const
843 {
844         return CraftOutput("", burntime);
845 }
846
847 CraftInput CraftDefinitionFuel::getInput(const CraftOutput &output, IGameDef *gamedef) const
848 {
849         std::vector<std::string> rec;
850         rec.push_back(recipe);
851         return CraftInput(CRAFT_METHOD_COOKING,(int)burntime,craftGetItems(rec,gamedef));
852 }
853
854 void CraftDefinitionFuel::decrementInput(CraftInput &input, std::vector<ItemStack> &output_replacements,
855         IGameDef *gamedef) const
856 {
857         craftDecrementOrReplaceInput(input, output_replacements, replacements, gamedef);
858 }
859
860 u64 CraftDefinitionFuel::getHash(CraftHashType type) const
861 {
862         if (type == CRAFT_HASH_TYPE_ITEM_NAMES) {
863                 return getHashForString(recipe_name);
864         }
865
866         if (type == CRAFT_HASH_TYPE_COUNT) {
867                 return 1;
868         }
869
870         // illegal hash type for this CraftDefinition (pre-condition)
871         assert(false);
872         return 0;
873 }
874
875 void CraftDefinitionFuel::initHash(IGameDef *gamedef)
876 {
877         if (hash_inited)
878                 return;
879         hash_inited = true;
880         recipe_name = craftGetItemName(recipe, gamedef);
881
882         if (isGroupRecipeStr(recipe_name))
883                 hash_type = CRAFT_HASH_TYPE_COUNT;
884         else
885                 hash_type = CRAFT_HASH_TYPE_ITEM_NAMES;
886 }
887
888 std::string CraftDefinitionFuel::dump() const
889 {
890         std::ostringstream os(std::ios::binary);
891         os << "(fuel, recipe=\"" << recipe
892                 << "\", burntime=" << burntime << ")"
893                 << ", replacements=" << replacements.dump() << ")";
894         return os.str();
895 }
896
897 /*
898         Craft definition manager
899 */
900
901 class CCraftDefManager: public IWritableCraftDefManager
902 {
903 public:
904         CCraftDefManager()
905         {
906                 m_craft_defs.resize(craft_hash_type_max + 1);
907         }
908
909         virtual ~CCraftDefManager()
910         {
911                 clear();
912         }
913
914         virtual bool getCraftResult(CraftInput &input, CraftOutput &output,
915                         std::vector<ItemStack> &output_replacement, bool decrementInput,
916                         IGameDef *gamedef) const
917         {
918                 if (input.empty())
919                         return false;
920
921                 std::vector<std::string> input_names;
922                 input_names = craftGetItemNames(input.items, gamedef);
923                 std::sort(input_names.begin(), input_names.end());
924
925                 // Try hash types with increasing collision rate
926                 // while remembering the latest, highest priority recipe.
927                 CraftDefinition::RecipePriority priority_best =
928                         CraftDefinition::PRIORITY_NO_RECIPE;
929                 CraftDefinition *def_best = nullptr;
930                 for (int type = 0; type <= craft_hash_type_max; type++) {
931                         u64 hash = getHashForGrid((CraftHashType) type, input_names);
932
933                         /*errorstream << "Checking type " << type << " with hash " << hash << std::endl;*/
934
935                         // We'd like to do "const [...] hash_collisions = m_craft_defs[type][hash];"
936                         // but that doesn't compile for some reason. This does.
937                         auto col_iter = (m_craft_defs[type]).find(hash);
938
939                         if (col_iter == (m_craft_defs[type]).end())
940                                 continue;
941
942                         const std::vector<CraftDefinition*> &hash_collisions = col_iter->second;
943                         // Walk crafting definitions from back to front, so that later
944                         // definitions can override earlier ones.
945                         for (std::vector<CraftDefinition*>::size_type
946                                         i = hash_collisions.size(); i > 0; i--) {
947                                 CraftDefinition *def = hash_collisions[i - 1];
948
949                                 /*errorstream << "Checking " << input.dump() << std::endl
950                                         << " against " << def->dump() << std::endl;*/
951
952                                 CraftDefinition::RecipePriority priority = def->getPriority();
953                                 if (priority > priority_best
954                                                 && def->check(input, gamedef)) {
955                                         // Check if the crafted node/item exists
956                                         CraftOutput out = def->getOutput(input, gamedef);
957                                         ItemStack is;
958                                         is.deSerialize(out.item, gamedef->idef());
959                                         if (!is.isKnown(gamedef->idef())) {
960                                                 infostream << "trying to craft non-existent "
961                                                         << out.item << ", ignoring recipe" << std::endl;
962                                                 continue;
963                                         }
964
965                                         output = out;
966                                         priority_best = priority;
967                                         def_best = def;
968                                 }
969                         }
970                 }
971                 if (priority_best == CraftDefinition::PRIORITY_NO_RECIPE)
972                         return false;
973                 if (decrementInput)
974                         def_best->decrementInput(input, output_replacement, gamedef);
975                 return true;
976         }
977
978         virtual std::vector<CraftDefinition*> getCraftRecipes(CraftOutput &output,
979                         IGameDef *gamedef, unsigned limit=0) const
980         {
981                 std::vector<CraftDefinition*> recipes;
982
983                 auto vec_iter = m_output_craft_definitions.find(output.item);
984
985                 if (vec_iter == m_output_craft_definitions.end())
986                         return recipes;
987
988                 const std::vector<CraftDefinition*> &vec = vec_iter->second;
989
990                 recipes.reserve(limit ? MYMIN(limit, vec.size()) : vec.size());
991
992                 for (std::vector<CraftDefinition*>::size_type i = vec.size();
993                                 i > 0; i--) {
994                         CraftDefinition *def = vec[i - 1];
995                         if (limit && recipes.size() >= limit)
996                                 break;
997                         recipes.push_back(def);
998                 }
999
1000                 return recipes;
1001         }
1002
1003         virtual bool clearCraftsByOutput(const CraftOutput &output, IGameDef *gamedef)
1004         {
1005                 auto to_clear = m_output_craft_definitions.find(output.item);
1006
1007                 if (to_clear == m_output_craft_definitions.end())
1008                         return false;
1009
1010                 for (auto def : to_clear->second) {
1011                         // Recipes are not yet hashed at this point
1012                         std::vector<CraftDefinition *> &defs = m_craft_defs[(int)CRAFT_HASH_TYPE_UNHASHED][0];
1013                         defs.erase(std::remove(defs.begin(), defs.end(), def), defs.end());
1014                         delete def;
1015                 }
1016                 m_output_craft_definitions.erase(to_clear);
1017                 return true;
1018         }
1019
1020         virtual bool clearCraftsByInput(const CraftInput &input, IGameDef *gamedef)
1021         {
1022                 if (input.empty())
1023                         return false;
1024
1025                 // Recipes are not yet hashed at this point
1026                 std::vector<CraftDefinition *> &defs = m_craft_defs[(int)CRAFT_HASH_TYPE_UNHASHED][0];
1027                 std::vector<CraftDefinition *> new_defs;
1028                 bool got_hit = false;
1029                 for (auto def : defs) {
1030                         if (!def->check(input, gamedef)) {
1031                                 new_defs.push_back(def);
1032                                 continue;
1033                         }
1034                         got_hit = true;
1035                         std::string output = def->getOutput(input, gamedef).item;
1036                         delete def;
1037                         auto it = m_output_craft_definitions.find(craftGetItemName(output, gamedef));
1038                         if (it == m_output_craft_definitions.end())
1039                                 continue;
1040                         std::vector<CraftDefinition *> &outdefs = it->second;
1041                         outdefs.erase(std::remove(outdefs.begin(), outdefs.end(), def), outdefs.end());
1042                 }
1043                 if (got_hit)
1044                         defs.swap(new_defs);
1045
1046                 return got_hit;
1047         }
1048
1049         virtual std::string dump() const
1050         {
1051                 std::ostringstream os(std::ios::binary);
1052                 os << "Crafting definitions:\n";
1053                 for (int type = 0; type <= craft_hash_type_max; ++type) {
1054                         for (auto it = m_craft_defs[type].begin();
1055                                         it != m_craft_defs[type].end(); ++it) {
1056                                 for (std::vector<CraftDefinition*>::size_type i = 0;
1057                                                 i < it->second.size(); i++) {
1058                                         os << "type " << type
1059                                                 << " hash " << it->first
1060                                                 << " def " << it->second[i]->dump()
1061                                                 << "\n";
1062                                 }
1063                         }
1064                 }
1065                 return os.str();
1066         }
1067         virtual void registerCraft(CraftDefinition *def, IGameDef *gamedef)
1068         {
1069                 TRACESTREAM(<< "registerCraft: registering craft definition: "
1070                                 << def->dump() << std::endl);
1071                 m_craft_defs[(int) CRAFT_HASH_TYPE_UNHASHED][0].push_back(def);
1072
1073                 CraftInput input;
1074                 std::string output_name = craftGetItemName(
1075                                 def->getOutput(input, gamedef).item, gamedef);
1076                 m_output_craft_definitions[output_name].push_back(def);
1077         }
1078         virtual void clear()
1079         {
1080                 for (int type = 0; type <= craft_hash_type_max; ++type) {
1081                         for (auto &it : m_craft_defs[type]) {
1082                                 for (auto &iit : it.second) {
1083                                         delete iit;
1084                                 }
1085                                 it.second.clear();
1086                         }
1087                         m_craft_defs[type].clear();
1088                 }
1089                 m_output_craft_definitions.clear();
1090         }
1091         virtual void initHashes(IGameDef *gamedef)
1092         {
1093                 // Move the CraftDefs from the unhashed layer into layers higher up.
1094                 std::vector<CraftDefinition *> &unhashed =
1095                         m_craft_defs[(int) CRAFT_HASH_TYPE_UNHASHED][0];
1096                 for (auto def : unhashed) {
1097                         // Initialize and get the definition's hash
1098                         def->initHash(gamedef);
1099                         CraftHashType type = def->getHashType();
1100                         u64 hash = def->getHash(type);
1101
1102                         // Enter the definition
1103                         m_craft_defs[type][hash].push_back(def);
1104                 }
1105                 unhashed.clear();
1106         }
1107 private:
1108         std::vector<std::unordered_map<u64, std::vector<CraftDefinition*> > >
1109                 m_craft_defs;
1110         std::unordered_map<std::string, std::vector<CraftDefinition*> >
1111                 m_output_craft_definitions;
1112 };
1113
1114 IWritableCraftDefManager* createCraftDefManager()
1115 {
1116         return new CCraftDefManager();
1117 }