Merge pull request #46 from mamoniot/experimental

1.2.14 release
This commit is contained in:
Monica Moniot
2023-03-21 09:01:34 -04:00
committed by GitHub
34 changed files with 1549 additions and 612 deletions

2
.gitignore vendored
View File

@@ -1 +1,3 @@
*.zip
.vscode/launch.json
.vscode/settings.json

8
.vscode/launch.json vendored
View File

@@ -55,6 +55,12 @@
"debugadapter": true,
"flib": true,
},
}
},
{
"type": "factoriomod",
"request": "launch",
"name": "Factorio Debug (Modded)",
"modsPath": "~/.factorio/mods-debug/",
},
]
}

View File

@@ -13,11 +13,13 @@
"~/.steam/steam/steamapps/common/Factorio/data/core/lualib",
"./.vscode/factorio",
"./.vscode/flib",
"/home/mami/.config/Code/User/workspaceStorage/fdae937c5189f993d370b36f3104188f/justarandomgeek.factoriomod-debug/sumneko-3rd/factorio/library"
"/home/mami/.config/Code/User/workspaceStorage/fdae937c5189f993d370b36f3104188f/justarandomgeek.factoriomod-debug/sumneko-3rd/factorio/library",
"/home/mami/.config/Code/User/workspaceStorage/95c2a8450056100ba3db30ac2a468857/justarandomgeek.factoriomod-debug/sumneko-3rd/factorio/library"
],
"Lua.workspace.userThirdParty": [
"/home/mami/.config/Code/User/workspaceStorage/fdae937c5189f993d370b36f3104188f/justarandomgeek.factoriomod-debug/sumneko-3rd",
"/home/mami/.config/Code/User/workspaceStorage/95c2a8450056100ba3db30ac2a468857/justarandomgeek.factoriomod-debug/sumneko-3rd"
"/home/mami/.config/Code/User/workspaceStorage/95c2a8450056100ba3db30ac2a468857/justarandomgeek.factoriomod-debug/sumneko-3rd",
"/home/mami/.config/Code/User/workspaceStorage/4e7db8f77e7d31cf7af01a542e707dfb/justarandomgeek.factoriomod-debug/sumneko-3rd"
],
"Lua.diagnostics.globals": [
"__DebugAdapter",
@@ -34,7 +36,7 @@
"Lua.runtime.special": {
"__object_name": "type"
},
"Lua.runtime.plugin": "/home/mami/.config/Code/User/workspaceStorage/fdae937c5189f993d370b36f3104188f/justarandomgeek.factoriomod-debug/sumneko-3rd/factorio/plugin.lua",
"Lua.runtime.plugin": "/home/mami/.config/Code/User/workspaceStorage/95c2a8450056100ba3db30ac2a468857/justarandomgeek.factoriomod-debug/sumneko-3rd/factorio/plugin.lua",
"Lua.diagnostics.disable": [
"lowercase-global"
],

View File

@@ -14,9 +14,9 @@ Copy the contents of https://raw.githubusercontent.com/mamoniot/project-cybersyn
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.
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 cargo, as in items or fluids, that station will request or provide to the Cybersyn network. A positive cargo signal is interpreted as that station providing that cargo to the network; A negative cargo signal is interpreted as that station requesting that cargo from the network.
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. Then add the signal called "Request threshold" to the constant combinator. Set its signal strength to be at most the cargo capacity of the trains in your system. 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.
To make a basic provider station, create a cargo 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 cargo you want this requester station to keep loaded in its cargo buffer, and flip the sign of each so the signal strength is negative. Then add the signal called "Request threshold" to the constant combinator. Set its signal strength to be at most the cargo capacity of the trains in your system. Once the provider station contains the cargo being requested, a train will automatically be sent to deliver that cargo from the provider station to the requester station. The requester station's buffer will automatically be topped up on the cargo 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. 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.
@@ -28,13 +28,13 @@ Follow the above directions and you have set up a bare minimum Cybersyn network!
### A whole suite of new and optional circuit network inputs and outputs to control your stations precisely
* Natively read out all deliveries currently in progress for a station, not just the loading or unloading orders of the parked train.
* Set request thresholds per item instead of just for the entire station.
* Read out item loading or unloading orders per train wagon, instead of just for the entire train.
* Set item filters per cargo wagon, making multi-item deliveries far less of a headache.
* Set request thresholds per cargo instead of just for the entire station.
* Read out cargo loading or unloading orders per train wagon, instead of just for the entire train.
* Set item filters per cargo wagon, making multi-item deliveries very easy.
![Image](https://raw.githubusercontent.com/mamoniot/project-cybersyn/main/previews/universal-station.png)
These all combine to make it possible to **create "universal" stations**; stations that provide any arbitrary number of different items for a train of any arbitrary length! Build stations that supply you all items from your mall, stations that grab selected items from your disorganized storage chests, or stations that transfer any item between two otherwise completely distinct networks! The possibilities are far less limited.
These all combine to make it possible to **create "universal" stations**; stations that provide any arbitrary number of different items for a train of any arbitrary length! Build stations that supply you all items from your mall, stations that grab selected items from your disorganized storage chests, or stations that transfer any item between two otherwise completely distinct networks!
![Image](https://raw.githubusercontent.com/mamoniot/project-cybersyn/main/previews/gui-allow-list.png)
@@ -48,9 +48,9 @@ Trains can **bypass visiting the depot** if they have enough fuel. Trains spend
![Image](https://raw.githubusercontent.com/mamoniot/project-cybersyn/main/previews/fault-alert.png)
**Improved fault handling.** Mistakes and misconfigured stations are unlikely to result in items being delivered to places they shouldn't, and the player will be alerted immediately about the fault.
**Improved fault handling.** Mistakes and misconfigured stations are unlikely to result in cargo being delivered to places they shouldn't, and the player will be alerted immediately about the fault.
Runs a custom, **highly optimized central planning algorithm**, resulting in exceptionally good performance. Outperforms LTN by a factor of ~3x *(disclaimer: there is no perfectly apples-to-apples performance test since the features and algorithms of these mods are not the same, see [project-cybersyn/previews/performance/](https://github.com/mamoniot/project-cybersyn/tree/main/previews/performance) for more details)*. The station update rate is twice that of LTN by default, and depots don't count towards station updates.
Runs a custom, **highly optimized central planning algorithm**, resulting in exceptionally good performance, see https://forums.factorio.com/viewtopic.php?p=580024#p580024. This allows the station update rate to be twice that of LTN by default, resulting in more responsive dispatching, especially on larger bases.
![Image](https://raw.githubusercontent.com/mamoniot/project-cybersyn/main/previews/se-compat.png)
@@ -72,7 +72,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 (negative) or unloaded (positive) from the train.
When placed adjacent to a vanilla train stop, a Cybersyn station is created. This station can provide or request cargo to your train network. Connect the input of the combinator to a circuit network; When a positive cargo signal is received, this station will provide that cargo to the network, when a negative signal is received, this station will request that cargo from the network. When a station is providing a type of cargo that another station is requesting, a train order will automatically be generated to transfer that cargo 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 cargo 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.
@@ -94,25 +94,25 @@ Refuelers can automatically build allow-lists. When this option is enabled, trai
![Image](https://raw.githubusercontent.com/mamoniot/project-cybersyn/main/previews/science.png)
When placed adjacent to the train stop of an already existing Cybersyn station, this combinator will provide a second set of inputs and outputs that can be used to more precisely control this station. The combinator input allows for request thresholds to be set per-item. Any non-zero item signal given on the input circuit network will override the station's request thresholds for just that item. When this is used on a provider station, if the station has more items than the input threshold, it will force the creation of a delivery for that item to the next available requester station. This effectively overrides the requester station's request threshold. This is useful for purging unwanted items from provider stations.
When placed adjacent to the train stop of an already existing Cybersyn station, this combinator will provide a second set of inputs and outputs that can be used to more precisely control this station. The combinator input allows for request thresholds to be set per-cargo. Any non-zero cargo signal given on the input circuit network will override the station's request thresholds for just that cargo. When this is used on a provider station, if the station has more cargo than the input threshold, it will force the creation of a delivery for that cargo to the next available requester station. This effectively overrides the requester station's request threshold. This is useful for purging unwanted cargo from provider stations.
If a station control combinator receives a "priority" signal as input, it will apply that priority to each item signal it is receiving as input. This allows you to specify up to 2 different priorities per-item on a single station.
If a station control combinator receives a "priority" signal as input, it will apply that priority to each cargo signal it is receiving as input. This allows you to specify up to 2 different priorities per-cargo on a single station.
The output of the combinator gives the sum total of all item loading or unloading orders in progress for the station. The very tick a train is dispatched for a new order to the station, that order is added to the output of this combinator, and it is removed as soon as the train leaves the station. The primary use case for this is to prevent duplicate orders from being generated for stations that provide the same pool of items. Only one train can be dispatched per-tick per-item specifically to accommodate this.
The output of the combinator gives the sum total of all cargo loading or unloading orders in progress for the station. The very tick a train is dispatched for a new order to the station, that order is added to the output of this combinator, and it is removed as soon as the train leaves the station. The primary use case for this is to prevent duplicate orders from being generated for stations that provide the same pool of cargo. Only one train can be dispatched per-tick per-cargo specifically to accommodate this.
### Wagon control mode
![Image](https://raw.githubusercontent.com/mamoniot/project-cybersyn/main/previews/filtered-slots.png)
When placed adjacent to the tracks of an already existing Cybersyn station, this combinator will connect to any wagon that parks adjacent to it. The output of this combinator gives the list of items expected to be loaded or unloaded to just this specific wagon. In addition, if this wagon is a cargo wagon, its slots will automatically be filtered so items can only enter it in sorted order. These combined make it straightforward to precisely load a cargo wagon to the exact specification desired by the requesting station. Connect the output to a filter inserter, keep a count of how many items have been loaded into the wagon with a memory cell, and use an unloading inserter to remove any items that exceed the requested load amount. If done correctly you have built a universal item loader for this cargo wagon. Build one of these units for each cargo wagon along the station and you have created what I call a universal station. The input of a wagon control combinator has no function currently.
When placed adjacent to the tracks of an already existing Cybersyn station, this combinator will connect to any wagon that parks adjacent to it. The output of this combinator gives the list of cargo expected to be loaded or unloaded to just this specific wagon. In addition, if this wagon is a cargo wagon, its slots will automatically be filtered so items can only enter it in sorted order. These combined make it straightforward to precisely load a cargo wagon to the exact specification desired by the requesting station. Connect the output to a filter inserter, keep a count of how many items have been loaded into the wagon with a memory cell, and use an unloading inserter to remove any items that exceed the requested load amount. If done correctly you have built a universal item loader for this cargo wagon. Build one of these units for each cargo wagon along the station and you have created what I call a universal station. The input of a wagon control combinator has no function currently.
### Networks
![Image](https://raw.githubusercontent.com/mamoniot/project-cybersyn/main/previews/gui-network.png)
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.
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 cargo; Orders will never be generated to transfer cargo 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.
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 a type of cargo, that cargo will be ignored by stations, its signal will only ever be interpreted as the network mask.
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.
@@ -120,7 +120,7 @@ A network can be set to the "each" virtual signal. When in this mode, each virtu
![Image](https://raw.githubusercontent.com/mamoniot/project-cybersyn/main/previews/virtual-signals.png)
If a station combinator receives a request threshold signal as input, a request order for the station will only be generated if the station is requesting a number of items exceeding the request threshold. In addition, there must be a station in the network which is providing at least as many items as the request threshold, and there must be a train in the network that has cargo capacity exceeding the request threshold. Therefore all generated orders must be for a number of items greater than or equal to the request threshold. By setting high thresholds, the traffic on your network can be greatly reduced, at the cost of needing to maintain larger item buffers at each station. The request threshold signal sets the request threshold "per-station" whereas the station control combinator can set or override the threshold per-item as well.
If a station combinator receives a request threshold signal as input, a request order for the station will only be generated if the station is requesting a number of cargo exceeding the request threshold. In addition, there must be a station in the network which is providing at least as much cargo as the request threshold, and there must be a train in the network that has cargo capacity exceeding the request threshold. Therefore all generated orders must be for a number of cargo greater than or equal to the request threshold. By setting high thresholds, the traffic on your network can be greatly reduced, at the cost of needing to maintain larger cargo buffers at each station. The request threshold signal sets the request threshold "per-station" whereas the station control combinator can set or override the threshold per-cargo as well.
On station combinators there is a setting called "Stack thresholds". When set, any request threshold for this station will be multiplied by the stack size of any item it is being compared to. This applies to station control thresholds as well. Thus the request threshold can be specified based on total stack count rather than total item count. Fluids are unaffected by the "Stack thresholds" setting, they are always specified by total fluid count.
@@ -128,13 +128,13 @@ There is no "provide threshold" in this mod because by design there is no need f
### Locked slots per cargo wagon
After an order has been generated, enough items will be subtracted from that order to ensure at least X number of slots in each cargo wagon can be left empty, where X is the "Locked slots per cargo wagon" signal being received by the station combinator. It is necessary for multi-item stations to function.
After an order has been generated, enough cargo will be subtracted from that order to ensure at least X number of slots in each cargo wagon can be left empty, where X is the "Locked slots per cargo wagon" signal being received by the station combinator. It is necessary for multi-item stations to function.
### Priority
Orders will be generated first for stations, depots and 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.
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 cargo shortage (front-pressure), round robin distribution will be used, and in times of cargo surplus (back-pressure), minimum travel distance distribution will be used. Provider stations will be prevented from providing cargo 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.
If a combinator set to station control mode receives a priority signal, for each cargo signal input to the combinator, cargo 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 cargo that a station processes.
### Train limits

1
TODO
View File

@@ -37,3 +37,4 @@ compat:
picker dollies?
cargo ships
editor extensions (test surface separation)
train fuel types for refuelers

View File

@@ -1,11 +1,32 @@
---------------------------------------------------------------------------------------------------
Version: 1.2.14
Date: 2023-3-14
Features:
- Added opt-in WIP trains, inventory, and station manager gui (highly experimental, use at your own risk)
Bugfixes:
- Provider override thresholds now correctly override the required train capacity as well; fix contributed by shopt
- Fixed a rare crash relating to an uninitialized network mask on a new station
- Fixed a SE space elevator bug that caused incorrect orders when the provider and requester station had the same name
- Added an incompatibility error with picker dollies, proper compatibility to come in a later update
---------------------------------------------------------------------------------------------------
Version: 1.2.13
Date: 2023-2-5
Features:
- Loader support for automatic allow-list contributed by BadgersOnWalls
Bugfixes:
- Fixed a crash caused when certain mod entities are deleted
---------------------------------------------------------------------------------------------------
Version: 1.2.12
Date: 2023-1-11
Translation:
- русский язык (Russian language) contributed by Eldrinn Elantey
---------------------------------------------------------------------------------------------------
Version: 1.2.11
Date: 2023-1-11
Bugfixes:
- Fixed a bug in 1.2.10 where allow lists were being set incorrectly, if you downloaded 1.2.10 and were affected by this uncheck and check "automatic allow list"
Translation:
- Deutsche sprache (German language) contributed by Ebaw
- русский язык (Russian language) contributed by Eldrinn Elantey
---------------------------------------------------------------------------------------------------
Version: 1.2.10
Date: 2023-1-11

152
cybersyn/constants.lua Normal file
View File

@@ -0,0 +1,152 @@
local constants = {}
constants.colors = {
caption = {
str = "255, 230, 192",
tbl = { 255, 230, 192 },
},
green = {
str = "69, 255, 69",
tbl = { 69, 255, 69 },
},
info = {
str = "128, 206, 240",
tbl = { 128, 206, 240 },
},
red = {
str = "255, 69, 69",
tbl = { 255, 69, 69 },
},
station_circle = {
str = "255, 50, 50, 190",
tbl = { 255, 50, 50, 190 },
},
yellow = {
str = "255, 240, 69",
tbl = { 255, 240, 69 },
},
white = {
str = "255, 255, 255",
tbl = { 255, 255, 255 },
},
}
-- dictionary locale identifier -> dictionary of hardcoded GUI sizes
constants.gui = {
en = {
trains = {
train_id = 90,
status = 378,
composition = 200,
depot = 149,
shipment = 36 * 6,
shipment_columns = 6,
},
stations = {
name = 238,
status = 53,
network_id = 84,
provided_requested = 36 * 6,
provided_requested_columns = 6,
shipments = 36 * 5,
shipments_columns = 5,
control_signals = 36 * 7,
control_signals_columns = 7,
},
depots = {
name = 200,
network_id = 84,
status = 200,
trains = 200,
},
history = {
train_id = 60,
route = 357,
depot = 160,
network_id = 84,
runtime = 68,
finished = 68,
shipment = (36 * 6),
shipment_checkbox_stretchy = true,
},
alerts = {
time = 68,
train_id = 60,
route = 326,
network_id = 84,
type = 230,
type_checkbox_stretchy = true,
contents = 36 * 6,
},
},
}
constants.gui_content_frame_height = 744
constants.gui_inventory_table_height = 40 * 18
constants.gui_translations = {
delivering_to = { "cybersyn-gui.delivering-to" },
fetching_from = { "cybersyn-gui.fetching-from" },
loading_at = { "cybersyn-gui.loading-at" },
not_available = { "cybersyn-gui.not-available" },
parked_at_depot_with_residue = { "cybersyn-gui.parked-at-depot-with-residue" },
parked_at_depot = { "cybersyn-gui.parked-at-depot" },
returning_to_depot = { "cybersyn-gui.returning-to-depot" },
unloading_at = { "cybersyn-gui.unloading-at" },
}
constants.input_sanitizers = {
["%%"] = "%%%%",
["%("] = "%%(",
["%)"] = "%%)",
["%.^[%*]"] = "%%.",
["%+"] = "%%+",
["%-"] = "%%-",
["^[%.]%*"] = "%%*",
["%?"] = "%%?",
["%["] = "%%[",
["%]"] = "%%]",
["%^"] = "%%^",
["%$"] = "%%$",
}
constants.ltn_control_signals = {
["ltn-depot"] = true,
["ltn-depot-priority"] = true,
-- excluded because it's shown as a separate column
-- ["ltn-network-id"] = true,
["ltn-min-train-length"] = true,
["ltn-max-train-length"] = true,
["ltn-max-trains"] = true,
["ltn-provider-threshold"] = true,
["ltn-provider-stack-threshold"] = true,
["ltn-provider-priority"] = true,
["ltn-locked-slots"] = true,
["ltn-requester-threshold"] = true,
["ltn-requester-stack-threshold"] = true,
["ltn-requester-priority"] = true,
["ltn-disable-warnings"] = true,
}
constants.ltn_event_names = {
on_stops_updated = true,
on_dispatcher_updated = true,
-- on_delivery_pickup_complete = true,
on_delivery_completed = true,
on_delivery_failed = true,
-- on_dispatcher_no_train_found = true,
on_provider_missing_cargo = true,
on_provider_unscheduled_cargo = true,
on_requester_remaining_cargo = true,
on_requester_unscheduled_cargo = true,
}
if script then
constants.open_station_gui_tooltip = {
"",
{ "cybersyn-gui.open-station-gui" },
remote.interfaces["ltn-combinator"] and { "", "\n", { "cybersyn-gui.open-ltn-combinator-gui" } } or nil,
}
end
return constants

View File

@@ -8,6 +8,8 @@ require('prototypes.entity')
require('prototypes.signal')
require('prototypes.misc')
require('prototypes.gui-style')
data:extend({
combinator_entity,
combinator_out_entity,
@@ -24,14 +26,14 @@ data:extend({
--{
-- type = "shortcut",
-- name = "ltnm-toggle-gui",
-- name = "cybersyn-toggle-gui",
-- icon = data_util.build_sprite(nil, { 0, 0 }, util.paths.shortcut_icons, 32, 2),
-- disabled_icon = data_util.build_sprite(nil, { 48, 0 }, util.paths.shortcut_icons, 32, 2),
-- small_icon = data_util.build_sprite(nil, { 0, 32 }, util.paths.shortcut_icons, 24, 2),
-- disabled_small_icon = data_util.build_sprite(nil, { 36, 32 }, util.paths.shortcut_icons, 24, 2),
-- toggleable = true,
-- action = "lua",
-- associated_control_input = "ltnm-toggle-gui",
-- associated_control_input = "cybersyn-toggle-gui",
-- technology_to_unlock = "logistic-train-network",
--},
})

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@@ -1,6 +1,6 @@
{
"name": "cybersyn",
"version": "1.2.11",
"version": "1.2.14",
"title": "Project Cybersyn",
"author": "Mami",
"factorio_version": "1.1",
@@ -11,6 +11,7 @@
"? space-exploration >= 0.6.94",
"? miniloader",
"? nullius",
"? pypostprocessing"
"? pypostprocessing",
"! LtnManager"
]
}

View File

@@ -12,6 +12,9 @@ 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)
cybersyn-manager-enabled=Enable the Cybersyn GUI.
cybersyn-manager-update-rate=Manager refresh tick interval
cybersyn-manager-result-limit=Max entities displayed on GUI pages.
[mod-setting-description]
cybersyn-enable-planner=Enable or disable the central planning algorithm. If disabled no new trains will be dispatched.
@@ -27,6 +30,7 @@ cybersyn-warmup-time=How many seconds a cybernetic combinator will wait before c
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.
cybersyn-manager-result-limit=Caps the number of matching enitities (e.g. stations, trains) to limit the amount of update time consumed when the list is refreshed.\n-1 means return all results.
[item-name]
cybersyn-combinator=Cybernetic combinator

View File

@@ -1,5 +1,8 @@
[mod-name]
cybersyn=Project Cybersyn
[controls]
cybersyn-toggle-gui=Toggle LTN Manager
cybersyn-toggle-gui=Toggle Cybersyn Manager
[cybersyn-gui]
alert-delivery-failed=Delivery failed
@@ -31,8 +34,10 @@ history=History
in-transit=In transit
inventory=Inventory
keep-open=Keep open
layout=Train Layout
loading-at=Loading at
name=Name
network-name-label=Network Filter:
network-id-label=Network ID:
network-id=Network ID
no-alerts=[img=warning-white] No alerts
@@ -41,8 +46,7 @@ no-history=[img=warning-white] No history
no-stations=[img=warning-white] No stations
not-available=Not available
no-trains=[img=warning-white] No trains
open-ltn-combinator-gui=[font=default-semibold][color=128,206,240]Control:[/color][/font] Open LTN Combinator GUI
open-station-gui=Open station GUI\n[font=default-semibold][color=128,206,240]Shift:[/color][/font] Open station on map
open-station-gui=Open station GUI\n[font=default-semibold][color=128,206,240]Shift:[/color][/font] Open station on map\n[font=default-semibold][color=128,206,240]Control:[/color][/font] Open Station Cybernetic Combinator\n[font=default-semibold][color=128,206,240]Alt:[/color][/font] Open Station Control Cybernetic Combinator
open-train-gui=Open train GUI
parked-at-depot=Parked at depot
parked-at-depot-with-residue=Parked at depot with residue
@@ -54,13 +58,14 @@ requested=Requested
returning-to-depot=Returning to depot
route=Route
runtime=Runtime
search-label=Search:
search-label=Station Name:
search-item-label=Item Filter:
shipments-description=Green = Inbound\nBlue = Outbound
shipment=Shipment
shipments=Shipments
stations=Stations
status-description=[img=flib_indicator_green]1 = normal status\n[img=flib_indicator_blue]n = LTN Controlled Train parked at stop, n = number of trains\n[img=flib_indicator_yellow]n = Stop is part of a scheduled delivery, n = number of trains\n[img=flib_indicator_white]1 = Error - not initialized\n[img=flib_indicator_red]1 = Error - short circuit\n[img=flib_indicator_red]2 = Error - deactivated stop
status=Status
status=Network Signal
surface-label=Surface:
time=Time
train-id=Train ID
@@ -69,9 +74,11 @@ type=Type
unloading-at=Unloading at
[cybersyn-message]
error-ltn-combinator-not-found=Could not find an LTN Combinator for this station.
error-cybernetic-combinator-not-found=Could not find a cybernetic combinator for this station.
error-station-control-combinator-not-found=Could not find a station control cybernetic combinator for this station.
error-station-is-invalid=Station is invalid, please refresh the GUI.
error-train-is-invalid=Train is invalid, please refresh the GUI.
error-cross-surface-camera-invalid=Cannot move the camera to an entity on a different surface!
[cybersyn-mod-setting-description]
iterations-per-tick=Decrease this number if you're having performance issues.
@@ -81,4 +88,4 @@ history-length=History length
iterations-per-tick=Iterations per tick [img=info]
[shortcut-name]
cybersyn-toggle-gui=Toggle LTN Manager
cybersyn-toggle-gui=Toggle Cybersyn Manager

View File

@@ -1,6 +1,38 @@
local constants = require("constants")
local util = require("prototypes.util")
local data_util = require("__flib__.data-util")
local util = {}
for key, value in pairs(require("__core__.lualib.util")) do
util[key] = value
end
util.paths = {
nav_icons = "__cybersyn__/graphics/gui/frame-action-icons.png",
shortcut_icons = "__cybersyn__/graphics/shortcut/ltn-manager-shortcut.png",
}
util.empty_checkmark = {
filename = data_util.empty_image,
priority = "very-low",
width = 1,
height = 1,
frame_count = 1,
scale = 8,
}
data:extend({
data_util.build_sprite("ltnm_pin_black", { 0, 32 }, util.paths.nav_icons, 32),
data_util.build_sprite("ltnm_pin_white", { 32, 32 }, util.paths.nav_icons, 32),
data_util.build_sprite("ltnm_refresh_black", { 0, 0 }, util.paths.nav_icons, 32),
data_util.build_sprite("ltnm_refresh_white", { 32, 0 }, util.paths.nav_icons, 32),
})
local styles = data.raw["gui-style"]["default"]
@@ -327,3 +359,36 @@ styles.ltnm_tabbed_pane = {
bottom_padding = 8,
},
}
if settings.startup["cybersyn-manager-enabled"].value then
data:extend({
-- custom inputs
{
type = "custom-input",
name = "cybersyn-toggle-gui",
key_sequence = "CONTROL + T",
action = "lua",
},
--{
-- type = "custom-input",
-- name = "ltnm-linked-focus-search",
-- key_sequence = "",
-- linked_game_control = "focus-search",
--},
-- shortcuts
{
type = "shortcut",
name = "cybersyn-toggle-gui",
icon = data_util.build_sprite(nil, { 0, 0 }, util.paths.shortcut_icons, 32, 2),
disabled_icon = data_util.build_sprite(nil, { 48, 0 }, util.paths.shortcut_icons, 32, 2),
small_icon = data_util.build_sprite(nil, { 0, 32 }, util.paths.shortcut_icons, 24, 2),
disabled_small_icon = data_util.build_sprite(nil, { 36, 32 }, util.paths.shortcut_icons, 24, 2),
toggleable = true,
action = "lua",
associated_control_input = "cybersyn-toggle-gui",
technology_to_unlock = "cybersyn-train-network",
},
})
end

View File

@@ -326,20 +326,13 @@ local function tick_dispatch(map_data, mod_settings)
end
local trains = map_data.available_trains[network_name]
local is_fluid = item_type == "fluid"
if not is_fluid and r_station.is_stack then
r_threshold = r_threshold*get_stack_size(map_data, item_name)
end
--no train exists with layout accepted by both provide and request stations
local correctness = 0
local closest_to_correct_p_station = nil
local slot_threshold
if is_fluid then
slot_threshold = r_threshold
elseif r_station.is_stack then
slot_threshold = r_threshold
r_threshold = r_threshold*get_stack_size(map_data, item_name)
else
slot_threshold = ceil(r_threshold/get_stack_size(map_data, item_name))
end
---@type uint?
local p_station_i = nil
local best_train_id = nil
@@ -349,7 +342,7 @@ local function tick_dispatch(map_data, mod_settings)
---@type uint
local j = 1
while j <= #p_stations do
local p_flag, r_flag, netand, best_p_train_id, best_t_prior, best_capacity, best_t_to_p_dist, effective_count, override_threshold, p_prior, best_p_to_r_dist
local p_flag, r_flag, netand, best_p_train_id, best_t_prior, best_capacity, best_t_to_p_dist, effective_count, override_threshold, p_prior, best_p_to_r_dist, effective_threshold, slot_threshold
local p_station_id = p_stations[j]
local p_station = stations[p_station_id]
@@ -369,7 +362,13 @@ local function tick_dispatch(map_data, mod_settings)
if override_threshold and p_station.is_stack and not is_fluid then
override_threshold = override_threshold*get_stack_size(map_data, item_name)
end
if effective_count < (override_threshold or r_threshold) then
if override_threshold and override_threshold <= r_threshold then
effective_threshold = override_threshold
else
effective_threshold = r_threshold
end
if effective_count < effective_threshold then
--this p station should have serviced the current r station, lock it so it can't serve any others
--this will lock stations even when the r station manages to find a p station, this not a problem because all stations will be unlocked before it could be an issue
table_remove(p_stations, j)
@@ -392,6 +391,13 @@ local function tick_dispatch(map_data, mod_settings)
if p_prior == best_p_prior and best_p_to_r_dist > best_dist then
goto p_continue
end
if is_fluid then
slot_threshold = effective_threshold
else
slot_threshold = ceil(effective_threshold/get_stack_size(map_data, item_name))
end
if correctness < 1 then
correctness = 1
closest_to_correct_p_station = p_station
@@ -682,38 +688,49 @@ end
function tick_poll_entities(map_data, mod_settings)
local tick_data = map_data.tick_data
--NOTE: the following have undefined behaviour if the item on tick_data is deleted
if map_data.total_ticks%5 == 0 then
local train_id, train = next(map_data.trains, tick_data.last_train)
tick_data.last_train = train_id
if train then
if train.manifest and not train.se_is_being_teleported and train.last_manifest_tick + mod_settings.stuck_train_time*mod_settings.tps < map_data.total_ticks then
if mod_settings.stuck_train_alert_enabled then
send_alert_stuck_train(map_data, train.entity)
if tick_data.last_train == nil or map_data.trains[tick_data.last_train] then
local train_id, train = next(map_data.trains, tick_data.last_train)
tick_data.last_train = train_id
if train then
if train.manifest and not train.se_is_being_teleported and train.last_manifest_tick + mod_settings.stuck_train_time*mod_settings.tps < map_data.total_ticks then
if mod_settings.stuck_train_alert_enabled then
send_alert_stuck_train(map_data, train.entity)
end
interface_raise_train_stuck(train_id)
end
interface_raise_train_stuck(train_id)
end
else
tick_data.last_train = nil
end
local refueler_id, _ = next(map_data.each_refuelers, tick_data.last_refueler)
tick_data.last_refueler = refueler_id
if refueler_id then
local refueler = map_data.refuelers[refueler_id]
if refueler.entity_stop.valid and refueler.entity_comb.valid then
set_refueler_from_comb(map_data, mod_settings, refueler_id, refueler)
else
on_refueler_broken(map_data, refueler_id, refueler)
if tick_data.last_refueler == nil or map_data.each_refuelers[tick_data.last_refueler] then
local refueler_id, _ = next(map_data.each_refuelers, tick_data.last_refueler)
tick_data.last_refueler = refueler_id
if refueler_id then
local refueler = map_data.refuelers[refueler_id]
if refueler.entity_stop.valid and refueler.entity_comb.valid then
set_refueler_from_comb(map_data, mod_settings, refueler_id, refueler)
else
on_refueler_broken(map_data, refueler_id, refueler)
end
end
else
tick_data.last_refueler = nil
end
else
local comb_id, comb = next(map_data.to_comb, tick_data.last_comb)
tick_data.last_comb = comb_id
if comb then
if comb.valid then
combinator_update(map_data, comb, true)
else
map_data.to_comb[comb_id] = nil
if tick_data.last_comb == nil or map_data.to_comb[tick_data.last_comb] then
local comb_id, comb = next(map_data.to_comb, tick_data.last_comb)
tick_data.last_comb = comb_id
if comb then
if comb.valid then
combinator_update(map_data, comb, true)
else
map_data.to_comb[comb_id] = nil
end
end
else
tick_data.last_comb = nil
end
end
end

View File

@@ -30,6 +30,8 @@ SETTING_DISABLE_DEPOT_BYPASS = 6
SETTING_ENABLE_SLOT_BARRING = 7
NETWORK_SIGNAL_DEFAULT = {name="signal-A", type="virtual"}
NETWORK_SIGNAL_GUI_DEFAULT = {name="signal-each", type="virtual"}
NETWORK_ANYTHING = "signal-anything"
NETWORK_EACH = "signal-each"
INACTIVITY_TIME = 100
LOCK_TRAIN_TIME = 60*60*60*24*7

View File

@@ -390,15 +390,20 @@ function set_station_from_comb(station)
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 = {}
local new_name = signal and signal.name or nil
if station.network_name ~= new_name then
station.network_name = new_name
if station.network_name == NETWORK_EACH then
station.network_flag = {}
else
station.network_flag = 0
end
end
end
---NOTE: does not check .valid

View File

@@ -86,7 +86,7 @@
---@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 network_flag int|{[string]: int} --transient
---@field public priority int
---@field public refueler_id uint?
---@field public se_is_being_teleported true? --se only
@@ -124,6 +124,8 @@
---@field public stuck_train_alert_enabled 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
---@field public enable_manager boolean
---@field public manager_update_rate int
--if this is uncommented it means there are migrations to write

View File

@@ -183,12 +183,12 @@ end
function register_gui_actions()
flib_gui.add_handlers({
["close"] = handle_close,
["drop_down"] = handle_drop_down,
["pr_switch"] = handle_pr_switch,
["network"] = handle_network,
["setting"] = handle_setting,
["setting_flip"] = handle_setting_flip,
["comb_close"] = handle_close,
["comb_drop_down"] = handle_drop_down,
["comb_pr_switch"] = handle_pr_switch,
["comb_network"] = handle_network,
["comb_setting"] = handle_setting,
["comb_setting_flip"] = handle_setting_flip,
})
flib_gui.handle_events()
script.on_event(defines.events.on_gui_opened, on_gui_opened)

View File

@@ -12,7 +12,7 @@ function alerts_tab.build(widths)
return {
tab = {
type = "tab",
caption = { "gui.ltnm-alerts" },
caption = { "cybersyn-gui.alerts" },
ref = { "alerts", "tab" },
actions = {
on_click = { gui = "main", action = "change_tab", tab = "alerts" },
@@ -36,7 +36,7 @@ function alerts_tab.build(widths)
type = "sprite-button",
style = "tool_button_red",
sprite = "utility/trash",
tooltip = { "gui.ltnm-delete-all-alerts" },
tooltip = { "cybersyn-gui.delete-all-alerts" },
ref = { "alerts", "delete_all_button" },
actions = {
on_click = { gui = "main", action = "delete_all_alerts" },
@@ -52,7 +52,7 @@ function alerts_tab.build(widths)
{
type = "label",
style = "ltnm_semibold_label",
caption = { "gui.ltnm-no-alerts" },
caption = { "cybersyn-gui.no-alerts" },
ref = { "alerts", "warning_label" },
},
},
@@ -121,7 +121,7 @@ function alerts_tab.update(self)
type = "label",
style = "ltnm_clickable_semibold_label",
style_mods = { width = widths.alerts.train_id, horizontal_align = "center" },
tooltip = { "gui.ltnm-open-train-gui" },
tooltip = { "cybersyn-gui.open-train-gui" },
},
{
type = "flow",
@@ -153,7 +153,7 @@ function alerts_tab.update(self)
type = "sprite-button",
style = "tool_button_red",
sprite = "utility/trash",
tooltip = { "gui.ltnm-delete-alert" },
tooltip = { "cybersyn-gui.delete-alert" },
},
})
end
@@ -185,8 +185,8 @@ function alerts_tab.update(self)
{ elem_mods = { caption = util.signed_int32(alerts_entry.train.network_id) } },
{
elem_mods = {
caption = { "gui.ltnm-alert-" .. string.gsub(alerts_entry.type, "_", "-") },
tooltip = { "gui.ltnm-alert-" .. string.gsub(alerts_entry.type, "_", "-") .. "-description" },
caption = { "cybersyn-gui.alert-" .. string.gsub(alerts_entry.type, "_", "-") },
tooltip = { "cybersyn-gui.alert-" .. string.gsub(alerts_entry.type, "_", "-") .. "-description" },
},
},
{},

View File

@@ -37,7 +37,7 @@ constants.gui = {
trains = {
train_id = 90,
status = 378,
composition = 200,
layout = 200,
depot = 149,
shipment = 36 * 6,
shipment_columns = 6,
@@ -85,14 +85,14 @@ constants.gui_content_frame_height = 744
constants.gui_inventory_table_height = 40 * 18
constants.gui_translations = {
delivering_to = { "gui.ltnm-delivering-to" },
fetching_from = { "gui.ltnm-fetching-from" },
loading_at = { "gui.ltnm-loading-at" },
not_available = { "gui.ltnm-not-available" },
parked_at_depot_with_residue = { "gui.ltnm-parked-at-depot-with-residue" },
parked_at_depot = { "gui.ltnm-parked-at-depot" },
returning_to_depot = { "gui.ltnm-returning-to-depot" },
unloading_at = { "gui.ltnm-unloading-at" },
delivering_to = { "cybersyn-gui.delivering-to" },
fetching_from = { "cybersyn-gui.fetching-from" },
loading_at = { "cybersyn-gui.loading-at" },
not_available = { "cybersyn-gui.not-available" },
parked_at_depot_with_residue = { "cybersyn-gui.parked-at-depot-with-residue" },
parked_at_depot = { "cybersyn-gui.parked-at-depot" },
returning_to_depot = { "cybersyn-gui.returning-to-depot" },
unloading_at = { "cybersyn-gui.unloading-at" },
}
constants.input_sanitizers = {
@@ -144,8 +144,7 @@ constants.ltn_event_names = {
if script then
constants.open_station_gui_tooltip = {
"",
{ "gui.ltnm-open-station-gui" },
remote.interfaces["ltn-combinator"] and { "", "\n", { "gui.ltnm-open-ltn-combinator-gui" } } or nil,
{ "cybersyn-gui.open-station-gui" },
}
end

View File

@@ -8,7 +8,7 @@ function depots_tab.build(widths)
return {
tab = {
type = "tab",
caption = { "gui.ltnm-depots" },
caption = { "cybersyn-gui.depots" },
ref = { "depots", "tab" },
actions = {
on_click = { gui = "main", action = "change_tab", tab = "depots" },
@@ -25,7 +25,7 @@ function depots_tab.build(widths)
style_mods = { right_padding = 4 },
templates.sort_checkbox(widths, "depots", "name", true, nil, true),
templates.sort_checkbox(widths, "depots", "network_id", false),
templates.sort_checkbox(widths, "depots", "status", false, { "gui.ltnm-status-description" }),
templates.sort_checkbox(widths, "depots", "status", false, { "cybersyn-gui.status-description" }),
templates.sort_checkbox(widths, "depots", "trains", false),
},
{ type = "scroll-pane", style = "ltnm_table_scroll_pane", ref = { "depots", "scroll_pane" } },
@@ -37,7 +37,7 @@ function depots_tab.build(widths)
{
type = "label",
style = "ltnm_semibold_label",
caption = { "gui.ltnm-no-depots" },
caption = { "cybersyn-gui.no-depots" },
ref = { "depots", "warning_label" },
},
},

View File

@@ -12,7 +12,7 @@ function history_tab.build(widths)
return {
tab = {
type = "tab",
caption = { "gui.ltnm-history" },
caption = { "cybersyn-gui.history" },
ref = { "history", "tab" },
actions = {
on_click = { gui = "main", action = "change_tab", tab = "history" },
@@ -38,7 +38,7 @@ function history_tab.build(widths)
type = "sprite-button",
style = "tool_button_red",
sprite = "utility/trash",
tooltip = { "gui.ltnm-clear-history" },
tooltip = { "cybersyn-gui.clear-history" },
ref = { "history", "clear_button" },
actions = {
on_click = { gui = "main", action = "clear_history" },
@@ -54,7 +54,7 @@ function history_tab.build(widths)
{
type = "label",
style = "ltnm_semibold_label",
caption = { "gui.ltnm-no-history" },
caption = { "cybersyn-gui.no-history" },
ref = { "history", "warning_label" },
},
},

View File

@@ -1,20 +1,22 @@
local misc = require("__flib__.misc")
local gui = require("__flib__.gui-lite")
local templates = require("templates")
local util = require("scripts.gui.util")
local templates = require("scripts.gui.templates")
local format = require("__flib__.format")
local inventory_tab = {}
function inventory_tab.build()
function inventory_tab.create()
return {
tab = {
name = "manager_inventory_tab",
type = "tab",
caption = { "gui.ltnm-inventory" },
caption = { "cybersyn-gui.inventory" },
ref = { "inventory", "tab" },
actions = {
on_click = { gui = "main", action = "change_tab", tab = "inventory" },
},
handler = inventory_tab.handle.on_inventory_tab_selected
},
content = {
name = "manager_inventory_content_frame",
type = "flow",
style_mods = { horizontal_spacing = 12 },
direction = "horizontal",
@@ -26,62 +28,245 @@ function inventory_tab.build()
}
end
local function update_table(self, name, color)
local translations = self.player_table.dictionaries.materials
---@param map_data MapData
---@param player_data PlayerData
function inventory_tab.build(map_data, player_data)
local state = self.state
local refs = self.refs.inventory
local refs = player_data.refs
local search_query = state.search_query
local search_network_id = state.network_id
local search_surface = state.surface
local search_query = player_data.search_query
local search_item = player_data.search_item
local search_network_name = player_data.search_network_name
local search_network_mask = player_data.search_network_mask
local search_surface_idx = player_data.search_surface_idx
local inventory_provided = {}
local inventory_in_transit = {}
local inventory_requested = {}
local ltn_inventory = state.ltn_data.inventory[name][search_surface]
local stations_sorted = {}
local i = 0
local table = refs[name].table
local children = table.children
for id, station in pairs(map_data.stations) do
local entity = station.entity_stop
if not entity.valid then
goto continue
end
for name, count_by_network_id in pairs(ltn_inventory or {}) do
if
bit32.btest(count_by_network_id.combined_id, search_network_id)
and string.find(string.lower(translations[name]), string.lower(search_query))
then
local running_count = 0
for network_id, count in pairs(count_by_network_id) do
if network_id ~= "combined_id" and bit32.btest(network_id, search_network_id) then
running_count = running_count + count
if search_query then
if not string.match(entity.backer_name, search_query) then
goto continue
end
end
-- move surface comparison up higher in query to short circuit query earlier if surface doesn't match
if search_surface_idx then
if search_surface_idx == -1 then
goto has_match
elseif entity.surface.index ~= search_surface_idx then
goto continue
end
::has_match::
end
if search_network_name then
if search_network_name == (NETWORK_EACH or NETWORK_ANYTHING) then
goto has_match
end
if search_network_name ~= station.network_name then
goto continue
end
::has_match::
local train_flag = get_network_flag(station, station.network_name)
if not bit32.btest(search_network_mask, train_flag) then
goto continue
end
elseif search_network_mask ~= -1 then
if station.network_name == NETWORK_EACH then
local masks = station.network_flag--[[@as {}]]
for _, network_flag in pairs(masks) do
if bit32.btest(search_network_mask, network_flag) then
goto has_match
end
end
goto continue
::has_match::
elseif not bit32.btest(search_network_mask, station.network_flag) then
goto continue
end
end
if search_item then
if station.deliveries then
for item_name, _ in pairs(station.deliveries) do
if item_name == search_item then
goto has_match
end
end
end
local comb1_signals, _ = get_signals(station)
if comb1_signals then
for _, signal_ID in pairs(comb1_signals) do
local item = signal_ID.signal.name
if item then
if item == search_item then
goto has_match
end
end
end
end
goto continue
::has_match::
end
stations_sorted[#stations_sorted + 1] = id
::continue::
end
for i, station_id in pairs(stations_sorted) do
--- @class Station
local station = map_data.stations[station_id]
local comb1_signals, _ = get_signals(station)
if comb1_signals then
for _, v in pairs(comb1_signals) do
local item = v.signal
local count = v.count
if item.type ~= "virtual" then
if count > 0 then
if inventory_provided[item.name] == nil then
inventory_provided[item.name] = count
else
inventory_provided[item.name] = inventory_provided[item.name] + count
end
else
if inventory_requested[item.name] == nil then
inventory_requested[item.name] = count
else
inventory_requested[item.name] = inventory_requested[item.name] + count
end
end
end
end
end
if running_count > 0 then
i = i + 1
local button = children[i]
if not button then
button = table.add({ type = "sprite-button", style = "flib_slot_button_" .. color, enabled = false })
local deliveries = station.deliveries
if deliveries then
for item, count in pairs(deliveries) do
if count > 0 then
if inventory_in_transit[item] == nil then
inventory_in_transit[item] = 0
inventory_in_transit[item] = inventory_in_transit[item] + count
else
inventory_in_transit[item] = inventory_in_transit[item] + count
end
end
button.sprite = string.gsub(name, ",", "/")
button.number = running_count
button.tooltip = "[img="
.. string.gsub(name, ",", "/")
.. "] [font=default-semibold]"
.. translations[name]
.. "[/font]\n"
.. misc.delineate_number(running_count)
end
end
end
for j = i + 1, #children do
children[j].destroy()
local inventory_provided_table = refs.inventory_provided_table
local provided_children = {}
local i = 0
for item, count in pairs(inventory_provided) do
i = i + 1
local sprite, img_path, item_string = util.generate_item_references(item)
if game.is_valid_sprite_path(sprite) then
provided_children[#provided_children+1] = {
type = "sprite-button",
style = "flib_slot_button_green",
enabled = false,
sprite = sprite,
number = count,
tooltip = { "",
img_path,
" [font=default-semibold]",
{ item_string },
"[/font]\n"..format.number(count),
},
}
end
end
local inventory_requested_table = refs.inventory_requested_table
local requested_children = {}
local i = 0
for item, count in pairs(inventory_requested) do
i = i + 1
local sprite, img_path, item_string = util.generate_item_references(item)
if game.is_valid_sprite_path(sprite) then
requested_children[#requested_children+1] = {
type = "sprite-button",
style = "flib_slot_button_red",
enabled = false,
sprite = sprite,
number = count,
tooltip = { "",
img_path,
" [font=default-semibold]",
{ item_string },
"[/font]\n"..format.number(count),
},
}
end
end
local inventory_in_transit_table = refs.inventory_in_transit_table
local in_transit_children = {}
local i = 0
for item, count in pairs(inventory_in_transit) do
i = i + 1
local sprite, img_path, item_string = util.generate_item_references(item)
if game.is_valid_sprite_path(sprite) then
in_transit_children[#in_transit_children+1] = {
type = "sprite-button",
style = "flib_slot_button_blue",
enabled = false,
sprite = sprite,
number = count,
tooltip = { "",
img_path,
" [font=default-semibold]",
{ item_string },
"[/font]\n"..format.number(count),
},
}
end
end
if next(inventory_provided_table.children) ~= nil then
refs.inventory_provided_table.clear()
end
if next(inventory_requested_table.children) ~= nil then
refs.inventory_requested_table.clear()
end
if next(inventory_in_transit_table.children) ~= nil then
refs.inventory_in_transit_table.clear()
end
gui.add(refs.inventory_provided_table, provided_children)
gui.add(refs.inventory_requested_table, requested_children)
gui.add(refs.inventory_in_transit_table, in_transit_children)
end
function inventory_tab.update(self)
update_table(self, "provided", "green")
update_table(self, "in_transit", "blue")
update_table(self, "requested", "red")
inventory_tab.handle = {}
--- @param e {player_index: uint}
function inventory_tab.wrapper(e, handler)
local player = game.get_player(e.player_index)
if not player then return end
local player_data = global.manager.players[e.player_index]
handler(player, player_data, player_data.refs, e)
end
---@param player LuaPlayer
---@param player_data PlayerData
function inventory_tab.handle.on_inventory_tab_selected(player, player_data)
player_data.selected_tab = "inventory_tab"
end
gui.add_handlers(inventory_tab.handle, inventory_tab.wrapper)
return inventory_tab

View File

@@ -4,9 +4,11 @@ local mod_gui = require("__core__.lualib.mod-gui")
local manager = require("scripts.gui.manager")
--- @class Manager
--- @field players table<uint, PlayerData>
--- @field item_order table<string, int>
--- @class PlayerData
--- @field is_manager_open boolean
--- @field refs {[string]: LuaGuiElement}?
--- @field search_query string?
--- @field search_network_name string?
@@ -16,6 +18,7 @@ local manager = require("scripts.gui.manager")
--- @field trains_orderings uint[]
--- @field trains_orderings_invert boolean[]
--- @field pinning boolean
--- @field selected_tab string?
@@ -33,8 +36,8 @@ local function top_left_button_update(player, player_data)
name = "top_left_button",
style = "mis_mod_gui_button_green",
sprite = "mis_configure_white",
tooltip = { "", "\n", { "mis-config-gui.configure-tooltip" } },
handler = manager.handle.toggle,
tooltip = { "", "\n", { "cybersyn.gui.configure-tooltip" } },
handler = manager.handle.manager_toggle,
})
end
end
@@ -44,8 +47,8 @@ end
local manager_gui = {}
function manager_gui.on_lua_shortcut(e)
if e.prototype_name == "ltnm-toggle-gui" then
manager.wrapper(e, manager.handle.toggle)
if e.prototype_name == "cybersyn-toggle-gui" or e.input_name == "cybersyn-toggle-gui" then
manager.wrapper(e, manager.handle.manager_toggle)
end
end
@@ -61,14 +64,14 @@ function manager_gui.on_player_created(e)
pinning = false,
refs = manager.create(player),
}
global.manager_data.players[e.player_index] = player_data
global.manager.players[e.player_index] = player_data
manager.update(global, player, player_data)
top_left_button_update(player, player_data)
--manager.update(global, player, player_data)
--top_left_button_update(player, player_data)
end
function manager_gui.on_player_removed(e)
global.manager_data.players[e.player_index] = nil
global.manager.players[e.player_index] = nil
end
--script.on_event(defines.events.on_player_joined_game, function(e)
@@ -83,12 +86,27 @@ function manager_gui.on_runtime_mod_setting_changed(e)
local player = game.get_player(e.player_index)
if not player then return end
local player_data = global.manager_data.players[e.player_index]
local player_data = global.manager.players[e.player_index]
player_data.disable_top_left_button = player.mod_settings["cybersyn-disable-top-left-button"].value
top_left_button_update(player, player_data)
end
end
commands.add_command("cybersyn_rebuild_manager_windows", nil, function(command)
local manager_data = global.manager
if manager_data then
---@param v PlayerData
for i, v in pairs(manager_data.players) do
local player = game.get_player(i)
if player ~= nil then
v.refs.manager_window.destroy()
v.refs = manager.create(player)
end
end
end
end)
--- @param manager Manager
local function init_items(manager)
@@ -121,14 +139,29 @@ local function init_items(manager)
end
function manager.on_migration()
function manager_gui.on_migration()
init_items(global.manager)
end
function manager.on_init()
global.manager = {}
function manager_gui.on_init()
global.manager = {
players = {},
}
init_items(global.manager)
end
--gui.handle_events()
---@param global cybersyn.global
function manager_gui.tick(global)
local manager_data = global.manager
if manager_data then
for i, v in pairs(manager_data.players) do
if v.is_manager_open then
local query_limit = settings.get_player_settings(i)["cybersyn-manager-result-limit"].value
manager.update(global, v, query_limit)
end
end
end
end
return manager_gui

View File

@@ -5,12 +5,13 @@ local constants = require("scripts.gui.constants")
--local actions = require("scripts.gui.actions")
local templates = require("scripts.gui.templates")
local stations_tab = require("scripts.gui.stations")
local trains_tab = require("scripts.gui.trains")
--local depots_tab = require("scripts.gui.depots")
--local stations_tab = require("scripts.gui.stations")
--local inventory_tab = require("scripts.gui.inventory")
local inventory_tab = require("scripts.gui.inventory")
--local history_tab = require("scripts.gui.history")
--local alerts_tab = require("scripts.gui.alerts")
local util = require("scripts.gui.util")
local manager = {}
@@ -28,28 +29,28 @@ function manager.create(player)
type = "frame",
direction = "vertical",
visible = false,
handler = manager.handle.close,
--handler = manager.handle.manager_close,
children = {
{
name = "manager_titlebar",
type = "flow",
style = "flib_titlebar_flow",
handler = manager.handle.titlebar_click,
handler = manager.handle.manager_titlebar_click,
children = {
{ type = "label", style = "frame_title", caption = { "mod-name.LtnManager" }, ignored_by_interaction = true },
{ type = "label", style = "frame_title", caption = { "mod-name.cybersyn" }, ignored_by_interaction = true },
{ type = "empty-widget", style = "flib_titlebar_drag_handle", ignored_by_interaction = true },
{
name = "manager_dispatcher_status_label",
type = "label",
style = "bold_label",
style_mods = { font_color = constants.colors.red.tbl, left_margin = -4, top_margin = 1 },
caption = { "gui.ltnm-dispatcher-disabled" },
tooltip = { "gui.ltnm-dispatcher-disabled-description" },
caption = { "cybersyn-gui.dispatcher-disabled" },
tooltip = { "cybersyn-gui.dispatcher-disabled-description" },
visible = not settings.global["cybersyn-enable-planner"].value,
},
templates.frame_action_button("manager_pin_button", "ltnm_pin", { "gui.ltnm-keep-open" }, manager.handle.pin),--on_gui_clicked
templates.frame_action_button("manager_refresh_button", "ltnm_refresh", { "gui.ltnm-refresh-tooltip" }, manager.handle.refresh_click),--on_gui_clicked
templates.frame_action_button(nil, "utility/close", { "gui.close-instruction" }, manager.handle.close),--on_gui_clicked
--templates.frame_action_button("manager_pin_button", "ltnm_pin", { "cybersyn-gui.keep-open" }, manager.handle.manager_pin),--on_gui_clicked
--templates.frame_action_button("manager_refresh_button", "ltnm_refresh", { "cybersyn-gui.refresh-tooltip" }, manager.handle.manager_refresh_click),--on_gui_clicked
templates.frame_action_button(nil, "utility/close", { "gui.close-instruction" }, manager.handle.manager_close),--on_gui_clicked
},
},
{
@@ -61,15 +62,19 @@ function manager.create(player)
type = "frame",
style = "ltnm_main_toolbar_frame",
children = {
{ type = "label", style = "subheader_caption_label", caption = { "gui.ltnm-search-label" } },
{ type = "label", style = "subheader_caption_label", caption = { "cybersyn-gui.search-label" } },
{
name = "manager_text_search_field",
type = "textfield",
clear_and_focus_on_right_click = true,
handler = manager.handle.update_text_search, --on_gui_text_changed
handler = manager.handle.manager_update_text_search, --on_gui_text_changed
},
{ type = "label", style = "subheader_caption_label", caption = { "cybersyn-gui.search-item-label" } },
{ type= "choose-elem-button", name="manager_item_filter", style="slot_button_in_shallow_frame", elem_type="signal", handler=manager.handle.manager_update_item_search, },
{ type = "empty-widget", style = "flib_horizontal_pusher" },
{ type = "label", style = "caption_label", caption = { "gui.ltnm-network-id-label" } },
{ type = "label", style = "caption_label", caption = { "cybersyn-gui.network-name-label" } },
{ type= "choose-elem-button", name="network", style="slot_button_in_shallow_frame", elem_type="signal", tooltip={"cybersyn-gui.network-tooltip"}, handler=manager.handle.manager_update_network_name, },
{ type = "label", style = "caption_label", caption = { "cybersyn-gui.network-id-label" } },
{
name = "manager_network_mask_field",
type = "textfield",
@@ -78,13 +83,13 @@ function manager.create(player)
allow_negative = true,
clear_and_focus_on_right_click = true,
text = "-1",
handler = manager.handle.update_network_mask, --on_gui_text_changed
handler = manager.handle.manager_update_network_mask, --on_gui_text_changed
},
{ type = "label", style = "caption_label", caption = { "gui.ltnm-surface-label" } },
{ type = "label", style = "caption_label", caption = { "cybersyn-gui.surface-label" } },
{
name = "manager_surface_dropdown",
type = "drop-down",
handler = manager.handle.update_surface, --on_gui_selection_state_changed
handler = manager.handle.manager_update_surface, --on_gui_selection_state_changed
},
},
},
@@ -92,6 +97,10 @@ function manager.create(player)
name = "manager_tabbed_pane",
type = "tabbed-pane",
style = "ltnm_tabbed_pane",
trains_tab.create(widths),
stations_tab.create(widths),
inventory_tab.create(),
selected_tab_index = 1,
},
},
},
@@ -107,12 +116,55 @@ function manager.create(player)
return refs
end
function manager.build(player_data)
local refs = player_data.refs
-- Surface dropdown
--- @type LuaGuiElement
local surface_dropdown = refs.manager_surface_dropdown
local surfaces = game.surfaces
local selected_surface_id = player_data.search_surface_idx
local currently_selected_index = surface_dropdown.selected_index
local currently_selected_surface = nil
if currently_selected_index ~= (nil or 0) then
currently_selected_surface = surface_dropdown.get_item(currently_selected_index)
end
surface_dropdown.clear_items()
surface_dropdown.add_item("all", 1)
i = 1
for name, _ in pairs(surfaces) do
i = i + 1
surface_dropdown.add_item(name, i)
--reselect same surface
if name == currently_selected_surface then
refs.manager_surface_dropdown.selected_index = i
end
end
-- Validate that the selected index still exist
if selected_surface_id then
local selected_surface = game.get_surface(selected_surface_id)
-- If the surface was invalidated since last update, reset to all
if not selected_surface then
player_data.search_surface_idx = -1
end
else
player_data.search_surface_idx = -1
end
end
--- @param map_data MapData
--- @param player LuaPlayer
--- @param player_data PlayerData
function manager.update(map_data, player, player_data)
local tab = trains_tab.build(map_data, player_data)
gui.add(_, tab, player_data.refs)
function manager.update(map_data, player_data, query_limit)
if player_data.selected_tab ~= nil then
manager.build(player_data)
end
if player_data.selected_tab == "stations_tab" then
stations_tab.build(map_data, player_data, query_limit)
elseif player_data.selected_tab == "inventory_tab" then
inventory_tab.build(map_data, player_data)
elseif player_data.selected_tab == "trains_tab" then
trains_tab.build(map_data, player_data, query_limit)
end
end
@@ -123,7 +175,7 @@ manager.handle = {}
function manager.wrapper(e, handler)
local player = game.get_player(e.player_index)
if not player then return end
local player_data = global.manager_data.players[e.player_index]
local player_data = global.manager.players[e.player_index]
handler(player, player_data, player_data.refs, e)
end
@@ -143,7 +195,7 @@ end
--- @param player LuaPlayer
--- @param player_data PlayerData
--- @param refs table<string, LuaGuiElement>
function manager.handle.open(player, player_data, refs)
function manager.handle.manager_open(player, player_data, refs)
refs.manager_window.bring_to_front()
refs.manager_window.visible = true
player_data.visible = true
@@ -152,46 +204,40 @@ function manager.handle.open(player, player_data, refs)
player.opened = refs.manager_window
end
player.set_shortcut_toggled("ltnm-toggle-gui", true)
player_data.is_manager_open = true
player.set_shortcut_toggled("cybersyn-toggle-gui", true)
end
--- @param player LuaPlayer
--- @param player_data PlayerData
--- @param refs table<string, LuaGuiElement>
function manager.handle.close(player, player_data, refs)
if player_data.pinning then
return
function manager.handle.manager_close(player, player_data, refs)
util.close_manager_window(player, player_data, refs)
end
--- @param player LuaPlayer
--- @param player_data PlayerData
--- @param refs table<string, LuaGuiElement>
function manager.handle.manager_toggle(player, player_data, refs)
if player_data.is_manager_open then
manager.handle.manager_close(player, player_data, refs)
else
manager.handle.manager_open(player, player_data, refs)
end
refs.manager_window.visible = false
player_data.visible = false
if player.opened == refs.manager_window then
player.opened = nil
end
player.set_shortcut_toggled("ltnm-toggle-gui", false)
end
--- @param player LuaPlayer
--- @param player_data PlayerData
--- @param refs table<string, LuaGuiElement>
function manager.handle.toggle(player, player_data, refs)
end
--- @param player LuaPlayer
--- @param player_data PlayerData
--- @param refs table<string, LuaGuiElement>
function manager.handle.recenter(player, player_data, refs)
function manager.handle.manager_recenter(player, player_data, refs)
refs.window.force_auto_center()
end
--- @param player LuaPlayer
--- @param player_data PlayerData
--- @param refs table<string, LuaGuiElement>
function manager.handle.toggle_auto_refresh(player, player_data, refs)
function manager.handle.manager_toggle_auto_refresh(player, player_data, refs)
player_data.auto_refresh = not player_data.auto_refresh
toggle_fab(refs.manager_refresh_button, "ltnm_refresh", player_data.auto_refresh)
end
@@ -199,7 +245,7 @@ end
--- @param player LuaPlayer
--- @param player_data PlayerData
--- @param refs table<string, LuaGuiElement>
function manager.handle.toggle_pinned(player, player_data, refs)
function manager.handle.manager_toggle_pinned(player, player_data, refs)
player_data.pinned = not player_data.pinned
toggle_fab(refs.manager_pin_button, "ltnm_pin", player_data.pinned)
end
@@ -208,21 +254,38 @@ end
--- @param player_data PlayerData
--- @param refs table<string, LuaGuiElement>
--- @param e GuiEventData
function manager.handle.update_text_search(player, player_data, refs, e)
function manager.handle.manager_update_text_search(player, player_data, refs, e)
local query = e.text
-- Input sanitization
for pattern, replacement in pairs(constants.input_sanitizers) do
query = string.gsub(query, pattern, replacement)
if query then
-- Input sanitization
for pattern, replacement in pairs(constants.input_sanitizers) do
query = string.gsub(query, pattern, replacement)
end
end
player_data.search_query = query
end
--- @param player LuaPlayer
--- @param player_data PlayerData
--- @param refs table<string, LuaGuiElement>
--- @param e GuiEventData
function manager.handle.manager_update_item_search(player, player_data, refs, e)
local element = e.element
local signal = element.elem_value
if signal then
player_data.search_item = signal.name
else
player_data.search_item = nil
end
end
--- @param player LuaPlayer
--- @param player_data PlayerData
--- @param refs table<string, LuaGuiElement>
function manager.handle.update_network_name(player, player_data, refs)
local signal = refs.manager_network_name.elem_value
function manager.handle.manager_update_network_name(player, player_data, refs, e)
local element = e.element
local signal = element.elem_value
if signal then
player_data.search_network_name = signal.name
else
@@ -232,18 +295,30 @@ end
--- @param player LuaPlayer
--- @param player_data PlayerData
--- @param refs table<string, LuaGuiElement>
function manager.handle.update_network_mask(player, player_data, refs)
player_data.search_network_mask = tonumber(refs.manager_network_mask_field.text) or -1
function manager.handle.manager_update_network_mask(player, player_data, refs, e)
player_data.search_network_mask = tonumber(e.text) or -1
end
--- @param player LuaPlayer
--- @param player_data PlayerData
--- @param refs table<string, LuaGuiElement>
function manager.handle.update_surface(player, player_data, refs)
local i = refs.manager_surface_dropdown.selected_index
player_data.search_surface_idx = i--TODO: fix this
function manager.handle.manager_update_surface(player, player_data, refs, e)
--- @type LuaGuiElement
local element = e.element
local i = element.selected_index
local refs = player_data.refs
local surface_id = -1
--all surfaces should always be the first entry with an index of 1
if i > 1 then
local surface_name = refs.manager_surface_dropdown.get_item(i)
local surface = game.get_surface(surface_name)
surface_id = surface.index
end
player_data.search_surface_idx = surface_id
end
gui.add_handlers(manager.handle, manager.wrapper)
return manager

View File

@@ -1,317 +1,340 @@
local gui = require("__flib__.gui")
local gui = require("__flib__.gui-lite")
local constants = require("constants")
local util = require("scripts.util")
local templates = require("templates")
local constants = require("scripts.gui.constants")
local util = require("scripts.gui.util")
local templates = require("scripts.gui.templates")
local stations_tab = {}
function stations_tab.build(widths)
return {
tab = {
type = "tab",
caption = { "gui.ltnm-stations" },
ref = { "stations", "tab" },
actions = {
on_click = { gui = "main", action = "change_tab", tab = "stations" },
},
},
content = {
type = "frame",
style = "ltnm_main_content_frame",
direction = "vertical",
ref = { "stations", "content_frame" },
{
type = "frame",
style = "ltnm_table_toolbar_frame",
templates.sort_checkbox(widths, "stations", "name", true),
templates.sort_checkbox(widths, "stations", "status", false, { "gui.ltnm-status-description" }),
templates.sort_checkbox(widths, "stations", "network_id", false),
templates.sort_checkbox(
widths,
"stations",
"provided_requested",
false,
{ "gui.ltnm-provided-requested-description" }
),
templates.sort_checkbox(widths, "stations", "shipments", false, { "gui.ltnm-shipments-description" }),
templates.sort_checkbox(widths, "stations", "control_signals", false),
},
{ type = "scroll-pane", style = "ltnm_table_scroll_pane", ref = { "stations", "scroll_pane" } },
{
type = "flow",
style = "ltnm_warning_flow",
visible = false,
ref = { "stations", "warning_flow" },
{
type = "label",
style = "ltnm_semibold_label",
caption = { "gui.ltnm-no-stations" },
ref = { "stations", "warning_label" },
},
},
},
function stations_tab.create(widths)
return {
tab = {
name = "manager_stations_tab",
type = "tab",
caption = { "cybersyn-gui.stations" },
ref = { "stations", "tab" },
handler = stations_tab.handle.on_stations_tab_selected
},
content = {
name = "manager_stations_content_frame",
type = "frame",
style = "ltnm_main_content_frame",
direction = "vertical",
ref = { "stations", "content_frame" },
{
type = "frame",
style = "ltnm_table_toolbar_frame",
templates.sort_checkbox(widths, "stations", "name", true),
templates.sort_checkbox(widths, "stations", "status", false), --repurposed status column, description no longer necessary
templates.sort_checkbox(widths, "stations", "network_id", false),
templates.sort_checkbox(
widths,
"stations",
"provided_requested",
false,
{ "cybersyn-gui-provided-requested-description" }
),
templates.sort_checkbox(widths, "stations", "shipments", false, { "cybersyn-gui-shipments-description" }),
templates.sort_checkbox(widths, "stations", "control_signals", false),
},
{ name = "manager_stations_tab_scroll_pane", type = "scroll-pane", style = "ltnm_table_scroll_pane", ref = { "stations", "scroll_pane" } },
{
type = "flow",
style = "ltnm_warning_flow",
visible = false,
ref = { "stations", "warning_flow" },
{
type = "label",
style = "ltnm_semibold_label",
caption = { "cybersyn-gui-no-stations" },
ref = { "stations", "warning_label" },
},
},
},
}
end
--- @param map_data MapData
--- @param player_data PlayerData
--- @return GuiElemDef
function stations_tab.build(map_data, player_data)
function stations_tab.build(map_data, player_data, query_limit)
local widths = constants.gui["en"]
local widths = constants.gui["en"]
local refs = player_data.refs
local search_item = player_data.search_item
local search_network_name = player_data.search_network_name
local search_network_mask = player_data.search_network_mask
local search_surface_idx = player_data.search_surface_idx
local search_query = player_data.search_query
local search_item = player_data.search_item
local search_network_name = player_data.search_network_name
local search_network_mask = player_data.search_network_mask
local search_surface_idx = player_data.search_surface_idx
local stations = map_data.stations
local stations_sorted = {}
local to_sorted_manifest = {}
local i = 0
for id, station in pairs(stations) do
local entity = station.entity_stop
if not entity.valid then
goto continue
end
local stations_sorted = {}
local to_sorted_manifest = {}
for id, station in pairs(map_data.stations) do
if search_network_name then
if search_network_name ~= station.network_name then
goto continue
end
local train_flag = get_network_flag(station, search_network_name)
if not bit32.btest(search_network_mask, train_flag) then
goto continue
end
elseif search_network_mask ~= -1 then
if station.network_name == NETWORK_EACH then
local masks = station.network_flag--[[@as {}]]
for _, network_flag in pairs(masks) do
if bit32.btest(search_network_mask, network_flag) then
goto has_match
end
end
goto continue
::has_match::
elseif not bit32.btest(search_network_mask, station.network_flag) then
goto continue
end
end
if search_surface_idx then
local entity = station.entity_stop
if not entity.valid then
goto continue
end
if entity.surface.index ~= search_surface_idx then
goto continue
end
end
if search_query then
if not string.match(entity.backer_name, search_query) then
goto continue
end
end
-- move surface comparison up higher in query to short circuit query earlier if surface doesn't match; this can exclude hundreds of stations instantly in SE
if search_surface_idx then
if search_surface_idx == -1 then
goto has_match
elseif entity.surface.index ~= search_surface_idx then
goto continue
end
::has_match::
end
if search_item then
if not station.deliveries then
goto continue
end
for item_name, _ in pairs(station.deliveries) do
if item_name == search_item then
goto has_match
end
end
goto continue
::has_match::
end
stations_sorted[#stations_sorted + 1] = id
--insertion sort
local manifest = {}
local manifest_type = {}
for name, _ in pairs(station.deliveries) do
local is_fluid = get_is_fluid(name)
local i = 1
while i <= #manifest do
if (not is_fluid and manifest_type[i]) or (is_fluid == manifest_type[i] and name < manifest[i]) then
break
end
i = i + 1
end
table.insert(manifest, i, name)
table.insert(manifest_type, i, is_fluid)
end
to_sorted_manifest[id] = manifest
::continue::
end
if search_network_name then
if search_network_name ~= station.network_name then
goto continue
end
local train_flag = get_network_flag(station, station.network_name)
if not bit32.btest(search_network_mask, train_flag) then
goto continue
end
elseif search_network_mask ~= -1 then
if station.network_name == NETWORK_EACH then
local masks = station.network_flag--[[@as {}]]
for _, network_flag in pairs(masks) do
if bit32.btest(search_network_mask, network_flag) then
goto has_match
end
end
goto continue
::has_match::
elseif not bit32.btest(search_network_mask, station.network_flag) then
goto continue
end
end
table.sort(stations_sorted, function(a, b)
local station1 = map_data.stations[a]
local station2 = map_data.stations[b]
for i, v in ipairs(player_data.trains_orderings) do
local invert = player_data.trains_orderings_invert[i]
if v == ORDER_LAYOUT then
if not station1.allows_all_trains and not station2.allows_all_trains then
local layout1 = station1.layout_pattern--[[@as uint[] ]]
local layout2 = station2.layout_pattern--[[@as uint[] ]]
for j, c1 in ipairs(layout1) do
local c2 = layout2[j]
if c1 ~= c2 then
return invert ~= (c2 and c1 < c2)
end
end
if layout2[#layout1 + 1] then
return invert ~= true
end
elseif station1.allows_all_trains ~= station2.allows_all_trains then
return invert ~= station2.allows_all_trains
end
elseif v == ORDER_NAME then
local name1 = station1.entity_stop.valid and station1.entity_stop.backer_name
local name2 = station2.entity_stop.valid and station2.entity_stop.backer_name
if name1 ~= name2 then
return invert ~= (name1 and (name2 and name1 < name2 or true) or false)
end
elseif v == ORDER_TOTAL_TRAINS then
if station1.deliveries_total ~= station2.deliveries_total then
return invert ~= (station1.deliveries_total < station2.deliveries_total)
end
elseif v == ORDER_MANIFEST then
if not next(station1.deliveries) then
if next(station2.deliveries) then
return invert ~= true
end
elseif not next(station2.deliveries) then
return invert ~= false
else
local first_item = nil
local first_direction = nil
for item_name in dual_pairs(station1.deliveries, station2.deliveries) do
if not first_item or item_lt(map_data.manager, item_name, first_item) then
local count1 = station1.deliveries[item_name] or 0
local count2 = station2.deliveries[item_name] or 0
if count1 ~= count2 then
first_item = item_name
first_direction = count1 < count2
end
end
end
if first_direction ~= nil then
return invert ~= first_direction
end
end
end
end
return (not player_data.trains_orderings_invert[#player_data.trains_orderings_invert]) == (a < b)
end)
if search_item then
if station.deliveries then
for item_name, _ in pairs(station.deliveries) do
if item_name == search_item then
goto has_match
end
end
end
local comb1_signals, _ = get_signals(station)
if comb1_signals then
for _, signal_ID in pairs(comb1_signals) do
local item = signal_ID.signal.name
if item then
if item == search_item then
goto has_match
end
end
end
end
goto continue
::has_match::
end
stations_sorted[#stations_sorted + 1] = id
i = i + 1
if query_limit ~= -1 and i >= query_limit then
break
end
::continue::
end
table.sort(stations_sorted, function(a, b)
local station1 = map_data.stations[a]
local station2 = map_data.stations[b]
for i, v in ipairs(player_data.trains_orderings) do
local invert = player_data.trains_orderings_invert[i]
if v == ORDER_LAYOUT then
if not station1.allows_all_trains and not station2.allows_all_trains then
local layout1 = station1.layout_pattern--[[@as uint[] ]]
local layout2 = station2.layout_pattern--[[@as uint[] ]]
for j, c1 in ipairs(layout1) do
local c2 = layout2[j]
if c1 ~= c2 then
return invert ~= (c2 and c1 < c2)
end
end
if layout2[#layout1 + 1] then
return invert ~= true
end
elseif station1.allows_all_trains ~= station2.allows_all_trains then
return invert ~= station2.allows_all_trains
end
elseif v == ORDER_NAME then
local name1 = station1.entity_stop.valid and station1.entity_stop.backer_name
local name2 = station2.entity_stop.valid and station2.entity_stop.backer_name
if name1 ~= name2 then
return invert ~= (name1 and (name2 and name1 < name2 or true) or false)
end
elseif v == ORDER_TOTAL_TRAINS then
if station1.deliveries_total ~= station2.deliveries_total then
return invert ~= (station1.deliveries_total < station2.deliveries_total)
end
elseif v == ORDER_MANIFEST then
if not next(station1.deliveries) then
if next(station2.deliveries) then
return invert ~= true
end
elseif not next(station2.deliveries) then
return invert ~= false
else
local first_item = nil
local first_direction = nil
for item_name in dual_pairs(station1.deliveries, station2.deliveries) do
if not first_item or item_lt(map_data.manager, item_name, first_item) then
local count1 = station1.deliveries[item_name] or 0
local count2 = station2.deliveries[item_name] or 0
if count1 ~= count2 then
first_item = item_name
first_direction = count1 < count2
end
end
end
if first_direction ~= nil then
return invert ~= first_direction
end
end
end
end
return (not player_data.trains_orderings_invert[#player_data.trains_orderings_invert]) == (a < b)
end)
local scroll_pane = refs.manager_stations_tab_scroll_pane
if next(scroll_pane.children) ~= nil then
refs.manager_stations_tab_scroll_pane.clear()
end
for i, station_id in pairs(stations_sorted) do
--- @type Station
local station = stations[station_id]
local network_sprite = "utility/close_black"
local network_name = station.network_name
local network_flag = get_network_flag(station, network_name)
if network_name ~= nil then
network_sprite, _, _ = util.generate_item_references(network_name)
end
local color = i % 2 == 0 and "dark" or "light"
gui.add(scroll_pane, {
type = "frame",
{
type = "label",
style = "ltnm_clickable_semibold_label",
style_mods = { width = widths.stations.name },
tooltip = constants.open_station_gui_tooltip,
caption = station.entity_stop.backer_name,
handler = stations_tab.handle.open_station_gui,
tags = { station_id = station_id }
},
--templates.status_indicator(widths.stations.status, true), --repurposing status column for network name
{ type = "sprite-button", style = "ltnm_small_slot_button_default", enabled = false, sprite = network_sprite, },
{ type = "label", style_mods = { width = widths.stations.network_id, horizontal_align = "center" }, caption = network_flag },
templates.small_slot_table(widths.stations, color, "provided_requested"),
templates.small_slot_table(widths.stations, color, "shipments"),
templates.small_slot_table(widths.stations, color, "control_signals"),
}, refs)
gui.add(refs.provided_requested_table, util.slot_table_build_from_station(station))
gui.add(refs.shipments_table, util.slot_table_build_from_deliveries(station))
gui.add(refs.control_signals_table, util.slot_table_build_from_control_signals(station))
end
if #stations_sorted == 0 then
--refs.warning_flow.visible = true
scroll_pane.visible = false
--refs.content_frame.style = "ltnm_main_warning_frame"
else
--refs.warning_flow.visible = false
scroll_pane.visible = true
--refs.content_frame.style = "ltnm_main_content_frame"
end
end
function stations_tab.update(self)
local refs = self.refs.stations
local widths = self.widths.stations
local search_query = state.search_query
local search_network_id = state.network_id
local search_surface = state.surface
stations_tab.handle = {}
local ltn_stations = state.ltn_data.stations
local scroll_pane = refs.scroll_pane
local children = scroll_pane.children
--- @param e {player_index: uint}
function stations_tab.wrapper(e, handler)
local player = game.get_player(e.player_index)
if not player then return end
local player_data = global.manager.players[e.player_index]
handler(player, player_data, player_data.refs, e)
end
local sorts = state.sorts.stations
local active_sort = sorts._active
local sorted_stations = state.ltn_data.sorted_stations[active_sort]
--- @param e GuiEventData
--- @param player LuaPlayer
--- @param player_data PlayerData
function stations_tab.handle.open_station_gui(player, player_data, refs, e)
local station_id = e.element.tags.station_id
--- @type Station
local station = global.stations[station_id]
local station_entity = station.entity_stop
local station_comb1 = station.entity_comb1
local station_comb2 = station.entity_comb2
local table_index = 0
-- False = ascending (arrow down), True = descending (arrow up)
local start, finish, step
if sorts[active_sort] then
start = #sorted_stations
finish = 1
step = -1
else
start = 1
finish = #sorted_stations
step = 1
end
for sorted_index = start, finish, step do
local station_id = sorted_stations[sorted_index]
local station_data = ltn_stations[station_id]
if station_data.entity.valid then
if
(search_surface == -1 or station_data.entity.surface.index == search_surface)
and bit32.btest(station_data.network_id, search_network_id)
and (
#search_query == 0 or string.find(station_data.search_strings[self.player.index], string.lower(search_query))
)
then
table_index = table_index + 1
local row = children[table_index]
local color = table_index % 2 == 0 and "dark" or "light"
if not row then
row = gui.add(scroll_pane, {
type = "frame",
style = "ltnm_table_row_frame_" .. color,
{
type = "label",
style = "ltnm_clickable_semibold_label",
style_mods = { width = widths.name },
tooltip = constants.open_station_gui_tooltip,
},
templates.status_indicator(widths.status, true),
{ type = "label", style_mods = { width = widths.network_id, horizontal_align = "center" } },
templates.small_slot_table(widths, color, "provided_requested"),
templates.small_slot_table(widths, color, "shipments"),
templates.small_slot_table(widths, color, "control_signals"),
})
end
gui.update(row, {
{
elem_mods = { caption = station_data.name },
actions = {
on_click = { gui = "main", action = "open_station_gui", station_id = station_id },
},
},
{
{ elem_mods = { sprite = "flib_indicator_" .. station_data.status.color } },
{ elem_mods = { caption = station_data.status.count } },
},
{ elem_mods = { caption = station_data.network_id } },
})
util.slot_table_update(row.provided_requested_frame.provided_requested_table, {
{ color = "green", entries = station_data.provided, translations = dictionaries.materials },
{ color = "red", entries = station_data.requested, translations = dictionaries.materials },
})
util.slot_table_update(row.shipments_frame.shipments_table, {
{ color = "green", entries = station_data.inbound, translations = dictionaries.materials },
{ color = "blue", entries = station_data.outbound, translations = dictionaries.materials },
})
util.slot_table_update(row.control_signals_frame.control_signals_table, {
{
color = "default",
entries = station_data.control_signals,
translations = dictionaries.virtual_signals,
type = "virtual-signal",
},
})
if not station_entity or not station_entity.valid then
util.error_flying_text(player, { "message.ltnm-error-station-is-invalid" })
return
end
if e.shift then
if station_entity.surface ~= player.surface then
util.error_flying_text(player, { "cybersyn-message.error-cross-surface-camera-invalid" })
else
player.zoom_to_world(station_entity.position, 1, station_entity)
rendering.draw_circle({
color = constants.colors.red.tbl,
target = station_entity.position,
surface = station_entity.surface,
radius = 0.5,
filled = false,
width = 5,
time_to_live = 60 * 3,
players = { player },
})
if not player_data.pinning then util.close_manager_window(player, player_data, refs) end
end
elseif e.control then
if station_comb1 ~= nil and station_comb1.valid then
player.opened = station_comb1
else
util.error_flying_text(player, { "cybersyn-message.error-cybernetic-combinator-not-found" })
end
elseif e.alt then
if station_comb2 ~= nil and station_comb2.valid then
player.opened = station_comb2
else
util.error_flying_text(player, { "cybersyn-message.error-station-control-combinator-not-found" })
end
else
player.opened = station_entity
end
end
end
for child_index = table_index + 1, #children do
children[child_index].destroy()
---@param player LuaPlayer
---@param player_data PlayerData
function stations_tab.handle.on_stations_tab_selected(player, player_data)
player_data.selected_tab = "stations_tab"
end
if table_index == 0 then
refs.warning_flow.visible = true
scroll_pane.visible = false
refs.content_frame.style = "ltnm_main_warning_frame"
else
refs.warning_flow.visible = false
scroll_pane.visible = true
refs.content_frame.style = "ltnm_main_content_frame"
end
end
gui.add_handlers(stations_tab.handle, stations_tab.wrapper)
return stations_tab

View File

@@ -30,7 +30,7 @@ function templates.inventory_slot_table(name, columns)
return {
type = "flow",
direction = "vertical",
{ type = "label", style = "bold_label", caption = { "gui.ltnm-" .. string.gsub(name, "_", "-") } },
{ type = "label", style = "bold_label", caption = { "cybersyn-gui." .. string.gsub(name, "_", "-") } },
{
type = "frame",
style = "deep_frame_in_shallow_frame",
@@ -43,7 +43,7 @@ function templates.inventory_slot_table(name, columns)
vertical_scroll_policy = "auto-and-reserve-space",
-- vertical_scroll_policy = "always",
ref = { "inventory", name, "scroll_pane" },
{ type = "table", style = "slot_table", column_count = columns, ref = { "inventory", name, "table" } },
{ type = "table", name = "inventory_" .. name .. "_table", style = "slot_table", column_count = columns, ref = { "inventory", name, "table" } },
},
},
}
@@ -77,7 +77,7 @@ function templates.sort_checkbox(widths, tab, column, selected, tooltip, state)
type = "checkbox",
style = selected and "ltnm_selected_sort_checkbox" or "ltnm_sort_checkbox",
style_mods = { width = widths and widths[tab][column] or nil, horizontally_stretchable = not widths },
caption = { "gui.ltnm-" .. string.gsub(column, "_", "-") },
caption = { "cybersyn-gui." .. string.gsub(column, "_", "-") },
tooltip = tooltip,
state = state,
ref = { tab, "toolbar", column .. "_checkbox" },

View File

@@ -1,4 +1,4 @@
local format = require("__flib__.format")
local train_util = require("__flib__.train")
local gui = require("__flib__.gui-lite")
local constants = require("constants")
@@ -8,21 +8,67 @@ local templates = require("scripts.gui.templates")
local trains_tab = {}
function trains_tab.create(widths)
return {
tab = {
name = "manager_trains_tab",
type = "tab",
--caption = #trains_sorted == 0 and { "cybersyn-gui.trains" } or { "cybersyn-gui.trains", #train_list },
caption = { "cybersyn-gui.trains" },
--badge_text = format.number(#ltn_data.sorted_trains.composition),
handler = trains_tab.handle.on_trains_tab_selected, --on_click
tags = { tab = "trains_tab" },
},
content = {
name = "manager_trains_tab_content_frame",
type = "frame",
style = "ltnm_main_content_frame",
direction = "vertical",
children = {
{
type = "frame",
style = "ltnm_table_toolbar_frame",
templates.sort_checkbox(widths, "trains", "train_id", false),
templates.sort_checkbox(widths, "trains", "status", false),
templates.sort_checkbox(widths, "trains", "layout", false),
templates.sort_checkbox(widths, "trains", "depot", false),
templates.sort_checkbox(widths, "trains", "shipment", false),
},
{ name = "manager_trains_tab_scroll_pane", type = "scroll-pane", style = "ltnm_table_scroll_pane" },
{
name = "trains_warning_flow",
type = "flow",
style = "ltnm_warning_flow",
},
},
},
}
end
--- @param map_data MapData
--- @param player_data PlayerData
--- @return GuiElemDef
function trains_tab.build(map_data, player_data)
function trains_tab.build(map_data, player_data, query_limit)
local widths = constants.gui["en"]
local refs = player_data.refs
local search_query = player_data.search_query
local search_item = player_data.search_item
local search_network_name = player_data.search_network_name
local search_network_mask = player_data.search_network_mask
local search_surface_idx = player_data.search_surface_idx
local trains = map_data.trains
local trains_sorted = {}
for id, train in pairs(map_data.trains) do
local layouts_table = util.build_train_layout_table(map_data)
local i = 0
for id, train in pairs(trains) do
if not train.entity.valid then
goto continue
end
if search_network_name then
if search_network_name ~= train.network_name then
goto continue
@@ -47,6 +93,9 @@ function trains_tab.build(map_data, player_data)
end
if search_surface_idx then
if search_surface_idx == -1 then
goto has_match
end
local entity = get_any_train_entity(train.entity)
if not entity then
goto continue
@@ -54,6 +103,7 @@ function trains_tab.build(map_data, player_data)
if entity.surface.index ~= search_surface_idx then
goto continue
end
::has_match::
end
if search_item then
@@ -70,6 +120,10 @@ function trains_tab.build(map_data, player_data)
end
trains_sorted[#trains_sorted + 1] = id
i = i + 1
if query_limit ~= -1 and i >= query_limit then
break
end
::continue::
end
@@ -126,106 +180,128 @@ function trains_tab.build(map_data, player_data)
return a < b
end)
local scroll_pane = refs.manager_trains_tab_scroll_pane
if next(scroll_pane.children) ~= nil then
refs.manager_trains_tab_scroll_pane.clear()
end
---@type GuiElemDef
local train_list = {}
if #trains_sorted == 0 then
train_list[1] = {
gui.add(scroll_pane, {
type = "label",
style = "ltnm_semibold_label",
caption = { "gui.ltnm-no-trains" },
}
caption = { "cybersyn-gui.no-trains" },
})
else
for idx, train_id in ipairs(trains_sorted) do
local train = map_data.trains[train_id]
local depot = map_data.depots[train.depot_id]
local depot_name = depot.entity_stop.valid and depot.entity_stop.backer_name or ""
local train_entity = train.entity
local locomotive
if train_entity.locomotives["front_movers"][1] then
locomotive = train_entity.locomotives["front_movers"][1]
else
locomotive = train_entity.locomotives["back_movers"][1]
end
local manifest = {}
if train.manifest ~= nil then
manifest = train.manifest
end
local network_sprite = "utility/close_black"
local network_name = train.network_name
local network_id = train.network_flag
if network_name then
network_sprite, _, _ = util.generate_item_references(network_name)
end
local color = idx % 2 == 0 and "dark" or "light"
train_list[idx] = {
gui.add(scroll_pane, {
type = "frame",
style = "ltnm_table_row_frame_" .. color,
children = {
{
type = "frame",
style = "ltnm_table_inset_frame_" .. color,
children = {
{
type = "minimap",
name = "train_minimap",
style = "ltnm_train_minimap",
{ type = "label", style = "ltnm_minimap_label" },
{ type = "label", style = "ltnm_minimap_label", caption = train_id },
{
type = "button",
style = "ltnm_train_minimap_button",
tooltip = { "gui.ltnm-open-train-gui" },
elem_mods = { entity = get_any_train_entity(train.entity) },
handler = trains_tab.handle.open_train_gui, --on_click
tooltip = { "cybersyn-gui.open-train-gui" },
tags = { train_id = train_id },
handler = trains_tab.handle.open_train_gui, --on_click
},
},
},
{
type = "frame",
style = "ltnm_table_row_frame_" .. color,
style_mods = { width = widths.trains.status },
{ type = "sprite-button", style = "ltnm_small_slot_button_default", enabled = false, sprite = network_sprite, number = network_id },
},
{
type = "label",
style_mods = { width = widths.trains.composition },
elem_mods = { caption = train.layout_id },
style_mods = { width = widths.trains.layout },
caption = layouts_table[train.layout_id],
},
{
type = "label",
style_mods = { width = widths.trains.depot },
elem_mods = { caption = depot_name },
caption = depot_name,
},
{
type = "frame",
name = "shipment_frame",
style = "ltnm_small_slot_table_frame_" .. color,
style_mods = { width = widths.trains.shipment },
children = {
{
type = "table",
name = "shipment_table",
style = "slot_table",
column_count = widths.trains.shipment_columns,
children = util.slot_table_build(train.manifest, "default"),
{ },
},
},
},
},
}
}, refs)
refs.train_minimap.entity = locomotive
gui.add(refs.shipment_table, util.slot_table_build_from_manifest(manifest, "default"))
end
end
return {
tab = {
name = "trains_tab",
type = "tab",
caption = #trains_sorted == 0 and { "gui.ltnm-trains" } or { "gui.ltnm-trains", #train_list },
--badge_text = format.number(#ltn_data.sorted_trains.composition),
handler = trains_tab.handle.change_tab, --on_click
tags = { tab = "trains_tab" },
},
content = {
name = "trains_content_frame",
type = "frame",
style = "ltnm_main_content_frame",
direction = "vertical",
children = {
{
type = "frame",
style = "ltnm_table_toolbar_frame",
templates.sort_checkbox(widths, "trains", "status", false),
templates.sort_checkbox(widths, "trains", "layout", false, { "gui.ltnm-composition-description" }),
templates.sort_checkbox(widths, "trains", "depot", false),
templates.sort_checkbox(widths, "trains", "shipment", false),
},
{ name = "trains_scroll_pane", type = "scroll-pane", style = "ltnm_table_scroll_pane" },
{
name = "trains_warning_flow",
type = "flow",
style = "ltnm_warning_flow",
children = train_list,
},
},
},
}
end
trains_tab.handle = {}
--- @param e {player_index: uint}
function trains_tab.wrapper(e, handler)
local player = game.get_player(e.player_index)
if not player then return end
local player_data = global.manager.players[e.player_index]
handler(player, player_data, player_data.refs, e)
end
--- @param e GuiEventData
--- @param player_data PlayerData
function trains_tab.handle.open_train_gui(player, player_data, refs, e)
local train_id = e.element.tags.train_id
--- @type Train
local train = global.trains[train_id]
local train_entity = train.entity
if not train_entity or not train_entity.valid then
util.error_flying_text(gui.player, { "message.ltnm-error-train-is-invalid" })
return
end
train_util.open_gui(player.index, train_entity)
end
---@param player LuaPlayer
---@param player_data PlayerData
function trains_tab.handle.on_trains_tab_selected(player, player_data)
player_data.selected_tab = "trains_tab"
end
gui.add_handlers(trains_tab.handle, trains_tab.wrapper)
return trains_tab

View File

@@ -33,34 +33,176 @@ function util.gui_list(parent, iterator, test, build, update, ...)
end
end
--- Builds a valid sprite path or returns nil
--- @param item string
--- @return string, string, string
function util.generate_item_references(item)
local sprite = nil
local image_path = ""
local item_name = ""
if game.is_valid_sprite_path("item/" .. item) then
sprite = "item/" .. item
image_path = "[img=item." .. item .. "]"
item_name = "item-name." .. item
elseif game.is_valid_sprite_path("fluid/" .. item) then
sprite = "fluid/" .. item
image_path = "[img=fluid." .. item .. "]"
item_name = "fluid-name." .. item
elseif game.is_valid_sprite_path("virtual-signal/" .. item) then
sprite = "virtual-signal/" .. item
image_path = "[img=virtual-signal." .. item .. "]"
item_name = "virtual-signal." .. item
end
return sprite, image_path, item_name
end
--- Updates a slot table based on the passed criteria.
--- @param manifest Manifest
--- @param color string
--- @return GuiElemDef[]
function util.slot_table_build(manifest, color)
function util.slot_table_build_from_manifest(manifest, color)
---@type GuiElemDef[]
local children = {}
for _, item in pairs(manifest) do
local name = item.name
local sprite
if item.type then
sprite = item.type .. "/" .. name
else
sprite = string.gsub(name, ",", "/")
if manifest then
for _, item in pairs(manifest) do
local name = item.name
local count = item.count
local sprite, img_path, item_string = util.generate_item_references(name)
if game.is_valid_sprite_path(sprite) then
children[#children + 1] = {
type = "sprite-button",
enabled = false,
style = "ltnm_small_slot_button_" .. color,
sprite = sprite,
number = count,
tooltip = {
"",
img_path,
{ item_string },
"\n"..format.number(count),
},
}
end
end
if game.is_valid_sprite_path(sprite) then
children[#children + 1] = {
type = "sprite-button",
enabled = false,
style = "ltnm_small_slot_button_" .. color,
sprite = sprite,
tooltip = {
"",
"[img=" .. sprite .. "]",
{ "item-name." .. name },
"\n"..format.number(count),
},
}
end
return children
end
--- @param station Station
--- @param color string
--- @return GuiElemDef[]
function util.slot_table_build_from_station(station)
---@type GuiElemDef[]
local children = {}
local comb1_signals, comb2_signals = get_signals(station)
if comb1_signals then
for _, v in pairs(comb1_signals) do
local item = v.signal
if item.type == "virtual" then
goto continue
end
local count = v.count
local name = item.name
local sprite, img_path, item_string = util.generate_item_references(name)
if sprite ~= nil then
local color
if count > 0 then
color = "green"
else
color = "red"
end
if game.is_valid_sprite_path(sprite) then
children[#children + 1] = {
type = "sprite-button",
enabled = false,
style = "ltnm_small_slot_button_" .. color,
sprite = sprite,
tooltip = {
"",
img_path,
{ item_string },
"\n"..format.number(count),
},
number = count
}
end
end
::continue::
end
end
return children
end
function util.slot_table_build_from_deliveries(station)
---@type GuiElemDef[]
local children = {}
local deliveries = station.deliveries
for item, count in pairs(deliveries) do
local sprite, img_path, item_string = util.generate_item_references(item)
if sprite ~= nil then
local color
if count > 0 then
color = "green"
else
color = "blue"
end
if game.is_valid_sprite_path(sprite) then
children[#children + 1] = {
type = "sprite-button",
enabled = false,
style = "ltnm_small_slot_button_" .. color,
sprite = sprite,
tooltip = {
"",
img_path,
{ item_string },
"\n"..format.number(count),
},
number = count
}
end
end
end
return children
end
--- @param station Station
--- @return GuiElemDef[]
function util.slot_table_build_from_control_signals(station)
---@type GuiElemDef[]
local children = {}
local comb1_signals, comb2_signals = get_signals(station)
if comb1_signals then
for _, v in pairs(comb1_signals) do
local item = v.signal
local count = v.count
local name = item.name
local sprite = ""
local color = "default"
if item.type ~= "virtual" then
goto continue
else
sprite = "virtual-signal" .. "/" .. name
end
if game.is_valid_sprite_path(sprite) then
children[#children + 1] = {
type = "sprite-button",
enabled = false,
style = "ltnm_small_slot_button_" .. color,
sprite = sprite,
tooltip = {
"",
"[img=virtual-signal." .. name .. "]",
{ "virtual-signal-name." .. name },
"\n"..format.number(count),
},
number = count
}
end
::continue::
end
end
return children
@@ -86,4 +228,37 @@ function util.signed_int32(val)
return (val >= MAX_INT and val - (2 * MAX_INT)) or val
end
function util.close_manager_window(player, player_data, refs)
if player_data.pinning then
return
end
refs.manager_window.visible = false
player_data.visible = false
if player.opened == refs.manager_window then
player.opened = nil
end
player_data.is_manager_open = false
player.set_shortcut_toggled("cybersyn-toggle-gui", false)
end
function util.build_train_layout_table(map_data)
local layouts = map_data.layouts
local layouts_table = {}
for i, v in pairs(layouts) do
local layout_string = table.concat(v, ",")
layout_string = layout_string.gsub(layout_string, "0", "[item=locomotive]")
layout_string = layout_string.gsub(layout_string, "1", "[item=cargo-wagon]")
layout_string = layout_string.gsub(layout_string, "2", "[item=fluid-wagon]")
layout_string = layout_string.gsub(layout_string, ",", "")
layouts_table[i] = layout_string
end
return layouts_table
end
return util

View File

@@ -1,9 +1,12 @@
--By Mami
local manager = require('gui.main')
local ceil = math.ceil
local table_insert = table.insert
local table_remove = table.remove
---@param map_data MapData
---@param stop LuaEntity
---@param comb LuaEntity
@@ -130,7 +133,7 @@ local function on_station_built(map_data, stop, comb1, comb2)
r_threshold = 0,
locked_slots = 0,
--network_name = set_station_from_comb,
--network_flag = set_station_from_comb,
network_flag = 0,
wagon_combs = nil,
deliveries = {},
accepted_layouts = {},
@@ -690,24 +693,28 @@ end
---@param schedule TrainSchedule
---@param stop LuaEntity
---@param old_surface_index uint
local function se_add_direct_to_station_order(schedule, stop, old_surface_index)
---@param search_start uint
local function se_add_direct_to_station_order(schedule, stop, old_surface_index, search_start)
--assert(search_start ~= 1 or schedule.current == 1)
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
for i = search_start, #records do
if records[i].station == name then
if i == 1 then
--we are assuming this is the depot order
--i == search_start == 1 only if schedule.current == 1, so we can append this order to the very end of the list and let it wrap around
records[#records + 1] = create_direct_to_station_order(stop)
schedule.current = #records--[[@as uint]]
return 2
else
table_insert(records, i, create_direct_to_station_order(stop))
return i + 2--[[@as uint]]
end
break
end
end
end
return search_start
end
local function setup_se_compat()
IS_SE_PRESENT = remote.interfaces["space-exploration"] ~= nil
@@ -778,28 +785,34 @@ local function setup_se_compat()
local schedule = train_entity.schedule
if schedule then
if train.status == STATUS_TO_P then
local stop = map_data.stations[train.p_station_id].entity_stop
if stop.valid then
se_add_direct_to_station_order(schedule, stop, old_surface_index)
end
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
if stop.valid then
se_add_direct_to_station_order(schedule, stop, old_surface_index)
end
end
if train.status == STATUS_TO_F then
local stop = map_data.refuelers[train.refueler_id].entity_stop
if stop.valid then
se_add_direct_to_station_order(schedule, stop, old_surface_index)
end
end
--this code relies on train chedules being in this specific order to work
local start = schedule.current
--check depot
if not train.use_any_depot then
local stop = map_data.depots[train.depot_id].entity_stop
if stop.valid then
se_add_direct_to_station_order(schedule, stop, old_surface_index)
start = se_add_direct_to_station_order(schedule, stop, old_surface_index, start)
end
end
--check provider
if train.status == STATUS_TO_P then
local stop = map_data.stations[train.p_station_id].entity_stop
if stop.valid then
start = se_add_direct_to_station_order(schedule, stop, old_surface_index, start)
end
end
--check requester
if train.status == STATUS_TO_P or train.status == STATUS_TO_R then
local stop = map_data.stations[train.r_station_id].entity_stop
if stop.valid then
start = se_add_direct_to_station_order(schedule, stop, old_surface_index, start)
end
end
--check refueler
if train.status == STATUS_TO_F then
local stop = map_data.refuelers[train.refueler_id].entity_stop
if stop.valid then
start = se_add_direct_to_station_order(schedule, stop, old_surface_index, start)
end
end
train_entity.schedule = schedule
@@ -808,6 +821,13 @@ local function setup_se_compat()
end)
end
local function setup_picker_dollies_compat()
IS_PICKER_DOLLIES_PRESENT = remote.interfaces["PickerDollies"] and remote.interfaces["PickerDollies"]["add_blacklist_name"]
if IS_PICKER_DOLLIES_PRESENT then
remote.call("PickerDollies", "add_blacklist_name", COMBINATOR_NAME)
remote.call("PickerDollies", "add_blacklist_name", COMBINATOR_OUT_NAME)
end
end
local function grab_all_settings()
mod_settings.enable_planner = settings.global["cybersyn-enable-planner"].value --[[@as boolean]]
@@ -822,6 +842,8 @@ local function grab_all_settings()
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]]
mod_settings.manager_enabled = settings.startup["cybersyn-manager-enabled"].value--[[@as boolean]]
mod_settings.manager_update_rate = settings.startup["cybersyn-manager-update-rate"].value--[[@as int]]
end
local function on_settings_changed(event)
grab_all_settings()
@@ -835,6 +857,7 @@ local function on_settings_changed(event)
script.on_nth_tick(nil)
end
end
manager.on_runtime_mod_setting_changed(event)
interface_raise_on_mod_settings_changed(event)
end
@@ -898,6 +921,9 @@ local function main()
script.on_nth_tick(nil)
end
local MANAGER_ENABLED = mod_settings.manager_enabled
script.on_init(function()
local setting = settings.global["cybersyn-invert-sign"]
setting.value = false
@@ -905,13 +931,36 @@ local function main()
mod_settings.invert_sign = false
init_global()
setup_se_compat()
setup_picker_dollies_compat()
if MANAGER_ENABLED then
manager.on_init()
end
end)
script.on_configuration_changed(on_config_changed)
script.on_configuration_changed(function(e)
on_config_changed(e)
if MANAGER_ENABLED then
manager.on_migration()
end
end)
script.on_load(function()
setup_se_compat()
setup_picker_dollies_compat()
end)
if MANAGER_ENABLED then
script.on_event(defines.events.on_player_removed, manager.on_player_removed)
script.on_event(defines.events.on_player_created, manager.on_player_created)
script.on_event(defines.events.on_lua_shortcut, manager.on_lua_shortcut)
script.on_event("cybersyn-toggle-gui", manager.on_lua_shortcut)
-- TODO: rework this to work as a per-player runtime setting
script.on_nth_tick(mod_settings.manager_update_rate, function()
manager.tick(global)
end)
end
end

View File

@@ -1,5 +1,6 @@
--By Mami
local flib_migration = require("__flib__.migration")
local manager_gui = require('gui.main')
local migrations_table = {
@@ -292,7 +293,13 @@ local migrations_table = {
is_registered[id] = true
end
end
end
end,
["1.2.14"] = function()
manager_gui.on_init()
for i, v in pairs(game.players) do
manager_gui.on_player_created({player_index = i})
end
end,
}
--STATUS_R_TO_D = 5

View File

@@ -243,6 +243,7 @@ function interface.update_stop_from_rail(rail, forbidden_entity, force_update)
update_stop_from_rail(global, rail, forbidden_entity, force_update)
end
------------------------------------------------------------------
--[[unsafe API]]
------------------------------------------------------------------

View File

@@ -102,6 +102,31 @@ data:extend({
setting_type = "runtime-global",
default_value = false,
},
{
type = "int-setting",
name = "cybersyn-manager-update-rate",
order = "ad",
setting_type = "startup",
default_value = 60,
minimum_value = 1,
maximum_value = 2147483647,
},
{
type = "bool-setting",
name = "cybersyn-manager-enabled",
order = "aa",
setting_type = "startup",
default_value = false,
},
{
type = "int-setting",
name = "cybersyn-manager-result-limit",
order = "aa",
setting_type = "runtime-per-user",
default_value = -1,
minimum_value = -1,
maximum_value = 2147483647,
}
--{
-- type = "bool-setting",
-- name = "cybersyn-disable-top-left-button",