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