api.lua 9.5 KB

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