) GenerateCargoTypesForExistingCars( List <TrainCar> orderedTrainCars, List <CargoGroup> availableCargoGroups, System.Random rng) { List <CarsPerCargoType> carsPerCargoTypes = new List <CarsPerCargoType>(); List <List <CargoType> > orderedCargoTypesPerTrainCar = (from tc in orderedTrainCars select GetCargoTypesForCarType(tc.carType)).ToList(); // find cargo groups that satisfy at least one cargo type for every train car List <CargoGroup> filteredCargoGroups = availableCargoGroups .Where(cg => orderedCargoTypesPerTrainCar.All(cts => cts.Intersect(cg.cargoTypes).Count() > 0)) .ToList(); if (filteredCargoGroups.Count == 0) { return(null, null); } CargoGroup pickedCargoGroup = GetRandomFromEnumerable <CargoGroup>(filteredCargoGroups, rng); List <CargoType> pickedCargoTypes = pickedCargoGroup.cargoTypes; List <CargoType> orderedCargoTypes = orderedCargoTypesPerTrainCar.Select( cts => GetRandomFromEnumerable <CargoType>(cts.Intersect(pickedCargoTypes).ToList(), rng) ).ToList(); return(orderedCargoTypes, pickedCargoGroup); }
public async Task OnGetUiFormat_GoodData_ExpectNoCargo() { byte testGroupCode = 101; byte testGroupCode2 = 102; var testDescription = "TEST_DESCRIPTION"; var testDescription2 = "TEST_DESCRIPTION2"; string CargoGroupKey = "CargoGroupKey"; string CargoCategoryKey = "CargoCategoryKey"; string expectedResult = "NoCargo"; CargoGroup CargoGroup = new CargoGroup() { Description = testDescription, IsUnitised = true, GroupCode = testGroupCode, CargoCategory = new List <CargoCategory>() }; var CargoCategories = new List <CargoCategory>() { new CargoCategory { GroupCode = testGroupCode, CategoryCode = 25, Description = testDescription }, new CargoCategory { GroupCode = testGroupCode2, CategoryCode = 26, Description = testDescription2 } }; await actualContext.CargoGroup.AddRangeAsync(CargoGroup); await actualContext.CargoCategory.AddRangeAsync(CargoCategories); actualContext.SaveChanges(); tempData.Put(CargoGroupKey, CargoGroup); tempData.Put(CargoCategoryKey, CargoCategories); var model = new CargoDetailsModel(actualContext, cargoPortValidateService) { PageContext = pageContext, TempData = tempData, Url = new UrlHelper(actionContext) }; var result = model.OnGetUiFormat(testDescription); Assert.AreEqual(result.Value, expectedResult); Assert.IsInstanceOfType(result, typeof(JsonResult)); }
) GenerateBaseCargoTrainData( int minCountCars, int maxCountCars, List <CargoGroup> availableCargoGroups, System.Random rng) { List <CarTypesPerCargoType> carTypesPerCargoTypes = new List <CarTypesPerCargoType>(); List <TrainCarType> allCarTypes = new List <TrainCarType>(); int countCarsInTrain = rng.Next(minCountCars, maxCountCars + 1); CargoGroup pickedCargoGroup = GetRandomFromEnumerable <CargoGroup>(availableCargoGroups, rng); List <CargoType> pickedCargoTypes = pickedCargoGroup.cargoTypes; pickedCargoTypes = GetMultipleRandomsFromList <CargoType>( pickedCargoTypes, Math.Min(countCarsInTrain, rng.Next(1, pickedCargoTypes.Count + 1)), rng ); int countCargoTypes = pickedCargoTypes.Count; int countCarsPerCargoType = countCarsInTrain / countCargoTypes; int countCargoTypesWithExtraCar = countCarsInTrain % countCargoTypes; for (int i = 0; i < countCargoTypes; i++) { int countCars = i < countCargoTypesWithExtraCar ? countCarsPerCargoType + 1 : countCarsPerCargoType; List <CargoContainerType> cargoContainerTypesThatSupportCargoType = CargoTypes.GetCarContainerTypesThatSupportCargoType(pickedCargoTypes[i]); List <TrainCarType> trainCarTypesThatAreSpecificContainerType = CargoTypes.GetTrainCarTypesThatAreSpecificContainerType( GetRandomFromEnumerable <CargoContainerType>(cargoContainerTypesThatSupportCargoType, rng) ); List <TrainCarType> trainCarTypes = new List <TrainCarType>(); for (int j = 0; j < countCars; j++) { trainCarTypes.Add( GetRandomFromEnumerable <TrainCarType>(trainCarTypesThatAreSpecificContainerType, rng)); } carTypesPerCargoTypes .Add(new CarTypesPerCargoType(trainCarTypes, pickedCargoTypes[i], (float)trainCarTypes.Count)); allCarTypes.AddRange(trainCarTypes); } return(carTypesPerCargoTypes, allCarTypes, pickedCargoGroup); }
public async Task IsValidCategory_WhenPassed_ShouldReturnValidCode() { string strcategory1 = "Crude Oil"; string strcategory2 = "Oil Products"; byte categoryCode12 = 12; byte categoryCode13 = 13; byte groupCode = 10; var CargoGroup = new CargoGroup() { Description = strcategory1, IsUnitised = true, GroupCode = groupCode, CargoCategory = new List <CargoCategory>() }; var CargoCategories = new List <CargoCategory>() { new CargoCategory { GroupCode = groupCode, CategoryCode = categoryCode12, Description = strcategory1 }, new CargoCategory { GroupCode = groupCode, CategoryCode = categoryCode13, Description = strcategory2 } }; await actualContext.CargoGroup.AddRangeAsync(CargoGroup); await actualContext.CargoCategory.AddRangeAsync(CargoCategories); actualContext.SaveChanges(); var result = _helperService.GetCategoryCodeByDescription(strcategory1); var category = CargoCategories.Find(x => x.Description == strcategory1).CategoryCode; Assert.IsNotNull(result); Assert.IsInstanceOfType(result, typeof(byte)); Assert.IsNotNull(category); Assert.AreEqual(result, category); }
public static JobChainControllerWithEmptyHaulGeneration GenerateShuntingUnloadJobWithCarSpawning( StationController destinationStation, bool forceLicenseReqs, System.Random rng) { Debug.Log("[PersistentJobs] unload: generating with car spawning"); YardTracksOrganizer yto = YardTracksOrganizer.Instance; List <CargoGroup> availableCargoGroups = destinationStation.proceduralJobsRuleset.inputCargoGroups; int countTrainCars = rng.Next( destinationStation.proceduralJobsRuleset.minCarsPerJob, destinationStation.proceduralJobsRuleset.maxCarsPerJob); if (forceLicenseReqs) { Debug.Log("[PersistentJobs] unload: forcing license requirements"); if (!LicenseManager.IsJobLicenseAcquired(JobLicenses.Shunting)) { Debug.LogError("[PersistentJobs] unload: Trying to generate a ShuntingUnload job with " + "forceLicenseReqs=true should never happen if player doesn't have Shunting license!"); return(null); } availableCargoGroups = (from cg in availableCargoGroups where LicenseManager.IsLicensedForJob(cg.CargoRequiredLicenses) select cg).ToList(); countTrainCars = Math.Min(countTrainCars, LicenseManager.GetMaxNumberOfCarsPerJobWithAcquiredJobLicenses()); } if (availableCargoGroups.Count == 0) { Debug.LogWarning("[PersistentJobs] unload: no available cargo groups"); return(null); } CargoGroup chosenCargoGroup = Utilities.GetRandomFromEnumerable(availableCargoGroups, rng); // choose cargo & trainCar types Debug.Log("[PersistentJobs] unload: choosing cargo & trainCar types"); List <CargoType> availableCargoTypes = chosenCargoGroup.cargoTypes; List <CargoType> orderedCargoTypes = new List <CargoType>(); List <TrainCarType> orderedTrainCarTypes = new List <TrainCarType>(); for (int i = 0; i < countTrainCars; i++) { CargoType chosenCargoType = Utilities.GetRandomFromEnumerable(availableCargoTypes, rng); List <CargoContainerType> availableContainers = CargoTypes.GetCarContainerTypesThatSupportCargoType(chosenCargoType); CargoContainerType chosenContainerType = Utilities.GetRandomFromEnumerable(availableContainers, rng); List <TrainCarType> availableTrainCarTypes = CargoTypes.GetTrainCarTypesThatAreSpecificContainerType(chosenContainerType); TrainCarType chosenTrainCarType = Utilities.GetRandomFromEnumerable(availableTrainCarTypes, rng); orderedCargoTypes.Add(chosenCargoType); orderedTrainCarTypes.Add(chosenTrainCarType); } float approxTrainLength = yto.GetTotalCarTypesLength(orderedTrainCarTypes) + yto.GetSeparationLengthBetweenCars(countTrainCars); // choose starting track Debug.Log("[PersistentJobs] unload: choosing starting track"); Track startingTrack = Utilities.GetTrackThatHasEnoughFreeSpace(yto, destinationStation.logicStation.yard.TransferInTracks, approxTrainLength); if (startingTrack == null) { Debug.LogWarning("[PersistentJobs] unload: Couldn't find startingTrack with enough free space for train!"); return(null); } // choose random starting station // no need to ensure it has has free space; this is just a back story Debug.Log("[PersistentJobs] unload: choosing origin (inconsequential)"); List <StationController> availableOrigins = new List <StationController>(chosenCargoGroup.stations); StationController startingStation = Utilities.GetRandomFromEnumerable(availableOrigins, rng); // spawn trainCars Debug.Log("[PersistentJobs] unload: spawning trainCars"); RailTrack railTrack = SingletonBehaviour <LogicController> .Instance.LogicToRailTrack[startingTrack]; List <TrainCar> orderedTrainCars = CarSpawner.SpawnCarTypesOnTrack( orderedTrainCarTypes, railTrack, true, 0.0, false, true); if (orderedTrainCars == null) { Debug.LogWarning("[PersistentJobs] unload: Failed to spawn trainCars!"); return(null); } JobChainControllerWithEmptyHaulGeneration jcc = GenerateShuntingUnloadJobWithExistingCars( startingStation, startingTrack, destinationStation, orderedTrainCars, orderedCargoTypes, rng, true); if (jcc == null) { Debug.LogWarning("[PersistentJobs] unload: Couldn't generate job chain. Deleting spawned trainCars!"); SingletonBehaviour <CarSpawner> .Instance.DeleteTrainCars(orderedTrainCars, true); return(null); } return(jcc); }
private void CargoItemValidation(MSD1 localMSD1, CargoCategory currentCargoCategory, CargoGroup currentCargoGroup) { ManualModelValidation(localMSD1); CargoCategoryHardValidations(localMSD1, currentCargoCategory); bool selectedCategory12 = currentCargoCategory.CategoryCode == (int)PortCategory.CrudeOilCode; bool selectedCategory13 = currentCargoCategory.CategoryCode == (int)PortCategory.OilProductsCode; bool isValidCategory = _cargoPortValidateService.IsValidPortForCategory(currentCargoCategory.CategoryCode, localMSD1.ReportingPort, localMSD1.AssociatedPort, localMSD1.IsInbound); if (!isValidCategory) { if (selectedCategory12) { ModelState.AddModelError("CargoItem.Category", $"Crude Oil cannot be carried through " + localMSD1.ReportingPort); } if (selectedCategory13) { var portName = (localMSD1.IsInbound == true ? localMSD1.AssociatedPort : localMSD1.ReportingPort); ModelState.AddModelError("CargoItem.Category", $"Oil Products cannot be carried through " + portName); } } bool isValidCategoryVessel = _cargoPortValidateService.IsValidVesselCargo(currentCargoCategory.CategoryCode, localMSD1.Imo); if (!isValidCategoryVessel) { ModelState.AddModelError("CargoItem.Category", $"Category '" + currentCargoCategory.Description + "' is not valid for the vessel '" + localMSD1.Imo + "-" + localMSD1.ShipName + "'"); } }
// 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(); } } }
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); }
public static JobChainControllerWithEmptyHaulGeneration GenerateShuntingLoadJobWithCarSpawning( StationController startingStation, bool forceLicenseReqs, System.Random rng) { Debug.Log("[PersistentJobs] load: generating with car spawning"); YardTracksOrganizer yto = YardTracksOrganizer.Instance; List <CargoGroup> availableCargoGroups = startingStation.proceduralJobsRuleset.outputCargoGroups; int countTrainCars = rng.Next( startingStation.proceduralJobsRuleset.minCarsPerJob, startingStation.proceduralJobsRuleset.maxCarsPerJob); if (forceLicenseReqs) { Debug.Log("[PersistentJobs] load: forcing license requirements"); if (!LicenseManager.IsJobLicenseAcquired(JobLicenses.Shunting)) { Debug.LogError("Trying to generate a ShuntingLoad job with forceLicenseReqs=true should " + "never happen if player doesn't have Shunting license!"); return(null); } availableCargoGroups = (from cg in availableCargoGroups where LicenseManager.IsLicensedForJob(cg.CargoRequiredLicenses) select cg).ToList(); countTrainCars = Math.Min(countTrainCars, LicenseManager.GetMaxNumberOfCarsPerJobWithAcquiredJobLicenses()); } if (availableCargoGroups.Count == 0) { Debug.LogWarning("[PersistentJobs] load: no available cargo groups"); return(null); } CargoGroup chosenCargoGroup = Utilities.GetRandomFromEnumerable(availableCargoGroups, rng); // choose cargo & trainCar types Debug.Log("[PersistentJobs] load: choosing cargo & trainCar types"); List <CargoType> availableCargoTypes = chosenCargoGroup.cargoTypes; List <CargoType> orderedCargoTypes = new List <CargoType>(); List <TrainCarType> orderedTrainCarTypes = new List <TrainCarType>(); for (int i = 0; i < countTrainCars; i++) { CargoType chosenCargoType = Utilities.GetRandomFromEnumerable(availableCargoTypes, rng); List <CargoContainerType> availableContainers = CargoTypes.GetCarContainerTypesThatSupportCargoType(chosenCargoType); CargoContainerType chosenContainerType = Utilities.GetRandomFromEnumerable(availableContainers, rng); List <TrainCarType> availableTrainCarTypes = CargoTypes.GetTrainCarTypesThatAreSpecificContainerType(chosenContainerType); TrainCarType chosenTrainCarType = Utilities.GetRandomFromEnumerable(availableTrainCarTypes, rng); orderedCargoTypes.Add(chosenCargoType); orderedTrainCarTypes.Add(chosenTrainCarType); } // choose starting tracks int maxCountTracks = startingStation.proceduralJobsRuleset.maxShuntingStorageTracks; int countTracks = rng.Next(1, maxCountTracks + 1); // bias toward less than max number of tracks for shorter trains if (orderedTrainCarTypes.Count < 2 * maxCountTracks) { countTracks = rng.Next(0, Mathf.FloorToInt(1.5f * maxCountTracks)) % maxCountTracks + 1; } Debug.Log(string.Format("[PersistentJobs] load: choosing {0} starting tracks", countTracks)); int countCarsPerTrainset = countTrainCars / countTracks; int countTrainsetsWithExtraCar = countTrainCars % countTracks; List <Track> tracks = new List <Track>(); do { tracks.Clear(); for (int i = 0; i < countTracks; i++) { int rangeStart = i * countCarsPerTrainset + Math.Min(i, countTrainsetsWithExtraCar); int rangeCount = i < countTrainsetsWithExtraCar ? countCarsPerTrainset + 1 : countCarsPerTrainset; List <TrainCarType> trainCarTypesPerTrack = orderedTrainCarTypes.GetRange(rangeStart, rangeCount); float approxTrainLengthPerTrack = yto.GetTotalCarTypesLength(trainCarTypesPerTrack) + yto.GetSeparationLengthBetweenCars(trainCarTypesPerTrack.Count); Track track = Utilities.GetTrackThatHasEnoughFreeSpace( yto, startingStation.logicStation.yard.StorageTracks.Except(tracks).ToList(), approxTrainLengthPerTrack / (float)countTracks); if (track == null) { break; } tracks.Add(track); } } while (tracks.Count < countTracks--); if (tracks.Count == 0) { Debug.LogWarning("[PersistentJobs] load: Couldn't find startingTrack with enough free space for train!"); return(null); } // choose random destination station that has at least 1 available track Debug.Log("[PersistentJobs] load: choosing destination"); float approxTrainLength = yto.GetTotalCarTypesLength(orderedTrainCarTypes) + yto.GetSeparationLengthBetweenCars(countTrainCars); List <StationController> availableDestinations = new List <StationController>(chosenCargoGroup.stations); StationController destStation = null; Track destinationTrack = null; while (availableDestinations.Count > 0 && destinationTrack == null) { destStation = Utilities.GetRandomFromEnumerable(availableDestinations, rng); availableDestinations.Remove(destStation); destinationTrack = Utilities.GetTrackThatHasEnoughFreeSpace( yto, yto.FilterOutOccupiedTracks(destStation.logicStation.yard.TransferInTracks), approxTrainLength); } if (destinationTrack == null) { Debug.LogWarning("Couldn't find a station with enough free space for train!"); return(null); } // spawn trainCars & form carsPerStartingTrack Debug.Log("[PersistentJobs] load: spawning trainCars"); List <TrainCar> orderedTrainCars = new List <TrainCar>(); List <CarsPerTrack> carsPerStartingTrack = new List <CarsPerTrack>(); for (int i = 0; i < tracks.Count; i++) { int rangeStart = i * countCarsPerTrainset + Math.Min(i, countTrainsetsWithExtraCar); int rangeCount = i < countTrainsetsWithExtraCar ? countCarsPerTrainset + 1 : countCarsPerTrainset; Debug.Log(string.Format( "[PersistentJobs] load: spawning cars in range [{0}-{1}) from total range [0-{2})", rangeStart, rangeStart + rangeCount, orderedTrainCarTypes.Count)); Track startingTrack = tracks[i]; RailTrack railTrack = SingletonBehaviour <LogicController> .Instance.LogicToRailTrack[startingTrack]; List <TrainCar> spawnedCars = CarSpawner.SpawnCarTypesOnTrack( orderedTrainCarTypes.GetRange(rangeStart, rangeCount), railTrack, true, 0.0, false, true); if (spawnedCars == null) { Debug.LogWarning("[PersistentJobs] load: Failed to spawn some trainCars!"); SingletonBehaviour <CarSpawner> .Instance.DeleteTrainCars(orderedTrainCars, true); return(null); } orderedTrainCars.AddRange(spawnedCars); carsPerStartingTrack.Add( new CarsPerTrack(startingTrack, (from car in spawnedCars select car.logicCar).ToList())); } JobChainControllerWithEmptyHaulGeneration jcc = GenerateShuntingLoadJobWithExistingCars( startingStation, carsPerStartingTrack, destStation, orderedTrainCars, orderedCargoTypes, rng, true); if (jcc == null) { Debug.LogWarning("[PersistentJobs] load: Couldn't generate job chain. Deleting spawned trainCars!"); SingletonBehaviour <CarSpawner> .Instance.DeleteTrainCars(orderedTrainCars, true); return(null); } return(jcc); }