Merge pull request #33 from mamoniot/experimental

1.2.10
This commit is contained in:
Monica Moniot
2023-01-11 09:52:45 -05:00
committed by GitHub
14 changed files with 731 additions and 386 deletions

2
TODO
View File

@@ -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
@@ -35,3 +36,4 @@ compat:
railloader
picker dollies?
cargo ships
editor extensions (test surface separation)

View File

@@ -1,15 +1,18 @@
---------------------------------------------------------------------------------------------------
Version: 1.2.10
Date: 2022-1-9
Date: 2023-1-9
Changes:
- Made the automatic allow list slightly more forgiving to stations where the last wagon would be on a curved rail
- Improved performance when fuel threshold is set to 1
- Prioritized distance from provider to requester over distance from train to provider
Bugfixes:
- Fixed a bug where it was possible for a single station to be updated twice per dispatch cycle, which could cause a crash
- Fixed a crash where trains would sometimes think a destroyed depot still exists
- Fixed a case where the central planner generated a confusing alert
- Removed unfinished mod setting with the broken translation key
---------------------------------------------------------------------------------------------------
Version: 1.2.9
Date: 2022-1-7
Date: 2023-1-7
Bugfixes:
- Fixed a bug with deliveries sometimes failing to be removed
- Fixed several rare crashes
@@ -17,7 +20,7 @@ Date: 2022-1-7
- Updated flib dependency to the correct version
---------------------------------------------------------------------------------------------------
Version: 1.2.8
Date: 2022-1-5
Date: 2023-1-5
Features:
- Improved placeholder cybernetic combinator art
- Added a wagon control setting to bar unfiltered slots in adjacent cargo wagons
@@ -33,7 +36,7 @@ Date: 2022-1-5
- Migrated to non-deprecated flib modules
---------------------------------------------------------------------------------------------------
Version: 1.2.7
Date: 2022-1-1
Date: 2023-1-1
Bugfixes:
- Fixed a bug with items attempting to be loaded into fluid wagons
---------------------------------------------------------------------------------------------------

View File

@@ -20,5 +20,18 @@ data:extend({
locked_slots_signal,
missing_train_icon,
lost_train_icon,
nonempty_train_icon
nonempty_train_icon,
--{
-- type = "shortcut",
-- name = "ltnm-toggle-gui",
-- icon = data_util.build_sprite(nil, { 0, 0 }, util.paths.shortcut_icons, 32, 2),
-- disabled_icon = data_util.build_sprite(nil, { 48, 0 }, util.paths.shortcut_icons, 32, 2),
-- small_icon = data_util.build_sprite(nil, { 0, 32 }, util.paths.shortcut_icons, 24, 2),
-- disabled_small_icon = data_util.build_sprite(nil, { 36, 32 }, util.paths.shortcut_icons, 24, 2),
-- toggleable = true,
-- action = "lua",
-- associated_control_input = "ltnm-toggle-gui",
-- technology_to_unlock = "logistic-train-network",
--},
})

View File

