renamed mod

This commit is contained in:
Monica Moniot
2022-10-16 01:59:56 -04:00
parent 822c9b6a7e
commit 131e9fab15
17 changed files with 0 additions and 0 deletions

5
cybersyn/changelog.txt Normal file
View File

@@ -0,0 +1,5 @@
---------------------------------------------------------------------------------------------------
Version: 0.1.0
Date: 2021-09-28
Features:
- Initial proof-of-concept

6
cybersyn/control.lua Normal file
View File

@@ -0,0 +1,6 @@
require("scripts.constants")
require("scripts.global")
require("scripts.controller")
require("scripts.main")

19
cybersyn/data.lua Normal file
View File

@@ -0,0 +1,19 @@
--By Mami
flib = require('__flib__.data-util')
require('scripts.constants')
require('prototypes.item')
require('prototypes.tech')
require('prototypes.entity')
data:extend({
cybersyn_depot_item,
cybersyn_station_item,
cybersyn_depot_recipe,
cybersyn_station_recipe,
cybersyn_tech,
cybersyn_depot_entity,
cybersyn_station_entity,
cybersyn_station_in,
cybersyn_station_out,
})

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

BIN
cybersyn/graphics/place.png Normal file
View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

11
cybersyn/info.json Normal file
View File

@@ -0,0 +1,11 @@
{
"name": "cybersyn",
"version": "0.0.1",
"title": "Project Cybersyn",
"author": "Mami",
"factorio_version": "1.1",
"dependencies": [
"base",
"flib >= 0.6.0"
]
}

View File

@@ -0,0 +1,9 @@
[mod-setting-name]
cybersyn-ticks-per-second=Dispatcher ticks per second
cybersyn-requester-threshold=Default requester threshold
cybersyn-provider-threshold=Default provider threshold
[mod-setting-description]
cybersyn-ticks-per-second=How many times per second to check all stations for possible deliveries. This value will be rounded up to a divisor of 60.
cybersyn-requester-threshold=When a requester threshold signal is not recieved by a station it will default to this value.
cybersyn-provider-threshold=When a provider threshold signal is not recieved by a station it will default to this value.

View File

@@ -0,0 +1,37 @@
--By Mami
cybersyn_station_entity = flib.copy_prototype(data.raw["train-stop"]["train-stop"], BUFFER_STATION_NAME)
cybersyn_station_entity.icon = "__cybersyn__/graphics/icon/station.png"
cybersyn_station_entity.icon_size = 64
cybersyn_station_entity.icon_mipmaps = 4
cybersyn_station_entity.next_upgrade = nil
cybersyn_depot_entity = flib.copy_prototype(data.raw["train-stop"]["train-stop"], DEPOT_STATION_NAME)
cybersyn_depot_entity.icon = "__cybersyn__/graphics/icon/depot.png"
cybersyn_depot_entity.icon_size = 64
cybersyn_depot_entity.icon_mipmaps = 4
cybersyn_depot_entity.next_upgrade = nil
cybersyn_station_in = flib.copy_prototype(data.raw["lamp"]["small-lamp"], STATION_IN_NAME)
cybersyn_station_in.icon = "__cybersyn__/graphics/icon/station.png"
cybersyn_station_in.icon_size = 64
cybersyn_station_in.icon_mipmaps = 4
cybersyn_station_in.next_upgrade = nil
cybersyn_station_in.minable = nil
cybersyn_station_in.selection_box = {{-0.5, -0.5}, {0.5, 0.5}}
cybersyn_station_in.selection_priority = cybersyn_station_in.selection_priority + 10
cybersyn_station_in.collision_box = {{-0.15, -0.15}, {0.15, 0.15}}
cybersyn_station_in.collision_mask = {"rail-layer"}
cybersyn_station_in.energy_usage_per_tick = "10W"
cybersyn_station_in.light = {intensity = 1, size = 6}
cybersyn_station_in.energy_source = {type="void"}
cybersyn_station_out = flib.copy_prototype(data.raw["constant-combinator"]["constant-combinator"],STATION_OUT_NAME)
cybersyn_station_out.icon = "__cybersyn__/graphics/icon/station.png"
cybersyn_station_out.icon_size = 64
cybersyn_station_out.icon_mipmaps = 4
cybersyn_station_out.next_upgrade = nil
cybersyn_station_out.minable = nil
cybersyn_station_out.selection_box = {{-0.5, -0.5}, {0.5, 0.5}}
cybersyn_station_out.selection_priority = cybersyn_station_out.selection_priority + 10
cybersyn_station_out.collision_box = {{-0.15, -0.15}, {0.15, 0.15}}
cybersyn_station_out.collision_mask = {"rail-layer"}

View File

