Alexander Weber 5b2c1b8071 crafting: add crafting helper
New button appears to auto-fill crafting field
See Issue #3
2017-11-16 23:50:48 +01:00

566 lines
20 KiB
Lua

local cache = smart_inventory.cache
local crecipes = smart_inventory.crecipes
local doc_addon = smart_inventory.doc_addon
local ui_tools = smart_inventory.ui_tools
-----------------------------------------------------
-- Update recipe preview item informations about the selected item
-----------------------------------------------------
local function update_crafting_preview(state)
local player = state.location.rootState.location.player
local listentry = state.param.crafting_recipes_preview_listentry
local selected = state.param.crafting_recipes_preview_selected
local itemdef = listentry.itemdef
local inf_state = state:get("inf_area"):getContainerState()
local cr_type_img = state:get("cr_type_img")
local craft_result = inf_state:get("craft_result")
local group_list = inf_state:get("item_groups")
-- get recipe to display, check paging buttons needed
local all_recipes
local valid_recipes = {}
local recipe
if listentry.recipes then -- preselected recipes (ie. craftable)
all_recipes = listentry.recipes
elseif cache.citems[listentry.item] then -- check all available recipes (ie. search)
all_recipes = cache.citems[listentry.item].in_output_recipe or {}
else -- no recipes
all_recipes = {}
end
for _, recipe in ipairs(all_recipes) do
if crecipes.crecipes[recipe]:is_revealed(player) then
table.insert(valid_recipes, recipe)
end
end
if valid_recipes[1] then
if not valid_recipes[selected] then
selected = 1
end
state.param.crafting_recipes_preview_selected = selected
if selected > 1 and valid_recipes[selected-1] then
state:get("preview_prev"):setVisible(true)
else
state:get("preview_prev"):setVisible(false)
end
if valid_recipes[selected+1] then
state:get("preview_next"):setVisible(true)
else
state:get("preview_next"):setVisible(false)
end
if valid_recipes[selected] then
recipe = valid_recipes[selected]
local crecipe = crecipes.crecipes[recipe]
if crecipe then
recipe = crecipe:get_with_placeholder(player, state.param.crafting_items_in_inventory)
end
end
else
state:get("preview_prev"):setVisible(false)
state:get("preview_next"):setVisible(false)
end
-- display the recipe result or selected item
if recipe then
if recipe.type == "normal" then
state:get("cr_type"):setText("")
cr_type_img:setVisible(false)
elseif recipe.type == "cooking" then
state:get("cr_type"):setText(recipe.type)
state:get("cr_type"):setText("")
cr_type_img:setVisible(true)
cr_type_img:setImage("default_furnace_front.png")
state:get("ac1"):setVisible(false)
else
state:get("cr_type"):setText(recipe.type)
cr_type_img:setVisible(false)
state:get("ac1"):setVisible(false)
end
craft_result:setImage(recipe.output)
craft_result:setVisible()
state:get("craft_preview"):setCraft(recipe)
state:get("ac1"):setVisible(true)
else
state:get("cr_type"):setText("")
state:get("craft_preview"):setCraft(nil)
cr_type_img:setVisible(false)
state:get("ac1"):setVisible(false)
if itemdef then
craft_result:setVisible(true)
craft_result:setImage(itemdef.name)
else
craft_result:setVisible(false)
end
end
-- display docs icon if revealed item
if smart_inventory.doc_items_mod then
inf_state:get("doc_btn"):setVisible(false)
local outitem = craft_result:getImage()
if outitem then
for z in outitem:gmatch("[^%s]+") do
if doc_addon.is_revealed_item(z, player) then
inf_state:get("doc_btn"):setVisible(true)
end
break
end
end
end
-- update info area
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
group_list:clearItems()
cache.add_item(listentry.itemdef) -- Note: this addition does not affect the already prepared root lists
if cache.citems[itemdef.name] then
for _, groupdef in ipairs(ui_tools.get_tight_groups(cache.citems[itemdef.name].cgroups)) do
group_list:addItem(groupdef.group_desc)
end
end
elseif listentry.item then
inf_state:get("info1"):setText("")
inf_state:get("info2"):setText("("..listentry.item..")")
inf_state:get("info3"):setText("")
else
inf_state:get("info1"):setText("")
inf_state:get("info2"):setText("")
inf_state:get("info3"):setText("")
group_list:clearItems()
end
end
-----------------------------------------------------
-- Update the group selection table
-----------------------------------------------------
local function update_group_selection(state, rebuild)
local grouped = state.param.crafting_grouped_items
local groups_sel = state:get("groups_sel")
local grid = state:get("buttons_grid")
local label = state:get("inf_area"):getContainerState():get("groups_label")
if rebuild then
state.param.crafting_group_list = ui_tools.update_group_selection(grouped, groups_sel, state.param.crafting_group_list)
end
local sel_id = groups_sel:getSelected()
if state.param.crafting_group_list[sel_id] then
state.param.crafting_craftable_list = grouped[state.param.crafting_group_list[sel_id]].items
table.sort(state.param.crafting_craftable_list, function(a,b)
return a.item < b.item
end)
grid:setList(state.param.crafting_craftable_list)
label:setText(groups_sel:getSelectedItem())
else
label:setText("Empty List")
grid:setList({})
end
end
-----------------------------------------------------
-- Update the items list
-----------------------------------------------------
local function update_from_recipelist(state, recipelist, preview_item, replace_not_in_list)
local old_preview_entry, old_preview_item, new_preview_entry, new_preview_item
if state.param.crafting_recipes_preview_listentry then
old_preview_item = state.param.crafting_recipes_preview_listentry.item
end
if preview_item == "" then
new_preview_item = nil
else
new_preview_item = preview_item
end
local duplicate_index_tmp = {}
local craftable_itemlist = {}
for recipe, _ in pairs(recipelist) do
local def = crecipes.crecipes[recipe].out_item
if duplicate_index_tmp[def.name] then
table.insert(duplicate_index_tmp[def.name].recipes, recipe)
else
local entry = {
itemdef = def,
recipes = {},
-- buttons_grid related
item = def.name,
is_button = true
}
duplicate_index_tmp[def.name] = entry
table.insert(entry.recipes, recipe)
table.insert(craftable_itemlist, entry)
if new_preview_item and def.name == new_preview_item then
new_preview_entry = entry
end
if old_preview_item and def.name == old_preview_item then
old_preview_entry = entry
end
end
end
-- update crafting preview if the old is not in list anymore
if new_preview_item then
if not replace_not_in_list or not old_preview_entry then
if not new_preview_entry then
new_preview_entry = {
itemdef = minetest.registered_items[new_preview_item],
item = new_preview_item
}
end
state.param.crafting_recipes_preview_selected = 1
state.param.crafting_recipes_preview_listentry = new_preview_entry
update_crafting_preview(state)
if state:get("info_tog"):getId() == 1 then
state:get("info_tog"):submit()
end
end
elseif replace_not_in_list and not old_preview_entry then
state.param.crafting_recipes_preview_selected = 1
state.param.crafting_recipes_preview_listentry = {}
update_crafting_preview(state)
end
-- update the groups selection
state.param.crafting_grouped_items = ui_tools.get_list_grouped(craftable_itemlist)
update_group_selection(state, true)
end
-----------------------------------------------------
-- Lookup for item lookup_item
-----------------------------------------------------
local function do_lookup_item(state, playername, lookup_item)
state.param.crafting_items_in_inventory = state.param.invobj:get_items()
state.param.crafting_items_in_inventory[lookup_item] = true -- prefer in recipe preview
-- get all craftable recipes with lookup-item as ingredient. Add recipes of lookup item to the list
local recipes = crecipes.get_revealed_recipes_with_items(playername, {[lookup_item] = true })
update_from_recipelist(state, recipes, lookup_item)
-- reset group selection and search field on proposal mode change
if state.param.survival_proposal_mode ~= "lookup" then
state.param.survival_proposal_mode = "lookup"
state:get("groups_sel"):setSelected(1)
state:get("search"):setText("")
end
end
-----------------------------------------------------
-- Lookup inventory
-----------------------------------------------------
local function create_lookup_inv(state, name)
local player = minetest.get_player_by_name(name)
local invname = name.."_crafting_inv"
local plistname = "crafting_inv_lookup"
local listname = "lookup"
local pinv = player:get_inventory()
local inv = minetest.get_inventory({type="detached", name=invname})
if not inv then
inv = minetest.create_detached_inventory(invname, {
allow_move = function(inv, from_list, from_index, to_list, to_index, count, player)
return 0
end,
allow_put = function(inv, listname, index, stack, player)
if pinv:is_empty(plistname) then
return 99
else
return 0
end
end,
allow_take = function(inv, listname, index, stack, player)
return 99
end,
on_move = function(inv, from_list, from_index, to_list, to_index, count, player)
end,
on_put = function(inv, listname, index, stack, player)
pinv:set_stack(plistname, index, stack)
local name = player:get_player_name()
local state = smart_inventory.get_page_state("crafting", name)
do_lookup_item(state, name, stack:get_name())
-- we are outsite of usual smartfs processing. So trigger the formspec update byself
state.location.rootState:show()
-- put back
minetest.after(1, function(stack)
local applied = pinv:add_item("main", stack)
pinv:set_stack(plistname, 1, applied)
inv:set_stack(listname, 1, applied)
end, stack)
end,
on_take = function(inv, listname, index, stack, player)
pinv:set_stack(plistname, index, nil)
end,
}, name)
end
-- copy the item from player:listname inventory to the detached
inv:set_size(listname, 1)
pinv:set_size(plistname, 1)
local stack = pinv:get_stack(plistname, 1)
inv:set_stack(listname, 1, stack)
end
-----------------------------------------------------
-- Page layout definition
-----------------------------------------------------
local function crafting_callback(state)
local player = state.location.rootState.location.player
-- set inventory style
state:element("code", {name = "inventory_bg_code", code = "listcolors[#00000069;#5A5A5A;#141318;#30434C;#FFF]"})
--Inventorys / left site
state:inventory(1, 5, 8, 4,"main")
state:inventory(1.2, 0.2, 3, 3,"craft")
state:inventory(4.3, 1.2, 1, 1,"craftpreview")
state:background(1, 0, 4.5, 3.5, "img1", "menu_bg.png")
-- crafting helper buttons
-- local btn_ac1 = state:image_button(4.3, 0.2, 1, 1, "ac1", "", "???.png")
local btn_ac1 = state:button(4.3, 0.2, 1, 1, "ac1", "Craft")
btn_ac1:onClick(function(self, state, player)
-- ui_tools.image_button_feedback(player, "crafting", "ac1")
local grid = state:get("craft_preview"):getCraft()
state.param.invobj:craft_item(grid)
end)
btn_ac1:setVisible(false)
-- swap slots buttons
state:image_button(0, 6, 1, 1, "swap1", "", "smart_inventory_swapline_button.png"):onClick(function(self, state, player)
ui_tools.image_button_feedback(player, "crafting", "swap1")
state.param.invobj:swap_row_to_top(2)
end)
state:image_button(0, 7, 1, 1, "swap2", "", "smart_inventory_swapline_button.png"):onClick(function(self, state, player)
ui_tools.image_button_feedback(player, "crafting", "swap2")
state.param.invobj:swap_row_to_top(3)
end)
state:image_button(0, 8, 1, 1, "swap3", "", "smart_inventory_swapline_button.png"):onClick(function(self, state, player)
ui_tools.image_button_feedback(player, "crafting", "swap3")
state.param.invobj:swap_row_to_top(4)
end)
ui_tools.create_trash_inv(state, player)
state:image(8,9,1,1,"trash_icon","smart_inventory_trash.png")
state:inventory(8, 9, 1, 1, "trash"):useDetached(player.."_trash_inv")
local btn_compress = state:image_button(1, 3.8, 1, 1, "compress", "","smart_inventory_compress_button.png")
btn_compress:setTooltip("Merge stacks with same items to get free place")
btn_compress:onClick(function(self, state, player)
ui_tools.image_button_feedback(player, "crafting", "compress")
state.param.invobj:compress()
end)
local btn_sweep = state:image_button(2, 3.8, 1, 1, "clear", "", "smart_inventory_sweep_button.png")
btn_sweep:setTooltip("Move all items from crafting grid back to inventory")
btn_sweep:onClick(function(self, state, player)
ui_tools.image_button_feedback(player, "crafting", "clear")
state.param.invobj:sweep_crafting_inventory()
end)
-- recipe preview area
smart_inventory.smartfs_elements.craft_preview(state, 6, 0, "craft_preview"):onButtonClicked(function(self, item, player)
do_lookup_item(state, player, item)
end)
state:image(7,2.8,1,1,"cr_type_img",""):setVisible(false)
state:label(7,3,"cr_type", "")
local pr_prev_btn = state:button(6, 3, 1, 0.5, "preview_prev", "<<")
pr_prev_btn:onClick(function(self, state, player)
state.param.crafting_recipes_preview_selected = state.param.crafting_recipes_preview_selected -1
update_crafting_preview(state)
end)
pr_prev_btn:setVisible(false)
local pr_next_btn = state:button(8, 3, 1, 0.5, "preview_next", ">>")
pr_next_btn:onClick(function(self, state, player)
state.param.crafting_recipes_preview_selected = state.param.crafting_recipes_preview_selected +1
update_crafting_preview(state)
end)
pr_next_btn:setVisible(false)
-- (dynamic-1) group selection
local group_sel = state:listbox(10.2, 0.15, 7.6, 3.6, "groups_sel",nil, true)
group_sel:onClick(function(self, state, player)
local selected = self:getSelectedItem()
if selected then
update_group_selection(state, false)
end
end)
-- (dynamic-2) item preview area
state:background(10.0, 0.1, 8, 3.8, "craft_img2", "minimap_overlay_square.png")
local inf_area = state:view(6.4, 0.1, "inf_area")
local inf_state = inf_area:getContainerState()
inf_state:label(11.5,0.5,"info1", "")
inf_state:label(11.5,1.0,"info2", "")
inf_state:label(11.5,1.5,"info3", "")
inf_state:item_image(10.2,0.3, 1, 1, "craft_result",nil):setVisible(false)
if smart_inventory.doc_items_mod then
local doc_btn = inf_state:item_image_button(10.2,2.3, 1, 1, "doc_btn","", "doc_identifier:identifier_solid")
doc_btn:setVisible(false)
doc_btn:onClick(function(self, state, player)
local outitem = state:get("craft_result"):getImage()
if outitem then
for z in outitem:gmatch("[^%s]+") do
if minetest.registered_items[z] then
doc_addon.show(z, player)
end
break
end
end
end)
end
inf_state:label(10.3, 3.25, "groups_label", "All")
inf_state:listbox(12, 2, 5.7, 1.3, "item_groups",nil, true)
inf_area:setVisible(false)
-- Lookup
create_lookup_inv(state, player)
state:image(10, 4, 1, 1,"lookup_icon", "smart_inventory_lookup_field.png")
local inv_lookup = state:inventory(10, 4.0, 1, 1,"lookup"):useDetached(player.."_crafting_inv")
-- Get craftable by items in inventory
local btn_craftable = state:image_button(11, 4, 1, 1, "craftable", "", "smart_inventory_craftable_button.png")
btn_craftable:setTooltip("Show items crafteable by items in inventory")
btn_craftable:onClick(function(self, state, player)
ui_tools.image_button_feedback(player, "crafting", "craftable")
-- reset group selection and search field on proposal mode change
if state.param.survival_proposal_mode ~= "craftable" then
state.param.survival_proposal_mode = "craftable"
state:get("groups_sel"):setSelected(1)
state:get("search"):setText("")
end
state.param.crafting_items_in_inventory = state.param.invobj:get_items()
local craftable = crecipes.get_recipes_craftable(player, state.param.crafting_items_in_inventory)
update_from_recipelist(state, craftable)
if state:get("info_tog"):getId() == 2 then
state:get("info_tog"):submit()
end
end)
-- Reveal tipps button
if smart_inventory.doc_items_mod then
local reveal_button = state:image_button(12, 4, 1, 1, "reveal_tipp", "", "smart_inventory_reveal_tips_button.png")
reveal_button:setTooltip("Show proposal what should be crafted to reveal more items")
reveal_button:onClick(function(self, state, player)
local all_revealed = ui_tools.filter_by_revealed(ui_tools.root_list_all, player)
local top_revealed = ui_tools.filter_by_top_reveal(all_revealed, player)
state.param.crafting_recipes_preview_selected = 1
state.param.crafting_recipes_preview_listentry = top_revealed[1] or {}
update_crafting_preview(state)
state.param.crafting_grouped_items = ui_tools.get_list_grouped(top_revealed)
-- reset group selection if proposal mode is changed
if state.param.survival_proposal_mode ~= "tipp" then
state.param.survival_proposal_mode = "tipp"
state:get("groups_sel"):setSelected(1)
end
update_group_selection(state, true)
end)
end
-- search
local searchfield = state:field(13.3, 4.5, 3, 0.5, "search")
searchfield:setCloseOnEnter(false)
searchfield:onKeyEnter(function(self, state, player)
local search_string = self:getText()
if string.len(search_string) < 3 then
return
end
local filtered_list = ui_tools.filter_by_searchstring(ui_tools.root_list_all, search_string)
filtered_list = ui_tools.filter_by_revealed(filtered_list, player)
state.param.crafting_grouped_items = ui_tools.get_list_grouped(filtered_list)
-- reset group selection if proposal mode is changed
if state.param.survival_proposal_mode ~= "search" then
state.param.survival_proposal_mode = "search"
state:get("groups_sel"):setSelected(1)
end
update_group_selection(state, true)
end)
-- groups toggle
local info_tog = state:toggle(16,4.2,2,0.5, "info_tog", {"Info", "Groups"})
info_tog:onToggle(function(self, state, player)
if self:getId() == 2 then
state:get("inf_area"):setVisible(true)
state:get("groups_sel"):setVisible(false)
else
state:get("inf_area"):setVisible(false)
state:get("groups_sel"):setVisible(true)
end
end)
-- craftable items grid
state:background(10, 5, 8, 4, "buttons_grid_Bg", "minimap_overlay_square.png")
local grid = smart_inventory.smartfs_elements.buttons_grid(state, 10.25, 5.15, 8 , 4, "buttons_grid", 0.75,0.75)
grid:onClick(function(self, state, index, player)
local listentry = state.param.crafting_craftable_list[index]
state.param.crafting_recipes_preview_selected = 1
state.param.crafting_recipes_preview_listentry = listentry
update_crafting_preview(state)
if state:get("info_tog"):getId() == 1 then
state:get("info_tog"):submit()
end
end)
-- initial values
btn_craftable:submit("not used fieldname", state.location.rootState.location.player)
end
-----------------------------------------------------
-- Register page in smart_inventory
-----------------------------------------------------
smart_inventory.register_page({
name = "crafting",
tooltip = "Craft new items",
icon = "smart_inventory_crafting_inventory_button.png",
smartfs_callback = crafting_callback,
sequence = 10
})
-----------------------------------------------------
-- Use lookup for predict item
-----------------------------------------------------
minetest.register_craft_predict(function(stack, player, old_craft_grid, craft_inv)
local name = player:get_player_name()
local state = smart_inventory.get_page_state("crafting", name)
if not state then
return
end
-- get all grid items for reference
local reference_items = {}
local items_hash = ""
for _, stack in ipairs(old_craft_grid) do
local name = stack:get_name()
if name and name ~= "" then
reference_items[name] = true
items_hash=items_hash.."|"..name
else
items_hash=items_hash.."|empty"
end
end
if items_hash ~= state.param.survival_grid_items_hash then
state.param.survival_grid_items_hash = items_hash
if not next(reference_items) then
state:get("craftable"):submit("not used fieldname", name)
else
-- update the grid with matched recipe items
state.param.survival_proposal_mode = "grid"
state:get("search"):setText("") -- reset search field because of mode change
local recipes = crecipes.get_recipes_started_craft(name, old_craft_grid, reference_items)
update_from_recipelist(state, recipes, stack:get_name(), true) -- replace_not_in_list=true
end
state.location.rootState:show()
end
end)