static bool Prefix(IdGenerator __instance, JobType jobType, StationsChainData jobStationsInfo, ref string __result) { if ((jobType != PassJobType.Express) && (jobType != PassJobType.Commuter)) { return(true); } string yardId = null; if (jobStationsInfo != null) { yardId = jobStationsInfo.chainOriginYardId; } string typeStr = (jobType == PassJobType.Express) ? EXPRESS_TYPE : COMMUTE_TYPE; string idStr = FindUnusedID(typeStr, yardId); if (idStr != null) { __instance.RegisterJobId(idStr); __result = idStr; } else { PassengerJobs.ModEntry.Logger.Warning($"Couldn't find free jobId for job type: {typeStr}! Using 0 for jobId number!"); __result = (yardId != null) ? $"{yardId}-{typeStr}-{0:D2}" : $"{typeStr}-{0:D2}"; } return(false); }
private bool ParseRoutes() { if (string.IsNullOrWhiteSpace(RouteString) || "*".Equals(RouteString)) { AllowAnyRoute = true; return(true); } string[] routePairs = RouteString.Split(','); Routes = new StationsChainData[routePairs.Length]; for (int i = 0; i < routePairs.Length; i++) { string[] stations = routePairs[i].ToUpper().Split('-'); if ((stations.Length == 2) && LogicController.Instance.YardIdToStationController.TryGetValue(stations[0], out _) && LogicController.Instance.YardIdToStationController.TryGetValue(stations[1], out _)) { Routes[i] = new StationsChainData(stations[0], stations[1]); } else { return(false); } } return(true); }
private static StaticPassengerJobDefinition PopulateTransportJobAndSpawn( JobChainController chainController, Station startStation, Track startTrack, Track destTrack, List <TrainCarType> carTypes, StationsChainData chainData, float timeLimit, float initialPay, bool unifyConsist = false) { // Spawn the cars RailTrack startRT = SingletonBehaviour <LogicController> .Instance.LogicToRailTrack[startTrack]; var spawnedCars = CarSpawner.SpawnCarTypesOnTrack(carTypes, startRT, true, 0, false, true); if (spawnedCars == null) { return(null); } chainController.trainCarsForJobChain = spawnedCars; var logicCars = TrainCar.ExtractLogicCars(spawnedCars); if (logicCars == null) { PassengerJobs.ModEntry.Logger.Error("Couldn't extract logic cars, deleting spawned cars"); SingletonBehaviour <CarSpawner> .Instance.DeleteTrainCars(spawnedCars, true); return(null); } if (unifyConsist && SkinManager_Patch.Enabled) { SkinManager_Patch.UnifyConsist(spawnedCars); } return(PopulateTransportJobExistingCars(chainController, startStation, startTrack, destTrack, logicCars, chainData, timeLimit, initialPay)); }
private static StaticPassengerJobDefinition CreateSavedPassengerJob(GameObject jobChainGO, PassengerJobDefinitionData jobData) { // associated station if (!(GetStationWithId(jobData.stationId) is Station logicStation)) { PrintError($"Couldn't find corresponding Station with ID: {jobData.stationId}! Skipping load of this job chain!"); return(null); } // bonus time limit, base payment if (jobData.timeLimitForJob < 0f || jobData.initialWage < 0f || string.IsNullOrEmpty(jobData.originStationId) || string.IsNullOrEmpty(jobData.destinationStationId)) { PrintError("Invalid data! Skipping load of this job chain!"); return(null); } // license requirements if (!LicenseManager.IsValidForParsingToJobLicense(jobData.requiredLicenses)) { PrintError("Undefined job licenses requirement! Skipping load of this job chain!"); return(null); } // starting track if (!(GetYardTrackWithId(jobData.startingTrack) is Track startTrack)) { PrintError($"Couldn't find corresponding start Track with ID: {jobData.startingTrack}! Skipping load of this job chain!"); return(null); } // destination track if (!(GetYardTrackWithId(jobData.destinationTrack) is Track destTrack)) { PrintError($"Couldn't find corresponding destination Track with ID: {jobData.destinationTrack}! Skipping load of this job chain!"); return(null); } // consist if (!(GetCarsFromCarGuids(jobData.trainCarGuids) is List <Car> consist)) { PrintError("Couldn't find all carsToTransport with transportCarGuids! Skipping load of this job chain!"); return(null); } StaticPassengerJobDefinition jobDefinition = jobChainGO.AddComponent <StaticPassengerJobDefinition>(); var chainData = new StationsChainData(jobData.originStationId, jobData.destinationStationId); jobDefinition.PopulateBaseJobDefinition(logicStation, jobData.timeLimitForJob, jobData.initialWage, chainData, (JobLicenses)jobData.requiredLicenses); jobDefinition.subType = (JobType)jobData.subType; jobDefinition.startingTrack = startTrack; jobDefinition.destinationTrack = destTrack; jobDefinition.trainCarsToTransport = consist; return(jobDefinition); }
private static JobChainControllerWithEmptyHaulGeneration GenerateTransportChainController( StationController startingStation, Track startingTrack, StationController destStation, Track destTrack, List <TrainCar> orderedTrainCars, List <CargoType> orderedCargoTypes, List <float> orderedCargoAmounts, bool forceCorrectCargoStateOnCars, float bonusTimeLimit, float initialWage, JobLicenses requiredLicenses) { Debug.Log(string.Format( "[PersistentJobs] transport: attempting to generate ChainJob[{0}]: {1} - {2}", JobType.ShuntingLoad, startingStation.logicStation.ID, destStation.logicStation.ID )); GameObject gameObject = new GameObject(string.Format( "ChainJob[{0}]: {1} - {2}", JobType.Transport, startingStation.logicStation.ID, destStation.logicStation.ID )); gameObject.transform.SetParent(startingStation.transform); JobChainControllerWithEmptyHaulGeneration jobChainController = new JobChainControllerWithEmptyHaulGeneration(gameObject); StationsChainData chainData = new StationsChainData( startingStation.stationInfo.YardID, destStation.stationInfo.YardID ); jobChainController.trainCarsForJobChain = orderedTrainCars; List <Car> orderedLogicCars = TrainCar.ExtractLogicCars(orderedTrainCars); StaticTransportJobDefinition staticTransportJobDefinition = gameObject.AddComponent <StaticTransportJobDefinition>(); staticTransportJobDefinition.PopulateBaseJobDefinition( startingStation.logicStation, bonusTimeLimit, initialWage, chainData, requiredLicenses ); staticTransportJobDefinition.startingTrack = startingTrack; staticTransportJobDefinition.destinationTrack = destTrack; staticTransportJobDefinition.trainCarsToTransport = orderedLogicCars; staticTransportJobDefinition.transportedCargoPerCar = orderedCargoTypes; staticTransportJobDefinition.cargoAmountPerCar = orderedCargoAmounts; staticTransportJobDefinition.forceCorrectCargoStateOnCars = forceCorrectCargoStateOnCars; jobChainController.AddJobDefinitionToChain(staticTransportJobDefinition); return(jobChainController); }
private static StaticPassengerJobDefinition PopulateTransportJobExistingCars( JobChainController chainController, Station startStation, Track startTrack, Track destTrack, List <Car> logicCars, StationsChainData chainData, float timeLimit, float initialPay) { // populate the actual job StaticPassengerJobDefinition jobDefinition = chainController.jobChainGO.AddComponent <StaticPassengerJobDefinition>(); jobDefinition.PopulateBaseJobDefinition(startStation, timeLimit, initialPay, chainData, PassLicenses.Passengers1); jobDefinition.startingTrack = startTrack; jobDefinition.trainCarsToTransport = logicCars; jobDefinition.destinationTrack = destTrack; return(jobDefinition); }
static bool Prefix(JobType jobType, StationsChainData jobStationsInfo, ref string __result, System.Random ___idRng, HashSet <string> ___existingJobIds) { if ((jobType != PassJobType.Express) && (jobType != PassJobType.Commuter)) { return(true); } string yardId = null; if (jobStationsInfo != null) { yardId = jobStationsInfo.chainOriginYardId; } string typeStr = (jobType == PassJobType.Express) ? EXPRESS_TYPE : COMMUTE_TYPE; int idNum = ___idRng.Next(0, 100); for (int attemptNum = 0; attemptNum < 99; attemptNum++) { string idStr = (yardId != null) ? $"{yardId}-{typeStr}-{idNum:D2}" : $"{typeStr}-{idNum:D2}"; if (!___existingJobIds.Contains(idStr)) { IdGenerator.RegisterJobId(idStr); __result = idStr; return(false); } idNum = (idNum >= 99) ? 0 : (idNum + 1); } PassengerJobs.ModEntry.Logger.Warning($"Couldn't find free jobId for job type: {typeStr}! Using 0 for jobId number!"); __result = (yardId != null) ? $"{yardId}-{typeStr}-{0:D2}" : $"{typeStr}-{0:D2}"; return(false); }
private static JobChainControllerWithEmptyHaulGeneration GenerateShuntingUnloadChainController( StationController startingStation, Track startingTrack, WarehouseMachine unloadMachine, StationController destinationStation, List <CarsPerTrack> carsPerDestinationTrack, List <TrainCar> orderedTrainCars, List <CargoType> orderedCargoTypes, List <float> orderedCargoAmounts, bool forceCorrectCargoStateOnCars, float bonusTimeLimit, float initialWage, JobLicenses requiredLicenses) { Debug.Log(string.Format( "[PersistentJobs] unload: attempting to generate ChainJob[{0}]: {1} - {2}", JobType.ShuntingLoad, startingStation.logicStation.ID, destinationStation.logicStation.ID )); GameObject gameObject = new GameObject(string.Format( "ChainJob[{0}]: {1} - {2}", JobType.ShuntingUnload, startingStation.logicStation.ID, destinationStation.logicStation.ID )); gameObject.transform.SetParent(destinationStation.transform); JobChainControllerWithEmptyHaulGeneration jobChainController = new JobChainControllerWithEmptyHaulGeneration(gameObject); StationsChainData chainData = new StationsChainData( startingStation.stationInfo.YardID, destinationStation.stationInfo.YardID); jobChainController.trainCarsForJobChain = orderedTrainCars; Dictionary <CargoType, List <(TrainCar, float)> > cargoTypeToTrainCarAndAmount = new Dictionary <CargoType, List <(TrainCar, float)> >(); for (int i = 0; i < orderedTrainCars.Count; i++) { if (!cargoTypeToTrainCarAndAmount.ContainsKey(orderedCargoTypes[i])) { cargoTypeToTrainCarAndAmount[orderedCargoTypes[i]] = new List <(TrainCar, float)>(); } cargoTypeToTrainCarAndAmount[orderedCargoTypes[i]].Add((orderedTrainCars[i], orderedCargoAmounts[i])); } List <CarsPerCargoType> unloadData = cargoTypeToTrainCarAndAmount.Select( kvPair => new CarsPerCargoType( kvPair.Key, kvPair.Value.Select(t => t.Item1.logicCar).ToList(), kvPair.Value.Aggregate(0.0f, (sum, t) => sum + t.Item2))).ToList(); StaticShuntingUnloadJobDefinition staticShuntingUnloadJobDefinition = gameObject.AddComponent <StaticShuntingUnloadJobDefinition>(); staticShuntingUnloadJobDefinition.PopulateBaseJobDefinition( destinationStation.logicStation, bonusTimeLimit, initialWage, chainData, requiredLicenses); staticShuntingUnloadJobDefinition.startingTrack = startingTrack; staticShuntingUnloadJobDefinition.carsPerDestinationTrack = carsPerDestinationTrack; staticShuntingUnloadJobDefinition.unloadData = unloadData; staticShuntingUnloadJobDefinition.unloadMachine = unloadMachine; staticShuntingUnloadJobDefinition.forceCorrectCargoStateOnCars = forceCorrectCargoStateOnCars; jobChainController.AddJobDefinitionToChain(staticShuntingUnloadJobDefinition); return(jobChainController); }
private static StaticPassengerJobDefinition CreateSavedPassengerJob(GameObject jobChainGO, PassengerJobDefinitionData jobData) { // associated station if (!(GetStationWithId(jobData.stationId) is Station logicStation)) { PrintError($"Couldn't find corresponding Station with ID: {jobData.stationId}! Skipping load of this job chain!"); return(null); } // bonus time limit, base payment if (jobData.timeLimitForJob < 0f || jobData.initialWage < 0f || string.IsNullOrEmpty(jobData.originStationId) || string.IsNullOrEmpty(jobData.destinationStationId)) { PrintError("Invalid data! Skipping load of this job chain!"); return(null); } // license requirements if (!LicenseManager.IsValidForParsingToJobLicense(jobData.requiredLicenses)) { PrintError("Undefined job licenses requirement! Skipping load of this job chain!"); return(null); } // starting track if (!(GetYardTrackWithId(jobData.startingTrack) is Track startTrack)) { PrintError($"Couldn't find corresponding start Track with ID: {jobData.startingTrack}! Skipping load of this job chain!"); return(null); } // destination track if (!(GetYardTrackWithId(jobData.destinationTrack) is Track destTrack)) { PrintError($"Couldn't find corresponding destination Track with ID: {jobData.destinationTrack}! Skipping load of this job chain!"); return(null); } // consist if (!(GetCarsFromCarGuids(jobData.trainCarGuids) is List <Car> consist)) { PrintError("Couldn't find all carsToTransport with transportCarGuids! Skipping load of this job chain!"); return(null); } // loading platform WarehouseMachine loadMachine = null; if (!string.IsNullOrEmpty(jobData.loadingTrackId)) { if (PlatformManager.GetPlatformByTrackId(jobData.loadingTrackId) is PlatformDefinition pd) { loadMachine = pd.Controller.LogicMachine; } } // unloading platform WarehouseMachine unloadMachine = null; if (!string.IsNullOrEmpty(jobData.unloadingTrackId)) { if (PlatformManager.GetPlatformByTrackId(jobData.unloadingTrackId) is PlatformDefinition pd) { unloadMachine = pd.Controller.LogicMachine; } } // Named train SpecialTrain special = null; if (!string.IsNullOrWhiteSpace(jobData.specialName)) { special = SpecialConsistManager.TrainDefinitions.Find(train => string.Equals(train.Name, jobData.specialName)); } StaticPassengerJobDefinition jobDefinition = jobChainGO.AddComponent <StaticPassengerJobDefinition>(); var chainData = new StationsChainData(jobData.originStationId, jobData.destinationStationId); jobDefinition.PopulateBaseJobDefinition(logicStation, jobData.timeLimitForJob, jobData.initialWage, chainData, (JobLicenses)jobData.requiredLicenses); jobDefinition.subType = (JobType)jobData.subType; jobDefinition.startingTrack = startTrack; jobDefinition.destinationTrack = destTrack; jobDefinition.trainCarsToTransport = consist; jobDefinition.loadMachine = loadMachine; jobDefinition.unloadMachine = unloadMachine; jobDefinition.specialDefinition = special; return(jobDefinition); }
private static JobChainController CreateSavedJobChain(PassengerChainSaveData passChainData) { // Figure out chain type PassengerChainSaveData.PassChainType chainType = passChainData.ChainType; if (InitializeCorrespondingJobBooklet == null) { PrintError("Failed to connect to JobSaveManager methods"); return(null); } List <TrainCar> trainCarsFromCarGuids = GetTrainCarsFromCarGuids(passChainData.trainCarGuids); if (trainCarsFromCarGuids == null) { PrintError("Couldn't find trainCarsForJobChain with trainCarGuids from chainSaveData! Skipping load of this job chain!"); return(null); } var jobChainGO = new GameObject(); JobChainController chainController; if (chainType == PassengerChainSaveData.PassChainType.Transport) { // PASSENGER TRANSPORT (EXPRESS) CHAIN StaticJobDefinition jobDefinition; StationsChainData chainData = null; chainController = new PassengerTransportChainController(jobChainGO) { trainCarsForJobChain = trainCarsFromCarGuids }; foreach (JobDefinitionDataBase jobData in passChainData.jobChainData) { if (jobData is PassengerJobDefinitionData pjData) { jobDefinition = CreateSavedPassengerJob(jobChainGO, pjData); jobDefinition?.ForceJobId(passChainData.firstJobId); chainData = new StationsChainData(jobData.originStationId, jobData.destinationStationId); } else { PrintError("Express pax chain contains invalid job type"); return(null); } if (jobDefinition == null) { PrintError("Failed to generate job definition from save data"); UnityEngine.Object.Destroy(jobChainGO); return(null); } chainController.AddJobDefinitionToChain(jobDefinition); } jobChainGO.name = $"[LOADED] ChainJob[Passenger]: {chainData.chainOriginYardId} - {chainData.chainDestinationYardId}"; } else { // COMMUTER JOB CHAIN StaticJobDefinition jobDefinition; StationsChainData chainData = null; bool first = true; chainController = new CommuterChainController(jobChainGO) { trainCarsForJobChain = trainCarsFromCarGuids }; foreach (JobDefinitionDataBase jobDataBase in passChainData.jobChainData) { if (jobDataBase is PassengerJobDefinitionData jobData) { jobDefinition = CreateSavedPassengerJob(jobChainGO, jobData); if (first) { chainData = new StationsChainData(jobData.originStationId, jobData.destinationStationId); jobDefinition?.ForceJobId(passChainData.firstJobId); first = false; } } else { PrintError("Commuter chain contains invalid job type"); return(null); } if (jobDefinition == null) { PrintError("Failed to generate job definition from save data"); UnityEngine.Object.Destroy(jobChainGO); return(null); } chainController.AddJobDefinitionToChain(jobDefinition); } jobChainGO.name = $"[LOADED] ChainJob[Commuter]: {chainData.chainOriginYardId} - {chainData.chainDestinationYardId}"; } 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 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); }