@@ -0,0 +1,12 @@
--By Mami
cybersyn_station_item = flib.copy_prototype(data.raw["item"]["train-stop"], BUFFER_STATION_NAME)
cybersyn_station_item.icon = "__cybersyn__/graphics/icons/station.png"
cybersyn_station_item.icon_size = 64
cybersyn_station_item.icon_mipmaps = 4
cybersyn_station_item.order = cybersyn_station_item.order.."-c"
cybersyn_depot_item = flib.copy_prototype(data.raw["item"]["train-stop"], DEPOT_STATION_NAME)
cybersyn_depot_item.icon = "__cybersyn__/graphics/icons/depot.png"
cybersyn_depot_item.icon_size = 64
cybersyn_depot_item.icon_mipmaps = 4
cybersyn_depot_item.order = cybersyn_depot_item.order.."-d"

View File

@@ -0,0 +1,46 @@
--By Mami
cybersyn_station_recipe = flib.copy_prototype(data.raw["recipe"]["train-stop"], BUFFER_STATION_NAME)
cybersyn_station_recipe.ingredients = {
{"train-stop", 1},
{"advanced-circuit", 5},
}
cybersyn_station_recipe.enabled = false
cybersyn_depot_recipe = flib.copy_prototype(data.raw["recipe"]["train-stop"], BUFFER_STATION_NAME)
cybersyn_depot_recipe.ingredients = {
{"train-stop", 1},
{"electronic-circuit", 5},
}
cybersyn_depot_recipe.enabled = false
cybersyn_tech = {
type = "technology",
name = "cybernetic-train-network",
icon = "__cybersyn__/graphics/icon/tech.png",
icon_size = 64,
icon_mipmaps = 4,
prerequisites = {
"automated-rail-transportation",
"circuit-network",
"advanced-electronics"
},
effects = {
{
type = "unlock-recipe",
recipe = BUFFER_STATION_NAME
},
{
type = "unlock-recipe",
recipe = DEPOT_STATION_NAME
},
},
unit = {
ingredients = {
{"automation-science-pack", 1},
{"logistic-science-pack", 1}
},
count = 300,
time = 30
},
order = "c-g-c"
}

View File

@@ -0,0 +1,19 @@
--By Mami
SIGNAL_PRIORITY = "cybersyn-priority"
REQUEST_THRESHOLD = "cybersyn-request-threshold"
PROVIDE_THRESHOLD = "cybersyn-provide-threshold"
STATION_IN_NAME = "cybersyn-station-in"
STATION_OUT_NAME = "cybersyn-station-out"
BUFFER_STATION_NAME = "cybersyn-station"
DEPOT_STATION_NAME = "cybersyn-depot"
DELTA = 1/2048
STATUS_D = 0
STATUS_D_TO_P = 1
STATUS_P = 2
STATUS_P_TO_R = 3
STATUS_R = 4
STATUS_R_TO_D = 5

View File