@@ -349,7 +349,7 @@ local function tick_dispatch(map_data, mod_settings)
---@type uint
local j = 1
while j <= #p_stations do
local p_flag, r_flag, netand, best_p_train_id, best_t_prior, best_capacity, best_t_to_p_dist, effective_count, override_threshold, p_prior, best_p_dist
local p_flag, r_flag, netand, best_p_train_id, best_t_prior, best_capacity, best_t_to_p_dist, effective_count, override_threshold, p_prior, best_p_to_r_dist
local p_station_id = p_stations[j]
local p_station = stations[p_station_id]
@@ -363,6 +363,35 @@ local function tick_dispatch(map_data, mod_settings)
if netand == 0 then
goto p_continue
end
effective_count = p_station.item_p_counts[item_name]
override_threshold = p_station.item_thresholds and p_station.item_thresholds[item_name]
if override_threshold and p_station.is_stack and not is_fluid then
override_threshold = override_threshold*get_stack_size(map_data, item_name)
end
if effective_count < (override_threshold or r_threshold) then
--this p station should have serviced the current r station, lock it so it can't serve any others
--this will lock stations even when the r station manages to find a p station, this not a problem because all stations will be unlocked before it could be an issue
table_remove(p_stations, j)
if band(p_station.display_state, 4) == 0 then
p_station.display_state = p_station.display_state + 4
update_display(map_data, p_station)
end
goto p_continue_remove
end
p_prior = p_station.priority
if override_threshold and p_station.item_priority then
p_prior = p_station.item_priority--[[@as int]]
end
if p_prior < best_p_prior then
goto p_continue
end
best_p_to_r_dist = p_station.entity_stop.valid and r_station.entity_stop.valid and get_dist(p_station.entity_stop, r_station.entity_stop) or INF
if p_prior == best_p_prior and best_p_to_r_dist > best_dist then
goto p_continue
end
if correctness < 1 then
correctness = 1
closest_to_correct_p_station = p_station
@@ -442,39 +471,10 @@ local function tick_dispatch(map_data, mod_settings)
goto p_continue
end
effective_count = p_station.item_p_counts[item_name]
override_threshold = p_station.item_thresholds and p_station.item_thresholds[item_name]
if override_threshold and p_station.is_stack and not is_fluid then
override_threshold = override_threshold*get_stack_size(map_data, item_name)
end
if effective_count < (override_threshold or r_threshold) then
--this p station should have serviced the current r station, lock it so it can't serve any others
--this will lock stations even when the r station manages to find a p station, this not a problem because all stations will be unlocked before it could be an issue
table_remove(p_stations, j)
if band(p_station.display_state, 4) == 0 then
p_station.display_state = p_station.display_state + 4
update_display(map_data, p_station)
end
goto p_continue_remove
end
p_prior = p_station.priority
if override_threshold and p_station.item_priority then
p_prior = p_station.item_priority--[[@as int]]
end
if p_prior < best_p_prior then
goto p_continue
end
best_p_dist = p_station.entity_stop.valid and r_station.entity_stop.valid and (best_t_to_p_dist + get_dist(p_station.entity_stop, r_station.entity_stop)) or INF
if p_prior == best_p_prior and best_p_dist > best_dist then
goto p_continue
end
p_station_i = j
best_train_id = best_p_train_id
best_p_prior = p_prior
best_dist = best_p_dist
best_dist = best_p_to_r_dist
::p_continue::
j = j + 1
::p_continue_remove::
@@ -725,9 +725,8 @@ function tick_init(map_data, mod_settings)
map_data.economy.all_r_stations = {}
map_data.economy.all_names = {}
local i = 1
while i <= #map_data.warmup_station_ids do
local id = map_data.warmup_station_ids[i]
while #map_data.warmup_station_ids > 0 do
local id = map_data.warmup_station_ids[1]
local station = map_data.stations[id]
if station then
local cycles = map_data.warmup_station_cycles[id]
@@ -736,20 +735,22 @@ function tick_init(map_data, mod_settings)
if station.last_delivery_tick + mod_settings.warmup_time*mod_settings.tps < map_data.total_ticks then
station.is_warming_up = nil
map_data.active_station_ids[#map_data.active_station_ids + 1] = id
map_data.warmup_station_ids[i] = nil
table_remove(map_data.warmup_station_ids, 1)
map_data.warmup_station_cycles[id] = nil
if station.entity_comb1.valid then
combinator_update(map_data, station.entity_comb1)
else
on_station_broken(map_data, id, station)
end
else
break
end
else
map_data.warmup_station_cycles[id] = cycles + 1
break
end
i = i + 1
else
table_remove(map_data.warmup_station_ids, i)
table_remove(map_data.warmup_station_ids, 1)
map_data.warmup_station_cycles[id] = nil
end
end

View File

@@ -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<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
---@param entity0 LuaEntity
---@param entity1 LuaEntity
@@ -40,6 +48,7 @@ end
---@param train LuaTrain
---@return LuaEntity?
function get_any_train_entity(train)
return train.valid and (train.front_stock or train.back_stock or train.carriages[1]) or nil
end
@@ -47,8 +56,9 @@ end
---@param e Station|Refueler|Train
---@param network_name string
---@return int
function get_network_flag(e, network_name)
return e.network_name == NETWORK_EACH and (e.network_flag[network_name] or 0) or e.network_flag
return e.network_name == NETWORK_EACH and (e.network_flag[network_name] or 0) or e.network_flag--[[@as int]]
end

View File

@@ -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

View File

@@ -0,0 +1,134 @@
local gui = require("__flib__.gui-lite")
local mod_gui = require("__core__.lualib.mod-gui")
local manager = require("scripts.gui.manager")
--- @class Manager
--- @field item_order table<string, int>
--- @class PlayerData
--- @field refs {[string]: LuaGuiElement}?
--- @field search_query string?
--- @field search_network_name string?
--- @field search_network_mask int
--- @field search_surface_idx uint?
--- @field search_item string?
--- @field trains_orderings uint[]
--- @field trains_orderings_invert boolean[]
--- @field pinning boolean
local function top_left_button_update(player, player_data)
local button_flow = mod_gui.get_button_flow(player)
local button = button_flow["top_left_button"]
if player_data.disable_top_left_button then
if button then
button.destroy()
end
elseif not button then
gui.add(button_flow, {
type = "sprite-button",
name = "top_left_button",
style = "mis_mod_gui_button_green",
sprite = "mis_configure_white",
tooltip = { "", "\n", { "mis-config-gui.configure-tooltip" } },
handler = manager.handle.toggle,
})
end
end
local manager_gui = {}
function manager_gui.on_lua_shortcut(e)
if e.prototype_name == "ltnm-toggle-gui" then
manager.wrapper(e, manager.handle.toggle)
end
end
function manager_gui.on_player_created(e)
local player = game.get_player(e.player_index)
if not player then return end
local player_data = {
search_network_mask = -1,
trains_orderings = {},
trains_orderings_invert = {},
pinning = false,
refs = manager.create(player),
}
global.manager_data.players[e.player_index] = player_data
manager.update(global, player, player_data)
top_left_button_update(player, player_data)
end
function manager_gui.on_player_removed(e)
global.manager_data.players[e.player_index] = nil
end
--script.on_event(defines.events.on_player_joined_game, function(e)
--end)
--script.on_event(defines.events.on_player_left_game, function(e)
--end)
function manager_gui.on_runtime_mod_setting_changed(e)
if e.setting == "cybersyn-disable-top-left-button" then
if not e.player_index then return end
local player = game.get_player(e.player_index)
if not player then return end
local player_data = global.manager_data.players[e.player_index]
player_data.disable_top_left_button = player.mod_settings["cybersyn-disable-top-left-button"].value
top_left_button_update(player, player_data)
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()
return manager_gui

View File

@@ -13,61 +13,16 @@ local trains_tab = require("scripts.gui.trains")
--local alerts_tab = require("scripts.gui.alerts")
--- @class PlayerData
--- @field refs {[string]: LuaGuiElement}?
--- @field search_query string?
--- @field network_name string
--- @field network_flag int
--- @field pinning boolean
function Index:dispatch(msg, e)
-- "Transform" the action based on criteria
if msg.transform == "handle_refresh_click" then
if e.shift then
msg.action = "toggle_auto_refresh"
else
self.state.ltn_data = global.data
self.do_update = true
end
elseif msg.transform == "handle_titlebar_click" then
if e.button == defines.mouse_button_type.middle then
msg.action = "recenter"
end
end
-- Dispatch the associated action
if msg.action then
local func = self.actions[msg.action]
if func then
func(self, msg, e)
else
log("Attempted to call action `" .. msg.action .. "` for which there is no handler yet.")
end
end
-- Update if necessary
if self.do_update then
self:update()
self.do_update = false
end
end
function Index:schedule_update()
self.do_update = true
end
local manager = {}
function manager.build(player, player_data)
--- @param player LuaPlayer
function manager.create(player)
local widths = constants.gui["en"]
---@type table<string, LuaGuiElement>
local refs = {}
local _, window = gui.add(player.gui.screen, {
gui.add(player.gui.screen, {
{
name = "manager_window",
type = "frame",
@@ -111,25 +66,25 @@ function manager.build(player, player_data)
name = "manager_text_search_field",
type = "textfield",
clear_and_focus_on_right_click = true,
handler = manager.handle.update_text_search_query, --on_gui_text_changed
handler = manager.handle.update_text_search, --on_gui_text_changed
},
{ type = "empty-widget", style = "flib_horizontal_pusher" },
{ type = "label", style = "caption_label", caption = { "gui.ltnm-network-id-label" } },
{
name = "manager_network_id_field",
name = "manager_network_mask_field",
type = "textfield",
style_mods = { width = 120 },
numeric = true,
allow_negative = true,
clear_and_focus_on_right_click = true,
text = "-1",
handler = manager.handle.update_network_id_query, --on_gui_text_changed
handler = manager.handle.update_network_mask, --on_gui_text_changed
},
{ type = "label", style = "caption_label", caption = { "gui.ltnm-surface-label" } },
{
name = "manager_surface_dropdown",
type = "drop-down",
handler = manager.handle.change_surface, --on_gui_selection_state_changed
handler = manager.handle.update_surface, --on_gui_selection_state_changed
},
},
},
@@ -137,9 +92,6 @@ function manager.build(player, player_data)
name = "manager_tabbed_pane",
type = "tabbed-pane",
style = "ltnm_tabbed_pane",
children = {
trains_tab.build(widths, refs),
},
},
},
},
@@ -149,23 +101,49 @@ function manager.build(player, player_data)
refs.manager_titlebar.drag_target = window
window.force_auto_center()
refs.manager_titlebar.drag_target = refs.manager_window
refs.manager_window.force_auto_center()
return refs
end
--- @param map_data MapData
--- @param player LuaPlayer
--- @param refs table<string, LuaGuiElement>
function manager.destroy(player, refs)
refs.manager_window.destroy()
player.set_shortcut_toggled("ltnm-toggle-gui", false)
player.set_shortcut_available("ltnm-toggle-gui", false)
--- @param player_data PlayerData
function manager.update(map_data, player, player_data)
local tab = trains_tab.build(map_data, player_data)
gui.add(_, tab, player_data.refs)
end
manager.handle = {}
--- @param e {player_index: uint}
function manager.wrapper(e, handler)
local player = game.get_player(e.player_index)
if not player then return end
local player_data = global.manager_data.players[e.player_index]
handler(player, player_data, player_data.refs, e)
end
local function toggle_fab(elem, sprite, state)
if state then
elem.style = "flib_selected_frame_action_button"
elem.sprite = sprite .. "_black"
else
elem.style = "frame_action_button"
elem.sprite = sprite .. "_white"
end
end
--- @param player LuaPlayer
--- @param player_data PlayerData
--- @param refs table<string, LuaGuiElement>
function manager.open(player, player_data, refs)
function manager.handle.open(player, player_data, refs)
refs.manager_window.bring_to_front()
refs.manager_window.visible = true
player_data.visible = true
@@ -177,10 +155,11 @@ function manager.open(player, player_data, refs)
player.set_shortcut_toggled("ltnm-toggle-gui", true)
end
--- @param player LuaPlayer
--- @param player_data PlayerData
--- @param refs table<string, LuaGuiElement>
function manager.close(player, player_data, refs)
function manager.handle.close(player, player_data, refs)
if player_data.pinning then
return
end
@@ -195,86 +174,76 @@ function manager.close(player, player_data, refs)
player.set_shortcut_toggled("ltnm-toggle-gui", false)
end
--- @param player LuaPlayer
--- @param player_data PlayerData
--- @param refs table<string, LuaGuiElement>
function manager.handle.toggle(player, player_data, refs)
manager.handle = {}
--- @param e GuiEventData
function manager.wrapper(e, handler)
local player = game.get_player(e.player_index)
if not player then return end
local player_data = global.manager.players[e.player_index]
handler(player, player_data, player_data.refs)
end
local function toggle_fab(elem, sprite, state)
if state then
elem.style = "flib_selected_frame_action_button"
elem.sprite = sprite .. "_black"
else
elem.style = "frame_action_button"
elem.sprite = sprite .. "_white"
end
end
manager.handle.close = manager.close
--- @param player LuaPlayer
--- @param player_data PlayerData
--- @param refs table<string, LuaGuiElement>
function manager.handle.recenter(player, player_data, refs)
refs.window.force_auto_center()
refs.window.force_auto_center()
end
--- @param player LuaPlayer
--- @param player_data PlayerData
--- @param refs table<string, LuaGuiElement>
function manager.handle.toggle_auto_refresh(player, player_data, refs)
player_data.auto_refresh = not player_data.auto_refresh
toggle_fab(refs.manager_refresh_button, "ltnm_refresh", player_data.auto_refresh)
player_data.auto_refresh = not player_data.auto_refresh
toggle_fab(refs.manager_refresh_button, "ltnm_refresh", player_data.auto_refresh)
end
--- @param player LuaPlayer
--- @param player_data PlayerData
--- @param refs table<string, LuaGuiElement>
function manager.handle.toggle_pinned(player, player_data, refs)
player_data.pinned = not player_data.pinned
toggle_fab(refs.manager_pin_button, "ltnm_pin", player_data.pinned)
player_data.pinned = not player_data.pinned
toggle_fab(refs.manager_pin_button, "ltnm_pin", player_data.pinned)
end
--- @param player LuaPlayer
--- @param player_data PlayerData
--- @param refs table<string, LuaGuiElement>
--- @param e GuiEventData
function manager.handle.update_text_search_query(player, player_data, refs, e)
local query = e.text
-- Input sanitization
for pattern, replacement in pairs(constants.input_sanitizers) do
query = string.gsub(query, pattern, replacement)
end
player_data.search_query = query
if Gui.state.search_job then
on_tick_n.remove(Gui.state.search_job)
end
if #query == 0 then
Gui:schedule_update()
else
Gui.state.search_job = on_tick_n.add(
game.tick + 30,
{ gui = "main", action = "update", player_index = Gui.player.index }
)
end
function manager.handle.update_text_search(player, player_data, refs, e)
local query = e.text
-- Input sanitization
for pattern, replacement in pairs(constants.input_sanitizers) do
query = string.gsub(query, pattern, replacement)
end
player_data.search_query = query
end
--- @param player LuaPlayer
--- @param player_data PlayerData
--- @param refs table<string, LuaGuiElement>
function manager.handle.update_network_id_query(player, player_data, refs)
Gui.state.network_id = tonumber(Gui.refs.toolbar.network_id_field.text) or -1
Gui:schedule_update()
function manager.handle.update_network_name(player, player_data, refs)
local signal = refs.manager_network_name.elem_value
if signal then
player_data.search_network_name = signal.name
else
player_data.search_network_name = nil
end
end
--- @param player LuaPlayer
--- @param player_data PlayerData
--- @param refs table<string, LuaGuiElement>
function manager.handle.update_network_mask(player, player_data, refs)
player_data.search_network_mask = tonumber(refs.manager_network_mask_field.text) or -1
end
--- @param player LuaPlayer
--- @param player_data PlayerData
--- @param refs table<string, LuaGuiElement>
function manager.handle.update_surface(player, player_data, refs)
local i = refs.manager_surface_dropdown.selected_index
player_data.search_surface_idx = i--TODO: fix this
end
gui.add_handlers(manager.handle, manager.wrapper)
return manager

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", "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

