Первый фикс

Пачки некоторых позиций увеличены
This commit is contained in:
2024-03-01 20:53:32 +03:00
commit 7c9c708c92
23653 changed files with 767936 additions and 0 deletions

View File

@@ -0,0 +1,61 @@
local cursor = {}
function cursor.set_stack(player, cursor_stack, player_table, item_name)
local is_cheating = player.cheat_mode or (player.controller_type == defines.controllers.editor)
local spawn_item = player_table.settings.spawn_items_when_cheating
-- space exploration - don't spawn items when in the satellite view
if script.active_mods["space-exploration"] and player.controller_type == defines.controllers.god then
spawn_item = false
end
-- don't bother if they don't have an inventory
local main_inventory = player.get_main_inventory()
if main_inventory then
-- set cursor stack
local item_stack, item_stack_index, inventory_index
for _, inventory in pairs({
main_inventory,
player.get_inventory(defines.inventory.character_ammo),
player.get_inventory(defines.inventory.character_guns),
}) do
item_stack, item_stack_index = inventory.find_item_stack(item_name)
if item_stack and item_stack.valid then
inventory_index = inventory.index
break
end
end
if item_stack and item_stack.valid then
if player.clear_cursor() then
-- actually transfer from the source inventory, then set the hand location
cursor_stack.transfer_stack(item_stack)
player.hand_location = { inventory = inventory_index, slot = item_stack_index }
end
return true
elseif spawn_item and is_cheating then
local stack_spec = { name = item_name, count = game.item_prototypes[item_name].stack_size }
-- insert into main inventory first, then transfer and set the hand location
if main_inventory.can_insert(stack_spec) and player.clear_cursor() then
main_inventory.insert(stack_spec)
local new_stack, new_stack_index = main_inventory.find_item_stack(item_name)
cursor_stack.transfer_stack(new_stack)
player.hand_location = { inventory = main_inventory.index, slot = new_stack_index }
else
player.create_local_flying_text({
text = { "message.qis-main-inventory-full" },
create_at_cursor = true,
})
player.play_sound({
path = "utility/cannot_build",
})
end
return true
elseif player.clear_cursor() then
player_table.last_item = item_name
player.cursor_ghost = item_name
return true
end
end
end
return cursor

View File

@@ -0,0 +1,17 @@
local dictionary = require("__flib__/dictionary-lite")
local global_data = {}
function global_data.init()
global.players = {}
global.update_search_results = {}
end
function global_data.build_dictionary()
dictionary.new("item")
for name, prototype in pairs(game.item_prototypes) do
dictionary.add("item", name, prototype.localised_name)
end
end
return global_data

View File

