Esempio n. 1
0
        public static MissionProfile CreateFromRecording(Vessel vessel, FlightRecording recording)
        {
            var profile = new MissionProfile();

            profile.profileName      = recording.profileName;
            profile.vesselName       = vessel.vesselName.ToString();
            profile.missionType      = recording.missionType;
            profile.launchCost       = recording.launchCost;
            profile.launchMass       = recording.launchMass - recording.payloadMass;
            profile.payloadMass      = recording.payloadMass;
            profile.minAltitude      = recording.minAltitude;
            profile.maxAltitude      = recording.maxAltitude;
            profile.bodyName         = recording.launchBodyName;
            profile.missionDuration  = recording.deploymentTime - recording.startTime;
            profile.crewCapacity     = vessel.GetCrewCapacity() - vessel.GetCrewCount(); // Capacity at the end of the mission, so we can use it for oneway- as well als return-trips.
            profile.dockingPortTypes = recording.dockingPortTypes;

            if (vessel.situation == Vessel.Situations.LANDED || vessel.situation == Vessel.Situations.SPLASHED)
            {
                profile.oneWayMission = false;
                profile.launchCost   -= recording.GetCurrentVesselValue();
                if (profile.launchCost < 0)
                {
                    profile.launchCost = 0; // Shouldn't happen
                }
            }
            else
            {
                profile.oneWayMission = true;
            }

            return(profile);
        }
Esempio n. 2
0
        // Returns the running recording for the given vessel, if one exists:
        public static FlightRecording GetFlightRecording(Vessel vessel)
        {
            FlightRecording recording = null;

            try
            {
                if (FlightRecorder.flightRecordings.TryGetValue(vessel.id.ToString(), out recording))
                {
                    // Update with the current values of the vessel:
                    recording.Update(vessel);
                }
                else if (
                    vessel.situation == Vessel.Situations.PRELAUNCH ||
                    // Sometimes the vessel starts "jumping" on the launchpad when the physics are kicking in,
                    // which is why we also have to count "landed" on the launchpad as pre-launch:
                    (vessel.situation == Vessel.Situations.LANDED && (vessel.landedAt.ToString() == "Runway" || vessel.landedAt.ToString() == "KSC_LaunchPad_Platform"))
                    )
                {
                    // No recording found, but vessel is pre-launch and thus eligible for a new recording:
                    recording = new FlightRecording(vessel);
                }
            }
            catch (Exception e)
            {
                Debug.LogError("[KSTS] getFlightRecording(): " + e.ToString());
            }
            return(recording);
        }
Esempio n. 3
0
        public static void CreateMissionProfile(Vessel vessel, FlightRecording recording)
        {
            var profile = MissionProfile.CreateFromRecording(vessel, recording);

            // Make the profile-name unique to use it as a key:
            profile.profileName = MissionController.GetUniqueProfileName(profile.profileName);

            MissionController.missionProfiles.Add(profile.profileName, profile);
            Debug.Log("[KSTS] saved new mission profile '" + profile.profileName + "'");
        }
Esempio n. 4
0
        public static void CreateMissionProfile(Vessel vessel, FlightRecording recording)
        {
            var profile = MissionProfile.CreateFromRecording(vessel, recording);

            // Make the profile-name unique to use it as a key:
            profile.profileName = MissionController.GetUniqueProfileName(profile.profileName);

            MissionController.missionProfiles.Add(profile.profileName, profile);
            Log.Warning("saved new mission profile '" + profile.profileName + "'" + "   Total of " + MissionController.missionProfiles.Count + " missions saved");
        }
Esempio n. 5
0
 // Adds the given recording to the list of running recordings:
 public static void StartRecording(Vessel vessel)
 {
     try
     {
         if (FlightRecorder.flightRecordings.ContainsKey(vessel.id.ToString()))
         {
             throw new Exception("duplicate recording for vessel '" + vessel.id.ToString() + "'");
         }
         FlightRecording recording = new FlightRecording(vessel);
         FlightRecorder.flightRecordings.Add(vessel.id.ToString(), recording);
         recording.status = FlightRecordingStatus.ASCENDING;
     }
     catch (Exception e)
     {
         Debug.LogError("[KSTS] StartRecording(): " + e.ToString());
     }
 }
