339 lines
15 KiB
Lua
339 lines
15 KiB
Lua
-- ** LOCAL UTIL **
|
|
-- Serves the dual-purpose of determining the appropriate settings for the recipe picker filter and, if there
|
|
-- is only one that matches, to return a recipe name that can be added directly without the modal dialog
|
|
local function run_preliminary_checks(player, modal_data)
|
|
local force_recipes, force_technologies = player.force.recipes, player.force.technologies
|
|
local preferences = util.globals.preferences(player)
|
|
|
|
local relevant_recipes = {}
|
|
local user_disabled_recipe = false
|
|
local counts = {disabled = 0, hidden = 0, disabled_hidden = 0}
|
|
|
|
local map = RECIPE_MAPS[modal_data.production_type][modal_data.category_id][modal_data.product_id]
|
|
if map ~= nil then -- this being nil means that the item has no recipes
|
|
for recipe_id, _ in pairs(map) do
|
|
local recipe = global.prototypes.recipes[recipe_id]
|
|
local force_recipe = force_recipes[recipe.name]
|
|
|
|
if recipe.custom then -- Add custom recipes by default
|
|
table.insert(relevant_recipes, {proto=recipe, enabled=true})
|
|
-- These are always enabled and non-hidden, so no need to tally them
|
|
-- They can also not be disabled by user preference
|
|
|
|
elseif force_recipe ~= nil then -- only add recipes that exist on the current force
|
|
local user_disabled = (preferences.ignore_barreling_recipes and recipe.barreling)
|
|
or (preferences.ignore_recycling_recipes and recipe.recycling)
|
|
user_disabled_recipe = user_disabled_recipe or user_disabled
|
|
|
|
if not user_disabled then -- only add recipes that are not disabled by the user
|
|
local recipe_enabled, recipe_hidden = force_recipe.enabled, recipe.hidden
|
|
local recipe_should_show = recipe.enabled_from_the_start or recipe_enabled
|
|
|
|
-- If the recipe is not enabled, it has to be made sure that there is at
|
|
-- least one enabled technology that could potentially enable it
|
|
if not recipe_should_show and recipe.enabling_technologies ~= nil then
|
|
for _, technology_name in pairs(recipe.enabling_technologies) do
|
|
local force_tech = force_technologies[technology_name]
|
|
if force_tech and (force_tech.enabled or force_tech.visible_when_disabled) then
|
|
recipe_should_show = true
|
|
break
|
|
end
|
|
end
|
|
end
|
|
|
|
if recipe_should_show then
|
|
table.insert(relevant_recipes, {proto=recipe, enabled=recipe_enabled})
|
|
|
|
if not recipe_enabled and recipe_hidden then counts.disabled_hidden = counts.disabled_hidden + 1
|
|
elseif not recipe_enabled then counts.disabled = counts.disabled + 1
|
|
elseif recipe_hidden then counts.hidden = counts.hidden + 1 end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Set filters to try and show at least one recipe, should one exist, incorporating user preferences
|
|
local filters = {}
|
|
local user_prefs = preferences.recipe_filters
|
|
local relevant_recipes_count = #relevant_recipes
|
|
|
|
if relevant_recipes_count - counts.disabled - counts.hidden - counts.disabled_hidden > 0 then
|
|
filters.disabled = user_prefs.disabled or false
|
|
filters.hidden = user_prefs.hidden or false
|
|
elseif relevant_recipes_count - counts.hidden - counts.disabled_hidden > 0 then
|
|
filters.disabled = true
|
|
filters.hidden = user_prefs.hidden or false
|
|
else
|
|
filters.disabled = true
|
|
filters.hidden = true
|
|
end
|
|
|
|
-- Return result, format: return recipe, error-message, filters
|
|
if relevant_recipes_count == 0 then
|
|
local error = (user_disabled_recipe) and {"fp.error_no_enabled_recipe"} or {"fp.error_no_relevant_recipe"}
|
|
return nil, error, nil
|
|
|
|
elseif relevant_recipes_count == 1 then
|
|
local chosen_recipe = relevant_recipes[1]
|
|
return chosen_recipe.proto.id, nil, nil
|
|
|
|
else -- 2+ relevant recipes
|
|
return relevant_recipes, nil, filters
|
|
end
|
|
end
|
|
|
|
-- Tries to add the given recipe to the current floor, then exiting the modal dialog
|
|
local function attempt_adding_line(player, recipe_id)
|
|
local ui_state = util.globals.ui_state(player)
|
|
local modal_data = ui_state.modal_data
|
|
local recipe = Recipe.init_by_id(recipe_id, modal_data.production_type)
|
|
local line = Line.init(recipe)
|
|
|
|
-- If finding a machine fails, this line is invalid
|
|
if Line.change_machine_to_default(line, player) == false then
|
|
util.messages.raise(player, "error", {"fp.error_no_compatible_machine"}, 1)
|
|
|
|
else
|
|
local floor = Subfactory.get(ui_state.context.subfactory, "Floor", modal_data.floor_id)
|
|
-- If add_after_position is given, insert it below that one, add it to the end otherwise
|
|
if modal_data.add_after_position == nil then
|
|
Floor.add(floor, line)
|
|
else
|
|
Floor.insert_at(floor, (modal_data.add_after_position + 1), line)
|
|
end
|
|
|
|
local message = nil
|
|
if not (recipe.proto.custom or player.force.recipes[recipe.proto.name].enabled) then
|
|
message = {text={"fp.warning_recipe_disabled"}, category="warning"}
|
|
end
|
|
local defaults_message = Line.apply_mb_defaults(line, player)
|
|
if not message then message = defaults_message end -- a bit silly
|
|
|
|
solver.update(player, ui_state.context.subfactory)
|
|
util.raise.refresh(player, "subfactory", nil)
|
|
if message ~= nil then util.messages.raise(player, message.category, message.text, 1) end
|
|
end
|
|
end
|
|
|
|
|
|
local function create_filter_box(modal_data)
|
|
local bordered_frame = modal_data.modal_elements.content_frame.add{type="frame", style="fp_frame_bordered_stretch"}
|
|
|
|
local table_filters = bordered_frame.add{type="table", column_count=2}
|
|
table_filters.style.horizontal_spacing = 16
|
|
|
|
local label_filters = table_filters.add{type="label", caption={"fp.show"}}
|
|
label_filters.style.top_margin = 2
|
|
label_filters.style.left_margin = 4
|
|
|
|
local flow_filter_switches = table_filters.add{type="flow", direction="vertical"}
|
|
util.gui.switch.add_on_off(flow_filter_switches, "toggle_recipe_filter", {filter_name="disabled"},
|
|
modal_data.filters.disabled, {"fp.unresearched_recipes"}, nil, false)
|
|
util.gui.switch.add_on_off(flow_filter_switches, "toggle_recipe_filter", {filter_name="hidden"},
|
|
modal_data.filters.hidden, {"fp.hidden_recipes"}, nil, false)
|
|
end
|
|
|
|
local function create_recipe_group_box(modal_data, relevant_group, translations)
|
|
local modal_elements = modal_data.modal_elements
|
|
local bordered_frame = modal_elements.content_frame.add{type="frame", style="fp_frame_bordered_stretch"}
|
|
bordered_frame.style.padding = 8
|
|
|
|
local next_index = #modal_elements.groups + 1
|
|
modal_elements.groups[next_index] = {name=relevant_group.proto.name, frame=bordered_frame, recipe_buttons={}}
|
|
local recipe_buttons = modal_elements.groups[next_index].recipe_buttons
|
|
|
|
local flow_group = bordered_frame.add{type="flow", direction="horizontal"}
|
|
flow_group.style.vertical_align = "center"
|
|
|
|
local group_sprite = flow_group.add{type="sprite-button", sprite=("item-group/" .. relevant_group.proto.name),
|
|
tooltip=relevant_group.proto.localised_name, style="transparent_slot"}
|
|
group_sprite.style.size = 64
|
|
group_sprite.style.right_margin = 12
|
|
|
|
local frame_recipes = flow_group.add{type="frame", direction="horizontal", style="fp_frame_deep_slots_small"}
|
|
local table_recipes = frame_recipes.add{type="table", column_count=MAGIC_NUMBERS.recipes_per_row,
|
|
style="filter_slot_table"}
|
|
|
|
for _, recipe in pairs(relevant_group.recipes) do
|
|
local recipe_proto = recipe.proto
|
|
local recipe_name = recipe_proto.name
|
|
|
|
local style = "flib_slot_button_green_small"
|
|
if not recipe.enabled then style = "flib_slot_button_yellow_small"
|
|
elseif recipe_proto.hidden then style = "flib_slot_button_default_small" end
|
|
|
|
local button_tags = {mod="fp", on_gui_click="pick_recipe", recipe_proto_id=recipe_proto.id}
|
|
local button_recipe = nil
|
|
|
|
if recipe_proto.custom then -- can't use choose-elem-buttons for custom recipes
|
|
button_recipe = table_recipes.add{type="sprite-button", tags=button_tags, style=style,
|
|
sprite=recipe_proto.sprite, tooltip=recipe_proto.tooltip, mouse_button_filter={"left"}}
|
|
else
|
|
button_recipe = table_recipes.add{type="choose-elem-button", elem_type="recipe", tags=button_tags,
|
|
style=style, recipe=recipe_name, mouse_button_filter={"left"}}
|
|
button_recipe.locked = true
|
|
end
|
|
|
|
-- Figure out the translated name here so search doesn't have to repeat the work for every character
|
|
local translated_name = (translations) and translations["recipe"][recipe_name] or nil
|
|
translated_name = (translated_name) and translated_name:lower() or recipe_name
|
|
recipe_buttons[{name=recipe_name, translated_name=translated_name, hidden=recipe_proto.hidden}] = button_recipe
|
|
end
|
|
end
|
|
|
|
local function create_dialog_structure(modal_data, translations)
|
|
local modal_elements = modal_data.modal_elements
|
|
local content_frame = modal_elements.content_frame
|
|
content_frame.style.width = 380
|
|
|
|
create_filter_box(modal_data)
|
|
|
|
local label_warning = content_frame.add{type="label", caption={"fp.error_message", {"fp.no_recipe_found"}}}
|
|
label_warning.style.font = "heading-2"
|
|
label_warning.style.margin = {8, 0, 0, 8}
|
|
modal_elements.warning_label = label_warning
|
|
|
|
modal_elements.groups = {}
|
|
for _, group in ipairs(ORDERED_RECIPE_GROUPS) do
|
|
local relevant_group = modal_data.recipe_groups[group.name]
|
|
|
|
-- Only actually create this group if it contains any relevant recipes
|
|
if relevant_group ~= nil then create_recipe_group_box(modal_data, relevant_group, translations) end
|
|
end
|
|
end
|
|
|
|
local function apply_recipe_filter(player, search_term)
|
|
local modal_data = util.globals.modal_data(player)
|
|
local disabled, hidden = modal_data.filters.disabled, modal_data.filters.hidden
|
|
|
|
local any_recipe_visible, desired_scroll_pane_height = false, 64+24
|
|
for _, group in ipairs(modal_data.modal_elements.groups) do
|
|
local group_data = modal_data.recipe_groups[group.name]
|
|
local any_group_recipe_visible = false
|
|
|
|
for recipe_data, button in pairs(group.recipe_buttons) do
|
|
local recipe_name = recipe_data.name
|
|
local recipe_enabled = group_data.recipes[recipe_name].enabled
|
|
|
|
|
|
-- Can only get to this if translations are complete, as the textfield is disabled otherwise
|
|
local found = (search_term == recipe_name) or string.find(recipe_data.translated_name, search_term, 1, true)
|
|
local visible = found and (disabled or recipe_enabled) and (hidden or not recipe_data.hidden)
|
|
|
|
button.visible = visible
|
|
any_group_recipe_visible = any_group_recipe_visible or visible
|
|
end
|
|
|
|
group.frame.visible = any_group_recipe_visible
|
|
any_recipe_visible = any_recipe_visible or any_group_recipe_visible
|
|
|
|
local button_table_height = math.ceil(table_size(group.recipe_buttons) / MAGIC_NUMBERS.recipes_per_row) * 36
|
|
local additional_height = math.max(88, button_table_height + 24) + 4
|
|
desired_scroll_pane_height = desired_scroll_pane_height + additional_height
|
|
end
|
|
|
|
modal_data.modal_elements.warning_label.visible = not any_recipe_visible
|
|
|
|
local scroll_pane_height = math.min(desired_scroll_pane_height, modal_data.dialog_maximal_height)
|
|
modal_data.modal_elements.content_frame.style.height = scroll_pane_height
|
|
end
|
|
|
|
|
|
local function handle_filter_change(player, tags, event)
|
|
local boolean_state = util.gui.switch.convert_to_boolean(event.element.switch_state)
|
|
util.globals.modal_data(player).filters[tags.filter_name] = boolean_state
|
|
util.globals.preferences(player).recipe_filters[tags.filter_name] = boolean_state
|
|
|
|
apply_recipe_filter(player, "")
|
|
end
|
|
|
|
|
|
-- Checks whether the dialog needs to be created at all
|
|
local function recipe_early_abort_check(player, modal_data)
|
|
-- Result is either the single possible recipe_id, or a table of relevant recipes
|
|
local result, error, filters = run_preliminary_checks(player, modal_data)
|
|
|
|
if error ~= nil then
|
|
util.messages.raise(player, "error", error, 1)
|
|
return true -- signal that the dialog does not need to actually be opened
|
|
|
|
else
|
|
-- If 1 relevant recipe is found, try it straight away
|
|
if type(result) == "number" then -- the given number being the recipe_id
|
|
attempt_adding_line(player, result)
|
|
return true -- idem. above
|
|
|
|
else -- Otherwise, save the relevant data for the dialog opener
|
|
modal_data.result = result
|
|
modal_data.filters = filters
|
|
return false -- signal that the dialog should be opened
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Handles populating the recipe dialog
|
|
local function open_recipe_dialog(player, modal_data)
|
|
-- At this point, we're sure the dialog should be opened
|
|
local recipe_groups = {}
|
|
for _, recipe in pairs(modal_data.result) do
|
|
local group_name = recipe.proto.group.name
|
|
recipe_groups[group_name] = recipe_groups[group_name] or {proto=recipe.proto.group, recipes={}}
|
|
recipe_groups[group_name].recipes[recipe.proto.name] = recipe
|
|
end
|
|
modal_data.recipe_groups = recipe_groups
|
|
|
|
local translations = util.globals.player_table(player).translation_tables
|
|
create_dialog_structure(modal_data, translations)
|
|
apply_recipe_filter(player, "")
|
|
modal_data.modal_elements.search_textfield.focus()
|
|
|
|
-- Dispose of the temporary GUI-opening variables
|
|
modal_data.result = nil
|
|
end
|
|
|
|
|
|
-- ** EVENTS **
|
|
local listeners = {}
|
|
|
|
listeners.gui = {
|
|
on_gui_click = {
|
|
{
|
|
name = "pick_recipe",
|
|
timeout = 20,
|
|
handler = (function(player, tags, _)
|
|
attempt_adding_line(player, tags.recipe_proto_id)
|
|
util.raise.close_dialog(player, "cancel")
|
|
end)
|
|
}
|
|
},
|
|
on_gui_switch_state_changed = {
|
|
{
|
|
name = "toggle_recipe_filter",
|
|
handler = handle_filter_change
|
|
}
|
|
}
|
|
}
|
|
|
|
listeners.dialog = {
|
|
dialog = "recipe",
|
|
metadata = (function(modal_data)
|
|
local product_proto = global.prototypes.items[modal_data.category_id].members[modal_data.product_id]
|
|
return {
|
|
caption = {"", {"fp.add"}, " ", {"fp.pl_recipe", 1}},
|
|
subheader_text = {"fp.recipe_instruction", {"fp." .. modal_data.production_type},
|
|
product_proto.localised_name},
|
|
search_handler_name = "apply_recipe_filter",
|
|
create_content_frame = true
|
|
}
|
|
end),
|
|
early_abort_check = recipe_early_abort_check,
|
|
open = open_recipe_dialog
|
|
}
|
|
|
|
listeners.global = {
|
|
apply_recipe_filter = apply_recipe_filter
|
|
}
|
|
|
|
return { listeners }
|