@@ -0,0 +1,291 @@
local gui = require("__flib__/gui")
local math = require("__flib__/math")
local constants = require("__QuickItemSearch__/constants")
local infinity_filter = require("__QuickItemSearch__/scripts/infinity-filter")
local infinity_filter_gui = {}
function infinity_filter_gui.build(player, player_table)
if player.gui.screen.qis_infinity_filter_window then
infinity_filter_gui.destroy(player_table)
end
local resolution = player.display_resolution
local scale = player.display_scale
local focus_frame_size = { resolution.width / scale, resolution.height / scale }
local refs = gui.build(player.gui.screen, {
{
type = "frame",
style = "invisible_frame",
style_mods = { size = focus_frame_size },
ref = { "focus_frame" },
visible = false,
actions = {
on_click = { gui = "infinity_filter", action = "close", reopen_after_subwindow = true },
},
},
{
type = "frame",
name = "qis_infinity_filter_window",
direction = "vertical",
visible = false,
ref = { "window" },
actions = {
on_closed = { gui = "infinity_filter", action = "close", reopen_after_subwindow = true },
},
children = {
{
type = "flow",
style = "flib_titlebar_flow",
ref = { "titlebar_flow" },
actions = {
on_click = { gui = "infinity_filter", action = "recenter" },
},
children = {
{
type = "label",
style = "frame_title",
caption = { "gui.qis-edit-infinity-filter" },
ignored_by_interaction = true,
},
{ type = "empty-widget", style = "flib_titlebar_drag_handle", ignored_by_interaction = true },
{
type = "sprite-button",
style = "frame_action_button",
sprite = "utility/close_white",
hovered_sprite = "utility/close_black",
clicked_sprite = "utility/close_black",
actions = {
on_click = { gui = "infinity_filter", action = "close", reopen_after_subwindow = true },
},
},
},
},
{
type = "frame",
style = "inside_shallow_frame",
direction = "vertical",
children = {
{
type = "frame",
style = "subheader_frame",
children = {
{ type = "label", style = "subheader_caption_label", ref = { "item_label" } },
{ type = "empty-widget", style = "flib_horizontal_pusher" },
},
},
{
type = "flow",
style_mods = { vertical_align = "center", horizontal_spacing = 8, padding = 12 },
children = {
{
type = "drop-down",
style_mods = { width = 60 },
items = { "", "", "=" },
selected_index = 3,
ref = { "filter_setter", "dropdown" },
actions = {
on_selection_state_changed = { gui = "infinity_filter", action = "change_filter_mode" },
},
},
{
type = "slider",
style = "notched_slider",
style_mods = { horizontally_stretchable = true },
minimum_value = 0,
maximum_value = 500,
value_step = 50,
value = 500,
discrete_slider = true,
discrete_values = true,
ref = { "filter_setter", "slider" },
actions = {
on_value_changed = {
gui = "infinity_filter",
action = "update_filter",
elem = "slider",
},
},
},
{
type = "textfield",
style = "slider_value_textfield",
numeric = true,
ref = { "filter_setter", "textfield" },
actions = {
on_text_changed = { gui = "infinity_filter", action = "update_filter", elem = "textfield" },
},
},
{
type = "sprite-button",
style = "item_and_count_select_confirm",
sprite = "utility/check_mark",
tooltip = { "", { "gui.qis-set-infinity-filter" }, { "gui.qis-confirm" } },
actions = {
on_click = { gui = "infinity_filter", action = "set_filter" },
},
},
{
type = "sprite-button",
style = "flib_tool_button_light_green",
style_mods = { top_margin = 1 },
sprite = "qis_temporary_request",
tooltip = { "", { "gui.qis-set-temporary-infinity-filter" }, { "gui.qis-shift-confirm" } },
actions = {
on_click = { gui = "infinity_filter", action = "set_filter", temporary = true },
},
},
{
type = "sprite-button",
style = "tool_button_red",
style_mods = { top_margin = 1 },
sprite = "utility/trash",
tooltip = { "", { "gui.qis-clear-infinity-filter" }, { "gui.qis-control-confirm" } },
actions = {
on_click = { gui = "infinity_filter", action = "clear_filter" },
},
},
},
},
},
},
},
},
})
refs.window.force_auto_center()
refs.titlebar_flow.drag_target = refs.window
player_table.guis.infinity_filter = {
refs = refs,
state = {
item_data = nil,
visible = false,
},
}
end
function infinity_filter_gui.destroy(player_table)
local gui_data = player_table.guis.infinity_filter
if gui_data then
local window = gui_data.refs.window
if window and window.valid then
window.destroy()
end
player_table.guis.infinity_filter = nil
end
end
function infinity_filter_gui.open(player, player_table, item_data)
local gui_data = player_table.guis.infinity_filter
local refs = gui_data.refs
local state = gui_data.state
-- update state
local stack_size = game.item_prototypes[item_data.name].stack_size
item_data.stack_size = stack_size
state.item_data = item_data
local infinity_filter_data = item_data.infinity_filter or { mode = "at-least", count = stack_size }
infinity_filter_data.name = item_data.name
state.infinity_filter = infinity_filter_data
state.visible = true
-- update item label
refs.item_label.caption = "[item=" .. item_data.name .. "] " .. item_data.translation
-- update filter setter
local filter_setter = refs.filter_setter
filter_setter.dropdown.selected_index = constants.infinity_filter_mode_to_index[infinity_filter_data.mode]
filter_setter.slider.set_slider_value_step(1)
filter_setter.slider.set_slider_minimum_maximum(0, stack_size * 10)
filter_setter.slider.set_slider_value_step(stack_size)
filter_setter.slider.slider_value = math.round(infinity_filter_data.count, stack_size)
filter_setter.textfield.text = tostring(infinity_filter_data.count)
filter_setter.textfield.select_all()
filter_setter.textfield.focus()
-- update window
refs.focus_frame.visible = true
refs.focus_frame.bring_to_front()
refs.window.visible = true
refs.window.bring_to_front()
-- set opened
player.opened = refs.window
end
function infinity_filter_gui.close(player, player_table)
local gui_data = player_table.guis.infinity_filter
gui_data.state.visible = false
gui_data.refs.focus_frame.visible = false
gui_data.refs.window.visible = false
if not player.opened then
player.opened = player_table.guis.search.refs.window
end
end
function infinity_filter_gui.set_filter(player, player_table, is_temporary)
player.play_sound({ path = "utility/confirm" })
infinity_filter.set(player, player_table, player_table.guis.infinity_filter.state.infinity_filter, is_temporary)
if is_temporary then
player.opened = nil
end
end
function infinity_filter_gui.clear_filter(player, player_table)
player.play_sound({ path = "utility/confirm" })
infinity_filter.clear(player, player_table, player_table.guis.infinity_filter.state.infinity_filter.name)
player.opened = nil
end
function infinity_filter_gui.cycle_filter_mode(gui_data)
local refs = gui_data.refs
local state = gui_data.state
state.infinity_filter.mode = (
next(constants.infinity_filter_modes, state.infinity_filter.mode) or next(constants.infinity_filter_modes)
)
refs.filter_setter.dropdown.selected_index = constants.infinity_filter_mode_to_index[state.infinity_filter.mode]
end
function infinity_filter_gui.handle_action(e, msg)
local player = game.get_player(e.player_index)
local player_table = global.players[e.player_index]
local gui_data = player_table.guis.infinity_filter
local refs = gui_data.refs
local state = gui_data.state
local item_data = state.item_data
local filter_data = state.infinity_filter
if msg.action == "close" then
infinity_filter_gui.close(player, player_table)
elseif msg.action == "change_filter_mode" then
local new_mode = constants.infinity_filter_modes_by_index[e.element.selected_index]
state.infinity_filter.mode = new_mode
elseif msg.action == "update_filter" then
if msg.elem == "slider" then
local count = e.element.slider_value
filter_data.count = count
refs.filter_setter.textfield.text = tostring(count)
else
local count = tonumber(e.element.text) or 0
filter_data.count = count
refs.filter_setter.slider.slider_value = math.round(count, item_data.stack_size)
end
elseif msg.action == "clear_filter" then
infinity_filter.clear(player, player_table, filter_data.name)
-- invoke `on_gui_closed` so the search GUI will be refocused
player.opened = nil
elseif msg.action == "set_filter" then
-- HACK: Makes it easy for the search GUI to tell that this was confirmed
player_table.confirmed_tick = game.ticks_played
infinity_filter.set(player, player_table, filter_data, msg.temporary)
-- invoke `on_gui_closed` so the search GUI will be refocused
player.opened = nil
end
end
return infinity_filter_gui

View File

