static bool Prefix(
                UnusedTrainCarDeleter __instance,
                List <TrainCar> ___unusedTrainCarsMarkedForDelete,
                Dictionary <TrainCar, CarVisitChecker> ___carVisitCheckersMap)
            {
                if (Main.modEntry.Active)
                {
                    try
                    {
                        if (___unusedTrainCarsMarkedForDelete.Count == 0)
                        {
                            return(false);
                        }

                        Debug.Log("[PersistentJobs] collecting deletion candidates...");
                        List <TrainCar> trainCarsToDelete = new List <TrainCar>();
                        for (int i = ___unusedTrainCarsMarkedForDelete.Count - 1; i >= 0; i--)
                        {
                            TrainCar trainCar = ___unusedTrainCarsMarkedForDelete[i];
                            if (trainCar == null)
                            {
                                ___unusedTrainCarsMarkedForDelete.RemoveAt(i);
                                continue;
                            }
                            bool areDeleteConditionsFulfilled = Traverse.Create(__instance)
                                                                .Method("AreDeleteConditionsFulfilled", new Type[] { typeof(TrainCar) })
                                                                .GetValue <bool>(trainCar);
                            if (areDeleteConditionsFulfilled)
                            {
                                ___unusedTrainCarsMarkedForDelete.RemoveAt(i);
                                trainCarsToDelete.Add(trainCar);
                            }
                        }
                        Debug.Log(
                            $"[PersistentJobs] found {trainCarsToDelete.Count} cars marked for deletion");
                        if (trainCarsToDelete.Count == 0)
                        {
                            return(false);
                        }

                        // ------ BEGIN JOB GENERATION ------
                        // group trainCars by trainset
                        Debug.Log("[PersistentJobs] grouping trainCars by trainSet...");
                        List <TrainCar> paxTrainCars = trainCarsToDelete
                                                       .Where(tc => Utilities.IsPassengerCar(tc.carType))
                                                       .ToList();
                        List <TrainCar> nonLocoOrPaxTrainCars = trainCarsToDelete
                                                                .Where(tc => !CarTypes.IsAnyLocomotiveOrTender(tc.carType) && !Utilities.IsPassengerCar(tc.carType))
                                                                .ToList();
                        List <TrainCar> emptyFreightCars = nonLocoOrPaxTrainCars
                                                           .Where(tc => tc.logicCar.CurrentCargoTypeInCar == CargoType.None ||
                                                                  tc.logicCar.LoadedCargoAmount < 0.001f)
                                                           .ToList();
                        List <TrainCar> loadedFreightTrainCars = nonLocoOrPaxTrainCars
                                                                 .Where(tc => tc.logicCar.CurrentCargoTypeInCar != CargoType.None &&
                                                                        tc.logicCar.LoadedCargoAmount >= 0.001f)
                                                                 .ToList();
                        var paxTrainCarsPerTrainSet    = JobProceduralGenerationUtilities.GroupTrainCarsByTrainset(paxTrainCars);
                        var emptyTrainCarsPerTrainSet  = JobProceduralGenerationUtilities.GroupTrainCarsByTrainset(emptyFreightCars);
                        var loadedTrainCarsPerTrainSet = JobProceduralGenerationUtilities.GroupTrainCarsByTrainset(loadedFreightTrainCars);
                        Debug.Log(
                            $"[PersistentJobs]\n" +
                            $"    found {paxTrainCarsPerTrainSet.Count} passenger trainSets,\n" +
                            $"    {emptyTrainCarsPerTrainSet.Count} empty trainSets,\n" +
                            $"    and {loadedTrainCarsPerTrainSet.Count} loaded trainSets");

                        // group trainCars sets by nearest stationController
                        Debug.Log("[PersistentJobs] grouping trainSets by nearest station...");
                        var paxTcsPerSc = JobProceduralGenerationUtilities.GroupTrainCarSetsByNearestStation(paxTrainCarsPerTrainSet);
                        Dictionary <StationController, List <(List <TrainCar>, List <CargoGroup>)> > emptyCgsPerTcsPerSc
                            = JobProceduralGenerationUtilities.GroupTrainCarSetsByNearestStation(emptyTrainCarsPerTrainSet);
                        Dictionary <StationController, List <(List <TrainCar>, List <CargoGroup>)> > loadedCgsPerTcsPerSc
                            = JobProceduralGenerationUtilities.GroupTrainCarSetsByNearestStation(loadedTrainCarsPerTrainSet);
                        Debug.Log(
                            $"[PersistentJobs]\n" +
                            $"    found {paxTcsPerSc.Count} stations for passenger trainSets,\n" +
                            $"    {emptyCgsPerTcsPerSc.Count} stations for empty trainSets,\n" +
                            $"    and {loadedCgsPerTcsPerSc.Count} stations for loaded trainSets");

                        // populate possible cargoGroups per group of trainCars
                        Debug.Log("[PersistentJobs] populating cargoGroups...");
                        JobProceduralGenerationUtilities.PopulateCargoGroupsPerTrainCarSet(emptyCgsPerTcsPerSc);
                        JobProceduralGenerationUtilities.PopulateCargoGroupsPerLoadedTrainCarSet(loadedCgsPerTcsPerSc);
                        Dictionary <StationController, List <List <TrainCar> > > emptyTcsPerSc
                            = JobProceduralGenerationUtilities.ExtractEmptyHaulTrainSets(emptyCgsPerTcsPerSc);

                        // pick new jobs for the trainCars at each station
                        Debug.Log("[PersistentJobs] picking jobs...");
                        System.Random rng = new System.Random(Environment.TickCount);
                        List <(StationController, List <CarsPerTrack>, StationController, List <TrainCar>, List <CargoType>)>
                        shuntingLoadJobInfos = ShuntingLoadJobProceduralGenerator
                                               .ComputeJobInfosFromCargoGroupsPerTrainCarSetPerStation(emptyCgsPerTcsPerSc, rng);
                        List <(StationController, Track, StationController, List <TrainCar>, List <CargoType>)>
                        transportJobInfos = TransportJobProceduralGenerator
                                            .ComputeJobInfosFromCargoGroupsPerTrainCarSetPerStation(
                            loadedCgsPerTcsPerSc.Select(kv => (
                                                            kv.Key,
                                                            kv.Value.Where(tpl => {
                            CargoGroup cg0 = tpl.Item2.FirstOrDefault();
                            return(cg0 != null && kv.Key.proceduralJobsRuleset.outputCargoGroups.Contains(cg0));
                        }).ToList()))
                            .Where(tpl => tpl.Item2.Count > 0)
                            .ToDictionary(tpl => tpl.Item1, tpl => tpl.Item2),
                            rng);
                        List <(StationController, Track, StationController, List <TrainCar>, List <CargoType>)>
                        shuntingUnloadJobInfos = ShuntingUnloadJobProceduralGenerator
                                                 .ComputeJobInfosFromCargoGroupsPerTrainCarSetPerStation(
                            loadedCgsPerTcsPerSc.Select(kv => (
                                                            kv.Key,
                                                            kv.Value.Where(tpl => {
                            CargoGroup cg0 = tpl.Item2.FirstOrDefault();
                            return(cg0 != null && kv.Key.proceduralJobsRuleset.inputCargoGroups.Contains(cg0));
                        }).ToList()))
                            .Where(tpl => tpl.Item2.Count > 0)
                            .ToDictionary(tpl => tpl.Item1, tpl => tpl.Item2),
                            rng);
                        Debug.Log(
                            $"[PersistentJobs]\n" +
                            $"    chose {shuntingLoadJobInfos.Count} shunting load jobs,\n" +
                            $"    {transportJobInfos.Count} transport jobs,\n" +
                            $"    {shuntingUnloadJobInfos.Count} shunting unload jobs,\n" +
                            $"    and {emptyTcsPerSc.Aggregate(0, (acc, kv) => acc + kv.Value.Count)} empty haul jobs");

                        // try to generate jobs
                        Debug.Log("[PersistentJobs] generating jobs...");
                        List <JobChainController>        paxJobChainControllers = JobProceduralGenerationUtilities.TryToGeneratePassengerJobs(paxTcsPerSc);
                        IEnumerable <JobChainController> shuntingLoadJobChainControllers
                            = ShuntingLoadJobProceduralGenerator.doJobGeneration(shuntingLoadJobInfos, rng);
                        IEnumerable <JobChainController> transportJobChainControllers
                            = TransportJobProceduralGenerator.doJobGeneration(transportJobInfos, rng);
                        IEnumerable <JobChainController> shuntingUnloadJobChainControllers
                            = ShuntingUnloadJobProceduralGenerator.doJobGeneration(shuntingUnloadJobInfos, rng);
                        IEnumerable <JobChainController> emptyHaulJobChainControllers = emptyTcsPerSc.Aggregate(
                            new List <JobChainController>(),
                            (list, kv) =>
                        {
                            list.AddRange(
                                kv.Value.Select(tcs => EmptyHaulJobProceduralGenerator
                                                .GenerateEmptyHaulJobWithExistingCars(kv.Key, tcs[0].logicCar.CurrentTrack, tcs, rng)));
                            return(list);
                        });
                        Debug.Log(
                            $"[PersistentJobs]\n" +
                            $"    generated {paxJobChainControllers.Where(jcc => jcc != null).Count()} passenger jobs,\n" +
                            $"    {shuntingLoadJobChainControllers.Where(jcc => jcc != null).Count()} shunting load jobs,\n" +
                            $"    {transportJobChainControllers.Where(jcc => jcc != null).Count()} transport jobs,\n" +
                            $"    {shuntingUnloadJobChainControllers.Where(jcc => jcc != null).Count()} shunting unload jobs,\n" +
                            $"    and {emptyHaulJobChainControllers.Where(jcc => jcc != null).Count()} empty haul jobs");

                        // finalize jobs & preserve job train cars
                        Debug.Log("[PersistentJobs] finalizing jobs...");
                        int totalCarsPreserved = 0;
                        foreach (var jcc in paxJobChainControllers)
                        {
                            if (jcc != null)
                            {
                                jcc.trainCarsForJobChain.ForEach(tc =>
                                {
                                    trainCarsToDelete.Remove(tc);
                                });
                                totalCarsPreserved += jcc.trainCarsForJobChain.Count;
                                // generation has already taken care of converting player spawned cars and finalizing the job chain
                            }
                        }
                        foreach (JobChainController jcc in shuntingLoadJobChainControllers)
                        {
                            if (jcc != null)
                            {
                                jcc.trainCarsForJobChain.ForEach(tc =>
                                {
                                    // force job's train cars to not be treated as player spawned
                                    // DV will complain if we don't do this
                                    Utilities.ConvertPlayerSpawnedTrainCar(tc);
                                    trainCarsToDelete.Remove(tc);
                                });
                                totalCarsPreserved += jcc.trainCarsForJobChain.Count;
                                jcc.FinalizeSetupAndGenerateFirstJob();
                            }
                        }
                        foreach (JobChainController jcc in transportJobChainControllers)
                        {
                            if (jcc != null)
                            {
                                jcc.trainCarsForJobChain.ForEach(tc =>
                                {
                                    // force job's train cars to not be treated as player spawned
                                    // DV will complain if we don't do this
                                    Utilities.ConvertPlayerSpawnedTrainCar(tc);
                                    trainCarsToDelete.Remove(tc);
                                });
                                totalCarsPreserved += jcc.trainCarsForJobChain.Count;
                                jcc.FinalizeSetupAndGenerateFirstJob();
                            }
                        }
                        foreach (JobChainController jcc in shuntingUnloadJobChainControllers)
                        {
                            if (jcc != null)
                            {
                                jcc.trainCarsForJobChain.ForEach(tc =>
                                {
                                    // force job's train cars to not be treated as player spawned
                                    // DV will complain if we don't do this
                                    Utilities.ConvertPlayerSpawnedTrainCar(tc);
                                    trainCarsToDelete.Remove(tc);
                                });
                                totalCarsPreserved += jcc.trainCarsForJobChain.Count;
                                jcc.FinalizeSetupAndGenerateFirstJob();
                            }
                        }
                        foreach (JobChainController jcc in emptyHaulJobChainControllers)
                        {
                            if (jcc != null)
                            {
                                jcc.trainCarsForJobChain.ForEach(tc =>
                                {
                                    // force job's train cars to not be treated as player spawned
                                    // DV will complain if we don't do this
                                    Utilities.ConvertPlayerSpawnedTrainCar(tc);
                                    trainCarsToDelete.Remove(tc);
                                });
                                totalCarsPreserved += jcc.trainCarsForJobChain.Count;
                                jcc.FinalizeSetupAndGenerateFirstJob();
                            }
                        }

                        // preserve all trainCars that are not locos
                        Debug.Log("[PersistentJobs] preserving cars...");
                        foreach (TrainCar tc in new List <TrainCar>(trainCarsToDelete))
                        {
                            if (tc.playerSpawnedCar || !CarTypes.IsAnyLocomotiveOrTender(tc.carType))
                            {
                                trainCarsToDelete.Remove(tc);
                                ___unusedTrainCarsMarkedForDelete.Add(tc);
                                totalCarsPreserved += 1;
                            }
                        }
                        Debug.Log($"[PersistentJobs] preserved {totalCarsPreserved} cars");
                        // ------ END JOB GENERATION ------

                        Debug.Log("[PersistentJobs] deleting cars...");
                        foreach (TrainCar tc in trainCarsToDelete)
                        {
                            ___unusedTrainCarsMarkedForDelete.Remove(tc);
                            ___carVisitCheckersMap.Remove(tc);
                        }
                        SingletonBehaviour <CarSpawner> .Instance.DeleteTrainCars(trainCarsToDelete, true);

                        Debug.Log($"[PersistentJobs] deleted {trainCarsToDelete.Count} cars");
                        return(false);
                    }
                    catch (Exception e)
                    {
                        Main.modEntry.Logger.Error(
                            $"Exception thrown during {"UnusedTrainCarDeleter"}.{"InstantConditionalDeleteOfUnusedCars"} {"prefix"} patch:" +
                            $"\n{e.ToString()}");
                        Main.OnCriticalFailure();
                    }
                }
                return(true);
            }
        // override/replacement for UnusedTrainCarDeleter.TrainCarsDeleteCheck coroutine
        // tries to generate new jobs for the train cars marked for deletion
        public static IEnumerator TrainCarsCreateJobOrDeleteCheck(float period, float interopPeriod)
        {
            List <TrainCar> trainCarsToDelete              = null;
            List <TrainCar> trainCarCandidatesForDelete    = null;
            Traverse        unusedTrainCarDeleterTraverser = null;
            List <TrainCar> unusedTrainCarsMarkedForDelete = null;
            Dictionary <TrainCar, DV.CarVisitChecker> carVisitCheckersMap = null;
            Traverse AreDeleteConditionsFulfilledMethod = null;

            try
            {
                trainCarsToDelete              = new List <TrainCar>();
                trainCarCandidatesForDelete    = new List <TrainCar>();
                unusedTrainCarDeleterTraverser = Traverse.Create(SingletonBehaviour <UnusedTrainCarDeleter> .Instance);
                unusedTrainCarsMarkedForDelete = unusedTrainCarDeleterTraverser
                                                 .Field("unusedTrainCarsMarkedForDelete")
                                                 .GetValue <List <TrainCar> >();
                carVisitCheckersMap = unusedTrainCarDeleterTraverser
                                      .Field("carVisitCheckersMap")
                                      .GetValue <Dictionary <TrainCar, DV.CarVisitChecker> >();
                AreDeleteConditionsFulfilledMethod
                    = unusedTrainCarDeleterTraverser.Method("AreDeleteConditionsFulfilled", new Type[] { typeof(TrainCar) });
            }
            catch (Exception e)
            {
                Main.modEntry.Logger.Error(
                    $"Exception thrown during TrainCarsCreateJobOrDeleteCheck setup:\n{e.ToString()}");
                Main.OnCriticalFailure();
            }
            for (; ;)
            {
                yield return(WaitFor.SecondsRealtime(period));

                try
                {
                    if (PlayerManager.PlayerTransform == null || FastTravelController.IsFastTravelling)
                    {
                        continue;
                    }

                    if (unusedTrainCarsMarkedForDelete.Count == 0)
                    {
                        if (carVisitCheckersMap.Count != 0)
                        {
                            carVisitCheckersMap.Clear();
                        }
                        continue;
                    }
                }
                catch (Exception e)
                {
                    Main.modEntry.Logger.Error(
                        $"Exception thrown during TrainCarsCreateJobOrDeleteCheck skip checks:\n{e.ToString()}");
                    Main.OnCriticalFailure();
                }

                Debug.Log("[PersistentJobs] collecting deletion candidates... (coroutine)");
                try
                {
                    trainCarCandidatesForDelete.Clear();
                    for (int i = unusedTrainCarsMarkedForDelete.Count - 1; i >= 0; i--)
                    {
                        TrainCar trainCar = unusedTrainCarsMarkedForDelete[i];
                        if (trainCar == null)
                        {
                            unusedTrainCarsMarkedForDelete.RemoveAt(i);
                        }
                        else if (AreDeleteConditionsFulfilledMethod.GetValue <bool>(trainCar))
                        {
                            unusedTrainCarsMarkedForDelete.RemoveAt(i);
                            trainCarCandidatesForDelete.Add(trainCar);
                        }
                    }
                    Debug.Log(
                        $"[PersistentJobs] found {trainCarCandidatesForDelete.Count} cars marked for deletion (coroutine)");
                    if (trainCarCandidatesForDelete.Count == 0)
                    {
                        continue;
                    }
                }
                catch (Exception e)
                {
                    Main.modEntry.Logger.Error(
                        $"Exception thrown during TrainCarsCreateJobOrDeleteCheck delete candidate collection:\n{e.ToString()}");
                    Main.OnCriticalFailure();
                }

                yield return(WaitFor.SecondsRealtime(interopPeriod));

                // ------ BEGIN JOB GENERATION ------
                // group trainCars by trainset
                Debug.Log("[PersistentJobs] grouping trainCars by trainSet... (coroutine)");
                Dictionary <Trainset, List <TrainCar> > paxTrainCarsPerTrainSet    = null;
                Dictionary <Trainset, List <TrainCar> > emptyTrainCarsPerTrainSet  = null;
                Dictionary <Trainset, List <TrainCar> > loadedTrainCarsPerTrainSet = null;
                try
                {
                    List <TrainCar> paxTrainCars = trainCarCandidatesForDelete
                                                   .Where(tc => Utilities.IsPassengerCar(tc.carType))
                                                   .ToList();
                    List <TrainCar> nonLocoOrPaxTrainCars = trainCarCandidatesForDelete
                                                            .Where(tc => !CarTypes.IsAnyLocomotiveOrTender(tc.carType) && !Utilities.IsPassengerCar(tc.carType))
                                                            .ToList();
                    List <TrainCar> emptyFreightCars = nonLocoOrPaxTrainCars
                                                       .Where(tc => tc.logicCar.CurrentCargoTypeInCar == CargoType.None ||
                                                              tc.logicCar.LoadedCargoAmount < 0.001f)
                                                       .ToList();
                    List <TrainCar> loadedFreightCars = nonLocoOrPaxTrainCars
                                                        .Where(tc => tc.logicCar.CurrentCargoTypeInCar != CargoType.None &&
                                                               tc.logicCar.LoadedCargoAmount >= 0.001f)
                                                        .ToList();

                    paxTrainCarsPerTrainSet = JobProceduralGenerationUtilities
                                              .GroupTrainCarsByTrainset(paxTrainCars);
                    emptyTrainCarsPerTrainSet = JobProceduralGenerationUtilities
                                                .GroupTrainCarsByTrainset(emptyFreightCars);
                    loadedTrainCarsPerTrainSet = JobProceduralGenerationUtilities
                                                 .GroupTrainCarsByTrainset(loadedFreightCars);
                }
                catch (Exception e)
                {
                    Main.modEntry.Logger.Error(
                        $"Exception thrown during TrainCarsCreateJobOrDeleteCheck trainset grouping:\n{e.ToString()}");
                    Main.OnCriticalFailure();
                }
                Debug.Log(
                    $"[PersistentJobs]\n" +
                    $"    found {paxTrainCarsPerTrainSet.Count} passenger trainSets,\n" +
                    $"    {emptyTrainCarsPerTrainSet.Count} empty trainSets,\n" +
                    $"    and {loadedTrainCarsPerTrainSet.Count} loaded trainSets (coroutine)");

                yield return(WaitFor.SecondsRealtime(interopPeriod));

                // group trainCars sets by nearest stationController
                Debug.Log("[PersistentJobs] grouping trainSets by nearest station... (coroutine)");
                Dictionary <StationController, List <(List <TrainCar>, List <CargoGroup>)> > paxTcsPerSc          = null;
                Dictionary <StationController, List <(List <TrainCar>, List <CargoGroup>)> > emptyCgsPerTcsPerSc  = null;
                Dictionary <StationController, List <(List <TrainCar>, List <CargoGroup>)> > loadedCgsPerTcsPerSc = null;
                try
                {
                    paxTcsPerSc = JobProceduralGenerationUtilities
                                  .GroupTrainCarSetsByNearestStation(paxTrainCarsPerTrainSet);
                    emptyCgsPerTcsPerSc = JobProceduralGenerationUtilities
                                          .GroupTrainCarSetsByNearestStation(emptyTrainCarsPerTrainSet);
                    loadedCgsPerTcsPerSc = JobProceduralGenerationUtilities
                                           .GroupTrainCarSetsByNearestStation(loadedTrainCarsPerTrainSet);
                }
                catch (Exception e)
                {
                    Main.modEntry.Logger.Error(
                        $"Exception thrown during TrainCarsCreateJobOrDeleteCheck station grouping:\n{e.ToString()}");
                    Main.OnCriticalFailure();
                }
                Debug.Log(
                    $"[PersistentJobs]\n" +
                    $"    found {paxTcsPerSc.Count} stations for passenger trainSets\n," +
                    $"    {emptyCgsPerTcsPerSc.Count} stations for empty trainSets\n," +
                    $"    and {loadedCgsPerTcsPerSc.Count} stations for loaded trainSets (coroutine)");

                yield return(WaitFor.SecondsRealtime(interopPeriod));

                // populate possible cargoGroups per group of trainCars
                Dictionary <StationController, List <List <TrainCar> > > emptyTcsPerSc = null;
                Debug.Log("[PersistentJobs] populating cargoGroups... (coroutine)");
                try
                {
                    JobProceduralGenerationUtilities.PopulateCargoGroupsPerTrainCarSet(emptyCgsPerTcsPerSc);
                    JobProceduralGenerationUtilities.PopulateCargoGroupsPerLoadedTrainCarSet(loadedCgsPerTcsPerSc);
                    emptyTcsPerSc = JobProceduralGenerationUtilities.ExtractEmptyHaulTrainSets(emptyCgsPerTcsPerSc);
                }
                catch (Exception e)
                {
                    Main.modEntry.Logger.Error(
                        $"Exception thrown during TrainCarsCreateJobOrDeleteCheck cargoGroup population:\n{e.ToString()}");
                    Main.OnCriticalFailure();
                }

                yield return(WaitFor.SecondsRealtime(interopPeriod));

                // pick new jobs for the trainCars at each station
                Debug.Log("[PersistentJobs] picking jobs... (coroutine)");
                System.Random rng = new System.Random(Environment.TickCount);
                List <(StationController, List <CarsPerTrack>, StationController, List <TrainCar>, List <CargoType>)>
                shuntingLoadJobInfos = null;
                List <(StationController, Track, StationController, List <TrainCar>, List <CargoType>)>
                transportJobInfos = null;
                List <(StationController, Track, StationController, List <TrainCar>, List <CargoType>)>
                shuntingUnloadJobInfos = null;
                try
                {
                    shuntingLoadJobInfos = ShuntingLoadJobProceduralGenerator
                                           .ComputeJobInfosFromCargoGroupsPerTrainCarSetPerStation(emptyCgsPerTcsPerSc, rng);

                    transportJobInfos = TransportJobProceduralGenerator
                                        .ComputeJobInfosFromCargoGroupsPerTrainCarSetPerStation(
                        loadedCgsPerTcsPerSc.Select(kv => (
                                                        kv.Key,
                                                        kv.Value.Where(tpl => {
                        CargoGroup cg0 = tpl.Item2.FirstOrDefault();
                        return(cg0 != null && kv.Key.proceduralJobsRuleset.outputCargoGroups.Contains(cg0));
                    }).ToList()))
                        .Where(tpl => tpl.Item2.Count > 0)
                        .ToDictionary(tpl => tpl.Item1, tpl => tpl.Item2),
                        rng);

                    shuntingUnloadJobInfos = ShuntingUnloadJobProceduralGenerator
                                             .ComputeJobInfosFromCargoGroupsPerTrainCarSetPerStation(
                        loadedCgsPerTcsPerSc.Select(kv => (
                                                        kv.Key,
                                                        kv.Value.Where(tpl => {
                        CargoGroup cg0 = tpl.Item2.FirstOrDefault();
                        return(cg0 != null && kv.Key.proceduralJobsRuleset.inputCargoGroups.Contains(cg0));
                    }).ToList()))
                        .Where(tpl => tpl.Item2.Count > 0)
                        .ToDictionary(tpl => tpl.Item1, tpl => tpl.Item2),
                        rng);
                }
                catch (Exception e)
                {
                    Main.modEntry.Logger.Error(
                        $"Exception thrown during TrainCarsCreateJobOrDeleteCheck job info selection:\n{e.ToString()}");
                    Main.OnCriticalFailure();
                }
                Debug.Log(
                    $"[PersistentJobs]\n" +
                    $"    chose {shuntingLoadJobInfos.Count} shunting load jobs,\n" +
                    $"    {transportJobInfos.Count} transport jobs,\n" +
                    $"    {shuntingUnloadJobInfos.Count} shunting unload jobs,\n" +
                    $"    and {emptyTcsPerSc.Aggregate(0, (acc, kv) => acc + kv.Value.Count)} empty haul jobs (coroutine)");

                yield return(WaitFor.SecondsRealtime(interopPeriod));

                // try to generate jobs
                Debug.Log("[PersistentJobs] generating jobs... (coroutine)");
                List <JobChainController>        paxJobChainControllers            = null;
                IEnumerable <JobChainController> shuntingLoadJobChainControllers   = null;
                IEnumerable <JobChainController> transportJobChainControllers      = null;
                IEnumerable <JobChainController> shuntingUnloadJobChainControllers = null;
                IEnumerable <JobChainController> emptyHaulJobChainControllers      = null;
                try
                {
                    paxJobChainControllers = JobProceduralGenerationUtilities.TryToGeneratePassengerJobs(paxTcsPerSc);
                    shuntingLoadJobChainControllers
                        = ShuntingLoadJobProceduralGenerator.doJobGeneration(shuntingLoadJobInfos, rng);
                    transportJobChainControllers
                        = TransportJobProceduralGenerator.doJobGeneration(transportJobInfos, rng);
                    shuntingUnloadJobChainControllers
                        = ShuntingUnloadJobProceduralGenerator.doJobGeneration(shuntingUnloadJobInfos, rng);
                    emptyHaulJobChainControllers = emptyTcsPerSc.Aggregate(
                        new List <JobChainController>(),
                        (list, kv) =>
                    {
                        list.AddRange(
                            kv.Value.Select(tcs => EmptyHaulJobProceduralGenerator
                                            .GenerateEmptyHaulJobWithExistingCars(kv.Key, tcs[0].logicCar.CurrentTrack, tcs, rng)));
                        return(list);
                    });
                }
                catch (Exception e)
                {
                    Main.modEntry.Logger.Error(
                        $"Exception thrown during TrainCarsCreateJobOrDeleteCheck job generation:\n{e.ToString()}");
                    Main.OnCriticalFailure();
                }
                Debug.Log(
                    $"[PersistentJobs]\n" +
                    $"    generated {paxJobChainControllers.Where(jcc => jcc != null).Count()} passenger jobs,\n" +
                    $"    {shuntingLoadJobChainControllers.Where(jcc => jcc != null).Count()} shunting load jobs,\n" +
                    $"    {transportJobChainControllers.Where(jcc => jcc != null).Count()} transport jobs,\n" +
                    $"    {shuntingUnloadJobChainControllers.Where(jcc => jcc != null).Count()} shunting unload jobs,\n" +
                    $"    and {emptyHaulJobChainControllers.Where(jcc => jcc != null).Count()} empty haul jobs (coroutine)");

                yield return(WaitFor.SecondsRealtime(interopPeriod));

                // finalize jobs & preserve job train cars
                Debug.Log("[PersistentJobs] finalizing jobs... (coroutine)");
                int totalCarsPreserved = 0;
                try
                {
                    foreach (var jcc in paxJobChainControllers)
                    {
                        if (jcc != null)
                        {
                            jcc.trainCarsForJobChain.ForEach(tc =>
                            {
                                trainCarCandidatesForDelete.Remove(tc);
                            });
                            totalCarsPreserved += jcc.trainCarsForJobChain.Count;
                            // generation has already taken care of converting player spawned cars and finalizing the job chain
                        }
                    }
                    foreach (JobChainController jcc in shuntingLoadJobChainControllers)
                    {
                        if (jcc != null)
                        {
                            jcc.trainCarsForJobChain.ForEach(tc =>
                            {
                                // force job's train cars to not be treated as player spawned
                                // DV will complain if we don't do this
                                Utilities.ConvertPlayerSpawnedTrainCar(tc);
                                trainCarCandidatesForDelete.Remove(tc);
                            });
                            totalCarsPreserved += jcc.trainCarsForJobChain.Count;
                            jcc.FinalizeSetupAndGenerateFirstJob();
                        }
                    }

                    foreach (JobChainController jcc in transportJobChainControllers)
                    {
                        if (jcc != null)
                        {
                            jcc.trainCarsForJobChain.ForEach(tc =>
                            {
                                // force job's train cars to not be treated as player spawned
                                // DV will complain if we don't do this
                                Utilities.ConvertPlayerSpawnedTrainCar(tc);
                                trainCarCandidatesForDelete.Remove(tc);
                            });
                            totalCarsPreserved += jcc.trainCarsForJobChain.Count;
                            jcc.FinalizeSetupAndGenerateFirstJob();
                        }
                    }

                    foreach (JobChainController jcc in shuntingUnloadJobChainControllers)
                    {
                        if (jcc != null)
                        {
                            jcc.trainCarsForJobChain.ForEach(tc =>
                            {
                                // force job's train cars to not be treated as player spawned
                                // DV will complain if we don't do this
                                Utilities.ConvertPlayerSpawnedTrainCar(tc);
                                trainCarCandidatesForDelete.Remove(tc);
                            });
                            totalCarsPreserved += jcc.trainCarsForJobChain.Count;
                            jcc.FinalizeSetupAndGenerateFirstJob();
                        }
                    }

                    foreach (JobChainController jcc in emptyHaulJobChainControllers)
                    {
                        if (jcc != null)
                        {
                            jcc.trainCarsForJobChain.ForEach(tc =>
                            {
                                // force job's train cars to not be treated as player spawned
                                // DV will complain if we don't do this
                                Utilities.ConvertPlayerSpawnedTrainCar(tc);
                                trainCarCandidatesForDelete.Remove(tc);
                            });
                            totalCarsPreserved += jcc.trainCarsForJobChain.Count;
                            jcc.FinalizeSetupAndGenerateFirstJob();
                        }
                    }
                }
                catch (Exception e)
                {
                    Main.modEntry.Logger.Error(
                        $"Exception thrown during TrainCarsCreateJobOrDeleteCheck trainCar preservation:\n{e.ToString()}");
                    Main.OnCriticalFailure();
                }

                yield return(WaitFor.SecondsRealtime(interopPeriod));

                // preserve all trainCars that are not locomotives
                Debug.Log("[PersistentJobs] preserving cars... (coroutine)");
                try
                {
                    foreach (TrainCar tc in new List <TrainCar>(trainCarCandidatesForDelete))
                    {
                        if (tc.playerSpawnedCar || !CarTypes.IsAnyLocomotiveOrTender(tc.carType))
                        {
                            trainCarCandidatesForDelete.Remove(tc);
                            unusedTrainCarsMarkedForDelete.Add(tc);
                            totalCarsPreserved += 1;
                        }
                    }
                    Debug.Log($"[PersistentJobs] preserved {totalCarsPreserved} cars (coroutine)");
                }
                catch (Exception e)
                {
                    Main.modEntry.Logger.Error(
                        $"Exception thrown during TrainCarsCreateJobOrDeleteCheck trainCar preservation:\n{e.ToString()}");
                    Main.OnCriticalFailure();
                }
                // ------ END JOB GENERATION ------

                yield return(WaitFor.SecondsRealtime(interopPeriod));

                Debug.Log("[PersistentJobs] deleting cars... (coroutine)");
                try
                {
                    trainCarsToDelete.Clear();
                    for (int j = trainCarCandidatesForDelete.Count - 1; j >= 0; j--)
                    {
                        TrainCar trainCar2 = trainCarCandidatesForDelete[j];
                        if (trainCar2 == null)
                        {
                            trainCarCandidatesForDelete.RemoveAt(j);
                        }
                        else if (AreDeleteConditionsFulfilledMethod.GetValue <bool>(trainCar2))
                        {
                            trainCarCandidatesForDelete.RemoveAt(j);
                            carVisitCheckersMap.Remove(trainCar2);
                            trainCarsToDelete.Add(trainCar2);
                        }
                        else
                        {
                            Debug.LogWarning(
                                $"Returning {trainCar2.name} to unusedTrainCarsMarkedForDelete list. PlayerTransform was outside" +
                                " of DELETE_SQR_DISTANCE_FROM_TRAINCAR range of train car, but after short period it" +
                                " was back in range!");
                            trainCarCandidatesForDelete.RemoveAt(j);
                            unusedTrainCarsMarkedForDelete.Add(trainCar2);
                        }
                    }
                    if (trainCarsToDelete.Count != 0)
                    {
                        SingletonBehaviour <CarSpawner> .Instance
                        .DeleteTrainCars(new List <TrainCar>(trainCarsToDelete), false);
                    }
                    Debug.Log($"[PersistentJobs] deleted {trainCarsToDelete.Count} cars (coroutine)");
                }
                catch (Exception e)
                {
                    Main.modEntry.Logger.Error(
                        $"Exception thrown during TrainCarsCreateJobOrDeleteCheck car deletion:\n{e.ToString()}");
                    Main.OnCriticalFailure();
                }
            }
        }
