420 lines
18 KiB
Lua

--[[ Copyright (c) 2017 Optera
* Part of Logistics Train Network
*
* See LICENSE.md in the project directory for license information.
--]]
-- update stop output when train enters stop
function TrainArrives(train)
local stopID = train.station.unit_number
local stop = global.LogisticTrainStops[stopID]
if stop then
local stop_name = stop.entity.backer_name
-- assign main loco name and force
local loco = Get_Main_Locomotive(train)
local trainForce = nil
local trainName = nil
if loco then
trainName = loco.backer_name
trainForce = loco.force
end
-- add train to global.StoppedTrains
global.StoppedTrains[train.id] = {
train = train,
name = trainName,
force = trainForce,
stopID = stopID,
}
-- add train to global.LogisticTrainStops
stop.parked_train = train
stop.parked_train_id = train.id
local frontDistance = Get_Distance(train.front_stock.position, train.station.position)
local backDistance = Get_Distance(train.back_stock.position, train.station.position)
if frontDistance > backDistance then
stop.parked_train_faces_stop = false
else
stop.parked_train_faces_stop = true
end
local is_provider = false
-- if message_level >= 3 then printmsg({"ltn-message.train-arrived", tostring(trainName), stop_name}, trainForce, false) end
if message_level >= 3 then printmsg({"ltn-message.train-arrived", Make_Train_RichText(train, nil), format("[train-stop=%d]", stopID)}, trainForce, false) end
if debug_log then log(format("(TrainArrives) Train [%d] \"%s\": arrived at LTN-stop [%d] \"%s\"; train_faces_stop: %s", train.id, trainName, stopID, stop_name, stop.parked_train_faces_stop )) end
if stop.error_code == 0 then
if stop.is_depot then
local delivery = global.Dispatcher.Deliveries[train.id]
if delivery then
-- delivery should have been removed when leaving requester. Handle like delivery timeout.
if message_level >= 1 then
printmsg({
"ltn-message.delivery-removed-depot",
Make_Stop_RichText(from_entity) or delivery.from,
Make_Stop_RichText(to_entity) or delivery.to
}, delivery.force, false)
end
if debug_log then log(format("(TrainArrives) Train [%d] \"%s\": Entered Depot with active Delivery. Failing Delivery and reseting train.", train.id, trainName)) end
script.raise_event(on_delivery_failed_event, {train_id = train.id, shipment = delivery.shipment})
RemoveDelivery(train.id)
end
-- clean fluid residue
local train_items = train.get_contents()
local train_fluids = train.get_fluid_contents()
if table_size(train_fluids) > 0 and depot_fluid_cleaning > 0 then
-- cleaning per wagon
for i, wagon in pairs(train.fluid_wagons) do
for fluid, count in pairs(wagon.get_fluid_contents()) do
if count <= depot_fluid_cleaning then
local removed = wagon.remove_fluid({name=fluid, amount=count})
if debug_log then log(format("(TrainArrives) Train \"%s\"[%d]: Depot fluid removal %s %f/%f", trainName, i, fluid, removed, count)) end
end
end
end
-- cleaning whole train doesn't work in 1.1.26
-- for fluid, count in pairs(train_fluids) do
-- if count <= depot_fluid_cleaning then
-- local removed = train.remove_fluid({name=fluid, amount=count})
-- log(format("Train %s: removed %s %f/%f", trainName, fluid, removed, count))
-- end
-- end
train_fluids = train.get_fluid_contents()
end
-- check for leftover cargo
if table_size(train_items) > 0 then
create_alert(stop.entity, "cargo-warning", {"ltn-message.depot_left_over_cargo", trainName, stop_name}, trainForce)
end
if table_size(train_fluids) > 0 then
create_alert(stop.entity, "cargo-warning", {"ltn-message.depot_left_over_cargo", trainName, stop_name}, trainForce)
end
-- make train available for new deliveries
local capacity, fluid_capacity = GetTrainCapacity(train)
global.Dispatcher.availableTrains[train.id] = {
train = train,
surface = loco.surface,
force = trainForce,
depot_priority = stop.depot_priority,
network_id = stop.network_id,
capacity = capacity,
fluid_capacity = fluid_capacity
}
global.Dispatcher.availableTrains_total_capacity = global.Dispatcher.availableTrains_total_capacity + capacity
global.Dispatcher.availableTrains_total_fluid_capacity = global.Dispatcher.availableTrains_total_fluid_capacity + fluid_capacity
-- log("added available train "..train.id..", inventory: "..tostring(global.Dispatcher.availableTrains[train.id].capacity)..", fluid capacity: "..tostring(global.Dispatcher.availableTrains[train.id].fluid_capacity))
-- reset schedule
local schedule = {current = 1, records = {}}
schedule.records[1] = NewScheduleRecord(stop_name, "inactivity", depot_inactivity)
train.schedule = schedule
-- reset filters and bars
if depot_reset_filters and train.cargo_wagons then
for n,wagon in pairs(train.cargo_wagons) do
local inventory = wagon.get_inventory(defines.inventory.cargo_wagon)
if inventory then
if inventory.is_filtered() then
-- log("Cargo-Wagon["..tostring(n).."]: reseting "..tostring(#inventory).." filtered slots.")
for slotIndex=1, #inventory, 1 do
inventory.set_filter(slotIndex, nil)
end
end
if inventory.supports_bar and #inventory - inventory.get_bar() > 0 then
-- log("Cargo-Wagon["..tostring(n).."]: reseting "..tostring(#inventory - inventory.get_bar()).." locked slots.")
inventory.set_bar()
end
end
end
end
setLamp(stop, "blue", 1)
else -- stop is no Depot
-- check requester for incorrect shipment
local delivery = global.Dispatcher.Deliveries[train.id]
if delivery then
is_provider = delivery.from_id == stop.entity.unit_number
if delivery.to_id == stop.entity.unit_number then
local requester_unscheduled_cargo = false
local unscheduled_load = {}
local train_items = train.get_contents()
for name, count in pairs(train_items) do
local typed_name = "item,"..name
if not delivery.shipment[typed_name] then
requester_unscheduled_cargo = true
unscheduled_load[typed_name] = count
end
end
local train_fluids = train.get_fluid_contents()
for name, count in pairs(train_fluids) do
local typed_name = "fluid,"..name
if not delivery.shipment[typed_name] then
requester_unscheduled_cargo = true
unscheduled_load[typed_name] = count
end
end
if requester_unscheduled_cargo then
create_alert(stop.entity, "cargo-alert", {"ltn-message.requester_unscheduled_cargo", trainName, stop_name}, trainForce)
script.raise_event(on_requester_unscheduled_cargo_alert, {train = train, station = stop.entity, planned_shipment = delivery.shipment, unscheduled_load = unscheduled_load})
end
end
end
-- set lamp to blue for LTN controlled trains
for i=1, #stop.active_deliveries, 1 do
if stop.active_deliveries[i] == train.id then
setLamp(stop, "blue", #stop.active_deliveries)
break
end
end
end
end
UpdateStopOutput(stop, is_provider and not(provider_show_existing_cargo) )
end
end
-- update stop output when train leaves stop
-- when called from on_train_created stoppedTrain.train will be invalid
function TrainLeaves(trainID)
local stoppedTrain = global.StoppedTrains[trainID] -- checked before every call of TrainLeaves
local train = stoppedTrain.train
local stopID = stoppedTrain.stopID
local stop = global.LogisticTrainStops[stopID]
if not stop then
if debug_log then log(format("(TrainLeaves) Error: StopID [%d] not found in global.LogisticTrainStops", stopID )) end
global.StoppedTrains[trainID] = nil
return
end
if not stop.entity.valid or not stop.input.valid or not stop.output.valid or not stop.lamp_control.valid then
if debug_log then log(format("(TrainLeaves) Error: StopID [%d] contains invalid entity. Processing skipped, train inventory not updated.", stopID )) end
global.StoppedTrains[trainID] = nil
-- don't call RemoveStop here as RemoveStop calls TrainLeaves again
return
end
local stop_name = stop.entity.backer_name
-- train was stopped at LTN depot
if stop.is_depot then
if global.Dispatcher.availableTrains[trainID] then -- trains are normally removed when deliveries are created
global.Dispatcher.availableTrains_total_capacity = global.Dispatcher.availableTrains_total_capacity - global.Dispatcher.availableTrains[trainID].capacity
global.Dispatcher.availableTrains_total_fluid_capacity = global.Dispatcher.availableTrains_total_fluid_capacity - global.Dispatcher.availableTrains[trainID].fluid_capacity
global.Dispatcher.availableTrains[trainID] = nil
end
if stop.error_code == 0 then
setLamp(stop, "green", 1)
end
if debug_log then log(format("(TrainLeaves) Train [%d] \"%s\": left Depot [%d] \"%s\".", trainID, stoppedTrain.name, stopID, stop.entity.backer_name )) end
-- train was stopped at LTN stop
else
-- remove delivery from stop
for i=#stop.active_deliveries, 1, -1 do
if stop.active_deliveries[i] == trainID then
table.remove(stop.active_deliveries, i)
end
end
local delivery = global.Dispatcher.Deliveries[trainID]
if train.valid and delivery then
if delivery.from_id == stop.entity.unit_number then
-- update delivery counts to train inventory
local actual_load = {}
local unscheduled_load = {}
local provider_unscheduled_cargo = false
local provider_missing_cargo = false
local train_items = train.get_contents()
for name, count in pairs(train_items) do
local typed_name = "item,"..name
local planned_count = delivery.shipment[typed_name]
if planned_count then
actual_load[typed_name] = count -- update shipment to actual inventory
if count < planned_count then
-- underloaded
provider_missing_cargo = true
end
else
-- loaded wrong items
unscheduled_load[typed_name] = count
provider_unscheduled_cargo = true
end
end
local train_fluids = train.get_fluid_contents()
for name, count in pairs(train_fluids) do
local typed_name = "fluid,"..name
local planned_count = delivery.shipment[typed_name]
if planned_count then
actual_load[typed_name] = count -- update shipment actual inventory
if planned_count-count > 0.1 then -- prevent lsb errors
-- underloaded
provider_missing_cargo = true
end
else
-- loaded wrong fluids
unscheduled_load[typed_name] = count
provider_unscheduled_cargo = true
end
end
delivery.pickupDone = true -- remove reservations from this delivery
if debug_log then log(format("(TrainLeaves) Train [%d] \"%s\": left Provider [%d] \"%s\"; cargo: %s; unscheduled: %s ", trainID, stoppedTrain.name, stopID, stop.entity.backer_name, serpent.line(actual_load), serpent.line(unscheduled_load) )) end
global.StoppedTrains[trainID] = nil
if provider_missing_cargo then
create_alert(stop.entity, "cargo-alert", {"ltn-message.provider_missing_cargo", stoppedTrain.name, stop_name}, stoppedTrain.force)
script.raise_event(on_provider_missing_cargo_alert, {
train = train,
station = stop.entity,
planned_shipment = delivery.shipment,
actual_shipment = actual_load
})
end
if provider_unscheduled_cargo then
create_alert(stop.entity, "cargo-alert", {"ltn-message.provider_unscheduled_cargo", stoppedTrain.name, stop_name}, stoppedTrain.force)
script.raise_event(on_provider_unscheduled_cargo_alert, {
train = train,
station = stop.entity,
planned_shipment = delivery.shipment,
unscheduled_load = unscheduled_load
})
end
script.raise_event(on_delivery_pickup_complete_event, {
train_id = trainID,
train = train,
planned_shipment = delivery.shipment,
actual_shipment = actual_load
})
delivery.shipment = actual_load
elseif delivery.to_id == stop.entity.unit_number then
-- reset schedule before API events
if requester_delivery_reset and train.schedule then
local schedule = {current = 1, records = {}}
schedule.records[1] = NewScheduleRecord(train.schedule.records[1].station, "inactivity", depot_inactivity)
train.schedule = schedule
end
local remaining_load = {}
local requester_left_over_cargo = false
local train_items = train.get_contents()
for name, count in pairs(train_items) do
-- not fully unloaded
local typed_name = "item,"..name
requester_left_over_cargo = true
remaining_load[typed_name] = count
end
local train_fluids = train.get_fluid_contents()
for name, count in pairs(train_fluids) do
-- not fully unloaded
local typed_name = "fluid,"..name
requester_left_over_cargo = true
remaining_load[typed_name] = count
end
if debug_log then log(format("(TrainLeaves) Train [%d] \"%s\": left Requester [%d] \"%s\" with left over cargo: %s", trainID, stoppedTrain.name, stopID, stop.entity.backer_name, serpent.line(remaining_load))) end
-- signal completed delivery and remove it
if requester_left_over_cargo then
create_alert(stop.entity, "cargo-alert", {"ltn-message.requester_left_over_cargo", stoppedTrain.name, stop_name}, stoppedTrain.force)
script.raise_event(on_requester_remaining_cargo_alert, {
train = train,
station = stop.entity,
remaining_load = remaining_load
})
end
script.raise_event(on_delivery_completed_event, {
train_id = trainID,
train = train,
shipment = delivery.shipment})
RemoveDelivery(trainID)
else
if debug_log then log(format("(TrainLeaves) Train [%d] \"%s\": left LTN-stop [%d] \"%s\".", trainID, stoppedTrain.name, stopID, stop.entity.backer_name)) end
end
end
if stop.error_code == 0 then
if #stop.active_deliveries > 0 then
setLamp(stop, "yellow", #stop.active_deliveries)
else
setLamp(stop, "green", 1)
end
end
end
-- remove train reference
stop.parked_train = nil
stop.parked_train_id = nil
-- if message_level >= 3 then printmsg({"ltn-message.train-left", tostring(stoppedTrain.name), stop.entity.backer_name}, stoppedTrain.force) end
if message_level >= 3 then printmsg({"ltn-message.train-left", Make_Train_RichText(train, stoppedTrain.name), format("[train-stop=%d]", stopID)}, stoppedTrain.force, false) end
UpdateStopOutput(stop)
global.StoppedTrains[trainID] = nil
end
-- local reverse_defines = require('__flib__.reverse-defines')
function OnTrainStateChanged(event)
-- log(game.tick.." (OnTrainStateChanged) Train name: "..tostring(Get_Train_Name(event.train))..", train.id:"..tostring(event.train.id).." stop: "..tostring(event.train.station and event.train.station.backer_name)..", state: "..reverse_defines.train_state[event.old_state].." > "..reverse_defines.train_state[event.train.state] )
local train = event.train
if train.state == defines.train_state.wait_station and train.station ~= nil and ltn_stop_entity_names[train.station.name] then
TrainArrives(train)
elseif event.old_state == defines.train_state.wait_station and global.StoppedTrains[train.id] then -- update to 0.16
TrainLeaves(train.id)
end
end
-- updates or removes delivery references
function Update_Delivery(old_train_id, new_train)
local delivery = global.Dispatcher.Deliveries[old_train_id]
-- expanded RemoveDelivery(old_train_id) to also update
for stopID, stop in pairs(global.LogisticTrainStops) do
if not stop.entity.valid or not stop.input.valid or not stop.output.valid or not stop.lamp_control.valid then
RemoveStop(stopID)
else
for i=#stop.active_deliveries, 1, -1 do --trainID should be unique => checking matching stop name not required
if stop.active_deliveries[i] == old_train_id then
if delivery then
stop.active_deliveries[i] = new_train.id -- update train id if delivery exists
else
table.remove(stop.active_deliveries, i) -- otherwise remove entry
if #stop.active_deliveries > 0 then
setLamp(stop, "yellow", #stop.active_deliveries)
else
setLamp(stop, "green", 1)
end
end
end
end
end
end
-- copy global.Dispatcher.Deliveries[old_train_id] to new_train.id and change attached train in delivery
if delivery then
delivery.train = new_train
global.Dispatcher.Deliveries[new_train.id] = delivery
end
if global.StoppedTrains[old_train_id] then
TrainLeaves(old_train_id) -- removal only, new train is added when on_train_state_changed fires with wait_station afterwards
end
global.Dispatcher.Deliveries[old_train_id] = nil
return delivery
end
function OnTrainCreated(event)
-- log("(on_train_created) Train name: "..tostring(Get_Train_Name(event.train))..", train.id:"..tostring(event.train.id)..", .old_train_id_1:"..tostring(event.old_train_id_1)..", .old_train_id_2:"..tostring(event.old_train_id_2)..", state: "..tostring(event.train.state))
-- on_train_created always sets train.state to 9 manual, scripts have to set the train back to its former state.
if event.old_train_id_1 then
Update_Delivery(event.old_train_id_1, event.train)
end
if event.old_train_id_2 then
Update_Delivery(event.old_train_id_2, event.train)
end
end