View File

@@ -1,146 +1,226 @@
local format = require("__flib__.format")
local gui = require("__flib__.gui-lite")
local constants = require("constants")
local util = require("scripts.gui.util")
local templates = require("templates")
local templates = require("scripts.gui.templates")
local trains_tab = {}
function trains_tab.build(map_data, player_id, player_data)
--- @param map_data MapData
--- @param player_data PlayerData
--- @return GuiElemDef
function trains_tab.build(map_data, player_data)
local widths = constants.gui["en"]
local search_query = player_data.search_query
local search_network_flag = player_data.network_flag
local search_network = player_data.network
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 trains_sorted = {}
for id, train in pairs(map_data.trains) do
if search_network_name then
if search_network_name ~= train.network_name then
goto continue
end
local train_flag = get_network_flag(train, search_network_name)
if not bit32.btest(search_network_mask, train_flag) then
goto continue
end
elseif search_network_mask ~= -1 then
if train.network_name == NETWORK_EACH then
local masks = train.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, train.network_flag) then
goto continue
end
end
if search_surface_idx then
local entity = get_any_train_entity(train.entity)
if not entity then
goto continue
end
if entity.surface.index ~= search_surface_idx then
goto continue
end
end
if search_item then
if not train.manifest then
goto continue
end
for i, v in ipairs(train.manifest) do
if v.name == search_item then
goto has_match
end
end
goto continue
::has_match::
end
trains_sorted[#trains_sorted + 1] = id
::continue::
end
table.sort(trains_sorted, function(a, b)
local train1 = map_data.trains[a]
local train2 = map_data.trains[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 train1.layout_id ~= train2.layout_id then
local layout1 = map_data.layouts[train1.layout_id]
local layout2 = map_data.layouts[train2.layout_id]
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
end
elseif v == ORDER_DEPOT then
local depot1 = map_data.depots[train1.depot_id]
local depot2 = map_data.depots[train2.depot_id]
local name1 = depot1.entity_stop.valid and depot1.entity_stop.backer_name
local name2 = depot2.entity_stop.valid and depot2.entity_stop.backer_name
if name1 ~= name2 then
return invert ~= (name1 and (name2 and name1 < name2 or true) or false)
end
elseif v == ORDER_STATUS then
if train1.status ~= train2.status then
return invert ~= (train1.status < train2.status)
end
elseif v == ORDER_MANIFEST then
if not train1.manifest then
if train2.manifest then
return invert ~= true
end
elseif not train2.manifest then
return invert ~= false
else
local primary_item1 = train1.manifest[1]
local primary_item2 = train2.manifest[1]
if primary_item1.name ~= primary_item2.name then
return invert ~= (primary_item1.type == primary_item2.type and primary_item1.name < primary_item2.name or primary_item1.type == "item")
elseif primary_item1.count ~= primary_item2.count then
return invert ~= (primary_item1.count < primary_item2.count)
end
end
end
end
return a < b
end)
local trains_sorted = player_data.trains_sorted
---@type GuiElemDef
local train_list = {}
--if not sorted_trains then
-- sorted_trains = {}
-- ids = {}
-- for id, train in pairs(map_data) do
-- local i = #ids + 1
-- ids[i] = id
-- sorted_trains[i] = train
-- end
-- dual_sort(ids, sorted_trains)
--end
if #trains_sorted == 0 then
train_list[1] = {
type = "label",
style = "ltnm_semibold_label",
caption = { "gui.ltnm-no-trains" },
ref = { "trains", "warning_label" },
}
else
local start, finish, step
if player_data.trains_ascending then
start = #trains_sorted
finish = 1
step = -1
else
start = 1
finish = #trains_sorted
step = 1
end
local gui_idx = 1
for idx = start, finish, step do
local train_id = trains_sorted[idx]
for idx, train_id in ipairs(trains_sorted) do
local train = map_data.trains[train_id]
local depot = map_data.depots[train.depot_id]
local depot_name = depot.entity_stop.valid and depot.entity_stop.backer_name or ""
if
true
then
local color = gui_idx % 2 == 0 and "dark" or "light"
train_list[gui_idx] = {
type = "frame",
style = "ltnm_table_row_frame_" .. color,
children = {
{
type = "frame",
style = "ltnm_table_inset_frame_" .. color,
children = {
type = "minimap",
style = "ltnm_train_minimap",
{ type = "label", style = "ltnm_minimap_label" },
{
type = "button",
style = "ltnm_train_minimap_button",
tooltip = { "gui.ltnm-open-train-gui" },
elem_mods = { entity = get_any_train_entity(train.entity) },
actions = {
on_click = { gui = "main", action = "open_train_gui", train_id = train_id },
},
},
},
},
{
type = "label",
style_mods = { width = widths.trains.composition },
elem_mods = { caption = train.composition },
},
{
type = "label", style_mods = { width = widths.trains.depot },
elem_mods = { caption = train.depot },
},
{
type = "frame",
name = "shipment_frame",
style = "ltnm_small_slot_table_frame_" .. color,
style_mods = { width = widths.trains.shipment },
children = {
{
type = "table",
name = "shipment_table",
style = "slot_table",
column_count = widths.trains.shipment_columns,
children = util.slot_table_build(train.manifest, "default"),
},
local color = idx % 2 == 0 and "dark" or "light"
train_list[idx] = {
type = "frame",
style = "ltnm_table_row_frame_" .. color,
children = {
{
type = "frame",
style = "ltnm_table_inset_frame_" .. color,
children = {
type = "minimap",
style = "ltnm_train_minimap",
{ type = "label", style = "ltnm_minimap_label" },
{
type = "button",
style = "ltnm_train_minimap_button",
tooltip = { "gui.ltnm-open-train-gui" },
elem_mods = { entity = get_any_train_entity(train.entity) },
handler = trains_tab.handle.open_train_gui, --on_click
tags = { train_id = train_id },
},
},
},
}
gui_idx = gui_idx + 1
end
{
type = "label",
style_mods = { width = widths.trains.composition },
elem_mods = { caption = train.layout_id },
},
{
type = "label",
style_mods = { width = widths.trains.depot },
elem_mods = { caption = depot_name },
},
{
type = "frame",
name = "shipment_frame",
style = "ltnm_small_slot_table_frame_" .. color,
style_mods = { width = widths.trains.shipment },
children = {
{
type = "table",
name = "shipment_table",
style = "slot_table",
column_count = widths.trains.shipment_columns,
children = util.slot_table_build(train.manifest, "default"),
},
},
},
},
}
end
end
return {
tab = {
name = "trains_tab",
type = "tab",
caption = #trains_sorted == 0 and { "gui.ltnm-trains" } or { "gui.ltnm-trains", #train_list },
badge_text = misc.delineate_number(#ltn_data.sorted_trains.composition),
ref = { "trains", "tab" },
actions = {
on_click = { gui = "main", action = "change_tab", tab = "trains" },
},
--badge_text = format.number(#ltn_data.sorted_trains.composition),
handler = trains_tab.handle.change_tab, --on_click
tags = { tab = "trains_tab" },
},
content = {
name = "trains_content_frame",
type = "frame",
style = "ltnm_main_content_frame",
direction = "vertical",
ref = { "trains", "content_frame" },
children = {
{
type = "frame",
style = "ltnm_table_toolbar_frame",
templates.sort_checkbox(widths, "trains", "train_id", true),
templates.sort_checkbox(widths, "trains", "status", false),
templates.sort_checkbox(widths, "trains", "composition", false, { "gui.ltnm-composition-description" }),
templates.sort_checkbox(widths, "trains", "layout", false, { "gui.ltnm-composition-description" }),
templates.sort_checkbox(widths, "trains", "depot", false),
templates.sort_checkbox(widths, "trains", "shipment", false),
},
{ type = "scroll-pane", style = "ltnm_table_scroll_pane", ref = { "trains", "scroll_pane" } },
{ name = "trains_scroll_pane", type = "scroll-pane", style = "ltnm_table_scroll_pane" },
{
name = "trains_warning_flow",
type = "flow",
style = "ltnm_warning_flow",
visible = false,
ref = { "trains", "warning_flow" },
children = train_list,
},
},

View File

@@ -38,8 +38,8 @@ end
--- @param color string
--- @return GuiElemDef[]
function util.slot_table_build(manifest, color)
---@type GuiElemDef[]
local children = {}
local i = 1
for _, item in pairs(manifest) do
local name = item.name
local sprite
@@ -49,7 +49,7 @@ function util.slot_table_build(manifest, color)
sprite = string.gsub(name, ",", "/")
end
if game.is_valid_sprite_path(sprite) then
children[i] = {
children[#children + 1] = {
type = "sprite-button",
enabled = false,
style = "ltnm_small_slot_button_" .. color,
@@ -61,7 +61,6 @@ function util.slot_table_build(manifest, color)
"\n"..format.number(count),
},
}
i = i + 1
end
end
return children

View File

@@ -471,7 +471,7 @@ function reset_stop_layout(map_data, stop, is_station_or_refueler, forbidden_ent
local wagon_number = 0
for i = 1, 112 do
local rail, rail_direction, rail_connection_direction = pre_rail.get_connected_rail({rail_direction = rail_direction_from_stop, rail_connection_direction = defines.rail_connection_direction.straight})
if not rail or rail_connection_direction ~= defines.rail_connection_direction.straight or not rail.valid then
if not rail or not rail.valid then
is_break = true
break
end
@@ -555,6 +555,10 @@ function reset_stop_layout(map_data, stop, is_station_or_refueler, forbidden_ent
end
search_area = area.move(search_area, area_delta)
end
if not rail_connection_direction ~= defines.rail_connection_direction.straight then
is_break = true
break
end
end
stop.layout_pattern = layout_pattern
if is_station_or_refueler then

View File

@@ -50,45 +50,24 @@ function irpairs(a)
return irnext, a, 0
end
---@generic V
---@param arr Array<V>
---@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<K, any>
--- @param t2 table<K, any>
--- @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

View File

@@ -103,12 +103,19 @@ data:extend({
default_value = false,
},
--{
-- type = "bool-setting",
-- name = "cybersyn-disable-top-left-button",
-- setting_type = "runtime-player",
-- default_value = false,
-- order = "ea",
--},
--{
-- type = "int-setting",
-- name = "cybersyn-history-length",
-- setting_type = "runtime-global",
-- minimum_value = 10,
-- maximum_value = 1000,
-- default_value = 50,
-- order = "ea",
-- order = "eb",
--},
})