Пример #1
0
        // Creates a new ship with the given parameters for this mission. The code however seems unnecessarily convoluted and
        // error-prone, but there are no better examples available on the internet.
        private void CreateShip()
        {
            try
            {
                // The ShipConstruct-object can only savely exist while not in flight, otherwise it will spam Null-Pointer Exceptions every tick:
                if (HighLogic.LoadedScene == GameScenes.FLIGHT)
                {
                    throw new Exception("unable to run CreateShip while in flight");
                }

                // Load the parts form the saved vessel:
                if (!File.Exists(shipTemplateFilename))
                {
                    throw new Exception("file '" + shipTemplateFilename + "' not found");
                }

                var shipConstruct = ShipConstruction.LoadShip(shipTemplateFilename);

                // Maybe adjust the orbit:
                var vesselHeight = Math.Max(Math.Max(shipConstruct.shipSize.x, shipConstruct.shipSize.y), shipConstruct.shipSize.z);
                if (missionType == MissionType.DEPLOY)
                {
                    // Make sure that there won't be any collisions, when the vessel is created at the given orbit:
                    orbit = GUIOrbitEditor.ApplySafetyDistance(orbit, vesselHeight);
                }
                else if (missionType == MissionType.CONSTRUCT)
                {
                    // Deploy the new ship next to the space-dock:
                    var spaceDock = TargetVessel.GetVesselById((Guid)targetVesselId);
                    orbit = GUIOrbitEditor.CreateFollowingOrbit(spaceDock.orbit, TargetVessel.GetVesselSize(spaceDock) + vesselHeight);
                    orbit = GUIOrbitEditor.ApplySafetyDistance(orbit, vesselHeight);
                }
                else
                {
                    throw new Exception("invalid mission-type '" + missionType + "'");
                }

                var game     = FlightDriver.FlightStateCache ?? HighLogic.CurrentGame;
                var profile  = GetProfile();
                var duration = profile.missionDuration;
                AssembleForLaunchUnlanded(shipConstruct, crewToDeliver ?? Enumerable.Empty <string>(), duration, orbit, flagURL, game);
                var newVessel = FlightGlobals.Vessels[FlightGlobals.Vessels.Count - 1];
                newVessel.vesselName = shipName;
                Debug.Log("[KSTS] deployed new ship '" + shipName + "' as '" + newVessel.protoVessel.vesselRef.id + "'");
                ScreenMessages.PostScreenMessage("Vessel '" + shipName + "' deployed"); // Popup message to notify the player

                // Notify other mods about the new vessel:
                GameEvents.onVesselCreate.Fire(newVessel);
            }
            catch (Exception e)
            {
                Debug.LogError("[KSTS] Mission.CreateShip(): " + e);
            }
        }
Пример #2
0
 public GUITransportSelector(Vessel targetVessel, MissionProfile missionProfile)
 {
     this.targetVessel   = targetVessel;
     this.missionProfile = missionProfile;
     resourceSelectors   = new List <GUIRichValueSelector>();
     availableResources  = TargetVessel.GetFreeResourcesCapacities(targetVessel);
     foreach (var availablePayload in availableResources)
     {
         var selector = new GUIRichValueSelector(availablePayload.name, 0, "", 0, Math.Round(availablePayload.amount, 2), true, "#,##0.00");
         resourceSelectors.Add(selector);
     }
     crewTransferSelector = new GUICrewTransferSelector(targetVessel, missionProfile);
 }
