An extensible inventory mod which allows searching crafting and browsing for recipes in the same dialogue. https://content.minetest.net/packages/RealBadAngel/unified_inventory/
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

308 lines
9.2KB

  1. local S = minetest.get_translator("unified_inventory")
  2. local F = minetest.formspec_escape
  3. -- Create detached creative inventory after loading all mods
  4. minetest.after(0.01, function()
  5. local rev_aliases = {}
  6. for source, target in pairs(minetest.registered_aliases) do
  7. if not rev_aliases[target] then rev_aliases[target] = {} end
  8. table.insert(rev_aliases[target], source)
  9. end
  10. unified_inventory.items_list = {}
  11. for name, def in pairs(minetest.registered_items) do
  12. if (not def.groups.not_in_creative_inventory or
  13. def.groups.not_in_creative_inventory == 0) and
  14. def.description and def.description ~= "" then
  15. table.insert(unified_inventory.items_list, name)
  16. local all_names = rev_aliases[name] or {}
  17. table.insert(all_names, name)
  18. for _, name in ipairs(all_names) do
  19. local recipes = minetest.get_all_craft_recipes(name)
  20. if recipes then
  21. for _, recipe in ipairs(recipes) do
  22. local unknowns
  23. for _,chk in pairs(recipe.items) do
  24. local groupchk = string.find(chk, "group:")
  25. if (not groupchk and not minetest.registered_items[chk])
  26. or (groupchk and not unified_inventory.get_group_item(string.gsub(chk, "group:", "")).item)
  27. or minetest.get_item_group(chk, "not_in_craft_guide") ~= 0 then
  28. unknowns = true
  29. end
  30. end
  31. if not unknowns then
  32. unified_inventory.register_craft(recipe)
  33. end
  34. end
  35. end
  36. end
  37. end
  38. end
  39. table.sort(unified_inventory.items_list)
  40. unified_inventory.items_list_size = #unified_inventory.items_list
  41. print("Unified Inventory. inventory size: "..unified_inventory.items_list_size)
  42. for _, name in ipairs(unified_inventory.items_list) do
  43. local def = minetest.registered_items[name]
  44. -- Simple drops
  45. if type(def.drop) == "string" then
  46. local dstack = ItemStack(def.drop)
  47. if not dstack:is_empty() and dstack:get_name() ~= name then
  48. unified_inventory.register_craft({
  49. type = "digging",
  50. items = {name},
  51. output = def.drop,
  52. width = 0,
  53. })
  54. end
  55. -- Complex drops. Yes, it's really complex!
  56. elseif type(def.drop) == "table" then
  57. --[[ Extract single items from the table and save them into dedicated tables
  58. to register them later, in order to avoid duplicates. These tables counts
  59. the total number of guaranteed drops and drops by chance (“maybes”) for each item.
  60. For “maybes”, the final count is the theoretical maximum number of items, not
  61. neccessarily the actual drop count. ]]
  62. local drop_guaranteed = {}
  63. local drop_maybe = {}
  64. -- This is for catching an obscure corner case: If the top items table has
  65. -- only items with rarity = 1, but max_items is set, then only the first
  66. -- max_items will be part of the drop, any later entries are logically
  67. -- impossible, so this variable is for keeping track of this
  68. local max_items_left = def.drop.max_items
  69. -- For checking whether we still encountered only guaranteed only so far;
  70. -- for the first “maybe” item it will become false which will cause ALL
  71. -- later items to be considered “maybes”.
  72. -- A common idiom is:
  73. -- { max_items 1, { items = {
  74. -- { items={"example:1"}, rarity = 5 },
  75. -- { items={"example:2"}, rarity = 1 }, }}}
  76. -- example:2 must be considered a “maybe” because max_items is set and it
  77. -- appears after a “maybe”
  78. local max_start = true
  79. -- Let's iterate through the items madness!
  80. -- Handle invalid drop entries gracefully.
  81. local drop_items = def.drop.items or { }
  82. for i=1,#drop_items do
  83. if max_items_left ~= nil and max_items_left <= 0 then break end
  84. local itit = drop_items[i]
  85. for j=1,#itit.items do
  86. local dstack = ItemStack(itit.items[j])
  87. if not dstack:is_empty() and dstack:get_name() ~= name then
  88. local dname = dstack:get_name()
  89. local dcount = dstack:get_count()
  90. -- Guaranteed drops AND we are not yet in “maybe mode”
  91. if #itit.items == 1 and itit.rarity == 1 and max_start then
  92. if drop_guaranteed[dname] == nil then
  93. drop_guaranteed[dname] = 0
  94. end
  95. drop_guaranteed[dname] = drop_guaranteed[dname] + dcount
  96. if max_items_left ~= nil then
  97. max_items_left = max_items_left - 1
  98. if max_items_left <= 0 then break end
  99. end
  100. -- Drop was a “maybe”
  101. else
  102. if max_items_left ~= nil then max_start = false end
  103. if drop_maybe[dname] == nil then
  104. drop_maybe[dname] = 0
  105. end
  106. drop_maybe[dname] = drop_maybe[dname] + dcount
  107. end
  108. end
  109. end
  110. end
  111. for itemstring, count in pairs(drop_guaranteed) do
  112. unified_inventory.register_craft({
  113. type = "digging",
  114. items = {name},
  115. output = itemstring .. " " .. count,
  116. width = 0,
  117. })
  118. end
  119. for itemstring, count in pairs(drop_maybe) do
  120. unified_inventory.register_craft({
  121. type = "digging_chance",
  122. items = {name},
  123. output = itemstring .. " " .. count,
  124. width = 0,
  125. })
  126. end
  127. end
  128. end
  129. for _, recipes in pairs(unified_inventory.crafts_for.recipe) do
  130. for _, recipe in ipairs(recipes) do
  131. local ingredient_items = {}
  132. for _, spec in pairs(recipe.items) do
  133. local matches_spec = unified_inventory.canonical_item_spec_matcher(spec)
  134. for _, name in ipairs(unified_inventory.items_list) do
  135. if matches_spec(name) then
  136. ingredient_items[name] = true
  137. end
  138. end
  139. end
  140. for name, _ in pairs(ingredient_items) do
  141. if unified_inventory.crafts_for.usage[name] == nil then
  142. unified_inventory.crafts_for.usage[name] = {}
  143. end
  144. table.insert(unified_inventory.crafts_for.usage[name], recipe)
  145. end
  146. end
  147. end
  148. end)
  149. -- load_home
  150. local function load_home()
  151. local input = io.open(unified_inventory.home_filename, "r")
  152. if not input then
  153. unified_inventory.home_pos = {}
  154. return
  155. end
  156. while true do
  157. local x = input:read("*n")
  158. if not x then break end
  159. local y = input:read("*n")
  160. local z = input:read("*n")
  161. local name = input:read("*l")
  162. unified_inventory.home_pos[name:sub(2)] = {x = x, y = y, z = z}
  163. end
  164. io.close(input)
  165. end
  166. load_home()
  167. function unified_inventory.set_home(player, pos)
  168. local player_name = player:get_player_name()
  169. unified_inventory.home_pos[player_name] = vector.round(pos)
  170. -- save the home data from the table to the file
  171. local output = io.open(unified_inventory.home_filename, "w")
  172. for k, v in pairs(unified_inventory.home_pos) do
  173. output:write(v.x.." "..v.y.." "..v.z.." "..k.."\n")
  174. end
  175. io.close(output)
  176. end
  177. function unified_inventory.go_home(player)
  178. local pos = unified_inventory.home_pos[player:get_player_name()]
  179. if pos then
  180. player:set_pos(pos)
  181. end
  182. end
  183. -- register_craft
  184. function unified_inventory.register_craft(options)
  185. if not options.output then
  186. return
  187. end
  188. local itemstack = ItemStack(options.output)
  189. if itemstack:is_empty() then
  190. return
  191. end
  192. if options.type == "normal" and options.width == 0 then
  193. options = { type = "shapeless", items = options.items, output = options.output, width = 0 }
  194. end
  195. if not unified_inventory.crafts_for.recipe[itemstack:get_name()] then
  196. unified_inventory.crafts_for.recipe[itemstack:get_name()] = {}
  197. end
  198. table.insert(unified_inventory.crafts_for.recipe[itemstack:get_name()],options)
  199. end
  200. local craft_type_defaults = {
  201. width = 3,
  202. height = 3,
  203. uses_crafting_grid = false,
  204. }
  205. function unified_inventory.craft_type_defaults(name, options)
  206. if not options.description then
  207. options.description = name
  208. end
  209. setmetatable(options, {__index = craft_type_defaults})
  210. return options
  211. end
  212. function unified_inventory.register_craft_type(name, options)
  213. unified_inventory.registered_craft_types[name] =
  214. unified_inventory.craft_type_defaults(name, options)
  215. end
  216. unified_inventory.register_craft_type("normal", {
  217. description = F(S("Crafting")),
  218. icon = "ui_craftgrid_icon.png",
  219. width = 3,
  220. height = 3,
  221. get_shaped_craft_width = function (craft) return craft.width end,
  222. dynamic_display_size = function (craft)
  223. local w = craft.width
  224. local h = math.ceil(table.maxn(craft.items) / craft.width)
  225. local g = w < h and h or w
  226. return { width = g, height = g }
  227. end,
  228. uses_crafting_grid = true,
  229. })
  230. unified_inventory.register_craft_type("shapeless", {
  231. description = F(S("Mixing")),
  232. icon = "ui_craftgrid_icon.png",
  233. width = 3,
  234. height = 3,
  235. dynamic_display_size = function (craft)
  236. local maxn = table.maxn(craft.items)
  237. local g = 1
  238. while g*g < maxn do g = g + 1 end
  239. return { width = g, height = g }
  240. end,
  241. uses_crafting_grid = true,
  242. })
  243. unified_inventory.register_craft_type("cooking", {
  244. description = F(S("Cooking")),
  245. icon = "default_furnace_front.png",
  246. width = 1,
  247. height = 1,
  248. })
  249. unified_inventory.register_craft_type("digging", {
  250. description = F(S("Digging")),
  251. icon = "default_tool_steelpick.png",
  252. width = 1,
  253. height = 1,
  254. })
  255. unified_inventory.register_craft_type("digging_chance", {
  256. description = "Digging (by chance)",
  257. icon = "default_tool_steelpick.png^[transformFY.png",
  258. width = 1,
  259. height = 1,
  260. })
  261. function unified_inventory.register_page(name, def)
  262. unified_inventory.pages[name] = def
  263. end
  264. function unified_inventory.register_button(name, def)
  265. if not def.action then
  266. def.action = function(player)
  267. unified_inventory.set_inventory_formspec(player, name)
  268. end
  269. end
  270. def.name = name
  271. table.insert(unified_inventory.buttons, def)
  272. end
  273. function unified_inventory.is_creative(playername)
  274. return minetest.check_player_privs(playername, {creative=true})
  275. or minetest.settings:get_bool("creative_mode")
  276. end