345 lines
15 KiB
Lua

--[[ 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)