Пример #3
0
        // Returns the number of crew-members that have a given trait (eg "Pilot"):
        public static int GetCrewCountWithTrait(Vessel vessel, string trait)
        {
            var traitCount = 0;

            foreach (var crewMember in TargetVessel.GetCrew(vessel))
            {
                if (crewMember.trait == trait)
                {
                    traitCount++;
                }
            }
            return(traitCount);
        }
        // Shows a list of all available mission-profiles and returns true, if the player has selected one:
        public bool DisplayList()
        {
            CheckInternals();
            GUILayout.Label("<size=14><b>Mission Profile:</b></size>");
            scrollPos = GUILayout.BeginScrollView(scrollPos, GUI.scrollStyle);
            var green = "#00FF00";
            var red   = "#FF0000";

            // Show a list with all possible mission-profiles:
            if (MissionController.missionProfiles.Count == 0)
            {
                GUILayout.Label("No recordings found, switch to a new vessel to start recording a mission.");
            }
            else
            {
                var contents       = new List <GUIContent>();
                var invalidIndices = new List <int>(); // Profiles which fall out of the defined filters will get noted here.
                var index          = 0;
                foreach (var missionProfile in MissionController.missionProfiles.Values)
                {
                    var isValidProfile = true;
                    var color          = "";

                    // Build the descriptive text with highlighting:
                    var description = "<color=#F9FA86><b>" + missionProfile.profileName + "</b></color> <color=#FFFFFF>(" + missionProfile.vesselName + ")\n";
                    description += "<b>Mass:</b> " + missionProfile.launchMass.ToString("0.0t") + ", Cost: " + missionProfile.launchCost.ToString("#,##0√") + ", ";

                    // One-Way or Round-Trip:
                    var missionRouteDetails = "";
                    if (missionProfile.oneWayMission)
                    {
                        missionRouteDetails = "one-way";
                    }
                    else
                    {
                        missionRouteDetails = "round-trip";
                    }
                    if (this.filterRoundTrip != null)
                    {
                        if (this.filterRoundTrip != missionProfile.oneWayMission)
                        {
                            isValidProfile = false; color = red;
                        }
                        else
                        {
                            color = green;
                        }
                        missionRouteDetails = "<color=" + color + ">" + missionRouteDetails + "</color>";
                    }
                    description += missionRouteDetails + "\n";

                    // Mission-Type:
                    var missionType = MissionProfile.GetMissionProfileTypeName(missionProfile.missionType);
                    if (this.filterMissionType != null)
                    {
                        if (this.filterMissionType != missionProfile.missionType)
                        {
                            isValidProfile = false; color = red;
                        }
                        else
                        {
                            color = green;
                        }
                        missionType = "<color=" + color + ">" + missionType + "</color>";
                    }
                    description += "<b>Type:</b> " + missionType + ", ";

                    description += "<b>Duration:</b> " + GUI.FormatDuration(missionProfile.missionDuration) + "\n";

                    // Docking-Ports:
                    var dockingPorts = "";
                    if (missionProfile.missionType == MissionProfileType.TRANSPORT || this.filterDockingPortTypes != null)
                    {
                        var hasFittingPort = false;
                        var portNumber     = 0;
                        if (missionProfile.dockingPortTypes != null)
                        {
                            foreach (var portType in missionProfile.dockingPortTypes)
                            {
                                if (portNumber > 0)
                                {
                                    dockingPorts += ", ";
                                }
                                if (this.filterDockingPortTypes != null && this.filterDockingPortTypes.Contains(portType))
                                {
                                    hasFittingPort = true;
                                    dockingPorts  += "<color=" + green + ">" + TargetVessel.TranslateDockingPortName(portType) + "</color>";
                                }
                                else
                                {
                                    dockingPorts += TargetVessel.TranslateDockingPortName(portType);
                                }
                                portNumber++;
                            }
                        }
                        if (portNumber == 0)
                        {
                            dockingPorts = "N/A";
                        }
                        if (this.filterDockingPortTypes != null && !hasFittingPort)
                        {
                            dockingPorts   = "<color=" + red + ">" + dockingPorts + "</color>";
                            isValidProfile = false;
                        }
                    }
                    if (dockingPorts != "")
                    {
                        description += "<b>Docking-Ports:</b> " + dockingPorts + "\n";
                    }

                    // Payload:
                    var payloadMass = missionProfile.payloadMass.ToString("0.0t");
                    if (this.filterMass != null)
                    {
                        // We only display one digit after the pount, so we should round here to avoid confustion:
                        if (Math.Round((double)this.filterMass, 1) > Math.Round(missionProfile.payloadMass, 1))
                        {
                            isValidProfile = false; color = red;
                        }
                        else
                        {
                            color = green;
                        }
                        payloadMass = "<color=" + color + ">" + payloadMass + "</color>";
                    }
                    description += "<b>Payload:</b> " + payloadMass;

                    // Body:
                    var bodyName = missionProfile.bodyName;
                    if (this.filterBody != null)
                    {
                        if (this.filterBody.bodyName != missionProfile.bodyName)
                        {
                            isValidProfile = false; color = red;
                        }
                        else
                        {
                            color = green;
                        }
                        bodyName = "<color=" + color + ">" + bodyName + "</color>";
                    }
                    description += " to " + bodyName;

                    // Altitude:
                    var maxAltitude = GUI.FormatAltitude(missionProfile.maxAltitude);
                    if (this.filterAltitude != null)
                    {
                        if (this.filterAltitude > missionProfile.maxAltitude)
                        {
                            isValidProfile = false; color = red;
                        }
                        else
                        {
                            color = green;
                        }
                        maxAltitude = "<color=" + color + ">" + maxAltitude + "</color>";
                    }
                    description += " @ " + maxAltitude + "\n";

                    // Crew-Capacity:
                    var crewCapacity = missionProfile.crewCapacity.ToString("0");
                    if (this.filterCrewCapacity != null)
                    {
                        if (this.filterCrewCapacity > missionProfile.crewCapacity)
                        {
                            isValidProfile = false; color = red;
                        }
                        else
                        {
                            color = green;
                        }
                        crewCapacity = "<color=" + color + ">" + crewCapacity + "</color>";
                    }
                    description += "<b>Crew-Capacity:</b> " + crewCapacity;

                    description += "</color>";
                    contents.Add(new GUIContent(description, GUI.GetVesselThumbnail(missionProfile.vesselName)));

                    if (!isValidProfile)
                    {
                        invalidIndices.Add(index);
                    }
                    index++;
                }

                var newSelection = GUILayout.SelectionGrid(selectedIndex, contents.ToArray(), 1, GUI.selectionGridStyle);
                if (newSelection != selectedIndex && !invalidIndices.Contains(newSelection))
                {
                    selectedIndex   = newSelection;
                    selectedProfile = MissionController.missionProfiles.Values.ToList()[selectedIndex];
                }
            }

            GUILayout.EndScrollView();
            return(this.selectedProfile != null);
        }
Пример #5
0
        // Shows a list of all available target-vessels and returns true, if the player has selected one:
        public bool DisplayList()
        {
            GUILayout.Label("<size=14><b>Target:</b></size>");
            scrollPos = GUILayout.BeginScrollView(scrollPos, GUI.scrollStyle);
            string green = "#00FF00";
            string red   = "#FF0000";

            // Build a list with all valid vessels:
            List <Vessel> validTargets = new List <Vessel>();

            foreach (Vessel vessel in FlightGlobals.Vessels)
            {
                if (!TargetVessel.IsValidTarget(vessel))
                {
                    continue;
                }
                validTargets.Add(vessel);
            }
            if (selectedIndex >= validTargets.Count)
            {
                selectedIndex = -1;
                targetVessel  = null;
            }

            if (validTargets.Count == 0)
            {
                // No valid targest available:
                GUILayout.Label("No valid targets in orbit.");
            }
            else
            {
                // Show list with all possible vessels:
                List <GUIContent> contents        = new List <GUIContent>();
                List <int>        filteredIndices = new List <int>(); // Target-vessels which fall out of the defined filters will get noted here.
                int index = 0;
                foreach (Vessel vessel in validTargets)
                {
                    bool filterThisTarget = false;
                    if (!TargetVessel.IsValidTarget(vessel))
                    {
                        continue;
                    }
                    List <string> descriptions = new List <string>();
                    descriptions.Add("<color=#F9FA86><b>" + vessel.vesselName + "</b></color><color=#FFFFFF>");

                    // Orbital-Parameters:
                    descriptions.Add("<b>Apoapsis:</b> " + GUI.FormatAltitude(vessel.orbit.ApA) + ", <b>Periapsis:</b> " + GUI.FormatAltitude(vessel.orbit.PeA) + ", <b>MET:</b> " + GUI.FormatDuration(vessel.missionTime));

                    // Docking-Port Types:
                    List <string> dockingPortsTranslated = new List <string>();
                    foreach (string dockingPortType in TargetVessel.GetVesselDockingPortTypes(vessel))
                    {
                        dockingPortsTranslated.Add(TargetVessel.TranslateDockingPortName(dockingPortType));
                    }
                    if (dockingPortsTranslated.Count > 0)
                    {
                        descriptions.Add("<b>Docking-Ports:</b> " + string.Join(", ", dockingPortsTranslated.ToArray()));
                    }

                    // Resources:
                    double capacity = 0;
                    foreach (PayloadResource availableResource in TargetVessel.GetFreeResourcesCapacities(vessel))
                    {
                        capacity += availableResource.amount * availableResource.mass;
                    }
                    if (capacity > 0)
                    {
                        descriptions.Add("<b>Resource Capacity:</b> " + capacity.ToString("#,##0.00t"));
                    }

                    // Crew:
                    int seats = TargetVessel.GetCrewCapacity(vessel);
                    int crew  = TargetVessel.GetCrew(vessel).Count;
                    if (seats > 0)
                    {
                        descriptions.Add("<b>Crew:</b> " + crew.ToString() + "/" + seats.ToString());
                    }

                    // Maybe apply additional filters and show their attributes:
                    List <string> filterAttributes = new List <string>();;
                    if (filterVesselType != null)
                    {
                        bool   isValidType = vessel.vesselType == (VesselType)filterVesselType;
                        string color       = isValidType ? green : red;
                        filterAttributes.Add("<b>Type:</b> <color=" + color + ">" + vessel.vesselType.ToString() + "</color>");
                        if (!isValidType)
                        {
                            filterThisTarget = true;
                        }
                    }
                    if (filterHasCrewTrait != null)
                    {
                        int traitCount    = TargetVessel.GetCrewCountWithTrait(vessel, filterHasCrewTrait);
                        int requiredCount = 1;
                        if (filterHasCrewTraitCount != null)
                        {
                            requiredCount = (int)filterHasCrewTraitCount;
                        }
                        string color = traitCount >= requiredCount ? green : red;
                        filterAttributes.Add("<b>" + filterHasCrewTrait + "s:</b> <color=" + color + ">" + traitCount.ToString() + "/" + requiredCount.ToString() + "</color>");
                        if (traitCount < requiredCount)
                        {
                            filterThisTarget = true;
                        }
                    }
                    if (filterAttributes.Count > 0)
                    {
                        descriptions.Add(String.Join(" ", filterAttributes.ToArray()));
                    }

                    contents.Add(new GUIContent(String.Join("\n", descriptions.ToArray()) + "</color>"));
                    if (filterThisTarget)
                    {
                        filteredIndices.Add(index);                   // If there were filters, which did not match, we still show the target, but don't allow to select it.
                    }
                    index++;
                }

                int newSelection = GUILayout.SelectionGrid(selectedIndex, contents.ToArray(), 1, GUI.selectionGridStyle);
                if (newSelection >= 0 && !filteredIndices.Contains(newSelection))
                {
                    // The player has selected a payload:
                    selectedIndex = newSelection;
                    targetVessel  = validTargets[selectedIndex];
                }
            }
            GUILayout.EndScrollView();
            return(targetVessel != null);
        }