Esempio n. 6
0
        public static void LoadRecordings(ConfigNode node)
        {
            FlightRecorder.flightRecordings.Clear();
            ConfigNode flightRecorderNode = node.GetNode("FlightRecorder");

            if (flightRecorderNode == null)
            {
                return;
            }

            foreach (ConfigNode flightRecordingNode in flightRecorderNode.GetNodes())
            {
                FlightRecorder.flightRecordings.Add(flightRecordingNode.name, FlightRecording.CreateFromConfigNode(flightRecordingNode));
            }

            FlightRecorder.CollectGarbage(); // Might not work as expected in KSP 1.2, so we added this also to the timer-function.
        }
Esempio n. 7
0
        public static void Display()
        {
            if (!initialized)
            {
                Initialize();
            }
            Vessel          vessel    = FlightGlobals.ActiveVessel;
            FlightRecording recording = null;

            if (vessel)
            {
                recording = FlightRecoorder.GetFlightRecording(vessel);
            }
            if (!vessel || recording == null)
            {
                Reset();

                // Show list of recorded profiles:
                missionProfileSelector.DisplayList();
                if (missionProfileSelector.selectedProfile != null)
                {
                    if (missionProfileSelector.selectedProfile != lastSelectedProfile)
                    {
                        // The selecte profile was switched:
                        lastSelectedProfile   = missionProfileSelector.selectedProfile;
                        newMissionProfileName = missionProfileSelector.selectedProfile.profileName;
                    }

                    GUILayout.BeginHorizontal();
                    GUILayout.Label("<size=14><b>Profile name:</b></size>", new GUIStyle(GUI.labelStyle)
                    {
                        stretchWidth = false
                    });
                    newMissionProfileName = GUILayout.TextField(newMissionProfileName, new GUIStyle(GUI.textFieldStyle)
                    {
                        stretchWidth = true
                    });
                    GUILayout.EndHorizontal();

                    GUILayout.BeginHorizontal();
                    if (GUILayout.Button("Save Profile", GUI.buttonStyle))
                    {
                        MissionController.ChangeMissionProfileName(missionProfileSelector.selectedProfile.profileName, newMissionProfileName);
                        missionProfileSelector = new GUIMissionProfileSelector(); // Deselect & Reset
                    }
                    if (GUILayout.Button("Delete Profile", GUI.buttonStyle))
                    {
                        MissionController.DeleteMissionProfile(missionProfileSelector.selectedProfile.profileName);
                        missionProfileSelector = new GUIMissionProfileSelector(); // Deselect & Reset
                    }
                    GUILayout.EndHorizontal();
                }
            }
            else
            {
                // During the recording, allow the player to change the name of the new flight-profile:
                GUILayout.BeginHorizontal();
                GUILayout.Label("<size=14><b>Profile name:</b></size>", new GUIStyle(GUI.labelStyle)
                {
                    stretchWidth = false
                });
                if (recording.status != FlightRecordingStatus.PRELAUNCH)
                {
                    recording.profileName = GUILayout.TextField(recording.profileName, new GUIStyle(GUI.textFieldStyle)
                    {
                        stretchWidth = true
                    });
                }
                else
                {
                    GUILayout.Label(recording.profileName, new GUIStyle(GUI.labelStyle)
                    {
                        stretchWidth = true
                    });
                }
                GUILayout.EndHorizontal();

                // Display all Information about the current recording:
                GUILayout.BeginScrollView(new Vector2(0, 0), new GUIStyle(GUI.scrollStyle)
                {
                    stretchHeight = true
                });
                List <KeyValuePair <string, string> > displayAttributes = recording.GetDisplayAttributes();
                foreach (KeyValuePair <string, string> displayAttribute in displayAttributes)
                {
                    GUILayout.BeginHorizontal();
                    GUILayout.Label("<b>" + displayAttribute.Key + "</b>");
                    GUILayout.Label(displayAttribute.Value + "  ", new GUIStyle(GUI.labelStyle)
                    {
                        alignment = TextAnchor.MiddleRight
                    });
                    GUILayout.EndHorizontal();
                }
                GUILayout.EndScrollView();

                // Display payload selector:
                if (recording.status == FlightRecordingStatus.ASCENDING || recording.status == FlightRecordingStatus.PRELAUNCH)
                {
                    GUILayout.BeginHorizontal();
                    GUILayout.Label("<size=14><b>Mission Type:</b></size>");
                    string[] missionTypeStrings = new string[] { "Deploy", "Transport" };
                    selectedMissionTypeTab = GUILayout.Toolbar(selectedMissionTypeTab, missionTypeStrings);
                    GUILayout.EndHorizontal();

                    scrollPos = GUILayout.BeginScrollView(scrollPos, GUI.scrollStyle, GUILayout.Height(210), GUILayout.MaxHeight(210));
                    if (selectedMissionTypeTab == 0)
                    {
                        // Show all deployable payloads:
                        if (!recording.CanPerformMission(MissionProfileType.DEPLOY))
                        {
                            GUILayout.Label("Deployment missions can only be performed if the vessel has detachable parts which haven't been used during the flight (no resource consumption, inactive, uncrewed, etc).");
                        }
                        else
                        {
                            // Show all detachable subassemblies:
                            foreach (PayloadAssembly payloadAssembly in recording.GetPayloadAssemblies())
                            {
                                GUILayout.BeginHorizontal();
                                if (GUILayout.Toggle(selectedPayloadAssemblyIds.Contains(payloadAssembly.id), "<b>" + payloadAssembly.name + "</b>"))
                                {
                                    if (!selectedPayloadAssemblyIds.Contains(payloadAssembly.id))
                                    {
                                        selectedPayloadAssemblyIds.Add(payloadAssembly.id);
                                    }
                                }
                                else if (selectedPayloadAssemblyIds.Contains(payloadAssembly.id))
                                {
                                    selectedPayloadAssemblyIds.Remove(payloadAssembly.id);
                                }
                                GUILayout.Label(payloadAssembly.partCount.ToString() + " parts, " + payloadAssembly.mass.ToString("#,##0.00 t") + "   ", new GUIStyle(GUI.labelStyle)
                                {
                                    alignment = TextAnchor.MiddleRight
                                });
                                GUILayout.EndHorizontal();
                            }
                        }
                    }
                    else
                    {
                        if (!recording.CanPerformMission(MissionProfileType.TRANSPORT))
                        {
                            GUILayout.Label("Transport missions can only be performed with vessels which have at least one docking port as well as RCS thrusters.");
                        }
                        else
                        {
                            // Show all payload-resources:
                            double totalPayloadMass = 0;
                            foreach (PayloadResource payloadResource in recording.GetPayloadResources())
                            {
                                double selectedAmount = 0;
                                selectedPayloadDeploymentResources.TryGetValue(payloadResource.name, out selectedAmount);

                                GUILayout.BeginHorizontal();
                                GUILayout.Label("<b>" + payloadResource.name + "</b>");
                                GUILayout.Label(((selectedAmount / payloadResource.amount) * 100).ToString("0.00") + "% (" + selectedAmount.ToString("#,##0.00") + " / " + payloadResource.amount.ToString("#,##0.00") + "): " + (selectedAmount * payloadResource.mass).ToString("#,##0.00 t") + "  ", new GUIStyle(GUI.labelStyle)
                                {
                                    alignment = TextAnchor.MiddleRight
                                });
                                GUILayout.EndHorizontal();

                                selectedAmount = GUILayout.HorizontalSlider((float)selectedAmount, 0, (float)payloadResource.amount);
                                if (selectedAmount < 0)
                                {
                                    selectedAmount = 0;
                                }
                                if (selectedAmount > payloadResource.amount)
                                {
                                    selectedAmount = payloadResource.amount;
                                }
                                if (payloadResource.amount - selectedAmount < 0.01)
                                {
                                    selectedAmount = payloadResource.amount;
                                }
                                totalPayloadMass += selectedAmount * payloadResource.mass;

                                if (selectedPayloadDeploymentResources.ContainsKey(payloadResource.name))
                                {
                                    selectedPayloadDeploymentResources[payloadResource.name] = selectedAmount;
                                }
                                else
                                {
                                    selectedPayloadDeploymentResources.Add(payloadResource.name, selectedAmount);
                                }
                            }

                            GUILayout.BeginHorizontal();
                            GUILayout.Label("<b>Total Payload</b>");
                            GUILayout.Label(totalPayloadMass.ToString("#,##0.00 t  "), new GUIStyle(GUI.labelStyle)
                            {
                                alignment = TextAnchor.MiddleRight
                            });
                            GUILayout.EndHorizontal();
                        }
                    }
                    GUILayout.EndScrollView();
                }

                // Bottom pane with action-buttons:
                GUILayout.BeginHorizontal();
                if (recording.status == FlightRecordingStatus.PRELAUNCH && GUILayout.Button("Record", GUI.buttonStyle))
                {
                    // Start Recording:
                    FlightRecoorder.StartRecording(vessel);
                }

                if (recording.CanDeploy() && GUILayout.Button("Release Payload", GUI.buttonStyle))
                {
                    if (selectedMissionTypeTab == 0)
                    {
                        List <PayloadAssembly> payloadAssemblies         = recording.GetPayloadAssemblies();
                        List <PayloadAssembly> selectedPayloadAssemblies = new List <PayloadAssembly>();
                        foreach (PayloadAssembly payloadAssembly in recording.GetPayloadAssemblies())
                        {
                            if (selectedPayloadAssemblyIds.Contains(payloadAssembly.id))
                            {
                                selectedPayloadAssemblies.Add(payloadAssembly);
                            }
                        }
                        if (selectedPayloadAssemblies.Count > 0)
                        {
                            recording.DeployPayloadAssembly(selectedPayloadAssemblies);
                        }
                    }
                    else
                    {
                        recording.DeployPayloadResources(selectedPayloadDeploymentResources);
                    }
                }

                if (recording.CanFinish() && GUILayout.Button("Stop & Save", GUI.buttonStyle))
                {
                    // Stop recording and create a mission-profile:
                    FlightRecoorder.SaveRecording(vessel);
                }

                if (recording.status != FlightRecordingStatus.PRELAUNCH && GUILayout.Button("Abort", GUI.buttonStyle))
                {
                    // Cancel runnig recording:
                    FlightRecoorder.CancelRecording(vessel);
                }
                GUILayout.EndHorizontal();
            }
        }
