// 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); } }
// 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); }
// 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"); } ShipConstruct shipConstruct = ShipConstruction.LoadShip(shipTemplateFilename); ProtoVessel dummyProto = new ProtoVessel(new ConfigNode(), null); Vessel dummyVessel = new Vessel(); dummyProto.vesselRef = dummyVessel; // Maybe adjust the orbit: float 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: Vessel 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.ToString() + "'"); } // Instead of loading and constructing the ship ourselfs ShipConstruction.AssembleForLaunch() seems like the better // option, this however just seems to work while in flight and even then I was unable to get it working correctly // (it switches to the newly created ship and setting an orbit afterwards does not work correctly). // 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++; int maxStageOffset = -1; foreach (Part p in dummyVessel.parts) { p.flagURL = flagURL == null ? HighLogic.CurrentGame.flagURL : flagURL; p.missionID = missionID; p.launchID = launchID; p.temperature = 1.0; maxStageOffset = Math.Max(p.stageOffset, maxStageOffset); // stageOffset is offset of this part in the staging-order (0..n with -1 meaning not staged) // Apparently the part's ID from the saved craft is stored in craftID and has to get copied by hand into flightID, which is 0 by default. // If it is not set, docking won't work and since it is referenced by surface-attachments like struts and fuel lines, it should always // be the same as in the stored craft: p.flightID = p.craftID; // 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 (ProtoPartSnapshot 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); // While each part knows in which stage they are, the vessel has to know how many stages there are in total: newVessel.protoVessel.stage = maxStageOffset + 1; // an offest of 0 would mean that there is only one stage // 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); } } // Notify other mods about the new vessel: GameEvents.onVesselCreate.Fire(newVessel); } catch (Exception e) { Debug.LogError("[KSTS] Mission.CreateShip(): " + e.ToString()); } }