// 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); } }
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); }
// 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); }
// 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); }
// 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); }
// 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() + "'"); } }
// 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); }
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()); } }
// 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); } }
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); }
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); }
// 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()); } }