--By Mami local flib_event = require("__flib__.event") local floor = math.floor local table_insert = table.insert ---@param map_data MapData ---@param station Station ---@param manifest Manifest ---@param sign int? local function set_comb1(map_data, station, manifest, sign) local comb = station.entity_comb1 if comb.valid then if manifest then local signals = {} for i, item in ipairs(manifest) do signals[i] = {index = i, signal = {type = item.type, name = item.name}, count = sign*item.count} end set_combinator_output(map_data, comb, signals) else set_combinator_output(map_data, comb, nil) end end end ---@param map_data MapData ---@param train Train 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] remove_manifest(map_data, station, train.manifest, 1) if train.status == STATUS_P then set_comb1(map_data, station, nil) unset_wagon_combs(map_data, station) 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] remove_manifest(map_data, station, train.manifest, -1) if train.status == STATUS_R then set_comb1(map_data, station, nil) unset_wagon_combs(map_data, station) end end train.r_station_id = 0 train.p_station_id = 0 train.manifest = nil end ---@param map_data MapData ---@param depot_id uint ---@param train_id uint local function add_available_train(map_data, depot_id, train_id) local depot = map_data.depots[depot_id] local train = map_data.trains[train_id] local comb = depot.entity_comb local network_name = get_comb_network_name(comb) if network_name then local network = map_data.available_trains[network_name] if not network then network = {} map_data.available_trains[network_name] = network end network[train_id] = 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 train.network_flag = mod_settings.network_flag train.priority = 0 if network_name then local signals = comb.get_merged_signals(defines.circuit_connector_id.combinator_input) if signals then for k, v in pairs(signals) do local item_name = v.signal.name local item_count = v.count if item_name then if item_name == SIGNAL_PRIORITY then train.priority = item_count end if item_name == network_name then train.network_flag = item_count end end end end end end ---@param map_data MapData ---@param train Train ---@param depot Depot function remove_available_train(map_data, train, depot) ---@type uint local train_id = depot.available_train_id if train.network_name then local network = map_data.available_trains[train.network_name] if network then network[train_id] = nil if next(network) == nil then map_data.available_trains[train.network_name] = nil end end end depot.available_train_id = nil end ---@param map_data MapData ---@param stop LuaEntity ---@param comb LuaEntity local function on_depot_built(map_data, stop, comb) local depot = { entity_stop = stop, entity_comb = comb, --available_train = nil, } map_data.depots[stop.unit_number] = depot end ---@param map_data MapData ---@param depot Depot 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] 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 end map_data.depots[depot.entity_stop.unit_number] = nil end ---@param map_data MapData ---@param stop LuaEntity ---@param comb1 LuaEntity ---@param comb2 LuaEntity local function on_station_built(map_data, stop, comb1, comb2) local station = { entity_stop = stop, entity_comb1 = comb1, entity_comb2 = comb2, wagon_combs = nil, deliveries_total = 0, last_delivery_tick = map_data.total_ticks, priority = 0, r_threshold = 0, locked_slots = 0, --network_name = param.first_signal and param.first_signal.name or nil, network_flag = 0, deliveries = {}, --allows_all_trains = param.second_constant == 1, accepted_layouts = {}, layout_pattern = nil, p_count_or_r_threshold_per_item = {}, } set_station_from_comb_state(station) local id = stop.unit_number--[[@as uint]] map_data.stations[id] = station map_data.warmup_station_ids[#map_data.warmup_station_ids + 1] = id update_station_if_auto(map_data, station, nil) end ---@param map_data MapData ---@param station_id uint ---@param station Station local function on_station_broken(map_data, station_id, station) if station.deliveries_total > 0 then --search for trains coming to the destroyed station for train_id, train in pairs(map_data.trains) do local is_r = train.r_station_id == station_id local is_p = 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) 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 end map_data.stations[station_id] = nil end ---@param map_data MapData ---@param stop LuaEntity ---@param comb_operation string ---@param comb_forbidden LuaEntity? local function search_for_station_combinator(map_data, stop, comb_operation, comb_forbidden) local pos_x = stop.position.x local pos_y = stop.position.y local search_area = { {pos_x - 2, pos_y - 2}, {pos_x + 2, pos_y + 2} } local entities = stop.surface.find_entities(search_area) for _, entity in pairs(entities) do if entity.valid and entity.name == COMBINATOR_NAME and entity ~= comb_forbidden and map_data.to_stop[entity.unit_number] == stop then local param = get_comb_params(entity) if param.operation == comb_operation then return entity end end end end ---@param map_data MapData ---@param comb LuaEntity local function on_combinator_built(map_data, comb) local pos_x = comb.position.x local pos_y = comb.position.y local search_area if comb.direction == defines.direction.north or comb.direction == defines.direction.south then search_area = { {pos_x - 1.5, pos_y - 2}, {pos_x + 1.5, pos_y + 2} } else search_area = { {pos_x - 2, pos_y - 1.5}, {pos_x + 2, pos_y + 1.5} } end local stop = nil local rail = nil local entities = comb.surface.find_entities(search_area) for _, cur_entity in pairs(entities) do if cur_entity.valid then if cur_entity.name == "train-stop" then --NOTE: if there are multiple stops we take the later one stop = cur_entity elseif cur_entity.type == "straight-rail" then rail = cur_entity end end end local out = comb.surface.create_entity({ name = COMBINATOR_OUT_NAME, position = comb.position, force = comb.force }) assert(out, "cybersyn: could not spawn combinator controller") comb.connect_neighbour({ target_entity = out, source_circuit_id = defines.circuit_connector_id.combinator_output, wire = defines.wire_type.green, }) comb.connect_neighbour({ target_entity = out, source_circuit_id = defines.circuit_connector_id.combinator_output, wire = defines.wire_type.red, }) local control = comb.get_or_create_control_behavior()--[[@as LuaArithmeticCombinatorControlBehavior]] local param = control.parameters local op = param.operation if op == OPERATION_DEFAULT then op = OPERATION_PRIMARY_IO param.operation = op param.first_signal = NETWORK_SIGNAL_DEFAULT control.parameters = param elseif op == OPERATION_PRIMARY_IO_ACTIVE or op == OPERATION_PRIMARY_IO_FAILED_REQUEST then op = OPERATION_PRIMARY_IO param.operation = op control.parameters = param end map_data.to_comb[comb.unit_number] = comb map_data.to_output[comb.unit_number] = out map_data.to_stop[comb.unit_number] = stop map_data.to_comb_params[comb.unit_number] = param if op == OPERATION_WAGON_MANIFEST then if rail then update_station_from_rail(map_data, rail, nil, true) end elseif op == OPERATION_DEPOT then if stop then local station = map_data.stations[stop.unit_number] ---@type Depot local depot = map_data.depots[stop.unit_number] if depot or station then --NOTE: repeated combinators are ignored else on_depot_built(map_data, stop, comb) end end elseif op == OPERATION_SECONDARY_IO then if stop then local station = map_data.stations[stop.unit_number] if station and not station.entity_comb2 then station.entity_comb2 = comb end end elseif op == OPERATION_PRIMARY_IO then if stop then local station = map_data.stations[stop.unit_number] if station then --NOTE: repeated combinators are ignored else local depot = map_data.depots[stop.unit_number] if depot then on_depot_broken(map_data, depot) end --no station or depot --add station local comb2 = search_for_station_combinator(map_data, stop, OPERATION_SECONDARY_IO, comb) on_station_built(map_data, stop, comb, comb2) end end end end ---@param map_data MapData ---@param comb LuaEntity ---@param network_name string? function on_combinator_network_updated(map_data, comb, network_name) local stop = map_data.to_stop[comb.unit_number] if stop and stop.valid then local station = map_data.stations[stop.unit_number] if station then if station.entity_comb1 == comb then station.network_name = network_name end else local depot_id = stop.unit_number local depot = map_data.depots[depot_id] if depot and depot.entity_comb == comb then local train_id = depot.available_train_id if train_id then local train = map_data.trains[train_id] remove_available_train(map_data, train, depot) add_available_train(map_data, depot_id, train_id) end end end end end ---@param map_data MapData ---@param comb LuaEntity local function on_combinator_broken(map_data, comb) --NOTE: we do not check for wagon manifest combinators and update their stations, it is assumed they will be lazy deleted later ---@type uint local comb_id = comb.unit_number local out = map_data.to_output[comb_id] local stop = map_data.to_stop[comb_id] if stop and stop.valid then local station = map_data.stations[stop.unit_number] if station then if station.entity_comb1 == comb then local comb1 = search_for_station_combinator(map_data, stop, OPERATION_PRIMARY_IO, comb) if comb1 then station.entity_comb1 = comb1 set_station_from_comb_state(station) update_station_if_auto(map_data, station) else on_station_broken(map_data, stop.unit_number, station) local depot_comb = search_for_station_combinator(map_data, stop, OPERATION_DEPOT, comb) if depot_comb then on_depot_built(map_data, stop, depot_comb) end end elseif station.entity_comb2 == comb then station.entity_comb2 = search_for_station_combinator(map_data, stop, OPERATION_SECONDARY_IO, comb) end else local depot = map_data.depots[stop.unit_number] if depot and depot.entity_comb == comb then --NOTE: this will disrupt deliveries in progress that where dispatched from this station in a minor way local depot_comb = search_for_station_combinator(map_data, stop, OPERATION_DEPOT, comb) if depot_comb then depot.entity_comb = depot_comb else on_depot_broken(map_data, depot) end end end end if out and out.valid then out.destroy() end map_data.to_comb[comb_id] = nil map_data.to_output[comb_id] = nil map_data.to_stop[comb_id] = nil map_data.to_comb_params[comb_id] = nil end ---@param map_data MapData ---@param comb LuaEntity ---@param new_params ArithmeticCombinatorParameters function on_combinator_updated(map_data, comb, new_params) local old_params = map_data.to_comb_params[comb.unit_number] if new_params.operation ~= old_params.operation then if (new_params.operation == OPERATION_PRIMARY_IO_ACTIVE or new_params.operation == OPERATION_PRIMARY_IO_FAILED_REQUEST or new_params.operation == OPERATION_PRIMARY_IO) and (old_params.operation == OPERATION_PRIMARY_IO_ACTIVE or old_params.operation == OPERATION_PRIMARY_IO_FAILED_REQUEST or old_params.operation == OPERATION_PRIMARY_IO) then set_combinator_operation(comb, old_params.operation) new_params.operation = old_params.operation else --NOTE: This is rather dangerous, we may need to actually implement operation changing on_combinator_broken(map_data, comb) on_combinator_built(map_data, comb) return end end local new_signal = new_params.first_signal local old_signal = old_params.first_signal local new_network = new_signal and new_signal.name or nil local old_network = old_signal and old_signal.name or nil if new_network ~= old_network then on_combinator_network_updated(map_data, comb, new_network) end if new_params.second_constant ~= old_params.second_constant then local stop = global.to_stop[comb.unit_number] if stop then local station = global.stations[stop.unit_number] if station then local bits = new_params.second_constant local is_pr_state = floor(bits/2)%3 station.is_p = is_pr_state == 0 or is_pr_state == 1 station.is_r = is_pr_state == 0 or is_pr_state == 2 local allows_all_trains = bits%2 == 1 if station.allows_all_trains ~= allows_all_trains then station.allows_all_trains = allows_all_trains update_station_if_auto(map_data, station) end end end end map_data.to_comb_params[comb.unit_number] = new_params end ---@param map_data MapData ---@param stop LuaEntity local function on_stop_built(map_data, stop) local pos_x = stop.position.x local pos_y = stop.position.y local search_area = { {pos_x - 2, pos_y - 2}, {pos_x + 2, pos_y + 2} } local comb2 = nil local comb1 = nil local depot_comb = nil local entities = stop.surface.find_entities(search_area) for _, entity in pairs(entities) do if entity.valid and entity.name == COMBINATOR_NAME and map_data.to_stop[entity.unit_number] == nil then map_data.to_stop[entity.unit_number] = stop local param = get_comb_params(entity) local op = param.operation if op == OPERATION_PRIMARY_IO then comb1 = entity elseif op == OPERATION_SECONDARY_IO then comb2 = entity elseif op == OPERATION_DEPOT then depot_comb = entity end end end if comb1 then on_station_built(map_data, stop, comb1, comb2) elseif depot_comb then on_depot_built(map_data, stop, depot_comb) end end ---@param map_data MapData ---@param stop LuaEntity local function on_stop_broken(map_data, stop) local pos_x = stop.position.x local pos_y = stop.position.y local search_area = { {pos_x - 2, pos_y - 2}, {pos_x + 2, pos_y + 2} } local entities = stop.surface.find_entities(search_area) for _, entity in pairs(entities) do if entity.valid and map_data.to_stop[entity.unit_number] == stop then map_data.to_stop[entity.unit_number] = nil end end local station = map_data.stations[stop.unit_number] if station then on_station_broken(map_data, stop.unit_number, station) else local depot = map_data.depots[stop.unit_number] if depot then on_depot_broken(map_data, depot) end end end ---@param map_data MapData ---@param stop LuaEntity ---@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] if station and station.deliveries_total > 0 then for train_id, train in pairs(map_data.trains) do local is_p = train.p_station_id == station_id local is_r = train.r_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 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] 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 else local depot = map_data.depots[station_id] if depot and depot.available_train_id then local train = map_data.trains[depot.available_train_id] train.depot_name = stop.backer_name end end end ---@param map_data MapData local function find_and_add_all_stations_from_nothing(map_data) for _, surface in pairs(game.surfaces) do local entities = surface.find_entities_filtered({name = COMBINATOR_NAME}) for k, comb in pairs(entities) do if comb.valid then on_combinator_built(map_data, comb) end end end end ---@param map_data MapData ---@param depot_id uint ---@param train_entity LuaTrain local function on_train_arrives_depot(map_data, depot_id, train_entity) local contents = train_entity.get_contents() local fluid_contents = train_entity.get_fluid_contents() local is_train_empty = next(contents) == nil and next(fluid_contents) == nil local train_id = train_entity.id local train = map_data.trains[train_id] if train then if train.manifest and train.status == STATUS_R_TO_D then --succeeded delivery train.p_station_id = 0 train.r_station_id = 0 train.manifest = nil 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 add_available_train(map_data, depot_id, train_id) end if is_train_empty then set_depot_schedule(train_entity, train.depot_name) else --train still has cargo lock_train(train.entity) remove_train(map_data, train, train_id) send_nonempty_train_in_depot_alert(train_entity) end elseif is_train_empty then train = { status = STATUS_D, entity = train_entity, layout_id = 0, item_slot_capacity = 0, fluid_capacity = 0, p_station_id = 0, r_station_id = 0, last_manifest_tick = map_data.total_ticks, --manifest = nil, } update_train_layout(map_data, train) map_data.trains[train_id] = train add_available_train(map_data, depot_id, train_id) set_depot_schedule(train_entity, train.depot_name) else lock_train(train.entity) send_nonempty_train_in_depot_alert(train_entity) end end ---@param map_data MapData ---@param stop LuaEntity ---@param train Train local function on_train_arrives_buffer(map_data, stop, train) if train.manifest then ---@type uint local station_id = stop.unit_number if train.status == STATUS_D_TO_P then if train.p_station_id == station_id then train.status = STATUS_P local station = map_data.stations[station_id] set_comb1(map_data, station, train.manifest, 1) set_p_wagon_combs(map_data, station, train) end elseif train.status == STATUS_P_TO_R then if train.r_station_id == station_id then train.status = STATUS_R local station = map_data.stations[station_id] set_comb1(map_data, station, train.manifest, -1) set_r_wagon_combs(map_data, station, train) end elseif train.status == STATUS_P and train.p_station_id == station_id then elseif train.status == STATUS_R and train.r_station_id == station_id then else on_failed_delivery(map_data, train) remove_train(map_data, train, train.entity.id) lock_train(train.entity) send_lost_train_alert(train.entity, train.depot_name) end else --train is lost somehow, probably from player intervention remove_train(map_data, train, train.entity.id) end end ---@param map_data MapData ---@param train Train 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] remove_manifest(map_data, station, train.manifest, 1) set_comb1(map_data, station, nil) unset_wagon_combs(map_data, station) if train.has_filtered_wagon then train.has_filtered_wagon = false for carriage_i, carriage in ipairs(train.entity.carriages) do if carriage.type == "cargo-wagon" then local inv = carriage.get_inventory(defines.inventory.cargo_wagon) if inv and inv.is_filtered() then ---@type uint for i = 1, #inv do inv.set_filter(i, nil) end end end end end elseif train.status == STATUS_R then train.status = STATUS_R_TO_D local station = map_data.stations[train.r_station_id] remove_manifest(map_data, station, train.manifest, -1) set_comb1(map_data, station, nil) unset_wagon_combs(map_data, station) end elseif train.status == STATUS_D then local depot = map_data.depots[train.depot_id] remove_available_train(map_data, train, depot) end end ---@param map_data MapData ---@param train Train local function on_train_broken(map_data, train) --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 lock_train(train.entity) end end end ---@param map_data MapData ---@param pre_train_id uint ---@param train_entity LuaEntity local function on_train_modified(map_data, pre_train_id, train_entity) local train = map_data.trains[pre_train_id] --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 lock_train(train.entity) end end end local function on_built(event) local entity = event.entity or event.created_entity if not entity or not entity.valid then return end if entity.name == "train-stop" then on_stop_built(global, entity) elseif entity.name == COMBINATOR_NAME then on_combinator_built(global, entity) elseif entity.type == "inserter" then update_station_from_inserter(global, entity) elseif entity.type == "pump" then update_station_from_pump(global, entity) elseif entity.type == "straight-rail" then update_station_from_rail(global, entity) end end local function on_broken(event) local entity = event.entity if not entity or not entity.valid then return end if entity.name == "train-stop" then on_stop_broken(global, entity) elseif entity.name == COMBINATOR_NAME then on_combinator_broken(global, entity) elseif entity.type == "inserter" then update_station_from_inserter(global, entity, entity) elseif entity.type == "pump" then update_station_from_pump(global, entity, entity) elseif entity.type == "straight-rail" then update_station_from_rail(global, entity, nil) elseif entity.train then local train = global.trains[entity.train.id] if train then on_train_broken(global, train) end end end local function on_rotate(event) local entity = event.entity or event.created_entity if not entity or not entity.valid then return end if entity.type == "inserter" then update_station_from_inserter(global, entity) end end local function on_rename(event) if event.entity.name == "train-stop" then on_station_rename(global, event.entity, event.old_name) 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_train_changed(event) local train_e = event.train 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 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_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.valid and entity.name == "train-stop" then on_stop_broken(global, entity) end end end end local function on_paste(event) local entity = event.destination if not entity or not entity.valid then return end if entity.name == COMBINATOR_NAME then on_combinator_updated(global, entity, get_comb_params(entity)) end end local function on_cursor_stack_changed(event) local i = event.player_index local player = game.get_player(i) if not player then return end local cursor = player.cursor_stack if global.is_player_cursor_blueprint[i] then --TODO: check if we can limit this search somehow? for id, comb in pairs(global.to_comb) do on_combinator_updated(global, comb, get_comb_params(comb)) end end local contains_comb = nil if cursor and cursor.valid_for_read and cursor.type == "blueprint" then local cost_to_build = cursor.cost_to_build for k, v in pairs(cost_to_build) do if k == COMBINATOR_NAME then contains_comb = true break end end end global.is_player_cursor_blueprint[i] = contains_comb end local function on_settings_changed(event) mod_settings.tps = settings.global["cybersyn-ticks-per-second"].value --[[@as int]] mod_settings.r_threshold = settings.global["cybersyn-request-threshold"].value--[[@as int]] mod_settings.network_flag = settings.global["cybersyn-network-flag"].value--[[@as int]] mod_settings.warmup_time = settings.global["cybersyn-warmup-time"].value--[[@as int]] mod_settings.stuck_train_time = settings.global["cybersyn-stuck-train-time"].value--[[@as int]] if event.setting == "cybersyn-ticks-per-second" then local nth_tick = math.ceil(60/mod_settings.tps); flib_event.on_nth_tick(nil) flib_event.on_nth_tick(nth_tick, function() tick(global, mod_settings) end) end end local filter_built = { {filter = "name", name = "train-stop"}, {filter = "name", name = COMBINATOR_NAME}, {filter = "type", type = "inserter"}, {filter = "type", type = "pump"}, {filter = "type", type = "straight-rail"}, } local filter_broken = { {filter = "name", name = "train-stop"}, {filter = "name", name = COMBINATOR_NAME}, {filter = "type", type = "inserter"}, {filter = "type", type = "pump"}, {filter = "type", type = "straight-rail"}, {filter = "rolling-stock"}, } local function main() mod_settings.tps = settings.global["cybersyn-ticks-per-second"].value --[[@as int]] mod_settings.r_threshold = settings.global["cybersyn-request-threshold"].value--[[@as int]] mod_settings.network_flag = settings.global["cybersyn-network-flag"].value--[[@as int]] mod_settings.warmup_time = settings.global["cybersyn-warmup-time"].value--[[@as int]] mod_settings.stuck_train_time = settings.global["cybersyn-stuck-train-time"].value--[[@as int]] --NOTE: There is a concern that it is possible to build or destroy important entities without one of these events being triggered, in which case the mod will have undefined behavior flib_event.register(defines.events.on_built_entity, on_built, filter_built) flib_event.register(defines.events.on_robot_built_entity, on_built, filter_built) flib_event.register({defines.events.script_raised_built, defines.events.script_raised_revive, defines.events.on_entity_cloned}, on_built) flib_event.register(defines.events.on_player_rotated_entity, on_rotate) flib_event.register(defines.events.on_pre_player_mined_item, on_broken, filter_broken) flib_event.register(defines.events.on_robot_pre_mined, on_broken, filter_broken) flib_event.register(defines.events.on_entity_died, on_broken, filter_broken) flib_event.register(defines.events.script_raised_destroy, on_broken) flib_event.register({defines.events.on_pre_surface_deleted, defines.events.on_pre_surface_cleared}, on_surface_removed) --flib_event.register(defines.events.on_entity_settings_pasted, on_paste) --flib_event.register(defines.events.on_player_cursor_stack_changed, on_cursor_stack_changed) local nth_tick = math.ceil(60/mod_settings.tps); flib_event.on_nth_tick(nth_tick, function() tick(global, mod_settings) end) flib_event.register(defines.events.on_train_created, on_train_built) flib_event.register(defines.events.on_train_changed_state, on_train_changed) flib_event.register(defines.events.on_entity_renamed, on_rename) flib_event.register(defines.events.on_runtime_mod_setting_changed, on_settings_changed) register_gui_actions() 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 main()