Пример #6
0
        // Generates a description for displaying on the GUI:
        public string GetDescription()
        {
            var description = "<color=#F9FA86><b>" + profileName + "</b></color> <color=#FFFFFF>(" + GetMissionTypeName(missionType) + ")\n";

            var shipTemplate = GetShipTemplate();

            if (shipTemplate != null)
            {
                description += "<b>Ship:</b> " + shipName + " (" + shipTemplate.shipName.ToString() + ")\n";
            }

            if (orbit != null)
            {
                description += "<b>Orbit:</b> " + orbit.referenceBody.bodyName.ToString() + " @ " + GUI.FormatAltitude(orbit.semiMajorAxis - orbit.referenceBody.Radius) + "\n";
            }

            // Display the targeted vessel (transport- and construction-missions):
            Vessel targetVessel = null;

            if (targetVesselId != null && (targetVessel = TargetVessel.GetVesselById((Guid)targetVesselId)) != null)
            {
                description += "<b>Target:</b> " + targetVessel.vesselName + " @ " + GUI.FormatAltitude(targetVessel.altitude) + "\n";
            }

            // Display the total weight of the payload we are hauling (transport-missions):
            if (resourcesToDeliver != null)
            {
                double totalMass = 0;
                foreach (var item in resourcesToDeliver)
                {
                    if (!KSTS.resourceDictionary.ContainsKey(item.Key))
                    {
                        continue;
                    }

                    totalMass += KSTS.resourceDictionary[item.Key].density * item.Value;
                }
                description += "<b>Cargo:</b> " + totalMass.ToString("#,##0.00t") + "\n";
            }

            // Display the crew-members we are transporting and collection:
            if (crewToDeliver != null && crewToDeliver.Count > 0)
            {
                description += "<b>Crew-Transfer (Outbound):</b> " + String.Join(", ", crewToDeliver.ToArray()).Replace(" Kerman", "") + "\n";
            }
            if (crewToCollect != null && crewToCollect.Count > 0)
            {
                description += "<b>Crew-Transfer (Inbound):</b> " + String.Join(", ", crewToCollect.ToArray()).Replace(" Kerman", "") + "\n";
            }

            // Display the remaining time:
            var remainingTime = eta - Planetarium.GetUniversalTime();

            if (remainingTime < 0)
            {
                remainingTime = 0;
            }

            var etaColorComponent = 0xFF;

            if (remainingTime <= 300)
            {
                etaColorComponent = (int)Math.Round((0xFF / 300.0) * remainingTime); // Starting at 5 minutes, start turning the ETA green.
            }

            var etaColor = "#" + etaColorComponent.ToString("X2") + "FF" + etaColorComponent.ToString("X2");

            description += "<color=" + etaColor + "><b>ETA:</b> " + GUI.FormatDuration(remainingTime) + "</color>";

            description += "</color>";
            return(description);
        }