Esempio n. 8
0
        // Gathers all important current stats for the given vessel (eg its mass, price, etc):
        public static RecordingVesselStats GetStats(FlightRecording recording, Vessel vessel)
        {
            RecordingVesselStats stats = new RecordingVesselStats();

            stats.hasCrew          = vessel.GetCrewCount() > 0;
            stats.payloadResources = new Dictionary <string, PayloadResource>();
            stats.dockingPortTypes = new List <string>();

            foreach (Part part in vessel.parts)
            {
                string partName = SanitizeParteName(part.name);

                // Check for modules which enable the vessel to perform certain actions:
                List <ModuleDockingNode> dockingNodes = part.FindModulesImplementing <ModuleDockingNode>();
                if (dockingNodes.Count() > 0)
                {
                    stats.hasDockingPort = true;
                    foreach (ModuleDockingNode dockingNode in dockingNodes)
                    {
                        if (!stats.dockingPortTypes.Contains(dockingNode.nodeType.ToString()))
                        {
                            stats.dockingPortTypes.Add(dockingNode.nodeType.ToString());                                                                    // Docking-nodes have differnt sizes, like "node0", "node1", etc
                        }
                    }
                }
                if (part.FindModulesImplementing <ModuleDecouple>().Count() > 0)
                {
                    stats.hasSeperator = true;
                }
                if (part.FindModulesImplementing <ModuleAnchoredDecoupler>().Count() > 0)
                {
                    stats.hasSeperator = true;
                }
                if (part.FindModulesImplementing <ModuleRCS>().Count() > 0)
                {
                    stats.hasRCS = true;
                }

                // Mass of the part and its resources:
                stats.mass += part.mass + part.resourceMass;

                // Sum up all the parts resources:
                double resourceCost    = 0;
                double resourceCostMax = 0;
                foreach (PartResource resource in part.Resources)
                {
                    PartResourceDefinition resourceDefinition = null;
                    if (!KSTS.resourceDictionary.TryGetValue(resource.resourceName.ToString(), out resourceDefinition))
                    {
                        Debug.LogError("[KSTS] RecordingVesselStats.GetStats(): resource '" + resource.resourceName.ToString() + "' not found in dictionary");
                    }
                    else
                    {
                        // Cost:
                        resourceCost    += (double)(resource.amount * resourceDefinition.unitCost);
                        resourceCostMax += (double)(resource.maxAmount * resourceDefinition.unitCost);

                        // Track remaining amout for payload delivery (only resources which have a weight):
                        if (resourceDefinition.density > 0 && resource.amount > 0)
                        {
                            PayloadResource payloadResource = null;
                            if (stats.payloadResources.TryGetValue(resource.resourceName.ToString(), out payloadResource))
                            {
                                stats.payloadResources.Remove(resource.resourceName.ToString());
                                payloadResource.amount += resource.amount;
                            }
                            else
                            {
                                payloadResource        = new PayloadResource();
                                payloadResource.amount = resource.amount;
                                payloadResource.name   = resource.resourceName.ToString();
                                payloadResource.mass   = resourceDefinition.density;
                            }
                            stats.payloadResources.Add(resource.resourceName.ToString(), payloadResource);
                        }
                    }
                }
                stats.cost += resourceCost;

                // The cost of the part is only available in the AvailablePart-class:
                AvailablePart availablePart = null;
                if (!KSTS.partDictionary.TryGetValue(partName, out availablePart))
                {
                    Debug.LogError("[KSTS] RecordingVesselStats.GetStats(): part '" + partName + "' not found in dictionary");
                }
                else
                {
                    // The cost of the part already includes the resource-costs, when completely filled:
                    double dryCost = availablePart.cost - resourceCostMax;
                    stats.cost += dryCost;
                }
            }

            // Find all crewed parts:
            List <string> crewedPartIds = new List <string>();

            foreach (ProtoCrewMember crewMember in vessel.GetVesselCrew())
            {
                if (crewMember.seat == null || crewMember.seat.part == null)
                {
                    continue;
                }
                if (crewedPartIds.Contains(crewMember.seat.part.flightID.ToString()))
                {
                    continue;
                }
                crewedPartIds.Add(crewMember.seat.part.flightID.ToString());
            }

            // Find all valid subassemblies, which can be detached from the control-part:
            stats.payloadAssemblies = new Dictionary <string, PayloadAssembly>();
            stats.FindPayloadAssemblies(recording, vessel.rootPart, null, crewedPartIds);

            return(stats);
        }
