craftdef.cpp 33 KB

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