Пример #7
0
        // Tries to execute this mission and returns true if it was successfull:
        public bool TryExecute()
        {
            switch (missionType)
            {
            case MissionType.DEPLOY:
                // Ship-Creation is only possible while not in flight with the current implementation:
                if (HighLogic.LoadedScene != GameScenes.FLIGHT)
                {
                    CreateShip();
                    return(true);
                }
                return(false);

            case MissionType.CONSTRUCT:
                if (HighLogic.LoadedScene != GameScenes.FLIGHT)
                {
                    Vessel targetVessel = null;
                    if (targetVesselId == null || (targetVessel = TargetVessel.GetVesselById((Guid)targetVesselId)) == null || !TargetVessel.IsValidTarget(targetVessel, MissionController.missionProfiles[profileName]))
                    {
                        // Abort mission (maybe the vessel was removed or got moved out of range):
                        Debug.Log("[KSTS] aborting transport-construction: target-vessel missing or out of range");
                        ScreenMessages.PostScreenMessage("Aborting construction-mission: Target-vessel not found at expected rendezvous-coordinates!");
                    }
                    else
                    {
                        CreateShip();
                        return(true);
                    }
                }
                return(false);

            case MissionType.TRANSPORT:
                // Our functions for manipulating ships don't work on active vessels, beeing in flight however should be fine:
                if (FlightGlobals.ActiveVessel == null || FlightGlobals.ActiveVessel.id != targetVesselId)
                {
                    Vessel targetVessel = null;
                    if (!MissionController.missionProfiles.ContainsKey(profileName))
                    {
                        throw new Exception("unable to execute transport-mission, profile '" + profileName + "' missing");
                    }

                    if (targetVesselId == null || (targetVessel = TargetVessel.GetVesselById((Guid)targetVesselId)) == null || !TargetVessel.IsValidTarget(targetVessel, MissionController.missionProfiles[profileName]))
                    {
                        // Abort mission (maybe the vessel was removed or got moved out of range):
                        Debug.Log("[KSTS] aborting transport-mission: target-vessel missing or out of range");
                        ScreenMessages.PostScreenMessage("Aborting transport-mission: Target-vessel not found at expected rendezvous-coordinates!");
                    }
                    else
                    {
                        // Do the actual transport-mission:
                        if (resourcesToDeliver != null)
                        {
                            foreach (var item in resourcesToDeliver)
                            {
                                TargetVessel.AddResources(targetVessel, item.Key, item.Value);
                            }
                        }
                        if (crewToCollect != null)
                        {
                            foreach (var kerbonautName in crewToCollect)
                            {
                                TargetVessel.RecoverCrewMember(targetVessel, kerbonautName);
                            }
                        }
                        if (crewToDeliver != null)
                        {
                            foreach (var kerbonautName in crewToDeliver)
                            {
                                TargetVessel.AddCrewMember(targetVessel, kerbonautName);
                            }
                        }
                    }
                    return(true);
                }
                return(false);

            default:
                throw new Exception("unexpected mission-type '" + missionType.ToString() + "'");
            }
        }
Пример #8
0
        // Modifies and returns the given orbit so that a vessel of the given size won't collide with any other vessel on the same orbit:
        public static Orbit ApplySafetyDistance(Orbit orbit, float vesselSize)
        {
            // Find out how many degrees one meter is on the given orbit (same formula as above):
            var anglePerMeters = Math.Sinh(1.0 / (2 * orbit.semiMajorAxis)) * 2;

            // Check with every other vessel on simmilar orbits, if they might collide in the future:
            var  rnd = new System.Random();
            var  adjustmentIterations = 0;
            bool orbitAdjusted;

            do
            {
                orbitAdjusted = false;
                foreach (var vessel in FlightGlobals.Vessels)
                {
                    if (vessel.situation != Vessel.Situations.ORBITING)
                    {
                        continue;
                    }
                    if (vessel.orbit.referenceBody != orbit.referenceBody)
                    {
                        continue;
                    }

                    // Find the next rendezvous (most of these parameters are just guesses, but they seem to work):
                    var    UT             = Planetarium.GetUniversalTime();
                    double dT             = 86400;
                    double threshold      = 5000;
                    var    MinUT          = Planetarium.GetUniversalTime() - 86400;
                    var    MaxUT          = Planetarium.GetUniversalTime() + 86400;
                    double epsilon        = 360;
                    var    maxIterations  = 25;
                    var    iterationCount = 0;
                    vessel.orbit.UpdateFromUT(Planetarium.GetUniversalTime()); // We apparently have to update both orbits to the current time to make this work.
                    orbit.UpdateFromUT(Planetarium.GetUniversalTime());
                    var closestApproach = Orbit._SolveClosestApproach(vessel.orbit, orbit, ref UT, dT, threshold, MinUT, MaxUT, epsilon, maxIterations, ref iterationCount);
                    if (closestApproach < 0)
                    {
                        continue;                      // No contact
                    }
                    if (closestApproach > 10000)
                    {
                        continue;                          // 10km should be fine
                    }
                    double blockerSize = TargetVessel.GetVesselSize(vessel);
                    if (closestApproach < (blockerSize + vesselSize) / 2) // We assume the closest approach is calculated from the center of mass, which is why we use /2
                    {
                        // Adjust orbit:
                        var adjustedAngle = (blockerSize / 2 + vesselSize / 2 + 1) * anglePerMeters; // Size of both vessels + 1m
                        if (adjustmentIterations >= 90)
                        {
                            adjustedAngle *= rnd.Next(1, 1000);                             // Lets get bolder here, time is running out ...
                        }
                        // Modifying "orbit.meanAnomalyAtEpoch" works for the actual vessel, but apparently one would have to call some other method as well to updated
                        // additional internals of the object, because the "_SolveClosestApproach"-function does not register this change, which is why we simply create
                        // a new, modified orbit:
                        orbit = CreateOrbit(orbit.inclination, orbit.eccentricity, orbit.semiMajorAxis, orbit.LAN, orbit.argumentOfPeriapsis, orbit.meanAnomalyAtEpoch + adjustedAngle, orbit.epoch, orbit.referenceBody);

                        orbitAdjusted = true;
                        Debug.Log("[KSTS] adjusting planned orbit by " + adjustedAngle + "° to avoid collision with '" + vessel.vesselName + "' (closest approach " + closestApproach.ToString() + "m @ " + UT.ToString() + " after " + iterationCount + " orbits)");
                    }
                }
                adjustmentIterations++;
                if (adjustmentIterations >= 100 && orbitAdjusted)
                {
                    Debug.LogError("[KSTS] unable to find a safe orbit after " + adjustmentIterations.ToString() + " iterations, the vessels will likely crash");
                    break;
                }
            }while (orbitAdjusted);
            return(orbit);
        }
