Exemple #1
0
 private static void SetToIdealTier(IColonizationResearchScenario colonizationResearch, IEnumerable <ITieredProducer> producers)
 {
     foreach (var producer in producers)
     {
         StaticAnalysis.SetToIdealTier(colonizationResearch, producer);
     }
 }
Exemple #2
0
        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);
        }
Exemple #3
0
 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;
         }
     }
 }
Exemple #4
0
        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
                });
            }
        }
Exemple #5
0
        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
                });
            }
        }
Exemple #6
0
        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
                });
            }
        }
Exemple #7
0
        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
            }));
        }
Exemple #8
0
        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
                    });
                }
            }
        }
Exemple #9
0
        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
                });
            }
        }
Exemple #10
0
        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
                });
            }
        }
Exemple #11
0
        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
            })));
        }
Exemple #12
0
        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
                    });
                }
            }
        }
Exemple #13
0
        /// <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;
            }
        }
Exemple #14
0
        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
                        });
                    }
                }
            }
        }
Exemple #15
0
        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);
        }
Exemple #16
0
 public bool ContributeResearch(IColonizationResearchScenario target, double amount)
 {
     return(target.ContributeResearch(this.Output, this.Body, amount));
 }
Exemple #17
0
 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();
Exemple #19
0
 public bool ContributeResearch(IColonizationResearchScenario target, double amount)
 => target.ContributeResearch(this.Output, "test", amount);