@@ -0,0 +1,382 @@
local gui = require("__flib__/gui")
local math = require("__flib__/math")
local constants = require("__QuickItemSearch__/constants")
local logistic_request = require("__QuickItemSearch__/scripts/logistic-request")
local logistic_request_gui = {}
function logistic_request_gui.build(player, player_table)
if player.gui.screen.qis_request_window then
logistic_request_gui.destroy(player_table)
end
local resolution = player.display_resolution
local scale = player.display_scale
local focus_frame_size = { resolution.width / scale, resolution.height / scale }
local refs = gui.build(player.gui.screen, {
{
type = "frame",
style = "invisible_frame",
style_mods = { size = focus_frame_size },
ref = { "focus_frame" },
visible = false,
actions = {
on_click = { gui = "request", action = "close", reopen_after_subwindow = true },
},
},
{
type = "frame",
name = "qis_request_window",
direction = "vertical",
visible = false,
ref = { "window" },
actions = {
on_closed = { gui = "request", action = "close", reopen_after_subwindow = true },
},
children = {
{
type = "flow",
style = "flib_titlebar_flow",
ref = { "titlebar_flow" },
actions = {
on_click = { gui = "request", action = "recenter" },
},
children = {
{
type = "label",
style = "frame_title",
caption = { "gui.qis-edit-logistic-request" },
ignored_by_interaction = true,
},
{ type = "empty-widget", style = "flib_titlebar_drag_handle", ignored_by_interaction = true },
{
type = "sprite-button",
style = "frame_action_button",
sprite = "utility/close_white",
hovered_sprite = "utility/close_black",
clicked_sprite = "utility/close_black",
actions = {
on_click = { gui = "request", action = "close", reopen_after_subwindow = true },
},
},
},
},
{
type = "frame",
style = "inside_shallow_frame",
direction = "vertical",
children = {
{
type = "frame",
style = "subheader_frame",
children = {
{ type = "label", style = "subheader_caption_label", ref = { "item_label" } },
{ type = "empty-widget", style = "flib_horizontal_pusher" },
},
},
{
type = "flow",
style_mods = { vertical_align = "center", horizontal_spacing = 8, padding = 12 },
children = {
{
type = "textfield",
style = "slider_value_textfield",
numeric = true,
clear_and_focus_on_right_click = true,
text = "0",
tags = { bound = "min" },
ref = { "logistic_setter", "min", "textfield" },
actions = {
on_confirmed = { gui = "request", action = "update_request" },
},
},
{
type = "flow",
direction = "vertical",
children = {
{
type = "slider",
style = "notched_slider",
style_mods = { horizontally_stretchable = true },
minimum_value = 0,
maximum_value = 500,
value_step = 50,
value = 0,
discrete_slider = true,
discrete_values = true,
tags = { bound = "max" },
ref = { "logistic_setter", "max", "slider" },
actions = {
on_value_changed = { gui = "request", action = "update_request" },
},
},
{
type = "slider",
style = "notched_slider",
style_mods = { horizontally_stretchable = true },
minimum_value = 0,
maximum_value = 500,
value_step = 50,
value = 500,
discrete_slider = true,
discrete_values = true,
tags = { bound = "min" },
ref = { "logistic_setter", "min", "slider" },
actions = {
on_value_changed = { gui = "request", action = "update_request" },
},
},
},
},
{
type = "textfield",
style = "slider_value_textfield",
numeric = true,
clear_and_focus_on_right_click = true,
text = constants.infinity_rep,
tags = { bound = "max" },
ref = { "logistic_setter", "max", "textfield" },
actions = {
on_confirmed = { gui = "request", action = "update_request" },
},
},
{
type = "sprite-button",
style = "item_and_count_select_confirm",
sprite = "utility/check_mark",
tooltip = { "", { "gui.qis-set-request" }, { "gui.qis-confirm" } },
ref = { "logistic_setter", "set_request_button" },
actions = {
on_click = { gui = "request", action = "set_request" },
},
},
{
type = "sprite-button",
style = "flib_tool_button_light_green",
style_mods = { top_margin = 1 },
sprite = "qis_temporary_request",
tooltip = { "", { "gui.qis-set-temporary-request" }, { "gui.qis-shift-confirm" } },
ref = { "logistic_setter", "set_temporary_request_button" },
actions = {
on_click = { gui = "request", action = "set_request", temporary = true },
},
},
{
type = "sprite-button",
style = "tool_button_red",
style_mods = { top_margin = 1 },
sprite = "utility/trash",
tooltip = { "", { "gui.qis-clear-request" }, { "gui.qis-control-confirm" } },
actions = {
on_click = { gui = "request", action = "clear_request" },
},
},
},
},
},
},
},
},
})
refs.window.force_auto_center()
refs.titlebar_flow.drag_target = refs.window
player_table.guis.request = {
refs = refs,
state = {
item_data = nil,
visible = false,
},
}
end
function logistic_request_gui.destroy(player_table)
local gui_data = player_table.guis.request
if not gui_data then
return
end
local window = gui_data.refs.window
if window and window.valid then
player_table.guis.request.refs.window.destroy()
end
player_table.guis.request = nil
end
function logistic_request_gui.open(player, player_table, item_data)
local gui_data = player_table.guis.request
local refs = gui_data.refs
local state = gui_data.state
-- update state
local stack_size = game.item_prototypes[item_data.name].stack_size
item_data.stack_size = stack_size
state.item_data = item_data
local request_data = item_data.request or { min = 0, max = math.max_uint }
state.request = request_data
state.visible = true
-- update item label
refs.item_label.caption = "[item=" .. item_data.name .. "] " .. item_data.translation
-- update logistic setter
local logistic_setter = refs.logistic_setter
for _, type in ipairs({ "min", "max" }) do
local elems = logistic_setter[type]
local count = request_data[type]
elems.textfield.enabled = true
if count == math.max_uint then
elems.textfield.text = constants.infinity_rep
else
elems.textfield.text = tostring(count)
end
elems.slider.enabled = true
elems.slider.set_slider_value_step(1)
elems.slider.set_slider_minimum_maximum(0, stack_size * 10)
elems.slider.set_slider_value_step(stack_size)
elems.slider.slider_value = math.round(count / stack_size) * stack_size
end
refs.logistic_setter.min.textfield.select_all()
refs.logistic_setter.min.textfield.focus()
-- update window
refs.focus_frame.visible = true
refs.focus_frame.bring_to_front()
refs.window.visible = true
refs.window.bring_to_front()
-- set opened
player.opened = refs.window
end
function logistic_request_gui.close(player, player_table)
local gui_data = player_table.guis.request
gui_data.state.visible = false
gui_data.refs.focus_frame.visible = false
gui_data.refs.window.visible = false
if not player.opened then
player.opened = player_table.guis.search.refs.window
end
end
function logistic_request_gui.update_focus_frame_size(player, player_table)
local gui_data = player_table.guis.request
if gui_data then
local resolution = player.display_resolution
local scale = player.display_scale
local size = { resolution.width / scale, resolution.height / scale }
gui_data.refs.focus_frame.style.size = size
end
end
function logistic_request_gui.set_request(player, player_table, is_temporary, skip_sound)
if not skip_sound then
player.play_sound({ path = "utility/confirm" })
end
local gui_data = player_table.guis.request
local refs = gui_data.refs
local state = gui_data.state
-- get the latest values from each textfield
logistic_request_gui.update_request(refs, state, refs.logistic_setter.min.textfield)
logistic_request_gui.update_request(refs, state, refs.logistic_setter.max.textfield)
-- set the request
logistic_request.set(player, player_table, state.item_data.name, state.request, is_temporary)
-- close this window
if is_temporary then
player.opened = nil
end
end
function logistic_request_gui.clear_request(player, player_table)
player.play_sound({ path = "utility/confirm" })
logistic_request.clear(player, player_table, player_table.guis.request.state.item_data.name)
player.opened = nil
end
function logistic_request_gui.update_request(refs, state, element)
local item_data = state.item_data
local request_data = state.request
local bound = gui.get_tags(element).bound
local elems = refs.logistic_setter[bound]
local count
if element.type == "textfield" then
count = tonumber(element.text)
if not count then
count = bound == "min" and 0 or math.max_uint
end
elems.slider.slider_value = math.round(count / item_data.stack_size) * item_data.stack_size
else
count = element.slider_value
local text
if bound == "max" and count == item_data.stack_size * 10 then
count = math.max_uint
text = constants.infinity_rep
else
text = tostring(count)
end
elems.textfield.text = text
end
request_data[bound] = count
-- sync border
if bound == "min" and count > request_data.max then
request_data.max = count
refs.logistic_setter.max.textfield.text = tostring(count)
refs.logistic_setter.max.slider.slider_value = math.round(count / item_data.stack_size) * item_data.stack_size
elseif bound == "max" and count < request_data.min then
request_data.min = count
refs.logistic_setter.min.textfield.text = tostring(count)
refs.logistic_setter.min.slider.slider_value = math.round(count / item_data.stack_size) * item_data.stack_size
end
-- switch textfield
if element.type == "textfield" then
if bound == "min" then
refs.logistic_setter.max.textfield.select_all()
refs.logistic_setter.max.textfield.focus()
else
refs.logistic_setter.min.textfield.select_all()
refs.logistic_setter.min.textfield.focus()
end
end
end
function logistic_request_gui.handle_action(e, msg)
local player = game.get_player(e.player_index)
local player_table = global.players[e.player_index]
local gui_data = player_table.guis.request
local refs = gui_data.refs
local state = gui_data.state
if msg.action == "close" then
logistic_request_gui.close(player, player_table)
elseif msg.action == "bring_to_front" then
refs.window.bring_to_front()
elseif msg.action == "recenter" and e.button == defines.mouse_button_type.middle then
refs.window.force_auto_center()
elseif msg.action == "update_request" then
logistic_request_gui.update_request(refs, state, e.element)
elseif msg.action == "clear_request" then
logistic_request.clear(player, player_table, state.item_data.name)
-- invoke `on_gui_closed` so the search GUI will be refocused
player.opened = nil
elseif msg.action == "set_request" then
-- HACK: Makes it easy for the search GUI to tell that this was confirmed
player_table.confirmed_tick = game.ticks_played
logistic_request_gui.set_request(player, player_table, msg.temporary, true)
-- invoke `on_gui_closed` if the above function did not
if not msg.temporary then
player.opened = nil
end
end
end
return logistic_request_gui

