Merge pull request #18 from mamoniot/1.1.8

This commit is contained in:
Monica Moniot
2022-12-23 21:52:22 -06:00
committed by GitHub
18 changed files with 968 additions and 580 deletions
+1
View File
@@ -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",
+19 -11
View File
@@ -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
+2 -7
View File
@@ -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
+18 -14
View File
@@ -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
+1 -1
View File
@@ -1,5 +1,5 @@
--By Mami
--<goto p_continue> at line 310 jumps into the scope of local 'p_flag'
require("scripts.constants")
require("scripts.global")
require("scripts.factorio-api")
+2 -2
View File
@@ -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"
+20 -15
View File
@@ -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.
+308 -165
View File
@@ -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
+3 -1
View File
@@ -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
+348 -185
View File
@@ -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
+17 -11
View File
@@ -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
+72 -51
View File
@@ -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]]
+14 -1
View File
@@ -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
+9
View File
@@ -0,0 +1,9 @@
function log_dispatch(network_name, r_stop, p_stop)
end
function log_refuel(network_name, refuel_stop)
end
+57 -79
View File
@@ -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])
+41 -18
View File
@@ -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
+9 -9
View File
@@ -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)
+27 -10
View File
@@ -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