Esempio n. 9
0
        // Is called every second and keeps track of used parts during a flight-recording:
        public static void Timer()
        {
            try
            {
                // Maybe remove old, invalid running recordings:
                FlightRecorder.CollectGarbage();

                // Check if we are on an vessel which is recording a flight:
                Vessel vessel = FlightGlobals.ActiveVessel;
                if (!vessel)
                {
                    return;
                }
                FlightRecording recording = GetFlightRecording(vessel);
                if (recording == null)
                {
                    return;
                }

                if (vessel.id.ToString() != FlightRecorder.timerVesselId)
                {
                    // The vessel has changed, reset all variables from the last timer-tick:
                    FlightRecorder.timerVesselId = vessel.id.ToString();
                    FlightRecorder.timerPartResources.Clear();
                }

                // Check all parts, if something has changed which makes the part unusable for payload-deployments:
                foreach (Part part in vessel.parts)
                {
                    if (recording.usedPartIds.Contains(part.flightID.ToString()))
                    {
                        continue;                                                           // Already blocked
                    }
                    bool   blockThis = false;
                    string partId    = part.flightID.ToString();

                    // Check for running engines:
                    foreach (ModuleEngines engineModule in part.FindModulesImplementing <ModuleEngines>())
                    {
                        if (engineModule.GetCurrentThrust() > 0)
                        {
                            blockThis = true;
                        }
                    }
                    foreach (ModuleEnginesFX engineModule in part.FindModulesImplementing <ModuleEnginesFX>())
                    {
                        if (engineModule.GetCurrentThrust() > 0)
                        {
                            blockThis = true;
                        }
                    }

                    // Check for resource-consumption:
                    foreach (PartResource resource in part.Resources)
                    {
                        PartResourceDefinition resourceDefinition = null;
                        string resourceId = resource.resourceName.ToString();
                        if (!KSTS.resourceDictionary.TryGetValue(resourceId, out resourceDefinition))
                        {
                            continue;
                        }
                        if (resourceDefinition.density <= 0)
                        {
                            continue;                                  // We only care about resources with mass, skipping electricity and such.
                        }
                        if (!FlightRecorder.timerPartResources.ContainsKey(partId))
                        {
                            FlightRecorder.timerPartResources.Add(partId, new Dictionary <string, double>());
                        }
                        double lastAmount;
                        if (!FlightRecorder.timerPartResources[partId].TryGetValue(resourceId, out lastAmount))
                        {
                            FlightRecorder.timerPartResources[partId].Add(resourceId, resource.amount);
                        }
                        else
                        {
                            if (lastAmount != resource.amount)
                            {
                                blockThis = true;                                // The amount has changed relative to the last timer-tick.
                            }
                        }
                    }

                    if (blockThis)
                    {
                        Debug.Log("[KSTS] marking part " + part.name.ToString() + " (" + part.flightID.ToString() + ") as used");
                        recording.usedPartIds.Add(part.flightID.ToString());
                    }
                }
            }
            catch (Exception e)
            {
                Debug.LogError("[KSTS] FlightRecoorder.Timer(): " + e.ToString());
            }
        }