Пример #9
0
        public override void OnSave(ConfigNode node)
        {
            try
            {
                /*
                 * When transporting an available kerbal to a ship or recovering one from orbit, we manipulate the (unloaded) vessel's crew
                 * as well as the roster-status of the kerbal. This is apparently not expected by KSP's core functionality, because there
                 * seems to be a secret list of roster-status which is enforced when the game is safed:
                 *
                 * [WRN 15:16:14.678] [ProtoCrewMember Warning]: Crewmember Sierina Kerman found inside a part but status is set as missing. Vessel must have failed to save earlier. Restoring assigned status.
                 * [WRN 15:17:42.913] [ProtoCrewMember Warning]: Crewmember Sierina Kerman found assigned but no vessels reference him. Sierina Kerman set as missing.
                 *
                 * Afterwards these kerbals would be lost for the player, which is why we have to use the workaround below to revert these
                 * changes and make sure each kerbal has the correct status. This effectively disables the "missing" status as these kerbals
                 * will always respawn, but I haven't seen a valid use-case for this thus far, so it is probably fine.
                 */
                if (HighLogic.CurrentGame.CrewRoster.Count > 0 && FlightGlobals.Vessels.Count > 0)
                {
                    // Build a list of all Kerbals which are assigned to vessels:
                    List <string> vesselCrewNames = new List <string>();
                    foreach (Vessel vessel in FlightGlobals.Vessels)
                    {
                        foreach (ProtoCrewMember crewMember in TargetVessel.GetCrew(vessel))
                        {
                            vesselCrewNames.Add(crewMember.name);
                        }
                    }

                    // Build a list of all kerbals which we could have manipulated:
                    List <ProtoCrewMember> kerbals = new List <ProtoCrewMember>();
                    foreach (ProtoCrewMember kerbal in HighLogic.CurrentGame.CrewRoster.Kerbals(ProtoCrewMember.KerbalType.Crew))
                    {
                        kerbals.Add(kerbal);
                    }
                    foreach (ProtoCrewMember kerbal in HighLogic.CurrentGame.CrewRoster.Kerbals(ProtoCrewMember.KerbalType.Tourist))
                    {
                        kerbals.Add(kerbal);
                    }

                    // Check those kerbals against our vessel-list and maybe restore their correct status:
                    foreach (ProtoCrewMember kerbal in kerbals)
                    {
                        if (kerbal.rosterStatus == ProtoCrewMember.RosterStatus.Dead)
                        {
                            continue;
                        }
                        if (vesselCrewNames.Contains(kerbal.name) && kerbal.rosterStatus != ProtoCrewMember.RosterStatus.Assigned)
                        {
                            Debug.Log("[KSTS] setting kerbal " + kerbal.name + " from " + kerbal.rosterStatus.ToString() + " to Assigned (see code for more info)");
                            kerbal.rosterStatus = ProtoCrewMember.RosterStatus.Assigned;
                        }
                        else if (!vesselCrewNames.Contains(kerbal.name) && kerbal.rosterStatus != ProtoCrewMember.RosterStatus.Available)
                        {
                            Debug.Log("[KSTS] setting kerbal " + kerbal.name + " from " + kerbal.rosterStatus.ToString() + " to Available (see code for more info)");
                            kerbal.rosterStatus = ProtoCrewMember.RosterStatus.Available;
                        }
                    }
                }

                FlightRecorder.SaveRecordings(node);
                MissionController.SaveMissions(node);
            }
            catch (Exception e)
            {
                Debug.LogError("[KSTS] OnSave(): " + e.ToString());
            }
        }
Пример #10
0
        // Shows a list of all available crew-members which the player can choose to transport and returns true, if the selection is valid:
        public bool DisplayList()
        {
            var targetCrewCapacity = 0;

            if (targetVessel != null)
            {
                targetCrewCapacity = TargetVessel.GetCrewCapacity(targetVessel);
            }
            else if (targetTemplate != null)
            {
                targetCrewCapacity = targetTemplate.GetCrewCapacity();
            }

            if (missionProfile.crewCapacity == 0 && missionProfile.missionType == MissionProfileType.TRANSPORT) // We only care about the seats on the transport-vessel during transport-missions.
            {
                GUILayout.Label("There are no available seats in the selected mission-profile.");
                return(true);
            }
            else if (targetCrewCapacity == 0) // If the target has no seats, we can't transport anyone.
            {
                GUILayout.Label("The selected target-vessel can not hold any crew-members.");
                return(true);
            }
            else
            {
                // Target-vessel summary:
                var    targetOverload = false;
                string headline;
                if (targetVessel != null) // Existing vessel (in- & outboud transfers possible)
                {
                    // Display capacity and transfer deltas:
                    var targetVesselCrew = TargetVessel.GetCrew(targetVessel);
                    if (targetVesselCrew.Count + crewToDeliver.Count - crewToCollect.Count > targetCrewCapacity)
                    {
                        targetOverload = true;
                    }
                    headline = "<b>" + targetVessel.vesselName + ":</b> " + targetVesselCrew.Count.ToString() + "/" + targetCrewCapacity.ToString();
                    var transfers = " inbound: " + crewToDeliver.Count.ToString("+#;-#;0") + ", outbound: " + (-crewToCollect.Count).ToString("+#;-#;0");
                    if (targetOverload)
                    {
                        transfers = "<color=#FF0000>" + transfers + "</color>";
                    }
                    GUILayout.Label(headline + transfers);

                    // Display Crew that is stationed on the target vessel:
                    foreach (var kerbonaut in targetVesselCrew)
                    {
                        var details = " <b>" + kerbonaut.name + "</b> (Level " + kerbonaut.experienceLevel.ToString() + " " + kerbonaut.trait + ")";
                        if (missionProfile.oneWayMission || MissionController.GetKerbonautsMission(kerbonaut.name) != null || missionProfile.missionType != MissionProfileType.TRANSPORT)
                        {
                            GUILayout.Label(" • " + details);                                                                                                                                                               // Do not transport kerbals, which are flagged for another mission or there isn't even a return-trip or transport happening
                        }
                        else
                        {
                            var selected = GUILayout.Toggle(crewToCollect.Contains(kerbonaut.name), details);
                            if (selected && !crewToCollect.Contains(kerbonaut.name))
                            {
                                crewToCollect.Add(kerbonaut.name);
                            }
                            else if (!selected && crewToCollect.Contains(kerbonaut.name))
                            {
                                crewToCollect.Remove(kerbonaut.name);
                            }
                        }
                    }
                    GUILayout.Label("");
                }
                else if (targetTemplate != null) // New vessel (only inbound transfers possible)
                {
                    // Display capacity:
                    if (crewToDeliver.Count > targetCrewCapacity)
                    {
                        targetOverload = true;
                    }
                    headline = "<b>" + targetTemplate.template.shipName + ":</b> ";
                    var seats = crewToDeliver.Count.ToString() + " / " + targetCrewCapacity.ToString() + " seat";
                    if (targetCrewCapacity != 1)
                    {
                        seats += "s";
                    }
                    if (targetOverload)
                    {
                        seats = "<color=#FF0000>" + seats + "</color>";
                    }
                    GUILayout.Label(headline + seats);
                }

                // Display Transport-vessel summary, if this is a transport-mission:
                var transportOutboundOverload = false;
                var transportInboundOverload  = false;
                if (missionProfile.missionType == MissionProfileType.TRANSPORT)
                {
                    if (crewToDeliver.Count > missionProfile.crewCapacity)
                    {
                        transportOutboundOverload = true;
                    }
                    if (crewToCollect.Count > missionProfile.crewCapacity)
                    {
                        transportInboundOverload = true;
                    }

                    headline = "<b>" + missionProfile.vesselName + ":</b> ";
                    var outbound = "outbound: " + crewToDeliver.Count.ToString() + "/" + missionProfile.crewCapacity.ToString();
                    if (transportOutboundOverload)
                    {
                        outbound = "<color=#FF0000>" + outbound + "</color>";
                    }
                    var inbound = "";
                    if (!missionProfile.oneWayMission)
                    {
                        inbound = ", inbound: " + crewToCollect.Count.ToString() + "/" + missionProfile.crewCapacity.ToString();
                        if (transportInboundOverload)
                        {
                            inbound = "<color=#FF0000>" + inbound + "</color>";
                        }
                    }
                    else
                    {
                        inbound += ", inbound: -";
                    }
                    GUILayout.Label(headline + outbound + inbound);
                }

                // Display crew-rowster:
                foreach (var kerbonaut in GetCrewRoster())
                {
                    var details = " <b>" + kerbonaut.name + "</b> (Level " + kerbonaut.experienceLevel.ToString() + " " + kerbonaut.trait.ToString() + ")";
                    if (MissionController.GetKerbonautsMission(kerbonaut.name) != null)
                    {
                        GUILayout.Label(" • " + details);                                                                 // Do not transport kerbals, which are flagged for another mission
                    }
                    else
                    {
                        var selected = GUILayout.Toggle(crewToDeliver.Contains(kerbonaut.name), details);
                        if (selected && !crewToDeliver.Contains(kerbonaut.name))
                        {
                            crewToDeliver.Add(kerbonaut.name);
                        }
                        else if (!selected && crewToDeliver.Contains(kerbonaut.name))
                        {
                            crewToDeliver.Remove(kerbonaut.name);
                        }
                    }
                }

                // Check if the selection is valid (it neither overloads the target nor the transport):
                if (!targetOverload && !transportOutboundOverload && !transportInboundOverload)
                {
                    return(true);
                }
                return(false);
            }
        }