Example #3
0
        public static JobChainControllerWithEmptyHaulGeneration GenerateTransportJobWithExistingCars(
            StationController startingStation,
            Track startingTrack,
            StationController destStation,
            List <TrainCar> trainCars,
            List <CargoType> transportedCargoPerCar,
            System.Random rng,
            bool forceCorrectCargoStateOnCars = false)
        {
            Debug.Log("[PersistentJobs] transport: generating with pre-spawned cars");
            YardTracksOrganizer          yto = YardTracksOrganizer.Instance;
            HashSet <CargoContainerType> carContainerTypes = new HashSet <CargoContainerType>();

            foreach (TrainCar trainCar in trainCars)
            {
                carContainerTypes.Add(CargoTypes.CarTypeToContainerType[trainCar.carType]);
            }

            Debug.Log("[PersistentJobs] transport: choosing destination track");
            float approxTrainLength = yto.GetTotalTrainCarsLength(trainCars)
                                      + yto.GetSeparationLengthBetweenCars(trainCars.Count);
            Track destinationTrack = Utilities.GetTrackThatHasEnoughFreeSpace(
                yto,
                yto.FilterOutOccupiedTracks(destStation.logicStation.yard.TransferInTracks),
                approxTrainLength);

            if (destinationTrack == null)
            {
                destinationTrack = Utilities.GetTrackThatHasEnoughFreeSpace(
                    yto,
                    destStation.logicStation.yard.TransferInTracks,
                    approxTrainLength);
            }
            if (destinationTrack == null)
            {
                Debug.LogWarning(string.Format(
                                     "[PersistentJobs] transport: Could not create ChainJob[{0}]: {1} - {2}. " +
                                     "Found no TransferInTrack with enough free space!",
                                     JobType.Transport,
                                     startingStation.logicStation.ID,
                                     destStation.logicStation.ID
                                     ));
                return(null);
            }
            List <TrainCarType> transportedCarTypes = (from tc in trainCars select tc.carType)
                                                      .ToList <TrainCarType>();

            Debug.Log("[PersistentJobs] transport: calculating time/wage/licenses");
            float bonusTimeLimit;
            float initialWage;

            Utilities.CalculateTransportBonusTimeLimitAndWage(
                JobType.Transport,
                startingStation,
                destStation,
                transportedCarTypes,
                transportedCargoPerCar,
                out bonusTimeLimit,
                out initialWage
                );
            JobLicenses requiredLicenses = LicenseManager.GetRequiredLicensesForJobType(JobType.Transport)
                                           | LicenseManager.GetRequiredLicensesForCargoTypes(transportedCargoPerCar)
                                           | LicenseManager.GetRequiredLicenseForNumberOfTransportedCars(trainCars.Count);

            return(TransportJobProceduralGenerator.GenerateTransportChainController(
                       startingStation,
                       startingTrack,
                       destStation,
                       destinationTrack,
                       trainCars,
                       transportedCargoPerCar,
                       trainCars.Select(
                           tc => tc.logicCar.CurrentCargoTypeInCar == CargoType.None ? 1.0f : tc.logicCar.LoadedCargoAmount).ToList(),
                       forceCorrectCargoStateOnCars,
                       bonusTimeLimit,
                       initialWage,
                       requiredLicenses
                       ));
        }