Experimental 1.2.2 (#21)

---------------------------------------------------------------------------------------------------
Version: 1.2.2
Date: 2022-12-29
  Features:
    - Added a station combinator setting to enable or disable the inactivity condition in a train's orders, disabled by default (but not in <=1.2.1 worlds)
    - Added a depot combinator setting to enable depot bypass, enabled by default
    - Added a depot combinator setting to force trains to park at the same depot, enabled by default
    - Added network "each" for depots
    - Added a map setting to modify the default locked slots per cargo wagon value
    - Added a map setting to modify the default priority value
    - Added a map setting to allow trains with cargo at depots, disabled by default
  Changes:
    - Inverted the sign of combinator outputs, a map setting has been added to maintain backwards compatibility with <=1.2.1 worlds
    - Overhauled the wagon control combinator algorithm to spread items out between cargo wagons
    - Trains with cargo held in the depot now check if they have been emptied and reset when they have
    - Cargo capacity is now prioritized over distance when choosing trains
    - Increased the default request threshold to 2000
    - Improved English localization
  Bugfixes:
    - Fixed a bug where trains with cargo sometimes weren't getting held at depots
    - Fixed a crash caused by changing a station combinator to the "each" network during a bad tick
    - Fixed a crash when changing a refueler away from network each
    - Multiple rare bugs and crashes relating to wagon control combinators are fixed
    - Fixed a bug with refueler direct orders not being applied after moving through a space elevator
    - Fixed a bug where filtered slots sometimes weren't being removed
---------------------------------------------------------------------------------------------------
This commit is contained in:
Monica Moniot
2022-12-29 10:02:07 -06:00
committed by GitHub
parent c48d1d3025
commit 7d2f0c2ccd
19 changed files with 833 additions and 511 deletions

View File

@@ -6,6 +6,8 @@ Behold one of the most feature-rich and performant train logistics network mods
## Quick Start Guide
Copy the contents of [Project Cybersyn Official Example Blueprints](https://raw.githubusercontent.com/mamoniot/project-cybersyn/main/cybersyn_blueprints.txt) and paste them into the import blueprint text box to see examples of how to create a functional network. I highly recommend taking a look at these; the blueprint book showcases both the basics of the mod and highly advanced designs you can create.
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 "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.
@@ -15,7 +17,7 @@ A bare minimum Cybersyn train network consists of 2 components: depots and stati
To make a basic provider station, create an item buffer of chests or tanks adjacent to the station's tracks, and connect that buffer by wire to the input of the cybernetic combinator. To make a basic requester station, repeat the same, except reverse the direction of the inserters or pumps so they are *unloading* instead of loading; then connect a constant combinator to the same circuit network, set it to output the number of item you want this requester station to keep loaded in its item buffer, and flip the sign of each so the signal strength is negative. Once the provider station contains the item being requested, a train will automatically be sent to deliver that item from the provider station to the requester station. The requester station's buffer will automatically be topped up on the item being requested. Be sure that the requester station has the space to at minimum unload 2 fully loaded trains.
Follow the above directions and you have set up a bare minimum Cybersyn network! You may continue adding onto it with more stations and depots and Cybersyn will automatically manage all of them for you. At some point you may notice small hiccups within this network, like trains attempting to deliver a tiny amount of an item instead of a full cargo load, or you may want to extend your network with things like multi-item stations or centralized train refueling. In either case refer to **Mod Details** below for a in depth explanation of every feature within this mod.
Follow the above directions and you have set up a bare minimum Cybersyn network! You may continue adding onto it with more stations and depots and Cybersyn will automatically manage all of them for you. There is one issue with this kind of network though; trains won't always deliver full loads of cargo, meaning trains will make deliveries more frequently than is necessary. To fix this issue you will need to start using "request thresholds". To get a full picture of how request thresholds work, either import the official example blueprints from above, or read the **Request thresholds** section below.
## Features
@@ -37,7 +39,7 @@ These all combine to make it possible to **create "universal" stations**; statio
Stations can **automatically build allow-lists for trains** they can load or unload. Inserters or pumps adjacent to the station's tracks are auto-detected. No more deadlocks caused by trains mistakenly attempting to fulfill a delivery to a station that cannot unload it. This feature is compatible with miniloaders.
Trains can automatically visit **centralized fuel loaders**. It is not required that every single depot loads trains with fuel.
Trains can automatically visit **centralized refuelers**. It is not required that every single depot loads trains with fuel.
Trains can **bypass visiting the depot** if they have enough fuel. Trains spend far more time productively making deliveries rather than travelling to and from their depot. Fewer reserve trains are needed as a result.
@@ -69,7 +71,7 @@ This mod adds a single new entity to the game, the cybernetic combinator. This c
![Image](https://raw.githubusercontent.com/mamoniot/project-cybersyn/main/previews/multi-item.png)
When placed adjacent to a vanilla train stop, a Cybersyn station is created. This station can provide or request items to your train network. Connect the input of the combinator to a circuit network; When a positive item signal is received, this station will provide that item to the network, when a negative signal is received, this station will request that item from the network. When a station is providing an item that another station is requesting, a train order will automatically be generated to transfer those items from the providing station to the requesting station. When a train arrives to fulfill this order, the output of the combinator will give the full list of items expected to be loaded (positive) or unloaded (negative) from the train.
When placed adjacent to a vanilla train stop, a Cybersyn station is created. This station can provide or request items to your train network. Connect the input of the combinator to a circuit network; When a positive item signal is received, this station will provide that item to the network, when a negative signal is received, this station will request that item from the network. When a station is providing an item that another station is requesting, a train order will automatically be generated to transfer those items from the providing station to the requesting station. When a train arrives to fulfill this order, the output of the combinator will give the full list of items expected to be loaded (negative) or unloaded (positive) from the train.
Stations can automatically build allow-lists. When this option is enabled, only trains that can be loaded or unloaded by this station will be allowed to make deliveries to it. Stations determine this based on what inserters or pumps are present at this station along its tracks. When disabled, all trains within the network are allowed.
@@ -81,11 +83,11 @@ Stations can be set to provide only or request only. By default stations can bot
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
### Refueler mode
When placed adjacent to a vanilla train stop, a Cybersyn fuel loader is created. Whenever a train completes a delivery, if it is running low on fuel (configurable in mod settings), it will attempt to visit a fuel loader before returning to the depot. The train will search for a fuel loader within its network that has not exceeded its train limit and that it is allow-listed for. If one is found it will schedule a detour to it to stock back up on fuel.
When placed adjacent to a vanilla train stop, a Cybersyn refueler is created. Whenever a train completes a delivery, if it is running low on fuel (configurable in mod settings), it will attempt to visit a refueler before returning to the depot. The train will search for a refueler within its network that has not exceeded its train limit and that it is allow-listed for. If one is found it will schedule a detour to it to stock back up on fuel.
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.
Refuelers 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 control mode
@@ -107,11 +109,11 @@ 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, 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.
Stations, depots and refuelers 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.
A network can 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 stop 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
@@ -127,7 +129,7 @@ After an order has been generated, enough items will be subtracted from that ord
### 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. Provider stations will be prevented from providing items to lower priority requester stations until the highest priority requester station is satisfied.
Orders will be generated first for stations, depots and refuelers which are receiving a higher priority signal than the others. If multiple depots or refuelers have the same priority, the closest one will be prioritized. Depots calculate distance based on the location of their train. If multiple 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 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.

4
TODO
View File

@@ -6,12 +6,12 @@ major:
add in game guide
move to an event based algorithm
models & art
train always returns to same depot setting
make train inactivity condition a setting
minor:
handle if signals are removed from the game during migration
update wagon control combinators immediately upon placement
railloader compat
deadlocks signals compat
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

View File

@@ -1,8 +1,33 @@
---------------------------------------------------------------------------------------------------
Version: 1.2.2
Date: 2022-12-29
Features:
- Added a station combinator setting to enable or disable the inactivity condition in a train's orders, disabled by default (but not in <=1.2.1 worlds)
- Added a depot combinator setting to enable depot bypass, enabled by default
- Added a depot combinator setting to force trains to park at the same depot, enabled by default
- Added network "each" for depots
- Added a map setting to modify the default locked slots per cargo wagon value
- Added a map setting to modify the default priority value
- Added a map setting to allow trains with cargo at depots, disabled by default
Changes:
- Inverted the sign of combinator outputs, a map setting has been added to maintain backwards compatibility with <=1.2.1 worlds
- Overhauled the wagon control combinator algorithm to spread items out between cargo wagons
- Trains with cargo held in the depot now check if they have been emptied and reset when they have
- Cargo capacity is now prioritized over distance when choosing trains
- Increased the default request threshold to 2000
- Improved English localization
Bugfixes:
- Fixed a bug where trains with cargo sometimes weren't getting held at depots
- Fixed a crash caused by changing a station combinator to the "each" network during a bad tick
- Fixed a crash when changing a refueler away from network each
- Multiple rare bugs and crashes relating to wagon control combinators are fixed
- Fixed a bug with refueler direct orders not being applied after moving through a space elevator
- Fixed a bug where filtered slots sometimes weren't being removed
---------------------------------------------------------------------------------------------------
Version: 1.2.1
Date: 2022-12-24
Bugfixes:
- Fixed an bug where sometimes refuelers would reject trains they should accept
- Fixed a bug where sometimes refuelers would reject trains they should accept
---------------------------------------------------------------------------------------------------
Version: 1.2.0
Date: 2022-12-23
@@ -49,7 +74,7 @@ Date: 2022-12-9
Version: 1.1.3
Date: 2022-12-8
Bugfixes:
- Fixed a crash when removing a fuel loader
- Fixed a crash when removing a refueler
- Fixed a gui bug
- Fixed a crash on newly generated worlds
- Fixed a crash with breaking combinators

View File

@@ -2,6 +2,7 @@
--<goto p_continue> at line 310 jumps into the scope of local 'p_flag'
require("scripts.constants")
require("scripts.global")
require("scripts.lib")
require("scripts.factorio-api")
require("scripts.layout")
require("scripts.central-planning")

View File

@@ -1,6 +1,6 @@
{
"name": "cybersyn",
"version": "1.2.1",
"version": "1.2.2",
"title": "Project Cybersyn",
"author": "Mami",
"factorio_version": "1.1",

View File

@@ -2,21 +2,29 @@
cybersyn-ticks-per-second=Central planning updates per second
cybersyn-update-rate=Central planning update rate
cybersyn-request-threshold=Default request threshold
cybersyn-priority=Default priority
cybersyn-locked-slots=Default locked slots per cargo wagon
cybersyn-network-flag=Default network mask
cybersyn-fuel-threshold=Fuel threshold
cybersyn-depot-bypass-enabled=Depot bypass enabled
cybersyn-warmup-time=Station warmup time (sec)
cybersyn-stuck-train-time=Stuck train timeout (sec)
cybersyn-allow-cargo-in-depot=Allow cargo in depots
cybersyn-invert-sign=Invert combinator output (deprecated)
[mod-setting-description]
cybersyn-ticks-per-second=How many times per second the central planner should update the state of the network and schedule deliveries. This value will be rounded up to a divisor of 60. Setting this to 0 will stop all dispatches.
cybersyn-update-rate=How many stations per tick can be polled at once or can have deliveries scheduled at once. Larger number allow the central planner to keep more up to date on the current state of the network, but at the cost of performance.
cybersyn-request-threshold=The default request threshold when a request threshold signal is not given to a station. When a station receives a negative item signal that surpasses its request threshold, so long as any station exists with a positive signal greater than the request threshold, a delivery of that item will be scheduled between the two stations.
cybersyn-priority=The default priority when a priority signal is not given to a station, depot or refueler. Stations with higher priorities will receive deliveries before stations with lower priorities.
cybersyn-locked-slots=The default number of locked slots per cargo wagon when a "locked slots per cargo wagon" signal is not given to a station. When a provider station has locked slots per cargo wagon of X, any train attempting to make a delivery from it will have its orders modified so every cargo wagon will have at least X item slots left empty. This is necessary to make multi-item provider stations function correctly.
cybersyn-network-flag=The default set of sub-networks a station will service when no network signal is given to a station. This integer is interpretted bit-wise to give 32 possible sub-networks to choose from.
cybersyn-fuel-threshold=What percentage of a train's fuel inventory must be full to skip refueling. If this is set to 1, trains will always visit a fuel loader after completing a delivery.
cybersyn-fuel-threshold=What percentage of a train's fuel inventory must be full to skip refueling. If this is set to 1, trains will always visit a refueler after completing a delivery.
cybersyn-depot-bypass-enabled=If checked, when a train completes a delivery and refueling, it may take a new order from the network before having to return to its depot.
cybersyn-warmup-time=How many seconds a cybernetic combinator will wait before connecting to the Cybersyn network. This is a grace period to modify or correct the circuit network before trains start dispatching to a newly blueprinted station.
cybersyn-stuck-train-time=After this many seconds from a train's dispatch, an alert will be sent to let you know a train is probably stuck and has not completed its delivery. The player will likely have to debug their network to get the train unstuck.
cybersyn-allow-cargo-in-depot=If checked, trains will be allowed to have cargo in depots; no alerts will be generated and the train will not be held. In addition, trains with orders to visit requester stations with "Inactivity condition" checked will wait for inactivity instead of waiting for empty cargo. Useful for creating train systems where depots handle excess cargo. For advanced users only.
cybersyn-invert-sign=Flip the sign of the output of cybernetic combinators to be the same as it is in LTN or in earlier versions of Project Cybersyn.
[item-name]
cybersyn-combinator=Cybernetic combinator
@@ -48,7 +56,7 @@ unexpected-train=A train has unexpectedly returned to the depot before completin
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
refueler-broken=A train is lost because its refueler 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__
@@ -61,16 +69,22 @@ combinator-title=Cybernetic combinator
operation=Mode
comb1=Station
depot=Depot
refueler=Fuel loader
refueler=Refueler
comb2=Station control
wagon-manifest=Wagon control
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.
switch-request-tooltip=Lock this station to only request items from the network. By default it both requests and provides.
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 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.
switch-request-tooltip=Lock this station to only request items from the network. By default it both requests and provides.
enable-inactive-description=Inactivity condition
enable-inactive-tooltip=When checked a train at a provider is required to wait for inactivity even if its order has been fulfilled. This is frequently useful for preventing inserters from getting items stuck in their hands.
use-same-depot-description=Require same depot
use-same-depot-tooltip=When checked trains from this depot always return to this depot. When unchecked the train is allowed to return to any depot with the same name as this one.
depot-bypass-description=Depot bypass
depot-bypass-tooltip=When checked trains from this depot do not have to be parked at this depot to receive new orders, they just have to have no current active orders. They will still return to this depot if they are low on fuel and no refuelers are available.

View File

@@ -40,15 +40,13 @@ 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]
local depot = map_data.depots[train.depot_id]
local is_at_depot = remove_available_train(map_data, train_id, train)
remove_available_train(map_data, train_id, train)
local depot_id = train.parked_at_depot_id
if depot_id then
map_data.depots[depot_id].available_train_id = nil
train.parked_at_depot_id = nil
end
--NOTE: we assume that the train is not being teleported at this time
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
--NOTE: set_manifest_schedule is allowed to cancel the delivery at the last second if applying the schedule to the train makes it lost
if set_manifest_schedule(map_data, train.entity, depot.entity_stop, not train.use_any_depot, p_station.entity_stop, p_station.enable_inactive, r_station.entity_stop, mod_settings.allow_cargo_in_depot and r_station.enable_inactive--[[@as boolean]], manifest, is_at_depot) then
local old_status = train.status
train.status = STATUS_TO_P
train.p_station_id = p_station_id
@@ -155,10 +153,12 @@ function create_manifest(map_data, r_station_id, p_station_id, train_id, primary
--locked slots is only taken into account after the train is already approved for dispatch
local locked_slots = p_station.locked_slots
local total_slots_left = train.item_slot_capacity
local total_item_slots
if locked_slots > 0 then
local total_cw = #train.entity.cargo_wagons
total_slots_left = min(total_slots_left, max(total_slots_left - total_cw*locked_slots, total_cw))
local total_cargo_wagons = #train.entity.cargo_wagons
total_item_slots = max(train.item_slot_capacity - total_cargo_wagons*locked_slots, 1)
else
total_item_slots = train.item_slot_capacity
end
local total_liquid_left = train.fluid_capacity
@@ -174,13 +174,13 @@ function create_manifest(map_data, r_station_id, p_station_id, train_id, primary
total_liquid_left = 0--no liquid merging
keep_item = true
end
elseif total_slots_left > 0 then
elseif total_item_slots > 0 then
local stack_size = get_stack_size(map_data, item.name)
local slots = ceil(item.count/stack_size)
if slots > total_slots_left then
item.count = total_slots_left*stack_size
if slots > total_item_slots then
item.count = total_item_slots*stack_size
end
total_slots_left = total_slots_left - slots
total_item_slots = total_item_slots - slots
keep_item = true
end
if keep_item then
@@ -329,7 +329,7 @@ local function tick_dispatch(map_data, mod_settings)
---@type uint
local j = 1
while j <= #p_stations do
local p_flag, r_flag, netand, best_p_train_id, best_t_prior, best_t_to_p_dist, effective_count, override_threshold, p_prior, best_p_dist
local p_flag, r_flag, netand, best_p_train_id, best_t_prior, best_capacity, best_t_to_p_dist, effective_count, override_threshold, p_prior, best_p_dist
local p_station_id = p_stations[j]
local p_station = stations[p_station_id]
@@ -337,8 +337,8 @@ local function tick_dispatch(map_data, mod_settings)
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
p_flag = get_network_flag(p_station, network_name)
r_flag = get_network_flag(r_station, network_name)
netand = band(p_flag, r_flag)
if netand == 0 then
goto p_continue
@@ -353,11 +353,13 @@ local function tick_dispatch(map_data, mod_settings)
---@type uint?
best_p_train_id = nil
best_t_prior = -INF
best_capacity = 0
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
local train_flag = get_network_flag(train, network_name)
if not btest(netand, train_flag) or train.se_is_being_teleported then
goto train_continue
end
if correctness < 2 then
@@ -397,14 +399,19 @@ local function tick_dispatch(map_data, mod_settings)
if train.priority < best_t_prior then
goto train_continue
end
if train.priority == best_t_prior and capacity < best_capacity 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
if capacity == best_capacity and t_to_p_dist > best_t_to_p_dist then
goto train_continue
end
best_p_train_id = train_id
best_capacity = capacity
best_t_prior = train.priority
best_t_to_p_dist = t_to_p_dist
::train_continue::
@@ -507,10 +514,11 @@ local function tick_poll_station(map_data, mod_settings)
end
end
station.r_threshold = mod_settings.r_threshold
station.priority = 0
station.priority = mod_settings.priority
station.item_priority = nil
station.locked_slots = 0
if station.network_name == NETWORK_EACH then
station.locked_slots = mod_settings.locked_slots
local is_each = station.network_name == NETWORK_EACH
if is_each then
station.network_flag = {}
else
station.network_flag = mod_settings.network_flag
@@ -548,12 +556,12 @@ local function tick_poll_station(map_data, mod_settings)
if item_type == "virtual" then
if item_name == SIGNAL_PRIORITY then
station.priority = item_count
elseif item_name == REQUEST_THRESHOLD and item_count ~= 0 then
elseif item_name == REQUEST_THRESHOLD then
--NOTE: thresholds must be >0 or they can cause a crash
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
elseif is_each then
station.network_flag[item_name] = item_count
end
comb1_signals[k] = nil
@@ -645,8 +653,38 @@ local function tick_poll_station(map_data, mod_settings)
end
---@param map_data MapData
---@param mod_settings CybersynModSettings
local function tick_poll_train(map_data, mod_settings)
function tick_init(map_data, mod_settings)
local tick_data = map_data.tick_data
map_data.economy.all_p_stations = {}
map_data.economy.all_r_stations = {}
map_data.economy.all_names = {}
for i, id in pairs(map_data.warmup_station_ids) do
local station = map_data.stations[id]
if station then
if station.last_delivery_tick + mod_settings.warmup_time*mod_settings.tps < map_data.total_ticks then
map_data.active_station_ids[#map_data.active_station_ids + 1] = id
map_data.warmup_station_ids[i] = nil
end
else
map_data.warmup_station_ids[i] = nil
end
end
if map_data.queue_station_update then
for id, _ in pairs(map_data.queue_station_update) do
local station = map_data.stations[id]
if station then
local pre = station.allows_all_trains
set_station_from_comb(station)
if station.allows_all_trains ~= pre then
update_stop_if_auto(map_data, station, true)
end
end
end
map_data.queue_station_update = nil
end
--NOTE: the following has undefined behavior if last_train is deleted, this should be ok since the following doesn't care how inconsistent our access pattern is
local train_id, train = next(map_data.trains, tick_data.last_train)
tick_data.last_train = train_id
@@ -657,10 +695,7 @@ local function tick_poll_train(map_data, mod_settings)
end
interface_raise_train_stuck(train_id)
end
end
---@param map_data MapData
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
@@ -673,6 +708,9 @@ local function tick_poll_comb(map_data, mod_settings)
if refueler_id then
set_refueler_from_comb(map_data, mod_settings, refueler_id)
end
map_data.tick_state = STATE_POLL_STATIONS
interface_raise_tick_init()
end
---@param map_data MapData
---@param mod_settings CybersynModSettings
@@ -680,30 +718,13 @@ 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
if map_data.total_ticks%(8*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 = {}
map_data.economy.all_names = {}
for i, id in pairs(map_data.warmup_station_ids) do
local station = map_data.stations[id]
if station then
if station.last_delivery_tick + mod_settings.warmup_time*mod_settings.tps < map_data.total_ticks then
map_data.active_station_ids[#map_data.active_station_ids + 1] = id
map_data.warmup_station_ids[i] = nil
end
else
map_data.warmup_station_ids[i] = nil
end
end
map_data.tick_state = STATE_POLL_STATIONS
interface_raise_tick_init()
tick_poll_train(map_data, mod_settings)
tick_poll_comb(map_data)
tick_init(map_data, mod_settings)
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

View File

@@ -22,6 +22,12 @@ MODE_DEPOT = "+"
MODE_WAGON = "-"
MODE_REFUELER = ">>"
SETTING_DISABLE_ALLOW_LIST = 2
SETTING_IS_STACK = 3
SETTING_ENABLE_INACTIVE = 4
SETTING_USE_ANY_DEPOT = 5
SETTING_DISABLE_DEPOT_BYPASS = 6
NETWORK_SIGNAL_DEFAULT = {name="signal-A", type="virtual"}
NETWORK_EACH = "signal-each"
INACTIVITY_TIME = 100

View File

@@ -39,15 +39,32 @@ function se_get_space_elevator_name(surface)
end
---@param train LuaTrain
function get_any_train_entity(train)
return train.front_stock or train.back_stock or train.carriages[1]
end
---@param e Station|Refueler|Train
---@param network_name string
function get_network_flag(e, network_name)
return e.network_name == NETWORK_EACH and (e.network_flag[network_name] or 0) or e.network_flag
end
------------------------------------------------------------------------------
--[[train schedules]]--
------------------------------------------------------------------------------
local create_loading_order_condition = {type = "inactivity", compare_type = "and", ticks = INACTIVITY_TIME}
local condition_wait_inactive = {type = "inactivity", compare_type = "and", ticks = INACTIVITY_TIME}
local condition_only_inactive = {condition_wait_inactive}
local condition_unloading_order = {{type = "empty", compare_type = "and"}}
local condition_direct_to_station = {{type = "time", compare_type = "and", ticks = 1}}
---@param stop LuaEntity
---@param manifest Manifest
function create_loading_order(stop, manifest)
---@param enable_inactive boolean
function create_loading_order(stop, manifest, enable_inactive)
local condition = {}
for _, item in ipairs(manifest) do
local cond_type
@@ -63,26 +80,30 @@ function create_loading_order(stop, manifest)
condition = {comparator = "", first_signal = {type = item.type, name = item.name}, constant = item.count}
}
end
condition[#condition + 1] = create_loading_order_condition
if enable_inactive then
condition[#condition + 1] = condition_wait_inactive
end
return {station = stop.backer_name, wait_conditions = condition}
end
local create_unloading_order_condition = {{type = "empty", compare_type = "and"}}
---@param stop LuaEntity
function create_unloading_order(stop)
return {station = stop.backer_name, wait_conditions = create_unloading_order_condition}
---@param enable_inactive boolean
function create_unloading_order(stop, enable_inactive)
if enable_inactive then
return {station = stop.backer_name, wait_conditions = condition_only_inactive}
else
return {station = stop.backer_name, wait_conditions = condition_unloading_order}
end
end
local create_inactivity_order_condition = {{type = "inactivity", compare_type = "and", ticks = INACTIVITY_TIME}}
---@param depot_name string
function create_inactivity_order(depot_name)
return {station = depot_name, wait_conditions = create_inactivity_order_condition}
return {station = depot_name, wait_conditions = condition_only_inactive}
end
local create_direct_to_station_order_condition = {{type = "time", compare_type = "and", ticks = 1}}
---@param stop LuaEntity
function create_direct_to_station_order(stop)
return {rail = stop.connected_rail, rail_direction = stop.connected_rail_direction, wait_conditions = create_direct_to_station_order_condition}
return {rail = stop.connected_rail, rail_direction = stop.connected_rail_direction, wait_conditions = condition_direct_to_station}
end
---@param train LuaTrain
@@ -138,13 +159,15 @@ function se_create_elevator_order(elevator_name, is_train_in_orbit)
end
---@param map_data MapData
---@param train LuaTrain
---@param depot_name string
---@param d_surface_i int
---@param depot_stop LuaEntity
---@param same_depot boolean
---@param p_stop LuaEntity
---@param p_enable_inactive boolean
---@param r_stop LuaEntity
---@param r_enable_inactive boolean
---@param manifest Manifest
---@param start_at_depot boolean?
function set_manifest_schedule(map_data, train, depot_name, d_surface_i, p_stop, r_stop, manifest, start_at_depot)
function set_manifest_schedule(map_data, train, depot_stop, same_depot, p_stop, p_enable_inactive, r_stop, r_enable_inactive, 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
@@ -153,6 +176,7 @@ function set_manifest_schedule(map_data, train, depot_name, d_surface_i, p_stop,
local t_surface = train.front_stock.surface
local p_surface = p_stop.surface
local r_surface = r_stop.surface
local d_surface_i = depot_stop.surface.index
local t_surface_i = t_surface.index
local p_surface_i = p_surface.index
local r_surface_i = r_surface.index
@@ -160,15 +184,19 @@ function set_manifest_schedule(map_data, train, depot_name, d_surface_i, p_stop,
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
local records = {
create_inactivity_order(depot_stop.backer_name),
create_direct_to_station_order(p_stop),
create_loading_order(p_stop, manifest, p_enable_inactive),
create_direct_to_station_order(r_stop),
create_unloading_order(r_stop, r_enable_inactive),
}
if same_depot then
records[6] = create_direct_to_station_order(depot_stop)
end
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),
}
records = records
}
if old_schedule and not train.has_path then
train.schedule = old_schedule
@@ -186,14 +214,14 @@ function set_manifest_schedule(map_data, train, depot_name, d_surface_i, p_stop,
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)}
local records = {create_inactivity_order(depot_stop.backer_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)
records[#records + 1] = create_loading_order(p_stop, manifest, p_enable_inactive)
if p_surface_i ~= r_surface_i then
records[#records + 1] = se_create_elevator_order(elevator_name, is_train_in_orbit)
@@ -201,7 +229,7 @@ function set_manifest_schedule(map_data, train, depot_name, d_surface_i, p_stop,
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)
records[#records + 1] = create_unloading_order(r_stop, r_enable_inactive)
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
@@ -221,9 +249,9 @@ function set_manifest_schedule(map_data, train, depot_name, d_surface_i, p_stop,
end
--NOTE: create a schedule that cannot be fulfilled, the train will be stuck but it will give the player information what went wrong
train.schedule = {current = 1, records = {
create_inactivity_order(depot_name),
create_loading_order(p_stop, manifest),
create_unloading_order(r_stop),
create_inactivity_order(depot_stop.backer_name),
create_loading_order(p_stop, manifest, p_enable_inactive),
create_unloading_order(r_stop, r_enable_inactive),
}}
lock_train(train)
send_alert_cannot_path_between_surfaces(map_data, train)
@@ -306,6 +334,78 @@ function get_comb_network_name(comb)
return signal and signal.name or nil
end
---@param station Station
function set_station_from_comb(station)
--NOTE: this does nothing to update currently active deliveries
--NOTE: this can only be called at the tick init boundary
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, SETTING_DISABLE_ALLOW_LIST) > 0
local is_stack = bit_extract(bits, SETTING_IS_STACK) > 0
local enable_inactive = bit_extract(bits, SETTING_ENABLE_INACTIVE) > 0
station.network_name = signal and signal.name or nil
station.allows_all_trains = allows_all_trains
station.is_stack = is_stack
station.enable_inactive = enable_inactive
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
if station.network_name == NETWORK_EACH then
station.network_flag = {}
end
end
---@param mod_settings CybersynModSettings
---@param train Train
---@param comb LuaEntity
function set_train_from_comb(mod_settings, train, comb)
--NOTE: this does nothing to update currently active deliveries
local params = get_comb_params(comb)
local signal = params.first_signal
local network_name = signal and signal.name or nil
local bits = params.second_constant or 0
local disable_bypass = bit_extract(bits, SETTING_DISABLE_DEPOT_BYPASS) > 0
local use_any_depot = bit_extract(bits, SETTING_USE_ANY_DEPOT) > 0
train.network_name = network_name
train.disable_bypass = disable_bypass
train.use_any_depot = use_any_depot
local is_each = train.network_name == NETWORK_EACH
if is_each then
train.network_flag = {}
else
train.network_flag = mod_settings.network_flag
end
train.priority = mod_settings.priority
local signals = comb.get_merged_signals(defines.circuit_connector_id.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
train.priority = item_count
elseif is_each then
if item_name ~= REQUEST_THRESHOLD and item_name ~= LOCKED_SLOTS then
train.network_flag[item_name] = item_count
end
end
end
if item_name == network_name then
train.network_flag = item_count
end
end
end
end
end
---@param map_data MapData
---@param mod_settings CybersynModSettings
---@param id uint
@@ -316,12 +416,14 @@ function set_refueler_from_comb(map_data, mod_settings, id)
local bits = params.second_constant or 0
local signal = params.first_signal
local old_network = refueler.network_name
local old_network_flag = refueler.network_flag
refueler.network_name = signal and signal.name or nil
refueler.allows_all_trains = bit_extract(bits, 2) > 0
refueler.priority = 0
refueler.allows_all_trains = bit_extract(bits, SETTING_DISABLE_ALLOW_LIST) > 0
refueler.priority = mod_settings.priority
if refueler.network_name == NETWORK_EACH then
local is_each = refueler.network_name == NETWORK_EACH
if is_each then
map_data.each_refuelers[id] = true
refueler.network_flag = {}
else
@@ -339,8 +441,10 @@ function set_refueler_from_comb(map_data, mod_settings, id)
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
elseif is_each then
if item_name ~= REQUEST_THRESHOLD and item_name ~= LOCKED_SLOTS then
refueler.network_flag[item_name] = item_count
end
end
end
if item_name == refueler.network_name then
@@ -352,7 +456,7 @@ function set_refueler_from_comb(map_data, mod_settings, id)
local f, a
if old_network == NETWORK_EACH then
f, a = pairs(refueler.network_flag--[[@as {[string]: int}]])
f, a = pairs(old_network_flag--[[@as {[string]: int}]])
elseif old_network ~= refueler.network_name then
f, a = once, old_network
else
@@ -406,24 +510,6 @@ function update_display(map_data, station)
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)
@@ -433,8 +519,6 @@ function get_comb_gui_settings(comb)
local switch_state = "none"
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
if is_pr_state == 0 then
switch_state = "none"
elseif is_pr_state == 1 then
@@ -454,7 +538,7 @@ function get_comb_gui_settings(comb)
elseif op == MODE_WAGON then
selected_index = 5
end
return selected_index, params.first_signal, switch_state, not allows_all_trains, is_stack
return selected_index, params.first_signal, switch_state, bits
end
---@param comb LuaEntity
---@param is_pr_state 0|1|2
@@ -467,23 +551,14 @@ function set_comb_is_pr_state(comb, is_pr_state)
control.parameters = param
end
---@param comb LuaEntity
---@param allows_all_trains boolean
function set_comb_allows_all_trains(comb, allows_all_trains)
---@param n int
---@param bit boolean
function set_comb_setting(comb, n, bit)
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)
param.second_constant = bit_replace(bits, bit and 1 or 0, n)
control.parameters = param
end
---@param comb LuaEntity
@@ -540,22 +615,25 @@ end
---@param map_data MapData
---@param station Station
function set_comb2(map_data, station)
local sign = mod_settings.invert_sign and -1 or 1
if station.entity_comb2 then
local deliveries = station.deliveries
local signals = {}
for item_name, count in pairs(deliveries) do
local i = #signals + 1
local is_fluid = game.item_prototypes[item_name] == nil--NOTE: this is expensive
signals[i] = {index = i, signal = {type = is_fluid and "fluid" or "item", name = item_name}, count = -count}
signals[i] = {index = i, signal = {type = is_fluid and "fluid" or "item", name = item_name}, count = sign*count}
end
set_combinator_output(map_data, station.entity_comb2, signals)
end
end
------------------------------------------------------------------------------
--[[alerts]]--
------------------------------------------------------------------------------
---@param train LuaTrain
---@param icon {}
---@param message string
@@ -694,7 +772,14 @@ function process_active_alerts(map_data)
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")
--this is an alert that we have to actively check if we can clear
local is_train_empty = next(train.get_contents()) == nil and next(train.get_fluid_contents()) == nil
if is_train_empty then
--NOTE: this function could get confused being called internally, be sure it can handle that
on_train_changed({train = train})
else
send_alert_for_train(train, send_nonempty_train_in_depot_alert_icon, "cybersyn-messages.nonempty-train")
end
elseif id == 3 then
send_alert_for_train(train, send_lost_train_alert_icon, "cybersyn-messages.depot-broken")
elseif id == 4 then

View File

@@ -9,6 +9,7 @@
---@field public stations {[uint]: Station}
---@field public active_station_ids uint[]
---@field public warmup_station_ids uint[]
---@field public queue_station_update {[uint]: true?}?
---@field public depots {[uint]: Depot}
---@field public refuelers {[uint]: Refueler}
---@field public trains {[uint]: Train}
@@ -29,6 +30,7 @@
---@field public is_p true?
---@field public is_r true?
---@field public is_stack true?
---@field public enable_inactive true?
---@field public allows_all_trains true?
---@field public deliveries_total int
---@field public last_delivery_tick int
@@ -50,7 +52,7 @@
---@class Depot
---@field public entity_stop LuaEntity
---@field public entity_comb LuaEntity
---@field public available_train_id uint?--train_id
---@field public available_train_id uint?--train_id, only present when a train is parked here
---@class Refueler
---@field public entity_stop LuaEntity
@@ -76,13 +78,13 @@
---@field public last_manifest_tick int
---@field public has_filtered_wagon true?
---@field public is_available true?
---@field public parked_at_depot_id uint?
---@field public depot_name string
---@field public depot_id uint
---@field public use_any_depot true?
---@field public disable_bypass true?
---@field public network_name string? --can only be nil when the train is parked at a depot
---@field public network_flag int
---@field public priority int
---@field public refueler_id uint?
---@field public se_depot_surface_i uint --se only
---@field public se_is_being_teleported true? --se only
---@field public se_awaiting_removal any? --se only
---@field public se_awaiting_rename any? --se only
@@ -105,17 +107,21 @@
---@field public tps double
---@field public update_rate int
---@field public r_threshold int
---@field public priority int
---@field public locked_slots int
---@field public network_flag int
---@field public warmup_time double
---@field public stuck_train_time double
---@field public fuel_threshold double
---@field public depot_bypass_enabled boolean
---@field public invert_sign boolean
---@field public allow_cargo_in_depot boolean
---@field public missing_train_alert_enabled boolean --interface setting
---@field public stuck_train_alert_enabled boolean --interface setting
---@field public react_to_nonempty_train_in_depot boolean --interface setting
---@field public react_to_train_at_incorrect_station boolean --interface setting
---@field public react_to_train_early_to_depot boolean --interface setting
--if this is uncommented it means there are migrations to write
---@alias cybersyn.global MapData
---@type CybersynModSettings
mod_settings = {}
@@ -150,9 +156,3 @@ function init_global()
IS_SE_PRESENT = remote.interfaces["space-exploration"] ~= nil
end
---@param v string
---@param h string?
function once(v, h)
return not h and v or nil--[[@as string|nil]]
end

View File

@@ -26,25 +26,37 @@ 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"
local bit_extract = bit32.extract
local function setting(bits, n)
return bit_extract(bits, n) > 0
end
local function setting_flip(bits, n)
return bit_extract(bits, n) == 0
end
---@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 is_depot = selected_index == 2
local uses_network = is_station or is_depot or selected_index == 3
local uses_allow_list = is_station or selected_index == 3
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]]
local first_settings = bottom_flow.first--[[@as LuaGuiElement]]
local depot_settings = bottom_flow.depot--[[@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
first_settings.allow_list.visible = uses_allow_list
first_settings.is_stack.visible = is_station
bottom_flow.enable_inactive.visible = is_station
depot_settings.visible = is_depot
end
---@param comb LuaEntity
@@ -53,7 +65,7 @@ function gui_opened(comb, player)
combinator_update(global, comb, true)
local rootgui = player.gui.screen
local selected_index, signal, switch_state, allow_list, is_stack = get_comb_gui_settings(comb)
local selected_index, signal, switch_state, bits = get_comb_gui_settings(comb)
local window = flib_gui.build(rootgui, {
{type="frame", direction="vertical", ref={"main_window"}, name=COMBINATOR_NAME, children={
@@ -69,8 +81,8 @@ function gui_opened(comb, player)
{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}},
{type="label", caption={STATUS_NAMES[comb.status] or STATUS_NAMES_DEFAULT}, ref={"status_label"}}
{type="sprite", sprite=STATUS_SPRITES[comb.status] or STATUS_SPRITES_DEFAULT, style="status_image", style_mods={stretch_image_to_widget_size=true}},
{type="label", caption={STATUS_NAMES[comb.status] or STATUS_NAMES_DEFAULT}}
}},
--preview
{type="frame", style="deep_frame_in_shallow_frame", style_mods={minimal_width=0, horizontally_stretchable=true, padding=0}, children={
@@ -94,25 +106,45 @@ function gui_opened(comb, player)
}},
---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, top_margin=2}, actions={
{type="label", name="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="top"}, children={
{type="choose-elem-button", name="network", style="slot_button_in_shallow_frame", 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="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="flow", name="depot", direction="vertical", style_mods={horizontal_align="left"}, children={
{type="flow", name="use_any_depot", direction="horizontal", style_mods={vertical_align="center"}, children={
{type="checkbox", name="use_same_depot", state=setting_flip(bits, SETTING_USE_ANY_DEPOT), tooltip={"cybersyn-gui.use-same-depot-tooltip"}, actions={
on_checked_state_changed={"setting-flip", comb.unit_number, SETTING_USE_ANY_DEPOT}
}},
{type="label", name="allow_list_label", style_mods={left_padding=3}, ref={"allow_list_label"}, caption={"cybersyn-gui.allow-list-description"}},
{type="label", name="use_same_depot_label", style_mods={left_padding=3}, caption={"cybersyn-gui.use-same-depot-description"}},
}},
{type="flow", name="depot_bypass", direction="horizontal", style_mods={vertical_align="center"}, children={
{type="checkbox", name="depot_bypass", state=setting_flip(bits, SETTING_DISABLE_DEPOT_BYPASS), tooltip={"cybersyn-gui.depot-bypass-tooltip"}, actions={
on_checked_state_changed={"setting-flip", comb.unit_number, SETTING_DISABLE_DEPOT_BYPASS}
}},
{type="label", name="depot_bypass_label", style_mods={left_padding=3}, caption={"cybersyn-gui.depot-bypass-description"}},
}},
}},
{type="flow", name="first", direction="vertical", style_mods={horizontal_align="left", right_margin=8}, children={
{type="flow", name="allow_list", direction="horizontal", style_mods={vertical_align="center"}, children={
{type="checkbox", name="allow_list", state=setting_flip(bits, SETTING_DISABLE_ALLOW_LIST), tooltip={"cybersyn-gui.allow-list-tooltip"}, actions={
on_checked_state_changed={"setting-flip", comb.unit_number, SETTING_DISABLE_ALLOW_LIST}
}},
{type="label", name="allow_list_label", style_mods={left_padding=3}, 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="checkbox", name="is_stack", state=setting(bits, SETTING_IS_STACK), tooltip={"cybersyn-gui.is-stack-tooltip"}, actions={
on_checked_state_changed={"setting", comb.unit_number, SETTING_IS_STACK}
}},
{type="label", name="is_stack_label", style_mods={left_padding=3}, ref={"is_stack_label"}, caption={"cybersyn-gui.is-stack-description"}},
{type="label", name="is_stack_label", style_mods={left_padding=3}, caption={"cybersyn-gui.is-stack-description"}},
}},
}}
}},
{type="flow", name="enable_inactive", direction="horizontal", style_mods={vertical_align="center"}, children={
{type="checkbox", name="enable_inactive", state=setting(bits, SETTING_ENABLE_INACTIVE), tooltip={"cybersyn-gui.enable-inactive-tooltip"}, actions={
on_checked_state_changed={"setting", comb.unit_number, SETTING_ENABLE_INACTIVE}
}},
{type="label", name="enable_inactive_label", style_mods={left_padding=3}, caption={"cybersyn-gui.enable-inactive-description"}},
}},
}}
}}
}}
@@ -197,38 +229,30 @@ 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
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
signal.name = NETWORK_EACH
element.elem_value = signal
end
set_comb_network_name(comb, signal)
combinator_update(global, comb)
elseif msg[1] == "allow_list" then
elseif msg[1] == "setting" 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 allows_all_trains = not element.state
set_comb_allows_all_trains(comb, allows_all_trains)
set_comb_setting(comb, msg[3], element.state)
combinator_update(global, comb)
elseif msg[1] == "is_stack" then
elseif msg[1] == "setting-flip" 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)
set_comb_setting(comb, msg[3], not element.state)
combinator_update(global, comb)
elseif msg[1] == "is_pr_switch" then

View File

@@ -3,37 +3,10 @@ local area = require("__flib__.area")
local abs = math.abs
local floor = math.floor
local ceil = math.ceil
local min = math.min
local max = math.max
local function table_compare(t0, t1)
if #t0 ~= #t1 then
return false
end
for i = 0, #t0 do
if t0[i] ~= t1[i] then
return false
end
end
return true
end
---@param a any[]
---@param i uint
local function iterr(a, i)
i = i + 1
if i <= #a then
local r = a[#a - i + 1]
return i, r
else
return nil, nil
end
end
---@param a any[]
local function irpairs(a)
return iterr, a, 0
end
---@param layout_pattern (0|1|2|3)[]
---@param layout (0|1|2)[]
function is_refuel_layout_accepted(layout_pattern, layout)
@@ -81,11 +54,6 @@ end
---@param train_id uint
---@param train Train
function remove_train(map_data, train_id, train)
local parked_at_depot_id = train.parked_at_depot_id
if parked_at_depot_id then
local depot = map_data.depots[parked_at_depot_id]
depot.available_train_id = nil
end
remove_available_train(map_data, train_id, train)
local layout_id = train.layout_id
@@ -191,9 +159,31 @@ function set_p_wagon_combs(map_data, station, train)
if not station.wagon_combs or not next(station.wagon_combs) then return end
local carriages = train.entity.carriages
local manifest = train.manifest--[[@as Manifest]]
if not manifest[1] then return end
local sign = mod_settings.invert_sign and 1 or -1
local is_reversed = get_train_direction(station.entity_stop, train.entity)
local locked_slots = station.locked_slots
local percent_slots_to_use_per_wagon = 1.0
if train.item_slot_capacity > 0 then
local total_item_slots
if locked_slots > 0 then
local total_cargo_wagons = #train.entity.cargo_wagons
total_item_slots = max(train.item_slot_capacity - total_cargo_wagons*locked_slots, 1)
else
total_item_slots = train.item_slot_capacity
end
local to_be_used_item_slots = 0
for i, item in ipairs(train.manifest) do
if item.type == "item" then
to_be_used_item_slots = to_be_used_item_slots + ceil(item.count/get_stack_size(map_data, item.name))
end
end
percent_slots_to_use_per_wagon = min(to_be_used_item_slots/total_item_slots, 1.0)
end
local item_i = 1
local item = manifest[item_i]
local item_count = item.count
@@ -215,39 +205,29 @@ function set_p_wagon_combs(map_data, station, train)
end
end
if carriage.type == "cargo-wagon" and item_i <= #manifest then
local signals = {}
local inv = carriage.get_inventory(defines.inventory.cargo_wagon)
if inv then
local signals = {}
local inv_filter_i = 1
local item_slots_capacity = #inv - station.locked_slots
local item_slots_capacity = max(ceil((#inv - locked_slots)*percent_slots_to_use_per_wagon), 1)
while item_slots_capacity > 0 do
local do_inc = false
local do_inc
if item.type == "item" then
local stack_size = get_stack_size(map_data, item.name)
local item_slots = ceil(item_count/stack_size)
local i = #signals + 1
local slots_to_filter
if item_slots > item_slots_capacity then
if comb then
signals[i] = {index = i, signal = {type = item.type, name = item.name}, count = item_slots_capacity*stack_size}
end
item_slots_capacity = 0
item_count = item_count - item_slots_capacity*stack_size
slots_to_filter = item_slots_capacity
else
if comb then
signals[i] = {index = i, signal = {type = item.type, name = item.name}, count = item_count}
end
item_slots_capacity = item_slots_capacity - item_slots
do_inc = true
slots_to_filter = item_slots
end
for j = 1, slots_to_filter do
local count_to_fill = min(item_slots_capacity*stack_size, item_count)
local slots_to_fill = ceil(count_to_fill/stack_size)
signals[i] = {index = i, signal = {type = item.type, name = item.name}, count = sign*count_to_fill}
item_count = item_count - count_to_fill
item_slots_capacity = item_slots_capacity - slots_to_fill
for j = 1, slots_to_fill do
inv.set_filter(inv_filter_i, item.name)
inv_filter_i = inv_filter_i + 1
end
train.has_filtered_wagon = true
do_inc = item_count == 0
else
do_inc = true
end
@@ -271,27 +251,18 @@ function set_p_wagon_combs(map_data, station, train)
local signals = {}
while fluid_capacity > 0 do
local do_inc = false
local do_inc
if fluid.type == "fluid" then
if fluid_count > fluid_capacity then
if comb then
signals[1] = {index = 1, signal = {type = fluid.type, name = fluid.name}, count = fluid_capacity}
end
fluid_capacity = 0
fluid_count = fluid_count - fluid_capacity
else
if comb then
signals[1] = {index = 1, signal = {type = fluid.type, name = fluid.name}, count = item_count}
end
fluid_capacity = fluid_capacity - fluid_count
fluid_i = fluid_i + 1
if fluid_i <= #manifest then
fluid = manifest[fluid_i]
fluid_count = fluid.count
end
end
break
local count_to_fill = min(fluid_count, fluid_capacity)
signals[1] = {index = 1, signal = {type = fluid.type, name = fluid.name}, count = sign*count_to_fill}
fluid_count = fluid_count - count_to_fill
fluid_capacity = 0
do_inc = fluid_count == 0
else
do_inc = true
end
if do_inc then
fluid_i = fluid_i + 1
if fluid_i <= #manifest then
fluid = manifest[fluid_i]
@@ -315,6 +286,7 @@ function set_r_wagon_combs(map_data, station, train)
local carriages = train.entity.carriages
local is_reversed = get_train_direction(station.entity_stop, train.entity)
local sign = mod_settings.invert_sign and -1 or 1
local ivpairs = is_reversed and irpairs or ipairs
for carriage_i, carriage in ivpairs(carriages) do
@@ -336,7 +308,7 @@ function set_r_wagon_combs(map_data, station, train)
local stack = inv[stack_i]
if stack.valid_for_read then
local i = #signals + 1
signals[i] = {index = i, signal = {type = "item", name = stack.name}, count = -stack.count}
signals[i] = {index = i, signal = {type = "item", name = stack.name}, count = sign*stack.count}
end
end
set_combinator_output(map_data, comb, signals)
@@ -347,7 +319,7 @@ function set_r_wagon_combs(map_data, station, train)
local inv = carriage.get_fluid_contents()
for fluid_name, count in pairs(inv) do
local i = #signals + 1
signals[i] = {index = i, signal = {type = "fluid", name = fluid_name}, count = -floor(count)}
signals[i] = {index = i, signal = {type = "fluid", name = fluid_name}, count = sign*floor(count)}
end
set_combinator_output(map_data, comb, signals)
end

51
cybersyn/scripts/lib.lua Normal file
View File

@@ -0,0 +1,51 @@
--By Mami
---@param v string
---@param h string?
function once(v, h)
return not h and v or nil--[[@as string|nil]]
end
---@param t any[]
---@return any
function rnext_consume(t)
local len = #t
if len > 1 then
local i = math.random(1, len)
local v = t[i]
t[i] = t[len]
t[len] = nil
return v
else
local v = t[1]
t[1] = nil
return v
end
end
function table_compare(t0, t1)
if #t0 ~= #t1 then
return false
end
for i = 0, #t0 do
if t0[i] ~= t1[i] then
return false
end
end
return true
end
---@param a any[]
---@param i uint
function irnext(a, i)
i = i + 1
if i <= #a then
local r = a[#a - i + 1]
return i, r
else
return nil, nil
end
end
---@param a any[]
function irpairs(a)
return irnext, a, 0
end

View File

@@ -1,9 +0,0 @@
function log_dispatch(network_name, r_stop, p_stop)
end
function log_refuel(network_name, refuel_stop)
end

View File

@@ -1,5 +1,4 @@
--By Mami
local floor = math.floor
local ceil = math.ceil
local table_insert = table.insert
@@ -23,12 +22,24 @@ end
---@param depot_id uint
---@param depot Depot
local function on_depot_broken(map_data, depot_id, depot)
local train_id = depot.available_train_id
if train_id then
local train = map_data.trains[train_id]
lock_train(train.entity)
send_alert_depot_of_train_broken(map_data, train.entity)
remove_train(map_data, train_id, train)
for train_id, train in pairs(map_data.trains) do
if train.depot_id == depot_id then
if train.use_any_depot then
local e = get_any_train_entity(train.entity)
local stops = e.force.get_train_stops({name = depot.entity_stop.backer_name, surface = e.surface})
for stop in rnext_consume, stops do
local new_depot_id = stop.unit_number
if map_data.depots[new_depot_id] then
train.depot_id = new_depot_id--[[@as uint]]
goto continue
end
end
end
lock_train(train.entity)
send_alert_depot_of_train_broken(map_data, train.entity)
remove_train(map_data, train_id, train)
end
::continue::
end
map_data.depots[depot_id] = nil
interface_raise_depot_removed(depot_id, depot)
@@ -105,17 +116,17 @@ local function on_station_built(map_data, stop, comb1, comb2)
entity_stop = stop,
entity_comb1 = comb1,
entity_comb2 = comb2,
--is_p = set_station_from_comb_state,
--is_r = set_station_from_comb_state,
--allows_all_trains = set_station_from_comb_state,
--is_p = set_station_from_comb,
--is_r = set_station_from_comb,
--allows_all_trains = set_station_from_comb,
deliveries_total = 0,
last_delivery_tick = map_data.total_ticks,
priority = 0,
item_priotity = nil,
r_threshold = 0,
locked_slots = 0,
--network_name = set_station_from_comb_state,
network_flag = 0,
--network_name = set_station_from_comb,
--network_flag = set_station_from_comb,
wagon_combs = nil,
deliveries = {},
accepted_layouts = {},
@@ -125,7 +136,6 @@ local function on_station_built(map_data, stop, comb1, comb2)
item_thresholds = nil,
display_state = 0,
}
set_station_from_comb_state(station)
local id = stop.unit_number--[[@as uint]]
map_data.stations[id] = station
map_data.warmup_station_ids[#map_data.warmup_station_ids + 1] = id
@@ -350,8 +360,8 @@ function combinator_update(map_data, comb, reset_display)
local params = control.parameters
local old_params = map_data.to_comb_params[unit_number]
local has_changed = false
local station
local id
local station = nil
local id = nil
if params.operation == MODE_PRIMARY_IO_ACTIVE or params.operation == MODE_PRIMARY_IO_FAILED_REQUEST or params.operation == MODE_PRIMARY_IO then
@@ -361,7 +371,10 @@ function combinator_update(map_data, comb, reset_display)
if stop then
id = stop.unit_number--[[@as uint]]
station = map_data.stations[id]
if should_reset and station and station.entity_comb1 == comb then
if station.entity_comb1 ~= comb then
station = nil
end
if should_reset and station then
--make sure only MODE_PRIMARY_IO gets stored on map_data.to_comb_params
if station.display_state == 0 then
params.operation = MODE_PRIMARY_IO
@@ -396,12 +409,12 @@ function combinator_update(map_data, comb, reset_display)
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
--NOTE: these updates have to be queued to occur at tick init since central planning is expecting them not to change between ticks
if not map_data.queue_station_update then
map_data.queue_station_update = {}
end
map_data.queue_station_update[id] = true
else
local depot = map_data.depots[id]
if depot then
@@ -426,11 +439,11 @@ function combinator_update(map_data, comb, reset_display)
if params.second_constant ~= old_params.second_constant then
has_changed = true
if station then
local pre = station.allows_all_trains
set_station_from_comb_state(station)
if station.allows_all_trains ~= pre then
update_stop_if_auto(map_data, station, true)
--NOTE: these updates have to be queued to occur at tick init since central planning is expecting them not to change between ticks
if not map_data.queue_station_update then
map_data.queue_station_update = {}
end
map_data.queue_station_update[id] = true
else
local refueler = map_data.refuelers[id]
if refueler then
@@ -553,13 +566,6 @@ local function on_stop_rename(map_data, stop, old_name)
end
end
end
else
local depot = map_data.depots[station_id]
if depot and depot.available_train_id then
local train = map_data.trains[depot.available_train_id--[[@as uint]]]
train.depot_name = stop.backer_name
--train.se_depot_surface_i = stop.surface.index
end
end
end
@@ -653,28 +659,28 @@ local function on_rename(event)
end
local function on_settings_changed(event)
mod_settings.tps = settings.global["cybersyn-ticks-per-second"].value --[[@as double]]
mod_settings.update_rate = settings.global["cybersyn-update-rate"].value --[[@as int]]
mod_settings.r_threshold = settings.global["cybersyn-request-threshold"].value--[[@as int]]
mod_settings.network_flag = settings.global["cybersyn-network-flag"].value--[[@as int]]
mod_settings.fuel_threshold = settings.global["cybersyn-fuel-threshold"].value--[[@as double]]
mod_settings.depot_bypass_enabled = settings.global["cybersyn-depot-bypass-enabled"].value--[[@as boolean]]
mod_settings.warmup_time = settings.global["cybersyn-warmup-time"].value--[[@as double]]
mod_settings.stuck_train_time = settings.global["cybersyn-stuck-train-time"].value--[[@as double]]
if event.setting == "cybersyn-ticks-per-second" then
if mod_settings.tps > DELTA then
local nth_tick = ceil(60/mod_settings.tps)--[[@as uint]];
script.on_nth_tick(nth_tick, function()
tick(global, mod_settings)
end)
else
script.on_nth_tick(nil)
---@param schedule TrainSchedule
---@param stop LuaEntity
---@param old_surface_index uint
local function se_add_direct_to_station_order(schedule, stop, old_surface_index)
local surface_i = stop.surface.index
if surface_i ~= old_surface_index then
local name = stop.backer_name
local records = schedule.records
for i = schedule.current, #records do
if records[i].station == name then
if i == 1 then
--we are assuming this is the depot order
records[#records + 1] = create_direct_to_station_order(stop)
schedule.current = #records--[[@as uint]]
else
table_insert(records, i, create_direct_to_station_order(stop))
end
break
end
end
end
interface_raise_on_mod_settings_changed(event)
end
local function setup_se_compat()
IS_SE_PRESENT = remote.interfaces["space-exploration"] ~= nil
if not IS_SE_PRESENT then return end
@@ -687,9 +693,6 @@ local function setup_se_compat()
---@type MapData
local map_data = global
local old_id = event.old_train_id_1
--NOTE: this is not guaranteed to be unique, it should be fine since the window of time for another train to mistakenly steal this train's event data is miniscule
--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 train_unique_identifier = event.train.front_stock.backer_name
local train = map_data.trains[old_id]
if not train then return end
@@ -706,19 +709,27 @@ local function setup_se_compat()
---@type uint
local new_id = train_entity.id
local old_surface_index = event.old_surface_index
local train_unique_identifier = event.train.front_stock.backer_name
--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 = event.old_train_id_1
local train = map_data.trains[old_id]
if not train then return end
if train.is_available then
local network = map_data.available_trains[train.network_name--[[@as string]]]
if network then
network[new_id] = true
network[old_id] = nil
local f, a
if train.network_name == NETWORK_EACH then
f, a = next, train.network_flag
else
f, a = once, train.network_name
end
for network_name in f, a do
local network = map_data.available_trains[network_name]
if network then
network[new_id] = true
network[old_id] = nil
if next(network) == nil then
map_data.available_trains[network_name] = nil
end
end
end
end
@@ -737,51 +748,60 @@ local function setup_se_compat()
train.se_awaiting_rename = nil
end
if not (train.status == STATUS_TO_P or train.status == STATUS_TO_R) then return end
local schedule = train_entity.schedule
if schedule then
if train.status == STATUS_TO_P or train.status == STATUS_TO_R then
local p_station = map_data.stations[train.p_station_id]
local p_name = p_station.entity_stop.backer_name
local p_surface_i = p_station.entity_stop.surface.index
local r_station = map_data.stations[train.r_station_id]
local r_name = r_station.entity_stop.backer_name
local r_surface_i = r_station.entity_stop.surface.index
local records = schedule.records
local i = schedule.current
while i <= #records do
if records[i].station == p_name and p_surface_i ~= old_surface_index then
table_insert(records, i, create_direct_to_station_order(p_station.entity_stop))
i = i + 1
elseif records[i].station == r_name and r_surface_i ~= old_surface_index then
table_insert(records, i, create_direct_to_station_order(r_station.entity_stop))
i = i + 1
end
i = i + 1
end
train_entity.schedule = schedule
elseif train.status == STATUS_TO_F then
local refueler = map_data.refuelers[train.refueler_id]
local f_name = refueler.entity_stop.backer_name
local f_surface_i = refueler.entity_stop.surface.index
local records = schedule.records
local i = schedule.current
while i <= #records do
if records[i].station == f_name and f_surface_i ~= old_surface_index then
table_insert(records, i, create_direct_to_station_order(refueler.entity_stop))
i = i + 1
end
i = i + 1
end
train_entity.schedule = schedule
if train.status == STATUS_TO_P then
local stop = map_data.stations[train.p_station_id].entity_stop
se_add_direct_to_station_order(schedule, stop, old_surface_index)
end
if train.status == STATUS_TO_P or train.status == STATUS_TO_R then
local stop = map_data.stations[train.r_station_id].entity_stop
se_add_direct_to_station_order(schedule, stop, old_surface_index)
end
if train.status == STATUS_TO_F then
local stop = map_data.refuelers[train.refueler_id].entity_stop
se_add_direct_to_station_order(schedule, stop, old_surface_index)
end
if not train.use_any_depot then
local depot = map_data.depots[train.depot_id]
se_add_direct_to_station_order(schedule, depot.entity_stop, old_surface_index)
end
train_entity.schedule = schedule
end
interface_raise_train_teleported(new_id, old_id)
end)
end
local function grab_all_settings()
mod_settings.tps = settings.global["cybersyn-ticks-per-second"].value --[[@as double]]
mod_settings.update_rate = settings.global["cybersyn-update-rate"].value --[[@as int]]
mod_settings.r_threshold = settings.global["cybersyn-request-threshold"].value--[[@as int]]
mod_settings.priority = settings.global["cybersyn-priority"].value--[[@as int]]
mod_settings.locked_slots = settings.global["cybersyn-locked-slots"].value--[[@as int]]
mod_settings.network_flag = settings.global["cybersyn-network-flag"].value--[[@as int]]
mod_settings.fuel_threshold = settings.global["cybersyn-fuel-threshold"].value--[[@as double]]
mod_settings.warmup_time = settings.global["cybersyn-warmup-time"].value--[[@as double]]
mod_settings.stuck_train_time = settings.global["cybersyn-stuck-train-time"].value--[[@as double]]
mod_settings.allow_cargo_in_depot = settings.global["cybersyn-allow-cargo-in-depot"].value--[[@as boolean]]
mod_settings.invert_sign = settings.global["cybersyn-invert-sign"].value--[[@as boolean]]
end
local function on_settings_changed(event)
grab_all_settings()
if event.setting == "cybersyn-ticks-per-second" then
if mod_settings.tps > DELTA then
local nth_tick = ceil(60/mod_settings.tps)--[[@as uint]];
script.on_nth_tick(nth_tick, function()
tick(global, mod_settings)
end)
else
script.on_nth_tick(nil)
end
end
interface_raise_on_mod_settings_changed(event)
end
local filter_built = {
{filter = "name", name = "train-stop"},
{filter = "name", name = COMBINATOR_NAME},
@@ -798,18 +818,10 @@ local filter_broken = {
{filter = "rolling-stock"},
}
local function main()
mod_settings.tps = settings.global["cybersyn-ticks-per-second"].value --[[@as double]]
mod_settings.update_rate = settings.global["cybersyn-update-rate"].value --[[@as int]]
mod_settings.r_threshold = settings.global["cybersyn-request-threshold"].value--[[@as int]]
mod_settings.network_flag = settings.global["cybersyn-network-flag"].value--[[@as int]]
mod_settings.fuel_threshold = settings.global["cybersyn-fuel-threshold"].value--[[@as double]]
mod_settings.warmup_time = settings.global["cybersyn-warmup-time"].value--[[@as double]]
mod_settings.stuck_train_time = settings.global["cybersyn-stuck-train-time"].value--[[@as double]]
mod_settings.depot_bypass_enabled = settings.global["cybersyn-depot-bypass-enabled"].value--[[@as boolean]]
grab_all_settings()
mod_settings.missing_train_alert_enabled = true
mod_settings.stuck_train_alert_enabled = true
mod_settings.react_to_nonempty_train_in_depot = true
mod_settings.react_to_train_at_incorrect_station = true
mod_settings.react_to_train_early_to_depot = true
@@ -848,6 +860,10 @@ local function main()
register_gui_actions()
script.on_init(function()
local setting = settings.global["cybersyn-invert-sign"]
setting.value = false
settings.global["cybersyn-invert-sign"] = setting
mod_settings.invert_sign = false
init_global()
setup_se_compat()
end)

View File

@@ -1,3 +1,4 @@
--By Mami
local flib_migration = require("__flib__.migration")
@@ -103,23 +104,40 @@ local migrations_table = {
map_data.each_refuelers = {}
map_data.se_tele_old_id = nil
for k, comb in pairs(map_data.to_comb) do
for id, comb in pairs(map_data.to_comb) do
local control = get_comb_control(comb)
local params = control.parameters
local params_old = map_data.to_comb_params[id]
local bits = params.second_constant or 0
local bits_old = params_old.second_constant or 0
local allows_all_trains = bits%2
local is_pr_state = math.floor(bits/2)%3
local allows_all_trains_old = bits_old%2
local is_pr_state_old = math.floor(bits_old/2)%3
local new_bits = bit32.bor(is_pr_state, allows_all_trains*4)
params.second_constant = new_bits
bits = bit32.bor(is_pr_state, allows_all_trains*4)
bits_old = bit32.bor(is_pr_state_old, allows_all_trains_old*4)
params.second_constant = bits
params_old.second_constant = bits_old
control.parameters = params
map_data.to_comb_params[id] = params_old
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)
local params = get_comb_params(station.entity_comb1)
local bits = params.second_constant or 0
local is_pr_state = bit32.extract(bits, 0, 2)
local allows_all_trains = bit32.extract(bits, SETTING_DISABLE_ALLOW_LIST) > 0
local is_stack = bit32.extract(bits, SETTING_IS_STACK) > 0
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
map_data.layout_train_count = {}
@@ -135,6 +153,77 @@ local migrations_table = {
end
end
end,
["1.2.2"] = function()
---@type MapData
local map_data = global
local setting = settings.global["cybersyn-invert-sign"]
setting.value = true
settings.global["cybersyn-invert-sign"] = setting
for id, comb in pairs(map_data.to_comb) do
local control = get_comb_control(comb)
local params = control.parameters
local params_old = map_data.to_comb_params[id]
local bits = params.second_constant or 0
local bits_old = params_old.second_constant or 0
bits = bit32.replace(bits, 1, SETTING_ENABLE_INACTIVE)--[[@as int]]
bits = bit32.replace(bits, 1, SETTING_ENABLE_INACTIVE)--[[@as int]]
bits_old = bit32.replace(bits_old, 1, SETTING_USE_ANY_DEPOT)--[[@as int]]
bits_old = bit32.replace(bits_old, 1, SETTING_USE_ANY_DEPOT)--[[@as int]]
params.second_constant = bits
params_old.second_constant = bits_old
control.parameters = params
map_data.to_comb_params[id] = params_old
end
for _, station in pairs(map_data.stations) do
station.enable_inactive = true
end
for train_id, train in pairs(map_data.trains) do
train.depot_id = train.parked_at_depot_id
if not train.depot_id then
local e = get_any_train_entity(train.entity)
local stops = e.force.get_train_stops({name = train.depot_name, surface = e.surface})
for stop in rnext_consume, stops do
local new_depot_id = stop.unit_number
if map_data.depots[new_depot_id] then
train.depot_id = new_depot_id--[[@as uint]]
break
end
end
end
if not train.depot_id then
train.depot_id = next(map_data.depots)
end
if not train.depot_id then
train.entity.manual_mode = true
send_alert_depot_of_train_broken(map_data, train.entity)
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 _, stop in pairs(global.stations) do
stop.accepted_layouts[layout_id] = nil
end
for _, stop in pairs(global.refuelers) do
stop.accepted_layouts[layout_id] = nil
end
else
global.layout_train_count[layout_id] = count - 1
end
map_data.trains[train_id] = nil
end
train.use_any_depot = true
train.disable_bypass = nil
train.depot_name = nil
train.se_depot_surface_i = nil
train.parked_at_depot_id = nil
end
end
}
--STATUS_R_TO_D = 5

View File

@@ -48,6 +48,18 @@ function on_failed_delivery(map_data, train_id, train)
unset_wagon_combs(map_data, station)
end
end
if train.has_filtered_wagon then
train.has_filtered_wagon = nil
for carriage_i, carriage in ipairs(train.entity.cargo_wagons) do
local inv = carriage.get_inventory(defines.inventory.cargo_wagon)
if inv and inv.is_filtered() then
---@type uint
for i = 1, #inv do
inv.set_filter(i, nil)
end
end
end
end
train.r_station_id = nil
train.p_station_id = nil
train.manifest = nil
@@ -60,14 +72,21 @@ end
---@param train_id uint
---@param train Train
function add_available_train(map_data, train_id, train)
local network_name = train.network_name
if network_name then
local network = map_data.available_trains[network_name]
if not network then
network = {}
map_data.available_trains[network_name] = network
if train.network_name then
local f, a
if train.network_name == NETWORK_EACH then
f, a = next, train.network_flag
else
f, a = once, train.network_name
end
for network_name in f, a do
local network = map_data.available_trains[network_name]
if not network then
network = {}
map_data.available_trains[network_name] = network
end
network[train_id] = true
end
network[train_id] = true
train.is_available = true
interface_raise_train_available(train_id)
end
@@ -80,77 +99,56 @@ end
---@param train Train
function add_available_train_to_depot(map_data, mod_settings, train_id, train, depot_id, depot)
local comb = depot.entity_comb
local network_name = get_comb_network_name(comb)
if network_name then
local network = map_data.available_trains[network_name]
if not network then
network = {}
map_data.available_trains[network_name] = network
end
network[train_id] = true
train.is_available = true
end
set_train_from_comb(mod_settings, train, comb)
depot.available_train_id = train_id
train.depot_id = depot_id
train.status = STATUS_D
train.parked_at_depot_id = depot_id
train.depot_name = depot.entity_stop.backer_name
train.se_depot_surface_i = depot.entity_stop.surface.index
train.network_name = network_name
train.network_flag = mod_settings.network_flag
train.priority = 0
if network_name then
local signals = comb.get_merged_signals(defines.circuit_connector_id.combinator_input)
if signals then
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
train.priority = item_count
end
if item_name == network_name then
train.network_flag = item_count
end
end
end
end
interface_raise_train_available(train_id)
end
add_available_train(map_data, train_id, train)
end
---@param map_data MapData
---@param train_id uint
---@param train Train
function remove_available_train(map_data, train_id, train)
---@type uint
if train.is_available and train.network_name then
local network = map_data.available_trains[train.network_name--[[@as string]]]
if network then
network[train_id] = nil
if next(network) == nil then
map_data.available_trains[train.network_name] = nil
if train.is_available then
train.is_available = nil
local f, a
if train.network_name == NETWORK_EACH then
f, a = next, train.network_flag
else
f, a = once, train.network_name
end
for network_name in f, a do
local network = map_data.available_trains[network_name]
if network then
network[train_id] = nil
if next(network) == nil then
map_data.available_trains[network_name] = nil
end
end
end
train.is_available = nil
local depot = map_data.depots[train.depot_id]
if depot.available_train_id == train_id then
depot.available_train_id = nil
return true
end
end
return false
end
---@param map_data MapData
---@param depot_id uint
---@param train_entity LuaTrain
local function on_train_arrives_depot(map_data, depot_id, train_entity)
local contents = train_entity.get_contents()
local fluid_contents = train_entity.get_fluid_contents()
local is_train_empty = next(contents) == nil and next(fluid_contents) == nil
local is_train_empty = next(train_entity.get_contents()) == nil and next(train_entity.get_fluid_contents()) == nil
local train_id = train_entity.id
local train = map_data.trains[train_id]
if train then
if train.status == STATUS_TO_D then
elseif train.status == STATUS_TO_D_BYPASS or train.status == STATUS_D then
--shouldn't be possible to get train.status == STATUS_D
elseif train.status == STATUS_TO_D_BYPASS or train.status == STATUS_D then
remove_available_train(map_data, train_id, train)
elseif mod_settings.react_to_train_early_to_depot then
if train.manifest then
@@ -160,21 +158,19 @@ local function on_train_arrives_depot(map_data, depot_id, train_entity)
else
return
end
if is_train_empty then
if is_train_empty or mod_settings.allow_cargo_in_depot then
local old_status = train.status
add_available_train_to_depot(map_data, mod_settings, train_id, train, depot_id, map_data.depots[depot_id])
set_depot_schedule(train_entity, train.depot_name)
local depot = map_data.depots[depot_id]
add_available_train_to_depot(map_data, mod_settings, train_id, train, depot_id, depot)
set_depot_schedule(train_entity, depot.entity_stop.backer_name)
interface_raise_train_status_changed(train_id, old_status, STATUS_D)
else
--train still has cargo
if mod_settings.react_to_nonempty_train_in_depot then
lock_train_to_depot(train_entity)
remove_train(map_data, train_id, train)
send_alert_nonempty_train_in_depot(map_data, train_entity)
end
interface_raise_train_nonempty_in_depot(depot_id, train_entity, train_id)
lock_train_to_depot(train_entity)
remove_train(map_data, train_id, train)
send_alert_nonempty_train_in_depot(map_data, train_entity)
end
elseif is_train_empty then
elseif is_train_empty or mod_settings.allow_cargo_in_depot then
--NOTE: only place where new Train
train = {
entity = train_entity,
@@ -188,22 +184,25 @@ local function on_train_arrives_depot(map_data, depot_id, train_entity)
last_manifest_tick = map_data.total_ticks,
has_filtered_wagon = nil,
--is_available = add_available_train_to_depot,
--parked_at_depot_id = add_available_train_to_depot,
--depot_name = add_available_train_to_depot,
--depot_id = add_available_train_to_depot,
--use_any_depot = add_available_train_to_depot,
--disable_bypass = add_available_train_to_depot,
--network_name = add_available_train_to_depot,
--network_flag = add_available_train_to_depot,
--priority = add_available_train_to_depot,
}
}--[[@as Train]]
set_train_layout(map_data, train)
map_data.trains[train_id] = train
add_available_train_to_depot(map_data, mod_settings, train_id, train, depot_id, map_data.depots[depot_id])
local depot = map_data.depots[depot_id]
add_available_train_to_depot(map_data, mod_settings, train_id, train, depot_id, depot)
set_depot_schedule(train_entity, train.depot_name)
set_depot_schedule(train_entity, depot.entity_stop.backer_name)
interface_raise_train_created(train_id, depot_id)
else
if mod_settings.react_to_nonempty_train_in_depot then
send_alert_nonempty_train_in_depot(map_data, train_entity)
end
lock_train_to_depot(train_entity)
send_alert_nonempty_train_in_depot(map_data, train_entity)
end
if not is_train_empty then
interface_raise_train_nonempty_in_depot(depot_id, train_entity)
end
end
@@ -218,7 +217,7 @@ local function on_train_arrives_station(map_data, station_id, train_id, train)
if train.p_station_id == station_id then
train.status = STATUS_P
local station = map_data.stations[station_id]
set_comb1(map_data, station, train.manifest, 1)
set_comb1(map_data, station, train.manifest, mod_settings.invert_sign and 1 or -1)
set_p_wagon_combs(map_data, station, train)
interface_raise_train_status_changed(train_id, STATUS_TO_P, STATUS_P)
end
@@ -226,7 +225,7 @@ local function on_train_arrives_station(map_data, station_id, train_id, train)
if train.r_station_id == station_id then
train.status = STATUS_R
local station = map_data.stations[station_id]
set_comb1(map_data, station, train.manifest, -1)
set_comb1(map_data, station, train.manifest, mod_settings.invert_sign and -1 or 1)
set_r_wagon_combs(map_data, station, train)
interface_raise_train_status_changed(train_id, STATUS_TO_R, STATUS_R)
end
@@ -316,45 +315,54 @@ local function on_train_leaves_stop(map_data, mod_settings, train_id, train)
end
if fuel_fill > mod_settings.fuel_threshold then
--if fuel_fill == INF, it's probably a modded electric train
if mod_settings.depot_bypass_enabled then
if not train.disable_bypass then
train.status = STATUS_TO_D_BYPASS
add_available_train(map_data, train_id, train)
interface_raise_train_status_changed(train_id, STATUS_R, STATUS_TO_D_BYPASS)
return
end
else
local refuelers = map_data.to_refuelers[train.network_name]
if refuelers then
local best_refueler_id = nil
local best_dist = INF
local best_prior = -INF
for id, _ in pairs(refuelers) do
local refueler = map_data.refuelers[id]
set_refueler_from_comb(map_data, mod_settings, id)
local f, a
if train.network_name == NETWORK_EACH then
f, a = next, train.network_flag
else
f, a = once, train.network_name
end
for network_name in f, a do
local refuelers = map_data.to_refuelers[network_name]
if refuelers then
local best_refueler_id = nil
local best_dist = INF
local best_prior = -INF
for id, _ in pairs(refuelers) do
local refueler = map_data.refuelers[id]
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
dist = get_stop_dist(train.entity.front_stock, refueler.entity_stop)
accepted = dist < best_dist
end
if accepted or refueler.priority > best_prior then
best_refueler_id = id
best_dist = dist or get_stop_dist(train.entity.front_stock, refueler.entity_stop)
best_prior = refueler.priority
local refueler_network_flag = get_network_flag(refueler, network_name)
local train_network_flag = get_network_flag(train, network_name)
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
dist = get_stop_dist(train.entity.front_stock, refueler.entity_stop)
accepted = dist < best_dist
end
if accepted or refueler.priority > best_prior then
best_refueler_id = id
best_dist = dist or get_stop_dist(train.entity.front_stock, refueler.entity_stop)
best_prior = refueler.priority
end
end
end
end
if best_refueler_id then
train.status = STATUS_TO_F
train.refueler_id = best_refueler_id
local refueler = map_data.refuelers[best_refueler_id]
refueler.trains_total = refueler.trains_total + 1
add_refueler_schedule(map_data, train.entity, refueler.entity_stop)
interface_raise_train_status_changed(train_id, STATUS_R, STATUS_TO_F)
return
if best_refueler_id then
train.status = STATUS_TO_F
train.refueler_id = best_refueler_id
local refueler = map_data.refuelers[best_refueler_id]
refueler.trains_total = refueler.trains_total + 1
add_refueler_schedule(map_data, train.entity, refueler.entity_stop)
interface_raise_train_status_changed(train_id, STATUS_R, STATUS_TO_F)
return
end
end
end
end
@@ -367,7 +375,7 @@ local function on_train_leaves_stop(map_data, mod_settings, train_id, train)
refueler.trains_total = refueler.trains_total - 1
unset_wagon_combs(map_data, refueler)
set_combinator_output(map_data, refueler.entity_comb, nil)
if mod_settings.depot_bypass_enabled then
if not train.disable_bypass then
train.status = STATUS_TO_D_BYPASS
add_available_train(map_data, train_id, train)
else

View File

@@ -18,28 +18,37 @@ data:extend({
minimum_value = 1,
maximum_value = 2147483647,
},
--{
-- type = "int-setting",
-- name = "cybersyn-wait-time",
-- order = "ab",
-- setting_type = "runtime-global",
-- default_value = 2000,
-- minimum_value = 1,
-- maximum_value = 2147483647,
--},
{
type = "double-setting",
type = "int-setting",
name = "cybersyn-request-threshold",
order = "ac",
order = "ba",
setting_type = "runtime-global",
default_value = 1,
minimum_value = 0,
default_value = 2000,
minimum_value = 1,
maximum_value = 2147483647,
},
{
type = "int-setting",
name = "cybersyn-priority",
order = "bb",
setting_type = "runtime-global",
default_value = 0,
minimum_value = -2147483648,
maximum_value = 2147483647,
},
{
type = "int-setting",
name = "cybersyn-locked-slots",
order = "bc",
setting_type = "runtime-global",
default_value = 0,
minimum_value = 0,
maximum_value = 1000,
},
{
type = "int-setting",
name = "cybersyn-network-flag",
order = "ad",
order = "bd",
setting_type = "runtime-global",
default_value = -1,
minimum_value = -2147483648,
@@ -48,23 +57,16 @@ data:extend({
{
type = "double-setting",
name = "cybersyn-fuel-threshold",
order = "ae",
order = "be",
setting_type = "runtime-global",
default_value = .5,
minimum_value = 0,
maximum_value = 1,
},
{
type = "bool-setting",
name = "cybersyn-depot-bypass-enabled",
order = "af",
setting_type = "runtime-global",
default_value = true,
},
{
type = "double-setting",
name = "cybersyn-warmup-time",
order = "ag",
order = "ca",
setting_type = "runtime-global",
default_value = 20,
minimum_value = 0,
@@ -73,10 +75,24 @@ data:extend({
{
type = "double-setting",
name = "cybersyn-stuck-train-time",
order = "ah",
order = "cb",
setting_type = "runtime-global",
default_value = 600,
minimum_value = 0,
maximum_value = 2147483647,
},
{
type = "bool-setting",
name = "cybersyn-allow-cargo-in-depot",
order = "cc",
setting_type = "runtime-global",
default_value = false,
},
{
type = "bool-setting",
name = "cybersyn-invert-sign",
order = "da",
setting_type = "runtime-global",
default_value = false,
},
})

1
cybersyn_blueprints.txt Normal file
View File

File diff suppressed because one or more lines are too long