diff --git a/.luarc.json b/.luarc.json index 7b1d9ce..d672eb8 100644 --- a/.luarc.json +++ b/.luarc.json @@ -26,6 +26,7 @@ "Lua.runtime.special": { "__object_name": "type" }, + "Lua.workspace.checkThirdParty": false, "Lua.workspace.library": [ "~/.steam/steam/steamapps/common/Factorio/data", "~/.steam/steam/steamapps/common/Factorio/data/core/lualib", diff --git a/README.md b/README.md index f1b9a5b..b6b133e 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Behold one of the most feature-rich and performant train logistics network mods Within Project Cybersyn, you can think of requester stations as requester chests, provider stations as passive provider chests, depots as roboports and trains as the logistics bots. There is a direct correspondence between the Cybersyn train network and Factorio's robot logistics network. -A bare minimum Cybersyn train network consists of 2 components: depots and stations. Both are created by placing a cybernetic combinator adjacent to a train stop. Select the "Control Mode" of the combinator to "Station" to create a station, and to "Depot" to create a depot. Create a basic train and order it to park at the depot you just created, it is now controlled by the Cybersyn network. Depots and stations can have any train stop name, names do not impact their function. The circuit network input of a station's cybernetic combinator determines what items that station will request or provide to the Cybersyn network. A positive item signal is interpreted as that station providing that item to the network; A negative item signal is interpreted as that station requesting that item from the network. +A bare minimum Cybersyn train network consists of 2 components: depots and stations. Both are created by placing a cybernetic combinator adjacent to a train stop. Select the "Mode" of the combinator to "Station" to create a station, and to "Depot" to create a depot. Create a basic train and order it to park at the depot you just created, it is now controlled by the Cybersyn network. Depots and stations can have any train stop name, names do not impact their function. The circuit network input of a station's cybernetic combinator determines what items that station will request or provide to the Cybersyn network. A positive item signal is interpreted as that station providing that item to the network; A negative item signal is interpreted as that station requesting that item from the network. ![Image](https://raw.githubusercontent.com/mamoniot/project-cybersyn/main/previews/basic-provider.png) ![Image](https://raw.githubusercontent.com/mamoniot/project-cybersyn/main/previews/basic-requester.png) @@ -63,7 +63,7 @@ If you like my work, consider supporting me on [ko-fi](https://ko-fi.com/lesbian ## Mod Details -This mod adds a single new entity to the game, the cybernetic combinator. This combinator can be in one of 5 different possible control modes. While each mode has a purpose, the only modes you have to use are primary station control and depot control. +This mod adds a single new entity to the game, the cybernetic combinator. This combinator can be in one of 5 different possible modes. While each mode has a purpose, the only modes you are required to use are station mode and depot mode. ### Station mode @@ -79,7 +79,7 @@ Stations can be set to provide only or request only. By default stations can bot ![Image](https://raw.githubusercontent.com/mamoniot/project-cybersyn/main/previews/big-depot.png) -When placed adjacent to a vanilla train stop, a Cybersyn depot is created. Any train which parks at this depot will automatically be added to the train network. Whenever a train order is generated, if this train has the cargo capacity to fulfill it, and is allow-listed by both stations, then it will automatically be dispatched to fulfill the order. When the order is completed, the train will return to any train stop with the same name as the depot it first parked in. This almost always means it returns to a Cybersyn depot where it will again await to fulfill a new order. To save on UPS the input of a depot control combinator is only read when a train parks at the depot; this is only relevant for networks which make extensive use of network masks on depots. +When placed adjacent to a vanilla train stop, a Cybersyn depot is created. Any train which parks at this depot will automatically be added to the train network. Whenever a train order is generated, if this train has the cargo capacity to fulfill it, and is allow-listed by both stations, then it will automatically be dispatched to fulfill the order. When the order is completed, the train will return to any train stop with the same name as the depot it first parked in. This almost always means it returns to a Cybersyn depot where it will again await to fulfill a new order. To save on UPS the input of a depot combinator is only read when a train parks at the depot; this is only relevant for networks which make extensive use of network masks on depots. ### Fuel loader mode @@ -87,13 +87,17 @@ When placed adjacent to a vanilla train stop, a Cybersyn fuel loader is created. Fuel loaders can automatically build allow-lists. When this option is enabled, trains will be prevented from parking at this station if one of their cargo wagons would be filled with fuel. -### Station info mode +### Station control mode ![Image](https://raw.githubusercontent.com/mamoniot/project-cybersyn/main/previews/science.png) -When placed adjacent to the train stop of an already existing Cybersyn station, this combinator will provide a second set of inputs and outputs that can be used to more precisely control this station. The combinator input allows for request thresholds to be set per-item. Any non-zero item signal given on the input circuit network will override the station's request thresholds for just that item. The output of the combinator gives the sum total of all item loading or unloading orders in progress for the station. The very tick a train is dispatched for a new order to the station, that order is added to the output of this combinator, and it is removed as soon as the train leaves the station. The primary use case for this is to prevent duplicate orders from being generated for stations that provide the same pool of items. Only one train can be dispatched per-tick per-item specifically to accommodate this. +When placed adjacent to the train stop of an already existing Cybersyn station, this combinator will provide a second set of inputs and outputs that can be used to more precisely control this station. The combinator input allows for request thresholds to be set per-item. Any non-zero item signal given on the input circuit network will override the station's request thresholds for just that item. When this is used on a provider station, if the station has more items than the input threshold, it will force the creation of a delivery for that item to the next available requester station. This effectively overrides the requester station's request threshold. This is useful for purging unwanted items from provider stations. -### Wagon info mode +If a station control combinator receives a "priority" signal as input, it will apply that priority to each item signal it is receiving as input. This allows you to specify up to 2 different priorities per-item on a single station. + +The output of the combinator gives the sum total of all item loading or unloading orders in progress for the station. The very tick a train is dispatched for a new order to the station, that order is added to the output of this combinator, and it is removed as soon as the train leaves the station. The primary use case for this is to prevent duplicate orders from being generated for stations that provide the same pool of items. Only one train can be dispatched per-tick per-item specifically to accommodate this. + +### Wagon control mode ![Image](https://raw.githubusercontent.com/mamoniot/project-cybersyn/main/previews/filtered-slots.png) @@ -103,25 +107,29 @@ When placed adjacent to the tracks of an already existing Cybersyn station, this ![Image](https://raw.githubusercontent.com/mamoniot/project-cybersyn/main/previews/gui-network.png) -Stations and depots can be set to belong to a particular network by setting that network on the control combinator. By default all combinators belong to the "signal-A" network. By setting a different signal Id, the combinator will belong to that different network. Networks identified with different signal Ids do not share any trains or items; Orders will never be generated to transfer items between separate networks. +Stations, depots and fuel loaders can be set to belong to a particular network by setting that network on their combinator. By default all combinators belong to the "signal-A" network. By setting a different signal Id, the combinator will belong to that different network. Networks identified with different signal Ids do not share any trains or items; Orders will never be generated to transfer items between separate networks. In addition, if the combinator receives as input a signal of the same Id as its network signal Id, then the value of this signal will be interpreted as a bitmask to give 32 "sub-networks" to choose from. Each station can belong to any set of sub-networks based on its mask. A delivery will only be made between two stations if any two bits match between the two masks, i.e. if `mask1 & mask2 > 0`. When a network Id is an item, that item will be ignored by stations, its signal will only ever be interpreted as the network mask. +Stations and fuel loader combinators allow their network to be set to the "each" virtual signal. When in this mode, each virtual signal given to them as input is interpretted as a network mask for that network Id. The station or fuel loader is thus made a part of that network with the specified network mask. This allows you to union together as many different networks as you would like. + ### Request threshold ![Image](https://raw.githubusercontent.com/mamoniot/project-cybersyn/main/previews/virtual-signals.png) -If a primary station control combinator receives a request threshold signal as input, a request order for the station will only be generated if the station is requesting a number of items exceeding the request threshold. In addition, there must be a station in the network which is providing at least as many items as the request threshold, and there must be a train in the network that has cargo capacity exceeding the request threshold. Through this logic all generated orders must be for a number of items greater than or equal to the request threshold. By setting high thresholds, the traffic on your network can be greatly reduced, at the cost of needing to maintain larger item buffers at each station. There is no "provide threshold" in this mod because by design there is almost no need for one. If desired a provide threshold can be simulated with a single decider combinator. The request threshold signal sets the request threshold "per-station" whereas the optional station control combinator can set or override the threshold per-item as well. +If a station combinator receives a request threshold signal as input, a request order for the station will only be generated if the station is requesting a number of items exceeding the request threshold. In addition, there must be a station in the network which is providing at least as many items as the request threshold, and there must be a train in the network that has cargo capacity exceeding the request threshold. Through this logic all generated orders must be for a number of items greater than or equal to the request threshold. By setting high thresholds, the traffic on your network can be greatly reduced, at the cost of needing to maintain larger item buffers at each station. There is no "provide threshold" in this mod because by design there is almost no need for one. If desired a provide threshold can be simulated with a single decider combinator. The request threshold signal sets the request threshold "per-station" whereas the station control combinator can set or override the threshold per-item as well. + +On station combinators there is a setting called "Stack thresholds". When set, any request threshold for this station will be multiplied by the stack size of any item it is being compared to. This applies to station control thresholds as well. Thus the request threshold can be specified based on total stack count rather than total item count. Fluids are unaffected by the "Stack thresholds" setting, they are always specified by total fluid count. ### Locked slots per cargo wagon -After an order has been generated, enough items will be subtracted from that order to ensure at least X number of slots in each cargo wagon can be left empty, where X is the "Locked slots per cargo wagon" signal being received by the station control combinator. It is necessary for multi-item stations to function. +After an order has been generated, enough items will be subtracted from that order to ensure at least X number of slots in each cargo wagon can be left empty, where X is the "Locked slots per cargo wagon" signal being received by the station combinator. It is necessary for multi-item stations to function. ### Priority -Orders will be generated first for stations, depots and fuel loaders which are receiving a higher priority signal than the others. If stations have the same priority, the least recently used requester station will be prioritized, and the provider station closest to the requester station will be prioritized. So in times of item shortage (front-pressure), round robin distribution will be used, and in times of item surplus (back-pressure), minimum travel distance distribution will be used. +Orders will be generated first for stations, depots and fuel loaders which are receiving a higher priority signal than the others. If stations have the same priority, the least recently used requester station will be prioritized, and the provider station closest to the requester station will be prioritized. So in times of item shortage (front-pressure), round robin distribution will be used, and in times of item surplus (back-pressure), minimum travel distance distribution will be used. Provider stations will be prevented from providing items to lower priority requester stations until the highest priority requester station is satisfied. -If a combinator set to station info mode receives a priority signal, for each item signal input to the combinator, items of that type will have its priority overridden in addition to its request threshold. This effectively allows you to choose one of two possible priorities for each item that a station processes. +If a combinator set to station control mode receives a priority signal, for each item signal input to the combinator, items of that type will have its priority overridden in addition to its request threshold. This effectively allows you to choose one of two possible priorities for each item that a station processes. ### Train limits diff --git a/TODO b/TODO index 18675b7..540d57c 100644 --- a/TODO +++ b/TODO @@ -1,20 +1,15 @@ bugs: - fix crash in sumbitted massively modded world - check for bugs in new player's submitted world - find se bug https://mods.factorio.com/mod/cybersyn/discussion/639d1ea26f00ecfa7b048da0 + https://mods.factorio.com/mod/cybersyn/discussion/63a52e54be4341bccc0446f8 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 minor: + handle if signals are removed from the game during migration railloader compat - request stack threshold close gui when the combinator is destroyed do not play close sound when a different gui is opened gui can desync if settings are changed outside of it while it is open diff --git a/cybersyn/changelog.txt b/cybersyn/changelog.txt index 5c8f108..74ad707 100644 --- a/cybersyn/changelog.txt +++ b/cybersyn/changelog.txt @@ -1,4 +1,21 @@ --------------------------------------------------------------------------------------------------- +Version: 1.2.0 +Date: 2022-12-23 + Changes: + - Forced provide stations to wait until they can service the highest priority request station + - Provide stations now override request thresholds with the per-item thresholds set by their station control combinator + - Allowed station and fuel combinators to be set to network id "each", for each virtual signal they recieve as input, the stop is added to that network and its signal strength is used as the network mask + - Added the ability to specify per-station whether request thresholds represent total items or total stacks + - Prioritized a train's distance from the provide station over the train's cargo capacity + - Added more detailed missing train alerts + - Nonempty trains in depot are no longer put in manual mode, instead they are forced to park at the depot + - Made several alerts persistent + - Fixed bug with depot priority not working + - Fixed a memory leak relating to train layouts + - Fixed a rare crash when building stations + - Fixed a bug where automatic allow-list sometimes could not be turned off + - Slightly improved compat with SE thanks to a bugfix in 0.6.94 +--------------------------------------------------------------------------------------------------- Version: 1.1.7 Date: 2022-12-17 Changes: @@ -32,7 +49,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 control combinators so one can override priority on items with station control combinators thresholds - Added refueler stations - Slightly more permissive allow-list logic - Made non-backwards compatible improvements and bugfixes to the modding interface @@ -45,19 +62,6 @@ Date: 2022-12-3 - 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 Changes: - Added depot bypass - Increased inactivity time so burner inserters are fast enough to trigger it diff --git a/cybersyn/control.lua b/cybersyn/control.lua index f3580ad..9ea0476 100644 --- a/cybersyn/control.lua +++ b/cybersyn/control.lua @@ -1,5 +1,5 @@ --By Mami - +-- at line 310 jumps into the scope of local 'p_flag' require("scripts.constants") require("scripts.global") require("scripts.factorio-api") diff --git a/cybersyn/info.json b/cybersyn/info.json index ac57f6b..aff5c6b 100644 --- a/cybersyn/info.json +++ b/cybersyn/info.json @@ -1,6 +1,6 @@ { "name": "cybersyn", - "version": "1.1.7", + "version": "1.2.0", "title": "Project Cybersyn", "author": "Mami", "factorio_version": "1.1", @@ -8,7 +8,7 @@ "dependencies": [ "base", "flib >= 0.6.0", - "? space-exploration >= 0.6.90", + "? space-exploration >= 0.6.94", "? miniloader", "? nullius", "? pypostprocessing" diff --git a/cybersyn/locale/en/base.cfg b/cybersyn/locale/en/base.cfg index c076410..60614f8 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 @@ -29,7 +29,7 @@ cybersyn-combinator=Cybernetic combinator cybersyn-combinator-output=Cybernetic combinator output [entity-description] -cybersyn-combinator=Has 4 different control modes. Primary control allows providing and requesting. Optional Control allows setting thresholds per-item and reading all in progress deliveries. Depot control allows parked trains to be added to the network. Wagon control allows for reading the desired contents of the adjacent wagon. +cybersyn-combinator=Has 5 different modes. However you only need station mode and depot mode to get started. [technology-name] cybersyn-train-network=Cybersyn train network @@ -43,28 +43,33 @@ 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 -cannot-path-between-surfaces=A train from depot __1__ is attempting to make a delivery between two unconnected surfaces, perhaps put them on separate networks -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. +stuck-train=A train is stuck +cannot-path-between-surfaces=A train is attempting to make a delivery between two unconnected surfaces, perhaps put them on separate networks +depot-broken=A train is lost because its depot was broken +refueler-broken=A train is lost because its fuel loader was broken +station-broken=A train is lost because one of its delivery stations was broken +train-at-incorrect=A train 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 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 -operation=Control Mode +operation=Mode comb1=Station depot=Depot refueler=Fuel loader -comb2=Station info -wagon-manifest=Wagon info +comb2=Station control +wagon-manifest=Wagon control 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. -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 +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. +allow-list-description=Automatic allow-list +allow-list-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. +is-stack-description=Stack thresholds +is-stack-tooltip=When checked all request thresholds for this station are interpretted as a count of item stacks rather than a count of total items. Thresholds for fluids are unaffected. switch-provide=Provide only switch-request=Request only switch-provide-tooltip=Lock this station to only provide items to the network. By default it both requests and provides. diff --git a/cybersyn/scripts/central-planning.lua b/cybersyn/scripts/central-planning.lua index 87ab317..88b6714 100644 --- a/cybersyn/scripts/central-planning.lua +++ b/cybersyn/scripts/central-planning.lua @@ -9,6 +9,7 @@ local band = bit32.band local table_remove = table.remove local random = math.random + ---@param map_data MapData ---@param station Station ---@param manifest Manifest @@ -23,74 +24,12 @@ function remove_manifest(map_data, station, manifest, sign) end set_comb2(map_data, station) station.deliveries_total = station.deliveries_total - 1 - if station.deliveries_total == 0 and station.display_state >= 2 then - station.display_state = station.display_state - 2 + if station.deliveries_total == 0 and band(station.display_state, 1) > 0 then + station.display_state = station.display_state - 1 update_display(map_data, station) end end ----@param map_data MapData ----@param r_station_id uint ----@param p_station_id uint ----@param item_type string ----@param min_slots_to_move int -local function get_valid_train(map_data, r_station_id, p_station_id, item_type, min_slots_to_move) - --NOTE: this code is the critical section for amortized run-time optimization - local r_station = map_data.stations[r_station_id] - local p_station = map_data.stations[p_station_id] - ---@type string - local network_name = p_station.network_name - - local p_to_r_dist = get_stop_dist(p_station.entity_stop, r_station.entity_stop) - local netand = band(p_station.network_flag, r_station.network_flag) - if p_to_r_dist == INF or netand == 0 then - return nil, INF - end - - ---@type uint? - local best_train = nil - local best_capacity = 0 - local best_dist = INF - local valid_train_exists = false - - local is_fluid = item_type == "fluid" - local trains = map_data.available_trains[network_name] - if trains then - for train_id, _ in pairs(trains) do - local train = map_data.trains[train_id] - local layout_id = train.layout_id - --check cargo capabilities - --check layout validity for both stations - local capacity = (is_fluid and train.fluid_capacity) or train.item_slot_capacity - if - capacity >= min_slots_to_move and - btest(netand, train.network_flag) and - (r_station.allows_all_trains or r_station.accepted_layouts[layout_id]) and - (p_station.allows_all_trains or p_station.accepted_layouts[layout_id]) and - not train.se_is_being_teleported - then - valid_train_exists = true - --check if exists valid path - --check if path is shortest so we prioritize locality - local t_to_p_dist = get_stop_dist(train.entity.front_stock, p_station.entity_stop) - DEPOT_PRIORITY_MULT*train.priority - - if capacity > best_capacity or (capacity == best_capacity and t_to_p_dist < best_dist) then - best_capacity = capacity - best_dist = t_to_p_dist - best_train = train_id - end - end - end - end - - if valid_train_exists then - return best_train, best_dist + p_to_r_dist - else - return nil, p_to_r_dist - end -end - - ---@param map_data MapData ---@param r_station_id uint ---@param p_station_id uint @@ -101,8 +40,6 @@ function create_delivery(map_data, r_station_id, p_station_id, train_id, manifes local r_station = map_data.stations[r_station_id] local p_station = map_data.stations[p_station_id] local train = map_data.trains[train_id] - ---@type string - local network_name = r_station.network_name remove_available_train(map_data, train_id, train) local depot_id = train.parked_at_depot_id @@ -111,7 +48,7 @@ function create_delivery(map_data, r_station_id, p_station_id, train_id, manifes train.parked_at_depot_id = nil end --NOTE: we assume that the train is not being teleported at this time - if set_manifest_schedule(train.entity, train.depot_name, train.se_depot_surface_i, p_station.entity_stop, r_station.entity_stop, manifest, depot_id ~= nil) then + if set_manifest_schedule(map_data, train.entity, train.depot_name, train.se_depot_surface_i, p_station.entity_stop, r_station.entity_stop, manifest, depot_id ~= nil) then local old_status = train.status train.status = STATUS_TO_P train.p_station_id = p_station_id @@ -125,31 +62,49 @@ function create_delivery(map_data, r_station_id, p_station_id, train_id, manifes r_station.deliveries_total = r_station.deliveries_total + 1 p_station.deliveries_total = p_station.deliveries_total + 1 + local r_is_each = r_station.network_name == NETWORK_EACH + local p_is_each = p_station.network_name == NETWORK_EACH for item_i, item in ipairs(manifest) do assert(item.count > 0, "main.lua error, transfer amount was not positive") r_station.deliveries[item.name] = (r_station.deliveries[item.name] or 0) + item.count p_station.deliveries[item.name] = (p_station.deliveries[item.name] or 0) - item.count - if item_i > 1 then + if item_i > 1 or r_is_each or p_is_each then + local f, a + if r_is_each then + f, a = pairs(r_station.network_flag--[[@as {[string]: int}]]) + if p_is_each then + for network_name, _ in f, a do + local item_network_name = network_name..":"..item.name + economy.all_r_stations[item_network_name] = nil + economy.all_p_stations[item_network_name] = nil + end + f, a = pairs(p_station.network_flag--[[@as {[string]: int}]]) + end + elseif p_is_each then + f, a = pairs(p_station.network_flag--[[@as {[string]: int}]]) + else + f, a = once, r_station.network_name + end --prevent deliveries from being processed for these items until their stations are re-polled - local item_network_name = network_name..":"..item.name - economy.all_r_stations[item_network_name] = nil - economy.all_p_stations[item_network_name] = nil + --if we don't wait until they are repolled a duplicate delivery might be generated for stations that share inventories + for network_name, _ in f, a do + local item_network_name = network_name..":"..item.name + economy.all_r_stations[item_network_name] = nil + economy.all_p_stations[item_network_name] = nil + end end end set_comb2(map_data, p_station) set_comb2(map_data, r_station) - if p_station.display_state < 2 then - p_station.display_state = 2 - update_display(map_data, p_station) - end - if r_station.display_state < 2 then - r_station.display_state = 2 - update_display(map_data, r_station) - end + p_station.display_state = 1 + update_display(map_data, p_station) + r_station.display_state = 1 + update_display(map_data, r_station) + interface_raise_train_status_changed(train_id, old_status, STATUS_TO_P) else interface_raise_train_dispatch_failed(train_id) @@ -177,9 +132,16 @@ function create_manifest(map_data, r_station_id, p_station_id, train_id, primary local r_effective_item_count = r_item_count + (r_station.deliveries[item_name] or 0) if r_effective_item_count < 0 and r_item_count < 0 then local r_threshold = r_station.item_thresholds and r_station.item_thresholds[item_name] or r_station.r_threshold + if r_station.is_stack and item_type == "item" then + r_threshold = r_threshold*get_stack_size(map_data, item_name) + end 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 override_threshold and p_station.is_stack and item_type == "item" then + override_threshold = override_threshold*get_stack_size(map_data, item_name) + end + 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] @@ -249,6 +211,7 @@ local function tick_dispatch(map_data, mod_settings) local p_stations local item_name local item_type + local item_network_name while true do local size = #all_names if size == 0 then @@ -259,7 +222,7 @@ local function tick_dispatch(map_data, mod_settings) --randomizing the ordering should only matter if we run out of available trains local name_i = size <= 2 and 2 or 2*random(size/2) - local item_network_name = all_names[name_i - 1]--[[@as string]] + item_network_name = all_names[name_i - 1]--[[@as string]] local signal = all_names[name_i]--[[@as SignalID]] --swap remove @@ -278,46 +241,54 @@ local function tick_dispatch(map_data, mod_settings) else for i, id in ipairs(r_stations) do local station = stations[id] - if station and station.display_state%2 == 0 then - station.display_state = station.display_state + 1 + if station and band(station.display_state, 2) == 0 then + station.display_state = station.display_state + 2 update_display(map_data, station) end end end end end - local max_threshold = INF while true do local r_station_i = nil local r_threshold = nil - local best_prior = -INF - local best_lru = INF + local best_r_prior = -INF + local best_timestamp = INF for i, id in ipairs(r_stations) do local station = stations[id] --NOTE: the station at r_station_id could have been deleted and reregistered since last poll, this check here prevents it from being processed for a delivery in that case - if station and station.deliveries_total < station.entity_stop.trains_limit then - local item_threshold = station.item_thresholds and station.item_thresholds[item_name] or nil - local threshold = station.r_threshold - local prior = station.priority - if item_threshold then - threshold = item_threshold - if station.item_priority then - prior = station.item_priority--[[@as int]] - end - end - if threshold <= max_threshold and (prior > best_prior or (prior == best_prior and station.last_delivery_tick < best_lru)) then - r_station_i = i - r_threshold = threshold - best_prior = prior - best_lru = station.last_delivery_tick + if not station or station.deliveries_total >= station.entity_stop.trains_limit then + goto continue + end + + local threshold = station.r_threshold + local prior = station.priority + local item_threshold = station.item_thresholds and station.item_thresholds[item_name] or nil + if item_threshold then + threshold = item_threshold + if station.item_priority then + prior = station.item_priority--[[@as int]] end end + if prior < best_r_prior then + goto continue + end + + if prior == best_r_prior and station.last_delivery_tick > best_timestamp then + goto continue + end + + r_station_i = i + r_threshold = threshold + best_r_prior = prior + best_timestamp = station.last_delivery_tick + ::continue:: end if not r_station_i then - for i, id in ipairs(r_stations) do + for _, id in ipairs(r_stations) do local station = stations[id] - if station and station.display_state%2 == 0 then - station.display_state = station.display_state + 1 + if station and band(station.display_state, 2) == 0 then + station.display_state = station.display_state + 2 update_display(map_data, station) end end @@ -326,54 +297,178 @@ local function tick_dispatch(map_data, mod_settings) local r_station_id = r_stations[r_station_i] local r_station = stations[r_station_id] + ---@type string + local network_name + if r_station.network_name == NETWORK_EACH then + _, _, network_name = string.find(item_network_name, "^(.*):") + else + network_name = r_station.network_name + end + 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 correctness = 0 + local closest_to_correct_p_station = nil - max_threshold = 0 - local best_i = 0 - local best_train = nil + local slot_threshold + if is_fluid then + slot_threshold = r_threshold + elseif r_station.is_stack then + slot_threshold = r_threshold + r_threshold = r_threshold*get_stack_size(map_data, item_name) + else + slot_threshold = ceil(r_threshold/get_stack_size(map_data, item_name)) + end + + ---@type uint? + local p_station_i = nil + local best_train_id = nil + local best_p_prior = -INF local best_dist = INF - local best_prior = -INF - local can_be_serviced = false - for j, p_station_id in ipairs(p_stations) do + --if no available trains in the network, skip search + ---@type uint + local j = 1 + while j <= #p_stations do + local p_flag, r_flag, netand, best_p_train_id, best_t_prior, best_t_to_p_dist, effective_count, override_threshold, p_prior, best_p_dist + + local p_station_id = p_stations[j] local p_station = stations[p_station_id] - if p_station and p_station.deliveries_total < p_station.entity_stop.trains_limit then - local effective_count = p_station.item_p_counts[item_name] - if effective_count >= r_threshold then - local item_threshold = p_station.item_thresholds and p_station.item_thresholds[item_name] or nil - local prior = p_station.priority - if item_threshold and p_station.item_priority then - prior = p_station.item_priority--[[@as int]] + if not p_station or p_station.deliveries_total >= p_station.entity_stop.trains_limit then + goto p_continue + end + + p_flag = p_station.network_name == NETWORK_EACH and (p_station.network_flag[network_name] or 0) or p_station.network_flag + r_flag = r_station.network_name == NETWORK_EACH and (r_station.network_flag[network_name] or 0) or r_station.network_flag + netand = band(p_flag, r_flag) + if netand == 0 then + goto p_continue + end + if correctness < 1 then + correctness = 1 + closest_to_correct_p_station = p_station + end + ---------------------------------------------------------------- + -- check for valid train + ---------------------------------------------------------------- + ---@type uint? + best_p_train_id = nil + best_t_prior = -INF + best_t_to_p_dist = INF + if trains then + for train_id, _ in pairs(trains) do + local train = map_data.trains[train_id] + if not btest(netand, train.network_flag) or train.se_is_being_teleported then + goto train_continue end - local slot_threshold = item_type == "fluid" and r_threshold or ceil(r_threshold/get_stack_size(map_data, item_name)) - local train, d = get_valid_train(map_data, r_station_id, p_station_id, item_type, slot_threshold) - if prior > best_prior or (prior == best_prior and d < best_dist) then - if train then - best_i = j - best_dist = d - best_train = train - best_prior = prior - can_be_serviced = true - elseif d < INF then - best_i = j - can_be_serviced = true - end + if correctness < 2 then + correctness = 2 + closest_to_correct_p_station = p_station end - end - if effective_count > max_threshold then - max_threshold = effective_count + + --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 + 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 + 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 + --check if path is shortest so we prioritize locality + local t_to_p_dist = get_stop_dist(train.entity.front_stock, p_station.entity_stop) - DEPOT_PRIORITY_MULT*train.priority + + 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 + ::train_continue:: end end + if not best_p_train_id 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_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 + + 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 then - local p_station_id = table_remove(p_stations, best_i) - local manifest = create_manifest(map_data, r_station_id, p_station_id, best_train, item_name) - create_delivery(map_data, r_station_id, p_station_id, best_train, manifest) + + if best_train_id then + 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 can_be_serviced and mod_settings.missing_train_alert_enabled then - send_missing_train_alert(r_station.entity_stop, stations[p_stations[best_i]].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 + if band(r_station.display_state, 2) == 0 then + r_station.display_state = r_station.display_state + 2 update_display(map_data, r_station) end end @@ -415,11 +510,16 @@ local function tick_poll_station(map_data, mod_settings) station.priority = 0 station.item_priority = nil station.locked_slots = 0 - station.network_flag = mod_settings.network_flag + if station.network_name == NETWORK_EACH then + station.network_flag = {} + else + station.network_flag = mod_settings.network_flag + end local comb1_signals, comb2_signals = get_signals(station) station.tick_signals = comb1_signals station.item_p_counts = {} + local is_requesting_nothing = true if comb1_signals then if comb2_signals then station.item_thresholds = {} @@ -453,6 +553,8 @@ local function tick_poll_station(map_data, mod_settings) station.r_threshold = abs(item_count) elseif item_name == LOCKED_SLOTS then station.locked_slots = max(item_count, 0) + elseif station.network_name == NETWORK_EACH then + station.network_flag[item_name] = item_count end comb1_signals[k] = nil end @@ -464,49 +566,80 @@ local function tick_poll_station(map_data, mod_settings) comb1_signals[k] = nil end end - local is_requesting_nothing = true for k, v in pairs(comb1_signals) do ---@type string local item_name = v.signal.name + local item_type = v.signal.type local item_count = v.count local effective_item_count = item_count + (station.deliveries[item_name] or 0) local is_not_requesting = true if station.is_r then local r_threshold = station.item_thresholds and station.item_thresholds[item_name] or station.r_threshold + if station.is_stack and item_type == "item" then + r_threshold = r_threshold*get_stack_size(map_data, item_name) + end if -effective_item_count >= r_threshold and -item_count >= r_threshold then is_not_requesting = false is_requesting_nothing = false - local item_network_name = station.network_name..":"..item_name - local stations = all_r_stations[item_network_name] - if stations == nil then - stations = {} - all_r_stations[item_network_name] = stations - all_names[#all_names + 1] = item_network_name - all_names[#all_names + 1] = v.signal + local f, a + if station.network_name == NETWORK_EACH then + f, a = pairs(station.network_flag--[[@as {[string]: int}]]) + else + f, a = once, station.network_name + end + for network_name, _ in f, a do + local item_network_name = network_name..":"..item_name + local stations = all_r_stations[item_network_name] + if stations == nil then + stations = {} + all_r_stations[item_network_name] = stations + all_names[#all_names + 1] = item_network_name + all_names[#all_names + 1] = v.signal + end + stations[#stations + 1] = station_id end - stations[#stations + 1] = station_id end end if is_not_requesting then if station.is_p and effective_item_count > 0 and item_count > 0 then - local item_network_name = station.network_name..":"..item_name - local stations = all_p_stations[item_network_name] - if stations == nil then - stations = {} - all_p_stations[item_network_name] = stations + local f, a + if station.network_name == NETWORK_EACH then + f, a = pairs(station.network_flag--[[@as {[string]: int}]]) + else + f, a = once, station.network_name + end + for network_name, _ in f, a do + local item_network_name = network_name..":"..item_name + local stations = all_p_stations[item_network_name] + if stations == nil then + stations = {} + all_p_stations[item_network_name] = stations + end + stations[#stations + 1] = station_id + station.item_p_counts[item_name] = effective_item_count end - stations[#stations + 1] = station_id - station.item_p_counts[item_name] = effective_item_count else comb1_signals[k] = nil end end end - if is_requesting_nothing and station.display_state%2 == 1 then - station.display_state = station.display_state - 1 + end + if station.display_state > 1 then + if is_requesting_nothing and band(station.display_state, 2) > 0 then + station.display_state = station.display_state - 2 update_display(map_data, station) end + if band(station.display_state, 8) > 0 then + if band(station.display_state, 4) > 0 then + station.display_state = station.display_state - 4 + else + station.display_state = station.display_state - 8 + update_display(map_data, station) + end + elseif band(station.display_state, 4) > 0 then + station.display_state = station.display_state + 4 + end end return false end @@ -520,26 +653,38 @@ 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(map_data, train.entity) end interface_raise_train_stuck(train_id) end end ---@param map_data MapData -local function tick_poll_comb(map_data) +local function tick_poll_comb(map_data, mod_settings) local tick_data = map_data.tick_data --NOTE: the following has undefined behavior if last_comb is deleted local comb_id, comb = next(map_data.to_comb, tick_data.last_comb) tick_data.last_comb = comb_id + local refueler_id, _ = next(map_data.each_refuelers, tick_data.last_refueler) + tick_data.last_refueler = refueler_id if comb and comb.valid then combinator_update(map_data, comb, true) end + if refueler_id then + set_refueler_from_comb(map_data, mod_settings, refueler_id) + end end ---@param map_data MapData ---@param mod_settings CybersynModSettings function tick(map_data, mod_settings) map_data.total_ticks = map_data.total_ticks + 1 + + if map_data.active_alerts then + if map_data.total_ticks%(10*mod_settings.tps) < 1 then + process_active_alerts(map_data) + end + end + if map_data.tick_state == STATE_INIT then map_data.economy.all_p_stations = {} map_data.economy.all_r_stations = {} @@ -559,9 +704,7 @@ function tick(map_data, mod_settings) interface_raise_tick_init() tick_poll_train(map_data, mod_settings) tick_poll_comb(map_data) - end - - if map_data.tick_state == STATE_POLL_STATIONS then + elseif map_data.tick_state == STATE_POLL_STATIONS then for i = 1, mod_settings.update_rate do if tick_poll_station(map_data, mod_settings) then break end end diff --git a/cybersyn/scripts/constants.lua b/cybersyn/scripts/constants.lua index ee5eeb8..0d7307a 100644 --- a/cybersyn/scripts/constants.lua +++ b/cybersyn/scripts/constants.lua @@ -19,11 +19,13 @@ MODE_PRIMARY_IO_FAILED_REQUEST = "^" MODE_PRIMARY_IO_ACTIVE = "<<" MODE_SECONDARY_IO = "%" MODE_DEPOT = "+" -MODE_WAGON_MANIFEST = "-" +MODE_WAGON = "-" MODE_REFUELER = ">>" NETWORK_SIGNAL_DEFAULT = {name="signal-A", type="virtual"} +NETWORK_EACH = "signal-each" INACTIVITY_TIME = 100 +LOCK_TRAIN_TIME = 60*60*60*24*7 DELTA = 1/2048 diff --git a/cybersyn/scripts/factorio-api.lua b/cybersyn/scripts/factorio-api.lua index a982a0f..8593c35 100644 --- a/cybersyn/scripts/factorio-api.lua +++ b/cybersyn/scripts/factorio-api.lua @@ -1,7 +1,9 @@ --By Mami local get_distance = require("__flib__.misc").get_distance -local floor = math.floor local table_insert = table.insert +local bit_extract = bit32.extract +local bit_replace = bit32.replace + local DEFINES_WORKING = defines.entity_status.working local DEFINES_LOW_POWER = defines.entity_status.low_power local DEFINES_COMBINATOR_INPUT = defines.circuit_connector_id.combinator_input @@ -93,6 +95,26 @@ end function lock_train(train) train.manual_mode = true end +---@param train LuaTrain +function lock_train_to_depot(train) + local schedule = train.schedule + if schedule then + local record = schedule.records[schedule.current] + if record then + local wait = record.wait_conditions + if wait and wait[1] then + wait[1].ticks = LOCK_TRAIN_TIME + else + record.wait_conditions = {{type = "inactivity", compare_type = "and", ticks = LOCK_TRAIN_TIME}} + end + train.schedule = schedule + else + train.manual_mode = true + end + else + train.manual_mode = true + end +end ---@param train LuaTrain ---@param stop LuaEntity @@ -114,6 +136,7 @@ end function se_create_elevator_order(elevator_name, is_train_in_orbit) return {station = elevator_name..(is_train_in_orbit and SE_ELEVATOR_ORBIT_SUFFIX or SE_ELEVATOR_PLANET_SUFFIX)} end +---@param map_data MapData ---@param train LuaTrain ---@param depot_name string ---@param d_surface_i int @@ -121,7 +144,7 @@ end ---@param r_stop LuaEntity ---@param manifest Manifest ---@param start_at_depot boolean? -function set_manifest_schedule(train, depot_name, d_surface_i, p_stop, r_stop, manifest, start_at_depot) +function set_manifest_schedule(map_data, train, depot_name, d_surface_i, p_stop, r_stop, manifest, start_at_depot) --NOTE: can only return false if start_at_depot is false, it should be incredibly rare that this function returns false local old_schedule if not start_at_depot then @@ -137,13 +160,16 @@ function set_manifest_schedule(train, depot_name, d_surface_i, p_stop, r_stop, m local is_r_on_t = t_surface_i == r_surface_i local is_d_on_t = t_surface_i == d_surface_i if is_p_on_t and is_r_on_t and is_d_on_t then - train.schedule = {current = start_at_depot and 1 or 2--[[@as uint]], records = { - create_inactivity_order(depot_name), - create_direct_to_station_order(p_stop), - create_loading_order(p_stop, manifest), - create_direct_to_station_order(r_stop), - create_unloading_order(r_stop), - }} + train.schedule = { + current = start_at_depot and 1 or 2--[[@as uint]], + records = { + create_inactivity_order(depot_name), + create_direct_to_station_order(p_stop), + create_loading_order(p_stop, manifest), + create_direct_to_station_order(r_stop), + create_unloading_order(r_stop), + } + } if old_schedule and not train.has_path then train.schedule = old_schedule return false @@ -155,37 +181,39 @@ function set_manifest_schedule(train, depot_name, d_surface_i, p_stop, r_stop, m if (is_p_on_t or p_surface_i == other_surface_i) and (is_r_on_t or r_surface_i == other_surface_i) and (is_d_on_t or d_surface_i == other_surface_i) then local t_zone = remote.call("space-exploration", "get_zone_from_surface_index", {surface_index = t_surface_i})--[[@as {}]] local other_zone = remote.call("space-exploration", "get_zone_from_surface_index", {surface_index = other_surface_i})--[[@as {}]] - local is_train_in_orbit = other_zone.orbit_index == t_zone.index - if is_train_in_orbit or t_zone.orbit_index == other_zone.index then - local elevator_name = se_get_space_elevator_name(t_surface) - if elevator_name then - local records = {create_inactivity_order(depot_name)} - if t_surface_i == p_surface_i then - records[#records + 1] = create_direct_to_station_order(p_stop) - else - records[#records + 1] = se_create_elevator_order(elevator_name, is_train_in_orbit) - is_train_in_orbit = not is_train_in_orbit - end - records[#records + 1] = create_loading_order(p_stop, manifest) + if t_zone and other_zone then + local is_train_in_orbit = other_zone.orbit_index == t_zone.index + if is_train_in_orbit or t_zone.orbit_index == other_zone.index then + local elevator_name = se_get_space_elevator_name(t_surface) + if elevator_name then + local records = {create_inactivity_order(depot_name)} + if t_surface_i == p_surface_i then + records[#records + 1] = create_direct_to_station_order(p_stop) + else + records[#records + 1] = se_create_elevator_order(elevator_name, is_train_in_orbit) + is_train_in_orbit = not is_train_in_orbit + end + records[#records + 1] = create_loading_order(p_stop, manifest) - if p_surface_i ~= r_surface_i then - records[#records + 1] = se_create_elevator_order(elevator_name, is_train_in_orbit) - is_train_in_orbit = not is_train_in_orbit - elseif t_surface_i == r_surface_i then - records[#records + 1] = create_direct_to_station_order(r_stop) - end - records[#records + 1] = create_unloading_order(r_stop) - if r_surface_i ~= d_surface_i then - records[#records + 1] = se_create_elevator_order(elevator_name, is_train_in_orbit) - is_train_in_orbit = not is_train_in_orbit - end + if p_surface_i ~= r_surface_i then + records[#records + 1] = se_create_elevator_order(elevator_name, is_train_in_orbit) + is_train_in_orbit = not is_train_in_orbit + elseif t_surface_i == r_surface_i then + records[#records + 1] = create_direct_to_station_order(r_stop) + end + records[#records + 1] = create_unloading_order(r_stop) + if r_surface_i ~= d_surface_i then + records[#records + 1] = se_create_elevator_order(elevator_name, is_train_in_orbit) + is_train_in_orbit = not is_train_in_orbit + end - train.schedule = {current = start_at_depot and 1 or 2--[[@as uint]], records = records} - if old_schedule and not train.has_path then - train.schedule = old_schedule - return false - else - return true + train.schedule = {current = start_at_depot and 1 or 2--[[@as uint]], records = records} + if old_schedule and not train.has_path then + train.schedule = old_schedule + return false + else + return true + end end end end @@ -198,14 +226,14 @@ 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(map_data, train) return true end +---@param map_data MapData ---@param train LuaTrain ---@param stop LuaEntity ----@param depot_name string -function add_refueler_schedule(train, stop, depot_name) +function add_refueler_schedule(map_data, train, stop) local schedule = train.schedule or {current = 1, records = {}} local i = schedule.current if i == 1 then @@ -252,8 +280,8 @@ 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) train.schedule = schedule + send_alert_cannot_path_between_surfaces(map_data, train) end @@ -272,6 +300,131 @@ function get_comb_params(comb) return comb.get_or_create_control_behavior().parameters--[[@as ArithmeticCombinatorParameters]] end ---@param comb LuaEntity +function get_comb_network_name(comb) + local params = get_comb_params(comb) + local signal = params.first_signal + + return signal and signal.name or nil +end +---@param map_data MapData +---@param mod_settings CybersynModSettings +---@param id uint +function set_refueler_from_comb(map_data, mod_settings, id) + --NOTE: this does nothing to update currently active deliveries + local refueler = map_data.refuelers[id] + local params = get_comb_params(refueler.entity_comb) + local bits = params.second_constant or 0 + local signal = params.first_signal + local old_network = refueler.network_name + + refueler.network_name = signal and signal.name or nil + refueler.allows_all_trains = bit_extract(bits, 2) > 0 + refueler.priority = 0 + + if refueler.network_name == NETWORK_EACH then + map_data.each_refuelers[id] = true + refueler.network_flag = {} + else + map_data.each_refuelers[id] = nil + refueler.network_flag = mod_settings.network_flag + end + + local signals = refueler.entity_comb.get_merged_signals(DEFINES_COMBINATOR_INPUT) + if signals then + for k, v in pairs(signals) do + local item_name = v.signal.name + local item_type = v.signal.type + local item_count = v.count + if item_name then + if item_type == "virtual" then + if item_name == SIGNAL_PRIORITY then + refueler.priority = item_count + elseif refueler.network_name == NETWORK_EACH then + refueler.network_flag[item_name] = item_count + end + end + if item_name == refueler.network_name then + refueler.network_flag = item_count + end + end + end + end + + local f, a + if old_network == NETWORK_EACH then + f, a = pairs(refueler.network_flag--[[@as {[string]: int}]]) + elseif old_network ~= refueler.network_name then + f, a = once, old_network + else + f, a = once, nil + end + for network_name, _ in f, a do + local network = map_data.to_refuelers[network_name] + if network then + network[id] = nil + if next(network) == nil then + map_data.to_refuelers[network_name] = nil + end + end + end + + if refueler.network_name == NETWORK_EACH then + f, a = pairs(refueler.network_flag--[[@as {[string]: int}]]) + elseif old_network ~= refueler.network_name then + f, a = once, refueler.network_name + else + f, a = once, nil + end + for network_name, _ in f, a do + local network = map_data.to_refuelers[network_name] + if not network then + network = {} + map_data.to_refuelers[network_name] = network + end + network[id] = true + end +end + +---@param map_data MapData +---@param station Station +function update_display(map_data, station) + local comb = station.entity_comb1 + if comb.valid then + local control = get_comb_control(comb) + local params = control.parameters + --NOTE: the following check can cause a bug where the display desyncs if the player changes the operation of the combinator and then changes it back before the mod can notice, however removing it causes a bug where the user's change is overwritten and ignored. Everything's bad we need an event to catch copy-paste by blueprint. + if params.operation == MODE_PRIMARY_IO or params.operation == MODE_PRIMARY_IO_ACTIVE or params.operation == MODE_PRIMARY_IO_FAILED_REQUEST then + if station.display_state == 0 then + params.operation = MODE_PRIMARY_IO + elseif station.display_state%2 == 1 then + params.operation = MODE_PRIMARY_IO_ACTIVE + else + params.operation = MODE_PRIMARY_IO_FAILED_REQUEST + end + control.parameters = params + end + end +end + + +---@param station Station +function set_station_from_comb_state(station) + --NOTE: this does nothing to update currently active deliveries + local params = get_comb_params(station.entity_comb1) + local signal = params.first_signal + + local bits = params.second_constant or 0 + local is_pr_state = bit_extract(bits, 0, 2) + local allows_all_trains = bit_extract(bits, 2) > 0 + local is_stack = bit_extract(bits, 3) > 0 + + station.network_name = signal and signal.name or nil + station.allows_all_trains = allows_all_trains + station.is_stack = is_stack + station.is_p = (is_pr_state == 0 or is_pr_state == 1) or nil + station.is_r = (is_pr_state == 0 or is_pr_state == 2) or nil +end +---@param comb LuaEntity function get_comb_gui_settings(comb) local params = get_comb_params(comb) local op = params.operation @@ -279,8 +432,9 @@ function get_comb_gui_settings(comb) local selected_index = 0 local switch_state = "none" local bits = params.second_constant or 0 - local allows_all_trains = bits%2 == 1 - local is_pr_state = floor(bits/2)%3 + local is_pr_state = bit_extract(bits, 0, 2) + local allows_all_trains = bit_extract(bits, 2) > 0 + local is_stack = bit_extract(bits, 3) > 0 if is_pr_state == 0 then switch_state = "none" elseif is_pr_state == 1 then @@ -297,86 +451,10 @@ function get_comb_gui_settings(comb) selected_index = 3 elseif op == MODE_SECONDARY_IO then selected_index = 4 - elseif op == MODE_WAGON_MANIFEST then + elseif op == MODE_WAGON then selected_index = 5 end - return selected_index, params.first_signal, not allows_all_trains, switch_state -end ----@param comb LuaEntity -function get_comb_network_name(comb) - local params = get_comb_params(comb) - local signal = params.first_signal - - return signal and signal.name or nil -end ----@param station Station -function set_station_from_comb_state(station) - --NOTE: this does nothing to update currently active deliveries - local params = get_comb_params(station.entity_comb1) - local bits = params.second_constant or 0 - local is_pr_state = floor(bits/2)%3 - local signal = params.first_signal - station.network_name = signal and signal.name or nil - station.allows_all_trains = bits%2 == 1 - station.is_p = is_pr_state == 0 or is_pr_state == 1 - station.is_r = is_pr_state == 0 or is_pr_state == 2 -end ----@param mod_settings CybersynModSettings ----@param refueler Refueler -function set_refueler_from_comb(mod_settings, refueler) - --NOTE: this does nothing to update currently active deliveries - local params = get_comb_params(refueler.entity_comb) - local bits = params.second_constant or 0 - local signal = params.first_signal - refueler.network_name = signal and signal.name or nil - refueler.allows_all_trains = bits%2 == 1 - - local signals = refueler.entity_comb.get_merged_signals(DEFINES_COMBINATOR_INPUT) - refueler.priority = 0 - refueler.network_flag = mod_settings.network_flag - if not signals then return end - for k, v in pairs(signals) do - local item_name = v.signal.name - local item_count = v.count - if item_name then - if item_name == SIGNAL_PRIORITY then - refueler.priority = item_count - end - if item_name == refueler.network_name then - refueler.network_flag = item_count - end - end - end -end - ----@param map_data MapData ----@param station Station -function update_display(map_data, station) - local comb = station.entity_comb1 - if comb.valid then - local control = get_comb_control(comb) - local params = control.parameters - --NOTE: the following check can cause a bug where the display desyncs if the player changes the operation of the combinator and then changes it back before the mod can notice, however removing it causes a bug where the user's change is overwritten and ignored. Everything's bad we need an event to catch copy-paste by blueprint. - if params.operation == MODE_PRIMARY_IO or params.operation == MODE_PRIMARY_IO_ACTIVE or params.operation == MODE_PRIMARY_IO_FAILED_REQUEST then - if station.display_state >= 2 then - params.operation = MODE_PRIMARY_IO_ACTIVE - elseif station.display_state == 1 then - params.operation = MODE_PRIMARY_IO_FAILED_REQUEST - else - params.operation = MODE_PRIMARY_IO - end - control.parameters = params - end - end -end ----@param comb LuaEntity ----@param allows_all_trains boolean -function set_comb_allows_all_trains(comb, allows_all_trains) - local control = get_comb_control(comb) - local param = control.parameters - local bits = param.second_constant or 0 - param.second_constant = (bits - bits%2) + (allows_all_trains and 1 or 0) - control.parameters = param + return selected_index, params.first_signal, switch_state, not allows_all_trains, is_stack end ---@param comb LuaEntity ---@param is_pr_state 0|1|2 @@ -384,7 +462,28 @@ function set_comb_is_pr_state(comb, is_pr_state) local control = get_comb_control(comb) local param = control.parameters local bits = param.second_constant or 0 - param.second_constant = (bits%2) + (2*is_pr_state) + + param.second_constant = bit_replace(bits, is_pr_state, 0, 2) + control.parameters = param +end +---@param comb LuaEntity +---@param allows_all_trains boolean +function set_comb_allows_all_trains(comb, allows_all_trains) + local control = get_comb_control(comb) + local param = control.parameters + local bits = param.second_constant or 0 + + param.second_constant = bit_replace(bits, allows_all_trains and 1 or 0, 2) + control.parameters = param +end +---@param comb LuaEntity +---@param is_stack boolean +function set_comb_is_stack(comb, is_stack) + local control = get_comb_control(comb) + local param = control.parameters + local bits = param.second_constant or 0 + + param.second_constant = bit_replace(bits, is_stack and 1 or 0, 3) control.parameters = param end ---@param comb LuaEntity @@ -459,92 +558,156 @@ end ---@param train LuaTrain ---@param icon {} ----@param message {} -local function send_alert_with_sound(train, icon, message) +---@param message string +local function send_alert_for_train(train, icon, message) local loco = train.front_stock or train.back_stock if loco then for _, player in pairs(loco.force.players) do player.add_custom_alert( loco, icon, - message, + {message}, true) + end + end +end +local send_alert_about_missing_train_icon = {name = MISSING_TRAIN_NAME, type = "fluid"} +---@param r_stop LuaEntity +---@param p_stop LuaEntity +---@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_alert_about_missing_train_icon, + {message, r_stop.backer_name, p_stop.backer_name}, + true) + end +end + +---@param train LuaTrain +function send_alert_sounds(train) + local loco = train.front_stock or train.back_stock + if loco then + for _, player in pairs(loco.force.players) do player.play_sound({path = ALERT_SOUND}) end end end -local send_missing_train_alert_for_stop_icon = {name = MISSING_TRAIN_NAME, type = "fluid"} + ---@param r_stop LuaEntity ---@param p_stop LuaEntity -function send_missing_train_alert(r_stop, p_stop) - 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}, - true) - end +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"} + +local send_stuck_train_alert_icon = {name = LOST_TRAIN_NAME, type = "fluid"} +---@param map_data MapData ---@param train LuaTrain ----@param depot_name string -function send_cannot_path_between_surfaces_alert(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) - 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) - 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) - 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) - send_alert_with_sound(train, send_lost_train_alert_icon, {"cybersyn-messages.train-at-incorrect", depot_name}) +function send_alert_stuck_train(map_data, train) + send_alert_for_train(train, send_stuck_train_alert_icon, "cybersyn-messages.stuck-train") + map_data.active_alerts = map_data.active_alerts or {} + map_data.active_alerts[train.id] = {train, 1, map_data.total_ticks} end local send_nonempty_train_in_depot_alert_icon = {name = NONEMPTY_TRAIN_NAME, type = "fluid"} +---@param map_data MapData ---@param train LuaTrain -function send_nonempty_train_in_depot_alert(train) - send_alert_with_sound(train, send_nonempty_train_in_depot_alert_icon, {"cybersyn-messages.nonempty-train"}) +function send_alert_nonempty_train_in_depot(map_data, train) + send_alert_for_train(train, send_nonempty_train_in_depot_alert_icon, "cybersyn-messages.nonempty-train") + send_alert_sounds(train) + map_data.active_alerts = map_data.active_alerts or {} + map_data.active_alerts[train.id] = {train, 2, map_data.total_ticks} +end + +local send_lost_train_alert_icon = {name = LOST_TRAIN_NAME, type = "fluid"} +---@param map_data MapData +---@param train LuaTrain +function send_alert_depot_of_train_broken(map_data, train) + send_alert_for_train(train, send_lost_train_alert_icon, "cybersyn-messages.depot-broken") + send_alert_sounds(train) + map_data.active_alerts = map_data.active_alerts or {} + map_data.active_alerts[train.id] = {train, 3, map_data.total_ticks} +end +---@param map_data MapData +---@param train LuaTrain +function send_alert_station_of_train_broken(map_data, train) + send_alert_for_train(train, send_lost_train_alert_icon, "cybersyn-messages.station-broken") + send_alert_sounds(train) + map_data.active_alerts = map_data.active_alerts or {} + map_data.active_alerts[train.id] = {train, 4, map_data.total_ticks} +end +---@param map_data MapData +---@param train LuaTrain +function send_alert_refueler_of_train_broken(map_data, train) + send_alert_for_train(train, send_lost_train_alert_icon, "cybersyn-messages.refueler-broken") + send_alert_sounds(train) + map_data.active_alerts = map_data.active_alerts or {} + map_data.active_alerts[train.id] = {train, 5, map_data.total_ticks} +end +---@param map_data MapData +---@param train LuaTrain +function send_alert_train_at_incorrect_station(map_data, train) + send_alert_for_train(train, send_lost_train_alert_icon, "cybersyn-messages.train-at-incorrect") + send_alert_sounds(train) + map_data.active_alerts = map_data.active_alerts or {} + map_data.active_alerts[train.id] = {train, 6, map_data.total_ticks} +end +---@param map_data MapData +---@param train LuaTrain +function send_alert_cannot_path_between_surfaces(map_data, train) + send_alert_for_train(train, send_lost_train_alert_icon, "cybersyn-messages.cannot-path-between-surfaces") + send_alert_sounds(train) + map_data.active_alerts = map_data.active_alerts or {} + map_data.active_alerts[train.id] = {train, 7, map_data.total_ticks} end ---@param train LuaTrain -function send_unexpected_train_alert(train) - local loco = train.front_stock or train.back_stock - if loco then - for _, player in pairs(loco.force.players) do - player.add_custom_alert( - loco, - send_lost_train_alert_icon, - {"cybersyn-messages.unexpected-train"}, - true) - end - end -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) - local loco = train.front_stock or train.back_stock - if loco then - for _, player in pairs(loco.force.players) do - player.add_custom_alert( - loco, - send_stuck_train_alert_icon, - {"cybersyn-messages.stuck-train", depot_name}, - true) +function send_alert_unexpected_train(train) + send_alert_for_train(train, send_lost_train_alert_icon, "cybersyn-messages.unexpected-train") +end + + +---@param map_data MapData +function process_active_alerts(map_data) + for train_id, data in pairs(map_data.active_alerts) do + local train = data[1] + if train.valid then + local id = data[2] + if id == 1 then + send_alert_for_train(train, send_stuck_train_alert_icon, "cybersyn-messages.stuck-train") + elseif id == 2 then + send_alert_for_train(train, send_nonempty_train_in_depot_alert_icon, "cybersyn-messages.nonempty-train") + elseif id == 3 then + send_alert_for_train(train, send_lost_train_alert_icon, "cybersyn-messages.depot-broken") + elseif id == 4 then + send_alert_for_train(train, send_lost_train_alert_icon, "cybersyn-messages.station-broken") + elseif id == 5 then + send_alert_for_train(train, send_lost_train_alert_icon, "cybersyn-messages.refueler-broken") + elseif id == 6 then + send_alert_for_train(train, send_lost_train_alert_icon, "cybersyn-messages.train-at-incorrect") + elseif id == 7 then + send_alert_for_train(train, send_lost_train_alert_icon, "cybersyn-messages.cannot-path-between-surfaces") + end + else + map_data.active_alerts[train_id] = nil end end end diff --git a/cybersyn/scripts/global.lua b/cybersyn/scripts/global.lua index a9be46c..5ddba18 100644 --- a/cybersyn/scripts/global.lua +++ b/cybersyn/scripts/global.lua @@ -19,15 +19,17 @@ ---@field public tick_state uint ---@field public tick_data {} ---@field public economy Economy ----@field public se_tele_old_id {[string]: uint} +---@field public each_refuelers {[uint]: true} +---@field public active_alerts {[uint]: {[1]: LuaTrain, [2]: int}}? ---@class Station ---@field public entity_stop LuaEntity ---@field public entity_comb1 LuaEntity ---@field public entity_comb2 LuaEntity? ----@field public is_p boolean ----@field public is_r boolean ----@field public allows_all_trains boolean +---@field public is_p true? +---@field public is_r true? +---@field public is_stack true? +---@field public allows_all_trains true? ---@field public deliveries_total int ---@field public last_delivery_tick int ---@field public priority int --transient @@ -35,7 +37,7 @@ ---@field public r_threshold int >= 0 --transient ---@field public locked_slots int >= 0 --transient ---@field public network_name string? ----@field public network_flag int --transient +---@field public network_flag int|{[string]: int} --transient ---@field public wagon_combs {[int]: LuaEntity}?--NOTE: allowed to be invalid entities or combinators with the wrong operation, these must be checked and lazy deleted when found ---@field public deliveries {[string]: int} ---@field public accepted_layouts {[uint]: true?} @@ -43,7 +45,7 @@ ---@field public tick_signals {[uint]: Signal}? --transient ---@field public item_p_counts {[string]: int} --transient ---@field public item_thresholds {[string]: int}? --transient ----@field public display_state 0|1|2|3 --low bit is if this station's request has failed, high bit is if a train is heading to this station +---@field public display_state int ---@class Depot ---@field public entity_stop LuaEntity @@ -57,10 +59,10 @@ ---@field public accepted_layouts {[uint]: true?} ---@field public layout_pattern (0|1|2|3)[]? ---@field public wagon_combs {[int]: LuaEntity}?--NOTE: allowed to be invalid entities or combinators with the wrong operation, these must be checked and lazy deleted when found ----@field public allows_all_trains boolean +---@field public allows_all_trains true? ---@field public priority int ---@field public network_name string? ----@field public network_flag int +---@field public network_flag int|{[string]: int} ---@class Train ---@field public entity LuaTrain --should only be invalid if se_is_being_teleported is true @@ -144,9 +146,13 @@ function init_global() global.layout_top_id = 1 global.refuelers = {} global.to_refuelers = {} + global.each_refuelers = {} IS_SE_PRESENT = remote.interfaces["space-exploration"] ~= nil - if IS_SE_PRESENT then - global.se_tele_old_id = {} - end +end + +---@param v string +---@param h string? +function once(v, h) + return not h and v or nil--[[@as string|nil]] end diff --git a/cybersyn/scripts/gui.lua b/cybersyn/scripts/gui.lua index 58607b8..a75c2e7 100644 --- a/cybersyn/scripts/gui.lua +++ b/cybersyn/scripts/gui.lua @@ -26,13 +26,34 @@ STATUS_NAMES[defines.entity_status.disabled_by_script] = "entity-status.disabled STATUS_NAMES[defines.entity_status.marked_for_deconstruction] = "entity-status.marked-for-deconstruction" STATUS_NAMES_DEFAULT = "entity-status.disabled" +---@param main_window LuaGuiElement +---@param selected_index int +local function set_visibility(main_window, selected_index) + local uses_network = selected_index == 1 or selected_index == 2 or selected_index == 3 + local uses_allow_list = selected_index == 1 or selected_index == 3 + local is_station = selected_index == 1 + + local vflow = main_window.frame.vflow--[[@as LuaGuiElement]] + local top_flow = vflow.top--[[@as LuaGuiElement]] + local bottom_flow = vflow.bottom--[[@as LuaGuiElement]] + local right_flow = bottom_flow.right--[[@as LuaGuiElement]] + + top_flow.is_pr_switch.visible = is_station + vflow.network_label.visible = uses_network + bottom_flow.network.visible = uses_network + right_flow.allow_list.visible = uses_allow_list + --right_flow.allow_list_label.visible = uses_allow_list + right_flow.is_stack.visible = is_station + --right_flow.is_stack_label.visible = is_station +end + ---@param comb LuaEntity ---@param player LuaPlayer function gui_opened(comb, player) combinator_update(global, comb, true) local rootgui = player.gui.screen - local selected_index, signal, check, switch_state = get_comb_gui_settings(comb) + local selected_index, signal, switch_state, allow_list, is_stack = get_comb_gui_settings(comb) local window = flib_gui.build(rootgui, { {type="frame", direction="vertical", ref={"main_window"}, name=COMBINATOR_NAME, children={ @@ -44,8 +65,8 @@ function gui_opened(comb, player) on_click = {"close", comb.unit_number} }} }}, - {type="frame", style="inside_shallow_frame_with_padding", style_mods={padding=12}, children={ - {type="flow", direction="vertical", style_mods={horizontal_align="left"}, children={ + {type="frame", name="frame", style="inside_shallow_frame_with_padding", style_mods={padding=12, bottom_padding=9}, children={ + {type="flow", name="vflow", direction="vertical", style_mods={horizontal_align="left"}, children={ --status {type="flow", style="status_flow", direction="horizontal", style_mods={vertical_align="center", horizontally_stretchable=true, bottom_padding=4}, children={ {type="sprite", sprite=STATUS_SPRITES[comb.status] or STATUS_SPRITES_DEFAULT, style="status_image", ref={"status_icon"}, style_mods={stretch_image_to_widget_size=true}}, @@ -67,21 +88,31 @@ function gui_opened(comb, player) {"cybersyn-gui.comb2"}, {"cybersyn-gui.wagon-manifest"}, }}, - {type="switch", name="switch", ref={"switch"}, allow_none_state=true, switch_state=switch_state, left_label_caption={"cybersyn-gui.switch-provide"}, right_label_caption={"cybersyn-gui.switch-request"}, left_label_tooltip={"cybersyn-gui.switch-provide-tooltip"}, right_label_tooltip={"cybersyn-gui.switch-request-tooltip"}, actions={ - on_switch_state_changed={"switch", comb.unit_number} + {type="switch", name="is_pr_switch", ref={"is_pr_switch"}, allow_none_state=true, switch_state=switch_state, left_label_caption={"cybersyn-gui.switch-provide"}, right_label_caption={"cybersyn-gui.switch-request"}, left_label_tooltip={"cybersyn-gui.switch-provide-tooltip"}, right_label_tooltip={"cybersyn-gui.switch-request-tooltip"}, actions={ + on_switch_state_changed={"is_pr_switch", comb.unit_number} }} }}, ---choose-elem-button {type="line", style_mods={top_padding=10}}, {type="label", name="network_label", ref={"network_label"}, style="heading_3_label", caption={"cybersyn-gui.network"}, style_mods={top_padding=8}}, {type="flow", name="bottom", direction="horizontal", style_mods={vertical_align="center"}, children={ - {type="choose-elem-button", name="network", style="slot_button_in_shallow_frame", ref={"network"}, elem_type="signal", tooltip={"cybersyn-gui.network-tooltip"}, signal=signal, style_mods={bottom_margin=1, right_margin=6}, actions={ + {type="choose-elem-button", name="network", style="slot_button_in_shallow_frame", ref={"network"}, elem_type="signal", tooltip={"cybersyn-gui.network-tooltip"}, signal=signal, style_mods={bottom_margin=1, right_margin=6, top_margin=2}, actions={ on_elem_changed={"choose-elem-button", comb.unit_number} }}, - {type="checkbox", name="radio_button", ref={"radio_button"}, state=check, tooltip={"cybersyn-gui.auto-tooltip"}, actions={ - on_checked_state_changed={"radio_button", comb.unit_number} - }}, - {type="label", name="radio_label", style_mods={left_padding=3}, ref={"radio_label"}, caption={"cybersyn-gui.auto-description"}}, + {type="flow", name="right", direction="vertical", style_mods={horizontal_align="left"}, children={ + {type="flow", name="allow_list", direction="horizontal", style_mods={vertical_align="center"}, children={ + {type="checkbox", name="allow_list", ref={"allow_list"}, state=allow_list, tooltip={"cybersyn-gui.allow-list-tooltip"}, actions={ + on_checked_state_changed={"allow_list", comb.unit_number} + }}, + {type="label", name="allow_list_label", style_mods={left_padding=3}, ref={"allow_list_label"}, caption={"cybersyn-gui.allow-list-description"}}, + }}, + {type="flow", name="is_stack", direction="horizontal", style_mods={vertical_align="center"}, children={ + {type="checkbox", name="is_stack", ref={"is_stack"}, state=is_stack, tooltip={"cybersyn-gui.is-stack-tooltip"}, actions={ + on_checked_state_changed={"is_stack", comb.unit_number} + }}, + {type="label", name="is_stack_label", style_mods={left_padding=3}, ref={"is_stack_label"}, caption={"cybersyn-gui.is-stack-description"}}, + }}, + }} }} }} }} @@ -91,14 +122,8 @@ function gui_opened(comb, player) window.preview.entity = comb window.titlebar.drag_target = window.main_window window.main_window.force_auto_center() - local uses_network = selected_index == 1 or selected_index == 2 or selected_index == 3 - local uses_allow_list = selected_index == 1 or selected_index == 3 - window.network.visible = uses_network - window.network_label.visible = uses_network - window.radio_button.visible = uses_allow_list - window.radio_label.visible = uses_allow_list - window.switch.visible = selected_index == 1 + set_visibility(window.main_window, selected_index) player.opened = window.main_window end @@ -142,45 +167,25 @@ function register_gui_actions() local comb = global.to_comb[msg[2]] if not comb or not comb.valid then return end - local top_flow = element.parent - local all_flow = top_flow.parent - local bottom_flow = all_flow.bottom - local param + set_visibility(rootgui[COMBINATOR_NAME], element.selected_index) + if element.selected_index == 1 then set_comb_operation(comb, MODE_PRIMARY_IO) - top_flow["switch"].visible = true - all_flow["network_label"].visible = true - bottom_flow["network"].visible = true - bottom_flow["radio_button"].visible = true - bottom_flow["radio_label"].visible = true elseif element.selected_index == 2 then set_comb_operation(comb, MODE_DEPOT) - top_flow["switch"].visible = false - all_flow["network_label"].visible = true - bottom_flow["network"].visible = true - bottom_flow["radio_button"].visible = false - bottom_flow["radio_label"].visible = false + --prevent the use of the each signal with depots + local network = element.parent.parent.bottom.network + local signal = network.elem_value + if signal.name == NETWORK_EACH then + network.elem_value = nil + set_comb_network_name(comb, nil) + end elseif element.selected_index == 3 then set_comb_operation(comb, MODE_REFUELER) - top_flow["switch"].visible = false - all_flow["network_label"].visible = true - bottom_flow["network"].visible = true - bottom_flow["radio_button"].visible = true - bottom_flow["radio_label"].visible = true elseif element.selected_index == 4 then set_comb_operation(comb, MODE_SECONDARY_IO) - top_flow["switch"].visible = false - all_flow["network_label"].visible = false - bottom_flow["network"].visible = false - bottom_flow["radio_button"].visible = false - bottom_flow["radio_label"].visible = false elseif element.selected_index == 5 then - set_comb_operation(comb, MODE_WAGON_MANIFEST) - top_flow["switch"].visible = false - all_flow["network_label"].visible = false - bottom_flow["network"].visible = false - bottom_flow["radio_button"].visible = false - bottom_flow["radio_label"].visible = false + set_comb_operation(comb, MODE_WAGON) else return end @@ -192,15 +197,21 @@ function register_gui_actions() local comb = global.to_comb[msg[2]] if not comb or not comb.valid then return end + local param = get_comb_params(comb) + local signal = element.elem_value if signal and (signal.name == "signal-everything" or signal.name == "signal-anything" or signal.name == "signal-each") then - signal = nil - element.elem_value = nil + if param.operation == MODE_PRIMARY_IO or param.operation == MODE_PRIMARY_IO_ACTIVE or param.operation == MODE_PRIMARY_IO_FAILED_REQUEST or param.operation == MODE_REFUELER then + signal.name = NETWORK_EACH + else + signal = nil + end + element.elem_value = signal end set_comb_network_name(comb, signal) combinator_update(global, comb) - elseif msg[1] == "radio_button" then + elseif msg[1] == "allow_list" then local element = event.element if not element then return end local comb = global.to_comb[msg[2]] @@ -210,7 +221,17 @@ function register_gui_actions() set_comb_allows_all_trains(comb, allows_all_trains) combinator_update(global, comb) - elseif msg[1] == "switch" then + elseif msg[1] == "is_stack" then + local element = event.element + if not element then return end + local comb = global.to_comb[msg[2]] + if not comb or not comb.valid then return end + + local is_stack = element.state + set_comb_is_stack(comb, is_stack) + + combinator_update(global, comb) + elseif msg[1] == "is_pr_switch" then local element = event.element if not element then return end local comb = global.to_comb[msg[2]] diff --git a/cybersyn/scripts/layout.lua b/cybersyn/scripts/layout.lua index 30baf6b..90c7b32 100644 --- a/cybersyn/scripts/layout.lua +++ b/cybersyn/scripts/layout.lua @@ -87,6 +87,19 @@ function remove_train(map_data, train_id, train) depot.available_train_id = nil end remove_available_train(map_data, train_id, train) + + local layout_id = train.layout_id + local count = global.layout_train_count[layout_id] + if count <= 1 then + global.layout_train_count[layout_id] = nil + global.layouts[layout_id] = nil + for _, station in pairs(global.stations) do + station.accepted_layouts[layout_id] = nil + end + else + global.layout_train_count[layout_id] = count - 1 + end + map_data.trains[train_id] = nil interface_raise_train_removed(train_id, train) end @@ -519,7 +532,7 @@ function reset_stop_layout(map_data, stop, is_station_or_refueler, forbidden_ent end elseif entity.name == COMBINATOR_NAME then local param = map_data.to_comb_params[entity.unit_number] - if param.operation == MODE_WAGON_MANIFEST then + if param.operation == MODE_WAGON then local pos = entity.position local is_there if is_ver then diff --git a/cybersyn/scripts/log.lua b/cybersyn/scripts/log.lua new file mode 100644 index 0000000..36acc00 --- /dev/null +++ b/cybersyn/scripts/log.lua @@ -0,0 +1,9 @@ + + + + +function log_dispatch(network_name, r_stop, p_stop) +end + +function log_refuel(network_name, refuel_stop) +end diff --git a/cybersyn/scripts/main.lua b/cybersyn/scripts/main.lua index 79261fb..9983db6 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(map_data, train.entity) remove_train(map_data, train_id, train) end map_data.depots[depot_id] = nil @@ -50,18 +50,10 @@ local function on_refueler_built(map_data, stop, comb) --network_name = set_refueler_from_comb, --network_flag = set_refueler_from_comb, } - set_refueler_from_comb(mod_settings, refueler) local id = stop.unit_number--[[@as uint]] map_data.refuelers[id] = refueler + set_refueler_from_comb(map_data, mod_settings, id) update_stop_if_auto(map_data, refueler, false) - if refueler.network_name then - local network = map_data.to_refuelers[refueler.network_name] - if not network then - network = {} - map_data.to_refuelers[refueler.network_name] = network - end - network[id] = true - end interface_raise_refueler_created(id) end ---@param map_data MapData @@ -76,20 +68,29 @@ 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(map_data, train.entity) else train.se_awaiting_removal = train_id end end end end - if refueler.network_name then - local network = map_data.to_refuelers[refueler.network_name] - network[refueler_id] = nil - if next(network) == nil then - map_data.to_refuelers[refueler.network_name] = nil + local f, a + if refueler.network_name == NETWORK_EACH then + f, a = pairs(refueler.network_flag--[[@as {[string]: int}]]) + else + f, a = once, refueler.network_name + end + for network_name, _ in f, a do + local network = map_data.to_refuelers[network_name] + if network then + network[refueler_id] = nil + if next(network) == nil then + map_data.to_refuelers[network_name] = nil + end end end + map_data.each_refuelers[refueler_id] = nil map_data.refuelers[refueler_id] = nil interface_raise_refueler_removed(refueler_id, refueler) end @@ -151,7 +152,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(map_data, train.entity) else train.se_awaiting_removal = train_id end @@ -246,7 +247,7 @@ local function on_combinator_built(map_data, comb) params.operation = op params.first_signal = NETWORK_SIGNAL_DEFAULT control.parameters = params - elseif op ~= MODE_PRIMARY_IO and op ~= MODE_SECONDARY_IO and op ~= MODE_DEPOT and op ~= MODE_REFUELER and op ~= MODE_WAGON_MANIFEST then + elseif op ~= MODE_PRIMARY_IO and op ~= MODE_SECONDARY_IO and op ~= MODE_DEPOT and op ~= MODE_REFUELER and op ~= MODE_WAGON then op = MODE_PRIMARY_IO params.operation = op control.parameters = params @@ -257,7 +258,7 @@ local function on_combinator_built(map_data, comb) map_data.to_output[comb.unit_number] = out map_data.to_stop[comb.unit_number] = stop - if op == MODE_WAGON_MANIFEST then + if op == MODE_WAGON then if rail then update_stop_from_rail(map_data, rail, nil, true) end @@ -297,55 +298,6 @@ local function on_combinator_built(map_data, comb) end ---@param map_data MapData ---@param comb LuaEntity ----@param network_name string? -function on_combinator_network_updated(map_data, comb, network_name) - local stop = map_data.to_stop[comb.unit_number] - - if stop and stop.valid then - local id = stop.unit_number - local station = map_data.stations[id] - if station then - if station.entity_comb1 == comb then - station.network_name = network_name - end - else - local depot = map_data.depots[id] - if depot then - if depot.entity_comb == comb then - local train_id = depot.available_train_id - if train_id then - local train = map_data.trains[train_id] - remove_available_train(map_data, train_id, train) - add_available_train_to_depot(map_data, mod_settings, train_id, train, id, depot) - interface_raise_train_status_changed(train_id, STATUS_D, STATUS_D) - end - end - else - local refueler = map_data.refuelers[id] - if refueler and refueler.entity_comb == comb then - if refueler.network_name then - local network = map_data.to_refuelers[refueler.network_name] - network[id] = nil - if next(network) == nil then - map_data.to_refuelers[refueler.network_name] = nil - end - end - refueler.network_name = network_name - if network_name then - local network = map_data.to_refuelers[network_name] - if not network then - network = {} - map_data.to_refuelers[network_name] = network - end - network[id] = true - end - end - end - end - end -end ----@param map_data MapData ----@param comb LuaEntity function on_combinator_broken(map_data, comb) --NOTE: we do not check for wagon manifest combinators and update their stations, it is assumed they will be lazy deleted later ---@type uint @@ -411,12 +363,12 @@ function combinator_update(map_data, comb, reset_display) station = map_data.stations[id] if should_reset and station and station.entity_comb1 == comb then --make sure only MODE_PRIMARY_IO gets stored on map_data.to_comb_params - if station.display_state >= 2 then - params.operation = MODE_PRIMARY_IO_ACTIVE - elseif station.display_state == 1 then - params.operation = MODE_PRIMARY_IO_FAILED_REQUEST - else + if station.display_state == 0 then params.operation = MODE_PRIMARY_IO + elseif station.display_state%2 == 1 then + params.operation = MODE_PRIMARY_IO_ACTIVE + else + params.operation = MODE_PRIMARY_IO_FAILED_REQUEST end control.parameters = params should_reset = false @@ -441,7 +393,35 @@ function combinator_update(map_data, comb, reset_display) local old_network = old_signal and old_signal.name or nil if new_network ~= old_network then has_changed = true - on_combinator_network_updated(map_data, comb, new_network) + + local stop = map_data.to_stop[comb.unit_number] + if stop and stop.valid then + id = stop.unit_number + station = map_data.stations[id] + if station then + if station.entity_comb1 == comb then + station.network_name = new_network + end + else + local depot = map_data.depots[id] + if depot then + if depot.entity_comb == comb then + local train_id = depot.available_train_id + if train_id then + local train = map_data.trains[train_id] + remove_available_train(map_data, train_id, train) + add_available_train_to_depot(map_data, mod_settings, train_id, train, id, depot) + interface_raise_train_status_changed(train_id, STATUS_D, STATUS_D) + end + end + else + local refueler = map_data.refuelers[id] + if refueler and refueler.entity_comb == comb then + set_refueler_from_comb(map_data, mod_settings, id) + end + end + end + end end if params.second_constant ~= old_params.second_constant then has_changed = true @@ -455,7 +435,7 @@ function combinator_update(map_data, comb, reset_display) local refueler = map_data.refuelers[id] if refueler then local pre = refueler.allows_all_trains - set_refueler_from_comb(mod_settings, refueler) + set_refueler_from_comb(map_data, mod_settings, id) if refueler.allows_all_trains ~= pre then update_stop_if_auto(map_data, refueler, false) end @@ -715,7 +695,6 @@ local function setup_se_compat() if not train then return end --NOTE: IMPORTANT, until se_on_train_teleport_finished_event is called map_data.trains[old_id] will reference an invalid train entity; our events have either been set up to account for this or should be impossible to trigger until teleportation is finished train.se_is_being_teleported = true - map_data.se_tele_old_id[train_unique_identifier] = old_id interface_raise_train_teleport_started(old_id) end) ---@param event {} @@ -731,8 +710,7 @@ local function setup_se_compat() --NOTE: event.old_train_id_1 from this event is useless, it's for one of the many transient trains SE spawns while teleporting the old train, only se_on_train_teleport_started_event returns the correct old train id --NOTE: please SE dev if you read this fix the issue where se_on_train_teleport_finished_event is returning the wrong old train id - local old_id = map_data.se_tele_old_id[train_unique_identifier] - map_data.se_tele_old_id[train_unique_identifier] = nil + local old_id = event.old_train_id_1 local train = map_data.trains[old_id] if not train then return end @@ -752,7 +730,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(map_data, train.entity) 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/migrations.lua b/cybersyn/scripts/migrations.lua index bb6e7a0..f922710 100644 --- a/cybersyn/scripts/migrations.lua +++ b/cybersyn/scripts/migrations.lua @@ -2,17 +2,9 @@ local flib_migration = require("__flib__.migration") local migrations_table = { - ["1.0.3"] = function() - ---@type MapData - local map_data = global - map_data.tick_state = STATE_INIT - map_data.tick_data = {} - end, ["1.0.6"] = function() ---@type MapData local map_data = global - map_data.tick_state = STATE_INIT - map_data.tick_data = {} for k, v in pairs(map_data.available_trains) do for id, _ in pairs(v) do local train = map_data.trains[id] @@ -29,8 +21,6 @@ local migrations_table = { ["1.0.7"] = function() ---@type MapData local map_data = global - map_data.tick_state = STATE_INIT - map_data.tick_data = {} map_data.available_trains = {} for id, v in pairs(map_data.trains) do v.parked_at_depot_id = v.depot_id @@ -53,8 +43,6 @@ local migrations_table = { ["1.0.8"] = function() ---@type MapData local map_data = global - map_data.tick_state = STATE_INIT - map_data.tick_data = {} for id, station in pairs(map_data.stations) do local params = get_comb_params(station.entity_comb1) if params.operation == MODE_PRIMARY_IO_FAILED_REQUEST then @@ -71,8 +59,6 @@ local migrations_table = { ["1.1.0"] = function() ---@type MapData local map_data = global - map_data.tick_state = STATE_INIT - map_data.tick_data = {} map_data.refuelers = {} map_data.to_refuelers = {} for id, station in pairs(map_data.stations) do @@ -98,27 +84,64 @@ local migrations_table = { ["1.1.2"] = function() ---@type MapData local map_data = global - map_data.tick_state = STATE_INIT - map_data.tick_data = {} map_data.refuelers = map_data.refuelers or {} map_data.to_refuelers = map_data.to_refuelers or {} end, ["1.1.3"] = function() ---@type MapData local map_data = global - map_data.tick_state = STATE_INIT - map_data.tick_data = {} for k, v in pairs(map_data.refuelers) do if not v.entity_comb.valid or not v.entity_stop.valid then map_data.refuelers[k] = nil end end end, + ["1.2.0"] = function() + ---@type MapData + local map_data = global + + map_data.each_refuelers = {} + map_data.se_tele_old_id = nil + + for k, comb in pairs(map_data.to_comb) do + local control = get_comb_control(comb) + local params = control.parameters + local bits = params.second_constant or 0 + local allows_all_trains = bits%2 + local is_pr_state = math.floor(bits/2)%3 + + local new_bits = bit32.bor(is_pr_state, allows_all_trains*4) + params.second_constant = new_bits + + control.parameters = params + end + for id, station in pairs(map_data.stations) do + station.display_state = (station.display_state >= 2 and 1 or 0) + (station.display_state%2)*2 + + set_station_from_comb_state(station) + update_stop_if_auto(map_data, station, true) + end + + map_data.layout_train_count = {} + for id, train in pairs(map_data.trains) do + map_data.layout_train_count[train.layout_id] = (map_data.layout_train_count[train.layout_id] or 0) + 1 + end + for layout_id, _ in pairs(map_data.layouts) do + if not map_data.layout_train_count[layout_id] then + map_data.layouts[layout_id] = nil + for id, station in pairs(map_data.stations) do + station.accepted_layouts[layout_id] = nil + end + end + end + end, } --STATUS_R_TO_D = 5 ---@param data ConfigurationChangedData function on_config_changed(data) + global.tick_state = STATE_INIT + global.tick_data = {} flib_migration.on_config_changed(data, migrations_table) IS_SE_PRESENT = remote.interfaces["space-exploration"] ~= nil 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..113bb20 100644 --- a/cybersyn/scripts/train-events.lua +++ b/cybersyn/scripts/train-events.lua @@ -1,6 +1,7 @@ --By Mami local min = math.min local INF = math.huge +local btest = bit32.btest ---@param map_data MapData ---@param station Station @@ -155,7 +156,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 @@ -167,9 +168,9 @@ local function on_train_arrives_depot(map_data, depot_id, train_entity) else --train still has cargo if mod_settings.react_to_nonempty_train_in_depot then - lock_train(train_entity) + lock_train_to_depot(train_entity) remove_train(map_data, train_id, train) - send_nonempty_train_in_depot_alert(train_entity) + send_alert_nonempty_train_in_depot(map_data, train_entity) end interface_raise_train_nonempty_in_depot(depot_id, train_entity, train_id) end @@ -201,8 +202,7 @@ local function on_train_arrives_depot(map_data, depot_id, train_entity) interface_raise_train_created(train_id, depot_id) 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(map_data, 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(map_data, train.entity) 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(map_data, train.entity) end end @@ -330,8 +330,10 @@ local function on_train_leaves_stop(map_data, mod_settings, train_id, train) local best_prior = -INF for id, _ in pairs(refuelers) do local refueler = map_data.refuelers[id] - set_refueler_from_comb(mod_settings, refueler) - if bit32.btest(train.network_flag, refueler.network_flag) and (refueler.allows_all_trains or refueler.accepted_layouts[train.layout_id]) and refueler.trains_total < refueler.entity_stop.trains_limit then + set_refueler_from_comb(map_data, mod_settings, id) + + local refueler_network_flag = refueler.network_name == NETWORK_EACH and (refueler.network_flag[train.network_name] or 0) or refueler.network_flag + if btest(train.network_flag, refueler_network_flag) and (refueler.allows_all_trains or refueler.accepted_layouts[train.layout_id]) and refueler.trains_total < refueler.entity_stop.trains_limit then local accepted = false local dist = nil if refueler.priority == best_prior then @@ -350,7 +352,7 @@ local function on_train_leaves_stop(map_data, mod_settings, train_id, train) train.refueler_id = best_refueler_id local refueler = map_data.refuelers[best_refueler_id] refueler.trains_total = refueler.trains_total + 1 - add_refueler_schedule(train.entity, refueler.entity_stop, train.depot_name) + add_refueler_schedule(map_data, train.entity, refueler.entity_stop) interface_raise_train_status_changed(train_id, STATUS_R, STATUS_TO_F) return end @@ -418,6 +420,21 @@ function on_train_changed(event) local train_e = event.train--[[@as LuaTrain]] if not train_e.valid then return end local train_id = train_e.id + + if global.active_alerts then + --remove the alert if the train is interacted with at all + local data = global.active_alerts[train_id] + if data then + --we need to wait for the train to come to a stop from being locked + if data[3] + 10*mod_settings.tps < global.total_ticks then + global.active_alerts[train_id] = nil + if next(global.active_alerts) == nil then + global.active_alerts = nil + end + end + end + end + if train_e.state == defines.train_state.wait_station then local stop = train_e.station if stop and stop.valid and stop.name == "train-stop" then