@@ -0,0 +1,381 @@
--By Mami
local get_distance = require("__flib__.misc").get_distance
local math = math
local INF = math.huge
local function icpairs(a, start_i)
if #a == 0 then
return nil
end
start_i = start_i%#a + 1
local i = start_i - 1
local flag = true
return function()
i = i%#a + 1
if i ~= start_i or flag then
flag = false
local v = a[i]
if v then
return i, v
end
end
end
end
local create_loading_order_condition = {type = "inactivity", compare_type = "and", ticks = 120}
function create_loading_order(stop, manifest)
local condition = {}
for _, item in ipairs(manifest) do
local cond_type
if item.type == "fluid" then
cond_type = "fluid_count"
else
cond_type = "item_count"
end
condition[1] = {
type = cond_type,
compare_type = "and",
condition = {comparator = "", first_signal = {type = item.type, name = item.name}, constant = item.count}
}
condition[2] = create_loading_order_condition
end
return {station = stop.backer_name, wait_conditions = condition}
end
local create_unloading_order_condition = {type = "empty", compare_type = "and"}
function create_unloading_order(stop)
return {station = stop.backer_name, wait_conditions = create_unloading_order_condition}
end
local create_inactivity_order_condition = create_loading_order_condition
function create_inactivity_order(depot_name)
return {station = depot_name, wait_conditions = create_inactivity_order_condition}
end
local create_direct_to_station_order_condition = {{type = "time", compare_type = "and", ticks = 0}}
local function create_direct_to_station_order(stop)
return {wait_conditions = create_direct_to_station_order_condition, rail = stop.connected_rail, rail_direction = stop.connected_rail_direction}
end
local function get_signals(station)
local signals = station.entity_in.get_merged_signals()
return signals
end
local function get_stop_dist(stop0, stop1)
return get_distance(stop0.position, stop1.position)
end
local function station_accepts_layout(station, layout_id)
return true
end
local function get_valid_train(map_data, r_station_id, p_station_id, item_type)
--NOTE: this code is the critical section for run-time optimization
local r_station = map_data.stations[r_station_id]
local p_station = map_data.stations[p_station_id]
local p_to_r_dist = get_stop_dist(p_station.entity, r_station.entity)
if p_to_r_dist == INF then
return nil, INF
end
local best_train = nil
local best_dist = INF
local valid_train_exists = false
local is_fluid = item_type == "fluid"
for train_id, _ in pairs(map_data.trains_available) do
local train = map_data.trains[train_id]
--check cargo capabilities
--check layout validity for both stations
if
((is_fluid and train.fluid_capacity > 0) or (not is_fluid and train.item_slot_capacity > 0))
and station_accepts_layout(r_station, train.layout_id)
and station_accepts_layout(p_station, train.layout_id)
and train.entity.station
then
valid_train_exists = true
--check if exists valid path
--check if path is shortest so we prioritize locality
local d_to_p_dist = get_stop_dist(train.entity.station, p_station.entity)
local dist = d_to_p_dist
if dist < best_dist then
best_dist = dist
best_train = train
end
end
end
if valid_train_exists then
return best_train, best_dist + p_to_r_dist
else
return nil, p_to_r_dist
end
end
local function send_train_between(map_data, r_station_id, p_station_id, train, primary_item_name, economy)
local r_station = map_data.stations[r_station_id]
local p_station = map_data.stations[p_station_id]
local requests = {}
local manifest = {}
local r_signals = get_signals(r_station)
for k, v in pairs(r_signals) do
local item_name = v.signal.name
local item_count = v.count
local item_type = v.signal.type
if item_name and item_type and item_type ~= "virtual" then
local effective_item_count = item_count + r_station.deliveries[item_name]
if -effective_item_count >= r_station.r_threshold then
requests[item_name] = -effective_item_count
end
end
end
local p_signals = get_signals(r_station)
for k, v in pairs(p_signals) do
local item_name = v.signal.name
local item_count = v.count
local item_type = v.signal.type
if item_name and item_type and item_type ~= "virtual" then
local effective_item_count = item_count + p_station.deliveries[item_name]
if effective_item_count >= p_station.p_threshold then
local r = requests[item_name]
if r then
local item = {name = item_name, count = math.min(r, effective_item_count), type = item_type}
if item_name == primary_item_name then
manifest[#manifest + 1] = manifest[1]
manifest[1] = item
else
manifest[#manifest + 1] = item
end
end
end
end
end
local total_slots_left = train.item_slot_capacity
local total_liquid_left = train.fluid_capacity
local i = 1
while i <= #manifest do
local item = manifest[i]
local keep_item = false
if item.type == "fluid" then
if total_liquid_left > 0 then
if item.count > total_liquid_left then
item.count = total_liquid_left
end
total_liquid_left = 0--no liquid merging
keep_item = true
end
elseif total_slots_left > 0 then
local stack_size = game.item_prototypes[item.name].stack_size
local slots = math.ceil(item.count/stack_size)
if slots > total_slots_left then
item.count = total_slots_left*stack_size
end
total_slots_left = total_slots_left - slots
keep_item = true
end
if keep_item then
i = i + 1
else--swap remove
manifest[i] = manifest[#manifest]
manifest[#manifest] = nil
end
end
r_station.last_delivery_tick = economy.ticks_total
p_station.last_delivery_tick = economy.ticks_total
r_station.deliveries_total = r_station.deliveries_total + 1
p_station.deliveries_total = p_station.deliveries_total + 1
for _, item in ipairs(manifest) do
assert(item.count > 0, "main.lua error, transfer amount was not positive")
r_station.deliveries[item.name] = (r_station.deliveries[item.name] or 0) + item.count
p_station.deliveries[item.name] = (p_station.deliveries[item.name] or 0) - item.count
local r_stations = economy.r_stations_all[item.name]
local p_stations = economy.p_stations_all[item.name]
for i, id in ipairs(r_stations) do
if id == r_station_id then
table.remove(r_stations, i)
break
end
end
for i, id in ipairs(p_stations) do
if id == p_station_id then
table.remove(p_stations, i)
break
end
end
end
map_data.trains_available[train.entity.id] = nil
train.status = STATUS_D_TO_P
train.p_station_id = p_station_id
train.r_station_id = r_station_id
train.manifest = manifest
do
local records = {
create_inactivity_order(train.depot_name),
create_direct_to_station_order(p_station.entity),
create_loading_order(p_station.entity, manifest),
create_direct_to_station_order(r_station.entity),
create_unloading_order(r_station.entity),
}
local schedule = {current = 1, records = records}
train.entity.schedule = schedule
end
end
function tick(map_data, mod_settings)
local ticks_total = map_data.ticks_total
local stations = map_data.stations
local economy = {
r_stations_all = {},
p_stations_all = {},
all_items = {},
ticks_total = ticks_total,
}
local r_stations_all = economy.r_stations_all
local p_stations_all = economy.p_stations_all
local all_items = economy.all_items
for station_id, station in pairs(stations) do
if station.deliveries_total < station.train_limit then
station.r_threshold = mod_settings.r_threshold
station.p_threshold = mod_settings.p_threshold
station.priority = 0
local signals = get_signals(station)
for k, v in pairs(signals) do
local item_name = v.signal.name
local item_count = v.count
local item_type = v.signal.type
if item_name and item_type then
if item_type == "virtual" then
if item_name == SIGNAL_PRIORITY then
station.priority = item_count
elseif item_name == REQUEST_THRESHOLD then
station.r_threshold = math.abs(item_count)
elseif item_name == PROVIDE_THRESHOLD then
station.p_threshold = math.abs(item_count)
end
signals[k] = nil
end
else
signals[k] = nil
end
end
for k, v in pairs(signals) do
local item_name = v.signal.name
local item_count = v.count
local effective_item_count = item_count + station.deliveries[item_name]
if -effective_item_count >= station.r_threshold then
if r_stations_all[item_name] == nil then
r_stations_all[item_name] = {}
p_stations_all[item_name] = {}
all_items[#all_items + 1] = item_name
end
table.insert(r_stations_all[item_name], station_id)
elseif effective_item_count >= station.p_threshold then
if r_stations_all[item_name] == nil then
r_stations_all[item_name] = {}
p_stations_all[item_name] = {}
all_items[#all_items + 1] = item_name
end
table.insert(p_stations_all[item_name], station_id)
end
end
end
end
local failed_because_missing_trains_total = 0
--we do not dispatch more than one train per station per tick
--psuedo-randomize what item (and what station) to check first so if trains available is low they choose orders psuedo-randomly
for _, item_name in icpairs(all_items, ticks_total) do
local r_stations = r_stations_all[item_name]
local p_stations = p_stations_all[item_name]
--NOTE: this is an approximation algorithm for solving the assignment problem (bipartite graph weighted matching), the true solution would be to implement the simplex algorithm (and run it twice to compare the locality solution to the round-robin solution) but I strongly believe most factorio players would prefer run-time efficiency over perfect train routing logic
if #r_stations > 0 and #p_stations > 0 then
if #r_stations <= #p_stations then
--probably backpressure, prioritize locality
repeat
local i = ticks_total%#r_stations + 1
local r_station_id = table.remove(r_stations, i)
local best = 0
local best_train = nil
local best_dist = INF
local highest_prior = -INF
local could_have_been_serviced = false
for j, p_station_id in ipairs(p_stations) do
local train, d = get_valid_train(map_data, r_station_id, p_station_id)
local prior = stations[p_station_id].priority
if prior > highest_prior or (prior == highest_prior and d < best_dist) then
if train then
best = j
best_dist = d
best_train = train
highest_prior = prior
elseif d < INF then
could_have_been_serviced = true
end
end
end
if best > 0 then
send_train_between(map_data, r_station_id, p_stations[best], best_train, item_name, economy)
elseif could_have_been_serviced then
failed_because_missing_trains_total = failed_because_missing_trains_total + 1
end
until #r_stations == 0
else
--prioritize round robin
repeat
local j = ticks_total%#p_stations + 1
local p_station_id = table.remove(p_stations, j)
local best = 0
local best_train = nil
local lowest_tick = INF
local highest_prior = -INF
local could_have_been_serviced = false
for i, r_station_id in ipairs(r_stations) do
local r_station = stations[r_station_id]
local prior = r_station.priority
if prior > highest_prior or (prior == highest_prior and r_station.last_delivery_tick < lowest_tick) then
local train, d = get_valid_train(map_data, r_station_id, p_station_id)
if train then
best = i
best_train = train
lowest_tick = r_station.last_delivery_tick
highest_prior = prior
elseif d < INF then
could_have_been_serviced = true
end
end
end
if best > 0 then
send_train_between(map_data, r_stations[best], p_station_id, best_train, item_name, economy)
elseif could_have_been_serviced then
failed_because_missing_trains_total = failed_because_missing_trains_total + 1
end
until #p_stations == 0
end
end
end
end

View File

@@ -0,0 +1,60 @@
--By Mami
--[[
global: {
total_ticks: int
layout_top_id: int
stations: {[stop_id]: Station}
trains: {[train_id]: Train}
trains_available: {[train_id]: bool}
layouts: {[layout_id]: Layout}
layout_train_count: {[layout_id]: int}
}
Station: {
deliveries_total: int
train_limit: int
priority: int
last_delivery_tick: int
r_threshold: int >= 0
p_threshold: int >= 0
entity: LuaEntity
deliveries: {
[item_name]: int
}
--train_layout: [char]
accepted_layouts: {
[layout_id]: bool
}
}
Train: {
entity: LuaEntity
entity_in: LuaEntity
entity_out: LuaEntity
layout_id: int
item_slot_capacity: int
fluid_capacity: int
depot_name: string
status: int
p_station_id: stop_id
r_station_id: stop_id
manifest: [{
name: string
type: string
count: int
}]
}
Layout: string
]]
--TODO: only init once
mod_settings = {}
mod_settings.tps = settings.global["cybersyn-ticks-per-second"]
mod_settings.r_threshold = settings.global["cybersyn-requester-threshold"]
mod_settings.p_threshold = settings.global["cybersyn-provider-threshold"]
global.total_ticks = 0
global.stations = {}
global.trains = {}
global.trains_available = {}
global.layouts = {}
global.layout_train_count = {}
global.layout_top_id = 1

563
cybersyn/scripts/main.lua Normal file
View File

@@ -0,0 +1,563 @@
--By Mami
local function on_failed_delivery(map_data, train)
--NOTE: must change train status to STATUS_D or remove it from tracked trains after this call
local is_p_delivery_made = train.status ~= STATUS_D_TO_P and train.status ~= STATUS_P
if not is_p_delivery_made then
local station = map_data.stations[train.p_station_id]
for i, item in ipairs(train.manifest) do
station.deliveries[item.name] = station.deliveries[item.name] + item.count
if station.deliveries[item.name] == 0 then
station.deliveries[item.name] = nil
end
end
station.deliveries_total = station.deliveries_total - 1
if train.status == STATUS_P then
--change circuit outputs
station.entity_out.get_control_behavior().parameters = nil
end
end
local is_r_delivery_made = train.status == STATUS_R_TO_D
if not is_r_delivery_made then
local station = map_data.stations[train.r_station_id]
for i, item in ipairs(train.manifest) do
station.deliveries[item.name] = station.deliveries[item.name] - item.count
if station.deliveries[item.name] == 0 then
station.deliveries[item.name] = nil
end
end
station.deliveries_total = station.deliveries_total - 1
if train.status == STATUS_R then
--change circuit outputs
station.entity_out.get_control_behavior().parameters = nil
end
end
train.r_station_id = 0
train.p_station_id = 0
train.manifest = nil
end
local function remove_train(map_data, train, train_id)
map_data.trains[train_id] = nil
map_data.trains_available[train_id] = nil
local layout_id = train.layout_id
local count = map_data.layout_train_count[layout_id]
if count <= 1 then
map_data.layout_train_count[layout_id] = nil
map_data.layouts[layout_id] = nil
for station_id, station in pairs(map_data.stations) do
station.accepted_layouts[layout_id] = nil
end
else
map_data.layout_train_count[layout_id] = count - 1
end
end
local function on_station_built(map_data, stop)
local pos_x = stop.position.x
local pos_y = stop.position.y
local in_pos
local out_pos
local direction
local search_area
if stop.direction == 0 then
direction = 0
in_pos = {pos_x, pos_y - 1}
out_pos = {pos_x - 1, pos_y - 1}
search_area = {
{pos_x + DELTA - 1, pos_y + DELTA - 1},
{pos_x - DELTA + 1, pos_y - DELTA}
}
elseif stop.direction == 2 then
direction = 2
in_pos = {pos_x, pos_y}
out_pos = {pos_x, pos_y - 1}
search_area = {
{pos_x + DELTA, pos_y + DELTA - 1},
{pos_x - DELTA + 1, pos_y - DELTA + 1}
}
elseif stop.direction == 4 then
direction = 4
in_pos = {pos_x - 1, pos_y}
out_pos = {pos_x, pos_y}
search_area = {
{pos_x + DELTA - 1, pos_y + DELTA},
{pos_x - DELTA + 1, pos_y - DELTA + 1}
}
elseif stop.direction == 6 then
direction = 6
in_pos = {pos_x - 1, pos_y - 1}
out_pos = {pos_x - 1, pos_y}
search_area = {
{pos_x + DELTA - 1, pos_y + DELTA - 1},
{pos_x - DELTA, pos_y - DELTA + 1}
}
else
assert(false, "cybersyn: invalid direction of train stop")
end
local entity_in = nil
local entity_out = nil
local entities = stop.surface.find_entities(search_area)
for _, cur_entity in pairs (entities) do
if cur_entity.valid then
if cur_entity.name == "entity-ghost" then
if cur_entity.ghost_name == STATION_IN_NAME then
_, entity_in = cur_entity.revive()
elseif cur_entity.ghost_name == STATION_OUT_NAME then
_, entity_out = cur_entity.revive()
end
elseif cur_entity.name == STATION_IN_NAME then
entity_in = cur_entity
elseif cur_entity.name == STATION_OUT_NAME then
entity_out = cur_entity
end
end
end
if entity_in == nil then -- create new
entity_in = stop.surface.create_entity({
name = STATION_IN_NAME,
position = in_pos,
force = stop.force
})
end
entity_in.operable = false
entity_in.minable = false
entity_in.destructible = false
if entity_out == nil then -- create new
entity_out = stop.surface.create_entity({
name = STATION_OUT_NAME,
position = out_pos,
direction = direction,
force = stop.force
})
end
entity_out.operable = false
entity_out.minable = false
entity_out.destructible = false
local station = {
entity = stop,
entity_in = entity_in,
entity_out = entity_out,
deliveries_total = 0,
train_limit = 100,
priority = 0,
last_delivery_tick = 0,
r_threshold = 0,
p_threshold = 0,
accepted_layouts = {}
}
map_data.stations[stop.unit_number] = station
end
local function on_station_broken(map_data, stop)
--search for trains coming to the destroyed station
local station_id = stop.unit_number
local station = map_data.stations[station_id]
for train_id, train in pairs(map_data.trains) do
if station.deliveries_total <= 0 then
break
end
local is_p = train.r_station_id == station_id
local is_r = train.p_station_id == station_id
if is_p or is_r then
local is_p_delivery_made = train.status ~= STATUS_D_TO_P and train.status ~= STATUS_P
local is_r_delivery_made = train.status == STATUS_R_TO_D
if (is_r and not is_r_delivery_made) or (is_p and not is_p_delivery_made) then
--train is attempting delivery to a stop that was destroyed, stop it
on_failed_delivery(map_data, train)
train.entity.schedule = nil
remove_train(map_data, train, train_id)
--TODO: mark train as lost in the alerts system
end
end
end
map_data.stations[station_id] = nil
end
local function on_station_rename(map_data, stop)
--search for trains coming to the renamed station
local station_id = stop.unit_number
local station = map_data.stations[station_id]
for train_id, train in pairs(map_data.trains) do
if station.deliveries_total <= 0 then
break
end
local is_p = train.r_station_id == station_id
local is_r = train.p_station_id == station_id
if is_p or is_r then
local is_p_delivery_made = train.status ~= STATUS_D_TO_P and train.status ~= STATUS_P
local is_r_delivery_made = train.status == STATUS_R_TO_D
if (is_r and not is_r_delivery_made) or (is_p and not is_p_delivery_made) then
--train is attempting delivery to a stop that was renamed
--TODO: test to make sure this code actually works
local record = train.entity.schedule.records
if is_p then
record[3] = create_loading_order(station.entity, train.manifest)
else
record[5] = create_unloading_order(station.entity)
end
end
end
end
end
local function find_and_add_all_stations(map_data)
for _, surface in pairs(game.surfaces) do
local stops = surface.find_entities_filtered({type="train-stop"})
if stops then
for k, stop in pairs(stops) do
if stop.name == BUFFER_STATION_NAME then
local station = map_data.stations[stop.unit_number]
if not station then
on_station_built(map_data, stop)
end
end
end
end
end
end
local function update_train_layout(map_data, train)
local carriages = train.entity.carriages
local layout = ""
local i = 1
local item_slot_capacity = 0
local fluid_capacity = 0
for _, carriage in pairs(carriages) do
if carriage.type == "cargo-wagon" then
layout = layout.."C"
item_slot_capacity = item_slot_capacity + carriage.prototype.inventory_size
elseif carriage.type == "fluid-wagon" then
layout = layout.."F"
fluid_capacity = fluid_capacity + carriage.prototype.capacity
else
layout = layout.."?"
end
i = i + 1
end
local layout_id = 0
for id, cur_layout in pairs(map_data.layouts) do
if layout == cur_layout then
layout_id = id
break
end
end
if layout_id == 0 then
--define new layout
layout_id = map_data.layout_top_id
map_data.layout_top_id = map_data.layout_top_id + 1
map_data.layouts[layout_id] = layout
map_data.layout_train_count[layout_id] = 1
--for station_id, station in pairs(map_data.stations) do
-- if #layout >= #station.train_layout then
-- local is_approved = true
-- for i, v in ipairs(station.train_layout) do
-- local c = string.sub(layout, i, i)
-- if v == "C" then
-- if c ~= "C" and c ~= "?" then
-- is_approved = false
-- break
-- end
-- elseif v == "F" then
-- if c ~= "F" then
-- is_approved = false
-- break
-- end
-- end
-- end
-- for i = #station.train_layout, #layout do
-- local c = string.sub(layout, i, i)
-- if c ~= "?" then
-- is_approved = false
-- break
-- end
-- end
-- if is_approved then
-- station.accepted_layouts[layout_id] = true
-- end
-- end
--end
else
map_data.layout_train_count[layout_id] = map_data.layout_train_count[layout_id] + 1
end
train.layout_id = layout_id
train.item_slot_capacity = item_slot_capacity
train.fluid_capacity = fluid_capacity
end
local function on_train_arrives_depot(map_data, train_entity)
local train = map_data.trains[train_entity.id]
if train then
if train.manifest then
if train.status == STATUS_R_TO_D then
--succeeded delivery
train.p_station_id = 0
train.r_station_id = 0
train.manifest = nil
train.depot_name = train_entity.station.backer_name
train.status = STATUS_D
train.entity.schedule = {current = 1, records = {create_inactivity_order(train.depot_name)}}
map_data.trains_available[train_entity.id] = true
else
on_failed_delivery(map_data, train)
local contents = train.entity.get_contents()
if next(contents) == nil then
train.depot_name = train_entity.station.backer_name
train.status = STATUS_D
map_data.trains_available[train_entity.id] = true
else--train still has cargo
train.entity.schedule = nil
remove_train(map_data, train, train_entity.id)
--TODO: mark train as lost in the alerts system
end
end
end
else
train = {
depot_name = train_entity.station.backer_name,
status = STATUS_D,
entity = train_entity,
layout_id = 0,
item_slot_capacity = 0,
fluid_capacity = 0,
p_station = 0,
r_station = 0,
manifest = nil,
}
update_train_layout(train)
map_data.trains[train_entity.id] = train
map_data.trains_available[train_entity.id] = true
end
end
local function on_train_arrives_buffer(map_data, station_id, train)
if train.manifest then
if train.status == STATUS_D_TO_P then
if train.p_station_id == station_id then
train.status = STATUS_P
--change circuit outputs
local station = map_data.stations[station_id]
local signals = {}
for i, item in ipairs(train.manifest) do
signals[i] = {index = i, signal = {type = item.type, name = item.name}, count = item.count}
end
station.entity_out.get_control_behavior().parameters = signals
end
elseif train.status == STATUS_P_TO_R then
if train.r_station_id == station_id then
train.status = STATUS_R
--change circuit outputs
local station = map_data.stations[station_id]
local signals = {}
for i, item in ipairs(train.manifest) do
signals[i] = {index = i, signal = {type = item.type, name = item.name}, count = -1}
end
station.entity_out.get_control_behavior().parameters = signals
end
else
on_failed_delivery(map_data, train)
remove_train(map_data, train, train.entity.id)
end
else
--train is lost somehow, probably from player intervention
remove_train(map_data, train, train.entity.id)
end
end
local function on_train_leaves_station(map_data, train)
if train.manifest then
if train.status == STATUS_P then
train.status = STATUS_P_TO_R
local station = map_data.stations[train.p_station_id]
for i, item in ipairs(train.manifest) do
station.deliveries[item.name] = station.deliveries[item.name] + item.count
if station.deliveries[item.name] == 0 then
station.deliveries[item.name] = nil
end
end
station.deliveries_total = station.deliveries_total - 1
--change circuit outputs
station.entity_out.get_control_behavior().parameters = nil
elseif train.status == STATUS_R then
train.status = STATUS_R_TO_D
local station = map_data.stations[train.r_station_id]
for i, item in ipairs(train.manifest) do
station.deliveries[item.name] = station.deliveries[item.name] - item.count
if station.deliveries[item.name] == 0 then
station.deliveries[item.name] = nil
end
end
station.deliveries_total = station.deliveries_total - 1
--change circuit outputs
station.entity_out.get_control_behavior().parameters = nil
end
end
end
local function on_train_broken(map_data, train)
if train.manifest then
on_failed_delivery(map_data, train)
remove_train(map_data, train, train.entity.id)
end
end
local function on_train_modified(map_data, pre_train_id, train_entity)
local train = map_data.trains[pre_train_id]
if train then
if train.manifest then
on_failed_delivery(map_data, train)
remove_train(map_data, train, pre_train_id)
else--train is in depot
remove_train(map_data, train, pre_train_id)
train.entity = train_entity
update_train_layout(map_data, train)
--TODO: update train stats
map_data.trains[train_entity.id] = train
map_data.trains_available[train_entity.id] = true
end
end
end
local function on_tick(event)
tick(global, mod_settings)
global.total_ticks = global.total_ticks + 1
end
local function on_built(event)
local entity = event.entity or event.created_entity or event.destination
if not entity or not entity.valid then return end
if entity.name == BUFFER_STATION_NAME then
on_station_built(global, entity)
elseif entity.type == "inserter" then
elseif entity.type == "pump" then
if entity.pump_rail_target then
end
end
end
local function on_broken(event)
local entity = event.entity
if not entity or not entity.valid then return end
if entity.train then
local train = global.trains[entity.id]
if train then
on_train_broken(global, entity.train)
end
elseif entity.name == BUFFER_STATION_NAME then
on_station_broken(entity.unit_number)
elseif entity.type == "inserter" then
elseif entity.type == "pump" then
end
end
local function on_train_changed(event)
local train_e = event.train
local train = global.trains[train_e.id]
if train_e.state == defines.train_state.wait_station and train_e.station ~= nil then
if train_e.station.name == DEPOT_STATION_NAME then
on_train_arrives_depot(global, train_e)
elseif train_e.station.name == BUFFER_STATION_NAME then
if train then
on_train_arrives_buffer(global, train_e.station.unit_number, train)
end
end
elseif event.old_state == defines.train_state.wait_station then
if train then
on_train_leaves_station(global, train)
end
end
end
local function on_train_built(event)
local train_e = event.train
if event.old_train_id_1 then
on_train_modified(global, event.old_train_id_1, train_e)
end
if event.old_train_id_2 then
on_train_modified(global, event.old_train_id_2, train_e)
end
end
local function on_surface_removed(event)
local surface = game.surfaces[event.surface_index]
if surface then
local train_stops = surface.find_entities_filtered({type = "train-stop"})
for _, entity in pairs(train_stops) do
if entity.name == BUFFER_STATION_NAME then
on_station_broken(entity.unit_number)
end
end
end
end
local function on_rename(event)
if event.entity.name == BUFFER_STATION_NAME then
on_station_rename(global, event.entity)
end
end
local filter_built = {
{filter = "type", type = "train-stop"},
{filter = "type", type = "inserter"},
{filter = "type", type = "pump"},
}
local filter_broken = {
{filter = "type", type = "train-stop"},
{filter = "type", type = "inserter"},
{filter = "type", type = "pump"},
{filter = "rolling-stock"},
}
local function register_events()
--NOTE: I have no idea if this correctly registers all events once in all situations
script.on_event(defines.events.on_built_entity, on_built, filter_built)
script.on_event(defines.events.on_robot_built_entity, on_built, filter_built)
script.on_event({defines.events.script_raised_built, defines.events.script_raised_revive, defines.events.on_entity_cloned}, on_built)
script.on_event(defines.events.on_pre_player_mined_item, on_broken, filter_broken)
script.on_event(defines.events.on_robot_pre_mined, on_broken, filter_broken)
script.on_event(defines.events.on_entity_died, on_broken, filter_broken)
script.on_event(defines.events.script_raised_destroy, on_broken)
script.on_event({defines.events.on_pre_surface_deleted, defines.events.on_pre_surface_cleared}, on_surface_removed)
local nth_tick = math.ceil(60/mod_settings.tps);
script.on_nth_tick(nil)
script.on_nth_tick(nth_tick, on_tick)
script.on_event(defines.events.on_train_created, on_train_built)
script.on_event(defines.events.on_train_changed_state, on_train_changed)
script.on_event(defines.events.on_entity_renamed, on_rename)
end
script.on_load(function()
register_events()
end)
script.on_init(function()
--TODO: we are not checking changed cargo capacities
find_and_add_all_stations(global)
register_events()
end)
script.on_configuration_changed(function(data)
--TODO: we are not checking changed cargo capacities
find_and_add_all_stations(global)
register_events()
end)

30
cybersyn/settings.lua Normal file
View File

@@ -0,0 +1,30 @@
--By cybersyn
data:extend({
{
type = "int-setting",
name = "cybersyn-ticks-per-second",
order = "aa",
setting_type = "runtime-global",
default_value = 10,
minimum_value = 1,
maximum_value = 60,
},
{
type = "int-setting",
name = "cybersyn-requester-threshold",
order = "ab",
setting_type = "runtime-global",
default_value = 1000000000,
minimum_value = 1,
maximum_value = 2147483647,
},
{
type = "int-setting",
name = "cybersyn-provider-threshold",
order = "ac",
setting_type = "runtime-global",
default_value = 1000000000,
minimum_value = 1,
maximum_value = 2147483647,
},
})