public void KerbalsHadASnack(Vessel vessel, double lastMealTime) { this.CheckForNewLoadedVesselSet(); var ungrounchedKerbals = new List <ProtoCrewMember>(); var unhungryKerbals = new List <ProtoCrewMember>(); foreach (var crew in vessel.GetVesselCrew()) { if (this.knownKerbals.TryGetValue(crew.name, out LifeSupportStatus crewStatus)) { if (crewStatus.IsGrouchy) { crew.type = ProtoCrewMember.KerbalType.Crew; KerbalRoster.SetExperienceTrait(crew, crewStatus.OldTrait); ungrounchedKerbals.Add(crew); } crewStatus.LastMeal = lastMealTime; crewStatus.IsGrouchy = false; this.incapacitatedKerbals.Remove(crew); } else { this.knownKerbals.Add(crew.name, new LifeSupportStatus { IsGrouchy = false, KerbalName = crew.name, LastMeal = Planetarium.GetUniversalTime(), OldTrait = null }); if (this.hungryKerbals.Remove(crew)) { unhungryKerbals.Add(crew); } } } if (ungrounchedKerbals.Any()) { ScreenMessages.PostScreenMessage( message: CrewBlurbs.CreateMessage("#LOC_KPBS_KERBAL_NOT_INCAPACITATED", ungrounchedKerbals, new string[] { }, TechTier.Tier0), duration: 15f, style: ScreenMessageStyle.UPPER_CENTER); } if (unhungryKerbals.Any()) { ScreenMessages.PostScreenMessage( message: CrewBlurbs.CreateMessage("#LOC_KPBS_KERBAL_NOT_HUNGRY", unhungryKerbals, new string[] { }, TechTier.Tier0), duration: 15f, style: ScreenMessageStyle.UPPER_CENTER); } }
public ResourceLode GetOrCreateResourceLoad(Vessel scannerVessel, Vessel nearVessel, TechTier tier, double scannerNetQuality) { // There's only allowed one resource load - you have to harvest it until it's gone // So find the thing first. var lode = this.activeLodes.FirstOrDefault(rl => rl.bodyName == nearVessel.mainBody.name && rl.Tier == tier); if (lode != null) { // Ensure that there's a waypoint if (Waypoints.TryFindWaypointById(lode.Identifier, out _)) { ScreenMessages.PostScreenMessage("A lode has already been identified - look for a waypoint on the surface"); } else { Waypoints.CreateWaypointAt($"Loose Crushins ({tier.DisplayName()})", nearVessel.mainBody, lode.Latitude, lode.Longitude); ScreenMessages.PostScreenMessage("A lode has already been identified - the waypoint was recreated"); } } else { var waypoint = Waypoints.CreateWaypointNear( $"Loose Crushins ({tier.DisplayName()})", nearVessel, 10000, 500000, scannerNetQuality, nearVessel.situation == Vessel.Situations.SPLASHED); lode = new ResourceLode(waypoint, tier); activeLodes.Add(lode); PopupMessageWithKerbal.ShowPopup( "Lookie What I Found!", CrewBlurbs.CreateMessage( scannerNetQuality < PksScanner.BadScannerNetQualityThreshold ? "#LOC_KPBS_SCANNER_FIND_NOSATS" : "#LOC_KPBS_SCANNER_FIND_SATS", scannerVessel.GetVesselCrew(), new string[] { nameof(PksScanningSkill) }, tier), "A waypoint has been created - you need to land a ship or drive a rover with a portable digger " + "to within 150m of the waypoint, deploy the drill, fill your tanks with CrushIns, haul the " + "load back to the base and unload it using the resource-transfer mechanism on the colony " + "status screen (the cupcake button). After you've dumped two loads with the same craft, " + "the crew at the base will be able to automatically gather resources in the future." + "\r\n\r\n" + "The more scanner satellites you have in polar orbit, the more likely you are to get a location " + "near your base.", "On it"); } return(lode); }
private const double secondsBeforeKerbalStarves = DaysBeforeKerbalStarves * 6 * 60 * 60; // 7 kerban days public void KerbalsMissedAMeal(Vessel vessel, bool hasActiveProducers) { if (vessel.isEVA) { return; } this.CheckForNewLoadedVesselSet(); List <ProtoCrewMember> crewThatBecameHungry = new List <ProtoCrewMember>(); List <ProtoCrewMember> crewThatBecameIncapacitated = new List <ProtoCrewMember>(); double now = Planetarium.GetUniversalTime(); foreach (var crew in vessel.GetVesselCrew()) { if (!this.knownKerbals.TryGetValue(crew.name, out LifeSupportStatus crewStatus)) { crewStatus = new LifeSupportStatus { IsGrouchy = false, KerbalName = crew.name, LastMeal = now, OldTrait = null }; this.knownKerbals.Add(crew.name, crewStatus); } if (!crewStatus.IsGrouchy && now > crewStatus.LastMeal + secondsBeforeKerbalStarves) { crewStatus.IsGrouchy = true; crewStatus.OldTrait = crew.experienceTrait.Title; crew.type = ProtoCrewMember.KerbalType.Tourist; KerbalRoster.SetExperienceTrait(crew, "Tourist"); } if (crewStatus.IsGrouchy && !this.incapacitatedKerbals.Contains(crew)) { crewThatBecameIncapacitated.Add(crew); this.incapacitatedKerbals.Add(crew); } else if (!crewStatus.IsGrouchy && (hasActiveProducers || now > crewStatus.LastMeal + .5 * secondsBeforeKerbalStarves) && !this.hungryKerbals.Contains(crew)) { crewThatBecameHungry.Add(crew); this.hungryKerbals.Add(crew); } } if (crewThatBecameIncapacitated.Any()) { ScreenMessages.PostScreenMessage( message: CrewBlurbs.CreateMessage("#LOC_KPBS_KERBAL_INCAPACITATED", crewThatBecameIncapacitated, new string[] { }, TechTier.Tier0), duration: 15f, style: ScreenMessageStyle.UPPER_CENTER); } else if (crewThatBecameHungry.Any()) { ScreenMessages.PostScreenMessage( message: CrewBlurbs.CreateMessage( (hasActiveProducers ? "#LOC_KPBS_KERBAL_HUNGRY_NO_PRODUCTION" : "#LOC_KPBS_KERBAL_HUNGRY"), crewThatBecameHungry, new string[] { }, TechTier.Tier0), duration: 15f, style: ScreenMessageStyle.UPPER_CENTER); } }
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; } }
public string BoringBreakthroughMessage(List <ProtoCrewMember> crew, TechTier newTier) => CrewBlurbs.CreateMessage(this.breakThroughExplanationTag, crew, this.crewSkills, newTier);