init.lua 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353
  1. local F = minetest.formspec_escape
  2. local S = minetest.get_translator("chest_of_everything")
  3. local detached_inventories = {}
  4. -- Per-player lists (indexed by player name)
  5. local current_pages = {} -- current page number
  6. local current_max_pages = {} -- current max. page number
  7. local current_searches = {} -- current search string
  8. local SLOTS_W = 10
  9. local SLOTS_H = 5
  10. local SLOTS = SLOTS_W * SLOTS_H
  11. -- This determines how the items are sorted
  12. -- "by_type": Sort by item type (tool/craftitem/node/"chest_of_everything" items), then alphabetically by itemstring
  13. -- "abc": Alphabetically by itemstring
  14. local SORT_MODE = "by_type"
  15. local all_items_list -- cached list of all items
  16. -- Create detached inventories
  17. local function add_detached_inventories(player)
  18. local name = player:get_player_name()
  19. local inv_items = minetest.create_detached_inventory("chest_of_everything_items_"..name, {
  20. allow_move = function(inv, from_list, from_index, to_list, to_index, count, player)
  21. return 0
  22. end,
  23. allow_put = function(inv, listname, index, stack, player)
  24. return 0
  25. end,
  26. allow_take = function(inv, listname, index, stack, player)
  27. return -1
  28. end,
  29. }, name)
  30. local inv_trash = minetest.create_detached_inventory("chest_of_everything_trash_"..name, {
  31. allow_take = function(inv, listname, index, stack, player)
  32. return 0
  33. end,
  34. allow_move = function(inv, from_list, from_index, to_list, to_index, count, player)
  35. return 0
  36. end,
  37. on_put = function(inv, listname, index, stack, player)
  38. inv:set_list(listname, {})
  39. end,
  40. }, name)
  41. inv_trash:set_size("main", 1)
  42. detached_inventories[name] = { items = inv_items, trash = inv_trash }
  43. end
  44. local sort_items_by_type = function(item1, item2)
  45. --[[ Sort items in this order:
  46. * Bag of Everything
  47. * Chest of Everything
  48. * Test tools
  49. * Other tools
  50. * Craftitems
  51. * Other items
  52. * Items from the 'broken' mod
  53. * Dummy items ]]
  54. local def1 = minetest.registered_items[item1]
  55. local def2 = minetest.registered_items[item2]
  56. local tool1 = def1.type == "tool"
  57. local tool2 = def2.type == "tool"
  58. local testtool1 = minetest.get_item_group(item1, "testtool") == 1
  59. local testtool2 = minetest.get_item_group(item2, "testtool") == 1
  60. local dummy1 = minetest.get_item_group(item1, "dummy") == 1
  61. local dummy2 = minetest.get_item_group(item2, "dummy") == 1
  62. local broken1 = def1.mod_origin == "broken"
  63. local broken2 = def2.mod_origin == "broken"
  64. local craftitem1 = def1.type == "craft"
  65. local craftitem2 = def2.type == "craft"
  66. if item1 == "chest_of_everything:bag" then
  67. return true
  68. elseif item2 == "chest_of_everything:bag" then
  69. return false
  70. elseif item1 == "chest_of_everything:chest" then
  71. return true
  72. elseif item2 == "chest_of_everything:chest" then
  73. return false
  74. elseif dummy1 and not dummy2 then
  75. return false
  76. elseif not dummy1 and dummy2 then
  77. return true
  78. elseif broken1 and not broken2 then
  79. return false
  80. elseif not broken1 and broken2 then
  81. return true
  82. elseif testtool1 and not testtool2 then
  83. return true
  84. elseif not testtool1 and testtool2 then
  85. return false
  86. elseif tool1 and not tool2 then
  87. return true
  88. elseif not tool1 and tool2 then
  89. return false
  90. elseif craftitem1 and not craftitem2 then
  91. return true
  92. elseif not craftitem1 and craftitem2 then
  93. return false
  94. else
  95. return item1 < item2
  96. end
  97. end
  98. local sort_items_alphabetically = function(item1, item2)
  99. return item1 < item2
  100. end
  101. local collect_items = function(filter, lang_code)
  102. local items = {}
  103. if filter then
  104. filter = string.trim(filter)
  105. filter = string.lower(filter) -- to make sure the search is case-insensitive
  106. end
  107. for itemstring, def in pairs(minetest.registered_items) do
  108. if itemstring ~= "" and itemstring ~= "unknown" and itemstring ~= "ignore" then
  109. if filter and lang_code then
  110. local desc = ItemStack(itemstring):get_description()
  111. local matches
  112. -- First, try to match original description
  113. if desc ~= "" then
  114. local ldesc = string.lower(desc)
  115. matches = string.match(ldesc, filter) ~= nil
  116. -- Second, try to match translated description
  117. if not matches then
  118. local tdesc = minetest.get_translated_string(lang_code, desc)
  119. if tdesc ~= "" then
  120. tdesc = string.lower(tdesc)
  121. matches = string.match(tdesc, filter) ~= nil
  122. end
  123. end
  124. -- Third, try to match translated short description
  125. if not matches then
  126. local sdesc = ItemStack(itemstring):get_short_description()
  127. if sdesc ~= "" then
  128. sdesc = minetest.get_translated_string(lang_code, sdesc)
  129. sdesc = string.lower(sdesc)
  130. matches = string.match(sdesc, filter) ~= nil
  131. end
  132. end
  133. end
  134. -- Fourth, try to match itemstring
  135. if not matches then
  136. matches = string.match(itemstring, filter) ~= nil
  137. end
  138. -- If item was matched, add to item list
  139. if matches then
  140. table.insert(items, itemstring)
  141. end
  142. else
  143. table.insert(items, itemstring)
  144. end
  145. end
  146. end
  147. local compare
  148. if SORT_MODE == "by_type" then
  149. compare = sort_items_by_type
  150. elseif SORT_MODE == "abc" then
  151. compare = sort_items_alphabetically
  152. end
  153. table.sort(items, compare)
  154. return items
  155. end
  156. local function update_inventory(name)
  157. local search = current_searches[name] or ""
  158. local items
  159. if search == "" then
  160. items = all_items_list
  161. else
  162. local lang_code = minetest.get_player_information(name).lang_code
  163. items = collect_items(search, lang_code)
  164. end
  165. local max_page = math.ceil(#items / SLOTS)
  166. current_max_pages[name] = max_page
  167. local inv = detached_inventories[name].items
  168. inv:set_size("main", #items)
  169. inv:set_list("main", items)
  170. if not current_pages[name] then
  171. current_pages[name] = 1
  172. end
  173. if current_pages[name] > max_page then
  174. current_pages[name] = max_page
  175. end
  176. if current_pages[name] < 1 then
  177. current_pages[name] = 1
  178. end
  179. end
  180. local function get_formspec(page, name)
  181. local start = 0 + (page-1)*SLOTS
  182. if not name then
  183. return ""
  184. end
  185. local player = minetest.get_player_by_name(name)
  186. local playerinvsize = player:get_inventory():get_size("main")
  187. local hotbarsize = player:hud_get_hotbar_itemcount()
  188. local pinv_w, pinv_h, pinv_x
  189. pinv_w = hotbarsize
  190. pinv_h = math.ceil(playerinvsize / pinv_w)
  191. pinv_w = math.min(pinv_w, 10)
  192. pinv_h = math.min(pinv_w, 4)
  193. pinv_x = 0
  194. if pinv_w < 9 then
  195. pinv_x = 1
  196. end
  197. local pagestr = ""
  198. local max_page = current_max_pages[name]
  199. if max_page > 1 then
  200. pagestr = "button[0,5.45;1,1;chest_of_everything_prev;"..F(S("<")).."]"..
  201. "button[1,5.45;1,1;chest_of_everything_next;"..F(S(">")).."]"..
  202. "label[0,5.1;"..F(S("Page: @1/@2", page, max_page)).."]"
  203. end
  204. local search_text = current_searches[name] or ""
  205. local inventory_list
  206. if current_max_pages[name] > 0 then
  207. inventory_list = "list[detached:chest_of_everything_items_"..name..";main;0,0;"..SLOTS_W..","..SLOTS_H..";"..start.."]"
  208. else
  209. inventory_list = "label[2.5,2.5;"..F(S("No items found.")).."]"
  210. if search_text ~= "" then
  211. inventory_list = inventory_list .. "button[2.5,3.25;3,0.8;search_button_reset_big;"..F(S("Reset search")).."]"
  212. end
  213. end
  214. return "size[10,10.5]"..
  215. inventory_list ..
  216. "list[current_player;main;"..pinv_x..",6.75;"..pinv_w..","..pinv_h..";]" ..
  217. "label[9,5.1;"..F(S("Trash:")).."]" ..
  218. "list[detached:chest_of_everything_trash_"..name..";main;9,5.5;1,1]" ..
  219. "field[2.2,5.75;4,1;search;;"..F(search_text).."]" ..
  220. "field_enter_after_edit[search;true]" ..
  221. "field_close_on_enter[search;false]" ..
  222. "button[6,5.45;1.6,1;search_button_start;"..F(S("Search")).."]" ..
  223. "button[7.6,5.45;0.8,1;search_button_reset;"..F(S("X")).."]" ..
  224. "tooltip[search_button_reset;"..F(S("Reset search")).."]" ..
  225. pagestr ..
  226. "listring[detached:chest_of_everything_items_"..name..";main]"..
  227. "listring[current_player;main]"..
  228. "listring[detached:chest_of_everything_trash_"..name..";main]"
  229. end
  230. local show_formspec = function(name)
  231. local page = current_pages[name]
  232. local form = get_formspec(page, name)
  233. minetest.show_formspec(name, "chest_of_everything:getitem", form)
  234. return true
  235. end
  236. minetest.register_on_player_receive_fields(function(player, formname, fields)
  237. if formname ~= "chest_of_everything:getitem" then
  238. return
  239. end
  240. local name = player:get_player_name()
  241. local page = current_pages[name]
  242. local old_page = page
  243. -- Next page or previous page
  244. if fields.chest_of_everything_next or fields.chest_of_everything_prev then
  245. if fields.chest_of_everything_next then
  246. page = page + 1
  247. elseif fields.chest_of_everything_prev then
  248. page = page - 1
  249. end
  250. -- Handle page change
  251. if page < 1 then
  252. page = 1
  253. end
  254. local max_page = current_max_pages[name]
  255. if page > max_page then
  256. page = max_page
  257. end
  258. if page ~= old_page then
  259. current_pages[name] = page
  260. show_formspec(name)
  261. end
  262. return
  263. -- Search
  264. elseif (fields.search_button_start or (fields.key_enter and fields.key_enter_field == "search")) and fields.search then
  265. current_searches[name] = fields.search
  266. update_inventory(name)
  267. show_formspec(name, fields.search)
  268. return
  269. -- Reset search
  270. elseif (fields.search_button_reset or fields.search_button_reset_big) then
  271. current_searches[name] = ""
  272. update_inventory(name)
  273. show_formspec(name)
  274. return
  275. end
  276. end)
  277. minetest.register_tool("chest_of_everything:bag", {
  278. description = S("Bag of Everything") .. "\n" ..
  279. S("Grants access to all items"),
  280. inventory_image = "chest_of_everything_bag.png",
  281. wield_image = "chest_of_everything_bag.png",
  282. groups = { disable_repair = 1 },
  283. on_use = function(itemstack, user)
  284. if user and user:is_player() then
  285. local name = user:get_player_name()
  286. show_formspec(name)
  287. end
  288. end,
  289. })
  290. minetest.register_node("chest_of_everything:chest", {
  291. description = S("Chest of Everything") .. "\n" ..
  292. S("Grants access to all items"),
  293. tiles ={"chest_of_everything_chest.png^[sheet:2x2:0,0", "chest_of_everything_chest.png^[sheet:2x2:0,0",
  294. "chest_of_everything_chest.png^[sheet:2x2:1,0", "chest_of_everything_chest.png^[sheet:2x2:1,0",
  295. "chest_of_everything_chest.png^[sheet:2x2:1,0", "chest_of_everything_chest.png^[sheet:2x2:0,1"},
  296. paramtype2 = "4dir",
  297. groups = { dig_immediate=2, choppy=3 },
  298. is_ground_content = false,
  299. on_construct = function(pos)
  300. local meta = minetest.get_meta(pos)
  301. meta:set_string("infotext", S("Chest of Everything"))
  302. end,
  303. on_rightclick = function(pos, node, clicker)
  304. if clicker and clicker:is_player() then
  305. local name = clicker:get_player_name()
  306. show_formspec(name)
  307. end
  308. end,
  309. })
  310. minetest.register_on_mods_loaded(function()
  311. all_items_list = collect_items()
  312. end)
  313. minetest.register_on_joinplayer(function(player)
  314. local name = player:get_player_name()
  315. current_searches[name] = ""
  316. current_pages[name] = 1
  317. current_max_pages[name] = 0
  318. add_detached_inventories(player)
  319. update_inventory(name)
  320. end)
  321. minetest.register_on_leaveplayer(function(player)
  322. local name = player:get_player_name()
  323. current_pages[name] = nil
  324. current_max_pages[name] = nil
  325. current_searches[name] = nil
  326. end)