592 lines
28 KiB
Lua

local table = require("__flib__.table")
require("presets.presets")
require("scripts.milestones_util")
local function refresh_arrow_buttons(gui_index, settings_flow)
local arrows_flow = settings_flow.children[gui_index].milestones_arrows_flow
arrows_flow.clear()
if gui_index == 1 then
arrows_flow.add{type="empty-widget", style="milestones_empty_button"}
else
arrows_flow.add{type="sprite-button", name="milestones_arrow_up", sprite="milestones_arrow_up", style="milestones_small_button",
tooltip={"milestones.settings_arrow_tooltip"}, tags={action="milestones_swap_setting", direction=-1}}
end
if gui_index == #settings_flow.children then
arrows_flow.add{type="empty-widget", style="milestones_empty_button"}
else
arrows_flow.add{type="sprite-button", name="milestones_arrow_down", sprite="milestones_arrow_down", style="milestones_small_button",
tooltip={"milestones.settings_arrow_tooltip"}, tags={action="milestones_swap_setting", direction=1}}
end
end
local function refresh_all_arrow_buttons(settings_flow)
for i, child in pairs(settings_flow.children) do
if child.type == "flow" then
refresh_arrow_buttons(i, settings_flow)
end
end
end
local function add_group(settings_flow, default_name, gui_index)
local group_flow = settings_flow.add{type="flow", direction="horizontal", style="milestones_horizontal_flow_big_settings", index=gui_index}
group_flow.add{type="sprite", sprite="milestones_icon_group", tooltip={"milestones.type_group"}}
group_flow.add{type="label", name="milestones_settings_label", caption={"", {"milestones.type_group"}, ":"}}
group_flow.add{type="textfield", name="milestones_settings_group_name", text=default_name, clear_and_focus_on_right_click=true}
group_flow.add{type="empty-widget", style="flib_horizontal_pusher"}
group_flow.add{type="flow", name="milestones_arrows_flow", direction="vertical"}
group_flow.add{type="checkbox", name="milestones_settings_checkbox", state=false,
tooltip={"milestones.settings_checkbox_tooltip"}, tags={action="milestones_select_setting"}}
return group_flow
end
local function add_milestone_setting(milestone, settings_flow, gui_index)
local prototype
local elem_button
local common_type
if milestone.type == "item_consumption" then
common_type = "item"
elseif milestone.type == "fluid_consumption" then
common_type = "fluid"
else
common_type = milestone.type
end
local milestone_flow = settings_flow.add{type="flow", direction="horizontal", style="milestones_horizontal_flow_big_settings", index=gui_index}
milestone_flow.add{type="sprite", sprite="milestones_icon_"..common_type, tooltip={"milestones.type_"..common_type}}
if milestone.hidden then
milestone_flow.tags = {hidden= true}
end
if milestone.type == "item" or milestone.type == "item_consumption" then
prototype = game.item_prototypes[milestone.name]
local default_selection = nil
if prototype ~= nil then default_selection = milestone.name end
elem_button = {type="choose-elem-button", name="milestones_settings_item", elem_type="item",
item=default_selection, tags={action="milestones_change_setting", milestone_type=milestone.type}}
elseif milestone.type == "fluid" or milestone.type == "fluid_consumption" then
prototype = game.fluid_prototypes[milestone.name]
local default_selection = nil
if prototype ~= nil then default_selection = milestone.name end
elem_button = {type="choose-elem-button", name="milestones_settings_item", elem_type="fluid",
fluid=default_selection, tags={action="milestones_change_setting", milestone_type=milestone.type}}
elseif milestone.type == "technology" then
prototype = game.technology_prototypes[milestone.name]
local default_selection = nil
if prototype ~= nil then default_selection = milestone.name end
elem_button = {type="choose-elem-button", name="milestones_settings_item", elem_type="technology",
technology=default_selection, tags={action="milestones_change_setting", milestone_type="technology"}}
elseif milestone.type == "kill" then
prototype = game.entity_prototypes[milestone.name]
local default_selection = nil
if prototype ~= nil then default_selection = milestone.name end
elem_button = {type="choose-elem-button", name="milestones_settings_item", elem_type="entity",
entity=default_selection, tags={action="milestones_change_setting", milestone_type="kill"},
elem_filters={{filter="entity-with-health"}}}
end
milestone_flow.add(elem_button)
local caption
if milestone.name ~= nil and prototype == nil then
caption = {"", "[color=red]", {"milestones.invalid_entry"}, milestone.name, "[/color]"}
else
caption = (prototype ~= nil) and prototype.localised_name or ""
end
milestone_flow.add{type="label", name="milestones_settings_label", caption=caption}
milestone_flow.add{type="empty-widget", style="flib_horizontal_pusher"}
local unique_technology = milestone.type ~= "technology" or (prototype ~= nil and prototype.research_unit_count_formula ~= nil) -- No text field or infinity button for unique technologies
milestone_flow.add{type="textfield", name="milestones_settings_quantity", text=milestone.quantity, numeric=true, clear_and_focus_on_right_click=true,
tags={action="milestones_change_setting_quantity"}, style="short_number_textfield", visible=unique_technology, tooltip={"milestones.settings_quantity_tooltip"}}
local infinity_button = milestone_flow.add({type="sprite-button", sprite="milestones_infinity_icon", tags={action="milestones_settings_infinity_button"}, style="milestones_grey_button", visible=unique_technology,
tooltip={"milestones.settings_infinity_button_tooltip"}})
milestone_flow.add({type="textfield", name="milestones_settings_next_textfield", text=milestone.next, style="milestones_very_short_textfield", visible=false,
clear_and_focus_on_right_click=true, tooltip={"milestones.settings_next_textfield_tooltip"}})
milestone_flow.add{type="empty-widget", name="milestones_settings_next_spacer", style="milestones_very_short_spacer", visible=true}
if milestone.next ~= nil then
toggle_infinity_button(infinity_button)
end
milestone_flow.add{type="flow", name="milestones_arrows_flow", direction="vertical"}
milestone_flow.add{type="checkbox", name="milestones_settings_checkbox", state=false,
tooltip={"milestones.settings_checkbox_tooltip"}, tags={action="milestones_select_setting"}}
return milestone_flow
end
local function add_alias_setting(milestone, settings_flow, gui_index)
local milestone_flow = settings_flow.add({type="flow", direction="horizontal", style="milestones_horizontal_flow_big_settings", index=gui_index})
local caption = {"", {"milestones.settings_alias"}, ": ", milestone.name, " = ", milestone.quantity, "x ", milestone.equals}
milestone_flow.add({type="label", name="milestones_settings_alias_label", caption=caption, tags={name=milestone.name, equals=milestone.equals, quantity=milestone.quantity}})
milestone_flow.add({type="empty-widget", style="flib_horizontal_pusher"})
milestone_flow.add({type="flow", name="milestones_arrows_flow", direction="vertical"})
milestone_flow.add{type="checkbox", name="milestones_settings_checkbox", state=false,
tooltip={"milestones.settings_checkbox_tooltip"}, tags={action="milestones_select_setting"}}
end
local function add_settings_element_from_json_item(settings_element, settings_flow, gui_index)
if settings_element.type == "group" then
return add_group(settings_flow, settings_element.name, gui_index)
elseif settings_element.type == "alias" then
return add_alias_setting(settings_element, settings_flow, gui_index)
else
return add_milestone_setting(settings_element, settings_flow, gui_index)
end
end
function toggle_infinity_button(button_element)
local textfield_element = button_element.parent.milestones_settings_next_textfield
local spacer_element = button_element.parent.milestones_settings_next_spacer
if button_element.style.name == "milestones_selected_grey_button" then
button_element.style = "milestones_grey_button"
textfield_element.visible = false
spacer_element.visible = true
else
button_element.style = "milestones_selected_grey_button"
textfield_element.visible = true
spacer_element.visible = false
if textfield_element.text == nil or textfield_element.text == "" then
textfield_element.text = "x10"
end
end
end
local function get_milestones_array_element(flow, allow_empty, player_index)
if not allow_empty
and (flow.milestones_settings_item == nil or flow.milestones_settings_item.elem_value == nil)
and (flow.milestones_settings_group_name == nil or flow.milestones_settings_group_name.text == nil)
and flow.milestones_settings_alias_label == nil then
return nil
end
if flow.milestones_settings_group_name then
return {type="group", name=flow.milestones_settings_group_name.text}
elseif flow.milestones_settings_alias_label then
return {type="alias", name=flow.milestones_settings_alias_label.tags.name, equals=flow.milestones_settings_alias_label.tags.equals, quantity=flow.milestones_settings_alias_label.tags.quantity}
end
local quantity = tonumber(flow.milestones_settings_quantity.text) or 1
local next_formula = flow.milestones_settings_next_textfield.text
if not flow.milestones_settings_next_textfield.visible or next_formula == "" then next_formula = nil end
if next_formula ~= nil then
local operator, _ = parse_next_formula(next_formula)
if operator == nil then
game.players[player_index].print({"", {"milestones.message_invalid_next"}, next_formula})
next_formula = nil
end
end
local hidden = flow.tags.hidden
if not hidden or hidden == "" then hidden = nil end
return {
type=flow.milestones_settings_item.tags.milestone_type,
name=flow.milestones_settings_item.elem_value,
quantity=quantity,
next=next_formula,
hidden=hidden
}
end
function get_resulting_milestones_array(player_index)
local resulting_milestones = {}
local settings_flow = global.players[player_index].settings_flow
for _, child in pairs(settings_flow.children) do
if child.type == "flow" then
local milestone = get_milestones_array_element(child, false, player_index)
table.insert(resulting_milestones, milestone)
end
end
return resulting_milestones
end
function fill_settings_flow(settings_flow, milestones)
for i, milestone in pairs(milestones) do
add_settings_element_from_json_item(milestone, settings_flow, nil)
if i < #milestones then
settings_flow.add{type="line"}
end
end
refresh_all_arrow_buttons(settings_flow)
end
local function is_preset_modified(current_milestones, preset_name)
return presets[preset_name] ~= nil
and not table.deep_compare(current_milestones, presets[preset_name].milestones)
end
function build_settings_page(player)
local main_frame = get_main_frame(player.index)
main_frame.milestones_titlebar.milestones_main_label.caption = {"milestones.settings_title"}
main_frame.milestones_titlebar.milestones_settings_button.visible = false
main_frame.milestones_titlebar.milestones_close_button.visible = false
main_frame.milestones_dialog_buttons.visible = true
local inner_frame = get_inner_frame(player.index)
local settings_outer_flow = inner_frame.add{type="flow", name="milestones_settings_outer_flow", direction="vertical", style="milestones_settings_outer_flow"}
local preset_flow = settings_outer_flow.add{type="flow", name="milestones_preset_flow", direction="horizontal", style="milestones_horizontal_flow_center"}
-- Preset dropdown
preset_flow.add{type="label", caption={"milestones.settings_preset"}, style="caption_label"}
local preset_dropdown = preset_flow.add{type="drop-down", name="milestones_preset_dropdown", items=global.valid_preset_names}
if global.current_preset_name == "Imported" then
preset_dropdown.caption = {"milestones.settings_imported"}
preset_dropdown.tags = {action="milestones_change_preset", imported=true}
else
local current_preset_index = table_get_index(global.valid_preset_names, global.current_preset_name) or 1
preset_dropdown.selected_index = current_preset_index
preset_dropdown.tags = {action="milestones_change_preset", imported=false}
if is_preset_modified(global.loaded_milestones, global.current_preset_name) then
preset_dropdown.caption = {"", global.current_preset_name, " ", {"milestones.settings_preset_modified"}}
end
end
preset_flow.add{type="button", caption={"milestones.settings_reset_preset"}, tooltip={"milestones.settings_reset_preset_tooltip"}, style="milestones_stretchable_button",
tags={action="milestones_reset_preset"}}
preset_flow.add{type="sprite-button", name="milestones_import_button", tooltip={"milestones.settings_import"}, sprite="utility/import_slot", style="tool_button",
tags={action="milestones_open_import"}}
preset_flow.add{type="sprite-button", name="milestones_export_button", tooltip={"milestones.settings_export"}, sprite="utility/export_slot", style="tool_button",
tags={action="milestones_open_export"}}
local settings_scroll = settings_outer_flow.add{type="scroll-pane", name="milestones_settings_scroll", style="milestones_settings_scroll"}
local settings_flow = settings_scroll.add{type="frame", name="milestones_settings_inner_flow", direction="vertical", style="milestones_deep_frame_in_shallow_frame"}
global.players[player.index].settings_flow = settings_flow
global.players[player.index].settings_selection_indexes = {}
fill_settings_flow(settings_flow, global.loaded_milestones)
local buttons_flow = settings_outer_flow.add{type="flow", name="milestones_button_flow", direction="horizontal"}
for _, type in pairs({"group", "item", "fluid", "technology", "kill"}) do
local tooltip = type == "group" and {"milestones.settings_add_group"} or {"milestones.settings_add_tooltip", {"milestones.type_"..type}}
buttons_flow.add{type="button", enabled=false, tooltip=tooltip,
caption={"", "[img=milestones_icon_"..type.."_black] ", {"milestones.settings_add_something", {"milestones.type_"..type}}},
tags={action="milestones_add_setting", type=type}}
end
settings_outer_flow.add{type="button", name="milestones_delete_settings_button",
enabled=false, style="red_button", tooltip={"milestones.settings_delete_tooltip"},
caption={"", "[img=utility/trash] ", {"gui.delete"}},
tags={action="milestones_delete_settings"}}
get_outer_frame(player.index).force_auto_center()
end
local function update_checkbox_states(settings_flow, settings_selection_indexes)
local nb_children = #settings_flow.children
for i = 1, nb_children, 2 do -- 2, because we skip line separators
settings_flow.children[i].milestones_settings_checkbox.state = settings_selection_indexes[i] or false
end
end
local function update_buttons_enabled_state(settings_outer_flow, settings_selection_indexes)
local is_anything_selected = table_size(settings_selection_indexes) > 0
local add_enabled = is_anything_selected or #settings_outer_flow.milestones_settings_scroll.milestones_settings_inner_flow.children == 0
for _, button in pairs(settings_outer_flow.milestones_button_flow.children) do
button.enabled = add_enabled
end
settings_outer_flow.milestones_delete_settings_button.enabled = is_anything_selected
end
function select_setting(event)
local global_player = global.players[event.player_index]
local checkbox_element = event.element
local index = checkbox_element.parent.get_index_in_parent()
if checkbox_element.state then -- The element state was already flipped at this point
if event.shift and table_size(global_player.settings_selection_indexes) > 0 then
-- Shift-selection, update the range, update selections
local first_selected
local last_selected
for i, checked in pairs(global_player.settings_selection_indexes) do
if checked then
if not first_selected then first_selected = i end
last_selected = i
end
end
if index < first_selected then
for i = index, first_selected - 2, 2 do
global_player.settings_selection_indexes[i] = true
end
elseif index > last_selected then
for i = last_selected + 2, index, 2 do
global_player.settings_selection_indexes[i] = true
end
else
global_player.settings_selection_indexes[index] = true
end
elseif event.control then
-- Additive selection
global_player.settings_selection_indexes[index] = true
else
-- Normal selection, deselect all others
global_player.settings_selection_indexes = {}
global_player.settings_selection_indexes[index] = true
end
else
global_player.settings_selection_indexes[index] = nil
end
update_checkbox_states(global_player.settings_flow, global_player.settings_selection_indexes)
update_buttons_enabled_state(global_player.settings_flow.parent.parent, global_player.settings_selection_indexes)
end
function swap_settings(player_index, event)
local index_delta = event.element.tags.direction *2 -- *2 because of separation lines
if event.shift then
index_delta = index_delta * 5
end
local settings_flow = global.players[player_index].settings_flow
gui_index1 = event.element.parent.parent.get_index_in_parent()
gui_index2 = gui_index1 + index_delta
gui_index1, gui_index2 = math.min(gui_index1, gui_index2), math.max(gui_index1, gui_index2) -- Now index1 is always the first element
gui_index1 = math.max(gui_index1, 1) -- Don't go beyond first element
gui_index2 = math.min(gui_index2, #settings_flow.children) -- Don't go beyond the last element
settings_flow.swap_children(gui_index1, gui_index2)
-- Swap selection
local global_player = global.players[event.player_index]
global_player.settings_selection_indexes[gui_index1], global_player.settings_selection_indexes[gui_index2] = global_player.settings_selection_indexes[gui_index2], global_player.settings_selection_indexes[gui_index1]
refresh_arrow_buttons(gui_index1, settings_flow)
refresh_arrow_buttons(gui_index2, settings_flow)
end
function delete_selected_settings(player_index)
local global_player = global.players[player_index]
local settings_flow = global_player.settings_flow
local inverted_indexes = {}
for gui_index, _ in pairs(global_player.settings_selection_indexes) do
table.insert(inverted_indexes, 1, gui_index)
end
for _, gui_index in pairs(inverted_indexes) do
settings_flow.children[gui_index].destroy()
if #settings_flow.children > 0 then
-- If this is the first element, we have to delete the line AFTER the element
-- The line AFTER will be index 1 once the element is destroyed
local line_gui_index = (gui_index == 1) and 1 or gui_index - 1
settings_flow.children[line_gui_index].destroy()
end
end
-- Update arrows of the potentially new first and last element
if #settings_flow.children >= 1 then
refresh_arrow_buttons(1, settings_flow)
end
if #settings_flow.children >= 2 then
refresh_arrow_buttons(#settings_flow.children, settings_flow)
end
global_player.settings_selection_indexes = {}
update_buttons_enabled_state(global_player.settings_flow.parent.parent, global_player.settings_selection_indexes)
end
function add_setting(player_index, button_element)
local global_player = global.players[player_index]
local settings_flow = global_player.settings_flow
local milestone_type = button_element.tags.type
-- Find last selected element
local last_selected_element_index = 0
for i = #settings_flow.children, 1, -2 do
if global_player.settings_selection_indexes[i] then
last_selected_element_index = i
break
end
end
local only_element = last_selected_element_index == 0
local insert_at_index = last_selected_element_index + 1
if not only_element then
settings_flow.add({type="line", index = insert_at_index})
insert_at_index = insert_at_index + 1
end
local new_flow
if milestone_type == "group" then
local group_name = "Group " -- Can't localize default textfield values
local group_digit = 1
for i = last_selected_element_index, 1, -1 do
local child = settings_flow.children[i]
if child.type == "flow" and child.milestones_settings_group_name then
local previous_group_name = child.milestones_settings_group_name.text
local previous_name_digit = string.match(previous_group_name, "(%d+)$")
if previous_name_digit then
previous_name_digit = tonumber(previous_name_digit)
group_digit = previous_name_digit + 1
end
break
end
end
group_name = group_name .. group_digit
new_flow = add_group(settings_flow, group_name, insert_at_index)
new_flow.milestones_settings_group_name.focus()
new_flow.milestones_settings_group_name.select_all()
else
local milestone = {type=milestone_type, quantity=1}
new_flow = add_milestone_setting(milestone, settings_flow, insert_at_index)
end
refresh_arrow_buttons(insert_at_index, settings_flow)
if only_element then
update_buttons_enabled_state(settings_flow.parent.parent, global_player.settings_selection_indexes)
else
refresh_arrow_buttons(last_selected_element_index, settings_flow)
end
local inner_frame = get_inner_frame(player_index)
inner_frame.milestones_settings_outer_flow.milestones_settings_scroll.scroll_to_element(new_flow)
end
function cancel_settings_page(player_index)
get_inner_frame(player_index).clear()
local player = game.get_player(player_index)
build_display_page(player)
local outer_frame = get_outer_frame(player_index)
local import_export_frame = outer_frame.milestones_settings_import_export
local inside_frame = import_export_frame.milestones_settings_import_export_inside
outer_frame.force_auto_center()
inside_frame.clear()
import_export_frame.visible = false
end
function confirm_settings_page(player_index)
local player = game.players[player_index]
if not player.admin then
game.players[player_index].print{"milestones.message_settings_permission_denied"}
return
end
local new_loaded_milestones = get_resulting_milestones_array(player_index)
if not table.deep_compare(global.loaded_milestones, new_loaded_milestones) then -- If something changed
local inner_frame = get_inner_frame(player_index)
local preset_dropdown = inner_frame.milestones_settings_outer_flow.milestones_preset_flow.milestones_preset_dropdown
if preset_dropdown.tags.imported then
global.current_preset_name = "Imported"
else
global.current_preset_name = preset_dropdown.get_item(preset_dropdown.selected_index)
end
global.loaded_milestones = table.deep_copy(new_loaded_milestones)
initialize_alias_table()
local backfilled_anything = false
for force_name, force in pairs(game.forces) do
local global_force = global.forces[force_name]
if global_force ~= nil then
merge_new_milestones(force_name, new_loaded_milestones)
backfilled_anything = backfill_completion_times(force)
end
end
local main_message = {"milestones.message_settings_changed"}
if game.is_multiplayer() then
main_message = game.print({"milestones.message_settings_changed_multiplayer", player.name})
end
local full_message = {"", main_message}
if backfilled_anything then
table.insert(full_message, " ")
table.insert(full_message, {"milestones.message_settings_backfilled"})
end
game.print(full_message)
end
cancel_settings_page(player_index)
end
script.on_event(defines.events.on_gui_elem_changed, function(event)
if not event.element then return end
if not event.element.tags then return end
if event.element.tags.action == "milestones_change_setting" then
local prototype
if event.element.elem_type == "item" then
prototype = game.item_prototypes[event.element.elem_value]
elseif event.element.elem_type == "fluid" then
prototype = game.fluid_prototypes[event.element.elem_value]
elseif event.element.elem_type == "technology" then
prototype = game.technology_prototypes[event.element.elem_value]
local visible_textfield = prototype ~= nil and prototype.research_unit_count_formula ~= nil -- No text field for unique technologies
event.element.parent.milestones_settings_quantity.visible = visible_textfield
if not visible_textfield then
event.element.parent.milestones_settings_quantity.text = "1"
end
elseif event.element.elem_type == "entity" then
prototype = game.entity_prototypes[event.element.elem_value]
end
event.element.parent.milestones_settings_label.caption = prototype ~= nil and prototype.localised_name or ""
end
end)
-- Replaces 0s with 1s in quantity settings
script.on_event(defines.events.on_gui_text_changed, function(event)
if not event.element then return end
if not event.element.tags then return end
if event.element.tags.action == "milestones_change_setting_quantity" then
local new_quantity = tonumber(event.text)
if new_quantity ~= nil and new_quantity < 1 then
event.element.text = "1"
end
end
end)
function preset_dropdown_changed(event)
event.element.tags = {action="milestones_change_preset", imported=false}
local selected_preset_name = event.element.get_item(event.element.selected_index)
local preset_milestones = {}
if presets[selected_preset_name] then
preset_milestones = presets[selected_preset_name].milestones
end
switch_to_milestones_set(preset_milestones, event.player_index)
end
function reset_preset(player_index)
-- Preset
local chosen_preset_name = get_auto_detected_preset_name()
local new_milestones = table.deep_copy(presets[chosen_preset_name].milestones)
-- Preset addons
for _preset_addon_name, preset_addon in pairs(preset_addons) do
if is_preset_mods_enabled(preset_addon) then
for _, milestone in pairs(preset_addon.milestones) do
table.insert(new_milestones, milestone)
end
end
end
local preset_dropdown = get_inner_frame(player_index).milestones_settings_outer_flow.milestones_preset_flow.milestones_preset_dropdown
local current_preset_index = table_get_index(global.valid_preset_names, chosen_preset_name) or 1
preset_dropdown.selected_index = current_preset_index
preset_dropdown.tags = {action="milestones_change_preset", imported=false}
switch_to_milestones_set(new_milestones, player_index)
-- Can be modified or not depending on addons
if is_preset_modified(new_milestones, chosen_preset_name) then
preset_dropdown.caption = {"", chosen_preset_name, " ", {"milestones.settings_preset_modified"}}
end
end
function switch_to_milestones_set(milestones_set, player_index)
local global_player = global.players[player_index]
local settings_flow = global_player.settings_flow
settings_flow.clear()
if next(milestones_set) then
fill_settings_flow(settings_flow, milestones_set)
get_outer_frame(player_index).force_auto_center()
end
global_player.settings_selection_indexes = {}
update_buttons_enabled_state(settings_flow.parent.parent, global_player.settings_selection_indexes)
end
function is_settings_page_visible(player_index)
return get_inner_frame(player_index).milestones_settings_scroll ~= nil
end