Esempio n. 10
0
        public static FlightRecording CreateFromConfigNode(ConfigNode node)
        {
            FlightRecording recording = new FlightRecording();

            return((FlightRecording)CreateFromConfigNode(node, recording));
        }
Esempio n. 11
0
        // Recursively finds all detachable assemblies, which are attached as children to the given part and
        // stores them in "payloadAssemblies":
        // TODO: Maybe we should look at the direction dockung-ports and decouples are facing. If they stay on the ship, thes should not count as payload, just as seperators should not get counted as well.
        protected PayloadAssembly FindPayloadAssemblies(FlightRecording recording, Part part, Part parent, List <string> crewedPartIds)
        {
            PayloadAssembly assembly = new PayloadAssembly();

            // Iterate through all attached parts:
            List <Part> attachedParts = new List <Part>(part.children);

            if (part.parent != null)
            {
                attachedParts.Add(part.parent);
            }
            foreach (Part attachedPart in attachedParts)
            {
                if (parent != null && attachedPart.flightID == parent.flightID)
                {
                    continue;                                                             // Ignore the part we came from in the iteration.
                }
                PayloadAssembly subassembly = this.FindPayloadAssemblies(recording, attachedPart, part, crewedPartIds);
                if (subassembly == null)
                {
                    continue;
                }
                assembly.mass      += subassembly.mass;
                assembly.partCount += subassembly.partCount;
                assembly.parts.AddRange(subassembly.parts);
                assembly.value += subassembly.value;
                if (subassembly.containsInvalidParts)
                {
                    assembly.containsInvalidParts = true;
                }
            }

            if (assembly.partCount > 0 && (
                    part.FindModulesImplementing <ModuleDockingNode>().Count() > 0 ||
                    part.FindModulesImplementing <ModuleDecouple>().Count() > 0 ||
                    part.FindModulesImplementing <ModuleAnchoredDecoupler>().Count() > 0
                    ))
            {
                // This is a seperator/dockingport, add all children as valid, detachable subassembly (excluding the seperator, providing the subassembly is actually valid):
                if (!assembly.containsInvalidParts)
                {
                    // Create a copy of the assembly, which will alow us to use the original object for assemblies higher up the recursion-chain:
                    PayloadAssembly payloadAssembly = new PayloadAssembly(assembly);

                    AvailablePart availablePart = null;
                    if (KSTS.partDictionary.TryGetValue(part.name.ToString(), out availablePart))
                    {
                        payloadAssembly.name = availablePart.title.ToString();
                    }
                    else
                    {
                        payloadAssembly.name = part.name.ToString();
                    }
                    payloadAssembly.detachmentPart = part;
                    payloadAssembly.id             = part.flightID.ToString();
                    if (!this.payloadAssemblies.ContainsKey(payloadAssembly.id))
                    {
                        this.payloadAssemblies.Add(payloadAssembly.id, payloadAssembly);
                    }
                }
                else
                {
                    assembly = new PayloadAssembly();
                }
            }

            // Check if this part was active at some point during the flight, making this an invalid subassembly:
            if (recording.usedPartIds.Contains(part.flightID.ToString()))
            {
                assembly.containsInvalidParts = true;
            }

            // Check if the part has a crewmember and thous can not be used as payload:
            if (crewedPartIds.Contains(part.flightID.ToString()))
            {
                assembly.containsInvalidParts = true;
            }

            // Determine the cost of the current part:
            double partCost = 0;
            string partName = SanitizeParteName(part.name);

            if (KSTS.partDictionary.ContainsKey(partName))
            {
                partCost += KSTS.partDictionary[partName].cost; // Includes resource-costs
                foreach (PartResource resource in part.Resources)
                {
                    // Determine the real value of the part with the current amount of resources inside:
                    if (!KSTS.resourceDictionary.ContainsKey(resource.resourceName))
                    {
                        continue;
                    }
                    partCost -= KSTS.resourceDictionary[resource.resourceName].unitCost * resource.maxAmount;
                    partCost += KSTS.resourceDictionary[resource.resourceName].unitCost * resource.amount;
                }
            }

            // Add this part's mass for subassemblies higher up the recursion:
            assembly.mass      += part.mass + part.resourceMass;
            assembly.partCount += 1;
            assembly.parts.Add(part);
            assembly.value += partCost;
            return(assembly);
        }