Ejemplo n.º 1
0
        // specifics support
        public Specifics Specs()
        {
            Specifics specs   = new Specifics();
            Process   process = Profile.processes.Find(k => k.modifiers.Contains(resource));

            if (process != null)
            {
                foreach (KeyValuePair <string, double> pair in process.inputs)
                {
                    if (!process.modifiers.Contains(pair.Key))
                    {
                        specs.Add(pair.Key, Lib.BuildString("<color=#ffaa00>", Lib.HumanReadableRate(pair.Value * capacity), "</color>"));
                    }
                    else
                    {
                        specs.Add(Local.ProcessController_info1, Lib.HumanReadableDuration(0.5 / pair.Value));                        //"Half-life"
                    }
                }
                foreach (KeyValuePair <string, double> pair in process.outputs)
                {
                    specs.Add(pair.Key, Lib.BuildString("<color=#00ff00>", Lib.HumanReadableRate(pair.Value * capacity), "</color>"));
                }
            }
            return(specs);
        }
Ejemplo n.º 2
0
        // specifics support
        public Specifics Specs()
        {
            Specifics specs = new Specifics();

            if (redundancy.Length > 0)
            {
                specs.Add("Redundancy", redundancy);
            }
            specs.Add("Repair", new CrewSpecs(repair).Info());
            specs.Add(string.Empty);
            specs.Add("<color=#00ffff>Standard quality</color>");
            specs.Add("MTBF", Lib.HumanReadableDuration(mtbf));
            specs.Add(string.Empty);
            specs.Add("<color=#00ffff>High quality</color>");
            specs.Add("MTBF", Lib.HumanReadableDuration(mtbf * Settings.QualityScale));
            if (extra_cost > double.Epsilon)
            {
                specs.Add("Extra cost", Lib.HumanReadableCost(extra_cost * part.partInfo.cost));
            }
            if (extra_mass > double.Epsilon)
            {
                specs.Add("Extra mass", Lib.HumanReadableMass(extra_mass * part.partInfo.partPrefab.mass));
            }
            return(specs);
        }
Ejemplo n.º 3
0
        //public override string GetModuleDisplayName() { return "Hard Drive"; }

        // specifics support
        public Specifics Specs()
        {
            Specifics specs = new Specifics();

            specs.Add("File capacity", Lib.HumanReadableDataSize(dataCapacity));
            specs.Add("Sample capacity", Lib.HumanReadableSampleSize(sampleCapacity));
            return(specs);
        }
Ejemplo n.º 4
0
        //public override string GetModuleDisplayName() { return "Hard Drive"; }

        // specifics support
        public Specifics Specs()
        {
            Specifics specs = new Specifics();

            specs.Add("File capacity", dataCapacity >= 0 ? Lib.HumanReadableDataSize(dataCapacity) : "unlimited");
            specs.Add("Sample capacity", sampleCapacity >= 0 ? Lib.HumanReadableSampleSize(sampleCapacity) : "unlimited");
            return(specs);
        }
Ejemplo n.º 5
0
		// specifics support
		public Specifics Specs()
		{
			Specifics specs = new Specifics();
			specs.Add("bonus", "firm-ground");
			specs.Add("EC/s", Lib.HumanReadableRate(ec_rate));
			specs.Add("deployable", deploy.Length > 0 ? "yes" : "no");
			return specs;
		}
Ejemplo n.º 6
0
		// specifics support
		public Specifics Specs()
		{
			Specifics specs = new Specifics();
			specs.Add("volume", Lib.HumanReadableVolume(volume > double.Epsilon ? volume : Lib.PartVolume(part)));
			specs.Add("surface", Lib.HumanReadableSurface(surface > double.Epsilon ? surface : Lib.PartSurface(part)));
			if (inflate.Length > 0) specs.Add("Inflatable", "yes");
			return specs;
		}
