private static void SetToIdealTier(IColonizationResearchScenario colonizationResearch, IEnumerable <ITieredProducer> producers) { foreach (var producer in producers) { StaticAnalysis.SetToIdealTier(colonizationResearch, producer); } }
private static List <ProducerData> FindProducers( List <ITieredProducer> producers, IColonizationResearchScenario colonizationScenario, Dictionary <string, double> availableResources, Dictionary <string, double> availableStorage) { // First run through the producers to find out who can contribute what List <ProducerData> productionPossibilities = new List <ProducerData>(); foreach (ITieredProducer producer in producers) { if (producer.IsProductionEnabled) { ProducerData data = productionPossibilities.FirstOrDefault( pp => pp.SourceTemplate.GetType() == producer.GetType() && pp.SourceTemplate.Tier == producer.Tier && pp.SourceTemplate.Output == producer.Output); if (data == null) { data = new ProducerData { SourceTemplate = producer, }; productionPossibilities.Add(data); } data.TotalProductionCapacity += producer.ProductionRate; if (producer.IsResearchEnabled) { data.ProductionContributingToResearch += producer.ProductionRate; } data.IsStockpiling |= availableStorage.ContainsKey(producer.Output.TieredName(producer.Tier)); data.IsStockpiling |= producer.Output.ExcessProductionCountsTowardsResearch; } } foreach (var pair in availableResources) { string storedResource = pair.Key; double amount = pair.Value; // TODO: Come up with a better way to filter it down to the resources that we care about. if (colonizationScenario.TryParseTieredResourceName(storedResource, out TieredResource resource, out TechTier tier)) { ProducerData resourceProducer = new ProducerData { SourceTemplate = new StorageProducer(resource, tier, amount), IsStockpiling = false, ProductionContributingToResearch = 0, TotalProductionCapacity = double.MaxValue, }; productionPossibilities.Add(resourceProducer); } } return(productionPossibilities); }
private static void SetToIdealTier(IColonizationResearchScenario colonizationResearch, ITieredProducer producer) { for (TechTier tier = TechTier.Tier4; tier >= TechTier.Tier0; --tier) { var suitability = StaticAnalysis.GetTierSuitability(colonizationResearch, producer.Output, tier, producer.MaximumTier, producer.Body); if (suitability == TierSuitability.Ideal) { producer.Tier = tier; break; } } }
internal static IEnumerable <WarningMessage> CheckRoverHasTwoSeats(IColonizationResearchScenario colonizationResearch, List <ITieredProducer> producers, Dictionary <string, double> amountAvailable, Dictionary <string, double> storageAvailable, int maxCrewCapacity) { bool isCrushinRover = producers.Any(p => p.Output == colonizationResearch.CrushInsResource); if (isCrushinRover && maxCrewCapacity < 2) { yield return(new WarningMessage { Message = "For this craft to be useable as an automated miner, it needs at least two seats -- " + "one for a miner and one for a pilot.", IsClearlyBroken = false, FixIt = null }); } }
internal static IEnumerable <WarningMessage> CheckHasRoverPilot(IColonizationResearchScenario colonizationResearch, List <ITieredProducer> producers, Dictionary <string, double> amountAvailable, Dictionary <string, double> storageAvailable, List <SkilledCrewman> crew) { bool needsRoverPilot = producers.Any(p => p.Input == colonizationResearch.CrushInsResource); if (needsRoverPilot && !crew.Any(c => c.CanPilotRover())) { yield return(new WarningMessage { Message = $"To ensure you can use automated mining (via a separate mining craft), you need to have " + $"a pilot at the base to drive it.", IsClearlyBroken = false, FixIt = null }); } }
internal static IEnumerable <WarningMessage> CheckHasSomeFood(IColonizationResearchScenario colonizationResearch, List <ITieredProducer> producers, Dictionary <string, double> amountAvailable, Dictionary <string, double> storageAvailable, List <SkilledCrewman> crew) { bool needsFood = crew.Any() || producers.Any(); bool hasStoredFood = (amountAvailable.TryGetValue("Snacks-Tier4", out var amount) && amount > 0); bool producesFood = producers.Any(p => p.Output.IsEdible && p.Output.GetPercentOfDietByTier(p.Tier) == 1); if (needsFood && !hasStoredFood && !producesFood) { yield return(new WarningMessage { Message = $"There's no Snacks on this vessel - the crew will get angry after {LifeSupportScenario.DaysBeforeKerbalStarves} days", IsClearlyBroken = false, FixIt = null }); } }
internal static IEnumerable <WarningMessage> CheckTieredProductionStorage(IColonizationResearchScenario colonizationResearch, List <ITieredProducer> producers, Dictionary <string, double> amountAvailable, Dictionary <string, double> storageAvailable) { HashSet <string> missingStorageComplaints = new HashSet <string>(); foreach (ITieredProducer producer in producers) { if (producer.Output.CanBeStored && (!storageAvailable.TryGetValue(producer.Output.TieredName(producer.Tier), out var available) || available == 0)) { missingStorageComplaints.Add($"This craft is producing {producer.Output.TieredName(producer.Tier)} but there's no storage for it."); } } return(missingStorageComplaints.OrderBy(s => s).Select(s => new WarningMessage { Message = s, FixIt = null, IsClearlyBroken = false })); }
internal static IEnumerable <WarningMessage> CheckCorrectCapacity(IColonizationResearchScenario colonizationResearch, List <ITieredProducer> producers, Dictionary <string, double> amountAvailable, Dictionary <string, double> storageAvailable) { var production = producers .GroupBy(p => p.Output) .ToDictionary(pair => pair.Key, pair => pair.Sum(p => p.ProductionRate)); var consumption = producers .Where(p => p.Input != null) .GroupBy(p => p.Input) .ToDictionary(pair => pair.Key, pair => pair.Sum(p => p.ProductionRate)); foreach (var inputPair in consumption) { TieredResource inputResource = inputPair.Key; double inputRequired = inputPair.Value; if (inputResource.IsHarvestedLocally) { // Crush-ins -- there are other things that ensure this works. } else if (!production.TryGetValue(inputPair.Key, out double outputAmount)) { // Okay, there's no producer for this - complain if there's no storage that either contains the // required tier or could contain it if it's gathered locally. TechTier requiredTier = producers.Where(p => p.Input == inputResource).Select(p => p.Tier).Min(); bool anyInStorage = Enumerable.Range((int)requiredTier, 1 + (int)TechTier.Tier4) .Any(i => amountAvailable.TryGetValue(inputResource.TieredName((TechTier)i), out var amount) && amount > 0); if (!inputResource.IsHarvestedLocally && !anyInStorage) { yield return(new WarningMessage() { Message = $"The ship needs {inputResource.BaseName} to produce {producers.First(p => p.Input == inputResource).Output.BaseName}", IsClearlyBroken = false, FixIt = null }); } } else if (outputAmount < inputRequired) { yield return(new WarningMessage() { Message = $"The ship needs at least {inputRequired} production of {inputResource.BaseName} but it is only producing {outputAmount}", IsClearlyBroken = false, FixIt = null }); } } }
internal static IEnumerable <WarningMessage> CheckBodyIsSet(IColonizationResearchScenario colonizationResearch, List <ITieredProducer> producers, Dictionary <string, double> amountAvailable, Dictionary <string, double> storageAvailable) { // Check for body parts List <ITieredProducer> bodySpecific = producers.Where(c => c.Output.ProductionRestriction != ProductionRestriction.Space).ToList(); var mostUsedBodyAndCount = bodySpecific .Where(c => c.Body != null) .GroupBy(c => c.Body) .Select(g => new { body = g.Key, count = g.Count() }) .OrderByDescending(o => o.count) .FirstOrDefault(); var mostUsedBody = mostUsedBodyAndCount?.body; int? numSetToMostUsed = mostUsedBodyAndCount?.count; int numNotSet = bodySpecific.Count(c => c.Body == null); Action fixIt = mostUsedBody == null ? (Action)null : () => { foreach (var producer in producers.Where(c => c.Output.ProductionRestriction != ProductionRestriction.Space)) { if (producer.Body != mostUsedBody) { producer.Body = mostUsedBody; } } }; if (numNotSet + numSetToMostUsed < bodySpecific.Count) { yield return(new WarningMessage { Message = $"Not all of the body-specific parts are set up for {mostUsedBody}", IsClearlyBroken = true, FixIt = fixIt }); } else if (numNotSet > 0) { yield return(new WarningMessage { Message = "Need to set up the target for the world-specific parts", IsClearlyBroken = true, FixIt = fixIt }); } }
internal static IEnumerable <WarningMessage> CheckCombiners(IColonizationResearchScenario colonizationResearch, List <ITieredProducer> producers, List <ITieredCombiner> combiners, Dictionary <string, double> amountAvailable, Dictionary <string, double> storageAvailable) { foreach (ITieredCombiner combinerWithMissingInput in combiners .GroupBy(c => c.NonTieredInputResourceName + c.NonTieredOutputResourceName) .Select(pair => pair.First()) .Where(n => !amountAvailable.TryGetValue(n.NonTieredInputResourceName, out var amount) || amount == 0)) { yield return(new WarningMessage { Message = $"To produce {combinerWithMissingInput.NonTieredOutputResourceName} you will need to bring some {combinerWithMissingInput.NonTieredInputResourceName}.", IsClearlyBroken = false, FixIt = null }); } foreach (ITieredCombiner combinerWithNoOutputStorage in combiners .GroupBy(c => c.NonTieredOutputResourceName) .Select(pair => pair.First()) .Where(n => !storageAvailable.TryGetValue(n.NonTieredOutputResourceName, out var amount) || amount == 0)) { yield return(new WarningMessage { Message = $"There's no place to put the {combinerWithNoOutputStorage.NonTieredOutputResourceName} this base is producing.", IsClearlyBroken = false, FixIt = null }); } foreach (ITieredCombiner combinerWithNoTieredInput in combiners .GroupBy(c => c.NonTieredInputResourceName + c.NonTieredOutputResourceName) .Select(pair => pair.First()) .Where(c => !producers.Any(p => p.Output == c.TieredInput))) { yield return(new WarningMessage { Message = $"To produce {combinerWithNoTieredInput.NonTieredOutputResourceName}, you need produce {combinerWithNoTieredInput.TieredInput.BaseName} as input.", IsClearlyBroken = false, FixIt = null }); } }
internal static IEnumerable <WarningMessage> CheckExtraBaggage(IColonizationResearchScenario colonizationResearch, List <ITieredProducer> producers, Dictionary <string, double> amountAvailable, Dictionary <string, double> storageAvailable) { HashSet <string> bannedBaggageComplaints = new HashSet <string>(); HashSet <string> extraBaggageComplaints = new HashSet <string>(); foreach (var pair in amountAvailable) { if (pair.Value == 0) { continue; } if (pair.Value > 0 && colonizationResearch.TryParseTieredResourceName(pair.Key, out var resource, out var tier)) { if (resource.GetReputationGain(tier, 100) != 0 || resource.IsHarvestedLocally) { bannedBaggageComplaints.Add(pair.Key); } else if (tier != TechTier.Tier4) { extraBaggageComplaints.Add(pair.Key); } } } return(extraBaggageComplaints .OrderBy(s => s).Select(s => new WarningMessage { Message = $"This vessel is carrying {s}. Usually that kind of cargo is produced, so likely there's no point in carrying it into orbit with you. You should probably empty those containers.", FixIt = () => UnloadCargo(s), IsClearlyBroken = false }) .Union(bannedBaggageComplaints.OrderBy(s => s) .Select(s => new WarningMessage { Message = $"This vessel is carrying {s}. That kind of cargo needs to be produced locally and can't be produced on Kerbin", FixIt = () => UnloadCargo(s), IsClearlyBroken = true }))); }
internal static IEnumerable <WarningMessage> CheckHasCrushinStorage(IColonizationResearchScenario colonizationResearch, List <ITieredProducer> producers, Dictionary <string, double> amountAvailable, Dictionary <string, double> storageAvailable) { var drills = producers .Where(p => p.Input == colonizationResearch.CrushInsResource) .ToArray(); if (drills.Length > 0) { double totalDrillCapacity = drills.Sum(p => p.ProductionRate); double crushinsRequired = totalDrillCapacity * SnackConsumption.DrillCapacityMultiplierForAutomaticMiningQualification; storageAvailable.TryGetValue(drills[0].Input.TieredName(drills[0].Tier), out double totalCrushinStorage); if (totalCrushinStorage < crushinsRequired) { // not enough storage yield return(new WarningMessage { Message = $"To ensure you can use automated mining (via a separate mining craft), you need to have " + $"storage for at least {crushinsRequired} {colonizationResearch.CrushInsResource.BaseName}. " + "You will also need to send a craft capable of mining it (which will be found in " + "scattered locations around the body using your orbital scanner) and bringing them " + "back to the base.", IsClearlyBroken = false, FixIt = null }); } else { yield return(new WarningMessage { Message = $"To ensure you can use automated mining (via a separate mining craft), you need to have " + $"a craft capable of mining and delivering {crushinsRequired} {colonizationResearch.CrushInsResource.BaseName}.", IsClearlyBroken = false, FixIt = null }); } } }
/// <summary> /// Calculates production and snack consumption, using the production capacity available /// on the vessel. It takes in <paramref name="fullTimespanInSeconds"/>, but it actually /// calculates only up until the first resource runs out (e.g. either fertilizer or /// snacks). The output variable, <paramref name="timePassedInSeconds"/> will be set to the /// amount of time until that event happened, so you need to call this method in a /// loop until you get a result where <paramref name="timePassedInSeconds"/> == <paramref name="fullTimespanInSeconds"/> /// or <paramref name="timePassedInSeconds"/> == 0 (which implies that there wasn't enough food /// to go around). /// </summary> /// <param name="numCrew">The number of mouths to feed.</param> /// <param name="fullTimespanInSeconds">The full time that has passed that we're looking to calculate for.</param> /// <param name="producers">The mechanisms on the vessel that can produce tiered stuff.</param> /// <param name="colonizationResearch">The research object (passed in for testability).</param> /// <param name="timePassedInSeconds">Set to the amount of <paramref name="fullTimespanInSeconds"/> that we've managed to calculate for.</param> /// <param name="breakthroughHappened">Set to true if the agronomy tier was eclipsed in the timespan.</param> /// <param name="consumptionFormula">Set to a formula for calculating the transformation of stuff.</param> /// <remarks> /// Simplifying production chain assumptions: /// <list type="bullet"> /// <item>Producers that require a source item require at most one of them.</item> /// <item>Producers produce at exactly the same rate as they consume.</item> /// <item>All producers require fed kerbals to operate, regardless of whether they have /// directly to do with food or not.</item> /// </list> /// These simplifying assumptions not only make the game easier to code, but easier to play as well. /// </remarks> public static void CalculateResourceUtilization( int numCrew, double fullTimespanInSeconds, List <ITieredProducer> producers, List <ITieredCombiner> combiners, IColonizationResearchScenario colonizationResearch, Dictionary <string, double> availableResources, Dictionary <string, double> availableStorage, out double timePassedInSeconds, out List <TieredResource> breakthroughs, out Dictionary <string, double> resourceConsumptionPerSecond, out Dictionary <string, double> resourceProductionPerSecond, out IEnumerable <string> limitingFactors, out Dictionary <string, double> unusedProduction) { // The mechanic we're after writes up pretty simple - Kerbals will try to use renewable // resources first, then they start in on the on-board stocks, taking as much of the low-tier // stuff as they can. Implementing that is tricky... // <--cacheable start // The stuff from here to 'cacheable end' could be stored - we'd just have to check to // see that 'availableResources.Keys' and 'availableStorage.Keys' are the same. // First just get a handle on what stuff we could produce. var limitMap = new Dictionary <string, string>(); unusedProduction = new Dictionary <string, double>(); List <ProducerData> producerInfos = FindProducers(producers, colonizationResearch, availableResources, availableStorage); SortProducerList(producerInfos); MatchProducersWithSourceProducers(producerInfos, limitMap, unusedProduction); Dictionary <TieredResource, AmalgamatedCombiners> inputToCombinerMap = combiners .Where(c => c.IsProductionEnabled) .GroupBy(c => c.TieredInput) .ToDictionary(pair => pair.Key, pair => new AmalgamatedCombiners(pair)); List <FoodProducer> snackProducers = GetFoodProducers(producerInfos); double ratioFulfilled = 0; foreach (FoodProducer foodProducer in snackProducers) { if (foodProducer.MaxDietRatio > ratioFulfilled) { double amountAskedFor = numCrew * (foodProducer.MaxDietRatio - ratioFulfilled); double amountReceived = foodProducer.ProductionChain.TryToProduce(amountAskedFor, limitMap); ratioFulfilled += amountReceived / numCrew; } } if (ratioFulfilled < (1 - AcceptableError)) { List <FoodProducer> snackStorage = GetFoodStorage(producerInfos); foreach (FoodProducer foodProducer in snackStorage) { if (foodProducer.MaxDietRatio > ratioFulfilled + AcceptableError) { double amountAskedFor = numCrew * (foodProducer.MaxDietRatio - ratioFulfilled); double amountReceived = foodProducer.ProductionChain.TryToProduce(amountAskedFor, limitMap); ratioFulfilled += amountReceived / numCrew; } } } if (ratioFulfilled < (1 - AcceptableError)) { // We couldn't put together a production plan that will satisfy all of the Kerbals // needs for any amount of time. timePassedInSeconds = 0; resourceConsumptionPerSecond = null; resourceProductionPerSecond = null; breakthroughs = null; limitingFactors = SquishLimitMap(limitMap); unusedProduction = null; return; } resourceProductionPerSecond = new Dictionary <string, double>(); resourceConsumptionPerSecond = new Dictionary <string, double>(); // Okay, now we know what the minimum plan is that will keep our kerbals fed. // See what goes into tiered->untiered converters foreach (ProducerData producerData in producerInfos) { if (inputToCombinerMap.TryGetValue(producerData.SourceTemplate.Output, out AmalgamatedCombiners combiner) && availableStorage.ContainsKey(combiner.NonTieredOutputResourceName)) { if (availableResources.ContainsKey(combiner.NonTieredInputResourceName)) { double productionRatio = combiner.GetRatioForTier(producerData.SourceTemplate.Tier); double suppliesWanted = (combiner.ProductionRate - combiner.UsedCapacity) * productionRatio; double applicableConverterCapacity = producerData.TryToProduce(suppliesWanted, limitMap); combiner.UsedCapacity -= applicableConverterCapacity; double usedResourcesRate = combiner.ProductionRate * (applicableConverterCapacity / suppliesWanted) * (1 - productionRatio); combiner.RequiredMixins += usedResourcesRate; double producedResourcesRate = (applicableConverterCapacity / suppliesWanted) * combiner.ProductionRate; AddTo(resourceProductionPerSecond, combiner.NonTieredOutputResourceName, UnitsPerDayToUnitsPerSecond(producedResourcesRate)); AddTo(resourceConsumptionPerSecond, combiner.NonTieredInputResourceName, UnitsPerDayToUnitsPerSecond(usedResourcesRate)); } else { limitMap.Add(combiner.NonTieredOutputResourceName, combiner.NonTieredInputResourceName); } } } // Augment that with stockpiling foreach (ProducerData producerData in producerInfos) { string resourceName = producerData.SourceTemplate.Output.TieredName(producerData.SourceTemplate.Tier); if (availableStorage.ContainsKey(resourceName) && !(producerData.SourceTemplate is StorageProducer)) { double stockpiledPerDay = producerData.TryToProduce(double.MaxValue, limitMap); double stockpiledPerSecond = UnitsPerDayToUnitsPerSecond(stockpiledPerDay); AddTo(resourceProductionPerSecond, resourceName, stockpiledPerSecond); } else if (producerData.SourceTemplate.Output.ExcessProductionCountsTowardsResearch) { // Just run the machine producerData.TryToProduce(double.MaxValue, limitMap); } } // <<-- end cacheable // Figure out how much time we can run with this production plan before the stores run out timePassedInSeconds = fullTimespanInSeconds; foreach (ProducerData producerData in producerInfos) { if (producerData.SourceTemplate is StorageProducer storage) { // producerData.AllottedCapacity is the amount consumed per day under our plan // storage.Amount is what we have on-hand double amountUsedPerSecond = UnitsPerDayToUnitsPerSecond(producerData.AllottedCapacity); if (amountUsedPerSecond > 0) { AddTo(resourceConsumptionPerSecond, storage.Output.TieredName(storage.Tier), amountUsedPerSecond); } } } foreach (var consumedPair in resourceConsumptionPerSecond) { double storageAmount = availableResources[consumedPair.Key]; double secondsToEmpty = (storageAmount / consumedPair.Value); timePassedInSeconds = Math.Min(timePassedInSeconds, secondsToEmpty); } // ...or before the storage space is packed foreach (var pair in resourceProductionPerSecond) { string resourceName = pair.Key; double amountStockpiledPerSecond = pair.Value; double secondsToFilled = availableStorage[resourceName] / amountStockpiledPerSecond; timePassedInSeconds = Math.Min(timePassedInSeconds, secondsToFilled); } // Okay, finally now we can apply the work done towards research breakthroughs = new List <TieredResource>(); foreach (ProducerData producerData in producerInfos) { double contributionInKerbals = Math.Min(producerData.AllottedCapacity, producerData.ProductionContributingToResearch); if (contributionInKerbals > 0) { // If we have some doodads with research associated with it and some not, then // what we want to do is make sure that the labs with research turned on do // all the work they can. if (producerData.SourceTemplate.ContributeResearch( colonizationResearch, timePassedInSeconds * contributionInKerbals /* convert to kerbal-seconds */)) { breakthroughs.Add(producerData.SourceTemplate.Output); } } } limitingFactors = SquishLimitMap(limitMap); foreach (var pi in producerInfos .Where(pi => !(pi.SourceTemplate is StorageProducer)) .Where(pi => pi.TotalProductionCapacity - pi.AllottedCapacity > AcceptableError)) { unusedProduction[pi.SourceTemplate.Output.TieredName(pi.SourceTemplate.Tier)] = pi.TotalProductionCapacity - pi.AllottedCapacity; } }
internal static IEnumerable <WarningMessage> CheckTieredProduction(IColonizationResearchScenario colonizationResearch, List <ITieredProducer> producers, Dictionary <string, double> amountAvailable, Dictionary <string, double> storageAvailable) { List <ITieredProducer> noScannerParts = new List <ITieredProducer>(); List <ITieredProducer> noSubResearchParts = new List <ITieredProducer>(); List <ITieredProducer> underTier = new List <ITieredProducer>(); foreach (var producer in producers) { var suitability = StaticAnalysis.GetTierSuitability(colonizationResearch, producer.Output, producer.Tier, producer.MaximumTier, producer.Body); switch (suitability) { case TierSuitability.LacksScanner: noScannerParts.Add(producer); break; case TierSuitability.LacksSubordinateResearch: noSubResearchParts.Add(producer); break; case TierSuitability.UnderTier: underTier.Add(producer); break; default: break; } } if (noScannerParts.Any()) { var examplePart = noScannerParts[0]; yield return(new WarningMessage { Message = $"Scanning technology at {examplePart.Body} has not kept up with production technologies - {examplePart.Tier.DisplayName()} parts will not function until you deploy an equal-tier scanner to orbit around {examplePart.Body}.", IsClearlyBroken = true, FixIt = () => SetToIdealTier(colonizationResearch, noScannerParts) }); } if (noSubResearchParts.Any()) { var examplePart = noSubResearchParts[0]; yield return(new WarningMessage { Message = $"Not all the products in the production chain for {examplePart.Output.DisplayName} have advanced to {examplePart.Tier}.", IsClearlyBroken = true, FixIt = () => SetToIdealTier(colonizationResearch, noSubResearchParts) }); } if (underTier.Any()) { var examplePart = underTier[0]; yield return(new WarningMessage { Message = $"This base is not taking advantage of the latest tech for producing {examplePart.Output.DisplayName}", IsClearlyBroken = true, FixIt = () => SetToIdealTier(colonizationResearch, underTier) }); } var mostUsedBodyAndCount = producers .Where(c => c.Output.ProductionRestriction != ProductionRestriction.Space) .Where(c => c.Body != null) .GroupBy(c => c.Body) .Select(g => new { body = g.Key, count = g.Count() }) .OrderByDescending(o => o.count) .ToArray(); string targetBody = mostUsedBodyAndCount.Length > 0 ? mostUsedBodyAndCount[0].body : null; foreach (var pair in producers.GroupBy(producer => producer.Output)) { TieredResource output = pair.Key; IEnumerable <ITieredProducer> parts = pair; // Parts should be set consistently TechTier minTier = parts.Min(p => p.Tier); TechTier maxTier = parts.Max(p => p.Tier); if (minTier != maxTier) { yield return(new WarningMessage { Message = $"Not all of the parts producing {output.BaseName} are set at {maxTier}", IsClearlyBroken = false, FixIt = () => { foreach (var part in parts) { part.Tier = maxTier; } } }); break; } // Supplier parts should be at least maxTier var firstPart = parts.First(); TieredResource input = firstPart.Input; if (parts.First().Input == null && output.IsHarvestedLocally && targetBody != null) { // then it depends on scanning TechTier maxScanningTier = colonizationResearch.GetMaxUnlockedScanningTier(targetBody); if (maxTier > maxScanningTier) { } } else if (input != null) { // Ensure that the suppliers are all at least the same tier. if (producers.Any(producer => producer.Output == input && producer.Tier < maxTier)) { yield return(new WarningMessage { Message = $"There are {maxTier.DisplayName()} producers of {output.BaseName}, but it requires equal-tier {input.BaseName} production in order to work.", IsClearlyBroken = true, FixIt = null }); } } } }
internal static TierSuitability GetTierSuitability( IColonizationResearchScenario colonizationResearchScenario, TieredResource tieredResource, TechTier tier, TechTier partMaxTier, string body) { if (body == null && tieredResource.ResearchCategory.Type != ProductionRestriction.Space) { return(TierSuitability.BodyNotSelected); } if (tier > partMaxTier) { return(TierSuitability.PartDoesntSupportTier); } var maxTier = colonizationResearchScenario.GetMaxUnlockedTier(tieredResource, body); if (tier > maxTier) { return(TierSuitability.NotResearched); } bool subordinateTechIsCapping = false; for (TieredResource requiredResource = tieredResource.MadeFrom(tier); requiredResource != null; requiredResource = requiredResource.MadeFrom(tier)) { if (requiredResource.ResearchCategory.Type != tieredResource.ResearchCategory.Type) { // This would be a case where the made-from is produced in one situation (e.g. landed) and consumed // in another (in space). We don't know where the stuff is produced, so we'll just have to assume // we can get the stuff from somewhere. break; } var t = colonizationResearchScenario.GetMaxUnlockedTier(requiredResource, body); if (tier > t) { return(TierSuitability.LacksSubordinateResearch); } else if (tier == t) { subordinateTechIsCapping = true; } } TechTier maxScanningTier = string.IsNullOrEmpty(body) ? TechTier.Tier4 : colonizationResearchScenario.GetMaxUnlockedScanningTier(body); if (tier > maxScanningTier) { return(TierSuitability.LacksScanner); } if (tier < maxTier && !subordinateTechIsCapping && (string.IsNullOrEmpty(body) || tier < maxScanningTier)) { return(TierSuitability.UnderTier); } return(TierSuitability.Ideal); }
public bool ContributeResearch(IColonizationResearchScenario target, double amount) { return(target.ContributeResearch(this.Output, this.Body, amount)); }
private static bool IsCrushinResource(IColonizationResearchScenario researchScenario, string resourceName) { return(researchScenario.TryParseTieredResourceName(resourceName, out TieredResource tieredResource, out var _) && tieredResource == researchScenario.CrushInsResource); }
public bool ContributeResearch(IColonizationResearchScenario target, double amount) => throw new NotImplementedException();
public bool ContributeResearch(IColonizationResearchScenario target, double amount) => target.ContributeResearch(this.Output, "test", amount);