diff --git a/TODO b/TODO index 56d378f..d1bccb1 100644 --- a/TODO +++ b/TODO @@ -5,12 +5,12 @@ bugs: major: debug output - more detailed missing train alerts - implement provide station priority locking add request threshold provide override add in game guide move to an event based algorithm models & art + add stack threshold setting + allow "any" signal for network unions Leave the train in automatic mode for at least a tick so that I can implement a memory cell to make an alert Make the alert persistent instead of going away after 10 seconds diff --git a/cybersyn/changelog.txt b/cybersyn/changelog.txt index e2d921a..3686b4a 100644 --- a/cybersyn/changelog.txt +++ b/cybersyn/changelog.txt @@ -3,7 +3,10 @@ Version: 1.1.8 Date: 2022-12-22 Changes: - Fixed depot priority - - A train's distance from the provide station is now prioritized over train cargo capacity + - Prioritized a train's distance from the provide station over train cargo capacity + - Forced provide stations to wait until they can service the highest priority request station + - Added more detailed missing train alerts + - Provide stations now override request thresholds with the per-item thresholds set by their station info combinator --------------------------------------------------------------------------------------------------- Version: 1.1.7 Date: 2022-12-17 @@ -38,7 +41,7 @@ Date: 2022-12-8 Version: 1.1.0 Date: 2022-12-8 Changes: - - Added the ability to use the priority signal as input to optional station control so one can override priority on items with optional station control thresholds + - Added the ability to use the priority signal as input to station info combinators so one can override priority on items with station info combinators thresholds - Added refueler stations - Slightly more permissive allow-list logic - Made non-backwards compatible improvements and bugfixes to the modding interface @@ -50,20 +53,8 @@ Date: 2022-12-3 Changes: - Fixed a bug with SE compat preventing players from joining multiplayer games --------------------------------------------------------------------------------------------------- -Version: 1.0.8 -Date: 2022-12-2 - Changes: - - Fixed a bug with combinator displays not updating correctly - - Improved combinator display performance - - Added a modding interface ---------------------------------------------------------------------------------------------------- -Version: 1.0.7 -Date: 2022-12-1 - Changes: - - Fixed a crash relating to depot bypass through space elevators ---------------------------------------------------------------------------------------------------- -Version: 1.0.6 -Date: 2022-11-30 +Version: 1.0.8optional station +optional station Changes: - Added depot bypass - Increased inactivity time so burner inserters are fast enough to trigger it diff --git a/cybersyn/locale/en/base.cfg b/cybersyn/locale/en/base.cfg index c076410..3c9d32c 100644 --- a/cybersyn/locale/en/base.cfg +++ b/cybersyn/locale/en/base.cfg @@ -1,7 +1,7 @@ [mod-setting-name] cybersyn-ticks-per-second=Central planning updates per second cybersyn-update-rate=Central planning update rate -cybersyn-request-threshold=Default requester threshold +cybersyn-request-threshold=Default request threshold cybersyn-network-flag=Default network mask cybersyn-fuel-threshold=Fuel threshold cybersyn-depot-bypass-enabled=Depot bypass enabled @@ -43,7 +43,6 @@ cybersyn-request-threshold=Request threshold cybersyn-locked-slots=Locked slots per cargo wagon [cybersyn-messages] -missing-trains=Could not find a train to make a delivery from __2__ to __1__ nonempty-train=A train is being held in the depot because it still has cargo unexpected-train=A train has unexpectedly returned to the depot before completing its delivery stuck-train=A train from depot __1__ is stuck @@ -52,6 +51,10 @@ depot-broken=A train from depot __1__ is lost because its depot was broken. refueler-broken=A train from depot __1__ is lost because its refueler was broken. station-broken=A train from depot __1__ is lost because one of its delivery stations was broken. train-at-incorrect=A train from depot __1__ is lost; it parked at a station it was not scheduled to delivered to. +missing-train=Could not find any train on the correct network to make a delivery from __2__ to __1__ +no-train-has-capacity=Could not find a train with large enough cargo capacity to make a delivery from __2__ to __1__ +no-train-matches-r-layout=Could not find a train on the allow-list of __1__ to make a delivery +no-train-matches-p-layout=Could not find a train on the allow-list of __2__ to make a delivery to __1__ [cybersyn-gui] combinator-title=Cybernetic combinator @@ -62,7 +65,7 @@ refueler=Fuel loader comb2=Station info wagon-manifest=Wagon info network=Network -network-tooltip=A signal is used to identify which network this combinator is a member of. Trains will only be dispatched from depots to provide and request stations if they are all identified with the same signal. +network-tooltip=A signal is used to identify which network this combinator is a member of. Trains will only be dispatched from depots to provider and requester stations if they are all identified with the same signal. auto-tooltip=When checked trains in the network are automatically added to the allow-list if every wagon of the train is able to be loaded or unloaded by this station. When unchecked the allow-list is not used and all trains are allowed to park here. auto-description=Automatic train allow-list switch-provide=Provide only diff --git a/cybersyn/scripts/central-planning.lua b/cybersyn/scripts/central-planning.lua index c8a0747..f84a790 100644 --- a/cybersyn/scripts/central-planning.lua +++ b/cybersyn/scripts/central-planning.lua @@ -117,7 +117,8 @@ function create_manifest(map_data, r_station_id, p_station_id, train_id, primary local r_threshold = r_station.item_thresholds and r_station.item_thresholds[item_name] or r_station.r_threshold local p_effective_item_count = p_station.item_p_counts[item_name] --could be an item that is not present at the station - if p_effective_item_count and p_effective_item_count >= r_threshold then + local override_threshold = p_station.item_thresholds and p_station.item_thresholds[item_name] + if p_effective_item_count and p_effective_item_count >= (override_threshold or r_threshold) then local item = {name = item_name, type = item_type, count = min(-r_effective_item_count, p_effective_item_count)} if item_name == primary_item_name then manifest[#manifest + 1] = manifest[1] @@ -224,7 +225,6 @@ local function tick_dispatch(map_data, mod_settings) end end end - local max_threshold = INF while true do local r_station_i = nil local r_threshold = nil @@ -246,10 +246,6 @@ local function tick_dispatch(map_data, mod_settings) prior = station.item_priority--[[@as int]] end end - if threshold > max_threshold then - goto continue - end - if prior < best_r_prior then goto continue end @@ -282,39 +278,31 @@ local function tick_dispatch(map_data, mod_settings) local trains = map_data.available_trains[network_name] local is_fluid = item_type == "fluid" --no train exists with layout accepted by both provide and request stations - local a_p_exists = false - local a_train_exists = false - local a_train_has_capacity = false - local a_train_has_r_layout = false + local correctness = 0 + local closest_to_correct_p_station = nil - max_threshold = 0 ---@type uint? - local best_p_i = nil + local p_station_i = nil local best_train_id = nil local best_p_prior = -INF local best_dist = INF --if no available trains in the network, skip search - for j, p_station_id in ipairs(p_stations) do + ---@type uint + local j = 1 + while j <= #p_stations do + local p_station_id = p_stations[j] local p_station = stations[p_station_id] if not p_station or p_station.deliveries_total >= p_station.entity_stop.trains_limit then goto p_continue end + local netand = band(p_station.network_flag, r_station.network_flag) if netand == 0 then goto p_continue end - local effective_count = p_station.item_p_counts[item_name] - max_threshold = max(max_threshold, effective_count) - if effective_count < r_threshold then - goto p_continue - end - a_p_exists = true - local p_prior = p_station.priority - if p_station.item_thresholds and p_station.item_thresholds[item_name] 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 + if correctness < 1 then + correctness = 1 + closest_to_correct_p_station = p_station end ---------------------------------------------------------------- -- check for valid train @@ -331,23 +319,40 @@ local function tick_dispatch(map_data, mod_settings) if not btest(netand, train.network_flag) or train.se_is_being_teleported then goto train_continue end - a_train_exists = true + if correctness < 2 then + correctness = 2 + closest_to_correct_p_station = p_station + end + --check cargo capabilities local capacity = (is_fluid and train.fluid_capacity) or train.item_slot_capacity if capacity < slot_threshold then --no train with high enough capacity is available goto train_continue end - a_train_has_capacity = true + if correctness < 3 then + correctness = 3 + closest_to_correct_p_station = p_station + end + --check layout validity for both stations local layout_id = train.layout_id if not (r_station.allows_all_trains or r_station.accepted_layouts[layout_id]) then goto train_continue end - a_train_has_r_layout = true + if correctness < 4 then + correctness = 4 + closest_to_correct_p_station = p_station + end + if not (p_station.allows_all_trains or p_station.accepted_layouts[layout_id]) then goto train_continue end + if correctness < 5 then + correctness = 5 + closest_to_correct_p_station = p_station + end + if train.priority < best_t_prior then goto train_continue end @@ -357,6 +362,7 @@ local function tick_dispatch(map_data, mod_settings) if train.priority == best_t_prior and t_to_p_dist > best_t_to_p_dist then goto train_continue end + best_p_train_id = train_id best_t_prior = train.priority best_t_to_p_dist = t_to_p_dist @@ -366,31 +372,52 @@ local function tick_dispatch(map_data, mod_settings) if not best_p_train_id then goto p_continue end + + local effective_count = p_station.item_p_counts[item_name] + local override_threshold = p_station.item_thresholds and p_station.item_thresholds[item_name] + 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) + goto p_continue_remove + end + + local 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 + local best_p_dist = best_t_to_p_dist + get_stop_dist(p_station.entity_stop, r_station.entity_stop) if p_prior == best_p_prior and best_p_dist > best_dist then goto p_continue end - best_p_i = j--[[@as uint]] + + p_station_i = j best_train_id = best_p_train_id best_p_prior = p_prior best_dist = best_p_dist ::p_continue:: + j = j + 1 + ::p_continue_remove:: end if best_train_id then - local p_station_id = table_remove(p_stations, best_p_i) + local p_station_id = table_remove(p_stations, p_station_i) local manifest = create_manifest(map_data, r_station_id, p_station_id, best_train_id, item_name) create_delivery(map_data, r_station_id, p_station_id, best_train_id, manifest) return false else - if a_train_has_r_layout then - send_no_train_p_layout_alert(r_station.entity_stop, r_station.entity_stop) - elseif a_train_has_capacity then - send_no_train_r_layout_alert(r_station.entity_stop, r_station.entity_stop) - elseif a_train_exists then - send_no_train_capacity_alert(r_station.entity_stop, r_station.entity_stop) - elseif a_p_exists then - send_missing_train_alert(r_station.entity_stop, r_station.entity_stop) + if correctness == 1 then + send_alert_missing_train(r_station.entity_stop, closest_to_correct_p_station.entity_stop) + elseif correctness == 2 then + send_alert_no_train_has_capacity(r_station.entity_stop, closest_to_correct_p_station.entity_stop) + elseif correctness == 3 then + send_alert_no_train_matches_r_layout(r_station.entity_stop, closest_to_correct_p_station.entity_stop) + elseif correctness == 4 then + send_alert_no_train_matches_p_layout(r_station.entity_stop, closest_to_correct_p_station.entity_stop) end if r_station.display_state%2 == 0 then r_station.display_state = r_station.display_state + 1 @@ -540,7 +567,7 @@ local function tick_poll_train(map_data, mod_settings) if train and train.manifest and not train.se_is_being_teleported and train.last_manifest_tick + mod_settings.stuck_train_time*mod_settings.tps < map_data.total_ticks then if mod_settings.stuck_train_alert_enabled then - send_stuck_train_alert(train.entity, train.depot_name) + send_alert_stuck_train(train.entity, train.depot_name) end interface_raise_train_stuck(train_id) end diff --git a/cybersyn/scripts/factorio-api.lua b/cybersyn/scripts/factorio-api.lua index a982a0f..06102e3 100644 --- a/cybersyn/scripts/factorio-api.lua +++ b/cybersyn/scripts/factorio-api.lua @@ -198,7 +198,7 @@ function set_manifest_schedule(train, depot_name, d_surface_i, p_stop, r_stop, m create_unloading_order(r_stop), }} lock_train(train) - send_cannot_path_between_surfaces_alert(train, depot_name) + send_alert_cannot_path_between_surfaces(train, depot_name) return true end @@ -252,7 +252,7 @@ function add_refueler_schedule(train, stop, depot_name) --create an order that probably cannot be fulfilled and alert the player table_insert(schedule.records, i, create_inactivity_order(stop.backer_name)) lock_train(train) - send_cannot_path_between_surfaces_alert(train, depot_name) + send_alert_cannot_path_between_surfaces(train, depot_name) train.schedule = schedule end @@ -473,55 +473,77 @@ local function send_alert_with_sound(train, icon, message) end end end - -local send_missing_train_alert_for_stop_icon = {name = MISSING_TRAIN_NAME, type = "fluid"} +local send_alert_about_missing_train_icon = {name = MISSING_TRAIN_NAME, type = "fluid"} ---@param r_stop LuaEntity ---@param p_stop LuaEntity -function send_missing_train_alert(r_stop, p_stop) +---@param message string +function send_alert_about_missing_train(r_stop, p_stop, message) for _, player in pairs(r_stop.force.players) do player.add_custom_alert( r_stop, - send_missing_train_alert_for_stop_icon, - {"cybersyn-messages.missing-trains", r_stop.backer_name, p_stop.backer_name}, + send_alert_about_missing_train_icon, + {message, r_stop.backer_name, p_stop.backer_name}, true) end end + +---@param r_stop LuaEntity +---@param p_stop LuaEntity +function send_alert_missing_train(r_stop, p_stop) + send_alert_about_missing_train(r_stop, p_stop, "cybersyn-messages.missing-train") +end +---@param r_stop LuaEntity +---@param p_stop LuaEntity +function send_alert_no_train_has_capacity(r_stop, p_stop) + send_alert_about_missing_train(r_stop, p_stop, "cybersyn-messages.no-train-has-capacity") +end +---@param r_stop LuaEntity +---@param p_stop LuaEntity +function send_alert_no_train_matches_r_layout(r_stop, p_stop) + send_alert_about_missing_train(r_stop, p_stop, "cybersyn-messages.no-train-matches-r-layout") +end +---@param r_stop LuaEntity +---@param p_stop LuaEntity +function send_alert_no_train_matches_p_layout(r_stop, p_stop) + send_alert_about_missing_train(r_stop, p_stop, "cybersyn-messages.no-train-matches-p-layout") +end + local send_lost_train_alert_icon = {name = LOST_TRAIN_NAME, type = "fluid"} ---@param train LuaTrain ---@param depot_name string -function send_cannot_path_between_surfaces_alert(train, depot_name) +function send_alert_cannot_path_between_surfaces(train, depot_name) send_alert_with_sound(train, send_lost_train_alert_icon, {"cybersyn-messages.cannot-path-between-surfaces", depot_name}) end ---@param train LuaTrain ---@param depot_name string -function send_depot_of_train_broken_alert(train, depot_name) +function send_alert_depot_of_train_broken(train, depot_name) send_alert_with_sound(train, send_lost_train_alert_icon, {"cybersyn-messages.depot-broken", depot_name}) end ---@param train LuaTrain ---@param depot_name string -function send_refueler_of_train_broken_alert(train, depot_name) +function send_alert_refueler_of_train_broken(train, depot_name) send_alert_with_sound(train, send_lost_train_alert_icon, {"cybersyn-messages.refueler-broken", depot_name}) end ---@param train LuaTrain ---@param depot_name string -function send_station_of_train_broken_alert(train, depot_name) +function send_alert_station_of_train_broken(train, depot_name) send_alert_with_sound(train, send_lost_train_alert_icon, {"cybersyn-messages.station-broken", depot_name}) end ---@param train LuaTrain ---@param depot_name string -function send_train_at_incorrect_station_alert(train, depot_name) +function send_alert_train_at_incorrect_station(train, depot_name) send_alert_with_sound(train, send_lost_train_alert_icon, {"cybersyn-messages.train-at-incorrect", depot_name}) end local send_nonempty_train_in_depot_alert_icon = {name = NONEMPTY_TRAIN_NAME, type = "fluid"} ---@param train LuaTrain -function send_nonempty_train_in_depot_alert(train) +function send_alert_nonempty_train_in_depot(train) send_alert_with_sound(train, send_nonempty_train_in_depot_alert_icon, {"cybersyn-messages.nonempty-train"}) end ---@param train LuaTrain -function send_unexpected_train_alert(train) +function send_alert_unexpected_train(train) local loco = train.front_stock or train.back_stock if loco then for _, player in pairs(loco.force.players) do @@ -536,7 +558,7 @@ end local send_stuck_train_alert_icon = {name = LOST_TRAIN_NAME, type = "fluid"} ---@param train LuaTrain ---@param depot_name string -function send_stuck_train_alert(train, depot_name) +function send_alert_stuck_train(train, depot_name) local loco = train.front_stock or train.back_stock if loco then for _, player in pairs(loco.force.players) do diff --git a/cybersyn/scripts/main.lua b/cybersyn/scripts/main.lua index 79261fb..f996204 100644 --- a/cybersyn/scripts/main.lua +++ b/cybersyn/scripts/main.lua @@ -27,7 +27,7 @@ local function on_depot_broken(map_data, depot_id, depot) if train_id then local train = map_data.trains[train_id] lock_train(train.entity) - send_depot_of_train_broken_alert(train.entity, depot.entity_stop.backer_name) + send_alert_depot_of_train_broken(train.entity, depot.entity_stop.backer_name) remove_train(map_data, train_id, train) end map_data.depots[depot_id] = nil @@ -76,7 +76,7 @@ local function on_refueler_broken(map_data, refueler_id, refueler) if not train.se_is_being_teleported then remove_train(map_data, train_id, train) lock_train(train.entity) - send_refueler_of_train_broken_alert(train.entity, train.depot_name) + send_alert_refueler_of_train_broken(train.entity, train.depot_name) else train.se_awaiting_removal = train_id end @@ -151,7 +151,7 @@ local function on_station_broken(map_data, station_id, station) if not train.se_is_being_teleported then remove_train(map_data, train_id, train) lock_train(train.entity) - send_station_of_train_broken_alert(train.entity, train.depot_name) + send_alert_station_of_train_broken(train.entity, train.depot_name) else train.se_awaiting_removal = train_id end @@ -752,7 +752,7 @@ local function setup_se_compat() if train.se_awaiting_removal then remove_train(map_data, train.se_awaiting_removal, train) lock_train(train.entity) - send_station_of_train_broken_alert(train.entity, train.depot_name) + send_alert_station_of_train_broken(train.entity, train.depot_name) return elseif train.se_awaiting_rename then rename_manifest_schedule(train.entity, train.se_awaiting_rename[1], train.se_awaiting_rename[2]) diff --git a/cybersyn/scripts/remote-interface.lua b/cybersyn/scripts/remote-interface.lua index 7828d2e..04863ac 100644 --- a/cybersyn/scripts/remote-interface.lua +++ b/cybersyn/scripts/remote-interface.lua @@ -347,15 +347,15 @@ interface.add_refueler_schedule = add_refueler_schedule --[[alerts]] ------------------------------------------------------------------ -interface.send_missing_train_alert = send_missing_train_alert -interface.send_unexpected_train_alert = send_unexpected_train_alert -interface.send_nonempty_train_in_depot_alert = send_nonempty_train_in_depot_alert -interface.send_stuck_train_alert = send_stuck_train_alert -interface.send_cannot_path_between_surfaces_alert = send_cannot_path_between_surfaces_alert -interface.send_depot_of_train_broken_alert = send_depot_of_train_broken_alert -interface.send_refueler_of_train_broken_alert = send_refueler_of_train_broken_alert -interface.send_station_of_train_broken_alert = send_station_of_train_broken_alert -interface.send_train_at_incorrect_station_alert = send_train_at_incorrect_station_alert +interface.send_alert_missing_train = send_alert_missing_train +interface.send_alert_unexpected_train = send_alert_unexpected_train +interface.send_alert_nonempty_train_in_depot = send_alert_nonempty_train_in_depot +interface.send_alert_stuck_train = send_alert_stuck_train +interface.send_alert_cannot_path_between_surfaces = send_alert_cannot_path_between_surfaces +interface.send_alert_depot_of_train_broken = send_alert_depot_of_train_broken +interface.send_alert_refueler_of_train_broken = send_alert_refueler_of_train_broken +interface.send_alert_station_of_train_broken = send_alert_station_of_train_broken +interface.send_alert_train_at_incorrect_station = send_alert_train_at_incorrect_station remote.add_interface("cybersyn", interface) diff --git a/cybersyn/scripts/train-events.lua b/cybersyn/scripts/train-events.lua index 815a6ef..d78d478 100644 --- a/cybersyn/scripts/train-events.lua +++ b/cybersyn/scripts/train-events.lua @@ -155,7 +155,7 @@ local function on_train_arrives_depot(map_data, depot_id, train_entity) if train.manifest then on_failed_delivery(map_data, train_id, train) end - send_unexpected_train_alert(train.entity) + send_alert_unexpected_train(train.entity) else return end @@ -169,7 +169,7 @@ local function on_train_arrives_depot(map_data, depot_id, train_entity) if mod_settings.react_to_nonempty_train_in_depot then lock_train(train_entity) remove_train(map_data, train_id, train) - send_nonempty_train_in_depot_alert(train_entity) + send_alert_nonempty_train_in_depot(train_entity) end interface_raise_train_nonempty_in_depot(depot_id, train_entity, train_id) end @@ -202,7 +202,7 @@ local function on_train_arrives_depot(map_data, depot_id, train_entity) else if mod_settings.react_to_nonempty_train_in_depot then lock_train(train_entity) - send_nonempty_train_in_depot_alert(train_entity) + send_alert_nonempty_train_in_depot(train_entity) end interface_raise_train_nonempty_in_depot(depot_id, train_entity) end @@ -238,12 +238,12 @@ local function on_train_arrives_station(map_data, station_id, train_id, train) on_failed_delivery(map_data, train_id, train) remove_train(map_data, train_id, train) lock_train(train.entity) - send_train_at_incorrect_station_alert(train.entity, train.depot_name) + send_alert_train_at_incorrect_station(train.entity, train.depot_name) end elseif mod_settings.react_to_train_at_incorrect_station then --train is lost somehow, probably from player intervention remove_train(map_data, train_id, train) - send_train_at_incorrect_station_alert(train.entity, train.depot_name) + send_alert_train_at_incorrect_station(train.entity, train.depot_name) end end