Пример #11
0
        private static bool DisplayInner()
        {
            // Payload selection:
            if (payloadShipSelector == null)
            {
                payloadShipSelector = new GUIPayloadShipSelector();
            }
            if (payloadShipSelector.payload == null)
            {
                payloadShipSelector.DisplayList();
                return(false);
            }
            if (payloadShipSelector.DisplaySelected())
            {
                targetVesselSelector   = null;
                missionProfileSelector = null;
                crewTransferSelector   = null;
                flagSelector           = null;
                return(false);
            }
            currentCost += payloadShipSelector.payload.template.totalCost;
            var    dryMass           = payloadShipSelector.payload.GetDryMass();
            double totalMass         = payloadShipSelector.payload.template.totalMass;
            var    engineersRequired = (int)Math.Ceiling(Math.Log(Math.Ceiling(dryMass / 10)) / Math.Log(2)) + 1; // One engineer can construct up to 10t, each additional engineer doubles that number

            // Target (space-dock) selection:
            if (targetVesselSelector == null)
            {
                targetVesselSelector = new GUITargetVesselSelector();
                targetVesselSelector.filterVesselType        = VesselType.Station;
                targetVesselSelector.filterHasCrewTrait      = "Engineer"; // There does not seem to be an enum for this.
                targetVesselSelector.filterHasCrewTraitCount = engineersRequired;
            }
            if (targetVesselSelector.targetVessel == null)
            {
                targetVesselSelector.DisplayList();
                return(false);
            }
            if (targetVesselSelector.DisplaySelected())
            {
                missionProfileSelector = null;
                crewTransferSelector   = null;
                flagSelector           = null;
                return(false);
            }

            // Mission-Profile selection:
            if (missionProfileSelector == null)
            {
                missionProfileSelector = new GUIMissionProfileSelector();
                missionProfileSelector.filterAltitude         = targetVesselSelector.targetVessel.orbit.ApA;
                missionProfileSelector.filterBody             = targetVesselSelector.targetVessel.orbit.referenceBody;
                missionProfileSelector.filterDockingPortTypes = TargetVessel.GetVesselDockingPortTypes(targetVesselSelector.targetVessel);
                missionProfileSelector.filterMissionType      = MissionProfileType.TRANSPORT;
                shipName = payloadShipSelector.payload.template.shipName;
            }
            if (missionProfileSelector.selectedProfile == null)
            {
                missionProfileSelector.DisplayList();
                return(false);
            }
            if (missionProfileSelector.DisplaySelected())
            {
                crewTransferSelector = null;
                flagSelector         = null;
                return(false);
            }

            if (crewTransferSelector == null)
            {
                crewTransferSelector = new GUICrewTransferSelector(payloadShipSelector.payload, missionProfileSelector.selectedProfile);
            }
            if (flagSelector == null)
            {
                flagSelector = new GUIFlagSelector();
            }

            // Display Construction-Info:
            scrollPos = GUILayout.BeginScrollView(scrollPos, GUI.scrollStyle);
            GUILayout.Label("<size=14><b>Construction Info:</b></size>");

            GUILayout.BeginHorizontal();
            GUILayout.Label("Ship name:", new GUIStyle(GUI.labelStyle)
            {
                stretchWidth = true
            });
            shipName = GUILayout.TextField(shipName, new GUIStyle(GUI.textFieldStyle)
            {
                alignment = TextAnchor.MiddleRight, stretchWidth = false, fixedWidth = 320
            });
            GUILayout.EndHorizontal();

            // Calculate and display all the construction-parameters:
            var engineers  = TargetVessel.GetCrewCountWithTrait(targetVesselSelector.targetVessel, "Engineer");
            var scientists = TargetVessel.GetCrewCountWithTrait(targetVesselSelector.targetVessel, "Scientist");

            if (engineers < engineersRequired)
            {
                throw new Exception("not enough engineers on target vessel");
            }
            if (missionProfileSelector.selectedProfile.payloadMass <= 0)
            {
                throw new Exception("mission profile payload too low");
            }
            var flights              = (int)Math.Ceiling(totalMass / missionProfileSelector.selectedProfile.payloadMass);
            var flightTime           = missionProfileSelector.selectedProfile.missionDuration;
            var totalFlightTime      = flightTime * flights;
            var baseConstructionTime = dryMass * 6 * 60 * 60; // 1 (kerbin-) day / ton
            var totalFlightCost      = missionProfileSelector.selectedProfile.launchCost * flights;

            currentCost     += totalFlightCost;
            constructionTime = baseConstructionTime;
            if (scientists > 0)
            {
                constructionTime = baseConstructionTime / (scientists + 1);                 // half the time per scientist
            }
            if (totalFlightTime > constructionTime)
            {
                constructionTime = totalFlightTime;
            }

            var leftLabel = new GUIStyle(GUI.labelStyle)
            {
                stretchWidth = true
            };
            var rightLabel = new GUIStyle(GUI.labelStyle)
            {
                stretchWidth = false, alignment = TextAnchor.MiddleRight
            };

            GUILayout.BeginHorizontal();
            GUILayout.Label("Mass:", leftLabel);
            GUILayout.Label(totalMass.ToString("#,##0.00t") + " / " + dryMass.ToString("#,##0.00t") + " dry", rightLabel);
            GUILayout.EndHorizontal();

            GUILayout.BeginHorizontal();
            GUILayout.Label("Dock-Capacity (" + engineers.ToString() + " engineer" + (engineers != 1 ? "s" : "") + "):", leftLabel);
            GUILayout.Label((Math.Pow(2, engineers - 1) * 10).ToString("#,##0.00t") + " dry", rightLabel);
            GUILayout.EndHorizontal();

            GUILayout.BeginHorizontal();
            GUILayout.Label("Single Flight (" + (missionProfileSelector.selectedProfile.launchCost / missionProfileSelector.selectedProfile.payloadMass).ToString("#,##0 √/t") + "):", leftLabel);
            GUILayout.Label(missionProfileSelector.selectedProfile.payloadMass.ToString("#,##0.00t") + " in " + GUI.FormatDuration(flightTime) + " for " + missionProfileSelector.selectedProfile.launchCost.ToString("#,##0 √"), rightLabel);
            GUILayout.EndHorizontal();

            GUILayout.BeginHorizontal();
            GUILayout.Label("Total Flights:", leftLabel);
            GUILayout.Label(flights.ToString("#,##0") + " in " + GUI.FormatDuration(totalFlightTime) + " for " + totalFlightCost.ToString("#,##0 √"), rightLabel);
            GUILayout.EndHorizontal();

            GUILayout.BeginHorizontal();
            GUILayout.Label("Base Construction Time (6h/t):", leftLabel);
            GUILayout.Label(GUI.FormatDuration(baseConstructionTime), rightLabel);
            GUILayout.EndHorizontal();

            GUILayout.BeginHorizontal();
            GUILayout.Label("Total Construction Time (" + scientists.ToString() + " scientist" + (scientists != 1 ? "s" : "") + "):", leftLabel);
            GUILayout.Label(GUI.FormatDuration(constructionTime), rightLabel);
            GUILayout.EndHorizontal();

            // Display crew-selector, if the new ship can hold kerbals:
            var selectionIsValid = true;

            if (payloadShipSelector.payload.GetCrewCapacity() > 0)
            {
                GUILayout.Label("");
                GUILayout.Label("<size=14><b>Crew:</b></size>");
                if (!crewTransferSelector.DisplayList())
                {
                    selectionIsValid = false;
                }
            }

            // Show Button for Flag-Selector:
            GUILayout.Label("");
            flagSelector.ShowButton();

            GUILayout.EndScrollView();
            return(selectionIsValid);
        }
