public static bool TryFindResourceToTransfer(Vessel sourceVessel, Vessel otherVessel, out Dictionary <string, double> toSend, out Dictionary <string, double> toReceive) { SnackConsumption.ResourceQuantities(sourceVessel, 1, out Dictionary <string, double> thisShipCanSupply, out Dictionary <string, double> thisShipCanStore); SnackConsumption.ResourceQuantities(otherVessel, 1, out Dictionary <string, double> otherShipCanSupply, out Dictionary <string, double> otherShipCanStore); string[] unpassableStuff = new string[] { "ElectricCharge", "StoredCharge" }; foreach (var stuff in unpassableStuff) { foreach (var dict in new[] { thisShipCanSupply, thisShipCanStore, otherShipCanStore, otherShipCanSupply }) { dict.Remove(stuff); } } List <string> couldSend = thisShipCanSupply.Keys.Intersect(otherShipCanStore.Keys).ToList(); List <string> couldTake = otherShipCanSupply.Keys.Intersect(thisShipCanStore.Keys).ToList(); List <PksTieredResourceConverter> otherVesselProducers = otherVessel.FindPartModulesImplementing <PksTieredResourceConverter>(); // Refactor me! Way too much "if other vessel has quality, then send, else if this vessel has that quality, receive." toSend = new Dictionary <string, double>(); toReceive = new Dictionary <string, double>(); if ((otherVessel.GetCrewCount() == 0 && otherVesselProducers.Count > 0) || otherVessel.vesselType == VesselType.Debris) { // The player's trying to abandon the base so we'll take everything and give nothing. foreach (var otherShipPair in otherShipCanSupply) { string resourceName = otherShipPair.Key; if (thisShipCanStore.ContainsKey(resourceName)) { toReceive.Add(resourceName, Math.Min(thisShipCanStore[resourceName], otherShipPair.Value)); } } return(toReceive.Count > 0); } // If other ship has a producer for a resource, take it // and if this ship has a producer for a resource (and the other doesn't), give it HashSet <string> thisShipsProducts = GetVesselsProducers(sourceVessel); HashSet <string> otherShipsProducts = GetVesselsProducers(otherVessel); foreach (string takeableStuff in otherShipCanSupply.Keys.Union(thisShipCanSupply.Keys)) { if (otherShipsProducts.Contains(takeableStuff) && !thisShipsProducts.Contains(takeableStuff) && thisShipCanStore.ContainsKey(takeableStuff) && otherShipCanSupply.ContainsKey(takeableStuff)) { toReceive.Add(takeableStuff, Math.Min(thisShipCanStore[takeableStuff], otherShipCanSupply[takeableStuff])); } else if (!otherShipsProducts.Contains(takeableStuff) && thisShipsProducts.Contains(takeableStuff) && otherShipCanStore.ContainsKey(takeableStuff) && thisShipCanSupply.ContainsKey(takeableStuff)) { toSend.Add(takeableStuff, Math.Min(otherShipCanStore[takeableStuff], thisShipCanSupply[takeableStuff])); } } // If one ship needs a resource to produce stuff and the other doesn't, but has some in storage, move it. HashSet <string> thisShipsConsumption = GetVesselsConsumers(sourceVessel); HashSet <string> otherShipsConsumption = GetVesselsConsumers(otherVessel); foreach (string resourceName in thisShipsConsumption.Union(otherShipsConsumption)) { if (otherShipCanSupply.ContainsKey(resourceName) && thisShipCanStore.ContainsKey(resourceName) && thisShipsConsumption.Contains(resourceName) && !otherShipsConsumption.Contains(resourceName)) { toReceive.Add(resourceName, Math.Min(thisShipCanStore[resourceName], otherShipCanSupply[resourceName])); } else if (thisShipCanSupply.ContainsKey(resourceName) && otherShipCanStore.ContainsKey(resourceName) && otherShipsConsumption.Contains(resourceName) && !thisShipsConsumption.Contains(resourceName)) { toSend.Add(resourceName, Math.Min(otherShipCanStore[resourceName], thisShipCanSupply[resourceName])); } } SnacksDirection snackDirectionBasedOnVesselType; if (sourceVessel.vesselType == VesselType.Ship && (otherVessel.vesselType == VesselType.Base || otherVessel.vesselType == VesselType.Rover || otherVessel.vesselType == VesselType.Lander) || sourceVessel.vesselType == VesselType.Base && (otherVessel.vesselType == VesselType.Rover || otherVessel.vesselType == VesselType.Lander)) { snackDirectionBasedOnVesselType = SnacksDirection.Send; } else if (otherVessel.vesselType == VesselType.Ship && (sourceVessel.vesselType == VesselType.Base || sourceVessel.vesselType == VesselType.Rover || sourceVessel.vesselType == VesselType.Lander) || otherVessel.vesselType == VesselType.Base && (sourceVessel.vesselType == VesselType.Rover || sourceVessel.vesselType == VesselType.Lander)) { snackDirectionBasedOnVesselType = SnacksDirection.Receive; } else if (otherVessel.GetCrewCount() > 0 && sourceVessel.GetCrewCount() == 0) { snackDirectionBasedOnVesselType = SnacksDirection.Send; } else if (otherVessel.GetCrewCount() == 0 && sourceVessel.GetCrewCount() > 0) { snackDirectionBasedOnVesselType = SnacksDirection.Receive; } else { snackDirectionBasedOnVesselType = SnacksDirection.Neither; } // Send snacks? if (otherShipCanStore.ContainsKey("Snacks-Tier4") && thisShipCanSupply.ContainsKey("Snacks-Tier4") && (thisShipsProducts.Contains("Snacks-Tier4") || // Always send if we produce it (!otherShipsProducts.Contains("Snacks-Tier4") && // Don't send if the other guy produces snackDirectionBasedOnVesselType == SnacksDirection.Send))) { toReceive.Remove("Snacks-Tier4"); toSend["Snacks-Tier4"] = Math.Min(thisShipCanSupply["Snacks-Tier4"], otherShipCanStore["Snacks-Tier4"]); } else if (thisShipCanStore.ContainsKey("Snacks-Tier4") && otherShipCanSupply.ContainsKey("Snacks-Tier4") && (otherShipsProducts.Contains("Snacks-Tier4") || // Always take if the other guy produces (!thisShipsProducts.Contains("Snacks-Tier4") && // Don't take if we produce it snackDirectionBasedOnVesselType == SnacksDirection.Receive))) { toSend.Remove("Snacks-Tier4"); toReceive["Snacks-Tier4"] = Math.Min(thisShipCanStore["Snacks-Tier4"], otherShipCanSupply["Snacks-Tier4"]); } return(toReceive.Any() || toSend.Any()); }
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; } }