craftdef.cpp 29 KB

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