View File

@@ -0,0 +1,488 @@
local gui = require("__flib__/gui")
local math = require("__flib__/math")
local constants = require("__QuickItemSearch__/constants")
local cursor = require("__QuickItemSearch__/scripts/cursor")
local search = require("__QuickItemSearch__/scripts/search")
local infinity_filter_gui = require("__QuickItemSearch__/scripts/gui/infinity-filter")
local logistic_request_gui = require("__QuickItemSearch__/scripts/gui/logistic-request")
local search_gui = {}
function search_gui.build(player, player_table)
local refs = gui.build(player.gui.screen, {
{
type = "frame",
style = "qis_window_dimmer",
style_mods = { size = { 448, 390 } },
visible = false,
ref = { "window_dimmer" },
},
{
type = "frame",
name = "qis_search_window",
direction = "vertical",
visible = false,
ref = { "window" },
actions = {
on_closed = { gui = "search", action = "close" },
on_location_changed = { gui = "search", action = "update_dimmer_location" },
},
children = {
{
type = "flow",
style = "flib_titlebar_flow",
ref = { "titlebar_flow" },
actions = {
on_click = { gui = "search", action = "recenter" },
},
children = {
{
type = "label",
style = "frame_title",
caption = { "mod-name.QuickItemSearch" },
ignored_by_interaction = true,
},
{ type = "empty-widget", style = "flib_titlebar_drag_handle", ignored_by_interaction = true },
{
type = "sprite-button",
style = "frame_action_button",
sprite = "utility/close_white",
hovered_sprite = "utility/close_black",
clicked_sprite = "utility/close_black",
actions = {
on_click = { gui = "search", action = "close" },
},
},
},
},
{
type = "frame",
style = "inside_shallow_frame_with_padding",
style_mods = { top_padding = -2 },
direction = "vertical",
children = {
{
type = "textfield",
style = "qis_disablable_textfield",
style_mods = { width = 400, top_margin = 9 },
clear_and_focus_on_right_click = true,
lose_focus_on_confirm = true,
ref = { "search_textfield" },
actions = {
on_confirmed = { gui = "search", action = "enter_result_selection" },
on_text_changed = { gui = "search", action = "update_search_query" },
},
},
{
type = "frame",
style = "deep_frame_in_shallow_frame",
style_mods = { top_margin = 10, height = 28 * 10 },
direction = "vertical",
children = {
{
type = "frame",
style = "negative_subheader_frame",
style_mods = { left_padding = 12, height = 28, horizontally_stretchable = true },
visible = false,
ref = { "warning_subheader" },
children = {
{
type = "label",
style = "bold_label",
caption = {
"",
"[img=utility/warning_white] ",
{ "gui.qis-not-connected-to-logistic-network" },
},
},
},
},
{
type = "scroll-pane",
style = "qis_list_box_scroll_pane",
style_mods = { vertically_stretchable = true, bottom_padding = 2 },
ref = { "results_scroll_pane" },
children = {
{
type = "table",
style = "qis_list_box_table",
column_count = 3,
ref = { "results_table" },
children = {
-- dummy elements for the borked first row
{ type = "empty-widget" },
{ type = "empty-widget" },
{ type = "empty-widget" },
},
},
},
},
},
},
},
},
},
},
})
refs.window.force_auto_center()
refs.titlebar_flow.drag_target = refs.window
player_table.guis.search = {
refs = refs,
state = {
last_search_update = game.ticks_played,
query = "",
raw_query = "",
selected_index = 1,
subwindow_open = false,
visible = false,
},
}
end
function search_gui.destroy(player_table)
player_table.guis.search.refs.window.destroy()
player_table.guis.search = nil
end
function search_gui.open(player, player_table)
local gui_data = player_table.guis.search
gui_data.refs.window.visible = true
gui_data.state.visible = true
player.set_shortcut_toggled("qis-search", true)
player.opened = gui_data.refs.window
gui_data.refs.search_textfield.focus()
gui_data.refs.search_textfield.select_all()
-- update the table right away
search_gui.perform_search(player, player_table)
global.update_search_results[player.index] = true
end
function search_gui.close(player, player_table, force_close)
local gui_data = player_table.guis.search
local refs = gui_data.refs
local state = gui_data.state
if not force_close and state.selected_item_tick == game.ticks_played then
player.opened = refs.window
elseif force_close or not state.subwindow_open then
refs.window.visible = false
state.visible = false
player.set_shortcut_toggled("qis-search", false)
if player.opened == refs.window then
player.opened = nil
end
end
global.update_search_results[player.index] = nil
end
function search_gui.toggle(player, player_table, force_open)
local gui_data = player_table.guis.search
if not gui_data then
return
end
if gui_data.state.visible then
search_gui.close(player, player_table)
elseif force_open or player.opened_gui_type and player.opened_gui_type == defines.gui_type.none then
search_gui.open(player, player_table)
end
end
function search_gui.reopen_after_subwindow(e)
local player = game.get_player(e.player_index)
local player_table = global.players[e.player_index]
local gui_data = player_table.guis.search
if gui_data then
local refs = gui_data.refs
local state = gui_data.state
refs.search_textfield.enabled = true
refs.window_dimmer.visible = false
state.subwindow_open = false
search_gui.perform_search(player, player_table)
if player_table.settings.auto_close and player_table.confirmed_tick == game.ticks_played then
search_gui.close(player, player_table)
else
player.opened = gui_data.refs.window
end
global.update_search_results[player.index] = true
end
end
function search_gui.perform_search(player, player_table, updated_query, combined_contents)
local gui_data = player_table.guis.search
local refs = gui_data.refs
local state = gui_data.state
state.last_search_update = game.ticks_played
local query = string.lower(state.query)
local results_table = refs.results_table
local children = results_table.children
-- deselect highlighted entry
if updated_query and #results_table.children > 3 then
results_table.children[state.selected_index * 3 + 1].style.font_color = constants.colors.normal
refs.results_scroll_pane.scroll_to_top()
-- reset selected index
state.selected_index = 1
end
local result_tooltip = {
"",
{ "gui.qis-result-click-tooltip" },
"\n",
{ "gui.qis-shift-click" },
" ",
(player.controller_type == defines.controllers.character and { "gui.qis-edit-logistic-request" } or {
"gui.qis-edit-infinity-filter",
}),
}
if #state.raw_query > 1 then
local i = 0
local results, connected_to_network, logistic_requests_available = search.run(
player,
player_table,
query,
combined_contents
)
for _, row in ipairs(results) do
i = i + 1
local i3 = i * 3
-- build row if nonexistent
if not results_table.children[i3 + 1] then
gui.build(results_table, {
{
type = "label",
style = "qis_clickable_item_label",
actions = {
on_click = { gui = "search", action = "select_item", index = i },
},
},
{ type = "label" },
{ type = "label" },
})
-- update our copy of the table
children = results_table.children
end
-- item label
local item_label = children[i3 + 1]
local hidden_abbrev = row.hidden and "[font=default-semibold](H)[/font] " or ""
item_label.caption = hidden_abbrev .. "[item=" .. row.name .. "] " .. row.translation
item_label.tooltip = result_tooltip
-- item counts
if player.controller_type == defines.controllers.character and connected_to_network then
children[i3 + 2].caption = (
(row.inventory or 0)
.. " / [color="
.. constants.colors.logistic_str
.. "]"
.. (row.logistic or 0)
.. "[/color]"
)
else
children[i3 + 2].caption = (row.inventory or 0)
end
-- request / infinity filter
local request_label = children[i3 + 3]
if player.controller_type == defines.controllers.editor then
local filter = row.infinity_filter
if filter then
request_label.caption = constants.infinity_filter_mode_to_symbol[filter.mode] .. " " .. filter.count
else
request_label.caption = "--"
end
else
if logistic_requests_available then
local request = row.request
if request then
local max = request.max
if max == math.max_uint then
max = constants.infinity_rep
end
request_label.caption = request.min .. " / " .. max
if request.is_temporary then
request_label.caption = "(T) " .. request_label.caption
end
request_label.style.font_color = constants.colors[row.request_color or "normal"]
else
request_label.caption = "--"
request_label.style.font_color = constants.colors.normal
end
else
request_label.caption = ""
end
end
end
-- destroy extraneous rows
for j = #results_table.children, ((i + 1) * 3) + 1, -1 do
results_table.children[j].destroy()
end
-- show or hide warning
if
logistic_requests_available
and player.controller_type == defines.controllers.character
and not connected_to_network
then
refs.warning_subheader.visible = true
else
refs.warning_subheader.visible = false
end
if
player.controller_type == defines.controllers.god
or (player.controller_type == defines.controllers.character and not logistic_requests_available)
then
results_table.style.right_margin = -15
else
results_table.style.right_margin = 0
end
-- add to state
state.results = results
-- clear table if it has contents
elseif #results_table.children > 3 then
-- clear results
results_table.clear()
state.results = {}
-- add new dummy elements
for _ = 1, 3 do
results_table.add({ type = "empty-widget" })
end
end
end
function search_gui.select_item(player, player_table, modifiers, index)
local gui_data = player_table.guis.search
local refs = gui_data.refs
local state = gui_data.state
local i = index or state.selected_index
local results = state.results
if not results then
return
end
local result = state.results[i]
if not result then
return
end
if modifiers.shift then
local player_controller = player.controller_type
if player_controller == defines.controllers.editor or player_controller == defines.controllers.character then
state.subwindow_open = true
refs.search_textfield.enabled = false
refs.window_dimmer.visible = true
refs.window_dimmer.bring_to_front()
if player_controller == defines.controllers.editor then
infinity_filter_gui.open(player, player_table, result)
elseif player_controller == defines.controllers.character then
logistic_request_gui.open(player, player_table, result)
end
player.play_sound({ path = "utility/confirm" })
else
player.play_sound({ path = "utility/cannot_build" })
end
else
-- make sure we're not already holding this item
local cursor_stack = player.cursor_stack
if cursor_stack and cursor_stack.valid_for_read and cursor_stack.name == result.name then
player.play_sound({ path = "utility/cannot_build" })
player.create_local_flying_text({ text = { "message.qis-already-holding-item" }, create_at_cursor = true })
else
-- Close the window after selection if desired
if player_table.settings.auto_close then
search_gui.close(player, player_table, true)
-- Or prevent the window from closing
else
state.selected_item_tick = game.ticks_played
end
cursor.set_stack(player, player.cursor_stack, player_table, result.name)
player.play_sound({ path = "utility/confirm" })
end
end
end
function search_gui.update_for_active_players()
local tick = game.ticks_played
for player_index in pairs(global.update_search_results) do
local player = game.get_player(player_index)
local player_table = global.players[player_index]
local gui_data = player_table.guis.search
if gui_data then
local state = gui_data.state
if tick - state.last_search_update > 120 then
search_gui.perform_search(player, player_table)
end
end
end
end
function search_gui.handle_action(e, msg)
local player = game.get_player(e.player_index)
local player_table = global.players[e.player_index]
local gui_data = player_table.guis.search
local refs = gui_data.refs
local state = gui_data.state
if msg.action == "close" then
search_gui.close(player, player_table)
elseif msg.action == "recenter" and e.button == defines.mouse_button_type.middle then
refs.window.force_auto_center()
elseif msg.action == "update_search_query" then
local query = e.text
-- fuzzy search
if player_table.settings.fuzzy_search then
query = string.gsub(query, ".", "%1.*")
end
-- input sanitization
for pattern, replacement in pairs(constants.input_sanitizers) do
query = string.gsub(query, pattern, replacement)
end
state.query = query
state.raw_query = e.text
search_gui.perform_search(player, player_table, true)
elseif msg.action == "perform_search" then
-- perform search without updating query
search_gui.perform_search(player, player_table)
elseif msg.action == "enter_result_selection" then
if #refs.results_table.children == 3 then
refs.search_textfield.focus()
return
else
refs.results_scroll_pane.focus()
end
if state.selected_index > ((#refs.results_table.children - 3) / 3) then
state.selected_index = 1
end
local results_table = refs.results_table
results_table.children[state.selected_index * 3 + 1].style.font_color = constants.colors.hovered
elseif msg.action == "update_selected_index" then
local results_table = refs.results_table
local selected_index = state.selected_index
results_table.children[selected_index * 3 + 1].style.font_color = constants.colors.normal
local new_selected_index = math.clamp(selected_index + msg.offset, 1, #results_table.children / 3 - 1)
state.selected_index = new_selected_index
results_table.children[new_selected_index * 3 + 1].style.font_color = constants.colors.hovered
refs.results_scroll_pane.scroll_to_element(results_table.children[new_selected_index * 3 + 1], "top-third")
elseif msg.action == "select_item" then
search_gui.select_item(player, player_table, { shift = e.shift, control = e.control }, msg.index)
elseif msg.action == "update_dimmer_location" then
refs.window_dimmer.location = refs.window.location
end
end
return search_gui

View File

@@ -0,0 +1,107 @@
local table = require("__flib__/table")
local search = require("__QuickItemSearch__/scripts/search")
local infinity_filter = {}
function infinity_filter.set(player, player_table, filter, is_temporary)
local infinity_filters = player_table.infinity_filters
local filter_data = infinity_filters.by_name[filter.name]
local filter_index
if filter_data then
filter_index = filter_data.index
else
filter_index = #infinity_filters.by_index + 1
end
-- save previous request if this one is temporary
if is_temporary then
-- set to `false` if it needs to be cleared
infinity_filters.temporary[filter.name] = filter_data and table.deep_copy(filter_data) or false
else
-- delete temporary request for this item if there is one
infinity_filters.temporary[filter.name] = nil
end
-- set on player
player.set_infinity_inventory_filter(
filter_index,
{ name = filter.name, mode = filter.mode, count = filter.count, index = filter_index }
)
-- update stored requests
infinity_filter.update(player, player_table)
end
function infinity_filter.clear(player, player_table, name)
local infinity_filters = player_table.infinity_filters
local filter_data = infinity_filters.by_name[name]
if filter_data then
player.set_infinity_inventory_filter(filter_data.index, nil)
infinity_filter.update(player, player_table)
end
end
-- updates by_index and by_name without replacing everything
function infinity_filter.update(player, player_table)
local by_index = {}
local by_name = {}
for i, existing_filter in pairs(player.infinity_inventory_filters) do
by_index[i] = existing_filter
by_name[existing_filter.name] = existing_filter
end
player_table.infinity_filters.by_index = by_index
player_table.infinity_filters.by_name = by_name
end
function infinity_filter.refresh(player, player_table)
local infinity_filters = {
by_index = {},
by_name = {},
temporary = {},
}
for i, existing_filter in pairs(player.infinity_inventory_filters) do
infinity_filters.by_index[i] = existing_filter
infinity_filters.by_name[existing_filter.name] = existing_filter
end
-- preserve valid temporary filters
-- this shouldn't be needed in 99% of cases, as infinity filters are immediately satisfied
local item_prototypes = game.item_prototypes
for item_name, request in pairs(player_table.infinity_filters.temporary) do
if item_prototypes[item_name] then
infinity_filters.temporary[item_name] = request
end
end
player_table.infinity_filters = infinity_filters
end
function infinity_filter.update_temporaries(player, player_table)
local main_inventory = player.get_main_inventory()
if main_inventory and main_inventory.valid then
local infinity_filters = player_table.infinity_filters
for name, old_filter_data in pairs(infinity_filters.temporary) do
-- infinity filters are guaranteed to be fulfilled, so we can safely remove temporaries immediately
if old_filter_data then
infinity_filter.set(player, player_table, old_filter_data)
else
infinity_filter.clear(player, player_table, name)
end
end
infinity_filters.temporary = {}
end
end
function infinity_filter.quick_trash_all(player, player_table)
local main_inventory = player.get_main_inventory()
if main_inventory and main_inventory.valid then
local infinity_filters = player_table.infinity_filters
for name, count in pairs(search.get_combined_inventory_contents(player, main_inventory)) do
local existing_filter = infinity_filters.by_name[name] or { mode = "at-least", count = 0 }
if existing_filter.mode == "at-least" and count > existing_filter.count then
infinity_filter.set(player, player_table, { name = name, mode = "exactly", count = 0 }, true)
end
end
end
end
return infinity_filter

View File

@@ -0,0 +1,194 @@
local math = require("__flib__/math")
local table = require("__flib__/table")
local constants = require("__QuickItemSearch__/constants")
local search = require("__QuickItemSearch__/scripts/search")
local logistic_request = {}
function logistic_request.set(player, player_table, name, counts, is_temporary)
local requests = player_table.logistic_requests
local request_data = requests.by_name[name]
local request_index
if request_data then
request_index = request_data.index
else
request_data = { min = 0, max = math.max_uint }
-- search for first empty slot
local i = 1
while true do
local existing_request = player.get_personal_logistic_slot(i)
if existing_request.name then
i = i + 1
else
request_index = i
break
end
end
end
-- save previous request if this one is temporary
if is_temporary then
-- Do not overwrite previously stored request.
if not requests.temporary[name] then
requests.temporary[name] = table.deep_copy(request_data)
-- Store age of temporary request in order to allow player to
-- persist changes after temporary requests have been fulfilled.
requests.temporary[name].age = game.tick
end
else
-- delete temporary request for this item if there is one
requests.temporary[name] = nil
end
-- set on player
-- this will create or update the data in the requests table automatically
player.set_personal_logistic_slot(request_index, {
name = name,
min = counts.min,
max = counts.max,
})
end
function logistic_request.clear(player, player_table, name)
local requests = player_table.logistic_requests
if not requests then
return
end
local request_data = requests.by_name[name]
if request_data then
player.clear_personal_logistic_slot(request_data.index)
end
end
function logistic_request.update(player, player_table, slot_index)
local requests = player_table.logistic_requests
if not requests then
return
end
local existing_request = player.get_personal_logistic_slot(slot_index)
if existing_request then
local request_data = requests.by_index[slot_index]
if request_data then
if request_data.name == existing_request.name then
-- update counts
request_data.min = existing_request.min
request_data.max = existing_request.max
else
requests.by_name[request_data.name] = nil
if existing_request.name then
existing_request.index = slot_index
requests.by_index[slot_index] = existing_request
requests.by_name[existing_request.name] = existing_request
else
-- delete this request's data entirely
requests.by_index[slot_index] = nil
end
end
elseif existing_request.name then
existing_request.index = slot_index
requests.by_index[slot_index] = existing_request
requests.by_name[existing_request.name] = existing_request
end
-- Update previous request's quantities if affected. Allows player
-- to make changes to logistic requests that are affected by
-- temporary requests or quick-trashing, and still preserve those
-- (manual) changes after temporary requests are fullfilled.
if existing_request.name then
local temporary_request = requests.temporary[existing_request.name]
local current_age = game.tick
if temporary_request and temporary_request.age < current_age then
temporary_request.age = current_age
temporary_request.min = existing_request.min
temporary_request.max = existing_request.max
end
end
end
end
function logistic_request.refresh(player, player_table)
local requests = {
by_index = {},
by_name = {},
temporary = {},
}
local character = player.character
if character then
for i = 1, character.request_slot_count do
local filter = player.get_personal_logistic_slot(i)
if filter and filter.name then
filter.index = i
requests.by_index[i] = filter
requests.by_name[filter.name] = filter
end
end
end
-- preserve valid temporary requests
local item_prototypes = game.item_prototypes
for item_name, request in pairs(player_table.logistic_requests.temporary) do
if item_prototypes[item_name] then
requests.temporary[item_name] = request
end
end
player_table.logistic_requests = requests
end
function logistic_request.update_temporaries(player, player_table, combined_contents)
local requests = player_table.logistic_requests
if not requests then
return
end
local temporary_requests = requests.temporary
for name, old_request_data in pairs(temporary_requests) do
local existing_request_data = requests.by_name[name]
if existing_request_data then
local has_count = combined_contents[name] or 0
-- if the request has been satisfied
if has_count >= existing_request_data.min and has_count <= existing_request_data.max then
-- clear the temporary request data first to avoid setting the slot twice
temporary_requests[name] = nil
-- set the former request
player.set_personal_logistic_slot(existing_request_data.index, old_request_data)
end
else
temporary_requests[name] = nil
end
end
end
function logistic_request.quick_trash_all(player, player_table)
local main_inventory = player.get_main_inventory()
if not main_inventory or not main_inventory.valid then
return
end
local requests = player_table.logistic_requests
if not requests then
return
end
local prototypes = game.item_prototypes
for name, count in pairs(search.get_combined_inventory_contents(player, main_inventory)) do
if not constants.ignored_item_types[prototypes[name].type] then
local existing_request = requests.by_name[name]
if existing_request then
if count > existing_request.min then
logistic_request.set(
player,
player_table,
name,
{ min = existing_request.min, max = existing_request.min },
true
)
end
else
logistic_request.set(player, player_table, name, { min = 0, max = 0 }, true)
end
end
end
end
return logistic_request

View File

@@ -0,0 +1,29 @@
local global_data = require("__QuickItemSearch__/scripts/global-data")
local player_data = require("__QuickItemSearch__/scripts/player-data")
return {
["2.0.0"] = function()
-- NUKE EVERYTHING
global = {}
-- re-init
global_data.init()
for i in pairs(game.players) do
player_data.init(i)
end
end,
["2.1.5"] = function()
local current_age = game.tick
for player_index in pairs(game.players) do
for _, request in pairs(global.players[player_index].logistic_requests.temporary) do
if not request.age then
request.age = current_age
end
end
end
end,
["2.1.6"] = function()
if global.__flib then
global.__flib.translation = nil
end
end
}

View File

@@ -0,0 +1,62 @@
local constants = require("__QuickItemSearch__/constants")
local infinity_filter = require("__QuickItemSearch__/scripts/infinity-filter")
local logistic_request = require("__QuickItemSearch__/scripts/logistic-request")
local infinity_filter_gui = require("__QuickItemSearch__/scripts/gui/infinity-filter")
local logistic_request_gui = require("__QuickItemSearch__/scripts/gui/logistic-request")
local search_gui = require("__QuickItemSearch__/scripts/gui/search")
local player_data = {}
function player_data.init(player_index)
global.players[player_index] = {
flags = {
can_open_gui = false,
show_message_after_translation = false,
},
guis = {},
infinity_filters = { by_index = {}, by_name = {}, temporary = {} },
logistic_requests = { by_index = {}, by_name = {}, temporary = {} },
settings = {},
}
end
function player_data.refresh(player, player_table)
-- destroy GUIs
if player_table.guis.infinity_filter then
infinity_filter_gui.destroy(player_table)
end
if player_table.guis.request then
logistic_request_gui.destroy(player_table)
end
if player_table.guis.search then
search_gui.destroy(player_table)
end
-- set shortcut state
player.set_shortcut_toggled("qis-search", false)
player.set_shortcut_available("qis-search", false)
-- update settings
player_data.update_settings(player, player_table)
-- refresh requests or infinity filters
if player.controller_type == defines.controllers.editor then
infinity_filter.refresh(player, player_table)
elseif player.controller_type == defines.controllers.character then
logistic_request.refresh(player, player_table)
end
end
function player_data.update_settings(player, player_table)
local player_settings = player.mod_settings
local settings = {}
for internal, prototype in pairs(constants.settings) do
settings[internal] = player_settings[prototype].value
end
player_table.settings = settings
end
return player_data

View File

@@ -0,0 +1,145 @@
local dictionary = require("__flib__/dictionary-lite")
local constants = require("__QuickItemSearch__/constants")
local search = {}
function search.run(player, player_table, query, combined_contents)
-- don't bother if they don't have a main inventory
local main_inventory = player.get_main_inventory()
if main_inventory and main_inventory.valid then
local requests = player_table.logistic_requests
local requests_by_name = requests.by_name
local filters = player_table.infinity_filters
local filters_by_name = filters.by_name
local settings = player_table.settings
local translations = dictionary.get(player.index, "item")
local item_prototypes = game.item_prototypes
local character = player.character
-- settings
local show_hidden = settings.show_hidden
local connected_to_network = false
local logistic_requests_available = false
local results = {}
-- get contents of all player inventories and cursor stack
-- in some cases, this is passed in externally to save performance
combined_contents = combined_contents or search.get_combined_inventory_contents(player, main_inventory)
-- don't bother doing anything if they don't have an inventory
local contents = {
inbound = {},
inventory = combined_contents,
logistic = {},
outbound = {},
}
local controller_type = player.controller_type
-- get logistic network and related contents
if character and character.valid then
logistic_requests_available = player.force.character_logistic_requests
for _, data in ipairs(constants.logistic_point_data) do
local point = character.get_logistic_point(data.logistic_point)
if point and point.valid then
contents[data.deliveries_table] = point[data.source_table]
if data.point_name == "requester" then
local logistic_network = point.logistic_network
if logistic_network.valid then
connected_to_network = true
contents.logistic = logistic_network.get_contents()
end
end
end
end
end
-- perform search
local i = 0
for name, translation in pairs(translations) do
if string.find(string.lower(translation), query) then
local hidden = item_prototypes[name].has_flag("hidden")
if show_hidden or not hidden then
local inventory_count = contents.inventory[name]
local logistic_count = contents.logistic[name]
local result = {
hidden = hidden,
inventory = inventory_count,
logistic = logistic_count and math.max(logistic_count, 0) or nil,
name = name,
translation = translation,
}
if controller_type == defines.controllers.character then
-- add logistic request, if one exists
local request = requests_by_name[name]
if request then
result.request = { min = request.min, max = request.max }
if requests.temporary[name] then
result.request.is_temporary = true
end
end
-- determine logistic request color
local color
if contents.inbound[name] then
color = "inbound"
elseif contents.outbound[name] then
color = "outbound"
elseif request and (inventory_count or 0) < request.min then
color = "unsatisfied"
else
color = "normal"
end
result.request_color = color
elseif controller_type == defines.controllers.editor then
-- add infinity filter, if one exists
local filter = filters_by_name[name]
if filter then
result.infinity_filter = { mode = filter.mode, count = filter.count }
end
end
i = i + 1
results[i] = result
end
end
if i > constants.results_limit then
break
end
end
return results, connected_to_network, logistic_requests_available
end
return {}, false, false
end
function search.get_combined_inventory_contents(player, main_inventory)
-- main inventory contents
local combined_contents = main_inventory.get_contents()
-- cursor stack
local cursor_stack = player.cursor_stack
if cursor_stack and cursor_stack.valid_for_read then
combined_contents[cursor_stack.name] = (combined_contents[cursor_stack.name] or 0) + cursor_stack.count
end
-- other
for _, inventory_def in ipairs({
-- for some reason, the character_ammo and character_guns inventories work in the editor as well
defines.inventory.character_ammo,
defines.inventory.character_guns,
-- defines.inventory.character_trash
}) do
local inventory = player.get_inventory(inventory_def)
if inventory and inventory.valid then
for name, count in pairs(inventory.get_contents() or {}) do
combined_contents[name] = (combined_contents[name] or 0) + count
end
end
end
return combined_contents, true
end
return search