Пример #12
0
        private static bool DisplayInner()
        {
            // Target selection:
            if (targetVesselSelector == null)
            {
                targetVesselSelector = new GUITargetVesselSelector();
            }
            if (targetVesselSelector.targetVessel == null)
            {
                targetVesselSelector.DisplayList();
                return(false);
            }
            if (targetVesselSelector.DisplaySelected())
            {
                missionProfileSelector  = null;
                payloadResourceSelector = null;
                return(false);
            }

            // Mission-Profile selection:
            if (missionProfileSelector == null)
            {
                missionProfileSelector = new GUIMissionProfileSelector();
                missionProfileSelector.filterAltitude         = targetVesselSelector.targetVessel.orbit.ApA;
                missionProfileSelector.filterBody             = targetVesselSelector.targetVessel.orbit.referenceBody;
                missionProfileSelector.filterDockingPortTypes = TargetVessel.GetVesselDockingPortTypes(targetVesselSelector.targetVessel);
                missionProfileSelector.filterMissionType      = MissionProfileType.TRANSPORT;
            }
            if (missionProfileSelector.selectedProfile == null)
            {
                missionProfileSelector.DisplayList();
                return(false);
            }
            if (missionProfileSelector.DisplaySelected(GUIMissionProfileSelector.SELECTED_DETAILS_PAYLOAD))
            {
                payloadResourceSelector = null;
                return(false);
            }
            currentCost += missionProfileSelector.selectedProfile.launchCost;

            // Payload-Resource selection:
            if (payloadResourceSelector == null)
            {
                payloadResourceSelector = new GUITransportSelector(targetVesselSelector.targetVessel, missionProfileSelector.selectedProfile);
            }
            payloadResourceSelector.DisplayList(); // Always display this selector (it has to be the last), but return when nothing is selected.

            if (payloadResourceSelector.selectedResources != null)
            {
                // Determine the cost of the selected resources:
                foreach (var payloadResource in payloadResourceSelector.selectedResources)
                {
                    currentCost += KSTS.resourceDictionary[payloadResource.name].unitCost * payloadResource.amount;
                }
                return(true);
            }
            else if (payloadResourceSelector.selectedCrewTransfers != null)
            {
                return(true);
            }
            return(false);
        }
