Aleksei-bird 7c9c708c92 Первый фикс
Пачки некоторых позиций увеличены
2024-03-01 20:54:33 +03:00

450 lines
19 KiB
Lua

-- ** LOCAL UTIL **
-- Adds a box with title and optional scope switch for the given type of utility
local function add_utility_box(player, modal_elements, type, show_tooltip, show_switch)
local bordered_frame = modal_elements.content_frame.add{type="frame", direction="vertical",
style="fp_frame_bordered_stretch"}
modal_elements[type .. "_box"] = bordered_frame
local flow_title_bar = bordered_frame.add{type="flow", direction="horizontal"}
flow_title_bar.style.vertical_align = "center"
flow_title_bar.style.margin = {2, 0, 4, 0}
-- Title
local caption = (show_tooltip) and {"fp.info_label", {"fp.utility_title_".. type}} or {"fp.utility_title_".. type}
local tooltip = (show_tooltip) and {"fp.utility_title_" .. type .. "_tt"}
local label_title = flow_title_bar.add{type="label", caption=caption, tooltip=tooltip, style="caption_label"}
label_title.style.top_margin = -2
-- Empty flow for custom controls
flow_title_bar.add{type="empty-widget", style="flib_horizontal_pusher"}
local flow_custom = flow_title_bar.add{type="flow"}
flow_custom.style.right_margin = 12
-- Scope switch
local scope_switch = nil
if show_switch then
local utility_scope = util.globals.preferences(player).utility_scopes[type]
local switch_state = (utility_scope == "Subfactory") and "left" or "right"
scope_switch = flow_title_bar.add{type="switch", switch_state=switch_state,
tags={mod="fp", on_gui_switch_state_changed="utility_change_scope", utility_type=type},
left_label_caption={"fp.pu_subfactory", 1}, right_label_caption={"fp.pu_floor", 1}}
end
return bordered_frame, flow_custom, scope_switch
end
local utility_structures = {}
local function update_request_button(player, modal_data, subfactory)
local modal_elements = modal_data.modal_elements
local button_enabled, switch_enabled = true, true
local caption = "" ---@type LocalisedString
local tooltip = "" ---@type LocalisedString
local font_color = {}
if subfactory.item_request_proxy ~= nil then
caption = {"fp.cancel_request"}
font_color = {0.8, 0, 0}
switch_enabled = false
else
local scope = util.globals.preferences(player).utility_scopes.components
local scope_string = {"fp.pl_" .. scope:lower(), 1}
caption, tooltip = {"fp.request_items"}, {"fp.request_items_tt", scope_string}
if not player.force.character_logistic_requests then
tooltip = {"fp.warning_with_icon", {"fp.request_logistics_not_researched"}}
button_enabled = false
elseif not next(modal_data.missing_items) then
tooltip = {"fp.warning_with_icon", {"fp.utility_no_items_necessary", scope_string}}
button_enabled = false
elseif player.character == nil then -- happens when the editor is active for example
tooltip = {"fp.warning_with_icon", {"fp.request_no_character"}}
button_enabled = false
end
end
modal_elements.request_button.caption = caption
modal_elements.request_button.tooltip = tooltip
modal_elements.request_button.style.font_color = font_color
modal_elements.request_button.enabled = button_enabled
modal_elements.scope_switch.enabled = switch_enabled
end
function utility_structures.components(player, modal_data)
local scope = util.globals.preferences(player).utility_scopes.components
local lower_scope = scope:lower()
local context = util.globals.context(player)
local modal_elements = modal_data.modal_elements
if modal_elements.components_box == nil then
local components_box, custom_flow, scope_switch = add_utility_box(player, modal_data.modal_elements,
"components", true, true)
modal_elements.components_box = components_box
modal_elements.scope_switch = scope_switch
local button_combinator = custom_flow.add{type="sprite-button", sprite="item/constant-combinator",
tooltip={"fp.ingredients_to_combinator_tt"}, tags={mod="fp", on_gui_click="utility_item_combinator"},
style="fp_sprite-button_rounded_mini", mouse_button_filter={"left"}}
button_combinator.style.size = 29
button_combinator.style.padding = 0
modal_elements.combinator_button = button_combinator
local button_request = custom_flow.add{type="button", tags={mod="fp", on_gui_click="utility_request_items"},
style="rounded_button", mouse_button_filter={"left"}}
button_request.style.minimal_width = 0
modal_elements.request_button = button_request
local table_components = components_box.add{type="table", column_count=2}
table_components.style.horizontal_spacing = 24
table_components.style.vertical_spacing = 8
local function add_component_row(type)
table_components.add{type="label", caption={"fp.pu_" .. type, 2}, style="heading_3_label"}
local flow = table_components.add{type="flow", direction="horizontal"}
modal_elements["components_" .. type .. "_flow"] = flow
end
add_component_row("machine")
add_component_row("module")
end
local function refresh_component_flow(type)
local component_row = modal_elements["components_" .. type .. "_flow"]
component_row.clear()
local inventory_contents = modal_data.inventory_contents
local component_data = _G[scope].get_component_data(context[lower_scope], nil)
local frame_components = component_row.add{type="frame", direction="horizontal", style="slot_button_deep_frame"}
local table_components = frame_components.add{type="table", column_count=10, style="filter_slot_table"}
for _, component in pairs(component_data[type .. "s"]) do
if component.amount > 0 then
local proto, required_amount = component.proto, component.amount
local amount_in_inventory = inventory_contents[proto.name] or 0
local missing_amount = required_amount - amount_in_inventory
if missing_amount > 0 then modal_data.missing_items[proto.name] = missing_amount end
local button_style = nil
if amount_in_inventory == 0 then button_style = "flib_slot_button_red"
elseif missing_amount > 0 then button_style = "flib_slot_button_yellow"
else button_style = "flib_slot_button_green" end
local tooltip = {"fp.components_needed_tt", {"fp.tt_title", proto.localised_name},
amount_in_inventory, required_amount}
local category_id = (proto.data_type == "items") and proto.category_id
or PROTOTYPE_MAPS.items["item"].id -- modules/beacons are always an 'item'
local proto_id = (proto.data_Type == "items") and proto.id or
PROTOTYPE_MAPS.items["item"].members[proto.name].id
table_components.add{type="sprite-button", sprite=proto.sprite, number=required_amount, tooltip=tooltip,
tags={mod="fp", on_gui_click="utility_craft_items", category_id=category_id, item_id=proto_id,
missing_amount=missing_amount}, style=button_style, mouse_button_filter={"left-and-right"}}
end
end
if not next(table_components.children_names) then
frame_components.visible = false
local label = component_row.add{type="label", caption={"fp.no_components_needed", {"fp.pl_" .. type, 2}}}
label.style.margin = {10, 0}
end
end
modal_data.missing_items = {} -- a flat structure works because there is no overlap between machines and modules
refresh_component_flow("machine")
refresh_component_flow("module")
local subfactory = util.globals.context(player).subfactory
Subfactory.validate_item_request_proxy(subfactory)
local any_missing_items = (next(modal_data.missing_items) ~= nil)
modal_elements.combinator_button.enabled = any_missing_items
modal_elements.combinator_button.tooltip = (any_missing_items) and {"fp.utility_combinator_tt"}
or {"fp.warning_with_icon", {"fp.utility_no_items_necessary", {"fp.pl_" .. lower_scope, 1}}}
update_request_button(player, modal_data, subfactory)
end
function utility_structures.blueprints(player, modal_data)
local modal_elements = modal_data.modal_elements
if modal_elements.blueprints_box == nil then
local blueprints_box = add_utility_box(player, modal_data.modal_elements, "blueprints", true, false)
modal_elements["blueprints_box"] = blueprints_box
local frame_blueprints = blueprints_box.add{type="frame", direction="horizontal", style="slot_button_deep_frame"}
local table_blueprints = frame_blueprints.add{type="table", column_count=MAGIC_NUMBERS.blueprint_limit,
style="filter_slot_table"}
modal_elements["blueprints_table"] = table_blueprints
end
local subfactory = util.globals.context(player).subfactory
local blueprints = subfactory.blueprints
local table_blueprints = modal_elements["blueprints_table"]
table_blueprints.clear()
local tutorial_tt = (util.globals.preferences(player).tutorial_mode)
and util.actions.tutorial_tooltip("act_on_blueprint", nil, player) or nil
local function format_signal(signal)
local type = (signal.type == "virtual") and "virtual-signal" or signal.type
return (type .. "/" .. signal.name)
end
local blueprint = modal_data.utility_inventory[1] -- re-usable inventory slot
for index, blueprint_string in pairs(blueprints) do
blueprint.import_stack(blueprint_string)
local blueprint_book = blueprint.is_blueprint_book
local tooltip = {"", (blueprint.label or "Blueprint"), tutorial_tt}
local sprite = (blueprint_book) and "item/blueprint-book" or "item/blueprint"
local button = table_blueprints.add{type="sprite-button", sprite=sprite, tooltip=tooltip,
tags={mod="fp", on_gui_click="act_on_blueprint", index=index}, mouse_button_filter={"left-and-right"}}
local icons = (not blueprint_book) and blueprint.blueprint_icons
or blueprint.get_inventory(defines.inventory.item_main)[1].blueprint_icons
if icons then -- this is jank-hell
local icon_count = #icons
local flow = button.add{type="flow", direction="horizontal", ignored_by_interaction=true}
local top_margin = (blueprint_book) and 4 or 7
if icon_count == 1 then
local sprite_icon = flow.add{type="sprite", sprite=format_signal(icons[1].signal)}
sprite_icon.style.margin = {top_margin, 0, 0, 7}
else
flow.style.padding = {4, 0, 0, 3}
local table = flow.add{type="table", column_count=2}
table.style.cell_padding = -4
if icon_count == 2 then table.style.top_margin = top_margin end
for _, icon in pairs(icons) do
table.add{type="sprite", sprite=format_signal(icon.signal)}
end
end
end
blueprint.clear()
end
if #blueprints < MAGIC_NUMBERS.blueprint_limit then
table_blueprints.add{type="sprite-button", tags={mod="fp", on_gui_click="utility_store_blueprint"},
sprite="utility/add", style="fp_sprite-button_inset_add_slot", mouse_button_filter={"left"}}
end
end
function utility_structures.notes(player, modal_data)
local utility_box = add_utility_box(player, modal_data.modal_elements, "notes", false, false)
local notes = util.globals.context(player).subfactory.notes
local text_box = utility_box.add{type="text-box", text=notes,
tags={mod="fp", on_gui_text_changed="subfactory_notes"}}
text_box.style.size = {500, 250}
text_box.word_wrap = true
text_box.style.top_margin = -2
end
local function handle_scope_change(player, tags, event)
local utility_scope = (event.element.switch_state == "left") and "Subfactory" or "Floor"
util.globals.preferences(player).utility_scopes[tags.utility_type] = utility_scope
local modal_data = util.globals.modal_data(player)
utility_structures.components(player, modal_data)
end
local function handle_item_request(player, _, _)
local ui_state = util.globals.ui_state(player)
local subfactory = ui_state.context.subfactory
if subfactory.item_request_proxy then -- if an item_proxy is set, cancel it
Subfactory.destroy_item_request_proxy(subfactory)
else
-- This crazy way to request items actually works, and is way easier than setting logistic requests
-- The advantage that is has is that the delivery is one-time, not a constant request
-- The disadvantage is that it's weird to have construction bots bring you stuff
subfactory.item_request_proxy = player.surface.create_entity{name="item-request-proxy",
position=player.position, force=player.force, target=player.character,
modules=ui_state.modal_data.missing_items}
end
update_request_button(player, ui_state.modal_data, subfactory)
end
local function handle_item_handcraft(player, tags, event)
local fly_text = util.cursor.create_flying_text
if not player.character then fly_text(player, {"fp.utility_no_character"}); return end
local permissions = player.permission_group
local forbidden = (permissions and not permissions.allows_action(defines.input_action.craft))
if forbidden then fly_text(player, {"fp.utility_no_crafting"}); return end
local desired_amount = (event.button == defines.mouse_button_type.right) and 5 or 1
local amount_to_craft = math.min(desired_amount, tags.missing_amount)
if amount_to_craft <= 0 then fly_text(player, {"fp.utility_no_demand"}); return end
local recipes = RECIPE_MAPS["produce"][tags.category_id][tags.item_id]
if not recipes then fly_text(player, {"fp.utility_no_recipe"}); return end
local success = false
for recipe_id, _ in pairs(recipes) do
local recipe_name = global.prototypes.recipes[recipe_id].name
local craftable_amount = player.get_craftable_count(recipe_name)
if craftable_amount > 0 then
success = true
local crafted_amount = math.min(amount_to_craft, craftable_amount)
player.begin_crafting{count=crafted_amount, recipe=recipe_name, silent=true}
amount_to_craft = amount_to_craft - crafted_amount
break
end
end
if not success then fly_text(player, {"fp.utility_no_resources"}); end
end
local function handle_inventory_change(player)
local ui_state = util.globals.ui_state(player)
if ui_state.modal_dialog_type == "utility" then
ui_state.modal_data.inventory_contents = player.get_main_inventory().get_contents()
utility_structures.components(player, ui_state.modal_data)
end
end
local function store_blueprint(player, _, _)
local ui_state = util.globals.ui_state(player)
local fly_text = util.cursor.create_flying_text
if player.is_cursor_empty() then
fly_text(player, {"fp.utility_cursor_empty"}); return
end
local cursor = player.cursor_stack
if not (cursor.is_blueprint or cursor.is_blueprint_book) then
if cursor.valid_for_read then
fly_text(player, {"fp.utility_no_blueprint"}); return
else
fly_text(player, {"fp.utility_blueprint_from_library"}); return
end
end
if cursor.is_blueprint then
if not cursor.is_blueprint_setup() then fly_text(player, {"fp.utility_blueprint_not_setup"}); return end
else -- blueprint book
local inventory = cursor.get_inventory(defines.inventory.item_main)
if inventory.is_empty() then fly_text(player, {"fp.utility_blueprint_book_empty"}); return end
end
table.insert(ui_state.context.subfactory.blueprints, cursor.export_stack())
fly_text(player, {"fp.utility_blueprint_stored"});
player.clear_cursor() -- doesn't delete blueprint, but puts it back in the inventory
utility_structures.blueprints(player, ui_state.modal_data)
end
local function handle_blueprint_click(player, tags, action)
local ui_state = util.globals.ui_state(player)
local blueprints = ui_state.context.subfactory.blueprints
if action == "pick_up" then
player.cursor_stack.import_stack(blueprints[tags.index])
util.raise.close_dialog(player, "cancel")
main_dialog.toggle(player)
elseif action == "delete" then
table.remove(blueprints, tags.index)
utility_structures.blueprints(player, ui_state.modal_data)
end
end
local function open_utility_dialog(player, modal_data)
-- Add the players' relevant inventory components to modal_data
modal_data.inventory_contents = player.get_main_inventory().get_contents()
modal_data.utility_inventory = game.create_inventory(1) -- used for blueprint decoding
utility_structures.components(player, modal_data)
utility_structures.blueprints(player, modal_data)
utility_structures.notes(player, modal_data)
end
local function close_utility_dialog(player, _)
util.globals.modal_data(player).utility_inventory.destroy()
util.raise.refresh(player, "subfactory_info", nil)
end
-- ** EVENTS **
local listeners = {}
listeners.gui = {
on_gui_click = {
{
name = "utility_item_combinator",
timeout = 20,
handler = (function(player, _, _)
local missing_items = util.globals.modal_data(player).missing_items
local success = util.cursor.set_item_combinator(player, missing_items)
if success then util.raise.close_dialog(player, "cancel"); main_dialog.toggle(player) end
end)
},
{
name = "utility_request_items",
timeout = 20,
handler = handle_item_request
},
{
name = "utility_craft_items",
handler = handle_item_handcraft
},
{
name = "utility_store_blueprint",
handler = store_blueprint
},
{
name = "act_on_blueprint",
modifier_actions = {
pick_up = {"left"},
delete = {"control-right"}
},
handler = handle_blueprint_click
},
},
on_gui_switch_state_changed = {
{
name = "utility_change_scope",
handler = handle_scope_change
}
},
on_gui_text_changed = {
{
name = "subfactory_notes",
handler = (function(player, _, event)
util.globals.context(player).subfactory.notes = event.element.text
end)
}
}
}
listeners.dialog = {
dialog = "utility",
metadata = (function(_) return {
caption = {"fp.utilities"},
create_content_frame = true
} end),
open = open_utility_dialog,
close = close_utility_dialog
}
listeners.misc = {
on_player_main_inventory_changed = handle_inventory_change
}
return { listeners }