Exemplo n.º 1
0
        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());
        }
Exemplo n.º 2
0
        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;
            }
        }