Grouping and filtering framework implemented

This commit is contained in:
Alexander Weber 2017-02-11 23:30:24 +01:00
parent 9c4ff2e2a8
commit dfed714d76
6 changed files with 300 additions and 117 deletions

148
cache.lua
View File

@ -1,17 +1,86 @@
local filter = smart_inventory.filter
local cache = {}
cache.groups = {} --cache.groups[group][itemname] = itemdef
cache.in_recipe = {} --table.insert(cache.in_recipe[recipe_item][itemname], info)
cache.groups = {}
cache.items = {}
cache.recipes = {}
cache.group_placeholder = {}
cache.group_info = {
group_level = { shortdesc = "Uses level information" },
group_dig_immediate = { shortdesc = "Fast removable" },
group_disable_jump = { shortdesc = "Not jumpable" },
group_less_damage = { shortdesc = "Less damage" },
group_more_damage = { shortdesc = "More damage" },
group_bouncy = { shortdesc = "Bouncy" },
group_falling_node = { shortdesc = "Falling" },
group_attached_node = { shortdesc = "Attachable" },
group_connect_to_raillike = { shortdesc = "Rail-like" },
-- TODO: http://dev.minetest.net/Groups/Custom_groups
type_tool = { shortdesc = "Tools" },
type_node = { shortdesc = "Node" },
-- custom
transluc = { shortdesc = "Translucent blocks" },
inventory = { shortdesc = "Chestlike vessels" },
-- list specific
all = {shortdesc = "All items" },
other = {shortdesc = "Other items" }
}
local function add_to_cache(group_name, def, shortdesc)
if not cache.groups[group_name] then
local group = {
name = group_name,
group_desc = shortdesc,
items = {}
}
if not group.group_desc and cache.group_info[group_name] then
group.group_desc = cache.group_info[group_name].shortdesc
end
if not group.group_desc then
group.group_desc = group_name
end
cache.groups[group_name] = group
end
table.insert(cache.groups[group_name].items,def)
if not cache.items[def.name] then
local item = {
groups = {}
}
cache.items[def.name] = item
end
table.insert(cache.items[def.name].groups,cache.groups[group_name])
end
-- build cache at start
function cache:fill_cache()
function cache.fill_cache()
for name, def in pairs(minetest.registered_items) do
-- cache groups
if def.description and def.description ~= "" and not def.groups.not_in_creative_inventory then
for group, grval in pairs(def.groups) do
if not self.groups[group] then
self.groups[group] = {}
local group_name = "group_"..group
if group == "fall_damage_add_percent" and grval < 0 then
group_name = "group_less_damage"
elseif group == "fall_damage_add_percent" and grval > 0 then
group_name = "group_more_damage"
else
group_name = "group_"..group
end
add_to_cache(group_name, def)
end
add_to_cache("type_"..def.type, def)
add_to_cache("mod_"..def.mod_origin, def) -- TODO: get mod description and add it to shortdesc
-- extended registred filters
for _, flt in pairs(filter.registered_filter) do
if flt:check_item_by_def(def) == true then
add_to_cache("filter_"..flt.name, def, flt.shortdesc)
end
self.groups[group][name] = def
end
end
@ -20,14 +89,14 @@ function cache:fill_cache()
for _, recipe in ipairs(recipelist) do
if recipe.output ~= "" then
for idx, recipe_item in pairs(recipe.items) do
if not self.in_recipe[recipe_item] then
self.in_recipe[recipe_item] = {}
if not cache.recipes[recipe_item] then
cache.recipes[recipe_item] = {}
end
if not self.in_recipe[recipe_item][name] then
self.in_recipe[recipe_item][name] = {}
if not cache.recipes[recipe_item][name] then
cache.recipes[recipe_item][name] = {}
end
-- "recipe_item" is in recipe of item "name". Multiple recipes possible
table.insert(self.in_recipe[recipe_item][name], recipe)
table.insert(cache.recipes[recipe_item][name], recipe)
end
end
end
@ -36,7 +105,7 @@ function cache:fill_cache()
end
-- Get all recipes with at least one item existing in players inventory
function cache:get_recipes_craftable_atnext(player)
function cache.get_recipes_craftable_atnext(player)
local inventory = minetest.get_player_by_name(player):get_inventory()
local invlist = inventory:get_list("main")
local items_in_inventory = {}
@ -47,15 +116,15 @@ function cache:get_recipes_craftable_atnext(player)
items_in_inventory[itemname] = true
end
for recipe_item, recipe_item_data in pairs(self.in_recipe) do
for recipe_item, recipe_item_data in pairs(cache.recipes) do
-- prepare current stack for crafting simulation
local item_ok = false
if items_in_inventory[recipe_item] then
item_ok = true
elseif recipe_item:sub(1, 6) == "group:" then
local group_name = recipe_item:sub(7)
if self.groups[group_name] then
for group_item, def in pairs(self.groups[group_name]) do
if cache.group_placeholder[group_name] then
for group_item, def in pairs(cache.group_placeholder[group_name]) do
if items_in_inventory[group_item] then
item_ok = true
end
@ -74,8 +143,8 @@ function cache:get_recipes_craftable_atnext(player)
end
-- Get all recipes with all required items in players inventory. Without count match
function cache:get_recipes_craftable(player)
local all, items_in_inventory = self:get_recipes_craftable_atnext(player)
function cache.get_recipes_craftable(player)
local all, items_in_inventory = cache.get_recipes_craftable_atnext(player)
local craftable = {}
for recipe, _ in pairs(all) do
local out_recipe = table.copy(recipe)
@ -85,8 +154,8 @@ function cache:get_recipes_craftable(player)
local in_inventory = false
if item:sub(1, 6) == "group:" then
local group_name = item:sub(7)
if self.groups[group_name] then
for group_item, def in pairs(self.groups[group_name]) do
if cache.group_placeholder[group_name] then
for group_item, def in ipairs(cache.group_placeholder[group_name].items) do
if items_in_inventory[group_item] then
in_inventory = true
out_recipe.items[idx] = group_item
@ -108,8 +177,47 @@ function cache:get_recipes_craftable(player)
return craftable, items_in_inventory
end
function cache.get_list_grouped(itemtable)
local grouped = {}
local other = {}
-- sort the entries to groups
for _, entry in ipairs(itemtable) do
local assigned = false
if cache.items[entry.item] then
for _, group in ipairs(cache.items[entry.item].groups) do
if not grouped[group.name] then
local group_info = table.copy(cache.groups[group.name])
group_info.items = {}
grouped[group.name] = group_info
end
table.insert(grouped[group.name].items, entry)
assigned = true
end
end
if assigned == false then
table.insert(other, entry)
end
end
-- TODO: magic to calculate relevant groups
-- default groups
grouped.all = {}
grouped.all.name = "all"
grouped.all.group_desc = cache.group_info[grouped.all.name].shortdesc
grouped.all.items = itemtable
grouped.other = {}
grouped.other.name = "other"
grouped.other.group_desc = cache.group_info[grouped.all.name].shortdesc
grouped.other.items = other
return grouped
end
-- fill the cache after all mods loaded
minetest.after(0, cache.fill_cache, cache)
minetest.after(0, cache.fill_cache)
-- return the reference to the mod
return cache