Ejemplo n.º 7
0
        //public override string GetModuleDisplayName() { return "Hard Drive"; }

        // specifics support
        public Specifics Specs()
        {
            Specifics specs = new Specifics();

            specs.Add(Local.HardDrive_info1, dataCapacity >= 0 ? Lib.HumanReadableDataSize(dataCapacity) : Local.HardDrive_Capacityunlimited);            //"File capacity""unlimited"
            specs.Add(Local.HardDrive_info2, sampleCapacity >= 0 ? Lib.HumanReadableSampleSize(sampleCapacity) : Local.HardDrive_Capacityunlimited);      //"Sample capacity""unlimited"
            return(specs);
        }
Ejemplo n.º 8
0
        // specifics support
        public Specifics Specs()
        {
            Specifics specs = new Specifics();

            specs.Add(Local.GravityRing_info1, "firm-ground");                                                    //"bonus"
            specs.Add("EC/s", Lib.HumanReadableRate(ec_rate));
            specs.Add(Local.GravityRing_info2, deploy.Length > 0 ? Local.GravityRing_yes : Local.GravityRing_no); //"deployable""yes""no"
            return(specs);
        }
Ejemplo n.º 9
0
        // specifics support
        public Specifics Specs()
        {
            Specifics specs = new Specifics();

            specs.Add("Researcher", new CrewSpecs(researcher).Info());
            specs.Add("EC rate", Lib.HumanReadableRate(ec_rate));
            specs.Add("Analysis rate", Lib.HumanReadableDataRate(analysis_rate));
            return(specs);
        }
Ejemplo n.º 10
0
        // specifics support
        public Specifics Specs()
        {
            Specifics specs = new Specifics();

            specs.Add(radiation >= 0.0 ? Localizer.Format("#KERBALISM_Emitter_Emitted") : Localizer.Format("#KERBALISM_Emitter_ActiveShielding"), Lib.HumanReadableRadiation(Math.Abs(radiation)));
            if (ec_rate > double.Epsilon)
            {
                specs.Add("EC/s", Lib.HumanReadableRate(ec_rate));
            }
            return(specs);
        }
Ejemplo n.º 11
0
        // specifics support
        public Specifics Specs()
        {
            Specifics specs = new Specifics();

            specs.Add(radiation >= 0.0 ? "Radiation emitted" : "Active shielding", Lib.HumanReadableRadiation(Math.Abs(radiation)));
            if (ec_rate > double.Epsilon)
            {
                specs.Add("EC/s", Lib.HumanReadableRate(ec_rate));
            }
            return(specs);
        }
Ejemplo n.º 12
0
        // specifics support
        public Specifics Specs()
        {
            Specifics specs = new Specifics();

            specs.Add(Localizer.Format("#KERBALISM_Laboratory_Researcher"), new CrewSpecs(researcher).Info());
            if (cleaner)
            {
                specs.Add(Localizer.Format("#KERBALISM_Laboratory_CanClean"));
            }
            specs.Add(Localizer.Format("#KERBALISM_Laboratory_ECrate"), Lib.HumanReadableRate(ec_rate));
            specs.Add(Localizer.Format("#KERBALISM_Laboratory_rate"), Lib.HumanReadableDataRate(analysis_rate));
            return(specs);
        }
Ejemplo n.º 13
0
        // specifics support
        public Specifics Specs()
        {
            Specifics specs = new Specifics();

            specs.Add(Local.Laboratory_Researcher, new CrewSpecs(researcher).Info());
            if (cleaner)
            {
                specs.Add(Local.Laboratory_CanClean);
            }
            specs.Add(Local.Laboratory_ECrate, Lib.HumanReadableRate(ec_rate));
            specs.Add(Local.Laboratory_rate, Lib.HumanReadableDataRate(analysis_rate));
            return(specs);
        }
