142 lines
6.2 KiB
Lua

--[[ Copyright (c) 2017 Optera
* Part of Logistics Train Network
*
* See LICENSE.md in the project directory for license information.
--]]
---Finds the next logistic stop in the schedule of the given train. Returns nil if the train is not executing a delivery or has no further logistic stops in its schedule.
---@param train LuaTrain
---@param schedule_index integer? the index in the schedule to search from, `schedule.current` if omitted. Starts from the next position if the train is currently stopping at that station.
---@return integer? schedule_index the index of next logistic stop in the schedule or nil
---@return integer? id the unit_number of the logistic stop
---@return "provider"|"requester"|nil type
function GetNextLogisticStop(train, schedule_index)
if not (train and train.valid) then
if debug_log then log("(GetNextLogisticStop) train not valid") end
return
end
if not train.schedule then
if debug_log then log(format("(GetNextLogisticStop) train [%d] has no schedule.", train.id)) end
return
end
local delivery = global.Dispatcher.Deliveries[train.id]
if not delivery then
if debug_log then log(format("(GetNextLogisticStop) train [%d] not found in deliveries.", train.id)) end
return
end
local item = next(delivery.shipment)
if not item then
-- this can happen when the train was unable to load anything at the provider
if debug_log then log(format("(GetNextLogisticStop) train [%d] no longer has a shipment list.", train.id)) end
return
end
-- Comparing stop names is not enough to find the provider and the requester,
-- they might share names with each other or another stop in the schedule.
-- So use a heuristic that also looks at the wait conditions
local itype, iname = match(item, match_string)
local records = train.schedule.records
local record_index = schedule_index or train.schedule.current or 2 -- defaulting to 1 is pointless because that's the depot
if record_index == train.schedule.current and train.state == defines.train_state.wait_station then
record_index = record_index + 1
end
local function get_wait_count_comparator(record)
if record.wait_conditions then
for _, wait_condition in pairs(record.wait_conditions) do
local condition = wait_condition.condition
if condition and condition.constant and (wait_condition.type == "item_count" or wait_condition.type == "fluid_count") then
local signal = condition.first_signal
return signal and signal.type == itype and signal.name == iname and condition.comparator
end
end
end
end
local record = records[record_index]
while record do
if record.station == delivery.from and get_wait_count_comparator(record) == "" then
return record_index, delivery.from_id, "provider"
end
if record.station == delivery.to and get_wait_count_comparator(record) == "=" then
return record_index, delivery.to_id, "requester"
end
record_index = record_index + 1
record = records[record_index]
end
end
local temp_wait_condition = {{type = "time", compare_type = "and", ticks = 0}}
---Ensures the next logistic stop in the schedule has a temporary stop if is on the same surface as the train.
---@param train LuaTrain
---@param schedule_index integer? the index in the schedule to search from, `schedule.current` if omitted. Starts from the next index if the train is currently stopping at that station.
---@return integer? stop_position index of created or existing temporary stop for next found logistic stop that was handled, nil if there is no further logistic stop or the next logistic stop is not on the same surface.
function GetOrCreateNextTempStop(train, schedule_index)
local stop_schedule_index, stop_id = GetNextLogisticStop(train, schedule_index)
if not stop_schedule_index then return end
--unlike ProcessDelivery we need to consider that the stop entity might be gone
local stop = global.LogisticTrainStops[stop_id]
if not stop or not stop.entity.valid then
if debug_log then log(format("(UpdateSchedule) skipping stop [%d] for train [%d], stop-entity not valid", stop_id, train.id)) end
return
end
local rail = stop.entity.connected_rail
local rail_direction = stop.entity.connected_rail_direction
if not rail or not rail_direction then
if debug_log then log(format("(UpdateSchedule) skipping stop [%d] for train [%d], not connected to a rail", stop_id, train.id)) end
return
end
-- the engine does not allow temp_stops on different surfaces
-- locomotive might not work here, a new train on another surface could still be incomplete
if train.carriages[1].surface ~= stop.entity.surface then
if debug_log then log(format("(UpdateSchedule) stop [%d] is on a different surface than train [%d]", stop_id, train.id)) end
return
end
-- insert temp stop in schedule
local schedule = train.schedule
local previous_record = schedule.records[stop_schedule_index-1]
if previous_record and previous_record.temporary then return stop_schedule_index-1 end -- schedule already up-to-date for stop_position
if debug_log then log(format("(UpdateSchedule) adding new temp-stop before stop [%d] at rail [%d] to train [%d] ", stop_id, rail.unit_number, train.id)) end
table.insert(schedule.records, stop_schedule_index, {
wait_conditions = temp_wait_condition,
rail = rail,
rail_direction = rail_direction,
temporary = true,
})
train.schedule = schedule
return stop_schedule_index
end
---reassigns an existing delivery from one train to another
---@param old_train_id integer
---@param new_train LuaTrain
---@return boolean reassigned true if the old train was executing a delivery, false otherwise
function ReassignDelivery(old_train_id, new_train)
-- check if delivery exists for given train id
if not (old_train_id and global.Dispatcher.Deliveries[old_train_id]) then
if debug_log then log(format("(ReassignDelivery) train [%d] not found in deliveries.", old_train_id)) end
return false
end
-- check if new train is valid
if not (new_train and new_train.valid and new_train.object_name == "LuaTrain") then
if debug_log then log("(ReassignDelivery) Received new_train was invalid.") end
return false
end
local delivery = Update_Delivery(old_train_id, new_train)
return delivery and true
end