]> git.lizzy.rs Git - minetest.git/blob - src/craftdef.cpp
Fix Enter key after creating a new world (#12997)
[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 <unordered_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                                 pairs.erase(j);
233                                 output_replacements.push_back(rep);
234                                 break;
235
236                         }
237                 }
238                 // No replacement was found, simply decrement count by one
239                 if (!found_replacement && item.count > 0)
240                         item.remove(1);
241         }
242 }
243
244 // Dump an itemstring matrix
245 static std::string craftDumpMatrix(const std::vector<std::string> &items,
246                 unsigned int width)
247 {
248         std::ostringstream os(std::ios::binary);
249         os << "{ ";
250         unsigned int x = 0;
251         for(std::vector<std::string>::size_type i = 0;
252                         i < items.size(); i++, x++) {
253                 if (x == width) {
254                         os << "; ";
255                         x = 0;
256                 } else if (x != 0) {
257                         os << ",";
258                 }
259                 os << '"' << items[i] << '"';
260         }
261         os << " }";
262         return os.str();
263 }
264
265 // Dump an item matrix
266 std::string craftDumpMatrix(const std::vector<ItemStack> &items,
267                 unsigned int width)
268 {
269         std::ostringstream os(std::ios::binary);
270         os << "{ ";
271         unsigned int x = 0;
272         for (std::vector<ItemStack>::size_type i = 0;
273                         i < items.size(); i++, x++) {
274                 if (x == width) {
275                         os << "; ";
276                         x = 0;
277                 } else if (x != 0) {
278                         os << ",";
279                 }
280                 os << '"' << (items[i].getItemString()) << '"';
281         }
282         os << " }";
283         return os.str();
284 }
285
286
287 /*
288         CraftInput
289 */
290
291 bool CraftInput::empty() const
292 {
293         for (const auto &item : items) {
294                 if (!item.empty())
295                         return false;
296         }
297         return true;
298 }
299
300 std::string CraftInput::dump() const
301 {
302         std::ostringstream os(std::ios::binary);
303         os << "(method=" << ((int)method) << ", items="
304                 << craftDumpMatrix(items, width) << ")";
305         return os.str();
306 }
307
308 /*
309         CraftOutput
310 */
311
312 std::string CraftOutput::dump() const
313 {
314         std::ostringstream os(std::ios::binary);
315         os << "(item=\"" << item << "\", time=" << time << ")";
316         return os.str();
317 }
318
319 /*
320         CraftReplacements
321 */
322
323 std::string CraftReplacements::dump() const
324 {
325         std::ostringstream os(std::ios::binary);
326         os<<"{";
327         const char *sep = "";
328         for (const auto &repl_p : pairs) {
329                 os << sep
330                         << '"' << (repl_p.first)
331                         << "\"=>\"" << (repl_p.second) << '"';
332                 sep = ",";
333         }
334         os << "}";
335         return os.str();
336 }
337
338 /*
339         CraftDefinitionShaped
340 */
341
342 CraftDefinitionShaped::CraftDefinitionShaped(
343                 const std::string &output_,
344                 unsigned int width_,
345                 const std::vector<std::string> &recipe_,
346                 const CraftReplacements &replacements_):
347         output(output_), width(width_), recipe(recipe_), replacements(replacements_)
348 {
349         if (hasGroupItem(recipe))
350                 priority = PRIORITY_SHAPED_AND_GROUPS;
351         else
352                 priority = PRIORITY_SHAPED;
353 }
354
355 std::string CraftDefinitionShaped::getName() const
356 {
357         return "shaped";
358 }
359
360 bool CraftDefinitionShaped::check(const CraftInput &input, IGameDef *gamedef) const
361 {
362         if (input.method != CRAFT_METHOD_NORMAL)
363                 return false;
364
365         // Get input item matrix
366         std::vector<std::string> inp_names = craftGetItemNames(input.items, gamedef);
367         unsigned int inp_width = input.width;
368         if (inp_width == 0)
369                 return false;
370         while (inp_names.size() % inp_width != 0)
371                 inp_names.emplace_back("");
372
373         // Get input bounds
374         unsigned int inp_min_x = 0, inp_max_x = 0, inp_min_y = 0, inp_max_y = 0;
375         if (!craftGetBounds(inp_names, inp_width, inp_min_x, inp_max_x,
376                         inp_min_y, inp_max_y))
377                 return false;  // it was empty
378
379         std::vector<std::string> rec_names;
380         if (hash_inited)
381                 rec_names = recipe_names;
382         else
383                 rec_names = craftGetItemNames(recipe, gamedef);
384
385         // Get recipe item matrix
386         unsigned int rec_width = width;
387         if (rec_width == 0)
388                 return false;
389         while (rec_names.size() % rec_width != 0)
390                 rec_names.emplace_back("");
391
392         // Get recipe bounds
393         unsigned int rec_min_x=0, rec_max_x=0, rec_min_y=0, rec_max_y=0;
394         if (!craftGetBounds(rec_names, rec_width, rec_min_x, rec_max_x,
395                         rec_min_y, rec_max_y))
396                 return false;  // it was empty
397
398         // Different sizes?
399         if (inp_max_x - inp_min_x != rec_max_x - rec_min_x ||
400                         inp_max_y - inp_min_y != rec_max_y - rec_min_y)
401                 return false;
402
403         // Verify that all item names in the bounding box are equal
404         unsigned int w = inp_max_x - inp_min_x + 1;
405         unsigned int h = inp_max_y - inp_min_y + 1;
406
407         for (unsigned int y=0; y < h; y++) {
408                 unsigned int inp_y = (inp_min_y + y) * inp_width;
409                 unsigned int rec_y = (rec_min_y + y) * rec_width;
410
411                 for (unsigned int x=0; x < w; x++) {
412                         unsigned int inp_x = inp_min_x + x;
413                         unsigned int rec_x = rec_min_x + x;
414
415                         if (!inputItemMatchesRecipe(
416                                         inp_names[inp_y + inp_x],
417                                         rec_names[rec_y + rec_x], gamedef->idef())) {
418                                 return false;
419                         }
420                 }
421         }
422
423         return true;
424 }
425
426 CraftOutput CraftDefinitionShaped::getOutput(const CraftInput &input, IGameDef *gamedef) const
427 {
428         return CraftOutput(output, 0);
429 }
430
431 CraftInput CraftDefinitionShaped::getInput(const CraftOutput &output, IGameDef *gamedef) const
432 {
433         return CraftInput(CRAFT_METHOD_NORMAL,width,craftGetItems(recipe,gamedef));
434 }
435
436 void CraftDefinitionShaped::decrementInput(CraftInput &input, std::vector<ItemStack> &output_replacements,
437          IGameDef *gamedef) const
438 {
439         craftDecrementOrReplaceInput(input, output_replacements, replacements, gamedef);
440 }
441
442 u64 CraftDefinitionShaped::getHash(CraftHashType type) const
443 {
444         assert(hash_inited); // Pre-condition
445         assert((type == CRAFT_HASH_TYPE_ITEM_NAMES)
446                 || (type == CRAFT_HASH_TYPE_COUNT)); // Pre-condition
447
448         std::vector<std::string> rec_names = recipe_names;
449         std::sort(rec_names.begin(), rec_names.end());
450         return getHashForGrid(type, rec_names);
451 }
452
453 void CraftDefinitionShaped::initHash(IGameDef *gamedef)
454 {
455         if (hash_inited)
456                 return;
457         hash_inited = true;
458         recipe_names = craftGetItemNames(recipe, gamedef);
459
460         if (hasGroupItem(recipe_names))
461                 hash_type = CRAFT_HASH_TYPE_COUNT;
462         else
463                 hash_type = CRAFT_HASH_TYPE_ITEM_NAMES;
464 }
465
466 std::string CraftDefinitionShaped::dump() const
467 {
468         std::ostringstream os(std::ios::binary);
469         os << "(shaped, output=\"" << output
470                 << "\", recipe=" << craftDumpMatrix(recipe, width)
471                 << ", replacements=" << replacements.dump() << ")";
472         return os.str();
473 }
474
475 /*
476         CraftDefinitionShapeless
477 */
478
479 CraftDefinitionShapeless::CraftDefinitionShapeless(
480                 const std::string &output_,
481                 const std::vector<std::string> &recipe_,
482                 const CraftReplacements &replacements_):
483         output(output_), recipe(recipe_), replacements(replacements_)
484 {
485         if (hasGroupItem(recipe))
486                 priority = PRIORITY_SHAPELESS_AND_GROUPS;
487         else
488                 priority = PRIORITY_SHAPELESS;
489 }
490
491 std::string CraftDefinitionShapeless::getName() const
492 {
493         return "shapeless";
494 }
495
496 bool CraftDefinitionShapeless::check(const CraftInput &input, IGameDef *gamedef) const
497 {
498         if (input.method != CRAFT_METHOD_NORMAL)
499                 return false;
500
501         // Filter empty items out of input
502         std::vector<std::string> input_filtered;
503         for (const auto &item : input.items) {
504                 if (!item.name.empty())
505                         input_filtered.push_back(item.name);
506         }
507
508         // If there is a wrong number of items in input, no match
509         if (input_filtered.size() != recipe.size()) {
510                 /*dstream<<"Number of input items ("<<input_filtered.size()
511                                 <<") does not match recipe size ("<<recipe.size()<<") "
512                                 <<"of recipe with output="<<output<<std::endl;*/
513                 return false;
514         }
515
516         std::vector<std::string> recipe_copy;
517         if (hash_inited)
518                 recipe_copy = recipe_names;
519         else {
520                 recipe_copy = craftGetItemNames(recipe, gamedef);
521                 std::sort(recipe_copy.begin(), recipe_copy.end());
522         }
523
524         // Try with all permutations of the recipe,
525         // start from the lexicographically first permutation (=sorted),
526         // recipe_names is pre-sorted
527         do {
528                 // If all items match, the recipe matches
529                 bool all_match = true;
530                 //dstream<<"Testing recipe (output="<<output<<"):";
531                 for (size_t i=0; i<recipe.size(); i++) {
532                         //dstream<<" ("<<input_filtered[i]<<" == "<<recipe_copy[i]<<")";
533                         if (!inputItemMatchesRecipe(input_filtered[i], recipe_copy[i],
534                                         gamedef->idef())) {
535                                 all_match = false;
536                                 break;
537                         }
538                 }
539                 //dstream<<" -> match="<<all_match<<std::endl;
540                 if (all_match)
541                         return true;
542         } while (std::next_permutation(recipe_copy.begin(), recipe_copy.end()));
543
544         return false;
545 }
546
547 CraftOutput CraftDefinitionShapeless::getOutput(const CraftInput &input, IGameDef *gamedef) const
548 {
549         return CraftOutput(output, 0);
550 }
551
552 CraftInput CraftDefinitionShapeless::getInput(const CraftOutput &output, IGameDef *gamedef) const
553 {
554         return CraftInput(CRAFT_METHOD_NORMAL, 0, craftGetItems(recipe, gamedef));
555 }
556
557 void CraftDefinitionShapeless::decrementInput(CraftInput &input, std::vector<ItemStack> &output_replacements,
558         IGameDef *gamedef) const
559 {
560         craftDecrementOrReplaceInput(input, output_replacements, replacements, gamedef);
561 }
562
563 u64 CraftDefinitionShapeless::getHash(CraftHashType type) const
564 {
565         assert(hash_inited); // Pre-condition
566         assert(type == CRAFT_HASH_TYPE_ITEM_NAMES
567                 || type == CRAFT_HASH_TYPE_COUNT); // Pre-condition
568         return getHashForGrid(type, recipe_names);
569 }
570
571 void CraftDefinitionShapeless::initHash(IGameDef *gamedef)
572 {
573         if (hash_inited)
574                 return;
575         hash_inited = true;
576         recipe_names = craftGetItemNames(recipe, gamedef);
577         std::sort(recipe_names.begin(), recipe_names.end());
578
579         if (hasGroupItem(recipe_names))
580                 hash_type = CRAFT_HASH_TYPE_COUNT;
581         else
582                 hash_type = CRAFT_HASH_TYPE_ITEM_NAMES;
583 }
584
585 std::string CraftDefinitionShapeless::dump() const
586 {
587         std::ostringstream os(std::ios::binary);
588         os << "(shapeless, output=\"" << output
589                 << "\", recipe=" << craftDumpMatrix(recipe, recipe.size())
590                 << ", replacements=" << replacements.dump() << ")";
591         return os.str();
592 }
593
594 /*
595         CraftDefinitionToolRepair
596 */
597
598 CraftDefinitionToolRepair::CraftDefinitionToolRepair(float additional_wear_):
599         additional_wear(additional_wear_)
600 {
601         priority = PRIORITY_TOOLREPAIR;
602 }
603
604 static ItemStack craftToolRepair(
605                 const ItemStack &item1,
606                 const ItemStack &item2,
607                 float additional_wear,
608                 IGameDef *gamedef)
609 {
610         IItemDefManager *idef = gamedef->idef();
611         if (item1.count != 1 || item2.count != 1 || item1.name != item2.name
612                         || idef->get(item1.name).type != ITEM_TOOL
613                         || itemgroup_get(idef->get(item1.name).groups, "disable_repair") == 1) {
614                 // Failure
615                 return ItemStack();
616         }
617
618         s32 item1_uses = 65536 - (u32) item1.wear;
619         s32 item2_uses = 65536 - (u32) item2.wear;
620         s32 new_uses = item1_uses + item2_uses;
621         s32 new_wear = 65536 - new_uses + floor(additional_wear * 65536 + 0.5);
622         if (new_wear >= 65536)
623                 return ItemStack();
624         if (new_wear < 0)
625                 new_wear = 0;
626
627         ItemStack repaired = item1;
628         repaired.wear = new_wear;
629         return repaired;
630 }
631
632 std::string CraftDefinitionToolRepair::getName() const
633 {
634         return "toolrepair";
635 }
636
637 bool CraftDefinitionToolRepair::check(const CraftInput &input, IGameDef *gamedef) const
638 {
639         if (input.method != CRAFT_METHOD_NORMAL)
640                 return false;
641
642         ItemStack item1;
643         ItemStack item2;
644         for (const auto &item : input.items) {
645                 if (!item.empty()) {
646                         if (item1.empty())
647                                 item1 = item;
648                         else if (item2.empty())
649                                 item2 = item;
650                         else
651                                 return false;
652                 }
653         }
654         ItemStack repaired = craftToolRepair(item1, item2, additional_wear, gamedef);
655         return !repaired.empty();
656 }
657
658 CraftOutput CraftDefinitionToolRepair::getOutput(const CraftInput &input, IGameDef *gamedef) const
659 {
660         ItemStack item1;
661         ItemStack item2;
662         for (const auto &item : input.items) {
663                 if (!item.empty()) {
664                         if (item1.empty())
665                                 item1 = item;
666                         else if (item2.empty())
667                                 item2 = item;
668                 }
669         }
670         ItemStack repaired = craftToolRepair(item1, item2, additional_wear, gamedef);
671         return CraftOutput(repaired.getItemString(), 0);
672 }
673
674 CraftInput CraftDefinitionToolRepair::getInput(const CraftOutput &output, IGameDef *gamedef) const
675 {
676         std::vector<ItemStack> stack;
677         stack.emplace_back();
678         return CraftInput(CRAFT_METHOD_COOKING, additional_wear, stack);
679 }
680
681 void CraftDefinitionToolRepair::decrementInput(CraftInput &input, std::vector<ItemStack> &output_replacements,
682         IGameDef *gamedef) const
683 {
684         craftDecrementInput(input, gamedef);
685 }
686
687 std::string CraftDefinitionToolRepair::dump() const
688 {
689         std::ostringstream os(std::ios::binary);
690         os << "(toolrepair, additional_wear=" << additional_wear << ")";
691         return os.str();
692 }
693
694 /*
695         CraftDefinitionCooking
696 */
697
698 CraftDefinitionCooking::CraftDefinitionCooking(
699                 const std::string &output_,
700                 const std::string &recipe_,
701                 float cooktime_,
702                 const CraftReplacements &replacements_):
703         output(output_), recipe(recipe_), cooktime(cooktime_), replacements(replacements_)
704 {
705         if (isGroupRecipeStr(recipe))
706                 priority = PRIORITY_SHAPELESS_AND_GROUPS;
707         else
708                 priority = PRIORITY_SHAPELESS;
709 }
710
711 std::string CraftDefinitionCooking::getName() const
712 {
713         return "cooking";
714 }
715
716 bool CraftDefinitionCooking::check(const CraftInput &input, IGameDef *gamedef) const
717 {
718         if (input.method != CRAFT_METHOD_COOKING)
719                 return false;
720
721         // Filter empty items out of input
722         std::vector<std::string> input_filtered;
723         for (const auto &item : input.items) {
724                 const std::string &name = item.name;
725                 if (!name.empty())
726                         input_filtered.push_back(name);
727         }
728
729         // If there is a wrong number of items in input, no match
730         if (input_filtered.size() != 1) {
731                 /*dstream<<"Number of input items ("<<input_filtered.size()
732                                 <<") does not match recipe size (1) "
733                                 <<"of cooking recipe with output="<<output<<std::endl;*/
734                 return false;
735         }
736
737         // Check the single input item
738         std::string rec_name = craftGetItemName(recipe, gamedef);
739         return inputItemMatchesRecipe(input_filtered[0], rec_name, gamedef->idef());
740 }
741
742 CraftOutput CraftDefinitionCooking::getOutput(const CraftInput &input, IGameDef *gamedef) const
743 {
744         return CraftOutput(output, cooktime);
745 }
746
747 CraftInput CraftDefinitionCooking::getInput(const CraftOutput &output, IGameDef *gamedef) const
748 {
749         std::vector<std::string> rec;
750         rec.push_back(recipe);
751         return CraftInput(CRAFT_METHOD_COOKING,cooktime,craftGetItems(rec,gamedef));
752 }
753
754 void CraftDefinitionCooking::decrementInput(CraftInput &input, std::vector<ItemStack> &output_replacements,
755         IGameDef *gamedef) const
756 {
757         craftDecrementOrReplaceInput(input, output_replacements, replacements, gamedef);
758 }
759
760 u64 CraftDefinitionCooking::getHash(CraftHashType type) const
761 {
762         if (type == CRAFT_HASH_TYPE_ITEM_NAMES) {
763                 return getHashForString(recipe_name);
764         }
765
766         if (type == CRAFT_HASH_TYPE_COUNT) {
767                 return 1;
768         }
769
770         // illegal hash type for this CraftDefinition (pre-condition)
771         assert(false);
772         return 0;
773 }
774
775 void CraftDefinitionCooking::initHash(IGameDef *gamedef)
776 {
777         if (hash_inited)
778                 return;
779         hash_inited = true;
780         recipe_name = craftGetItemName(recipe, gamedef);
781
782         if (isGroupRecipeStr(recipe_name))
783                 hash_type = CRAFT_HASH_TYPE_COUNT;
784         else
785                 hash_type = CRAFT_HASH_TYPE_ITEM_NAMES;
786 }
787
788 std::string CraftDefinitionCooking::dump() const
789 {
790         std::ostringstream os(std::ios::binary);
791         os << "(cooking, output=\"" << output
792                 << "\", recipe=\"" << recipe
793                 << "\", cooktime=" << cooktime << ")"
794                 << ", replacements=" << replacements.dump() << ")";
795         return os.str();
796 }
797
798 /*
799         CraftDefinitionFuel
800 */
801
802 CraftDefinitionFuel::CraftDefinitionFuel(
803                 const std::string &recipe_,
804                 float burntime_,
805                 const CraftReplacements &replacements_):
806         recipe(recipe_), burntime(burntime_), replacements(replacements_)
807 {
808         if (isGroupRecipeStr(recipe_name))
809                 priority = PRIORITY_SHAPELESS_AND_GROUPS;
810         else
811                 priority = PRIORITY_SHAPELESS;
812 }
813
814 std::string CraftDefinitionFuel::getName() const
815 {
816         return "fuel";
817 }
818
819 bool CraftDefinitionFuel::check(const CraftInput &input, IGameDef *gamedef) const
820 {
821         if (input.method != CRAFT_METHOD_FUEL)
822                 return false;
823
824         // Filter empty items out of input
825         std::vector<std::string> input_filtered;
826         for (const auto &item : input.items) {
827                 const std::string &name = item.name;
828                 if (!name.empty())
829                         input_filtered.push_back(name);
830         }
831
832         // If there is a wrong number of items in input, no match
833         if (input_filtered.size() != 1) {
834                 /*dstream<<"Number of input items ("<<input_filtered.size()
835                                 <<") does not match recipe size (1) "
836                                 <<"of fuel recipe with burntime="<<burntime<<std::endl;*/
837                 return false;
838         }
839
840         // Check the single input item
841         std::string rec_name = craftGetItemName(recipe, gamedef);
842         return inputItemMatchesRecipe(input_filtered[0], rec_name, gamedef->idef());
843 }
844
845 CraftOutput CraftDefinitionFuel::getOutput(const CraftInput &input, IGameDef *gamedef) const
846 {
847         return CraftOutput("", burntime);
848 }
849
850 CraftInput CraftDefinitionFuel::getInput(const CraftOutput &output, IGameDef *gamedef) const
851 {
852         std::vector<std::string> rec;
853         rec.push_back(recipe);
854         return CraftInput(CRAFT_METHOD_COOKING,(int)burntime,craftGetItems(rec,gamedef));
855 }
856
857 void CraftDefinitionFuel::decrementInput(CraftInput &input, std::vector<ItemStack> &output_replacements,
858         IGameDef *gamedef) const
859 {
860         craftDecrementOrReplaceInput(input, output_replacements, replacements, gamedef);
861 }
862
863 u64 CraftDefinitionFuel::getHash(CraftHashType type) const
864 {
865         if (type == CRAFT_HASH_TYPE_ITEM_NAMES) {
866                 return getHashForString(recipe_name);
867         }
868
869         if (type == CRAFT_HASH_TYPE_COUNT) {
870                 return 1;
871         }
872
873         // illegal hash type for this CraftDefinition (pre-condition)
874         assert(false);
875         return 0;
876 }
877
878 void CraftDefinitionFuel::initHash(IGameDef *gamedef)
879 {
880         if (hash_inited)
881                 return;
882         hash_inited = true;
883         recipe_name = craftGetItemName(recipe, gamedef);
884
885         if (isGroupRecipeStr(recipe_name))
886                 hash_type = CRAFT_HASH_TYPE_COUNT;
887         else
888                 hash_type = CRAFT_HASH_TYPE_ITEM_NAMES;
889 }
890
891 std::string CraftDefinitionFuel::dump() const
892 {
893         std::ostringstream os(std::ios::binary);
894         os << "(fuel, recipe=\"" << recipe
895                 << "\", burntime=" << burntime << ")"
896                 << ", replacements=" << replacements.dump() << ")";
897         return os.str();
898 }
899
900 /*
901         Craft definition manager
902 */
903
904 class CCraftDefManager: public IWritableCraftDefManager
905 {
906 public:
907         CCraftDefManager()
908         {
909                 m_craft_defs.resize(craft_hash_type_max + 1);
910         }
911
912         virtual ~CCraftDefManager()
913         {
914                 clear();
915         }
916
917         virtual bool getCraftResult(CraftInput &input, CraftOutput &output,
918                         std::vector<ItemStack> &output_replacement, bool decrementInput,
919                         IGameDef *gamedef) const
920         {
921                 if (input.empty())
922                         return false;
923
924                 std::vector<std::string> input_names;
925                 input_names = craftGetItemNames(input.items, gamedef);
926                 std::sort(input_names.begin(), input_names.end());
927
928                 // Try hash types with increasing collision rate
929                 // while remembering the latest, highest priority recipe.
930                 CraftDefinition::RecipePriority priority_best =
931                         CraftDefinition::PRIORITY_NO_RECIPE;
932                 CraftDefinition *def_best = nullptr;
933                 for (int type = 0; type <= craft_hash_type_max; type++) {
934                         u64 hash = getHashForGrid((CraftHashType) type, input_names);
935
936                         /*errorstream << "Checking type " << type << " with hash " << hash << std::endl;*/
937
938                         // We'd like to do "const [...] hash_collisions = m_craft_defs[type][hash];"
939                         // but that doesn't compile for some reason. This does.
940                         auto col_iter = (m_craft_defs[type]).find(hash);
941
942                         if (col_iter == (m_craft_defs[type]).end())
943                                 continue;
944
945                         const std::vector<CraftDefinition*> &hash_collisions = col_iter->second;
946                         // Walk crafting definitions from back to front, so that later
947                         // definitions can override earlier ones.
948                         for (std::vector<CraftDefinition*>::size_type
949                                         i = hash_collisions.size(); i > 0; i--) {
950                                 CraftDefinition *def = hash_collisions[i - 1];
951
952                                 /*errorstream << "Checking " << input.dump() << std::endl
953                                         << " against " << def->dump() << std::endl;*/
954
955                                 CraftDefinition::RecipePriority priority = def->getPriority();
956                                 if (priority > priority_best
957                                                 && def->check(input, gamedef)) {
958                                         // Check if the crafted node/item exists
959                                         CraftOutput out = def->getOutput(input, gamedef);
960                                         ItemStack is;
961                                         is.deSerialize(out.item, gamedef->idef());
962                                         if (!is.isKnown(gamedef->idef())) {
963                                                 infostream << "trying to craft non-existent "
964                                                         << out.item << ", ignoring recipe" << std::endl;
965                                                 continue;
966                                         }
967
968                                         output = out;
969                                         priority_best = priority;
970                                         def_best = def;
971                                 }
972                         }
973                 }
974                 if (priority_best == CraftDefinition::PRIORITY_NO_RECIPE)
975                         return false;
976                 if (decrementInput)
977                         def_best->decrementInput(input, output_replacement, gamedef);
978                 return true;
979         }
980
981         virtual std::vector<CraftDefinition*> getCraftRecipes(CraftOutput &output,
982                         IGameDef *gamedef, unsigned limit=0) const
983         {
984                 std::vector<CraftDefinition*> recipes;
985
986                 auto vec_iter = m_output_craft_definitions.find(output.item);
987
988                 if (vec_iter == m_output_craft_definitions.end())
989                         return recipes;
990
991                 const std::vector<CraftDefinition*> &vec = vec_iter->second;
992
993                 recipes.reserve(limit ? MYMIN(limit, vec.size()) : vec.size());
994
995                 for (std::vector<CraftDefinition*>::size_type i = vec.size();
996                                 i > 0; i--) {
997                         CraftDefinition *def = vec[i - 1];
998                         if (limit && recipes.size() >= limit)
999                                 break;
1000                         recipes.push_back(def);
1001                 }
1002
1003                 return recipes;
1004         }
1005
1006         virtual bool clearCraftsByOutput(const CraftOutput &output, IGameDef *gamedef)
1007         {
1008                 auto to_clear = m_output_craft_definitions.find(output.item);
1009
1010                 if (to_clear == m_output_craft_definitions.end())
1011                         return false;
1012
1013                 for (auto def : to_clear->second) {
1014                         // Recipes are not yet hashed at this point
1015                         std::vector<CraftDefinition *> &defs = m_craft_defs[(int)CRAFT_HASH_TYPE_UNHASHED][0];
1016                         defs.erase(std::remove(defs.begin(), defs.end(), def), defs.end());
1017                         delete def;
1018                 }
1019                 m_output_craft_definitions.erase(to_clear);
1020                 return true;
1021         }
1022
1023         virtual bool clearCraftsByInput(const CraftInput &input, IGameDef *gamedef)
1024         {
1025                 if (input.empty())
1026                         return false;
1027
1028                 // Recipes are not yet hashed at this point
1029                 std::vector<CraftDefinition *> &defs = m_craft_defs[(int)CRAFT_HASH_TYPE_UNHASHED][0];
1030                 std::unordered_set<const CraftDefinition *> defs_to_remove;
1031                 std::vector<CraftDefinition *> new_defs;
1032
1033                 for (auto def : defs) {
1034                         if (def->check(input, gamedef))
1035                                 defs_to_remove.insert(def);
1036                         else
1037                                 new_defs.push_back(def);
1038                 }
1039
1040                 if (!defs_to_remove.empty()) {
1041                         for (auto def : defs_to_remove)
1042                                 delete def;
1043
1044                         defs.swap(new_defs);
1045
1046                         for (auto &output : m_output_craft_definitions) {
1047                                 std::vector<CraftDefinition *> &outdefs = output.second;
1048                                 outdefs.erase(std::remove_if(outdefs.begin(), outdefs.end(), [&](const CraftDefinition* def) {
1049                                         return defs_to_remove.find(def) != defs_to_remove.end();
1050                                 }), outdefs.end());
1051                         }
1052                 }
1053
1054                 return !defs_to_remove.empty();
1055         }
1056
1057         virtual std::string dump() const
1058         {
1059                 std::ostringstream os(std::ios::binary);
1060                 os << "Crafting definitions:\n";
1061                 for (int type = 0; type <= craft_hash_type_max; ++type) {
1062                         for (auto it = m_craft_defs[type].begin();
1063                                         it != m_craft_defs[type].end(); ++it) {
1064                                 for (std::vector<CraftDefinition*>::size_type i = 0;
1065                                                 i < it->second.size(); i++) {
1066                                         os << "type " << type
1067                                                 << " hash " << it->first
1068                                                 << " def " << it->second[i]->dump()
1069                                                 << "\n";
1070                                 }
1071                         }
1072                 }
1073                 return os.str();
1074         }
1075         virtual void registerCraft(CraftDefinition *def, IGameDef *gamedef)
1076         {
1077                 TRACESTREAM(<< "registerCraft: registering craft definition: "
1078                                 << def->dump() << std::endl);
1079                 m_craft_defs[(int) CRAFT_HASH_TYPE_UNHASHED][0].push_back(def);
1080
1081                 CraftInput input;
1082                 std::string output_name = craftGetItemName(
1083                                 def->getOutput(input, gamedef).item, gamedef);
1084                 m_output_craft_definitions[output_name].push_back(def);
1085         }
1086         virtual void clear()
1087         {
1088                 for (int type = 0; type <= craft_hash_type_max; ++type) {
1089                         for (auto &it : m_craft_defs[type]) {
1090                                 for (auto &iit : it.second) {
1091                                         delete iit;
1092                                 }
1093                                 it.second.clear();
1094                         }
1095                         m_craft_defs[type].clear();
1096                 }
1097                 m_output_craft_definitions.clear();
1098         }
1099         virtual void initHashes(IGameDef *gamedef)
1100         {
1101                 // Move the CraftDefs from the unhashed layer into layers higher up.
1102                 std::vector<CraftDefinition *> &unhashed =
1103                         m_craft_defs[(int) CRAFT_HASH_TYPE_UNHASHED][0];
1104                 for (auto def : unhashed) {
1105                         // Initialize and get the definition's hash
1106                         def->initHash(gamedef);
1107                         CraftHashType type = def->getHashType();
1108                         u64 hash = def->getHash(type);
1109
1110                         // Enter the definition
1111                         m_craft_defs[type][hash].push_back(def);
1112                 }
1113                 unhashed.clear();
1114         }
1115 private:
1116         std::vector<std::unordered_map<u64, std::vector<CraftDefinition*> > >
1117                 m_craft_defs;
1118         std::unordered_map<std::string, std::vector<CraftDefinition*> >
1119                 m_output_craft_definitions;
1120 };
1121
1122 IWritableCraftDefManager* createCraftDefManager()
1123 {
1124         return new CCraftDefManager();
1125 }