Ejemplo n.º 14
0
        public override string GetInfo()
        {
            Specifics specs = new Specifics();

            specs.Add(Lib.Color(Local.Module_Experiment_Specifics_info8, Lib.Kolor.Cyan, true));            //"Needs:"
            if (ec_rate > 0.0)
            {
                specs.Add(Local.Module_Experiment_Specifics_info9, Lib.HumanReadableRate(ec_rate));                           //"EC"
            }
            if (comms_rate > 0.0)
            {
                specs.Add(Local.Module_Experiment_Specifics_info2, Lib.HumanReadableDataRate(comms_rate));                               // "Data rate"
            }
            return(specs.Info());
        }
Ejemplo n.º 15
0
        // specifics support
        public Specifics Specs()
        {
            var specs = new Specifics();

            specs.Add(Local.Sensor_Type, type);            //"Type"
            return(specs);
        }
Ejemplo n.º 16
0
        // specifics support
        public Specifics Specs()
        {
            var specs = new Specifics();

            specs.Add("Type", type);
            return(specs);
        }
Ejemplo n.º 17
0
        // specifics support
        public Specifics Specs()
        {
            Specifics specs = new Specifics();

            specs.Add("bonus", bonus);
            return(specs);
        }
Ejemplo n.º 18
0
        // specifics support
        public Specifics Specs()
        {
            Specifics specs = new Specifics();

            specs.Add("Volume", Lib.HumanReadableVolume(volume > double.Epsilon ? volume : Lib.PartVolume(part)));
            specs.Add("Surface", Lib.HumanReadableSurface(surface > double.Epsilon ? surface : Lib.PartSurface(part)));
            if (inflate.Length > 0)
            {
                specs.Add("Inflatable", "yes");
            }
            if (PhysicsGlobals.KerbalCrewMass > 0)
            {
                specs.Add("Added mass per crew", Lib.HumanReadableMass(PhysicsGlobals.KerbalCrewMass));
            }

            return(specs);
        }
Ejemplo n.º 19
0
        // specifics support
        public Specifics Specs()
        {
            Specifics specs = new Specifics();

            specs.Add(Local.Habitat_info1, Lib.HumanReadableVolume(volume > 0.0 ? volume : Lib.PartBoundsVolume(part)) + (volume > 0.0 ? "" : " (bounds)"));      //"Volume"
            specs.Add(Local.Habitat_info2, Lib.HumanReadableSurface(surface > 0.0 ? surface : Lib.PartBoundsSurface(part)) + (surface > 0.0 ? "" : " (bounds)")); //"Surface"
            specs.Add(Local.Habitat_info3, max_pressure >= Settings.PressureThreshold ? Local.Habitat_yes : Local.Habitat_no);                                    //"Pressurized""yes""no"
            if (inflate.Length > 0)
            {
                specs.Add(Local.Habitat_info4, Local.Habitat_yes);                    //"Inflatable""yes"
            }
            if (PhysicsGlobals.KerbalCrewMass > 0)
            {
                specs.Add(Local.Habitat_info5, Lib.HumanReadableMass(PhysicsGlobals.KerbalCrewMass));//"Added mass per crew"
            }
            return(specs);
        }
Ejemplo n.º 20
0
        // specifics support
        public Specifics Specs()
        {
            if (slots == 0 && !cureEverybody)
            {
                return(null);
            }
            Specifics specs = new Specifics();

            if (cureEverybody)
            {
                specs.Add("Cures", "All kerbals in part");
            }
            else if (slots > 0)
            {
                specs.Add("Capacity", slots + " Kerbals");
            }
            return(specs);
        }
Ejemplo n.º 21
0
        // specifics support
        public Specifics Specs()
        {
            if (slots == 0 && !cureEverybody)
            {
                return(null);
            }
            Specifics specs = new Specifics();

            if (cureEverybody)
            {
                specs.Add(Local.Sickbay_info1, Local.Sickbay_info2);                          //"Cures""All kerbals in part"
            }
            else if (slots > 0)
            {
                specs.Add(Local.Sickbay_info3, Local.Sickbay_info4.Format(slots.ToString()));                            //"Capacity""<<1>> Kerbals"
            }
            return(specs);
        }
