save for 1.2.10

This commit is contained in:
monica
2023-01-11 09:45:11 -05:00
parent de8d7381b3
commit 9de913536a
6 changed files with 303 additions and 130 deletions

1
TODO
View File

@@ -19,6 +19,7 @@ major:
dramatically improve the mods handling of invalid entities dramatically improve the mods handling of invalid entities
minor: minor:
break the central planner into a separate library
check if necessary entities can be destroyed without raising events check if necessary entities can be destroyed without raising events
improve the behavior of trains if players intervene during deliveries improve the behavior of trains if players intervene during deliveries
handle if signals are removed from the game during migration handle if signals are removed from the game during migration

View File

@@ -15,6 +15,14 @@ function get_stack_size(map_data, item_name)
return game.item_prototypes[item_name].stack_size return game.item_prototypes[item_name].stack_size
end end
---@param item_order table<string, int>
---@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 ---NOTE: does not check .valid
---@param entity0 LuaEntity ---@param entity0 LuaEntity
---@param entity1 LuaEntity ---@param entity1 LuaEntity

View File

@@ -23,6 +23,7 @@
---@field public economy Economy ---@field public economy Economy
---@field public each_refuelers {[uint]: true} ---@field public each_refuelers {[uint]: true}
---@field public active_alerts {[uint]: {[1]: LuaTrain, [2]: int}}? ---@field public active_alerts {[uint]: {[1]: LuaTrain, [2]: int}}?
---@field public manager Manager
---@class Station ---@class Station
---@field public entity_stop LuaEntity ---@field public entity_stop LuaEntity

View File

@@ -3,6 +3,8 @@ local mod_gui = require("__core__.lualib.mod-gui")
local manager = require("scripts.gui.manager") local manager = require("scripts.gui.manager")
--- @class Manager
--- @field item_order table<string, int>
--- @class PlayerData --- @class PlayerData
--- @field refs {[string]: LuaGuiElement}? --- @field refs {[string]: LuaGuiElement}?
@@ -88,6 +90,45 @@ function manager_gui.on_runtime_mod_setting_changed(e)
end 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() --gui.handle_events()
return manager_gui return manager_gui

View File

@@ -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", "status", false, { "gui.ltnm-status-description" }),
templates.sort_checkbox(widths, "stations", "network_id", false), templates.sort_checkbox(widths, "stations", "network_id", false),
templates.sort_checkbox( templates.sort_checkbox(
widths, widths,
"stations", "stations",
"provided_requested", "provided_requested",
false, false,
{ "gui.ltnm-provided-requested-description" } { "gui.ltnm-provided-requested-description" }
), ),
templates.sort_checkbox(widths, "stations", "shipments", false, { "gui.ltnm-shipments-description" }), templates.sort_checkbox(widths, "stations", "shipments", false, { "gui.ltnm-shipments-description" }),
templates.sort_checkbox(widths, "stations", "control_signals", false), templates.sort_checkbox(widths, "stations", "control_signals", false),
}, },
{ type = "scroll-pane", style = "ltnm_table_scroll_pane", ref = { "stations", "scroll_pane" } }, { 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", type = "label",
style = "ltnm_warning_flow", style = "ltnm_semibold_label",
visible = false, caption = { "gui.ltnm-no-stations" },
ref = { "stations", "warning_flow" }, 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 end
function stations_tab.update(self) function stations_tab.update(self)
local dictionaries = self.player_table.dictionaries
local state = self.state
local refs = self.refs.stations local refs = self.refs.stations
local widths = self.widths.stations local widths = self.widths.stations
@@ -95,80 +238,80 @@ function stations_tab.update(self)
if station_data.entity.valid then if station_data.entity.valid then
if if
(search_surface == -1 or station_data.entity.surface.index == search_surface) (search_surface == -1 or station_data.entity.surface.index == search_surface)
and bit32.btest(station_data.network_id, search_network_id) and bit32.btest(station_data.network_id, search_network_id)
and ( and (
#search_query == 0 or string.find(station_data.search_strings[self.player.index], string.lower(search_query)) #search_query == 0 or string.find(station_data.search_strings[self.player.index], string.lower(search_query))
) )
then then
table_index = table_index + 1 table_index = table_index + 1
local row = children[table_index] local row = children[table_index]
local color = table_index % 2 == 0 and "dark" or "light" local color = table_index % 2 == 0 and "dark" or "light"
if not row then if not row then
row = gui.add(scroll_pane, { row = gui.add(scroll_pane, {
type = "frame", type = "frame",
style = "ltnm_table_row_frame_" .. color, 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, {
{ {
elem_mods = { caption = station_data.name }, type = "label",
actions = { style = "ltnm_clickable_semibold_label",
on_click = { gui = "main", action = "open_station_gui", station_id = station_id }, style_mods = { width = widths.name },
}, tooltip = constants.open_station_gui_tooltip,
},
{
{ 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",
}, },
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 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
end end
end
for child_index = table_index + 1, #children do
children[child_index].destroy() for child_index = table_index + 1, #children do
end children[child_index].destroy()
end
if table_index == 0 then
refs.warning_flow.visible = true if table_index == 0 then
scroll_pane.visible = false refs.warning_flow.visible = true
refs.content_frame.style = "ltnm_main_warning_frame" scroll_pane.visible = false
else refs.content_frame.style = "ltnm_main_warning_frame"
refs.warning_flow.visible = false else
scroll_pane.visible = true refs.warning_flow.visible = false
refs.content_frame.style = "ltnm_main_content_frame" scroll_pane.visible = true
end refs.content_frame.style = "ltnm_main_content_frame"
end
end end
return stations_tab return stations_tab

View File

@@ -50,45 +50,24 @@ function irpairs(a)
return irnext, a, 0 return irnext, a, 0
end end
---@generic V --- @generic K
---@param arr Array<V> --- @param t1 table<K, any>
---@param comp fun(a: V, b: V) A comparison function for sorting. Must return truthy if `a < b`. --- @param t2 table<K, any>
function stable_sort(arr, comp) --- @return fun(): K?
local size = #arr function dual_pairs(t1, t2)
for i = 2, size do local state = true
local a = arr[i] local key = nil
local j = i return function()
while j > 1 do if state then
local b = arr[j - 1] key = next(t1, key)
if comp(a, b) then if key then
arr[j] = b return key
j = j - 1
else
break
end end
state = false
end end
arr[j] = a repeat
end key = next(t2, key)
end until t1[key] == nil
return key
---@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]
end end
end end