Пример #13
0
        // Creates a new ship with the given parameters for this mission. The code however seems unnecessarily convoluted and
        // error-prone, but there are no better examples available on the internet.
        private void CreateShip()
        {
            // TODO: Apply the staging which was saved in the editor.
            // TODO: Settings from other mods like Part-Switchers must also be applied here, maybe copy the config-nodes...
            try
            {
                if (!File.Exists(shipTemplateFilename))
                {
                    throw new Exception("file '" + shipTemplateFilename + "' not found");
                }
                if (missionType == MissionType.DEPLOY)
                {
                    // Make sure that there won't be any collisions, when the vessel is created at the given orbit:
                    orbit = GUIOrbitEditor.ApplySafetyDistance(orbit);
                }
                else if (missionType == MissionType.CONSTRUCT)
                {
                    // Deploy the new ship next to the space-dock:
                    Vessel spaceDock = TargetVessel.GetVesselById((Guid)targetVesselId);
                    orbit = GUIOrbitEditor.CreateFollowingOrbit(spaceDock.orbit, 100); // TODO: Calculate the distance using the ships's size
                    orbit = GUIOrbitEditor.ApplySafetyDistance(orbit);
                }
                else
                {
                    throw new Exception("invalid mission-type '" + missionType.ToString() + "'");
                }

                // The ShipConstruct-object can only savely exist while not in flight, otherwise it will spam Null-Pointer Exceptions every tick:
                if (HighLogic.LoadedScene == GameScenes.FLIGHT)
                {
                    throw new Exception("unable to run CreateShip while in flight");
                }

                // Load the parts form the saved vessel:
                ShipConstruct shipConstruct = ShipConstruction.LoadShip(shipTemplateFilename);
                ProtoVessel   dummyProto    = new ProtoVessel(new ConfigNode(), null);
                Vessel        dummyVessel   = new Vessel();
                dummyProto.vesselRef = dummyVessel;

                // In theory it should be enough to simply copy the parts from the ShipConstruct to the ProtoVessel, but
                // this only seems to work when the saved vessel starts with the root-part and is designed top down from there.
                // It seems that the root part has to be the first part in the ProtoVessel's parts-list and all other parts have
                // to be listed in sequence radiating from the root part (eg 1=>2=>R<=3<=4 should be R,2,1,3,4). If the parts
                // are not in the correct order, their rotation seems to get messed up or they are attached to the wrong
                // attachmet-nodes, which is why we have to re-sort the parts with our own logic here.
                // This part of the code is experimental however and only based on my own theories and observations about KSP's vessels.
                Part rootPart = null;
                foreach (Part p in shipConstruct.parts)
                {
                    if (p.parent == null)
                    {
                        rootPart = p; break;
                    }
                }
                List <Part> pList = null;
                dummyVessel.parts = FindAndAddAttachedParts(rootPart, ref pList); // Find all parts which are directly attached to the root-part and add them in order.

                // Handle Subassemblies which are attached by surface attachment-nodes:
                bool handleSurfaceAttachments = true;
                while (dummyVessel.parts.Count < shipConstruct.parts.Count)
                {
                    int processedParts = 0;
                    foreach (Part p in shipConstruct.parts)
                    {
                        if (dummyVessel.parts.Contains(p))
                        {
                            continue;
                        }
                        if (handleSurfaceAttachments)
                        {
                            // Check if the part is attached by a surface-node:
                            if (p.srfAttachNode != null && dummyVessel.parts.Contains(p.srfAttachNode.attachedPart))
                            {
                                // Add this surface attached part and all the sub-parts:
                                dummyVessel.parts = FindAndAddAttachedParts(p, ref dummyVessel.parts);
                                processedParts++;
                            }
                        }
                        else
                        {
                            // Simply copy this part:
                            dummyVessel.parts.Add(p);
                        }
                    }
                    if (processedParts == 0)
                    {
                        // If there are still unprocessed parts, just throw them in the list during the next iteration,
                        // this should not happen but we don't want to end up in an endless loop:
                        handleSurfaceAttachments = false;
                    }
                }

                // Initialize all parts:
                uint missionID = (uint)Guid.NewGuid().GetHashCode();
                uint launchID  = HighLogic.CurrentGame.launchID++;
                foreach (Part p in dummyVessel.parts)
                {
                    p.flagURL     = flagURL == null ? HighLogic.CurrentGame.flagURL : flagURL;
                    p.missionID   = missionID;
                    p.launchID    = launchID;
                    p.temperature = 1.0;

                    // If the KRnD-Mod is installed, make sure that all parts of this newly created ship are set to the lates version:
                    foreach (PartModule module in p.Modules)
                    {
                        if (module.moduleName != "KRnDModule")
                        {
                            continue;
                        }
                        Debug.Log("[KSTS] found KRnD on '" + p.name.ToString() + "', setting to latest stats");
                        foreach (BaseField field in module.Fields)
                        {
                            if (field.name.ToString() == "upgradeToLatest")
                            {
                                field.SetValue(1, module); // Newer versions of KRnD use this flag to upgrade all attributes of the given part to the latest levels, when the vessel is activated.
                                if (field.GetValue(module).ToString() != "1")
                                {
                                    Debug.LogError("[KSTS] unable to modify '" + field.name.ToString() + "'");
                                }
                            }
                        }
                    }

                    dummyProto.protoPartSnapshots.Add(new ProtoPartSnapshot(p, dummyProto));
                }

                // Store the parts in Config-Nodes:
                foreach (ProtoPartSnapshot p in dummyProto.protoPartSnapshots)
                {
                    p.storePartRefs();
                }
                List <ConfigNode> partNodesL = new List <ConfigNode>();
                foreach (var snapShot in dummyProto.protoPartSnapshots)
                {
                    ConfigNode node = new ConfigNode("PART");
                    snapShot.Save(node);
                    partNodesL.Add(node);
                }
                ConfigNode[] partNodes       = partNodesL.ToArray();
                ConfigNode[] additionalNodes = new ConfigNode[0];

                // This will actually create the ship and add it to the global list of flights:
                ConfigNode  protoVesselNode = ProtoVessel.CreateVesselNode(shipName, VesselType.Ship, orbit, 0, partNodes, additionalNodes);
                ProtoVessel pv = HighLogic.CurrentGame.AddVessel(protoVesselNode);
                Debug.Log("[KSTS] deployed new ship '" + shipName.ToString() + "' as '" + pv.vesselRef.id.ToString() + "'");
                ScreenMessages.PostScreenMessage("Vessel '" + shipName.ToString() + "' deployed"); // Popup message to notify the player
                Vessel newVessel = FlightGlobals.Vessels.Find(x => x.id == pv.vesselID);

                // Maybe add the initial crew to the vessel:
                if (crewToDeliver != null && crewToDeliver.Count > 0 && newVessel != null)
                {
                    foreach (string kerbonautName in crewToDeliver)
                    {
                        TargetVessel.AddCrewMember(newVessel, kerbonautName);
                    }
                }
            }
            catch (Exception e)
            {
                Debug.LogError("[KSTS] Mission.CreateShip(): " + e.ToString());
            }
        }