Ejemplo n.º 22
0
        // specifics support
        public Specifics Specs()
        {
            Specifics specs = new Specifics();

            specs.Add("slots", slots.ToString());
            specs.Add("reconfigure", new CrewSpecs(reconfigure).Info());
            specs.Add(string.Empty);
            specs.Add("setups:");

            // organize setups by tech required, and add the ones without tech
            Dictionary <string, List <string> > org = new Dictionary <string, List <string> >();

            foreach (ConfigureSetup setup in setups)
            {
                if (setup.tech.Length > 0)
                {
                    if (!org.ContainsKey(setup.tech))
                    {
                        org.Add(setup.tech, new List <string>());
                    }
                    org[setup.tech].Add(setup.name);
                }
                else
                {
                    specs.Add(Lib.BuildString("• <b>", setup.name, "</b>"));
                }
            }

            // add setups grouped by tech
            foreach (var pair in org)
            {
                // shortcuts
                string        tech_id     = pair.Key;
                List <string> setup_names = pair.Value;

                // get tech title
                // note: non-stock technologies will return empty titles, so we use tech-id directly in that case
                string tech_title = ResearchAndDevelopment.GetTechnologyTitle(tech_id).ToLower();
                tech_title = !string.IsNullOrEmpty(tech_title) ? tech_title : tech_id;

                // add tech name
                specs.Add(string.Empty);
                specs.Add(Lib.BuildString("<color=#00ffff>", tech_title, ":</color>"));

                // add setup names
                foreach (string setup_name in setup_names)
                {
                    specs.Add(Lib.BuildString("• <b>", setup_name, "</b>"));
                }
            }

            return(specs);
        }
        static bool Prefix(ModuleDataTransmitter __instance, ref string __result)
        {
            // Patch only if science is enabled
            if (!Features.Science)
            {
                return(true);
            }

            string text = CultureInfo.CurrentCulture.TextInfo.ToTitleCase(__instance.antennaType.displayDescription());

            // Antenna type: direct
            string result = Localizer.Format("#autoLOC_7001005", text);

            // Antenna rating: 500km
            result += Localizer.Format("#autoLOC_7001006", Lib.HumanReadableDistance(__instance.antennaPower));
            result += "\n";

            var dsn1 = CommNet.CommNetScenario.RangeModel.GetMaximumRange(__instance.antennaPower, GameVariables.Instance.GetDSNRange(0f));
            var dsn2 = CommNet.CommNetScenario.RangeModel.GetMaximumRange(__instance.antennaPower, GameVariables.Instance.GetDSNRange(0.5f));
            var dsn3 = CommNet.CommNetScenario.RangeModel.GetMaximumRange(__instance.antennaPower, GameVariables.Instance.GetDSNRange(1f));

            result += Lib.BuildString(Localizer.Format("#autoLOC_236834"), " ", Lib.HumanReadableDistance(dsn1));
            result += Lib.BuildString(Localizer.Format("#autoLOC_236835"), " ", Lib.HumanReadableDistance(dsn2));
            result += Lib.BuildString(Localizer.Format("#autoLOC_236836"), " ", Lib.HumanReadableDistance(dsn3));

            double ec = __instance.DataResourceCost * __instance.DataRate;

            Specifics specs = new Specifics();

            specs.Add(Local.DataTransmitter_ECidle, Lib.Color(Lib.HumanReadableRate(ec * Settings.TransmitterPassiveEcFactor), Lib.Kolor.Orange));            //"EC (idle)"

            if (__instance.antennaType != AntennaType.INTERNAL)
            {
                specs.Add(Local.DataTransmitter_ECTX, Lib.Color(Lib.HumanReadableRate(ec * Settings.TransmitterActiveEcFactor), Lib.Kolor.Orange)); //"EC (transmitting)"
                specs.Add("");
                specs.Add(Local.DataTransmitter_Maxspeed, Lib.HumanReadableDataRate(__instance.DataRate));                                          //"Max. speed"
            }

            __result = Lib.BuildString(result, "\n\n", specs.Info());

            // don't call default implementation
            return(false);
        }
