--luacheck: ignore local Functions = require 'modules.fjei.functions' local math_ceil = math.ceil local math_abs = math.abs local table_remove = table.remove local table_insert = table.insert local main_window_width = 228 local recipe_window_width = 480 local recipe_window_amount_width = 38 local recipe_window_item_name_width = 128 local recipe_window_position = 'screen' local column_count = 5 local line_count = 5 local items_per_page = column_count * line_count local Public = {} local function save_window_location(player, frame) if not global.fjei.player_data then return end if not global.fjei.player_data[player.index] then return end global.fjei.player_data[player.index].gui_position = {frame.location.x, frame.location.y} end local function get_total_page_count(player) if not global.fjei.player_data[player.index].filtered_list then Functions.set_filtered_list(player) end local count = math_ceil(global.fjei.player_data[player.index].size_of_filtered_list / items_per_page) if count < 1 then count = 1 end return count end local function set_page_count_caption(player) if not global.fjei.player_data[player.index].filtered_list then Functions.set_filtered_list(player) end local active_page = global.fjei.player_data[player.index].active_page local element = player.gui.left.fjei_main_window.fjei_main_window_control_table.fjei_main_window_page_counter local caption = active_page .. '/' .. get_total_page_count(player) element.caption = caption --set textfield width local style = player.gui.left.fjei_main_window.fjei_main_window_control_table.fjei_main_window_search_textfield.style local width = 112 - string.len(caption) * 6 style.minimal_width = width style.maximal_width = width end local function get_formatted_amount(amount) if amount < 1000 then amount = amount .. ' x ' return amount end if amount >= 1000000 then amount = math.floor(amount * 0.00001) * 0.1 .. 'm ' return amount end if amount >= 1000 then amount = math.floor(amount * 0.01) * 0.1 .. 'k ' return amount end end local function get_localised_name(name) local item = game.item_prototypes[name] if item then return item.localised_name end local fluid = game.fluid_prototypes[name] if fluid then return fluid.localised_name end local recipe = game.recipe_prototypes[name] if recipe then return recipe.localised_name end return name end local function get_sprite_type(name) if game.item_prototypes[name] then return 'item' end if game.fluid_prototypes[name] then return 'fluid' end if game.entity_prototypes[name] then return 'entity' end if game.recipe_prototypes[name] then return 'recipe' end return false end local function get_active_recipe_name(player) local fjei_recipe_window = player.gui[recipe_window_position]['fjei_recipe_window'] if not fjei_recipe_window then return end if not fjei_recipe_window.recipe_container then return end local container = fjei_recipe_window.recipe_container.children[1] if not container then return end return container.children[1].name end local function add_choose_elem_button(element, name, is_recipe) local elem_type if is_recipe then elem_type = 'recipe' else elem_type = get_sprite_type(name) end if not elem_type then return end local elem_type = get_sprite_type(name) local choose_elem_button = element.add({type = 'choose-elem-button', name = name, elem_type = elem_type}) choose_elem_button.locked = true choose_elem_button.elem_value = name choose_elem_button.style.minimal_width = 36 choose_elem_button.style.minimal_height = 36 choose_elem_button.style.maximal_width = 36 choose_elem_button.style.maximal_height = 36 choose_elem_button.style.margin = -1 choose_elem_button.style.right_margin = 2 choose_elem_button.style.padding = 2 end local function add_sprite_icon(element, name, is_recipe, use_localised_name) local sprite_type if is_recipe then sprite_type = 'recipe' else sprite_type = get_sprite_type(name) end if not sprite_type then return end local sprite if use_localised_name then sprite = element.add({type = 'sprite', name = name, sprite = sprite_type .. '/' .. name, tooltip = get_localised_name(name)}) else sprite = element.add({type = 'sprite', name = name, sprite = sprite_type .. '/' .. name, tooltip = name}) end sprite.style.minimal_width = 32 sprite.style.minimal_height = 32 sprite.style.maximal_width = 32 sprite.style.maximal_height = 32 sprite.style.margin = 4 sprite.style.padding = 0 end local function display_item_list(player) if not player.gui.left.fjei_main_window then return end if not global.fjei.player_data[player.index].filtered_list then Functions.set_filtered_list(player) end local active_page = global.fjei.player_data[player.index].active_page local starting_index = 1 + (active_page - 1) * items_per_page local sorted_item_list = global.fjei.sorted_item_list local filtered_list = global.fjei.player_data[player.index].filtered_list local item_list_table = player.gui.left.fjei_main_window.fjei_main_window_item_list_table item_list_table.clear() for i = starting_index, starting_index + items_per_page - 1, 1 do if not filtered_list[i] then return end add_sprite_icon(item_list_table, sorted_item_list[filtered_list[i]], false) end end local function display_history(player) if not player.gui.left.fjei_main_window then return end local player_data = global.fjei.player_data[player.index] local history = player_data.history if not history then return end local history_table = player.gui.left.fjei_main_window.fjei_main_window_history_table history_table.clear() for i = player_data.size_of_history - column_count, player_data.size_of_history, 1 do local name = history[i] if name then add_sprite_icon(history_table, name, false) end end end function Public.refresh_main_window(player) if not player.gui.left.fjei_main_window then return end set_page_count_caption(player) display_item_list(player) display_history(player) end local function draw_main_window(player) if player.gui.left['fjei_main_window'] then player.gui.left['fjei_main_window'].destroy() end local frame = player.gui.left.add({type = 'frame', name = 'fjei_main_window', direction = 'vertical'}) frame.style.minimal_width = main_window_width frame.style.maximal_width = main_window_width frame.style.padding = 4 frame.style.margin = 2 local t = frame.add({type = 'table', name = 'fjei_main_window_control_table', column_count = 4}) local element = t.add({type = 'label', name = 'fjei_main_window_page_counter', caption = ' '}) element.style.font = 'heading-2' element.style.font_color = {222, 222, 222} element.style.right_margin = 4 local element = t.add({type = 'sprite-button', name = 'fjei_main_window_previous_page', caption = '←'}) element.style.font = 'heading-1' element.style.maximal_height = 32 element.style.maximal_width = 32 local element = t.add({type = 'sprite-button', name = 'fjei_main_window_next_page', caption = '→'}) element.style.font = 'heading-1' element.style.maximal_height = 32 element.style.maximal_width = 32 local text = global.fjei.player_data[player.index].active_filter if not text then text = '' end local textfield = t.add({type = 'textfield', name = 'fjei_main_window_search_textfield', text = text}) textfield.style.left_margin = 4 frame.add({type = 'line'}) local t = frame.add({type = 'table', name = 'fjei_main_window_item_list_table', column_count = column_count}) frame.add({type = 'line'}) local t = frame.add({type = 'table', name = 'fjei_main_window_history_table', column_count = column_count}) Public.refresh_main_window(player) end local function refresh_recipe_bar(player, selected_recipe) local old_recipe_name = get_active_recipe_name(player) if not old_recipe_name then return end if not player.gui[recipe_window_position]['fjei_recipe_window'] then return end if not player.gui[recipe_window_position]['fjei_recipe_window'].header_container then return end local fjei_recipe_window_select_table = player.gui[recipe_window_position]['fjei_recipe_window'].header_container.header_table.scroll_pane.fjei_recipe_window_select_table local container = fjei_recipe_window_select_table[old_recipe_name] container.clear() add_sprite_icon(container, old_recipe_name, true) local container = fjei_recipe_window_select_table[selected_recipe] container.clear() local element = container.add({type = 'frame', name = 'fjei_recipe_window_selected_recipe'}) element.style.margin = 0 element.style.padding = -4 add_sprite_icon(element, selected_recipe, true) end local function draw_recipe_window_header(player, container, item_name, recipes, mode, recipe_name) if mode == 1 then container.parent.caption = 'Product of recipe: ' else container.parent.caption = 'Ingredient in recipe: ' end local t = container.add({type = 'table', name = 'header_table', column_count = 3}) local element = t.add({type = 'label', name = item_name, caption = ' '}) element.visible = false local scroll_pane = t.add({type = 'scroll-pane', name = 'scroll_pane', horizontal_scroll_policy = 'always', vertical_scroll_policy = 'never'}) scroll_pane.style.minimal_width = recipe_window_width - 66 scroll_pane.style.maximal_width = recipe_window_width - 66 scroll_pane.style.minimal_height = 60 scroll_pane.style.maximal_height = 60 local tt = scroll_pane.add({type = 'table', name = 'fjei_recipe_window_select_table', column_count = 8192}) for key, name in pairs(recipes) do if not tt[name] then local ttt = tt.add({type = 'table', name = name, column_count = 1}) if recipe_name == name then local element = ttt.add({type = 'frame', name = 'fjei_recipe_window_selected_recipe'}) element.style.margin = 0 element.style.padding = -4 add_sprite_icon(element, name, true) else add_sprite_icon(ttt, name, true) end end end local element = t.add {type = 'sprite-button', sprite = 'utility/close_fat', name = 'fjei_close_recipe_window'} element.style.left_margin = 5 local element = container.add({type = 'line'}) element.style.right_margin = 6 end local function draw_recipe(player, container, recipe_name) local recipe = game.recipe_prototypes[recipe_name] local t = container.add({type = 'table', column_count = 2}) add_choose_elem_button(t, recipe.name, true) local tt = t.add({type = 'table', column_count = 1}) local ttt = tt.add({type = 'table', column_count = 2}) local element_1 = ttt.add({type = 'label', caption = recipe.localised_name}) element_1.style.font = 'heading-1' element_1.style.single_line = false local element_2 = ttt.add({type = 'label', caption = ' '}) element_2.style.font = 'heading-1' if not player.force.recipes[recipe_name].enabled then element_2.style.font_color = {200, 0, 0} element_2.caption = '*' local str = 'Further research is required to unlock this recipe.' element_2.tooltip = str element_1.tooltip = str end local element = tt.add({type = 'label', caption = '◷ ' .. math.round(recipe.energy, 2) .. ' seconds crafting time'}) element.style.font = 'default' element.style.font_color = {215, 215, 215} element.style.top_margin = -4 container.add({type = 'line'}) local element = container.add({type = 'label', caption = 'Products:'}) element.style.font = 'heading-2' local t = container.add({type = 'table', column_count = 2}) for _, product in pairs(recipe.products) do local tt = t.add({type = 'table', column_count = 3}) local amount = product.amount if not amount then amount = 1 end amount = amount * product.probability local element = tt.add({type = 'label', caption = get_formatted_amount(amount)}) element.style.minimal_width = recipe_window_amount_width element.style.maximal_width = recipe_window_amount_width element.style.single_line = false element.style.horizontal_align = 'right' add_choose_elem_button(tt, product.name) if product.temperature then local ttt = tt.add({type = 'table', column_count = 1}) local element = ttt.add({type = 'label', caption = get_localised_name(product.name)}) element.style.minimal_width = recipe_window_item_name_width element.style.maximal_width = recipe_window_item_name_width element.style.single_line = false element.style.font = 'default' element.style.bottom_margin = -3 local element = ttt.add({type = 'label', caption = product.temperature .. ' °C'}) element.style.top_margin = -3 else local element = tt.add({type = 'label', caption = get_localised_name(product.name)}) element.style.minimal_width = recipe_window_item_name_width element.style.maximal_width = recipe_window_item_name_width element.style.single_line = false element.style.font = 'default' end end local element = container.add({type = 'label', caption = 'Ingredients:'}) element.style.font = 'heading-2' local t = container.add({type = 'table', column_count = 2}) for key, ingredient in pairs(recipe.ingredients) do local tt = t.add({type = 'table', column_count = 3}) local element = tt.add({type = 'label', caption = get_formatted_amount(ingredient.amount)}) element.style.minimal_width = recipe_window_amount_width element.style.maximal_width = recipe_window_amount_width element.style.single_line = false element.style.horizontal_align = 'right' add_choose_elem_button(tt, ingredient.name) if ingredient.minimum_temperature and math_abs(ingredient.minimum_temperature) < 4294967296 then local ttt = tt.add({type = 'table', column_count = 1}) local element = ttt.add({type = 'label', caption = get_localised_name(ingredient.name)}) element.style.minimal_width = recipe_window_item_name_width element.style.maximal_width = recipe_window_item_name_width element.style.single_line = false element.style.font = 'default' element.style.bottom_margin = -3 local element = ttt.add({type = 'label', caption = ingredient.minimum_temperature .. ' °C'}) element.style.top_margin = -3 else local element = tt.add({type = 'label', caption = get_localised_name(ingredient.name)}) element.style.minimal_width = recipe_window_item_name_width element.style.maximal_width = recipe_window_item_name_width element.style.single_line = false element.style.font = 'default' end end local machines = Functions.get_crafting_machines_for_recipe(player.force.name, recipe) local element = container.add({type = 'line'}) element.style.top_margin = 2 element.style.bottom_margin = 2 if #machines == 0 then local t = container.add({type = 'table', column_count = 10}) local element = t.add({type = 'label', caption = 'Made by:'}) element.style.font = 'heading-2' local element = t.add({type = 'label', caption = 'Crafting method unknown.'}) element.style.font = 'heading-2' element.style.font_color = {150, 150, 150} return end local t = container.add({type = 'table', column_count = 10}) local element = t.add({type = 'label', caption = 'Made by:'}) element.style.right_margin = 2 element.style.font = 'heading-2' for key, machine in pairs(machines) do local prototype = game.entity_prototypes[machine] if prototype then add_choose_elem_button(t, machine) end end end local function create_recipe_window(item_name, player, button, selected_recipe) local mode if button == defines.mouse_button_type.left then mode = 1 else mode = 2 end if selected_recipe and player.gui[recipe_window_position]['fjei_recipe_window'] then refresh_recipe_bar(player, selected_recipe) local container = player.gui[recipe_window_position]['fjei_recipe_window'].recipe_container container.clear() draw_recipe(player, container, selected_recipe) return end if not global.fjei.item_list[item_name] then return end --shift researched recipes forward in the list local recipes = {} local size_of_recipes = 0 local researched_recipes = {} local size_of_researched_recipes = 0 local unknown_recipes = {} local size_of_unknown_recipes = 0 local force_recipes = player.force.recipes for k, recipe_name in pairs(global.fjei.item_list[item_name][mode]) do if k > 512 then break end if force_recipes[recipe_name] then if force_recipes[recipe_name].enabled then size_of_researched_recipes = size_of_researched_recipes + 1 researched_recipes[size_of_researched_recipes] = recipe_name else size_of_unknown_recipes = size_of_unknown_recipes + 1 unknown_recipes[size_of_unknown_recipes] = recipe_name end end end for k, recipe_name in pairs(researched_recipes) do size_of_recipes = size_of_recipes + 1 recipes[size_of_recipes] = recipe_name end for k, recipe_name in pairs(unknown_recipes) do size_of_recipes = size_of_recipes + 1 recipes[size_of_recipes] = recipe_name end if size_of_recipes == 0 then return end if not selected_recipe then for key, recipe_name in pairs(recipes) do if #Functions.get_crafting_machines_for_recipe(player.force.name, game.recipe_prototypes[recipe_name]) > 0 then selected_recipe = recipe_name break end if key > 16 then break end end end local recipe_name = recipes[1] if selected_recipe then for k, v in pairs(recipes) do if v == selected_recipe then recipe_name = recipes[k] end end end if player.gui[recipe_window_position]['fjei_recipe_window'] then save_window_location(player, player.gui[recipe_window_position]['fjei_recipe_window']) player.gui[recipe_window_position]['fjei_recipe_window'].destroy() end local frame = player.gui[recipe_window_position].add({type = 'frame', name = 'fjei_recipe_window', direction = 'vertical', caption = ' '}) frame.style.minimal_width = recipe_window_width frame.style.maximal_width = recipe_window_width frame.style.padding = 4 frame.style.left_padding = 6 frame.style.right_padding = 6 local player_data = global.fjei.player_data local position if player_data[player.index] and player_data[player.index].gui_position then position = {x = player_data[player.index].gui_position[1], y = player_data[player.index].gui_position[2]} else position = {x = 2, y = 370} end frame.location = position local container = frame.add({type = 'table', name = 'header_container', column_count = 1}) draw_recipe_window_header(player, container, item_name, recipes, mode, recipe_name) local container = frame.add({type = 'table', name = 'recipe_container', column_count = 1}) draw_recipe(player, container, recipe_name) return true end local function add_to_history(item_name, player) if not game.item_prototypes[item_name] and not game.fluid_prototypes[item_name] then return end local player_data = global.fjei.player_data[player.index] if not player_data.history then player_data.history = {item_name} player_data.size_of_history = 1 return end --shift existing item forward for k, v in pairs(player_data.history) do if v == item_name then table_remove(player_data.history, k) table_insert(player_data.history, item_name) return end end --remove first item, add new item if player_data.size_of_history >= column_count then table_remove(player_data.history, 1) player_data.history[column_count] = item_name return end player_data.size_of_history = player_data.size_of_history + 1 player_data.history[player_data.size_of_history] = item_name end local function show_cursor_stack_item(element, player, button) local cursor_stack = player.cursor_stack if not cursor_stack then return end if not cursor_stack.valid_for_read then return end if not global.fjei then Functions.build_tables() end if not global.fjei.player_data[player.index] then global.fjei.player_data[player.index] = {} end if not global.fjei.item_list[cursor_stack.name] then return end add_to_history(cursor_stack.name, player) draw_main_window(player) create_recipe_window(cursor_stack.name, player, button) return true end local function toggle_main_window(element, player, button) if show_cursor_stack_item(element, player, button) then return true end if player.gui.left.fjei_main_window then player.gui.left.fjei_main_window.destroy() if player.gui[recipe_window_position].fjei_recipe_window then player.gui[recipe_window_position].fjei_recipe_window.destroy() end else draw_main_window(player) end return true end local function main_window_next_page(element, player, button) local player_data = global.fjei.player_data[player.index] if button == defines.mouse_button_type.right then for _ = 1, 5, 1 do if player_data.active_page == get_total_page_count(player) then player_data.active_page = 1 else player_data.active_page = player_data.active_page + 1 end end else if player_data.active_page == get_total_page_count(player) then player_data.active_page = 1 else player_data.active_page = player_data.active_page + 1 end end Public.refresh_main_window(player) return true end local function main_window_previous_page(element, player, button) local player_data = global.fjei.player_data[player.index] if button == defines.mouse_button_type.right then for _ = 1, 5, 1 do if player_data.active_page == 1 then player_data.active_page = get_total_page_count(player) else player_data.active_page = player_data.active_page - 1 end end else if player_data.active_page == 1 then player_data.active_page = get_total_page_count(player) else player_data.active_page = player_data.active_page - 1 end end Public.refresh_main_window(player) return true end local function clear_search_textfield(element, player, button) if show_cursor_stack_item(element, player, button) then return true end if button ~= defines.mouse_button_type.right then return end global.fjei.player_data[player.index].active_filter = false element.text = '' Functions.set_filtered_list(player) Public.refresh_main_window(player) return true end local function close_recipe_window(element, player, button) local recipe_window = player.gui[recipe_window_position]['fjei_recipe_window'] save_window_location(player, recipe_window) if recipe_window then recipe_window.destroy() end return true end local gui_actions = { ['fjei_toggle_button'] = toggle_main_window, ['fjei_main_window_next_page'] = main_window_next_page, ['fjei_main_window_previous_page'] = main_window_previous_page, ['fjei_main_window_search_textfield'] = clear_search_textfield, ['fjei_close_recipe_window'] = close_recipe_window } function Public.gui_click_actions(element, player, button) if not gui_actions[element.name] then return end if not global.fjei then Functions.build_tables() end if not global.fjei.player_data[player.index] then global.fjei.player_data[player.index] = {} end gui_actions[element.name](element, player, button) return true end function Public.draw_top_toggle_button(player) if player.gui.top['fjei_toggle_button'] then return end local button = player.gui.top.add({type = 'sprite-button', name = 'fjei_toggle_button', caption = 'FJEI'}) button.style.font = 'heading-1' button.style.font_color = {222, 222, 222} button.style.minimal_height = 38 button.style.minimal_width = 50 button.style.padding = -2 end function Public.open_recipe(element, player, button) if not global.fjei then Functions.build_tables() end if not global.fjei.player_data[player.index] then global.fjei.player_data[player.index] = {} end local item_name = element.name local selected_recipe = false if element.parent.name == 'fjei_recipe_window_selected_recipe' then return end if element.parent then if element.parent.parent then if element.parent.parent.name == 'fjei_recipe_window_select_table' then selected_recipe = item_name end end end if element.parent.name == 'fjei_main_window_item_list_table' or element.parent.name == 'fjei_main_window_history_table' then local recipe_window = player.gui[recipe_window_position]['fjei_recipe_window'] if recipe_window then local active_item = recipe_window.header_container.header_table.children[1].name if active_item == element.name and global.fjei.player_data[player.index].last_button == button then save_window_location(player, recipe_window) recipe_window.destroy() return end end end add_to_history(item_name, player) if not create_recipe_window(item_name, player, button, selected_recipe) then return end display_history(player) global.fjei.player_data[player.index].last_button = button end return Public