--[[ Copyright (c) 2017 Optera * Part of Logistics Train Network * * See LICENSE.md in the project directory for license information. --]] ---- INITIALIZATION ---- local function initialize(oldVersion, newVersion) --log("oldVersion: "..tostring(oldVersion)..", newVersion: "..tostring(newVersion)) ---- always start with stop updated after a config change, ensure consistent data and filled tables global.tick_state = 0 -- index determining on_tick update mode 0: init, 1: stop update, 2: sort requests, 3: parse requests, 4: raise API update events global.tick_stop_index = nil global.tick_request_index = nil global.tick_interval_start = nil -- stores tick of last state 0 for on_dispatcher_updated_event.update_interval ---- initialize logger global.messageBuffer = {} ---- initialize Dispatcher global.Dispatcher = global.Dispatcher or {} -- set in UpdateAllTrains global.Dispatcher.availableTrains = global.Dispatcher.availableTrains or {} global.Dispatcher.availableTrains_total_capacity = global.Dispatcher.availableTrains_total_capacity or 0 global.Dispatcher.availableTrains_total_fluid_capacity = global.Dispatcher.availableTrains_total_fluid_capacity or 0 global.Dispatcher.Provided = global.Dispatcher.Provided or {} -- dictionary [type,name] used to quickly find available items global.Dispatcher.Provided_by_Stop = global.Dispatcher.Provided_by_Stop or {} -- dictionary [stopID]; used only by interface global.Dispatcher.Requests = global.Dispatcher.Requests or {} -- array of requests sorted by priority and age; used to loop over all requests global.Dispatcher.Requests_by_Stop = global.Dispatcher.Requests_by_Stop or {} -- dictionary [stopID]; used to keep track of already handled requests global.Dispatcher.RequestAge = global.Dispatcher.RequestAge or {} global.Dispatcher.Deliveries = global.Dispatcher.Deliveries or {} ---- initialize stops global.LogisticTrainStops = global.LogisticTrainStops or {} -- table of connections per surface used to decide if providers from another surface are valid sources -- { [surface1.index|surface2.index] = { [entity1.unit_number|entity2.unit_number] = { entity1, entity2, network_id } } -- entity_key_pairs are automatically removed during delivery processing if at least one of the referenced entities becomes invalid global.ConnectedSurfaces = global.ConnectedSurfaces or {} -- clean obsolete global global.Dispatcher.Requested = nil global.Dispatcher.Orders = nil global.Dispatcher.OrderAge = nil global.Dispatcher.Storage = nil global.useRailTanker = nil global.tickCount = nil global.stopIdStartIndex = nil global.Dispatcher.UpdateInterval = nil global.Dispatcher.UpdateStopsPerTick = nil global.TrainStopNames = nil -- update to 1.3.0 if oldVersion and oldVersion < "01.03.00" then for stopID, stop in pairs(global.LogisticTrainStops) do stop.minDelivery = nil stop.ignoreMinDeliverySize = nil end end -- update to 1.5.0 renamed priority to provider_priority if oldVersion and oldVersion < "01.05.00" then for stopID, stop in pairs (global.LogisticTrainStops) do stop.provider_priority = stop.priority or 0 stop.priority = nil end global.Dispatcher.Requests = {} global.Dispatcher.RequestAge = {} end -- update to 1.6.1 migrate locomotiveID to trainID if oldVersion and oldVersion < "01.06.01" then local locoID_to_trainID = {} -- id dictionary local new_availableTrains = {} local new_Deliveries = {} for _,surface in pairs(game.surfaces) do local trains = surface.get_trains() for _, train in pairs(trains) do -- build dictionary local loco = Get_Main_Locomotive(train) if loco then locoID_to_trainID[loco.unit_number] = train.id end end end -- log("locoID_to_trainID: "..serpent.block(locoID_to_trainID)) for locoID, delivery in pairs(global.Dispatcher.Deliveries) do local trainID = locoID_to_trainID[locoID] if trainID then log("Migrating global.Dispatcher.Deliveries from ["..tostring(locoID).."] to ["..tostring(trainID).."]") new_Deliveries[trainID] = delivery end end -- log("new_Deliveries: "..serpent.dump(new_Deliveries)) global.Dispatcher.Deliveries = new_Deliveries end -- update to 1.8.0 if oldVersion and oldVersion < "01.08.00" then for stopID, stop in pairs(global.LogisticTrainStops) do stop.entity.get_or_create_control_behavior().send_to_train = true stop.entity.get_or_create_control_behavior().read_from_train = true end end -- update to 1.12.3 migrate networkID to network_id if oldVersion and oldVersion < "01.12.03" then for train_id, delivery in pairs(global.Dispatcher.Deliveries) do delivery.network_id = delivery.networkID delivery.networkID = nil end end -- update to 1.13.1 renamed almost all stop properties if oldVersion and oldVersion < "01.13.01" and next(global.LogisticTrainStops) then for stopID, stop in pairs(global.LogisticTrainStops) do stop.lamp_control = stop.lamp_control or stop.lampControl stop.lampControl = nil stop.error_code = stop.error_code or stop.errorCode or -1 stop.errorCode = nil stop.active_deliveries = stop.active_deliveries or stop.activeDeliveries or {} stop.activeDeliveries = nil -- control signals stop.is_depot = stop.is_depot or stop.isDepot or false stop.isDepot = nil stop.depot_priority = stop.depot_priority or 0 stop.max_carriages = stop.max_carriages or stop.maxTraincars or 0 stop.maxTraincars = nil stop.min_carriages = stop.min_carriages or stop.minTraincars or 0 stop.minTraincars = nil stop.max_trains = stop.max_trains or stop.trainLimit or 0 stop.trainLimit = nil stop.providing_threshold = stop.providing_threshold or stop.provideThreshold or min_provided stop.provideThreshold = nil stop.providing_threshold_stacks = stop.providing_threshold_stacks or stop.provideStackThreshold or 0 stop.provideStackThreshold = nil stop.provider_priority = stop.provider_priority or stop.providePriority or 0 stop.providePriority = nil stop.requesting_threshold = stop.requesting_threshold or stop.requestThreshold or min_requested stop.requestThreshold = nil stop.requesting_threshold_stacks = stop.requesting_threshold_stacks or stop.requestStackThreshold or 0 stop.requestStackThreshold = nil stop.requester_priority = stop.requester_priority or stop.requestPriority or 0 stop.requestPriority = nil stop.locked_slots = stop.locked_slots or stop.lockedSlots or 0 stop.lockedSlots = nil stop.no_warnings = stop.no_warnings or stop.noWarnings or false stop.noWarnings = nil -- parked train data will be set during initializeTrainStops() and updateAllTrains() stop.parkedTrain = nil stop.parkedTrainID = nil stop.parkedTrainFacesStop = nil end end -- update to 1.9.4 if oldVersion and oldVersion < "01.09.04" then for stopID, stop in pairs(global.LogisticTrainStops) do stop.lamp_control.teleport({stop.input.position.x, stop.input.position.y}) -- move control under lamp stop.input.disconnect_neighbour({target_entity=stop.lamp_control, wire=defines.wire_type.green}) -- reconnect wires stop.input.disconnect_neighbour({target_entity=stop.lamp_control, wire=defines.wire_type.red}) stop.input.connect_neighbour({target_entity=stop.lamp_control, wire=defines.wire_type.green}) stop.input.connect_neighbour({target_entity=stop.lamp_control, wire=defines.wire_type.red}) end end end -- run every time the mod configuration is changed to catch stops from other mods -- ensures global.LogisticTrainStops contains valid entities local function initializeTrainStops() global.LogisticTrainStops = global.LogisticTrainStops or {} -- remove invalidated stops for stopID, stop in pairs (global.LogisticTrainStops) do if not stop then log("[LTN] removing empty stop entry "..tostring(stopID) ) global.LogisticTrainStops[stopID] = nil elseif not(stop.entity and stop.entity.valid) then -- stop entity is corrupt/missing remove I/O entities log("[LTN] removing corrupt stop "..tostring(stopID) ) if stop.input and stop.input.valid then stop.input.destroy() end if stop.output and stop.output.valid then stop.output.destroy() end if stop.lamp_control and stop.lamp_control.valid then stop.lamp_control.destroy() end global.LogisticTrainStops[stopID] = nil end end -- add missing ltn stops for _, surface in pairs(game.surfaces) do local foundStops = surface.find_entities_filtered{type="train-stop"} if foundStops then for k, stop in pairs(foundStops) do -- validate global.LogisticTrainStops if ltn_stop_entity_names[stop.name] then local ltn_stop = global.LogisticTrainStops[stop.unit_number] if ltn_stop then if not(ltn_stop.output and ltn_stop.output.valid and ltn_stop.input and ltn_stop.input.valid and ltn_stop.lamp_control and ltn_stop.lamp_control.valid) then -- I/O entities are corrupted log("[LTN] recreating corrupt stop "..tostring(stop.backer_name) ) global.LogisticTrainStops[stop.unit_number] = nil CreateStop(stop) -- recreate to spawn missing I/O entities end else log("[LTN] recreating stop missing from global.LogisticTrainStops "..tostring(stop.backer_name) ) CreateStop(stop) -- recreate LTN stops missing from global.LogisticTrainStops end end end end end end -- run every time the mod configuration is changed to catch changes to wagon capacities by other mods local function updateAllTrains() -- reset global lookup tables global.StoppedTrains = {} -- trains stopped at LTN stops global.StopDistances = {} -- reset station distance lookup table global.WagonCapacity = { --preoccupy table with wagons to ignore at 0 capacity ["rail-tanker"] = 0 } global.Dispatcher.availableTrains_total_capacity = 0 global.Dispatcher.availableTrains_total_fluid_capacity = 0 global.Dispatcher.availableTrains = {} -- remove all parked train from logistic stops for stopID, stop in pairs (global.LogisticTrainStops) do stop.parked_train = nil stop.parked_train_id = nil UpdateStopOutput(stop) end -- add still valid trains back to stops for force_name, force in pairs(game.forces) do local trains = force.get_trains() if trains then for _, train in pairs(trains) do if train.station and ltn_stop_entity_names[train.station.name] then TrainArrives(train) end end end end end -- register events local function registerEvents() local filters_on_built = {{ filter="type", type="train-stop" }} local filters_on_mined = {{ filter="type", type="train-stop" }, { filter="rolling-stock" }} -- always track built/removed train stops for duplicate name list script.on_event( defines.events.on_built_entity, OnEntityCreated, filters_on_built ) script.on_event( defines.events.on_robot_built_entity, OnEntityCreated, filters_on_built ) script.on_event( {defines.events.script_raised_built, defines.events.script_raised_revive, defines.events.on_entity_cloned}, OnEntityCreated ) script.on_event( defines.events.on_pre_player_mined_item, OnEntityRemoved, filters_on_mined ) script.on_event( defines.events.on_robot_pre_mined, OnEntityRemoved, filters_on_mined ) script.on_event( defines.events.on_entity_died, function(event) OnEntityRemoved(event, true) end, filters_on_mined ) script.on_event( defines.events.script_raised_destroy, OnEntityRemoved ) script.on_event( {defines.events.on_pre_surface_deleted, defines.events.on_pre_surface_cleared }, OnSurfaceRemoved ) if global.LogisticTrainStops and next(global.LogisticTrainStops) then -- script.on_event(defines.events.on_tick, OnTick) script.on_nth_tick(nil) script.on_nth_tick(dispatcher_nth_tick, OnTick) script.on_event(defines.events.on_train_changed_state, OnTrainStateChanged) script.on_event(defines.events.on_train_created, OnTrainCreated) end -- disable instant blueprint in creative mode if remote.interfaces["creative-mode"] and remote.interfaces["creative-mode"]["exclude_from_instant_blueprint"] then remote.call("creative-mode", "exclude_from_instant_blueprint", ltn_stop_input) remote.call("creative-mode", "exclude_from_instant_blueprint", ltn_stop_output) remote.call("creative-mode", "exclude_from_instant_blueprint", ltn_stop_output_controller) end -- blacklist LTN entities from picker dollies if remote.interfaces["PickerDollies"] and remote.interfaces["PickerDollies"]["add_blacklist_name"] then for name, offset in pairs(ltn_stop_entity_names) do remote.call("PickerDollies", "add_blacklist_name", name, true) end remote.call("PickerDollies", "add_blacklist_name", ltn_stop_input, true) remote.call("PickerDollies", "add_blacklist_name", ltn_stop_output, true) remote.call("PickerDollies", "add_blacklist_name", ltn_stop_output_controller, true) end end script.on_load(function() registerEvents() end) script.on_init(function() -- format version string to "00.00.00" local oldVersion, newVersion = nil local newVersionString = game.active_mods[MOD_NAME] if newVersionString then newVersion = format("%02d.%02d.%02d", match(newVersionString, "(%d+).(%d+).(%d+)")) end initialize(oldVersion, newVersion) initializeTrainStops() updateAllTrains() registerEvents() log("[LTN] ".. MOD_NAME.." "..tostring(newVersionString).." initialized.") end) script.on_configuration_changed(function(data) if data and data.mod_changes[MOD_NAME] then -- format version string to "00.00.00" local oldVersion, newVersion = nil local oldVersionString = data.mod_changes[MOD_NAME].old_version if oldVersionString then oldVersion = format("%02d.%02d.%02d", match(oldVersionString, "(%d+).(%d+).(%d+)")) end local newVersionString = data.mod_changes[MOD_NAME].new_version if newVersionString then newVersion = format("%02d.%02d.%02d", match(newVersionString, "(%d+).(%d+).(%d+)")) end if oldVersion and oldVersion < "01.01.01" then log("[LTN] Migration failed. Migrating from "..tostring(oldVersionString).." to "..tostring(newVersionString).."not supported.") printmsg("[LTN] Error: Direct migration from "..tostring(oldVersionString).." to "..tostring(newVersionString).." is not supported. Oldest supported version: 1.1.1") return else initialize(oldVersion, newVersion) log("[LTN] Migrating from "..tostring(oldVersionString).." to "..tostring(newVersionString).." complete.") printmsg("[LTN] Migration from "..tostring(oldVersionString).." to "..tostring(newVersionString).." complete.") end end initializeTrainStops() updateAllTrains() registerEvents() log("[LTN] ".. MOD_NAME.." "..tostring(game.active_mods[MOD_NAME]).." configuration updated.") end)