Ejemplo n.º 24
0
		// specifics support
		public Specifics Specs()
		{
			Specifics specs = new Specifics();
			specs.Add("type", ((HarvestTypes)type).ToString());
			specs.Add("resource", resource);
			if (min_abundance > double.Epsilon) specs.Add("min abundance", Lib.HumanReadablePerc(min_abundance, "F2"));
			if (type == 2 && min_pressure > double.Epsilon) specs.Add("min pressure", Lib.HumanReadablePressure(min_pressure));
			specs.Add("extraction rate", Lib.HumanReadableRate(rate));
			specs.Add("at abundance", Lib.HumanReadablePerc(abundance_rate, "F2"));
			if (ec_rate > double.Epsilon) specs.Add("ec consumption", Lib.HumanReadableRate(ec_rate));
			return specs;
		}
Ejemplo n.º 25
0
        // specifics support
        public Specifics Specs()
        {
            var specs = new Specifics();

            specs.Add("Name", ResearchAndDevelopment.GetExperiment(experiment).experimentTitle);
            specs.Add("Data rate", Lib.HumanReadableDataRate(data_rate));
            specs.Add("EC required", Lib.HumanReadableRate(ec_rate));
            if (crew.Length > 0)
            {
                specs.Add("Operator", new CrewSpecs(crew).Info());
            }
            specs.Add(string.Empty);
            specs.Add("<color=#00ffff>Situations:</color>", string.Empty);
            var tokens = Lib.Tokenize(situations, ',');

            foreach (string s in tokens)
            {
                specs.Add(Lib.BuildString("• <b>", s, "</b>"));
            }
            return(specs);
        }
Ejemplo n.º 26
0
        // specifics support
        public Specifics Specs()
        {
            Specifics specs = new Specifics();

            specs.Add(Local.Harvester_info1, ((HarvestTypes)type).ToString()); //"type"
            specs.Add(Local.Harvester_info2, resource);                        //"resource"
            if (min_abundance > double.Epsilon)
            {
                specs.Add(Local.Harvester_info3, Lib.HumanReadablePerc(min_abundance, "F2"));                                            //"min abundance"
            }
            if (type == 2 && min_pressure > double.Epsilon)
            {
                specs.Add(Local.Harvester_info4, Lib.HumanReadablePressure(min_pressure)); //"min pressure"
            }
            specs.Add(Local.Harvester_info5, Lib.HumanReadableRate(rate));                 //"extraction rate"
            specs.Add(Local.Harvester_info6, Lib.HumanReadablePerc(abundance_rate, "F2")); //"at abundance"
            if (ec_rate > double.Epsilon)
            {
                specs.Add(Local.Harvester_info7, Lib.HumanReadableRate(ec_rate));                                      //"ec consumption"
            }
            return(specs);
        }
Ejemplo n.º 27
0
        // specifics support
        public Specifics Specs()
        {
            double[] ranges = new double[12];
            for (int i = 0; i < 12; ++i)
            {
                ranges[i] = dist / 13.0 * (double)(i + 1);
            }

            Specifics specs = new Specifics();

            specs.Add("Type", type == KAntennaType.low_gain ? "low-gain" : "high-gain");
            specs.Add("Transmission cost", Lib.BuildString(cost.ToString("F2"), " EC/s"));
            specs.Add("Nominal rate", Lib.HumanReadableDataRate(rate));
            specs.Add("Nominal distance", Lib.HumanReadableRange(dist));
            specs.Add(string.Empty);
            specs.Add("<color=#00ffff><b>Transmission rates</b></color>");
            foreach (double range in ranges)
            {
                specs.Add(Lib.BuildString(Lib.HumanReadableRange(range), "\t<b>", Lib.HumanReadableDataRate(Calculate_Rate(range * 0.99, dist, rate)), "</b>"));
            }
            return(specs);
        }
