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); }
public PassengerTransportChainController GenerateNewTransportJob(TrainCarsPerLogicTrack consistInfo = null, SpecialTrain prevSpecial = null) { int nTotalCars; List <TrainCarType> jobCarTypes = null; float trainLength; Track startSiding; // Establish the starting consist and its storage location if (consistInfo == null) { // generate a consist nTotalCars = Rand.Next(MIN_CARS_EXPRESS, MAX_CARS_EXPRESS + 1); float singleCarLength = TrackOrg.GetCarTypeLength(TrainCarType.PassengerRed); trainLength = (singleCarLength * nTotalCars) + TrackOrg.GetSeparationLengthBetweenCars(nTotalCars); // pick start storage track var emptyTracks = TrackOrg.FilterOutOccupiedTracks(StorageTracks); startSiding = TrackOrg.GetTrackWithEnoughFreeSpace(emptyTracks, trainLength); if (startSiding == null) { startSiding = TrackOrg.GetTrackWithEnoughFreeSpace(StorageTracks, trainLength); } } else { // Use existing consist nTotalCars = consistInfo.cars.Count; jobCarTypes = consistInfo.cars.Select(car => car.carType).ToList(); trainLength = TrackOrg.GetTotalCarTypesLength(jobCarTypes) + TrackOrg.GetSeparationLengthBetweenCars(nTotalCars); startSiding = consistInfo.track; } if (startSiding == null) { //PassengerJobs.ModEntry.Logger.Log($"No available starting siding for express job at {Controller.stationInfo.Name}"); return(null); } // Try to find a loading platform to use PlatformDefinition loadingPlatform = PlatformManager.PickPlatform(Controller.stationInfo.YardID); // Choose a route var destPool = PassDestinations.Values.ToList(); Track destSiding = null; StationController destStation = null; // this prevents generating jobs like "ChainJob[Passenger]: FF - FF (FF-PE-47)" destPool.Remove(Controller); while ((destSiding == null) && (destPool.Count > 0)) { // search the possible destinations 1 by 1 until we find an opening (or we don't) destStation = destPool.ChooseOne(Rand); // pick ending platform PassengerJobGenerator destGenerator = LinkedGenerators[destStation]; destSiding = TrackOrg.GetTrackWithEnoughFreeSpace(destGenerator.StorageTracks, trainLength); // remove this station from the pool destPool.Remove(destStation); } if (destSiding == null) { //PassengerJobs.ModEntry.Logger.Log($"No available destination siding for new job at {Controller.stationInfo.Name}"); return(null); } // we found a route :D // if we're creating a new consist, check if it can be a special train // let's try 2/3 chance of special train, 1/3 normal gen SpecialTrain specialInfo = null; List <string> skinList = null; if (consistInfo == null) { // default 67% chance, or as configured (if there is a special available) double choice = Rand.NextDouble(); if ((choice <= PassengerJobs.Settings.NamedTrainProbability) && (SpecialConsistManager.GetTrainForRoute(Controller.stationInfo.YardID, destStation.stationInfo.YardID) is SpecialTrain special)) { specialInfo = special; IEnumerable <SpecialTrainSkin> consistSkins = special.Skins.ChooseMany(Rand, nTotalCars); jobCarTypes = consistSkins.Select(s => s.CarType).ToList(); skinList = consistSkins.Select(s => s.Name).ToList(); } else { // normal consist generation if (PassengerJobs.Settings.UniformConsists) { TrainCarType carType = PassCarTypes.ChooseOne(Rand); jobCarTypes = Enumerable.Repeat(carType, nTotalCars).ToList(); } else { jobCarTypes = PassCarTypes.ChooseMany(Rand, nTotalCars); } } } else { // extant consist, use existing special (if it exists) specialInfo = prevSpecial; } // Try to find an unloading platform PlatformDefinition unloadingPlatform = PlatformManager.PickPlatform(destStation.stationInfo.YardID); // create job chain controller var chainJobObject = new GameObject($"ChainJob[Passenger]: {Controller.logicStation.ID} - {destStation.logicStation.ID}"); chainJobObject.transform.SetParent(Controller.transform); var chainController = new PassengerTransportChainController(chainJobObject); StaticPassengerJobDefinition jobDefinition; //-------------------------------------------------------------------------------------------------------------------------------- // Create transport leg job var chainData = new StationsChainData(Controller.stationInfo.YardID, destStation.stationInfo.YardID); PaymentCalculationData transportPaymentData = GetJobPaymentData(jobCarTypes); // calculate haul payment float haulDistance = JobPaymentCalculator.GetDistanceBetweenStations(Controller, destStation); float bonusLimit = JobPaymentCalculator.CalculateHaulBonusTimeLimit(haulDistance, false); float transportPayment = JobPaymentCalculator.CalculateJobPayment(JobType.Transport, haulDistance, transportPaymentData); // calculate additional payment for shunting const float shuntDistance = 500f; PaymentCalculationData emptyPaymentData = GetJobPaymentData(jobCarTypes, true); float platformBonusTime = JobPaymentCalculator.CalculateShuntingBonusTimeLimit(1) * 0.7f + PlatformController.START_XFER_DELAY; if (loadingPlatform?.Initialized == true) { float loadPayment = JobPaymentCalculator.CalculateJobPayment(JobType.ShuntingLoad, shuntDistance, emptyPaymentData); transportPayment += loadPayment; bonusLimit += platformBonusTime; } if (unloadingPlatform?.Initialized == true) { float unloadPayment = JobPaymentCalculator.CalculateJobPayment(JobType.ShuntingUnload, shuntDistance, emptyPaymentData); transportPayment += unloadPayment; bonusLimit += platformBonusTime; } // scale job payment depending on settings float wageScale = PassengerJobs.Settings.UseCustomWages ? BASE_WAGE_SCALE : 1; transportPayment = Mathf.Round(transportPayment * wageScale); if (consistInfo == null) { jobDefinition = PopulateTransportJobAndSpawn( chainController, Controller.logicStation, startSiding, destSiding, jobCarTypes, chainData, bonusLimit, transportPayment, true, skinList); } else { chainController.trainCarsForJobChain = consistInfo.cars; jobDefinition = PopulateTransportJobExistingCars( chainController, Controller.logicStation, startSiding, destSiding, consistInfo.LogicCars, chainData, bonusLimit, transportPayment); } if (jobDefinition == null) { PassengerJobs.ModEntry.Logger.Warning($"Failed to generate transport job definition for {chainController.jobChainGO.name}"); chainController.DestroyChain(); return(null); } jobDefinition.subType = PassJobType.Express; jobDefinition.specialDefinition = specialInfo; // Setup any warehouse tasks if (loadingPlatform?.Initialized == true) { jobDefinition.loadMachine = loadingPlatform.Controller.LogicMachine; } if (unloadingPlatform?.Initialized == true) { jobDefinition.unloadMachine = unloadingPlatform.Controller.LogicMachine; } chainController.AddJobDefinitionToChain(jobDefinition); // Finalize job chainController.FinalizeSetupAndGenerateFirstJob(); PassengerJobs.ModEntry.Logger.Log($"Generated new passenger haul job: {chainJobObject.name} ({chainController.currentJobInChain.ID})"); return(chainController); }
public PassengerTransportChainController GenerateNewTransportJob(TrainCarsPerLogicTrack consistInfo = null) { int nTotalCars; List <TrainCarType> jobCarTypes; float trainLength; Track startPlatform; if (consistInfo == null) { // generate a consist nTotalCars = Rand.Next(MIN_CARS_EXPRESS, MAX_CARS_EXPRESS + 1); if (PassengerJobs.Settings.UniformConsists) { TrainCarType carType = PassCarTypes.ChooseOne(Rand); jobCarTypes = Enumerable.Repeat(carType, nTotalCars).ToList(); } else { jobCarTypes = PassCarTypes.ChooseMany(Rand, nTotalCars); } trainLength = TrackOrg.GetTotalCarTypesLength(jobCarTypes) + TrackOrg.GetSeparationLengthBetweenCars(nTotalCars); var pool = TrackOrg.FilterOutReservedTracks(TrackOrg.FilterOutOccupiedTracks(PlatformTracks)); if (!(TrackOrg.GetTrackThatHasEnoughFreeSpace(pool, trainLength) is Track startTrack)) { PassengerJobs.ModEntry.Logger.Log($"Couldn't find storage track with enough free space for new job at {Controller.stationInfo.YardID}"); return(null); } startPlatform = startTrack; } else { // Use existing consist nTotalCars = consistInfo.cars.Count; jobCarTypes = consistInfo.cars.Select(car => car.carType).ToList(); trainLength = TrackOrg.GetTotalCarTypesLength(jobCarTypes) + TrackOrg.GetSeparationLengthBetweenCars(nTotalCars); startPlatform = consistInfo.track; } if (startPlatform == null) { PassengerJobs.ModEntry.Logger.Log($"No available platform for new job at {Controller.stationInfo.Name}"); return(null); } // Choose a route var destPool = PassDestinations.Values.ToList(); Track destPlatform = null; StationController destStation = null; // this prevents generating jobs like "ChainJob[Passenger]: FF - FF (FF-PE-47)" destPool.Remove(Controller); while ((destPlatform == null) && (destPool.Count > 0)) { // search the possible destinations 1 by 1 until we find an opening (or we don't) destStation = destPool.ChooseOne(Rand); // pick ending platform PassengerJobGenerator destGenerator = LinkedGenerators[destStation]; destPlatform = TrackOrg.GetTrackThatHasEnoughFreeSpace(destGenerator.PlatformTracks, trainLength); // remove this station from the pool destPool.Remove(destStation); } if (destPlatform == null) { PassengerJobs.ModEntry.Logger.Log($"No available destination platform for new job at {Controller.stationInfo.Name}"); return(null); } // create job chain controller var chainJobObject = new GameObject($"ChainJob[Passenger]: {Controller.logicStation.ID} - {destStation.logicStation.ID}"); chainJobObject.transform.SetParent(Controller.transform); var chainController = new PassengerTransportChainController(chainJobObject); StaticPassengerJobDefinition jobDefinition; //-------------------------------------------------------------------------------------------------------------------------------- // Create transport leg job var chainData = new StationsChainData(Controller.stationInfo.YardID, destStation.stationInfo.YardID); PaymentCalculationData transportPaymentData = GetJobPaymentData(jobCarTypes); // calculate haul payment float haulDistance = JobPaymentCalculator.GetDistanceBetweenStations(Controller, destStation); float bonusLimit = JobPaymentCalculator.CalculateHaulBonusTimeLimit(haulDistance, false); float transportPayment = JobPaymentCalculator.CalculateJobPayment(JobType.Transport, haulDistance, transportPaymentData); // scale job payment depending on settings float wageScale = PassengerJobs.Settings.UseCustomWages ? BASE_WAGE_SCALE : 1; transportPayment = Mathf.Round(transportPayment * wageScale); if (consistInfo == null) { jobDefinition = PopulateTransportJobAndSpawn( chainController, Controller.logicStation, startPlatform, destPlatform, jobCarTypes, chainData, bonusLimit, transportPayment, true); } else { chainController.trainCarsForJobChain = consistInfo.cars; jobDefinition = PopulateTransportJobExistingCars( chainController, Controller.logicStation, startPlatform, destPlatform, consistInfo.LogicCars, chainData, bonusLimit, transportPayment); } if (jobDefinition == null) { PassengerJobs.ModEntry.Logger.Warning($"Failed to generate transport job definition for {chainController.jobChainGO.name}"); chainController.DestroyChain(); return(null); } jobDefinition.subType = PassJobType.Express; chainController.AddJobDefinitionToChain(jobDefinition); // Finalize job chainController.FinalizeSetupAndGenerateFirstJob(); PassengerJobs.ModEntry.Logger.Log($"Generated new passenger haul job: {chainJobObject.name} ({chainController.currentJobInChain.ID})"); return(chainController); }
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); }