api.lua 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274
  1. -- Crafting Mod - semi-realistic crafting in minetest
  2. -- Copyright (C) 2018 rubenwardy <rw@rubenwardy.com>
  3. --
  4. -- This library is free software; you can redistribute it and/or
  5. -- modify it under the terms of the GNU Lesser General Public
  6. -- License as published by the Free Software Foundation; either
  7. -- version 2.1 of the License, or (at your option) any later version.
  8. --
  9. -- This library is distributed in the hope that it will be useful,
  10. -- but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. -- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  12. -- Lesser General Public License for more details.
  13. --
  14. -- You should have received a copy of the GNU Lesser General Public
  15. -- License along with this library; if not, write to the Free Software
  16. -- Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
  17. crafting = {
  18. recipes = {},
  19. recipes_by_id = {},
  20. registered_on_crafts = {},
  21. }
  22. function crafting.register_type(name)
  23. crafting.recipes[name] = {}
  24. end
  25. local recipe_counter = 0
  26. function crafting.register_recipe(def)
  27. assert(def.output, "Output needed in recipe definition")
  28. assert(def.type, "Type needed in recipe definition")
  29. assert(def.items, "Items needed in recipe definition")
  30. def.level = def.level or 1
  31. local tab = crafting.recipes[def.type]
  32. assert(tab, "Unknown craft type " .. def.type)
  33. recipe_counter = recipe_counter + 1
  34. def.id = recipe_counter
  35. crafting.recipes_by_id[recipe_counter] = def
  36. tab[#tab + 1] = def
  37. return def.id
  38. end
  39. local unlocked_cache = {}
  40. function crafting.get_unlocked(name)
  41. local player = minetest.get_player_by_name(name)
  42. if not player then
  43. minetest.log("warning", "Crafting doesn't support getting unlocks for offline players")
  44. return {}
  45. end
  46. local retval = unlocked_cache[name]
  47. if not retval then
  48. retval = minetest.parse_json(player:get_attribute("crafting:unlocked")
  49. or "{}")
  50. unlocked_cache[name] = retval
  51. end
  52. return retval
  53. end
  54. if minetest then
  55. minetest.register_on_leaveplayer(function(player)
  56. unlocked_cache[player:get_player_name()] = nil
  57. end)
  58. end
  59. function crafting.unlock(name, output)
  60. local player = minetest.get_player_by_name(name)
  61. if not player then
  62. minetest.log("warning", "Crafting doesn't support setting unlocks for offline players")
  63. return {}
  64. end
  65. local unlocked = crafting.get_unlocked(name)
  66. if type(output) == "table" then
  67. for i=1, #output do
  68. unlocked[output[i]] = true
  69. minetest.chat_send_player(name, "You've unlocked " .. output[i])
  70. end
  71. else
  72. unlocked[output] = true
  73. minetest.chat_send_player(name, "You've unlocked " .. output)
  74. end
  75. unlocked_cache[name] = unlocked
  76. player:set_attribute("crafting:unlocked", minetest.write_json(unlocked))
  77. end
  78. function crafting.get_recipe(id)
  79. return crafting.recipes_by_id[id]
  80. end
  81. function crafting.get_all(type, level, item_hash, unlocked)
  82. assert(crafting.recipes[type], "No such craft type!")
  83. local results = {}
  84. for _, recipe in pairs(crafting.recipes[type]) do
  85. local craftable = true
  86. if recipe.level <= level and (recipe.always_known or unlocked[recipe.output]) then
  87. -- Check all ingredients are available
  88. local items = {}
  89. for _, item in pairs(recipe.items) do
  90. item = ItemStack(item)
  91. local needed_count = item:get_count()
  92. local available_count = item_hash[item:get_name()] or 0
  93. if available_count < needed_count then
  94. craftable = false
  95. end
  96. items[#items + 1] = {
  97. name = item:get_name(),
  98. have = available_count,
  99. need = needed_count,
  100. }
  101. end
  102. results[#results + 1] = {
  103. recipe = recipe,
  104. items = items,
  105. craftable = craftable,
  106. }
  107. end
  108. end
  109. return results
  110. end
  111. function crafting.set_item_hashes_from_list(inv, listname, item_hash)
  112. for _, stack in pairs(inv:get_list(listname)) do
  113. if not stack:is_empty() then
  114. local itemname = stack:get_name()
  115. item_hash[itemname] = (item_hash[itemname] or 0) + stack:get_count()
  116. local def = minetest.registered_items[itemname]
  117. if def and def.groups then
  118. for groupname, _ in pairs(def.groups) do
  119. local group = "group:" .. groupname
  120. item_hash[group] = (item_hash[group] or 0) + stack:get_count()
  121. end
  122. end
  123. end
  124. end
  125. end
  126. function crafting.get_all_for_player(player, type, level)
  127. local unlocked = crafting.get_unlocked(player:get_player_name())
  128. -- Get items hashed
  129. local item_hash = {}
  130. crafting.set_item_hashes_from_list(player:get_inventory(), "main", item_hash)
  131. return crafting.get_all(type, level, item_hash, unlocked)
  132. end
  133. function crafting.can_craft(name, type, level, recipe)
  134. local unlocked = crafting.get_unlocked(name)
  135. return recipe.type == type and recipe.level <= level and
  136. (recipe.always_known or unlocked[recipe.output])
  137. end
  138. local function give_all_to_player(inv, list)
  139. for _, item in pairs(list) do
  140. inv:add_item("main", item)
  141. end
  142. end
  143. function crafting.find_required_items(inv, listname, recipe)
  144. local items = {}
  145. for _, item in pairs(recipe.items) do
  146. item = ItemStack(item)
  147. local itemname = item:get_name()
  148. if item:get_name():sub(1, 6) == "group:" then
  149. local groupname = itemname:sub(7, #itemname)
  150. local required = item:get_count()
  151. -- Find stacks in group
  152. for i = 1, inv:get_size(listname) do
  153. local stack = inv:get_stack(listname, i)
  154. -- Is it in group?
  155. local def = minetest.registered_items[stack:get_name()]
  156. if def and def.groups and def.groups[groupname] then
  157. stack = ItemStack(stack)
  158. if stack:get_count() > required then
  159. stack:set_count(required)
  160. end
  161. items[#items + 1] = stack
  162. required = required - stack:get_count()
  163. if required == 0 then
  164. break
  165. end
  166. end
  167. end
  168. if required > 0 then
  169. return nil
  170. end
  171. else
  172. if inv:contains_item(listname, item) then
  173. items[#items + 1] = item
  174. else
  175. return nil
  176. end
  177. end
  178. end
  179. return items
  180. end
  181. function crafting.has_required_items(inv, listname, recipe)
  182. return crafting.find_required_items(inv, listname, recipe) ~= nil
  183. end
  184. function crafting.register_on_craft(func)
  185. table.insert(crafting.registered_on_crafts, func)
  186. end
  187. function crafting.perform_craft(name, inv, listname, outlistname, recipe)
  188. local items = crafting.find_required_items(inv, listname, recipe)
  189. if not items then
  190. return false
  191. end
  192. -- Take items
  193. local taken = {}
  194. for _, item in pairs(items) do
  195. item = ItemStack(item)
  196. local took = inv:remove_item(listname, item)
  197. taken[#taken + 1] = took
  198. if took:get_count() ~= item:get_count() then
  199. minetest.log("error", "Unexpected lack of items in inventory")
  200. give_all_to_player(inv, taken)
  201. return false
  202. end
  203. end
  204. for i=1, #crafting.registered_on_crafts do
  205. crafting.registered_on_crafts[i](name, recipe)
  206. end
  207. -- Add output
  208. inv:add_item(outlistname, recipe.output)
  209. return true
  210. end
  211. local function to_hex(str)
  212. return (str:gsub('.', function (c)
  213. return string.format('%02X', string.byte(c))
  214. end))
  215. end
  216. function crafting.calc_inventory_list_hash(inv, listname)
  217. local str = ""
  218. for _, stack in pairs(inv:get_list(listname)) do
  219. str = str .. stack:get_name() .. stack:get_count()
  220. end
  221. return minetest.sha1(to_hex(str))
  222. end