Ejemplo n.º 28
0
        // specifics support
        public Specifics Specs()
        {
            Specifics specs = new Specifics();

            specs.Add("Harvest size", Lib.HumanReadableAmount(crop_size, " " + crop_resource));
            specs.Add("Harvest time", Lib.HumanReadableDuration(1.0 / crop_rate));
            specs.Add("Lighting tolerance", Lib.HumanReadableFlux(light_tolerance));
            if (pressure_tolerance > double.Epsilon)
            {
                specs.Add("Pressure tolerance", Lib.HumanReadablePressure(Sim.PressureAtSeaLevel() * pressure_tolerance));
            }
            if (radiation_tolerance > double.Epsilon)
            {
                specs.Add("Radiation tolerance", Lib.HumanReadableRadiation(radiation_tolerance));
            }
            specs.Add("Lamps EC rate", Lib.HumanReadableRate(ec_rate));
            specs.Add(string.Empty);
            specs.Add("<color=#00ffff>Required resources</color>");

            // do we have combined WasteAtmosphere and CO2
            Set_WACO2();
            bool dis_WACO2 = false;

            foreach (ModuleResource input in resHandler.inputResources)
            {
                // combine WasteAtmosphere and CO2 if both exist
                if (WACO2 && (input.name == "WasteAtmosphere" || input.name == "CarbonDioxide"))
                {
                    if (dis_WACO2)
                    {
                        continue;
                    }
                    ModuleResource sec;
                    if (input.name == "WasteAtmosphere")
                    {
                        sec = resHandler.inputResources.Find(x => x.name.Contains("CarbonDioxide"));
                    }
                    else
                    {
                        sec = resHandler.inputResources.Find(x => x.name.Contains("WasteAtmosphere"));
                    }
                    specs.Add("CarbonDioxide", Lib.BuildString("<color=#ffaa00>", Lib.HumanReadableRate(input.rate + sec.rate), "</color>"));
                    specs.Add("Crops can also use the CO2 in the atmosphere without a scrubber.");
                    dis_WACO2 = true;
                }
                else
                {
                    specs.Add(input.name, Lib.BuildString("<color=#ffaa00>", Lib.HumanReadableRate(input.rate), "</color>"));
                }
            }
            specs.Add(string.Empty);
            specs.Add("<color=#00ffff>By-products</color>");
            foreach (ModuleResource output in resHandler.outputResources)
            {
                specs.Add(output.name, Lib.BuildString("<color=#00ff00>", Lib.HumanReadableRate(output.rate), "</color>"));
            }
            return(specs);
        }
