diff --git a/TODO b/TODO index 52bf5a6..6bdfff0 100644 --- a/TODO +++ b/TODO @@ -6,3 +6,4 @@ models & art space elevator compat railloader compat major bug with copy-paste when the operation is changed by blueprint but it gets copied to the old settings before it's checked for update +go over init and make it agree with type info diff --git a/cybersyn/changelog.txt b/cybersyn/changelog.txt index 048ae83..a61230a 100644 --- a/cybersyn/changelog.txt +++ b/cybersyn/changelog.txt @@ -47,3 +47,8 @@ Version: 0.4.4 Date: 2022-11-25 Features: - Greatly improved automatic train blacklist logic +--------------------------------------------------------------------------------------------------- +Version: 0.5.0 +Date: 2022-11-25 + Features: + - Added SE space elevator compat diff --git a/cybersyn/info.json b/cybersyn/info.json index 88b9ef8..6c19641 100644 --- a/cybersyn/info.json +++ b/cybersyn/info.json @@ -1,12 +1,13 @@ { "name": "cybersyn", - "version": "0.4.4", + "version": "0.5.0", "title": "Project Cybersyn", "author": "Mami", "factorio_version": "1.1", "dependencies": [ "base", "flib >= 0.6.0", + "? space-exploration >= 0.6.90", "? miniloader" ] } diff --git a/cybersyn/scripts/central-planning.lua b/cybersyn/scripts/central-planning.lua index dcff70e..b61aeac 100644 --- a/cybersyn/scripts/central-planning.lua +++ b/cybersyn/scripts/central-planning.lua @@ -1,5 +1,4 @@ --By Mami -local get_distance = require("__flib__.misc").get_distance local min = math.min local max = math.max local abs = math.abs @@ -12,12 +11,6 @@ local table_sort = table.sort local random = math.random ----@param stop0 LuaEntity ----@param stop1 LuaEntity -local function get_stop_dist(stop0, stop1) - return get_distance(stop0.position, stop1.position) -end - ---@param map_data MapData ---@param station Station @@ -217,7 +210,7 @@ local function send_train_between(map_data, r_station_id, p_station_id, depot, p train.manifest = manifest train.last_manifest_tick = map_data.total_ticks - train.entity.schedule = create_manifest_schedule(train.depot_name, p_station.entity_stop, r_station.entity_stop, manifest) + set_manifest_schedule(train.entity, depot.entity_stop, p_station.entity_stop, r_station.entity_stop, manifest) set_comb2(map_data, p_station) set_comb2(map_data, r_station) if p_station.entity_comb1.valid then diff --git a/cybersyn/scripts/constants.lua b/cybersyn/scripts/constants.lua index a2f187e..e76795d 100644 --- a/cybersyn/scripts/constants.lua +++ b/cybersyn/scripts/constants.lua @@ -34,17 +34,15 @@ STATUS_P_TO_R = 3 STATUS_R = 4 STATUS_R_TO_D = 5 -TRAIN_LAYOUT_NA = "N" -TRAIN_LAYOUT_CARGO = "C" -TRAIN_LAYOUT_FLUID = "F" ---TRAIN_LAYOUT_ARTILLERY = "A" -STATION_LAYOUT_NA = "N" -STATION_LAYOUT_ALL = "." -STATION_LAYOUT_NOT_FLUID = "[NC]" -STATION_LAYOUT_NOT_CARGO = "[NF]" - LONGEST_INSERTER_REACH = 2 STATE_INIT = 0 STATE_POLL_STATIONS = 1 STATE_DISPATCH = 2 + +DIFFERENT_SURFACE_DISTANCE = 1000000000 + +SE_ELEVATOR_STOP_PROTO_NAME = "se-space-elevator-train-stop" +SE_ELEVATOR_ORBIT_SUFFIX = " ↓" +SE_ELEVATOR_PLANET_SUFFIX = " ↑" +SE_ELEVATOR_SUFFIX_LENGTH = 4 diff --git a/cybersyn/scripts/factorio-api.lua b/cybersyn/scripts/factorio-api.lua index 6eca48d..ca20e59 100644 --- a/cybersyn/scripts/factorio-api.lua +++ b/cybersyn/scripts/factorio-api.lua @@ -1,4 +1,5 @@ --By Mami +local get_distance = require("__flib__.misc").get_distance local abs = math.abs local floor = math.floor @@ -10,6 +11,13 @@ function get_stack_size(map_data, item_name) end +---@param stop0 LuaEntity +---@param stop1 LuaEntity +function get_stop_dist(stop0, stop1) + return get_distance(stop0.position, stop1.position) +end + + local create_loading_order_condition = {type = "inactivity", compare_type = "and", ticks = 120} ---@param stop LuaEntity ---@param manifest Manifest @@ -47,29 +55,50 @@ end local create_direct_to_station_order_condition = {{type = "time", compare_type = "and", ticks = 1}} ---@param stop LuaEntity -local function create_direct_to_station_order(stop) - return {rail = stop.connected_rail, rail_direction = stop.connected_rail_direction,wait_conditions = create_direct_to_station_order_condition} +function create_direct_to_station_order(stop) + return {rail = stop.connected_rail, rail_direction = stop.connected_rail_direction, wait_conditions = create_direct_to_station_order_condition} end +---@param train LuaTrain ---@param depot_name string -function create_depot_schedule(depot_name) - return {current = 1, records = {create_inactivity_order(depot_name)}} +function set_depot_schedule(train, depot_name) + train.schedule = {current = 1, records = {create_inactivity_order(depot_name)}} end ----@param depot_name string +---@param train LuaTrain +function lock_train(train) + train.manual_mode = true +end +--[[ +---@param train LuaTrain +---@param depot_stop LuaEntity ---@param p_stop LuaEntity ---@param r_stop LuaEntity ---@param manifest Manifest -function create_manifest_schedule(depot_name, p_stop, r_stop, manifest) - return {current = 1, records = { - create_inactivity_order(depot_name), +function set_manifest_schedule(train, depot_stop, p_stop, r_stop, manifest) + train.schedule = {current = 1, records = { + create_inactivity_order(depot_stop.backer_name), create_direct_to_station_order(p_stop), create_loading_order(p_stop, manifest), create_direct_to_station_order(r_stop), create_unloading_order(r_stop), }} end - +]] +---@param train LuaTrain +---@param stop LuaEntity +---@param old_name string +function rename_manifest_schedule(train, stop, old_name) + local new_name = stop.backer_name + local schedule = train.schedule + if not schedule then return end + for i, record in ipairs(schedule.records) do + if record.station == old_name then + record.station = new_name + end + end + train.schedule = schedule +end function get_comb_params(comb) return comb.get_or_create_control_behavior().parameters--[[@as ArithmeticCombinatorParameters]] end @@ -283,3 +312,89 @@ function send_stuck_train_alert(train, depot_name) end end end + +--function se_create_placeholder_order() +--end + +---@param surface LuaSurface +local function se_get_space_elevator_name(surface) + --TODO: check how expensive the following is and potentially cache it's results + local entity = surface.find_entities_filtered({ + name = SE_ELEVATOR_STOP_PROTO_NAME, + type = "train-stop", + limit = 1, + })[1] + if entity and entity.valid then + return string.sub(entity.backer_name, 1, string.len(entity.backer_name) - SE_ELEVATOR_SUFFIX_LENGTH) + end +end + +---@param train LuaTrain +---@param depot_stop LuaEntity +---@param p_stop LuaEntity +---@param r_stop LuaEntity +---@param manifest Manifest +function set_manifest_schedule(train, depot_stop, p_stop, r_stop, manifest) + --NOTE: train must be on same surface as depot_stop + local d_surface = depot_stop.surface + local p_surface = p_stop.surface + local r_surface = r_stop.surface + local d_surface_i = d_surface.index + local p_surface_i = p_surface.index + local r_surface_i = r_surface.index + if d_surface_i == p_surface_i and p_surface_i == r_surface_i then + train.schedule = {current = 1, records = { + create_inactivity_order(depot_stop.backer_name), + create_direct_to_station_order(p_stop), + create_loading_order(p_stop, manifest), + create_direct_to_station_order(r_stop), + create_unloading_order(r_stop), + }} + return + elseif IS_SE_PRESENT and (d_surface_i == p_surface_i or p_surface_i == r_surface_i or r_surface_i == d_surface_i) then + local d_zone = remote.call("space-exploration", "get_zone_from_surface_index", {surface_index = d_surface_i}) + local other_zone = remote.call("space-exploration", "get_zone_from_surface_index", {surface_index = (d_surface_i == p_surface_i) and r_surface_i or p_surface_i}) + local is_train_in_orbit = other_zone.orbit_index == d_zone.index + if is_train_in_orbit or d_zone.orbit_index == other_zone.index then + local elevator_name = se_get_space_elevator_name(d_surface) + if elevator_name then + local records = {create_inactivity_order(depot_stop.backer_name)} + if d_surface_i == p_surface_i then + records[#records + 1] = create_direct_to_station_order(p_stop) + else + records[#records + 1] = {station = elevator_name..(is_train_in_orbit and SE_ELEVATOR_ORBIT_SUFFIX or SE_ELEVATOR_PLANET_SUFFIX)} + is_train_in_orbit = not is_train_in_orbit + end + records[#records + 1] = create_loading_order(p_stop, manifest) + if p_surface_i ~= r_surface_i then + records[#records + 1] = {station = elevator_name..(is_train_in_orbit and SE_ELEVATOR_ORBIT_SUFFIX or SE_ELEVATOR_PLANET_SUFFIX)} + is_train_in_orbit = not is_train_in_orbit + end + records[#records + 1] = create_unloading_order(r_stop) + if r_surface_i ~= d_surface_i then + records[#records + 1] = {station = elevator_name..(is_train_in_orbit and SE_ELEVATOR_ORBIT_SUFFIX or SE_ELEVATOR_PLANET_SUFFIX)} + is_train_in_orbit = not is_train_in_orbit + end + + train.schedule = {current = 1, records = records} + return + end + end + end + --NOTE: create a schedule that cannot be fulfilled, the train will be stuck but it will give the player information what went wrong + train.schedule = {current = 1, records = { + create_inactivity_order(depot_stop.backer_name), + create_loading_order(p_stop, manifest), + create_unloading_order(r_stop), + }} + lock_train(train) + send_lost_train_alert(train, depot_stop.backer_name) +end + +---@param stop0 LuaEntity +---@param stop1 LuaEntity +function se_get_stop_dist(stop0, stop1) + local surface0 = stop0.surface.index + local surface1 = stop1.surface.index + return (surface0 == surface1 and get_distance(stop0.position, stop1.position) or DIFFERENT_SURFACE_DISTANCE) +end diff --git a/cybersyn/scripts/global.lua b/cybersyn/scripts/global.lua index f6e072c..38f57dc 100644 --- a/cybersyn/scripts/global.lua +++ b/cybersyn/scripts/global.lua @@ -18,6 +18,7 @@ ---@field public tick_state uint ---@field public tick_data {} ---@field public economy Economy +---@field public se_tele_old_id {[any]: uint} ---@class Station ---@field public is_p boolean @@ -63,6 +64,8 @@ ---@field public network_name string ---@field public network_flag int ---@field public priority int +---@field public se_awaiting_removal any? +---@field public se_awaiting_rename any? ---@alias Manifest {}[] ---@alias TrainClass {[uint]: true} @@ -84,6 +87,8 @@ ---@type CybersynModSettings mod_settings = {} +IS_SE_PRESENT = remote.interfaces["space-exploration"] ~= nil + function init_global() global.total_ticks = 0 global.tick_state = STATE_INIT @@ -107,4 +112,8 @@ function init_global() global.layout_train_count = {} global.layout_top_id = 1 global.is_player_cursor_blueprint = {} + + if IS_SE_PRESENT then + global.se_tele_old_id = {} + end end diff --git a/cybersyn/scripts/layout.lua b/cybersyn/scripts/layout.lua index 18b3301..c0cd134 100644 --- a/cybersyn/scripts/layout.lua +++ b/cybersyn/scripts/layout.lua @@ -43,7 +43,7 @@ end ---@param train Train ---@param train_id uint function remove_train(map_data, train, train_id) - if train.depot_id then + if train.status == STATUS_D then local depot = map_data.depots[train.depot_id] remove_available_train(map_data, train, depot) end @@ -84,7 +84,7 @@ function update_train_layout(map_data, train) i = i + 1 end local back_movers = train.entity.locomotives["back_movers"] - if back_movers and #back_movers > 0 then + if #back_movers > 0 then --mark the layout as reversible layout[0] = true end diff --git a/cybersyn/scripts/main.lua b/cybersyn/scripts/main.lua index 5dd5873..2cd2e3f 100644 --- a/cybersyn/scripts/main.lua +++ b/cybersyn/scripts/main.lua @@ -1,6 +1,7 @@ --By Mami local flib_event = require("__flib__.event") local floor = math.floor +local table_insert = table.insert ---@param map_data MapData @@ -67,6 +68,7 @@ local function add_available_train(map_data, depot_id, train_id) network[train_id] = depot_id end depot.available_train_id = train_id + train.status = STATUS_D train.depot_id = depot_id train.depot_name = depot.entity_stop.backer_name train.network_name = network_name @@ -105,7 +107,6 @@ function remove_available_train(map_data, train, depot) end end end - train.depot_id = nil depot.available_train_id = nil end @@ -128,7 +129,7 @@ local function on_depot_broken(map_data, depot) local train_id = depot.available_train_id if train_id then local train = map_data.trains[train_id] - train.entity.schedule = nil + lock_train(train.entity) send_lost_train_alert(train.entity, depot.entity_stop.backer_name) remove_available_train(map_data, train, depot) map_data.trains[train_id] = nil @@ -181,9 +182,13 @@ local function on_station_broken(map_data, station_id, station) 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) - send_lost_train_alert(train.entity, train.depot_name) + if train.entity then + remove_train(map_data, train, train_id) + lock_train(train.entity) + send_lost_train_alert(train.entity, train.depot_name) + else + train.se_awaiting_removal = train_id + end end end end @@ -510,7 +515,8 @@ local function on_stop_broken(map_data, stop) end ---@param map_data MapData ---@param stop LuaEntity -local function on_station_rename(map_data, stop) +---@param old_name string +local function on_station_rename(map_data, stop, old_name) --search for trains coming to the renamed station local station_id = stop.unit_number local station = map_data.stations[station_id] @@ -521,13 +527,21 @@ local function on_station_rename(map_data, stop) 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 + if is_r and not is_r_delivery_made then + local r_station = map_data.stations[train.r_station_id] + if train.entity then + rename_manifest_schedule(train.entity, r_station.entity_stop, old_name) + elseif IS_SE_PRESENT then + train.se_awaiting_rename = {r_station.entity_stop, old_name} + end + elseif is_p and not is_p_delivery_made then --train is attempting delivery to a stop that was renamed local p_station = map_data.stations[train.p_station_id] - local r_station = map_data.stations[train.r_station_id] - local schedule = create_manifest_schedule(train.depot_name, p_station.entity_stop, r_station.entity_stop, train.manifest) - schedule.current = train.entity.schedule.current - train.entity.schedule = schedule + if train.entity then + rename_manifest_schedule(train.entity, p_station.entity_stop, old_name) + elseif IS_SE_PRESENT then + train.se_awaiting_rename = {p_station.entity_stop, old_name} + end end end end @@ -568,21 +582,19 @@ local function on_train_arrives_depot(map_data, depot_id, train_entity) train.p_station_id = 0 train.r_station_id = 0 train.manifest = nil - train.status = STATUS_D add_available_train(map_data, depot_id, train_id) else if train.manifest then on_failed_delivery(map_data, train) send_unexpected_train_alert(train.entity) end - train.status = STATUS_D add_available_train(map_data, depot_id, train_id) end if is_train_empty then - train_entity.schedule = create_depot_schedule(train.depot_name) + set_depot_schedule(train_entity, train.depot_name) else --train still has cargo - train_entity.schedule = nil + lock_train(train.entity) remove_train(map_data, train, train_id) send_nonempty_train_in_depot_alert(train_entity) end @@ -596,16 +608,15 @@ local function on_train_arrives_depot(map_data, depot_id, train_entity) p_station_id = 0, r_station_id = 0, last_manifest_tick = map_data.total_ticks, - manifest = nil, + --manifest = nil, } update_train_layout(map_data, train) map_data.trains[train_id] = train add_available_train(map_data, depot_id, train_id) - local schedule = create_depot_schedule(train.depot_name) - train_entity.schedule = schedule + set_depot_schedule(train_entity, train.depot_name) else - train_entity.schedule = nil + lock_train(train.entity) send_nonempty_train_in_depot_alert(train_entity) end end @@ -635,7 +646,7 @@ local function on_train_arrives_buffer(map_data, stop, train) else on_failed_delivery(map_data, train) remove_train(map_data, train, train.entity.id) - train.entity.schedule = nil + lock_train(train.entity) send_lost_train_alert(train.entity, train.depot_name) end else @@ -674,7 +685,7 @@ local function on_train_leaves_station(map_data, train) set_comb1(map_data, station, nil) unset_wagon_combs(map_data, station) end - elseif train.depot_id then + elseif train.status == STATUS_D then local depot = map_data.depots[train.depot_id] remove_available_train(map_data, train, depot) end @@ -684,11 +695,12 @@ end ---@param map_data MapData ---@param train Train local function on_train_broken(map_data, train) - if train.manifest then + --NOTE: train.entity is only absent if the train is climbing a space elevator as of 0.5.0 + if train.manifest and train.entity then on_failed_delivery(map_data, train) remove_train(map_data, train, train.entity.id) if train.entity.valid then - train.entity.schedule = nil + lock_train(train.entity) end end end @@ -697,13 +709,14 @@ end ---@param train_entity LuaEntity local function on_train_modified(map_data, pre_train_id, train_entity) local train = map_data.trains[pre_train_id] - if train then + --NOTE: train.entity is only absent if the train is climbing a space elevator as of 0.5.0 + if train and train.entity then if train.manifest then on_failed_delivery(map_data, train) end remove_train(map_data, train, pre_train_id) if train.entity.valid then - train.entity.schedule = nil + lock_train(train.entity) end end end @@ -756,7 +769,7 @@ local function on_rotate(event) end local function on_rename(event) if event.entity.name == "train-stop" then - on_station_rename(global, event.entity) + on_station_rename(global, event.entity, event.old_name) end end @@ -771,26 +784,25 @@ local function on_train_built(event) end local function on_train_changed(event) local train_e = event.train - if train_e.valid then - local train = global.trains[train_e.id] - if train_e.state == defines.train_state.wait_station then - local stop = train_e.station - if stop and stop.valid and stop.name == "train-stop" then - if global.stations[stop.unit_number] then - if train then - on_train_arrives_buffer(global, stop, train) - end - else - local depot_id = stop.unit_number - if global.depots[depot_id] then - on_train_arrives_depot(global, depot_id, train_e) - end + if not train_e.valid then return end + local train = global.trains[train_e.id] + if train_e.state == defines.train_state.wait_station then + local stop = train_e.station + if stop and stop.valid and stop.name == "train-stop" then + if global.stations[stop.unit_number] then + if train then + on_train_arrives_buffer(global, stop, train) + end + else + local depot_id = stop.unit_number + if global.depots[depot_id] then + on_train_arrives_depot(global, depot_id, train_e) end end - elseif event.old_state == defines.train_state.wait_station then - if train then - on_train_leaves_station(global, 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 @@ -800,7 +812,7 @@ local function on_surface_removed(event) if surface then local train_stops = surface.find_entities_filtered({type = "train-stop"}) for _, entity in pairs(train_stops) do - if entity.name == "train-stop" then + if entity.valid and entity.name == "train-stop" then on_stop_broken(global, entity) end end @@ -915,6 +927,87 @@ local function main() flib_event.on_init(init_global) flib_event.on_configuration_changed(on_config_changed) + + + if IS_SE_PRESENT then + flib_event.on_load(function() + local se_on_train_teleport_finished_event = remote.call("space-exploration", "get_on_train_teleport_finished_event") + local se_on_train_teleport_started_event = remote.call("space-exploration", "get_on_train_teleport_started_event") + + + flib_event.register(se_on_train_teleport_started_event, function(event) + ---@type MapData + local map_data = global + local old_id = event.old_train_id_1 + local old_surface_index = event.old_surface_index + --NOTE: this is not guaranteed to be unique, it should be fine since the window of time for another train to mistakenly steal this train's event data is miniscule + --NOTE: please SE dev if you read this fix the issue where se_on_train_teleport_finished_event is returning the wrong old train id + local train_unique_identifier = event.train.front_stock.backer_name + + local train = map_data.trains[old_id] + if not train then return end + --NOTE: IMPORTANT, until se_on_train_teleport_finished_event is called map_data.trains[old_id] will reference an invalid train entity; very few of our events care about this and the ones that do should be impossible to trigger until teleportation is finished + train.entity = nil + map_data.se_tele_old_id[train_unique_identifier] = old_id + end) + flib_event.register(se_on_train_teleport_finished_event, function(event) + ---@type MapData + local map_data = global + ---@type LuaTrain + local train_entity = event.train + ---@type uint + local new_id = train_entity.id + local old_surface_index = event.old_surface_index + local train_unique_identifier = event.train.front_stock.backer_name + + --NOTE: event.old_train_id_1 from this event is useless, it's for one of the many transient trains SE spawns while teleporting the old train, only se_on_train_teleport_started_event returns the correct old train id + --NOTE: please SE dev if you read this fix the issue where se_on_train_teleport_finished_event is returning the wrong old train id + local old_id = map_data.se_tele_old_id[train_unique_identifier] + map_data.se_tele_old_id[train_unique_identifier] = nil + local train = map_data.trains[old_id] + if not train then return end + + map_data.trains[new_id] = train + map_data.trains[old_id] = nil + train.entity = train_entity + + if train.se_awaiting_removal then + remove_train(map_data, train, train.se_awaiting_removal) + lock_train(train.entity) + send_lost_train_alert(train.entity, train.depot_name) + return + elseif train.se_awaiting_rename then + rename_manifest_schedule(train.entity, train.se_awaiting_rename[1], train.se_awaiting_rename[2]) + train.se_awaiting_rename = nil + end + + if not (train.status == STATUS_D_TO_P or train.status == STATUS_P_TO_R) then return end + + local schedule = train_entity.schedule + if schedule then + local p_station = map_data.stations[train.p_station_id] + local p_name = p_station.entity_stop.backer_name + local p_surface_i = p_station.entity_stop.surface.index + local r_station = map_data.stations[train.r_station_id] + local r_name = r_station.entity_stop.backer_name + local r_surface_i = r_station.entity_stop.surface.index + local records = schedule.records + local i = schedule.current + while i <= #records do + if records[i].station == p_name and p_surface_i ~= old_surface_index then + table_insert(records, i, create_direct_to_station_order(p_station.entity_stop)) + i = i + 1 + elseif records[i].station == r_name and r_surface_i ~= old_surface_index then + table_insert(records, i, create_direct_to_station_order(r_station.entity_stop)) + i = i + 1 + end + i = i + 1 + end + train_entity.schedule = schedule + end + end) + end) + end end diff --git a/cybersyn/scripts/migrations.lua b/cybersyn/scripts/migrations.lua index 8c1cbd4..6209f04 100644 --- a/cybersyn/scripts/migrations.lua +++ b/cybersyn/scripts/migrations.lua @@ -109,10 +109,13 @@ local migrations_table = { for id, station in pairs(map_data.stations) do reset_station_layout(map_data, station) end - end, + end } ---@param data ConfigurationChangedData function on_config_changed(data) flib_migration.on_config_changed(data, migrations_table) + if IS_SE_PRESENT and not global.se_tele_old_id then + global.se_tele_old_id = {} + end end