View File

@ -5,29 +5,83 @@ local function on_item_select(state, itemdef, recipe)
if itemdef then
inf_state:get("info1"):setText(itemdef.description)
inf_state:get("info2"):setText("("..itemdef.name..")")
if itemdef._doc_items_longdesc then
inf_state:get("info3"):setText(itemdef._doc_items_longdesc)
else
inf_state:get("info3"):setText("")
end
if recipe.type ~="normal" then
inf_state:get("cr_type"):setText(recipe.type)
else
inf_state:get("cr_type"):setText("")
end
inf_state:get("craft_preview"):setCraft(recipe)
inf_state:get("craft_result"):setImage(recipe.output)
inf_state:get("craft_result"):setIsHidden(false)
state:get("craft_preview"):setCraft(recipe)
else
inf_state:get("info1"):setText("")
inf_state:get("info2"):setText("")
inf_state:get("info3"):setText("")
inf_state:get("cr_type"):setText("")
inf_state:get("craft_preview"):setCraft(nil)
inf_state:get("craft_result"):setIsHidden(true)
state:get("craft_preview"):setCraft(nil)
end
end
local function update_group_selection(state)
state.param.craftable_list = nil
-- get grouped
local grouped = state.param.grouped_items
if grouped[state.param.selected_filter] then
state.param.craftable_list = grouped[state.param.selected_filter].items
else
--reset if unknown selection
state.param.craftable_list = grouped.all.items
state.param.selected_filter = "all"
end
table.sort(state.param.craftable_list, function(a,b)
return a.item < b.item
end)
local grid = state:get("buttons_grid")
grid:setList(state.param.craftable_list)
-- set group dropdown list
local groups_sel = state:get("groups_sel")
groups_sel:clearItems()
local group_sorted = {}
for _, group in pairs(grouped) do
table.insert(group_sorted, group)
end
table.sort(group_sorted, function(a,b)
if a.name == "all" then
return true
elseif a.name == "other" then
return false
else
return a.name < b.name
end
end)
for _, group in ipairs(group_sorted) do
if #group.items > 1 then
local idx = groups_sel:addItem(group.group_desc.." ("..#group.items..")")
state.param.group_list[idx] = group.name
end
end
end
local function update_craftable_list(state)
state.param.craftable_list = {}
state.param.group_list_labels = {}
state.param.group_list = {}
state.param.grouped_items = {}
local player = state.location.rootState.location.player
local craftable = cache:get_recipes_craftable(player)
local craftable = cache.get_recipes_craftable(player)
local duplicate_index_tmp = {}
local group_list = {}
local craftable_itemlist = {}
-- get the full list of craftable
for recipe, _ in pairs(craftable) do
local def = minetest.registered_items[recipe.output]
if not def then
@ -38,8 +92,8 @@ local function update_craftable_list(state)
end)
end
if def then
if duplicate_index_tmp[def] then
table.insert(duplicate_index_tmp[def].recipes, recipe)
if duplicate_index_tmp[def.name] then
table.insert(duplicate_index_tmp[def.name].recipes, recipe)
else
local entry = {
itemdef = def,
@ -48,67 +102,31 @@ local function update_craftable_list(state)
item = def.name,
is_button = true
}
duplicate_index_tmp[def] = entry
for group, _ in pairs(def.groups) do
if group_list[group] then
group_list[group] = group_list[group] + 1
else
group_list[group] = 1
end
end
if not state.param.selected_group or
state.param.selected_group == "all" or
def.groups[state.param.selected_group] then
table.insert(entry.recipes, recipe)
table.insert(state.param.craftable_list, entry)
table.insert(craftable_itemlist, entry)
end
end
end
end
table.sort(state.param.craftable_list, function(a,b)
return a.item < b.item
end)
local grid = state:get("buttons_grid")
grid:setList(state.param.craftable_list)
-- set group dropdown list
local groups_sel = state:get("groups_sel")
groups_sel:clearItems()
local group_tmp = {}
for group, count in pairs(group_list) do
if count > 1 then
table.insert(group_tmp, {group = group, label = group.." ("..count..")"})
end
end
table.sort(group_tmp, function(a,b)
return a.label < b.label
end)
groups_sel:addItem("all")
for _, group in ipairs(group_tmp) do
local idx = groups_sel:addItem(group.label)
state.param.group_list_labels[idx] = group.group
end
state.param.grouped_items = cache.get_list_grouped(craftable_itemlist)
update_group_selection(state)
end
local function crafting_callback(state)
local player = state.location.rootState.location.player
--Inventorys / left site
state:inventory(0.7, 6, 8, 4,"main")
state:inventory(0.7, 0.5, 3, 3,"craft")
state:inventory(4.1, 2.5, 1, 1,"craftpreview")
state:background(0.6, 0.1, 4.6, 3.8, "img1", "menu_bg.png")
state:inventory(1, 5, 8, 4,"main")
state:inventory(1.2, 0.2, 3, 3,"craft")
state:inventory(4.2, 2.2, 1, 1,"craftpreview")
state:background(1, 0, 4.5, 3.5, "img1", "menu_bg.png")
-- functional buttons right site
local refresh_button = state:button(17, 4.3, 2, 0.5, "refresh", "Refresh")
local refresh_button = state:button(16, 4.3, 2, 0.5, "refresh", "Refresh")
refresh_button:onClick(function(self, state, player)
update_craftable_list(state)
end)
-- functional buttons right site
local groups_button = state:button(9, 4.3, 4, 0.5, "groups_btn", "All items")
local groups_button = state:button(10, 4.3, 6, 0.5, "groups_btn", "All items")
groups_button:onClick(function(self, state, player)
if state:get("groups_sel"):getIsHidden() == true then
state:get("inf_area"):setIsHidden(true)
@ -120,41 +138,47 @@ local function crafting_callback(state)
end)
-- preview area / multifunctional
state:background(5.4, 0.1, 3.5, 3.8, "craft_img1", "menu_bg.png")
state:background(9.0, 0.1, 10, 3.8, "craft_img2", "minimap_overlay_square.png")
local inf_area = state:container(5.4, 0.1, "inf_area", true)
-- state:background(5.4, 0.1, 3.5, 3.8, "craft_img1", "menu_bg.png")
state:background(10.0, 0.1, 8, 3.8, "craft_img2", "minimap_overlay_square.png")
local inf_area = state:container(6.4, 0.1, "inf_area", true)
local inf_state = inf_area:getContainerState()
inf_state:label(10.5,0.5,"info1", "")
inf_state:label(10.5,1.0,"info2", "")
inf_state:label(10.5,1.5,"info3", "")
smart_inventory.smartfs_elements.craft_preview(inf_state, 5.5, 0.5, "craft_preview")
inf_state:label(5.7,3,"cr_type", "")
inf_state:label(11.5,0.5,"info1", "")
inf_state:label(11.5,1.0,"info2", "")
inf_state:label(11.5,1.5,"info3", "")
smart_inventory.smartfs_elements.craft_preview(state, 6, 0, "craft_preview")
inf_state:label(6.7,3,"cr_type", "")
inf_state:item_image(10.2,0.3, 1, 1, "craft_result",nil):setIsHidden(true)
inf_area:setIsHidden(true)
local group_sel = state:listbox(9.2, 0.1, 9.6, 3.5, "groups_sel",nil, true)
local group_sel = state:listbox(10.2, 0.15, 7.6, 3.6, "groups_sel",nil, true)
group_sel:onClick(function(self, state, index, player)
state.param.selected_group = state.param.group_list_labels[index]
print("selected", index, state.param.selected_group)
update_craftable_list(state)
if state.param.selected_group then
state:get("groups_btn"):setText(state.param.selected_group)
else
state:get("groups_btn"):setText("All items")
end
state.param.selected_filter = state.param.group_list[index]
update_group_selection(state)
state:get("groups_btn"):setText(self:getSelectedItem())
end)
group_sel:setIsHidden(true)
-- craftable items grid
local grid = smart_inventory.smartfs_elements.buttons_grid(state, 9, 5.5, 10 , 5, "buttons_grid")
local grid = smart_inventory.smartfs_elements.buttons_grid(state, 10, 5.5, 8 , 4, "buttons_grid")
grid:onClick(function(self, state, index, player)
local listentry = state.param.craftable_list[index]
if listentry then
print(dump(listentry.itemdef)) --DEBUG
else
print("unknown index:", index, dump(state.param.craftable_list))
end
on_item_select(state, listentry.itemdef, listentry.recipes[1]) --TODO: recipes paging
if state:get("inf_area"):getIsHidden() == true then
state:get("groups_btn"):submit()
end
end)
-- initial values
state.param.selected_filter = "all"
update_craftable_list(state)
group_sel:setSelected(1)
if group_sel:getSelectedItem() then
state:get("groups_btn"):setText(group_sel:getSelectedItem())
end
end
smart_inventory.register_page({

57
filter.lua Normal file
View File

@ -0,0 +1,57 @@
local filter = {}
filter.registered_filter = {}
--- API
function filter.get(name)
return filter.registered_filter[name]
end
function filter.register_filter(def)
assert(def.name, "filter needs a name")
assert(def.shortdesc, "filter needs a shortdesc that is used as for users readable name")
assert(def.filter_func, "filter function required")
assert(not filter.registered_filter[def.name], "filter already exists")
local self = def
function self:check_item_by_name(itemname)
return self.filter_func(minetest.registered_items[itemname], itemname)
end
function self:check_item_by_def(def)
if not def then
return false
else
return self.filter_func(def, def.name)
end
end
filter.registered_filter[self.name] = self
end
filter.register_filter({
name = "transluc",
shortdesc = "Translucent blocks",
filter_func = function(def, name)
if def.sunlight_propagates == true then
return true
else
return false
end
end
})
filter.register_filter({
name = "vessel",
shortdesc = "Vessel",
filter_func = function(def, name)
if def.allow_metadata_inventory_move or
def.allow_metadata_inventory_take or
def.on_metadata_inventory_put then
return true
else
return false
end
end
})
----------------
return filter

View File

@ -41,6 +41,8 @@ local inventory_form = smartfs.create("smart_inventory:main", function(state)
state:size(20,12)
state:label(1,0.2,"header","Smart Inventory")
state:image(0,0,1,1,"header_logo", "logo.png")
-- state:image_button(19,0,1,1,"exit", "Exit","???.png", true)
state:button(19,0,1,1,"exit", "Exit", true)
local button_x = 0.1
table.sort(smart_inventory.registered_pages, function(a,b)
if not a.sequence then
@ -99,13 +101,14 @@ function smart_inventory.register_page(def)
table.insert(smart_inventory.registered_pages, def)
end
-- build up caches
smart_inventory.filter = dofile(modpath.."/filter.lua")
smart_inventory.cache = dofile(modpath.."/cache.lua")
--smart_inventory.filter = dofile(modpath.."/filter.lua")
-- register pages
dofile(modpath.."/crafting.lua")
--if minetest.get_modpath("3d_armor") then
-- dofile(modpath.."/armor.lua")
--end
--[[
if minetest.get_modpath("3d_armor") then
dofile(modpath.."/armor.lua")
end
]]

View File

@ -21,11 +21,6 @@ function craft_preview:onCreate()
"craft:"..x..":"..y,nil):setIsHidden(true)
end
end
self._state:item_image(
self.data.pos.x+(4*self.data.zoom),
self.data.pos.y+self.data.zoom,
self.data.zoom, self.data.zoom,
"craft_result",nil):setIsHidden(true)
if self.data.recipe then
self:setCraft(self.data.recipe)
end
@ -39,6 +34,10 @@ function craft_preview:setCraft(craft)
if craft then
if not craft.width or craft.width == 0 then
item = craft.items[(y-1)*3+x]
elseif craft.width == 1 then
if x == 2 then
item = craft.items[y]
end
elseif x <= craft.width then
item = craft.items[(y-1)*craft.width+x]
end
@ -64,13 +63,6 @@ function craft_preview:setCraft(craft)
end
end
end
local res = self._state:get("craft_result")
if craft then
res:setImage(craft.output)
res:setIsHidden(false)
else
res:setIsHidden(true)
end
end
smartfs.element("craft_preview", craft_preview)

View File

@ -1337,7 +1337,6 @@ smartfs.element("container", {
-- element interface methods
build = function(self)
--the background string is "under" the container. Parts of this background can be overriden by elements (with background) from container
print("smartfs build:", self.name, self:getIsHidden())
if self:getIsHidden() == false then
if self.data.relative ~= true then
return self:getBackgroundString()..