Ejemplo n.º 29
0
        // specifics support
        public Specifics Specs()
        {
            var specs = new Specifics();
            var exp   = Science.Experiment(experiment_id);

            if (exp == null)
            {
                specs.Add(Localizer.Format("#KERBALISM_ExperimentInfo_Unknown"));
                return(specs);
            }

            specs.Add(Lib.BuildString("<b>", exp.name, "</b>"));
            if (!string.IsNullOrEmpty(experiment_desc))
            {
                specs.Add(Lib.BuildString("<i>", experiment_desc, "</i>"));
            }

            specs.Add(string.Empty);
            double expSize = exp.max_amount;

            if (sample_mass < float.Epsilon)
            {
                specs.Add("Data", Lib.HumanReadableDataSize(expSize));
                specs.Add("Data rate", Lib.HumanReadableDataRate(data_rate));
                specs.Add("Duration", Lib.HumanReadableDuration(expSize / data_rate));
            }
            else
            {
                specs.Add("Sample size", Lib.HumanReadableSampleSize(expSize));
                specs.Add("Sample mass", Lib.HumanReadableMass(sample_mass));
                if (!sample_collecting && Math.Abs(sample_reservoir - sample_mass) > double.Epsilon && sample_mass > double.Epsilon)
                {
                    specs.Add("Experiments", "" + Math.Round(sample_reservoir / sample_mass, 0));
                }
                specs.Add("Duration", Lib.HumanReadableDuration(expSize / data_rate));
            }

            List <string> situations = exp.Situations();

            if (situations.Count > 0)
            {
                specs.Add(string.Empty);
                specs.Add("<color=#00ffff>Situations:</color>", string.Empty);
                foreach (string s in situations)
                {
                    specs.Add(Lib.BuildString("• <b>", s, "</b>"));
                }
            }

            specs.Add(string.Empty);
            specs.Add("<color=#00ffff>Needs:</color>");

            specs.Add("EC", Lib.HumanReadableRate(ec_rate));
            foreach (var p in KerbalismProcess.ParseResources(resources))
            {
                specs.Add(p.Key, Lib.HumanReadableRate(p.Value));
            }

            if (crew_prepare.Length > 0)
            {
                var cs = new CrewSpecs(crew_prepare);
                specs.Add("Preparation", cs ? cs.Info() : "none");
            }
            if (crew_operate.Length > 0)
            {
                var cs = new CrewSpecs(crew_operate);
                specs.Add("Operation", cs ? cs.Info() : "unmanned");
            }
            if (crew_reset.Length > 0)
            {
                var cs = new CrewSpecs(crew_reset);
                specs.Add("Reset", cs ? cs.Info() : "none");
            }

            if (!string.IsNullOrEmpty(requires))
            {
                specs.Add(string.Empty);
                specs.Add("<color=#00ffff>Requires:</color>", string.Empty);
                var tokens = Lib.Tokenize(requires, ',');
                foreach (string s in tokens)
                {
                    specs.Add(Lib.BuildString("• <b>", Science.RequirementText(s), "</b>"));
                }
            }

            return(specs);
        }
Ejemplo n.º 30
0
        // specifics support
        public Specifics Specs()
        {
            Specifics specs = new Specifics();

            specs.Add("Slots", slots.ToString());
            specs.Add("Reconfigure", new CrewSpecs(reconfigure).Info());
            specs.Add(string.Empty);
            specs.Add("Setups:");

            // organize setups by tech required, and add the ones without tech
            Dictionary <string, List <string> > org = new Dictionary <string, List <string> >();

            foreach (ConfigureSetup setup in setups)
            {
                if (setup.tech.Length > 0)
                {
                    if (!org.ContainsKey(setup.tech))
                    {
                        org.Add(setup.tech, new List <string>());
                    }
                    org[setup.tech].Add(setup.name);
                }
                else
                {
                    specs.Add(Lib.BuildString("• <b>", setup.name, "</b>"));
                }
            }

            // add setups grouped by tech
            foreach (var pair in org)
            {
                // shortcuts
                string        tech_id     = pair.Key;
                List <string> setup_names = pair.Value;

                // get tech title
                // note: non-stock technologies will return empty titles, so we use tech-id directly in that case

                // this works in KSP 1.6
                string tech_title = Localizer.Format(ResearchAndDevelopment.GetTechnologyTitle(tech_id));
                tech_title = !string.IsNullOrEmpty(tech_title) ? tech_title : tech_id;

                // this seems to have worked for KSP < 1.6
                if (tech_title.StartsWith("#", StringComparison.Ordinal))
                {
                    tech_title = Localizer.Format(ResearchAndDevelopment.GetTechnologyTitle(tech_id.ToLower()));
                }

                // safeguard agains having #autoloc_1234 texts in the UI
                if (tech_title.StartsWith("#", StringComparison.Ordinal))
                {
                    tech_title = tech_id;
                }

                // add tech name
                specs.Add(string.Empty);
                specs.Add(Lib.BuildString("<color=#00ffff>", tech_title, ":</color>"));

                // add setup names
                foreach (string setup_name in setup_names)
                {
                    specs.Add(Lib.BuildString("• <b>", setup_name, "</b>"));
                }
            }

            return(specs);
        }