select_item = {} local S = minetest.get_translator("select_item") -- Cache for result of item filters local player_filters = {} local player_filter_results = {} local player_compares = {} local player_maxpage = {} local reset_player_info = function(playername) player_filters[playername] = nil player_filter_results[playername] = nil player_compares[playername] = nil player_maxpage[playername] = nil end -- Predefined filters select_item.filters = {} -- Filters out all items not for the Creative Inventory. -- This elimininates all items reserved for internal use. select_item.filters.creative = function(itemstring) local itemdef = minetest.registered_items[itemstring] if itemstring == "air" then return false end if itemdef.description == nil or itemdef.description == "" then return false end if minetest.get_item_group(itemstring, "not_in_creative_inventory") == 1 then return false end return true end -- No filtering select_item.filters.all = function() return true end local check_item = function(itemstring, filter) local itemdef = minetest.registered_items[itemstring] if itemstring == "" or itemstring == "unknown" or itemstring == "ignore" or itemdef == nil then return end if type(filter) == "function" then if filter(itemstring) ~= true then return false end end return true end local get_items = function(filter, compare) local it = {} for itemstring, itemdef in pairs(minetest.registered_items) do if check_item(itemstring, filter) then table.insert(it, {itemstring=itemstring, itemdef=itemdef}) end end local internal_compare if not compare then -- Default sorting: Move description-less items and -- items not_in_creative_inventory=1 to the end, then sort by itemstring. internal_compare = function(t1, t2) local t1d = minetest.registered_items[t1.itemstring].description local t2d = minetest.registered_items[t2.itemstring].description local t1g = minetest.get_item_group(t1.itemstring, "not_in_creative_inventory") local t2g = minetest.get_item_group(t2.itemstring, "not_in_creative_inventory") if (t1d == "" and t2d ~= "") then return false elseif (t1d ~= "" and t2d == "") then return true end if (t1g == 1 and t2g == 0) then return false elseif (t1g == 0 and t2g == 1) then return true end return t1.itemstring < t2.itemstring end else internal_compare = function(t1, t2) return compare(t1.itemstring, t2.itemstring) end end table.sort(it, internal_compare) return it end local xsize_norm = 12 local ysize_norm = 9 -- Opens the item selection dialog for player with the given filter function at page. -- The dialog has unique identifier dialogname. -- Returns: Number of items it displays. local show_dialog_page = function(playername, dialogname, filter, compare, page) local items if player_filters[playername] == nil then player_filters[playername] = filter player_compares[playername] = compare items = get_items(filter, compare) player_filter_results[playername] = items end items = player_filter_results[playername] local xsize, ysize, total_pages if #items < xsize_norm * ysize_norm then xsize = xsize_norm ysize = math.ceil(#items / xsize) total_pages = 1 else xsize = xsize_norm ysize = ysize_norm total_pages = math.ceil(#items / (xsize * ysize)) end local bg = "" -- Legacy default formspec background (MT<=0.4.17) if minetest.get_modpath("default") and default.gui_bg then bg = default.gui_bg .. default.gui_bg_img .. default.gui_slots end if #items == 0 then local form = "size[6,2]".. bg .. "label[0,0;"..minetest.formspec_escape(S("There are no items to choose from.")).."]".. "button_exit[0,1;2,1;cancel;"..minetest.formspec_escape(S("There are no items to choose from.")).."]" minetest.show_formspec(playername, "select_item:page1", form) return #items end local form = "size["..xsize..","..(ysize+1).."]" .. bg local x = 0 local y = 0.5 if page == nil then page = 1 end local start = 1 + (page-1) * xsize * ysize player_maxpage[playername] = total_pages form = form .. "label[0,0;"..minetest.formspec_escape(S("Select an item:")).."]" for i=start, #items do local itemstring = items[i].itemstring local itemdef = items[i].itemdef local name = "item_"..itemstring form = form .. "item_image_button["..x..","..y..";1,1;"..itemstring..";"..name..";]" if itemdef.description == nil or itemdef.description == "" then form = form .. "tooltip["..name..";"..itemstring.."]" end x = x + 1 if x >= xsize then x = 0 y = y + 1 if y >= ysize then break end end end local ynav = (ysize + 0.5) if total_pages > 1 then form = form .. "button[0,"..ynav..";1,1;previous;<]" form = form .. "button[1,"..ynav..";1,1;next;>]" form = form .. "label[2,"..ynav..";"..minetest.formspec_escape(S("Page @1/@2", page, total_pages)).."]" end form = form .. "button_exit["..(xsize-2)..","..ynav..";2,1;cancel;"..minetest.formspec_escape(S("Cancel")).."]" minetest.show_formspec(playername, "select_item:page"..page.."%%"..dialogname, form) return #items end select_item.show_dialog = function(playername, dialogname, filter, compare) show_dialog_page(playername, dialogname, filter, compare, 1) end local callbacks = {} select_item.register_on_select_item = function(callback) table.insert(callbacks, callback) end minetest.register_on_player_receive_fields(function(player, formname, fields) local playername = player:get_player_name() if string.sub(formname, 1, 16) == "select_item:page" then -- Parse formname local rest = string.sub(formname, 17, string.len(formname)) local split = string.split(rest, "%%", true, 2) local page = tonumber(split[1]) local dialogname = split[2] local item for field,_ in pairs(fields) do if string.sub(field, 1, 5) == "item_" then item = string.sub(field, 6, string.len(field)) break end end if item or fields.quit then local close = true for i=1,#callbacks do local ret = callbacks[i](playername, dialogname, item) if ret == false then close = false end end if close then minetest.close_formspec(playername, formname) end reset_player_info(playername) end if fields.quit or fields.cancel then reset_player_info(playername) end if page ~= nil then if fields.previous and page > 1 then show_dialog_page(player:get_player_name(), dialogname, player_filters[playername], player_compares[playername], page - 1) elseif fields.next then local maxpage = player_maxpage[playername] if maxpage and (page + 1 <= maxpage) then show_dialog_page(playername, dialogname, player_filters[playername], player_compares[playername], page + 1) end if not maxpage then minetest.log("warning", "[select_item] Player "..playername.." managed to navigate select_item menu without maxpage set!") end end end end end) minetest.register_on_leaveplayer(function(player) reset_player_info(player:get_player_name()) end)