local set_fs = i3.set_fs local hud_notif = i3.hud_notif local POLL_FREQ = 0.25 IMPORT("reg_items", "reg_nodes") IMPORT("fmt", "search", "table_merge", "array_diff") IMPORT("is_group", "extract_groups", "item_has_groups", "apply_recipe_filters") i3.remove_minitab"Nodes" i3.remove_minitab"Items" i3.new_minitab("Unlocked", { sorter = function(item, data) return data.items_progress[item] end }) local function get_filtered_items(player, data) local items, known = {}, 0 for i = 1, #i3.init_items do local item = i3.init_items[i] local recipes = i3.recipes_cache[item] local usages = i3.usages_cache[item] recipes = #apply_recipe_filters(recipes or {}, player) usages = #apply_recipe_filters(usages or {}, player) if recipes > 0 or usages > 0 then items[item] = true known += recipes + usages end end data.known_recipes = known return items end local function item_in_inv(item, inv_items) local inv_items_size = #inv_items if is_group(item) then local groupname = item:sub(7) local group_cache = i3.groups[groupname] local groups = group_cache and group_cache.groups or extract_groups(item) for i = 1, inv_items_size do local def = core.registered_items[inv_items[i]] if def then if item_has_groups(def.groups, groups) then return true end end end else for i = 1, inv_items_size do if inv_items[i] == item then return true end end end end local function recipe_in_inv(rcp, inv_items) for _, item in pairs(rcp.items) do if not item_in_inv(item, inv_items) then return end end return true end local function progressive_filter(recipes, player) if not recipes then return {} end local name = player:get_player_name() local data = i3.data[name] if #data.inv_items == 0 then return {} end local filtered, c = {}, 0 for i = 1, #recipes do local recipe = recipes[i] if recipe_in_inv(recipe, data.inv_items) then c++ filtered[c] = recipe end end return filtered end local item_lists = {"main", "craft", "craftpreview"} local function get_inv_items(player) local inv = player:get_inventory() if not inv then return {} end local stacks = {} for i = 1, #item_lists do local list = inv:get_list(item_lists[i]) table_merge(stacks, list) end local inv_items, c = {}, 0 for i = 1, #stacks do local stack = stacks[i] if not stack:is_empty() then local name = stack:get_name() if core.registered_items[name] then c++ inv_items[c] = name end end end return inv_items end -- Workaround. Need an engine call to detect when the contents of -- the player inventory changed, instead. local function poll_new_items(player, data, join) local inv_items = get_inv_items(player) local diff = array_diff(inv_items, data.inv_items) if join or #diff > 0 then data.inv_items = table_merge(diff, data.inv_items) local oldknown = data.known_recipes or 0 local items = get_filtered_items(player, data) data.discovered = data.known_recipes - oldknown if data.discovered > 0 then local msg = fmt("%u new recipe%s unlocked!", data.discovered, data.discovered > 1 and "s" or "") local last_discovered = diff[1] local img = reg_items[last_discovered].inventory_image if reg_nodes[last_discovered] then local id = core.get_content_id(last_discovered) img = i3.cubes[id] or img end hud_notif(data.player_name, msg, img) end data.items_progress = items data.itab = 1 search(data) set_fs(player) end core.after(POLL_FREQ, poll_new_items, player, data) end i3.add_recipe_filter("Default progressive filter", progressive_filter) core.register_on_joinplayer(function(player) local name = player:get_player_name() local data = i3.data[name] if not data then return end data.inv_items = data.inv_items or {} data.known_recipes = data.known_recipes or 0 data.discovered = data.discovered or 0 poll_new_items(player, data, true) end)