From 9de913536ad75a2e48a3999093b63b636254c451 Mon Sep 17 00:00:00 2001 From: monica Date: Wed, 11 Jan 2023 09:45:11 -0500 Subject: [PATCH] save for 1.2.10 --- TODO | 1 + cybersyn/scripts/factorio-api.lua | 8 + cybersyn/scripts/global.lua | 1 + cybersyn/scripts/gui/main.lua | 41 ++++ cybersyn/scripts/gui/stations.lua | 327 +++++++++++++++++++++--------- cybersyn/scripts/lib.lua | 55 ++--- 6 files changed, 303 insertions(+), 130 deletions(-) diff --git a/TODO b/TODO index b3633c0..6cb2eee 100644 --- a/TODO +++ b/TODO @@ -19,6 +19,7 @@ major: dramatically improve the mods handling of invalid entities minor: + break the central planner into a separate library check if necessary entities can be destroyed without raising events improve the behavior of trains if players intervene during deliveries handle if signals are removed from the game during migration diff --git a/cybersyn/scripts/factorio-api.lua b/cybersyn/scripts/factorio-api.lua index d5b2c24..3706f11 100644 --- a/cybersyn/scripts/factorio-api.lua +++ b/cybersyn/scripts/factorio-api.lua @@ -15,6 +15,14 @@ function get_stack_size(map_data, item_name) return game.item_prototypes[item_name].stack_size end +---@param item_order table +---@param item1_name string +---@param item2_name string +function item_lt(item_order, item1_name, item2_name) + return item_order[item1_name] < item_order[item2_name] +end + + ---NOTE: does not check .valid ---@param entity0 LuaEntity ---@param entity1 LuaEntity diff --git a/cybersyn/scripts/global.lua b/cybersyn/scripts/global.lua index c2557c0..d0180fa 100644 --- a/cybersyn/scripts/global.lua +++ b/cybersyn/scripts/global.lua @@ -23,6 +23,7 @@ ---@field public economy Economy ---@field public each_refuelers {[uint]: true} ---@field public active_alerts {[uint]: {[1]: LuaTrain, [2]: int}}? +---@field public manager Manager ---@class Station ---@field public entity_stop LuaEntity diff --git a/cybersyn/scripts/gui/main.lua b/cybersyn/scripts/gui/main.lua index fc72dcd..5a42e5b 100644 --- a/cybersyn/scripts/gui/main.lua +++ b/cybersyn/scripts/gui/main.lua @@ -3,6 +3,8 @@ local mod_gui = require("__core__.lualib.mod-gui") local manager = require("scripts.gui.manager") +--- @class Manager +--- @field item_order table --- @class PlayerData --- @field refs {[string]: LuaGuiElement}? @@ -88,6 +90,45 @@ function manager_gui.on_runtime_mod_setting_changed(e) end +--- @param manager Manager +local function init_items(manager) + local item_order = {} + manager.item_order = item_order + local i = 1 + + for _, protos in pairs{game.item_prototypes, game.fluid_prototypes} do + --- @type (LuaItemPrototype|LuaFluidPrototype)[] + local all_items = {} + for _, proto in pairs(protos) do + all_items[#all_items + 1] = proto + end + table.sort(all_items, function(a, b) + if a.group.order == b.group.order then + if a.subgroup.order == b.subgroup.order then + return a.order < b.order + else + return a.subgroup.order < b.subgroup.order + end + else + return a.group.order < b.group.order + end + end) + for _, v in ipairs(all_items) do + item_order[v.name] = i + i = i + 1 + end + end +end + + +function manager.on_migration() + init_items(global.manager) +end + +function manager.on_init() + global.manager = {} + init_items(global.manager) +end --gui.handle_events() return manager_gui diff --git a/cybersyn/scripts/gui/stations.lua b/cybersyn/scripts/gui/stations.lua index dacc567..e7ab746 100644 --- a/cybersyn/scripts/gui/stations.lua +++ b/cybersyn/scripts/gui/stations.lua @@ -30,36 +30,179 @@ function stations_tab.build(widths) templates.sort_checkbox(widths, "stations", "status", false, { "gui.ltnm-status-description" }), templates.sort_checkbox(widths, "stations", "network_id", false), templates.sort_checkbox( - widths, - "stations", - "provided_requested", - false, - { "gui.ltnm-provided-requested-description" } - ), - templates.sort_checkbox(widths, "stations", "shipments", false, { "gui.ltnm-shipments-description" }), - templates.sort_checkbox(widths, "stations", "control_signals", false), - }, - { type = "scroll-pane", style = "ltnm_table_scroll_pane", ref = { "stations", "scroll_pane" } }, + widths, + "stations", + "provided_requested", + false, + { "gui.ltnm-provided-requested-description" } + ), + templates.sort_checkbox(widths, "stations", "shipments", false, { "gui.ltnm-shipments-description" }), + templates.sort_checkbox(widths, "stations", "control_signals", false), + }, + { type = "scroll-pane", style = "ltnm_table_scroll_pane", ref = { "stations", "scroll_pane" } }, + { + type = "flow", + style = "ltnm_warning_flow", + visible = false, + ref = { "stations", "warning_flow" }, { - type = "flow", - style = "ltnm_warning_flow", - visible = false, - ref = { "stations", "warning_flow" }, - { - type = "label", - style = "ltnm_semibold_label", - caption = { "gui.ltnm-no-stations" }, - ref = { "stations", "warning_label" }, - }, + type = "label", + style = "ltnm_semibold_label", + caption = { "gui.ltnm-no-stations" }, + ref = { "stations", "warning_label" }, }, }, - } + }, +} +end + +--- @param map_data MapData +--- @param player_data PlayerData +--- @return GuiElemDef +function stations_tab.build(map_data, player_data) + + local widths = constants.gui["en"] + + local search_item = player_data.search_item + local search_network_name = player_data.search_network_name + local search_network_mask = player_data.search_network_mask + local search_surface_idx = player_data.search_surface_idx + + + local stations_sorted = {} + local to_sorted_manifest = {} + for id, station in pairs(map_data.stations) do + if search_network_name then + if search_network_name ~= station.network_name then + goto continue + end + local train_flag = get_network_flag(station, search_network_name) + if not bit32.btest(search_network_mask, train_flag) then + goto continue + end + elseif search_network_mask ~= -1 then + if station.network_name == NETWORK_EACH then + local masks = station.network_flag--[[@as {}]] + for _, network_flag in pairs(masks) do + if bit32.btest(search_network_mask, network_flag) then + goto has_match + end + end + goto continue + ::has_match:: + elseif not bit32.btest(search_network_mask, station.network_flag) then + goto continue + end + end + + if search_surface_idx then + local entity = station.entity_stop + if not entity.valid then + goto continue + end + if entity.surface.index ~= search_surface_idx then + goto continue + end + end + + if search_item then + if not station.deliveries then + goto continue + end + for item_name, _ in pairs(station.deliveries) do + if item_name == search_item then + goto has_match + end + end + goto continue + ::has_match:: + end + + stations_sorted[#stations_sorted + 1] = id + --insertion sort + local manifest = {} + local manifest_type = {} + for name, _ in pairs(station.deliveries) do + local is_fluid = get_is_fluid(name) + local i = 1 + while i <= #manifest do + if (not is_fluid and manifest_type[i]) or (is_fluid == manifest_type[i] and name < manifest[i]) then + break + end + i = i + 1 + end + table.insert(manifest, i, name) + table.insert(manifest_type, i, is_fluid) + end + to_sorted_manifest[id] = manifest + ::continue:: + end + + + table.sort(stations_sorted, function(a, b) + local station1 = map_data.stations[a] + local station2 = map_data.stations[b] + for i, v in ipairs(player_data.trains_orderings) do + local invert = player_data.trains_orderings_invert[i] + if v == ORDER_LAYOUT then + if not station1.allows_all_trains and not station2.allows_all_trains then + local layout1 = station1.layout_pattern--[[@as uint[] ]] + local layout2 = station2.layout_pattern--[[@as uint[] ]] + for j, c1 in ipairs(layout1) do + local c2 = layout2[j] + if c1 ~= c2 then + return invert ~= (c2 and c1 < c2) + end + end + if layout2[#layout1 + 1] then + return invert ~= true + end + elseif station1.allows_all_trains ~= station2.allows_all_trains then + return invert ~= station2.allows_all_trains + end + elseif v == ORDER_NAME then + local name1 = station1.entity_stop.valid and station1.entity_stop.backer_name + local name2 = station2.entity_stop.valid and station2.entity_stop.backer_name + if name1 ~= name2 then + return invert ~= (name1 and (name2 and name1 < name2 or true) or false) + end + elseif v == ORDER_TOTAL_TRAINS then + if station1.deliveries_total ~= station2.deliveries_total then + return invert ~= (station1.deliveries_total < station2.deliveries_total) + end + elseif v == ORDER_MANIFEST then + if not next(station1.deliveries) then + if next(station2.deliveries) then + return invert ~= true + end + elseif not next(station2.deliveries) then + return invert ~= false + else + local first_item = nil + local first_direction = nil + for item_name in dual_pairs(station1.deliveries, station2.deliveries) do + if not first_item or item_lt(map_data.manager, item_name, first_item) then + local count1 = station1.deliveries[item_name] or 0 + local count2 = station2.deliveries[item_name] or 0 + if count1 ~= count2 then + first_item = item_name + first_direction = count1 < count2 + end + end + end + if first_direction ~= nil then + return invert ~= first_direction + end + end + end + end + return (not player_data.trains_orderings_invert[#player_data.trains_orderings_invert]) == (a < b) + end) + + end function stations_tab.update(self) - local dictionaries = self.player_table.dictionaries - - local state = self.state local refs = self.refs.stations local widths = self.widths.stations @@ -95,80 +238,80 @@ function stations_tab.update(self) if station_data.entity.valid then if - (search_surface == -1 or station_data.entity.surface.index == search_surface) - and bit32.btest(station_data.network_id, search_network_id) - and ( - #search_query == 0 or string.find(station_data.search_strings[self.player.index], string.lower(search_query)) - ) - then - table_index = table_index + 1 - local row = children[table_index] - local color = table_index % 2 == 0 and "dark" or "light" - if not row then - row = gui.add(scroll_pane, { - type = "frame", - style = "ltnm_table_row_frame_" .. color, - { - type = "label", - style = "ltnm_clickable_semibold_label", - style_mods = { width = widths.name }, - tooltip = constants.open_station_gui_tooltip, - }, - templates.status_indicator(widths.status, true), - { type = "label", style_mods = { width = widths.network_id, horizontal_align = "center" } }, - templates.small_slot_table(widths, color, "provided_requested"), - templates.small_slot_table(widths, color, "shipments"), - templates.small_slot_table(widths, color, "control_signals"), - }) - end - - gui.update(row, { + (search_surface == -1 or station_data.entity.surface.index == search_surface) + and bit32.btest(station_data.network_id, search_network_id) + and ( + #search_query == 0 or string.find(station_data.search_strings[self.player.index], string.lower(search_query)) + ) + then + table_index = table_index + 1 + local row = children[table_index] + local color = table_index % 2 == 0 and "dark" or "light" + if not row then + row = gui.add(scroll_pane, { + type = "frame", + style = "ltnm_table_row_frame_" .. color, { - elem_mods = { caption = station_data.name }, - actions = { - on_click = { gui = "main", action = "open_station_gui", station_id = station_id }, - }, - }, - { - { elem_mods = { sprite = "flib_indicator_" .. station_data.status.color } }, - { elem_mods = { caption = station_data.status.count } }, - }, - { elem_mods = { caption = station_data.network_id } }, - }) - - util.slot_table_update(row.provided_requested_frame.provided_requested_table, { - { color = "green", entries = station_data.provided, translations = dictionaries.materials }, - { color = "red", entries = station_data.requested, translations = dictionaries.materials }, - }) - util.slot_table_update(row.shipments_frame.shipments_table, { - { color = "green", entries = station_data.inbound, translations = dictionaries.materials }, - { color = "blue", entries = station_data.outbound, translations = dictionaries.materials }, - }) - util.slot_table_update(row.control_signals_frame.control_signals_table, { - { - color = "default", - entries = station_data.control_signals, - translations = dictionaries.virtual_signals, - type = "virtual-signal", + type = "label", + style = "ltnm_clickable_semibold_label", + style_mods = { width = widths.name }, + tooltip = constants.open_station_gui_tooltip, }, + templates.status_indicator(widths.status, true), + { type = "label", style_mods = { width = widths.network_id, horizontal_align = "center" } }, + templates.small_slot_table(widths, color, "provided_requested"), + templates.small_slot_table(widths, color, "shipments"), + templates.small_slot_table(widths, color, "control_signals"), }) end + + gui.update(row, { + { + elem_mods = { caption = station_data.name }, + actions = { + on_click = { gui = "main", action = "open_station_gui", station_id = station_id }, + }, + }, + { + { elem_mods = { sprite = "flib_indicator_" .. station_data.status.color } }, + { elem_mods = { caption = station_data.status.count } }, + }, + { elem_mods = { caption = station_data.network_id } }, + }) + + util.slot_table_update(row.provided_requested_frame.provided_requested_table, { + { color = "green", entries = station_data.provided, translations = dictionaries.materials }, + { color = "red", entries = station_data.requested, translations = dictionaries.materials }, + }) + util.slot_table_update(row.shipments_frame.shipments_table, { + { color = "green", entries = station_data.inbound, translations = dictionaries.materials }, + { color = "blue", entries = station_data.outbound, translations = dictionaries.materials }, + }) + util.slot_table_update(row.control_signals_frame.control_signals_table, { + { + color = "default", + entries = station_data.control_signals, + translations = dictionaries.virtual_signals, + type = "virtual-signal", + }, + }) end end - - for child_index = table_index + 1, #children do - children[child_index].destroy() - end - - if table_index == 0 then - refs.warning_flow.visible = true - scroll_pane.visible = false - refs.content_frame.style = "ltnm_main_warning_frame" - else - refs.warning_flow.visible = false - scroll_pane.visible = true - refs.content_frame.style = "ltnm_main_content_frame" - end +end + +for child_index = table_index + 1, #children do + children[child_index].destroy() +end + +if table_index == 0 then + refs.warning_flow.visible = true + scroll_pane.visible = false + refs.content_frame.style = "ltnm_main_warning_frame" +else + refs.warning_flow.visible = false + scroll_pane.visible = true + refs.content_frame.style = "ltnm_main_content_frame" +end end return stations_tab diff --git a/cybersyn/scripts/lib.lua b/cybersyn/scripts/lib.lua index 95afb2f..e4bdaf4 100644 --- a/cybersyn/scripts/lib.lua +++ b/cybersyn/scripts/lib.lua @@ -50,45 +50,24 @@ function irpairs(a) return irnext, a, 0 end ----@generic V ----@param arr Array ----@param comp fun(a: V, b: V) A comparison function for sorting. Must return truthy if `a < b`. -function stable_sort(arr, comp) - local size = #arr - for i = 2, size do - local a = arr[i] - local j = i - while j > 1 do - local b = arr[j - 1] - if comp(a, b) then - arr[j] = b - j = j - 1 - else - break +--- @generic K +--- @param t1 table +--- @param t2 table +--- @return fun(): K? +function dual_pairs(t1, t2) + local state = true + local key = nil + return function() + if state then + key = next(t1, key) + if key then + return key end + state = false end - arr[j] = a - end -end - ----@param values number[] ----@param keys any[] -function dual_sort(values, keys) - local size = #values - for i = 2, size do - local a = values[i] - local j = i - while j > 1 do - local b = values[j - 1] - if a < b then - values[j] = b - keys[j] = keys[j - 1] - j = j - 1 - else - break - end - end - values[j] = a - keys[j] = keys[i] + repeat + key = next(t2, key) + until t1[key] == nil + return key end end