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); }
internal static IEnumerable <WarningMessage> CheckTieredProduction(IColonizationResearchScenario colonizationResearch, List <ITieredProducer> producers, Dictionary <string, double> amountAvailable, Dictionary <string, double> storageAvailable) { List <ITieredProducer> noScannerParts = new List <ITieredProducer>(); List <ITieredProducer> noSubResearchParts = new List <ITieredProducer>(); List <ITieredProducer> underTier = new List <ITieredProducer>(); foreach (var producer in producers) { var suitability = StaticAnalysis.GetTierSuitability(colonizationResearch, producer.Output, producer.Tier, producer.MaximumTier, producer.Body); switch (suitability) { case TierSuitability.LacksScanner: noScannerParts.Add(producer); break; case TierSuitability.LacksSubordinateResearch: noSubResearchParts.Add(producer); break; case TierSuitability.UnderTier: underTier.Add(producer); break; default: break; } } if (noScannerParts.Any()) { var examplePart = noScannerParts[0]; yield return(new WarningMessage { Message = $"Scanning technology at {examplePart.Body} has not kept up with production technologies - {examplePart.Tier.DisplayName()} parts will not function until you deploy an equal-tier scanner to orbit around {examplePart.Body}.", IsClearlyBroken = true, FixIt = () => SetToIdealTier(colonizationResearch, noScannerParts) }); } if (noSubResearchParts.Any()) { var examplePart = noSubResearchParts[0]; yield return(new WarningMessage { Message = $"Not all the products in the production chain for {examplePart.Output.DisplayName} have advanced to {examplePart.Tier}.", IsClearlyBroken = true, FixIt = () => SetToIdealTier(colonizationResearch, noSubResearchParts) }); } if (underTier.Any()) { var examplePart = underTier[0]; yield return(new WarningMessage { Message = $"This base is not taking advantage of the latest tech for producing {examplePart.Output.DisplayName}", IsClearlyBroken = true, FixIt = () => SetToIdealTier(colonizationResearch, underTier) }); } var mostUsedBodyAndCount = producers .Where(c => c.Output.ProductionRestriction != ProductionRestriction.Space) .Where(c => c.Body != null) .GroupBy(c => c.Body) .Select(g => new { body = g.Key, count = g.Count() }) .OrderByDescending(o => o.count) .ToArray(); string targetBody = mostUsedBodyAndCount.Length > 0 ? mostUsedBodyAndCount[0].body : null; foreach (var pair in producers.GroupBy(producer => producer.Output)) { TieredResource output = pair.Key; IEnumerable <ITieredProducer> parts = pair; // Parts should be set consistently TechTier minTier = parts.Min(p => p.Tier); TechTier maxTier = parts.Max(p => p.Tier); if (minTier != maxTier) { yield return(new WarningMessage { Message = $"Not all of the parts producing {output.BaseName} are set at {maxTier}", IsClearlyBroken = false, FixIt = () => { foreach (var part in parts) { part.Tier = maxTier; } } }); break; } // Supplier parts should be at least maxTier var firstPart = parts.First(); TieredResource input = firstPart.Input; if (parts.First().Input == null && output.IsHarvestedLocally && targetBody != null) { // then it depends on scanning TechTier maxScanningTier = colonizationResearch.GetMaxUnlockedScanningTier(targetBody); if (maxTier > maxScanningTier) { } } else if (input != null) { // Ensure that the suppliers are all at least the same tier. if (producers.Any(producer => producer.Output == input && producer.Tier < maxTier)) { yield return(new WarningMessage { Message = $"There are {maxTier.DisplayName()} producers of {output.BaseName}, but it requires equal-tier {input.BaseName} production in order to work.", IsClearlyBroken = true, FixIt = null }); } } } }
public static string CreateMessage(string baseMessageTag, List <ProtoCrewMember> crew, IEnumerable <string> experienceEffects, TechTier tier) { HashSet <string> possibleTraits = new HashSet <string>( experienceEffects.SelectMany(effect => GameDatabase.Instance .ExperienceConfigs .GetTraitsWithEffect(effect))); var crewDescriptors = crew.Select(c => FromKsp(c, protocrew => possibleTraits.Contains(protocrew.trait))).ToList(); CrewDescriptor perp = null; CrewDescriptor victim = null; string Replacer(Match match) { switch (match.Value) { case "[tier]": return(tier.DisplayName()); case "[perp_name]": perp = perp ?? ChoosePerpetrator(crewDescriptors); return(perp.Name); case "[victim_name]": victim = victim ?? ChooseVictim(crewDescriptors); return(victim.Name); case "[perp_heshe]": perp = perp ?? ChoosePerpetrator(crewDescriptors); return(perp.heshe); case "[perp_himher]": perp = perp ?? ChoosePerpetrator(crewDescriptors); return(perp.himher); case "[perp_hisher]": perp = perp ?? ChoosePerpetrator(crewDescriptors); return(perp.hisher); case "[victim_hisher]": victim = victim ?? ChooseVictim(crewDescriptors); return(victim.hisher); case "[perps]": return(GetGroupDescription(crewDescriptors, true)); case "[victims]": return(GetGroupDescription(crewDescriptors, false)); case "[victims_hashave]": return(crewDescriptors.hashave()); case "[victims_isare]": return(crewDescriptors.isare()); case "[victims_hishertheir]": return(crewDescriptors.hishertheir()); case "[crew]": return(GetGroupDescription(crewDescriptors)); case "[resource_name]": return(GetMessage("LOC_KPBS_RANDOM_MINERAL")); case "[body]": return(FlightGlobals.ActiveVessel.mainBody.name); default: return($"?Unknown Substitution: {match.Value}?"); } } string message = replacement.Replace(GetMessage(baseMessageTag), Replacer); return(message); }
/// <summary> /// Calculates snacks consumption aboard the vessel. /// </summary> /// <param name="crew">The crew</param> /// <param name="deltaTime">The amount of time (in seconds) since the last calculation was done</param> /// <returns>The amount of <paramref name="deltaTime"/> in which food was supplied.</returns> private void ProduceAndConsume(List <ProtoCrewMember> crew, double deltaTime) { var tieredProducers = this.vessel.FindPartModulesImplementing <ITieredProducer>(); var combiners = this.vessel.FindPartModulesImplementing <ITieredCombiner>(); this.ResourceQuantities(out var availableResources, out var availableStorage); // Create a mock producer for the CrushIns which we're auto-supplying. Also remove it // from the list of available storage, else the algorithm will attempt to fill it. foreach (var resourceName in availableResources.Keys.Where(resourceName => this.ResourceIsAutosupplied(resourceName))) { tieredProducers.Add(new AutoMinerProducer(resourceName)); availableStorage.Remove(resourceName); } var crewPart = vessel.parts.FirstOrDefault(p => p.CrewCapacity > 0); double remainingTime = deltaTime; while (remainingTime > ResourceUtilities.FLOAT_TOLERANCE) { TieredProduction.CalculateResourceUtilization( crew.Count, remainingTime, tieredProducers, combiners, ColonizationResearchScenario.Instance, availableResources, availableStorage, out double elapsedTime, out List <TieredResource> breakthroughCategories, out Dictionary <string, double> resourceConsumptionPerSecond, out Dictionary <string, double> resourceProductionPerSecond, out var _, out var _); if (elapsedTime == 0) { LifeSupportScenario.Instance.KerbalsMissedAMeal(this.vessel, hasActiveProducers: tieredProducers.Any(p => p.IsProductionEnabled)); break; } if (resourceConsumptionPerSecond != null || resourceProductionPerSecond != null) { ConversionRecipe consumptionRecipe = new ConversionRecipe(); if (resourceConsumptionPerSecond != null) { foreach (var pair in resourceConsumptionPerSecond) { ColonizationResearchScenario.Instance.TryParseTieredResourceName(pair.Key, out var consumedResource, out var consumedResourceTier); if (consumedResource == ColonizationResearchScenario.LodeResource && ResourceLodeScenario.Instance.TryFindResourceLodeInRange(vessel, consumedResourceTier, out var resourceLode)) { ResourceLodeScenario.Instance.TryConsume(resourceLode, pair.Value * elapsedTime, out _); } else { double newAmount = availableResources[pair.Key] - pair.Value * elapsedTime; if (newAmount < ResourceUtilities.FLOAT_TOLERANCE) { availableResources.Remove(pair.Key); } else { availableResources[pair.Key] = newAmount; } consumptionRecipe.Inputs.Add(new ResourceRatio() { ResourceName = pair.Key, Ratio = pair.Value, DumpExcess = false, FlowMode = ResourceFlowMode.ALL_VESSEL }); } } } if (resourceProductionPerSecond != null) { foreach (var pair in resourceProductionPerSecond) { double newAmount = availableStorage[pair.Key] - elapsedTime * pair.Value; if (newAmount < ResourceUtilities.FLOAT_TOLERANCE) { availableStorage.Remove(pair.Key); } else { availableStorage[pair.Key] = newAmount; } } consumptionRecipe.Outputs.AddRange( resourceProductionPerSecond.Select(pair => new ResourceRatio() { ResourceName = pair.Key, Ratio = pair.Value, DumpExcess = true, FlowMode = ResourceFlowMode.ALL_VESSEL })); } var consumptionResult = this.ResConverter.ProcessRecipe(elapsedTime, consumptionRecipe, crewPart, null, 1f); Debug.Assert(Math.Abs(consumptionResult.TimeFactor - elapsedTime) < ResourceUtilities.FLOAT_TOLERANCE, "ProgressiveColonizationSystem.SnackConsumption.CalculateSnackFlow is busted - it somehow got the consumption recipe wrong."); } foreach (TieredResource resource in breakthroughCategories) { TechTier newTier = ColonizationResearchScenario.Instance.GetMaxUnlockedTier(resource, this.vessel.lastBody.name); string title = $"{resource.ResearchCategory.DisplayName} has progressed to {newTier.DisplayName()}!"; string message = resource.ResearchCategory.BreakthroughMessage(crew, newTier); string boringMessage = resource.ResearchCategory.BoringBreakthroughMessage(crew, newTier); PopupMessageWithKerbal.ShowPopup(title, message, boringMessage, "That's Just Swell"); } remainingTime -= elapsedTime; double lastMealTime = Planetarium.GetUniversalTime() - remainingTime; LifeSupportScenario.Instance.KerbalsHadASnack(this.vessel, lastMealTime); } }
private void CalculateTierLabels(bool setTier) { for (TechTier tier = TechTier.Tier0; tier <= TechTier.Tier4; ++tier) { var suitability = StaticAnalysis.GetTierSuitability(ColonizationResearchScenario.Instance, this.Product, tier, this.MaxTierForPart, this.Body); bool isEnabled; Func <string, string> transform; string toolTipTag; switch (suitability) { default: case TierSuitability.Ideal: transform = TextEffects.Green; toolTipTag = "#LOC_KPBS_IDEAL_TIER"; isEnabled = true; if (setTier) { this.RiskLevel = TierSuitability.Ideal; this.Tier = tier; } break; case TierSuitability.LacksScanner: transform = TextEffects.Yellow; toolTipTag = "#LOC_KPBS_SCANNING_SKILL_LAGS"; isEnabled = true; break; case TierSuitability.LacksSubordinateResearch: transform = TextEffects.Red; toolTipTag = "#LOC_KPBS_SUBORDINATE_SKILL_LAGS"; isEnabled = true; break; case TierSuitability.UnderTier: transform = TextEffects.Yellow; toolTipTag = "#LOC_KPBS_UNDER_TIER"; isEnabled = true; break; case TierSuitability.NotResearched: transform = s => s; toolTipTag = "#LOC_KPBS_NOT_RESEARCHED"; isEnabled = false; break; case TierSuitability.BodyNotSelected: transform = s => s; toolTipTag = "#LOC_KPBS_CHOOSE_A_BODY_FIRST"; isEnabled = false; break; case TierSuitability.PartDoesntSupportTier: transform = s => s; toolTipTag = "#LOC_KPBS_PART_DOES_NOT_SUPPORT_TIER"; isEnabled = false; break; } TechTier techTierCopy = tier; string labelGetter() => transform(techTierCopy.DisplayName()); void onToggled(bool isSelected) { if (isSelected && isEnabled) { this.Tier = techTierCopy; this.RiskLevel = suitability; } } if (this.tierToggles[(int)tier] == null) { this.tierToggles[(int)tier] = new DialogGUIToggle(() => techTierCopy == this.Tier, labelGetter, onToggled); } else { this.tierToggles[(int)tier].setLabel = labelGetter; this.tierToggles[(int)tier].onToggled = onToggled; } this.tierToggles[(int)tier].OptionInteractableCondition = () => isEnabled; this.tierToggles[(int)tier].tooltipText = Localizer.GetStringByTag(toolTipTag); } }