private void RecalculateResults() { List <Part> parts = EditorLogic.fetch.ship.Parts; List <ITieredProducer> producers = parts .Select(p => p.FindModuleImplementing <ITieredProducer>()) .Where(p => p != null) .ToList(); List <ITieredCombiner> combiners = parts .Select(p => p.FindModuleImplementing <ITieredCombiner>()) .Where(p => p != null) .ToList(); Dictionary <string, double> amountAvailable = parts .SelectMany(p => p.Resources) .GroupBy(r => r.resourceName) .ToDictionary(g => g.Key, g => g.Sum(r => r.amount)); Dictionary <string, double> storageAvailable = parts .SelectMany(p => p.Resources) .GroupBy(r => r.resourceName) .ToDictionary(g => g.Key, g => g.Sum(r => r.maxAmount - r.amount)); const double aWholeLot = 10000.0; Dictionary <string, double> unlimitedAmounts = amountAvailable.ToDictionary(pair => pair.Key, pair => aWholeLot); foreach (var producer in producers) { if (producer.Input != null && producer.Input.IsHarvestedLocally) { unlimitedAmounts[producer.Input.TieredName(producer.Tier)] = aWholeLot; } } int crewCount = KSP.UI.CrewAssignmentDialog.Instance.GetManifest(false).CrewCount; ResearchSink researchSink = new ResearchSink(); TieredProduction.CalculateResourceUtilization( crewCount, 1.0, producers, combiners, researchSink, unlimitedAmounts, unlimitedAmounts, out double timePassed, out var _, out Dictionary <string, double> resourcesConsumedPerSecond, out Dictionary <string, double> resourcesProducedPerSecond, out IEnumerable <string> limitingResources, out Dictionary <string, double> unusedProduction); if (timePassed < 1.0) { this.consumptionInfo = "-"; this.productionInfo = "-"; this.productionLimitedBy = string.Join(", ", limitingResources.ToArray()); return; } StringBuilder consumption = new StringBuilder(); foreach (var pair in resourcesConsumedPerSecond.OrderBy(pair => pair.Key)) { var name = pair.Key; var amountPerDay = TieredProduction.UnitsPerSecondToUnitsPerDay(pair.Value); consumption.Append($"{amountPerDay:N1} {name}/day"); if (ColonizationResearchScenario.Instance.TryParseTieredResourceName(name, out TieredResource resource, out TechTier tier) && resource.IsHarvestedLocally) { consumption.Append(" - will need to be fetched from a resource lode"); }
internal static void BuildStatusStrings( bool isAutoMining, string minerStatusMessage, bool anyCrewDeficiencies, bool anyDisabledParts, SnackConsumption activeSnackConsumption, Dictionary <string, double> resources, Dictionary <string, double> storage, List <ITieredProducer> tieredProducers, List <ITieredCombiner> tieredCombiners, int crewCount, int crewDelta, out string introLineMessage, out string productionMessage, out string unusedCapacityMessage, out string limitedByMessage, out List <ResearchData> progress) { ResearchSink researchSink = new ResearchSink(); TieredProduction.CalculateResourceUtilization( crewCount + crewDelta, 1, tieredProducers, tieredCombiners, researchSink, resources, storage, out double timePassed, out var _, out Dictionary <string, double> resourcesConsumed, out Dictionary <string, double> resourcesProduced, out IEnumerable <string> limitingResources, out Dictionary <string, double> unusedProduction); if (timePassed == 0) { var introMessageBuilder = new StringBuilder(); if (!activeSnackConsumption.IsAtHome) { Dictionary <int, List <ProtoCrewMember> > buckets = new Dictionary <int, List <ProtoCrewMember> >(); foreach (var crew in activeSnackConsumption.Vessel.GetVesselCrew()) { var kerbalIsKnown = LifeSupportScenario.Instance.TryGetStatus(crew, out double daysSinceMeal, out double daysToGrouchy, out bool isGrouchy); if (!kerbalIsKnown) { // Maybe if ! on kerban we complain about this? // Debug.LogError($"Couldn't find a life support record for {crew.name}"); } int bucketKey = isGrouchy ? -1 : (int)daysToGrouchy; if (!buckets.TryGetValue(bucketKey, out var crewInBucket)) { crewInBucket = new List <ProtoCrewMember>(); buckets.Add(bucketKey, crewInBucket); } crewInBucket.Add(crew); } CrewBlurbs.random = new System.Random(FlightGlobals.ActiveVessel.GetHashCode()); foreach (List <ProtoCrewMember> crewInBucket in buckets.Values) { // yeah yeah, recomputing this is wasteful & all... LifeSupportScenario.Instance.TryGetStatus(crewInBucket[0], out double daysSinceMeal, out double daysToGrouchy, out bool isGrouchy); if (isGrouchy) { introMessageBuilder.AppendLine(TextEffects.Red(CrewBlurbs.StarvingKerbals(crewInBucket))); } else if (daysToGrouchy < 2) { introMessageBuilder.AppendLine(CrewBlurbs.GrumpyKerbals(crewInBucket, daysToGrouchy, tieredProducers.Any())); } else { introMessageBuilder.AppendLine(CrewBlurbs.HungryKerbals(crewInBucket, daysToGrouchy, tieredProducers.Any())); } } if (tieredProducers.Any()) { introMessageBuilder.AppendLine(); introMessageBuilder.AppendLine("Nothing can be produced at this base until the snack situation gets fixed."); } } introLineMessage = introMessageBuilder.ToString(); productionMessage = null; unusedCapacityMessage = null; limitedByMessage = null; progress = new List <ResearchData>(); } else if (crewCount == 0) { introLineMessage = "No kerbals are aboard to produce anything."; productionMessage = null; unusedCapacityMessage = null; limitedByMessage = null; progress = new List <ResearchData>(); } else { if (resourcesConsumed.Any()) { var consumptionBuilder = new StringBuilder(); if (minerStatusMessage != null) { consumptionBuilder.AppendLine(minerStatusMessage); consumptionBuilder.AppendLine(); } consumptionBuilder.AppendLine(TextEffects.DialogHeading(crewDelta == 0 ? $"The crew of {crewCount + crewDelta} is using:" : $"A crew of {crewCount + crewDelta} would use:")); foreach (var resourceName in resourcesConsumed.Keys.OrderBy(n => n)) { if (!isAutoMining || !IsCrushinResource(researchSink, resourceName)) { double perDay = TieredProduction.UnitsPerSecondToUnitsPerDay(resourcesConsumed[resourceName]); double daysLeft = resources[resourceName] / perDay; consumptionBuilder.AppendLine($"{perDay:N1} {resourceName} per day ({daysLeft:N1} days left)"); } } introLineMessage = consumptionBuilder.ToString(); } else { introLineMessage = $"This vessel can sustain a crew of {crewCount + crewDelta} indefinitely."; } if (resourcesProduced != null && resourcesProduced.Count > 0) { var productionMessageBuilder = new StringBuilder(); foreach (var resourceName in resourcesProduced.Keys.OrderBy(n => n)) { double perDay = TieredProduction.UnitsPerSecondToUnitsPerDay(resourcesProduced[resourceName]); productionMessageBuilder.AppendLine($"{perDay:N1} {resourceName}"); } productionMessage = productionMessageBuilder.ToString(); } else { productionMessage = null; } // Because of the way PksTieredCombiners work, we'll often end up with the non-tiered stuff // showing up as a rate-limiter. While it's technically correct, it's not going to be a thing // that the player will want to know about. var localParts = ColonizationResearchScenario.Instance.TryGetTieredResourceByName("LocalParts"); if (localParts != null) { foreach (TechTier t in Enum.GetValues(typeof(TechTier))) { unusedProduction.Remove(localParts.TieredName(t)); } } unusedCapacityMessage = unusedProduction.Any() ? string.Join("\r\n", unusedProduction.Select(pair => $"{pair.Value:N1} {pair.Key}").ToArray()) : null; List <string> shortfalls = new List <string>(); if (anyCrewDeficiencies) { shortfalls.Add("uncrewed parts"); } if (anyDisabledParts) { shortfalls.Add("disabled parts"); } shortfalls.AddRange(limitingResources); shortfalls.AddRange(tieredCombiners .Where(tc => tc.IsProductionEnabled) .Select(tc => tc.NonTieredOutputResourceName) .Where(resourceName => !storage.ContainsKey(resourceName)) .Distinct() .Select(resourceName => $"storage for {resourceName}")); shortfalls.AddRange(tieredProducers .Where(tp => tp.IsProductionEnabled && tp.Output.CanBeStored) .Select(tp => tp.Output.TieredName(tp.Tier)) .Where(resourceName => !storage.ContainsKey(resourceName)) .Distinct() .Select(resourceName => $"storage for {resourceName}")); limitedByMessage = shortfalls.Count == 0 ? null : string.Join(", ", shortfalls.ToArray()); var allResearchEntries = researchSink.Data.Values.ToList(); foreach (var group in tieredProducers .Where(tp => !researchSink.Data.ContainsKey(tp.Output.ResearchCategory)) .GroupBy(tp => tp.Output.ResearchCategory)) { var maxTier = group.Max(tp => tp.Tier); var topTierProducers = group.Where(tp => tp.Tier == maxTier).ToArray(); ITieredProducer exampleProducer = group.FirstOrDefault(tp => tp.IsResearchEnabled && tp.IsProductionEnabled); // We're looking for the best example of why research isn't enabled - maxtier is the top exampleProducer = topTierProducers.FirstOrDefault(tp => tp.Tier == TechTier.Tier4); if (exampleProducer == null) { exampleProducer = topTierProducers.FirstOrDefault(tp => tp.IsProductionEnabled); } if (exampleProducer == null) { exampleProducer = topTierProducers.First(); } var researchEntry = ColonizationResearchScenario.Instance.GetResearchProgress( exampleProducer.Output, exampleProducer.Body, exampleProducer.Tier, exampleProducer.IsResearchEnabled ? "Production Blocked" : exampleProducer.ReasonWhyResearchIsDisabled); allResearchEntries.Add(researchEntry); } progress = allResearchEntries; } }