public IEnumerable <DialogGUIBase> GetTraitDescription(bool showParts, List <ProtoCrewMember> crew) { List <ExperienceTraitConfig> careers = GameDatabase.Instance.ExperienceConfigs .GetTraitsWithEffect(this.Trait) .Select(name => GameDatabase.Instance.ExperienceConfigs.GetExperienceTraitConfig(name)) .ToList(); // #Needed Specialist #Crew Generalist #Crew // 1.5 Miner 2 3*Engineer 0 // Scrounger Drill- var requirementRow = new DialogGUIHorizontalLayout(); string quantity = UncrewedQuantity + DisabledQuantity > 0.001 ? $"{this.TotalQuantity - UncrewedQuantity - DisabledQuantity:N1}/{this.TotalQuantity:N1}" : this.TotalQuantity.ToString("N1"); if (UncrewedQuantity > 0.001) { quantity = TextEffects.Red(quantity); } else if (DisabledQuantity > 0.001) { quantity = TextEffects.Yellow(quantity); } requirementRow.AddChild(new DialogGUILabel(quantity, width: NumberColumnWidth)); for (int i = 0; i < careers.Count; ++i) { ExperienceEffectConfig effectConfig = careers[i].Effects.First(effect => effect.Name == this.Trait); var levelString = effectConfig.Config.GetValue("level"); int numStars = SkillLevel - int.Parse(levelString ?? "0"); requirementRow.AddChild(new DialogGUILabel( PksCrewRequirement.DescribeKerbalTrait(numStars, careers[i].Title), ProfessionColumnWidth)); requirementRow.AddChild(new DialogGUILabel( $"{crew.Count(c => c.trait == careers[i].Title && c.experienceLevel >= numStars)}", width: NumberColumnWidth)); } yield return(requirementRow); if (showParts) { var partsRow = new DialogGUIHorizontalLayout(); partsRow.AddChild(new DialogGUISpace(15)); partsRow.AddChild(new DialogGUILabel($"<I>{string.Join(", ", this.PartNames)}</I>")); yield return(partsRow); } }
public override string GetInfo() { StringBuilder info = new StringBuilder(); if (this.Input != null) { info.AppendLine($"{TextEffects.Green("Input:")} {this.Input.BaseName}"); } info.AppendLine($"{TextEffects.Green("Capacity:")} {this.capacity} {this.Output.CapacityUnits}"); info.AppendLine($"{TextEffects.Green("Output:")} {this.untieredOutput}"); info.AppendLine($"{TextEffects.Green("%Local:")}"); foreach (TechTier tier in TechTierExtensions.AllTiers) { info.AppendLine($" {tier.ToString()}: {(int)(combinationRates[(int)tier] * 100)}%"); } return(info.ToString()); }
public override string GetInfo() { List <ExperienceTraitConfig> careers = GameDatabase.Instance.ExperienceConfigs .GetTraitsWithEffect(this.requiredEffect) .Select(name => GameDatabase.Instance.ExperienceConfigs.GetExperienceTraitConfig(name)) .ToList(); StringBuilder info = new StringBuilder(); info.AppendLine(TextEffects.Green("Required Crew:")); info.AppendLine($"Staffing Level: {this.requiredCrew}"); info.AppendLine(TextEffects.Green("Traits:")); foreach (TechTier tier in TechTierExtensions.AllTiers) { info.Append($"{tier}: "); info.Append(DescribeKerbalsWithEffect(this.requiredEffect, tier)); info.AppendLine(); } return(info.ToString()); }
private DialogGUIBase DrawProductionTab() { return(new DialogGUIVerticalLayout( new DialogGUILabel(() => this.introLineMessage), new DialogGUISpace(5), new DialogGUIHorizontalLayout( new DialogGUILabel( () => $"{TextEffects.DialogHeading("Production/day:")}\r\n{this.productionMessage ?? "<none>"}"), new DialogGUISpace(30), new DialogGUILabel(() => this.unusedCapacityMessage == null ? "" : $"{TextEffects.DialogHeading("Unused Capacity:")}\r\n{this.unusedCapacityMessage}")), new DialogGUISpace(5), new DialogGUILabel(() => this.limitedByMessage == null ? "" : $"{TextEffects.DialogHeading("Limited by:")} {this.limitedByMessage}"), new DialogGUIFlexibleSpace(), new DialogGUIHorizontalLayout(TextAnchor.MiddleLeft, new DialogGUILabel("What if we"), new DialogGUIButton("Add", () => { ++crewDelta; }, () => true, false), new DialogGUILabel("/"), new DialogGUIButton("Remove", () => { --crewDelta; }, () => FlightGlobals.ActiveVessel.GetCrewCount() + this.crewDelta > 1, false), new DialogGUILabel("a kerbal?")))); }
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; } }
private static string MakePartCountString(string partName, IEnumerable <PksCrewRequirement> parts) { var partsArray = parts.ToArray(); int numUncrewed = parts.Count(p => p.IsRunning && !p.IsStaffed); int numTurnedOff = parts.Count(p => !p.IsRunning); if (numUncrewed == 0 && numTurnedOff == 0) { return(MakePartAndCountString(partsArray.Length, partName)); } else if (numUncrewed > 0 && numTurnedOff == 0) { return($"{MakePartAndCountString(partsArray.Length - numUncrewed, partName)} {TextEffects.Red($"({numUncrewed} unstaffed)")}"); } else if (numUncrewed == 0 && numTurnedOff == partsArray.Length) { return(TextEffects.Yellow($"{MakePartAndCountString(numTurnedOff, partName)} (disabled)")); } else if (numUncrewed == 0 && numTurnedOff > 0) { return($"{MakePartAndCountString(partsArray.Length - numTurnedOff, partName)} {TextEffects.Yellow($"({numTurnedOff} disabled)")}"); } else { return($"{MakePartAndCountString(partsArray.Length - numUncrewed - numTurnedOff, partName)} {TextEffects.Red($"({numUncrewed} unstaffed, {numTurnedOff} disabled)")}"); } }
private DialogGUIBase DrawCalculatorDialog() { List <Part> parts = EditorLogic.fetch.ship.Parts; List <ITieredProducer> producers = parts .Select(p => p.FindModuleImplementing <ITieredProducer>()) .Where(p => p != null).ToList(); if (!producers.Any(p => p.Output.IsEdible && p.Tier == TechTier.Tier4 && p.Output.GetPercentOfDietByTier(TechTier.Tier4) == 1) && !parts.Any(p => p.Resources.Any(r => r.resourceName == "Snacks-Tier4" && r.amount > 0))) { return(new DialogGUILabel("There is no source of top-tier Snacks on this vessel - only well-fed and happy Kerbals will produce things")); } // If we have some LandedOnBody and some not, that's going to spell trouble for our ability to calculate anything sensible. if (producers.Select(p => p.Output.ProductionRestriction == ProductionRestriction.LandedOnBody).Distinct().Count() > 1) { return(new DialogGUILabel("This ship looks like a composite ship - that is one with several sub-ships to take on " + "different missions (e.g. landed, in-orbit and in-transit). The calculator can't work effectively " + "on ships like that. Build the sub-ships individually, check the calculator and warnings panel " + "on each one, then merge them together (using either sub-assemblies or the 'Merge' button on the " + "load screen.")); } string requiredSituationString; if (!producers.Any() || producers[0].Output.ProductionRestriction == ProductionRestriction.Space) { requiredSituationString = "in space"; } else { string body = producers.Select(p => p.Body).FirstOrDefault(b => !string.IsNullOrEmpty(b)); requiredSituationString = producers[0].Output.ProductionRestriction == ProductionRestriction.LandedOnBody ? $"landed at {body}" : $"in orbit of {body}"; } // Calculator // Duration: [_____] Days [x] Landed // Consumes: // ... // [[Fill Cans+10%]] [[Fill Cans+25%]] // Produces: RecalculateResults(); return(new DialogGUIVerticalLayout( new DialogGUISpace(3), new DialogGUIHorizontalLayout(TextAnchor.MiddleLeft, new DialogGUILabel("Duration:"), new DialogGUITextInput( txt: this.plannedMissionDuration.ToString(), multiline: false, maxlength: 5, textSetFunc: OnInputText, getString: () => this.plannedMissionDuration.ToString(), contentType: TMPro.TMP_InputField.ContentType.IntegerNumber, hght: 24f), new DialogGUILabel("days while " + requiredSituationString), new DialogGUIFlexibleSpace() ), new DialogGUISpace(3), new DialogGUILabel("<b>Production:</b>"), new DialogGUILabel(() => this.productionInfo), new DialogGUISpace(3), new DialogGUILabel("<b>Consumption:</b>"), new DialogGUILabel(() => this.consumptionInfo), new DialogGUISpace(3), new DialogGUILabel(() => string.IsNullOrEmpty(this.productionLimitedBy) ? "" : TextEffects.Yellow("<b>Production Limited By:</b>")), new DialogGUILabel(() => this.productionLimitedBy))); }
internal static string StarvingKerbals(List <CrewDescriptor> crewInBucket) { return(TextEffects.Red($"{GetGroupDescription(crewInBucket)} {isare(crewInBucket)} refusing to do any more work and {isare(crewInBucket)} contemplating legal action against KSP! Get {himherthem(crewInBucket)} home or get some food out here right away!")); }