From e6436c0e1882095c9edabdc65676eb8b6e012294 Mon Sep 17 00:00:00 2001 From: Sovetskysoyuz Date: Thu, 11 Feb 2021 13:21:49 -0500 Subject: [PATCH 01/48] Initial set of changes from before fork --- AvionicsSystems.csproj | 39 +- ModifiedCode/ApproachSolverBW.cs | 418 ++ ModifiedCode/MASComponentAudioPlayer.cs | 284 ++ ModifiedCode/MASFlightComputer.cs | 1986 ++++++++ ModifiedCode/MASFlightComputerProxy.cs | 5462 ++++++++++++++++++++++ ModifiedCode/MASFlightComputerProxy2.cs | 4187 +++++++++++++++++ ModifiedCode/MASIKAC.cs | 266 ++ ModifiedCode/MASITransfer.cs | 1746 +++++++ ModifiedCode/MASVesselComputer.cs | 1499 ++++++ ModifiedCode/MASVesselComputerModules.cs | 1815 +++++++ Source/ApproachSolverBW.cs | 29 +- Source/MASComponentAudioPlayer.cs | 11 +- Source/MASFlightComputer.cs | 4 + Source/MASFlightComputerProxy.cs | 9 +- Source/MASFlightComputerProxy2.cs | 20 + Source/MASIAtmosphereAutopilot.cs | 214 + Source/MASIKAC.cs | 36 + Source/MASITransfer.cs | 153 + Source/MASVesselComputer.cs | 126 +- Source/MASVesselComputerModules.cs | 2 +- 20 files changed, 18271 insertions(+), 35 deletions(-) create mode 100644 ModifiedCode/ApproachSolverBW.cs create mode 100644 ModifiedCode/MASComponentAudioPlayer.cs create mode 100644 ModifiedCode/MASFlightComputer.cs create mode 100644 ModifiedCode/MASFlightComputerProxy.cs create mode 100644 ModifiedCode/MASFlightComputerProxy2.cs create mode 100644 ModifiedCode/MASIKAC.cs create mode 100644 ModifiedCode/MASITransfer.cs create mode 100644 ModifiedCode/MASVesselComputer.cs create mode 100644 ModifiedCode/MASVesselComputerModules.cs create mode 100644 Source/MASIAtmosphereAutopilot.cs diff --git a/AvionicsSystems.csproj b/AvionicsSystems.csproj index 72fb0026..718c1a68 100644 --- a/AvionicsSystems.csproj +++ b/AvionicsSystems.csproj @@ -32,54 +32,60 @@ - D:\Games\KSP-IVA\KSP_x64_Data\Managed\Assembly-CSharp.dll + D:\KSPMod\IVAInstall\Minimal Mods\Kerbal Space Program\KSP_x64_Data\Managed\Assembly-CSharp.dll - D:\Games\KSP-IVA\KSP_x64_Data\Managed\Assembly-CSharp-firstpass.dll + D:\KSPMod\IVAInstall\Minimal Mods\Kerbal Space Program\KSP_x64_Data\Managed\Assembly-CSharp-firstpass.dll + + + ..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Kerbal Space Program\GameData\AtmosphereAutopilot\AtmosphereAutopilot.dll + + + ..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Kerbal Space Program\GameData\AtmosphereAutopilot\AtmosphereAutopilot.UI.dll - GameData\MOARdV\AvionicsSystems\MoonSharp.Interpreter.dll + ..\..\..\Downloads\Games\KSP\Modding\moonsharp_release_2.0.0.0\repl\MoonSharp.Interpreter.dll - D:\Games\KSP-IVA\KSP_x64_Data\Managed\UnityEngine.dll + D:\KSPMod\IVAInstall\Minimal Mods\Kerbal Space Program\KSP_x64_Data\Managed\UnityEngine.dll - D:\Games\KSP-IVA\KSP_x64_Data\Managed\UnityEngine.AnimationModule.dll + D:\KSPMod\IVAInstall\Minimal Mods\Kerbal Space Program\KSP_x64_Data\Managed\UnityEngine.AnimationModule.dll - D:\Games\KSP-IVA\KSP_x64_Data\Managed\UnityEngine.AssetBundleModule.dll + D:\KSPMod\IVAInstall\Minimal Mods\Kerbal Space Program\KSP_x64_Data\Managed\UnityEngine.AssetBundleModule.dll - D:\Games\KSP-IVA\KSP_x64_Data\Managed\UnityEngine.AudioModule.dll + D:\KSPMod\IVAInstall\Minimal Mods\Kerbal Space Program\KSP_x64_Data\Managed\UnityEngine.AudioModule.dll - D:\Games\KSP-IVA\KSP_x64_Data\Managed\UnityEngine.CoreModule.dll + D:\KSPMod\IVAInstall\Minimal Mods\Kerbal Space Program\KSP_x64_Data\Managed\UnityEngine.CoreModule.dll - D:\Games\KSP-IVA\KSP_x64_Data\Managed\UnityEngine.IMGUIModule.dll + D:\KSPMod\IVAInstall\Minimal Mods\Kerbal Space Program\KSP_x64_Data\Managed\UnityEngine.IMGUIModule.dll - D:\Games\KSP-IVA\KSP_x64_Data\Managed\UnityEngine.InputLegacyModule.dll + D:\KSPMod\IVAInstall\Minimal Mods\Kerbal Space Program\KSP_x64_Data\Managed\UnityEngine.InputLegacyModule.dll - D:\Games\KSP-IVA\KSP_x64_Data\Managed\UnityEngine.PhysicsModule.dll + D:\KSPMod\IVAInstall\Minimal Mods\Kerbal Space Program\KSP_x64_Data\Managed\UnityEngine.PhysicsModule.dll - D:\Games\KSP-IVA\KSP_x64_Data\Managed\UnityEngine.TextRenderingModule.dll + D:\KSPMod\IVAInstall\Minimal Mods\Kerbal Space Program\KSP_x64_Data\Managed\UnityEngine.TextRenderingModule.dll - D:\Games\KSP-IVA\KSP_x64_Data\Managed\UnityEngine.UI.dll + D:\KSPMod\IVAInstall\Minimal Mods\Kerbal Space Program\KSP_x64_Data\Managed\UnityEngine.UI.dll - D:\Games\KSP-IVA\KSP_x64_Data\Managed\UnityEngine.UIModule.dll + D:\KSPMod\IVAInstall\Minimal Mods\Kerbal Space Program\KSP_x64_Data\Managed\UnityEngine.UIModule.dll - D:\Games\KSP-IVA\KSP_x64_Data\Managed\UnityEngine.UnityWebRequestAssetBundleModule.dll + D:\KSPMod\IVAInstall\Minimal Mods\Kerbal Space Program\KSP_x64_Data\Managed\UnityEngine.UnityWebRequestAssetBundleModule.dll - D:\Games\KSP-IVA\KSP_x64_Data\Managed\UnityEngine.UnityWebRequestModule.dll + D:\KSPMod\IVAInstall\Minimal Mods\Kerbal Space Program\KSP_x64_Data\Managed\UnityEngine.UnityWebRequestModule.dll @@ -108,6 +114,7 @@ + diff --git a/ModifiedCode/ApproachSolverBW.cs b/ModifiedCode/ApproachSolverBW.cs new file mode 100644 index 00000000..ec0a4cbf --- /dev/null +++ b/ModifiedCode/ApproachSolverBW.cs @@ -0,0 +1,418 @@ +//#define USE_OLD_SOLVER +/***************************************************************************** + * The MIT License (MIT) + * + * Copyright (c) 2016-2018 MOARdV + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + ****************************************************************************/ +using System; +using System.Collections.Generic; + +namespace AvionicsSystems +{ + internal class ApproachSolver + { + /// + /// How many subdivisions do we want to use per iteration to find the + /// local minimum? + /// + private static readonly int NumSubdivisions = 64; + + /// + /// How many recursions do we want to allow before we punt? + /// + private static readonly int MaxRecursions = 16; + + /// + /// How many orbits into the future do we want to look for the closest + /// approach? + /// + private static readonly int NumOrbitsLookAhead = 6; + + /// + /// Iterate through the next several patches on the orbit to find the + /// first one that shares the same reference body as the supplied + /// parameter. + /// + /// The orbit we're starting from. + /// The CelestialBody that we're heading for. + /// +#if USE_OLD_SOLVER + static private Orbit SelectClosestOrbit(Orbit startOrbit, CelestialBody referenceBody) + { + Orbit checkorbit = startOrbit; + int orbitcount = 0; + + while (checkorbit.nextPatch != null && checkorbit.patchEndTransition != Orbit.PatchTransitionType.FINAL && orbitcount < 3) + { + checkorbit = checkorbit.nextPatch; + orbitcount++; + if (checkorbit.referenceBody == referenceBody) + { + return checkorbit; + } + } + + return startOrbit; + } +#endif + + /// + /// Iterate across one step of the search. + /// + static private void FindClosest(Orbit sourceOrbit, Orbit targetOrbit, double startUT, double endUT, int recursionDepth, ref double closestDistance, ref double closestUT) + { + double deltaT = (endUT - startUT) / (double)NumSubdivisions; + double closestDistSq = closestDistance * closestDistance; + double closestTime = (startUT + endUT) * 0.5; + bool foundClosest = false; + for (double t = startUT; t <= endUT; t += deltaT) + { + Vector3d vesselPos = sourceOrbit.getPositionAtUT(t); + Vector3d targetPos = targetOrbit.getPositionAtUT(t); + + double distSq = (vesselPos - targetPos).sqrMagnitude; + if (distSq < closestDistSq) + { + closestDistSq = distSq; + closestTime = t; + foundClosest = true; + } + } + + if (foundClosest) + { + closestDistance = Math.Sqrt(closestDistSq); + closestUT = closestTime; + + if (deltaT < 1.0) + { + // If our timesteps are this small, I think + // this is an accurate enough estimate. + return; + } + if (recursionDepth == MaxRecursions) + { + // Hit recursion limit. Done. + return; + } + + FindClosest(sourceOrbit, targetOrbit, Math.Max(closestTime - deltaT, startUT), Math.Min(closestTime + deltaT, endUT), recursionDepth + 1, ref closestDistance, ref closestUT); + } + + // Did not improve on the previous iteration. Don't recurse. + } + + /// + /// Find the first orbit / orbit patch that is valid at the specified validUT. + /// + /// + /// + /// +#if !USE_OLD_SOLVER + private static Orbit FindOrbit(Orbit startingOrbit, double validUT) + { + if ((startingOrbit.StartUT <= validUT && startingOrbit.EndUT >= validUT) || startingOrbit.patchEndTransition == Orbit.PatchTransitionType.FINAL) + { + return startingOrbit; + } + + Orbit o = startingOrbit; + while (o.patchEndTransition != Orbit.PatchTransitionType.FINAL) + { + if (o.nextPatch == null) + { + // Not sure this is actually feasible. + return o; + } + o = o.nextPatch; + + if ((o.StartUT <= validUT && o.EndUT >= validUT) || o.patchEndTransition == Orbit.PatchTransitionType.FINAL) + { + return o; + } + } + + Utility.LogStaticError("... Wait! Exception because o.nextPath {0} and endTransition is {1}", + (o.nextPatch != null) ? "!null" : "null", o.patchEndTransition); + + throw new ArgumentNullException("FindOrbit failed... no valid orbits existed during validUT"); + } +#endif + + /// + /// Reset the "I'm ready" flag. + /// + internal void ResetComputation() + { + resultsReady = false; + } + + internal bool resultsReady { get; private set; } + internal double targetClosestDistance { get; private set; } + internal double targetClosestUT { get; private set; } + internal double targetClosestSpeed { get; private set; } + + /// + /// Find the closest approach to a given body. + /// + /// We do this by looking for every orbit patch that lists the targetBody + /// as its referenceBody, and then looking for the lowest periapsis amongst + /// those patches. We short-circuit the search at the first patch whose + /// Pe is below the datum (or sea-level). + /// + /// If no patches orbit the target body, we fall back to the conventional + /// orbital segments search. + /// + /// The starting orbit (either the vessel.orbit, or the first patch of a maneuver). + /// The body we are intercepting. + internal void SolveBodyIntercept(Orbit vesselOrbit, CelestialBody targetBody) + { + targetClosestDistance = double.MaxValue; + + double targetRadius = targetBody.Radius; + bool resultsFound = false; + + if (vesselOrbit.referenceBody == targetBody) + { + targetClosestDistance = vesselOrbit.PeR; + targetClosestUT = vesselOrbit.StartUT + vesselOrbit.timeToPe; + targetClosestSpeed = vesselOrbit.getOrbitalSpeedAt(targetClosestUT); + + if (targetClosestDistance < targetRadius) + { + resultsReady = true; + return; // Early return: the first segment we tested impacts the surface. + } + + resultsFound = true; + } + + Orbit checkorbit = vesselOrbit; + + while (checkorbit.nextPatch != null && checkorbit.patchEndTransition != Orbit.PatchTransitionType.FINAL) + { + checkorbit = checkorbit.nextPatch; + if (checkorbit.referenceBody == targetBody) + { + if (checkorbit.PeR < targetClosestDistance) + { + targetClosestDistance = checkorbit.PeR; + targetClosestUT = checkorbit.StartUT + checkorbit.timeToPe; + targetClosestSpeed = checkorbit.getOrbitalSpeedAt(targetClosestUT); + + if (checkorbit.PeR < targetRadius) + { + resultsReady = true; + return; // Early return: the first segment we tested impacts the surface. + } + + resultsFound = true; + } + } + } + + if (!resultsFound) + { + // None of the orbits orbit the target body. Fall back to the standard + // solver. + SolveOrbitIntercept(vesselOrbit, targetBody.orbit); + } + } + + /// + /// Find the closest approach between two objects in orbit(s). + /// + /// + /// + internal void SolveOrbitIntercept(Orbit vesselOrbit, Orbit targetOrbit) + { +#if USE_OLD_SOLVER + try + { + double now = Planetarium.GetUniversalTime(); + + Orbit startOrbit = SelectClosestOrbit(vesselOrbit, targetOrbit.referenceBody); + + if (startOrbit.eccentricity >= 1.0 || targetOrbit.eccentricity >= 1.0) + { + // Hyperbolic orbit is involved. Don't check multiple orbits. + // This is a fairly quick search. + + double startTime = now; + startTime = Math.Max(startTime, startOrbit.StartUT); + startTime = Math.Max(startTime, targetOrbit.StartUT); + + double endTime; + if (startOrbit.eccentricity >= 1.0) + { + endTime = startOrbit.EndUT; + } + else + { + endTime = startTime + startOrbit.period * 6.0; + } + if (targetOrbit.eccentricity >= 1.0) + { + endTime = Math.Min(endTime, targetOrbit.EndUT); + } + else + { + endTime = Math.Min(endTime, targetOrbit.period * 6.0); + } + + double targetClosestDistance = float.MaxValue; + double targetClosestTime = float.MaxValue; + FindClosest(startOrbit, targetOrbit, startTime, endTime, 0, ref targetClosestDistance, ref targetClosestTime); + + this.targetClosestDistance = targetClosestDistance; + this.targetClosestUT = targetClosestTime; + Vector3d relativeVelocity = targetOrbit.getOrbitalVelocityAtUT(targetClosestTime) - startOrbit.getOrbitalVelocityAtUT(targetClosestTime); + this.targetClosestSpeed = relativeVelocity.magnitude; + } + else + { + double startTime = now; + double endTime = startTime + startOrbit.period; + targetClosestDistance = float.MaxValue; + + for (int i = 0; i < NumOrbitsLookAhead; ++i) + { + double closestDistance = float.MaxValue; + double closestTime = float.MaxValue; + + FindClosest(startOrbit, targetOrbit, startTime, endTime, 0, ref closestDistance, ref closestTime); + startTime += startOrbit.period; + endTime += startOrbit.period; + + if (closestDistance < this.targetClosestDistance) + { + this.targetClosestDistance = closestDistance; + this.targetClosestUT = closestTime; + } + } + + Vector3d relativeVelocity = targetOrbit.getOrbitalVelocityAtUT(this.targetClosestUT) - startOrbit.getOrbitalVelocityAtUT(this.targetClosestUT); + this.targetClosestSpeed = relativeVelocity.magnitude; + + //this.targetClosestDistance = closestDistance; + //this.targetClosestUT = closestTime; + } + this.resultsReady = true; + } + catch (Exception e) + { + Utility.LogInfo("ApproachSolver threw {0}", e); + } +#else + // Set up initial conditions + Orbit vessel = vesselOrbit; + Orbit target1; + Orbit target2; + + double now; + double then; + + double closestDistance = float.MaxValue; + double closestUT = 0.0; + + while (vessel.patchEndTransition != Orbit.PatchTransitionType.FINAL) + { + now = vessel.StartUT; + then = vessel.EndUT; + + target1 = FindOrbit(targetOrbit, vessel.StartUT); + target2 = FindOrbit(targetOrbit, vessel.EndUT); + + //Utility.LogMessage(this, "vessel : {0:0} to {1:0}, transitions = {3} / {2}", vessel.StartUT, vessel.EndUT, vessel.patchEndTransition, vessel.patchStartTransition); + //Utility.LogMessage(this, "target1: {0:0} to {1:0}, transitions = {3} / {2}", target1.StartUT, target1.EndUT, target1.patchEndTransition, target1.patchStartTransition); + //Utility.LogMessage(this, "target2: {0:0} to {1:0}, transitions = {3} / {2}", target2.StartUT, target2.EndUT, target2.patchEndTransition, target2.patchStartTransition); + + while (target1 != target2 && !Double.IsInfinity(vessel.EndUT)) + { + + Utility.LogInfo(this, "ApproachSolver first FindClosest: vessel {0}, target1 {1}, target1.StartUT {2}, target1.EndUT{3}, closestDistance{4}, closestUT{5}", + vessel, target1, target1.StartUT, target1.EndUT, closestDistance, closestUT); + FindClosest(vessel, target1, Math.Max(now, target1.StartUT), target1.EndUT, 0, ref closestDistance, ref closestUT); + + target1 = target1.nextPatch; + } + + Utility.LogInfo(this, "ApproachSolver second FindClosest: vessel {0}, target1 {1}, now {2}, then {3}, closestDistance{4}, closestUT{5}", + vessel, target1, now, then, closestDistance, closestUT); + if (!Double.IsInfinity(then)) + { + FindClosest(vessel, target1, now, then, 0, ref closestDistance, ref closestUT); + } + vessel = vessel.nextPatch; + } + + // Final transition. + now = vessel.StartUT; + then = vessel.EndUT; + + // Don't bother searching ahead with hyperbolic orbits + int orbitsToCheck = (vessel.eccentricity < 1.0) ? NumOrbitsLookAhead : 1; + + for (int i = 0; i < orbitsToCheck; ++i) + { + target1 = FindOrbit(targetOrbit, now); + target2 = FindOrbit(target1, then); + + while (target1 != target2 && !Double.IsInfinity(vessel.EndUT)) + { + Utility.LogInfo(this, "ApproachSolver third FindClosest: vessel {0}, target1 {1}, target1.StartUT {2}, target1.EndUT{3}, closestDistance{4}, closestUT{5}", + vessel, target1, target1.StartUT, target1.EndUT, closestDistance, closestUT); + FindClosest(vessel, target1, Math.Max(now, target1.StartUT), target1.EndUT, 0, ref closestDistance, ref closestUT); + + target1 = target1.nextPatch; + } + + if (!Double.IsInfinity(then) && !Double.IsInfinity(now)) + { + Utility.LogInfo(this, "ApproachSolver fourth FindClosest: vessel {0}, target1 {1}, now {2}, then {3}, closestDistance{4}, closestUT{5}", + vessel, target1, now, then, closestDistance, closestUT); + FindClosest(vessel, target1, now, then, 0, ref closestDistance, ref closestUT); + } + + now = then; + then += vessel.period; + } + + resultsReady = true; + + if (closestUT > 0.0) + { + targetClosestUT = closestUT; + targetClosestDistance = closestDistance; + targetClosestSpeed = (vesselOrbit.GetFrameVelAtUT(closestUT) - targetOrbit.GetFrameVelAtUT(closestUT)).magnitude; + } + else + { + // Did not solve. Use "now" as closest approach. + targetClosestUT = vesselOrbit.StartUT; + targetClosestDistance = (vesselOrbit.getPositionAtUT(targetClosestUT) - targetOrbit.getPositionAtUT(targetClosestUT)).magnitude; + targetClosestSpeed = (vesselOrbit.GetFrameVelAtUT(vesselOrbit.StartUT) - targetOrbit.GetFrameVelAtUT(vesselOrbit.StartUT)).magnitude; + } +#endif + } + } +} diff --git a/ModifiedCode/MASComponentAudioPlayer.cs b/ModifiedCode/MASComponentAudioPlayer.cs new file mode 100644 index 00000000..587d5edb --- /dev/null +++ b/ModifiedCode/MASComponentAudioPlayer.cs @@ -0,0 +1,284 @@ +/***************************************************************************** + * The MIT License (MIT) + * + * Copyright (c) 2016-2018 MOARdV + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + ****************************************************************************/ +using System; +using System.Collections.Generic; +using System.Text; +using UnityEngine; + +namespace AvionicsSystems +{ + internal class MASComponentAudioPlayer : IMASSubComponent + { + private float pitch = 1.0f; + private float volume = 1.0f; + private Variable soundVariable; + private AudioSource audioSource; + private readonly bool mustPlayOnce = false; + private bool hasAudioClip = false; + private bool currentState = false; + private readonly PlaybackMode playbackTrigger = PlaybackMode.ON; + + private enum PlaybackMode + { + ON, + OFF, + BOTH, + LOOP + }; + + internal MASComponentAudioPlayer(ConfigNode config, InternalProp prop, MASFlightComputer comp) + : base(config, prop, comp) + { + string variableName = string.Empty; + string pitchVariableName = string.Empty; + string volumeVariableName = string.Empty; + if (!config.TryGetValue("volume", ref volumeVariableName)) + { + volumeVariableName = "1"; + } + + if (!config.TryGetValue("pitch", ref pitchVariableName)) + { + pitchVariableName = "1"; + } + + string sound = string.Empty; + string soundVariableName = string.Empty; + if (!config.TryGetValue("sound", ref sound) || string.IsNullOrEmpty(sound)) + { + if (!config.TryGetValue("variableSound", ref soundVariableName) || string.IsNullOrEmpty(soundVariableName)) + { + throw new ArgumentException("Missing or invalid parameters 'sound' and/or 'soundVariable' in AUDIO_PLAYER " + name); + } + } + + if (!config.TryGetValue("mustPlayOnce", ref mustPlayOnce)) + { + mustPlayOnce = false; + } + + //Try Load audio + AudioClip clip = null; + if (!string.IsNullOrEmpty(sound)) + { + clip = GameDatabase.Instance.GetAudioClip(sound); + if (clip == null) + { + throw new ArgumentException("Unable to load 'sound' " + sound + " in AUDIO_PLAYER " + name); + } + else + { + hasAudioClip = true; + } + } + + string playbackTrigger = string.Empty; + config.TryGetValue("trigger", ref playbackTrigger); + if (string.IsNullOrEmpty(playbackTrigger)) + { + throw new ArgumentException("Missing parameter 'trigger' in AUDIO_PLAYER " + name); + } + else + { + playbackTrigger = playbackTrigger.Trim(); + if (playbackTrigger == PlaybackMode.ON.ToString()) + { + this.playbackTrigger = PlaybackMode.ON; + } + else if (playbackTrigger == PlaybackMode.OFF.ToString()) + { + this.playbackTrigger = PlaybackMode.OFF; + } + else if (playbackTrigger == PlaybackMode.BOTH.ToString()) + { + this.playbackTrigger = PlaybackMode.BOTH; + } + else if (playbackTrigger == PlaybackMode.LOOP.ToString()) + { + if (mustPlayOnce) + { + throw new ArgumentException("Cannot use 'mustPlayOnce' with looping audio in AUDIO_PLAYER" + name); + } + this.playbackTrigger = PlaybackMode.LOOP; + } + else + { + throw new ArgumentException("Unrecognized parameter 'trigger = " + playbackTrigger + "' in AUDIO_PLAYER " + name); + } + } + + Transform audioTransform = new GameObject().transform; + audioTransform.gameObject.name = Utility.ComposeObjectName(this.GetType().Name, name, prop.propID); + audioTransform.gameObject.layer = prop.transform.gameObject.layer; + audioTransform.SetParent(prop.transform, false); + audioSource = audioTransform.gameObject.AddComponent(); + + audioSource.clip = clip; + audioSource.Stop(); + audioSource.volume = GameSettings.SHIP_VOLUME; + audioSource.rolloffMode = AudioRolloffMode.Logarithmic; + audioSource.maxDistance = 8.0f; + audioSource.minDistance = 2.0f; + audioSource.dopplerLevel = 0.0f; + audioSource.panStereo = 0.0f; + audioSource.playOnAwake = false; + audioSource.loop = (this.playbackTrigger == PlaybackMode.LOOP); + audioSource.pitch = 1.0f; + + if (!config.TryGetValue("variable", ref variableName) || string.IsNullOrEmpty(variableName)) + { + throw new ArgumentException("Invalid or missing 'variable' in ANIMATION_PLAYER " + name); + } + variableName = variableName.Trim(); + + audioSource.mute = ((CameraManager.Instance.currentCameraMode != CameraManager.CameraMode.IVA) && (CameraManager.Instance.currentCameraMode != CameraManager.CameraMode.Internal)); + + GameEvents.OnCameraChange.Add(OnCameraChange); + + variableRegistrar.RegisterVariableChangeCallback(pitchVariableName, (double newPitch) => + { + pitch = (float)newPitch; + audioSource.pitch = pitch; + }); + variableRegistrar.RegisterVariableChangeCallback(volumeVariableName, (double newVolume) => + { + volume = Mathf.Clamp01((float)newVolume); + audioSource.volume = GameSettings.SHIP_VOLUME * volume; + }); + variableRegistrar.RegisterVariableChangeCallback(variableName, VariableCallback); + if (!string.IsNullOrEmpty(soundVariableName)) + { + soundVariable = variableRegistrar.RegisterVariableChangeCallback(soundVariableName, SoundClipCallback, false); + // Initialize the audio. + SoundClipCallback(0.0); + } + } + + /// + /// Callback used when camera switches (so I can mute audio when not in craft). + /// + /// + private void OnCameraChange(CameraManager.CameraMode newCameraMode) + { + try + { + audioSource.mute = ((CameraManager.Instance.currentCameraMode != CameraManager.CameraMode.IVA) && (CameraManager.Instance.currentCameraMode != CameraManager.CameraMode.Internal)); + } + catch (Exception e) + { + throw new ArgumentException("Error in AudioPlayer OnCameraChange: \"" + e.Source + e.TargetSite + e.Data + e.StackTrace + CameraManager.Instance.currentCameraMode + "\"", e); + } + } + + /// + /// Callback that allows changing the audio clip attached to this player. + /// + private void SoundClipCallback(double dontCare) + { + audioSource.Stop(); + + AudioClip clip = GameDatabase.Instance.GetAudioClip(soundVariable.AsString()); + if (clip == null) + { + Utility.LogError(this, "Unable to load audio clip '{0}'.", soundVariable.AsString()); + hasAudioClip = false; + } + else + { + audioSource.clip = clip; + hasAudioClip = true; + PlayAudio(); + } + } + + /// + /// Update the audio play state. + /// + private void PlayAudio() + { + if (currentState) + { + if (playbackTrigger != PlaybackMode.OFF) + { + if (hasAudioClip) + { + audioSource.Play(); + } + } + else + { + if (!mustPlayOnce) + { + audioSource.Stop(); + } + } + } + else if (playbackTrigger == PlaybackMode.ON || playbackTrigger == PlaybackMode.LOOP) + { + if (!mustPlayOnce) + { + audioSource.Stop(); + } + } + else if (hasAudioClip) + { + audioSource.Play(); + } + } + + /// + /// Variable callback used to update the audio source when it is playing. + /// + /// + private void VariableCallback(double newValue) + { + bool newState = (newValue > 0.0); + + if (newState != currentState) + { + currentState = newState; + if (hasAudioClip == true) + { + // No audio clip: return early. + PlayAudio(); + } + } + } + + /// + /// Release resources + /// + public override void ReleaseResources(MASFlightComputer comp, InternalProp prop) + { + GameEvents.OnCameraChange.Remove(OnCameraChange); + + variableRegistrar.ReleaseResources(); + + audioSource.Stop(); + audioSource.clip = null; + audioSource = null; + } + } +} diff --git a/ModifiedCode/MASFlightComputer.cs b/ModifiedCode/MASFlightComputer.cs new file mode 100644 index 00000000..e1b1c972 --- /dev/null +++ b/ModifiedCode/MASFlightComputer.cs @@ -0,0 +1,1986 @@ +//#define MEASURE_FC_FIXEDUPDATE +//#define LIST_LUA_VARIABLES +/***************************************************************************** + * The MIT License (MIT) + * + * Copyright (c) 2016-2020 MOARdV + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + ****************************************************************************/ +using MoonSharp.Interpreter; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; +using System.Reflection; +using System.Text; +using UnityEngine; + +namespace AvionicsSystems +{ + /// + /// The MASFlightComputer manages the components attached to props inside + /// the part this computer is attached to. It is a central clearing-house + /// for the data requsted by the props, and it manages data that persists + /// across game sessions. + /// + public partial class MASFlightComputer : PartModule + { + /// + /// ID that is stored automatically on-save by KSP. This value is the + /// string version of fcId, so on load we can restore the Guid. + /// + [KSPField(isPersistant = true)] + public string flightComputerId = string.Empty; + + /// + /// Ship's description string copied from the editor. At flight time, we + /// will parse this to formulate the action group fields (equivalent to + /// AGMEMO in RPM). + /// + [KSPField(isPersistant = true)] + public string shipDescription = string.Empty; + internal string[] agMemoOff = { "AG0", "AG1", "AG2", "AG3", "AG4", "AG5", "AG6", "AG7", "AG8", "AG9" }; + internal string[] agMemoOn = { "AG0", "AG1", "AG2", "AG3", "AG4", "AG5", "AG6", "AG7", "AG8", "AG9" }; + internal string vesselDescription = string.Empty; + + /// + /// The maximum g-loading the command pod can sustain without disrupting + /// power. + /// + [KSPField] + public float gLimit = float.MaxValue; + + /// + /// The chance that power is disrupted at (gLimit + 1) Gs. This number + /// scales with G forces. A 1 represents 100% chance of a power disruption. + /// + [KSPField] + public float baseDisruptionChance = 0.0f; + internal float disruptionChance = 0.0f; + + /// + /// Does the command pod's instruments require power to function? + /// + [KSPField] + public bool requiresPower = false; + internal bool isPowered = true; + + /// + /// How much power does the MASFlightComputer draw on its own? Ignored if `requiresPower` is false. + /// + [KSPField] + public float rate = 0.0f; + + [KSPField] + public string powerOnVariable = string.Empty; + internal bool powerOnValid = true; + + [KSPField] + public float maxRot = 80.0f; + + [KSPField] + public float minPitch = -80.0f; + + [KSPField] + public float maxPitch = 45.0f; + + [KSPField] + public string startupScript = string.Empty; + + [KSPField] + public string onEnterIVA = string.Empty; + private DynValue enterIvaScript = null; + + [KSPField] + public string onExitIVA = string.Empty; + private DynValue exitIvaScript = null; + + /// + /// Our module ID (so each FC can be distinguished in a save file). + /// + private Guid fcId = Guid.Empty; + + /// + /// ID of our parent vessel (for some sanity checking) + /// + private Guid parentVesselId = Guid.Empty; + + /// + /// This flight computer's Lua context + /// + private Script script; + + /// + /// Instance of the flight computer proxy used in the Lua context. + /// + private MASFlightComputerProxy fcProxy; + + /// + /// Instance of the Chatterer proxy class. + /// + private MASIChatterer chattererProxy; + + /// + /// Instance of the aircraft engines proxy class. + /// + private MASIEngine engineProxy; + + /// + /// Instance of the FAR proxy class. + /// + private MASIFAR farProxy; + + /// + /// Instance of the Kerbal Alarm Clock proxy class. + /// + private MASIKAC kacProxy; + + /// + /// Instance of the Kerbal Engineer proxy class; + /// + private MASIKerbalEngineer keProxy; + + /// + /// Instance of the MechJeb proxy class. + /// + private MASIMechJeb mjProxy; + + /// + /// Instance of the Navigation proxy class. + /// + private MASINavigation navProxy; + + /// + /// Instance of the parachute proxy class. + /// + private MASIParachute parachuteProxy; + + /// + /// Instance of the Transfer proxy class. + /// + private MASITransfer transferProxy; + + /// + /// Instance of the VTOL manager proxy class. + /// + private MASIVTOL vtolProxy; + + /// + /// Have we initialized? + /// + internal bool initialized { get; private set; } + + /// + /// Has there been an addition or subtraction to the mutable variables list? + /// + private bool mutableVariablesChanged = false; + + /// + /// Dictionary of known RasterPropComputer-compatible named colors. + /// + private Dictionary namedColors = new Dictionary(); + + /// + /// Dictionary of loaded Action Lua wrappers. + /// + private Dictionary actions = new Dictionary(); + + /// + /// Dictionary of named monitors, for routing softkeys. + /// + private Dictionary monitors = new Dictionary(); + + /// + /// Set to true when any of the throttle keys are pressed. + /// + internal bool anyThrottleKeysPressed; + + /// + /// Custom MAS Action Group dictionary. + /// + internal Dictionary masActionGroup = new Dictionary(); + + /// + /// Reference to the current vessel computer. + /// + internal MASVesselComputer vc; + + /// + /// Reference to the current vessel autopilot. + /// + internal MASAutoPilot ap; + + /// + /// Additional EC required by subcomponents via `fc.IncreasePowerDraw(rate)`. Ignored if requiresPower is false. + /// + private float additionalEC = 0.0f; + + /// + /// Resource ID of the electric charge. + /// + private int resourceId = -1; + + /// + /// Reference to the current IVA Kerbal. + /// + internal Kerbal currentKerbal; + + /// + /// Is the current Kerbal suffering the effects of GLOC? + /// + internal bool currentKerbalBlackedOut; + + /// + /// Direct index to the vessel resource list. + /// + private int electricChargeIndex = -1; + + /// + /// The ModuleCommand on this IVA. + /// + private ModuleCommand commandModule = null; + private int activeControlPoint = 0; + + internal ModuleColorChanger colorChangerModule = null; + + internal static readonly string vesselIdLabel = "__vesselId"; + internal static readonly string vesselFilterLabel = "__vesselFilter"; + + internal int vesselFilterValue = 0; + internal List activeVesselFilter = new List(); + private readonly Dictionary vesselBitRemap = new Dictionary + { + { 1, VesselType.Ship}, + { 2, VesselType.Plane}, + { 3, VesselType.Probe}, + { 4, VesselType.Lander}, + { 5, VesselType.Station}, + { 6, VesselType.Relay}, + { 7, VesselType.Rover}, + { 8, VesselType.Base}, + { 9, VesselType.EVA}, + { 10, VesselType.Flag}, + { 11, VesselType.Debris}, + { 12, VesselType.SpaceObject}, + { 13, VesselType.Unknown} + }; + private readonly Dictionary bitVesselRemap = new Dictionary + { + { VesselType.Ship, 1}, + { VesselType.Plane, 2}, + { VesselType.Probe, 3}, + { VesselType.Lander, 4}, + { VesselType.Station, 5}, + { VesselType.Relay, 6}, + { VesselType.Rover, 7}, + { VesselType.Base, 8}, + { VesselType.EVA, 9}, + { VesselType.Flag, 10}, + { VesselType.Debris, 11}, + { VesselType.SpaceObject, 12}, + { VesselType.Unknown, 13} + }; + + internal ProtoCrewMember[] localCrew = new ProtoCrewMember[0]; + private kerbalExpressionSystem[] localCrewMedical = new kerbalExpressionSystem[0]; + + + private Stopwatch nativeStopwatch = new Stopwatch(); + private Stopwatch luaStopwatch = new Stopwatch(); + private Stopwatch dependentStopwatch = new Stopwatch(); + long samplecount = 0; + long nativeEvaluationCount = 0; + long luaEvaluationCount = 0; + long dependentEvaluationCount = 0; + + private static bool dpaiChecked = false; + private static Func GetDpaiName = null; + + #region Internal Interface + /// + /// Return the MASFlightComputer attached to the given part, or null if + /// none is found. + /// + /// The part where the flight computer should live + /// MASFlightComputer or null + static internal MASFlightComputer Instance(Part part) + { + for (int i = part.Modules.Count - 1; i >= 0; --i) + { + if (part.Modules[i].ClassName == typeof(MASFlightComputer).Name) + { + return part.Modules[i] as MASFlightComputer; + } + } + + return null; + } + + /// + /// Convert a name to a + /// + /// + /// Prop that is associated with the variable request; + /// null indicates "Do not condition this variable" + /// + private string ConditionVariableName(string initialName, InternalProp prop) + { + initialName = initialName.Trim(); + double numeric; + if (double.TryParse(initialName, out numeric)) + { + // If the variable is a numeric constant, we want to + // canonicalize it so we don't create multiple variables that + // all have the same value, eg "0" and "0.0" and "0.00" + initialName = string.Format("{0:R}", numeric); + } + + if (prop == null) + { + return initialName; + } + else + { + if (initialName.Contains("%AUTOID%")) + { + string replacementString = string.Format("PROP-{0}-{1}", prop.propName, prop.propID); + initialName = initialName.Replace("%AUTOID%", replacementString); + } + if (initialName.Contains("%PROPID%")) + { + initialName = initialName.Replace("%PROPID%", prop.propID.ToString()); + } + if (initialName.Contains("%PROPCOUNT%")) + { + if (prop.internalModel != null) + { + initialName = initialName.Replace("%PROPCOUNT%", prop.internalModel.props.Count.ToString()); + } + else + { + initialName = initialName.Replace("%PROPCOUNT%", "1"); + } + } + return initialName; + } + } + + /// + /// Register to receive on-changed callback notification for + /// a variable. + /// + /// Un-massaged name of the variable. + /// The prop associated with this variable. + /// The callback to invoke when this variable changes. + /// Should the callback be invoked immediately? + /// The variable created, or null. + internal Variable RegisterVariableChangeCallback(string variableName, InternalProp prop, Action callback, bool initializeNow = true) + { + Variable v = null; + try + { + v = GetVariable(variableName, prop); + + v.RegisterNumericCallback(callback); + + if (initializeNow) + { + callback(v.AsDouble()); + } + } + catch (Exception e) + { + throw new ArgumentException("Error parsing variable \"" + variableName + "\"", e); + } + + return v; + } + + /// + /// Register a monitor so it will receive softkey events. If the monitor's name + /// already exists, this is a no-op. + /// + /// Name of the monitor + /// + internal void RegisterMonitor(string monitorName, InternalProp prop, MASMonitor monitor) + { + monitorName = ConditionVariableName(monitorName, prop); + + if (!monitors.ContainsKey(monitorName)) + { + monitors[monitorName] = monitor; + } + } + + /// + /// Unregister a monitor so it will no longer receive softkey events. + /// + /// + /// + internal void UnregisterMonitor(string monitorName, InternalProp prop, MASMonitor monitor) + { + monitorName = ConditionVariableName(monitorName, prop); + + if (monitors.ContainsKey(monitorName)) + { + monitors.Remove(monitorName); + } + } + + /// + /// Handle a softkey event by forwarding it to the named monitor (if it exists). + /// + /// + /// + /// + internal bool HandleSoftkey(string monitorName, int key) + { + MASMonitor monitor; + if (monitors.TryGetValue(monitorName, out monitor)) + { + return monitor.HandleSoftkey(key); + } + + return false; + } + + /// + /// Function to handle a touchscreen (COLLIDER_ADVANCED) event. + /// + /// The monitor that will receive the event. + /// The x and y coordinates of the event, as processed by the COLLIDER_ADVANCED + internal void HandleTouchEvent(string monitorName, Vector2 hitCoordinate, EventType eventType) + { + MASMonitor monitor; + if (monitors.TryGetValue(monitorName, out monitor)) + { + monitor.HandleTouchEvent(hitCoordinate, eventType); + } + } + + /// + /// Convert an RPM-compatible named color to a Color32. Returns true + /// if successful. + /// + /// Color to convert + /// True if the namedColor was a COLOR_ field, false otherwise. + internal bool TryGetNamedColor(string namedColor, out Color32 color) + { + namedColor = namedColor.Trim(); + if (namedColor.StartsWith("#")) + { + return Utility.ParseHexColor(namedColor, out color); + } + else if (namedColor.StartsWith("COLOR_")) + { + if (namedColors.TryGetValue(namedColor, out color)) + { + return true; + } + else if (MASLoader.namedColors.TryGetValue(namedColor, out color)) + { + namedColors.Add(namedColor, color); + return true; + } + } + + color = Color.black; + return false; + } + + /// + /// Converts an RPM-compatible named color (COLOR_*) to a Color32. If + /// a part-local override exists, it will be chosen; otherwise, a check + /// of the global table is made. If the named color is not found, an + /// exception is thrown. + /// + /// + /// + internal Color32 GetNamedColor(string namedColor) + { + Color32 colorValue; + if (namedColors.TryGetValue(namedColor, out colorValue)) + { + return colorValue; + } + else + { + if (MASLoader.namedColors.TryGetValue(namedColor, out colorValue)) + { + namedColors.Add(namedColor, colorValue); + return colorValue; + } + } + + Utility.ComplainLoudly("GetNamedColor with unknown named color"); + throw new ArgumentException("[MASFlightComputer] Unknown named color '" + namedColor + "'."); + } + + /// + /// Returns or generates an Action that encapsulates the supplied actionName, + /// which is simply the Lua function(s) that are to be executed. + /// + /// The action (Lua code snippet) to execute. + /// The Action. + internal Action GetAction(string actionName, InternalProp prop) + { + actionName = ConditionVariableName(actionName, prop); + + Action action; + if (actions.TryGetValue(actionName, out action)) + { + return action; + } + else + { + try + { + DynValue dv = script.LoadString(actionName); + + action = () => + { + try + { + script.Call(dv); + } + catch (Exception e) + { + Utility.ComplainLoudly("Action " + actionName + " triggered an exception"); + Utility.LogError(this, "Action {0} triggered exception:", actionName); + Utility.LogError(this, e.ToString()); + } + }; + + //Utility.LogMessage(this, "Adding new Action '{0}'", actionName); + actions.Add(actionName, action); + return action; + } + catch + { + return null; + } + } + } + + /// + /// Creates a Lua function that uses 'actionName' as its body, then generates an Action that takes + /// a double for its parameter that is used to call the newly-created function. + /// + /// + /// + /// + /// + internal Action GetDragAction(string actionName, string componentName, InternalProp prop) + { + string preppedActionName = actionName.Replace("%DRAG%", "dragDelta"); + string propName = ConditionVariableName(string.Format("%AUTOID%_{0}", componentName), prop); + propName = propName.Replace('-', '_').Replace(' ', '_'); + + StringBuilder sb = StringBuilderCache.Acquire(); + sb.AppendFormat("function {0}_drag(dragDelta)", propName).AppendLine().AppendFormat(" {0}", preppedActionName).AppendLine().AppendLine("end"); + preppedActionName = ConditionVariableName(sb.ToStringAndRelease(), prop); + + // Compile the script. + script.DoString(preppedActionName); + // Get the function. + DynValue closure = script.Globals.Get(string.Format("{0}_drag", propName)); + if (closure.Type == DataType.Function) + { + return (double newValue) => + { + DynValue parm = DynValue.NewNumber(newValue); + DynValue result = script.Call(closure, parm); + }; + } + else + { + Utility.LogError(this, "Failed to compile \"{0}\" into a Lua function", actionName); + return null; + } + } + + /// + /// Returns an action that the COLLIDER_ADVANCED object can use to pass click information to a monitor. + /// + /// ID of the monitor. + /// + /// + internal Action GetHitAction(string monitorID, InternalProp prop, Action hitAction) + { + string conditionedId = ConditionVariableName(monitorID, prop); + + Action ev = (xy, eventType) => hitAction(conditionedId, xy, eventType); + return ev; + } + + internal Action GetColliderAction(string actionName, int hitboxID, string actionType, InternalProp prop) + { + actionName = ConditionVariableName(actionName, prop); + + string propName = ConditionVariableName(string.Format("%AUTOID%_{0}", hitboxID), prop); + propName = propName.Replace('-', '_').Replace(' ', '_'); + + Action act = null; + if (actionName.Contains("%X%") || actionName.Contains("%Y%")) + { + // Case where the location within the hitbox matters. + + actionName = actionName.Replace("%X%", "x").Replace("%Y%", "y"); + StringBuilder sb = StringBuilderCache.Acquire(); + sb.AppendFormat("function {0}_{1}(x, y)", propName, actionType).AppendLine().AppendFormat(" {0}", actionName).AppendLine().AppendLine("end"); + string preppedActionName = ConditionVariableName(sb.ToStringAndRelease(), prop); + + // Compile the script. + script.DoString(preppedActionName); + // Get the function. + DynValue closure = script.Globals.Get(string.Format("{0}_{1}", propName, actionType)); + if (closure.Type == DataType.Function) + { + return (loc) => + { + DynValue xIn = DynValue.NewNumber(loc.x); + DynValue yIn = DynValue.NewNumber(loc.y); + try + { + script.Call(closure, xIn, yIn); + } + catch (Exception e) + { + Utility.ComplainLoudly("Action " + actionName + " triggered an exception"); + Utility.LogError(this, "Action {0} triggered exception:", actionName); + Utility.LogError(this, e.ToString()); + } + }; + } + else + { + Utility.LogError(this, "Failed to compile \"{0}\" into a Lua function", actionName); + return null; + } + } + else + { + // Simple case - doesn't use X or Y parameter. + try + { + DynValue dv = script.LoadString(actionName); + + act = (loc) => + { + try + { + script.Call(dv); + } + catch (Exception e) + { + Utility.ComplainLoudly("Action " + actionName + " triggered an exception"); + Utility.LogError(this, "Action {0} triggered exception:", actionName); + Utility.LogError(this, e.ToString()); + } + }; + } + catch + { + return null; + } + } + + return act; + } + + /// + /// Write a Lua script to transform x, y, z normalized Collider coordinates into an x, y value + /// that is sent to the monitor. + /// + /// + /// + /// + /// + internal Func GetColliderTransformation(string xTransformation, string yTransformation, string componentName, InternalProp prop) + { + string propName = ConditionVariableName(string.Format("%AUTOID%_{0}", componentName), prop); + propName = propName.Replace('-', '_').Replace(' ', '_'); + + string conditionedX = ConditionVariableName(xTransformation, prop).Replace("%X%", "x").Replace("%Y%", "y").Replace("%Z%", "z"); + string conditionedY = ConditionVariableName(yTransformation, prop).Replace("%X%", "x").Replace("%Y%", "y").Replace("%Z%", "z"); + StringBuilder sb = StringBuilderCache.Acquire(); + + sb.AppendFormat("function {0}_transform(x, y, z)", propName).AppendLine().AppendFormat(" local x1 = {0}", conditionedX).AppendFormat(" local y1 = {0}", conditionedY).AppendLine().AppendLine(" return x1, y1").AppendLine("end"); + + string preppedFunction = sb.ToStringAndRelease(); + script.DoString(preppedFunction); + + // Get the function. + DynValue closure = script.Globals.Get(string.Format("{0}_transform", propName)); + + Func f = (x, y, z) => + { + DynValue xIn = DynValue.NewNumber(x); + DynValue yIn = DynValue.NewNumber(y); + DynValue zIn = DynValue.NewNumber(z); + DynValue result = script.Call(closure, xIn, yIn, zIn); + if (result.Type == DataType.Tuple) + { + DynValue[] multiret = result.Tuple; + double x1 = multiret[0].Number; + double y1 = multiret[1].Number; + + return new Vector2((float)x1, (float)y1); + } + else + { + Utility.LogError(this, "Called script - result = {0}, not DataType.Tuple", result.Type); + + return new Vector2(x, y); + } + }; + return f; + } + + /// + /// Fetch the kerbalExpressionSystem for a local crew seat. + /// + /// + /// + internal kerbalExpressionSystem GetLocalKES(int crewSeat) + { + if (crewSeat >= 0 && crewSeat < localCrew.Length && localCrew[crewSeat] != null) + { + localCrew[crewSeat].KerbalRef.GetComponentCached(ref localCrewMedical[crewSeat]); + + return localCrewMedical[crewSeat]; + } + + return null; + } + + internal string GetDockingPortName(Part dockingNodePart) + { + if (GetDpaiName == null) + { + return dockingNodePart.partInfo.title; + } + else + { + PartModule namedDockModule = dockingNodePart.Modules["ModuleDockingNodeNamed"]; + + return (namedDockModule != null) ? GetDpaiName(namedDockModule) : dockingNodePart.partInfo.title; + } + } + + internal float GetPowerDraw() + { + return (requiresPower) ? (additionalEC + rate) : 0.0f; + } + + internal float ChangePowerDraw(float amount) + { + if (requiresPower) + { + additionalEC = Mathf.Max(0.0f, amount + additionalEC); + return additionalEC + rate; + } + else + { + return 0.0f; + } + } + + internal int GetCurrentControlPoint() + { + return activeControlPoint; + } + + internal string GetControlPointName(int controlPoint) + { + if (controlPoint == -1) + { + controlPoint = activeControlPoint; + } + + if (commandModule != null && controlPoint >= 0 && controlPoint < commandModule.controlPoints.Count) + { + return commandModule.controlPoints.At(controlPoint).displayName; + } + return string.Empty; + } + + internal int GetNumControlPoints() + { + if (commandModule != null) + { + return commandModule.controlPoints.Count; + } + return 0; + } + + internal float SetCurrentControlPoint(int newControlPoint) + { + if (commandModule != null && newControlPoint >= 0 && newControlPoint < commandModule.controlPoints.Count) + { + commandModule.SetControlPoint(commandModule.controlPoints.At(newControlPoint).name); + return 1.0f; + } + return 0.0f; + } + #endregion + + #region Target Tracking + internal bool ClearTargetFilter(int bitIndex) + { + int bit = 1 << bitIndex; + int oldValue = vesselFilterValue; + vesselFilterValue &= ~bit; + + if (oldValue != vesselFilterValue) + { + activeVesselFilter.Remove(vesselBitRemap[bitIndex]); + SetPersistent(vesselFilterLabel, vesselFilterValue.ToString("X")); + return true; + } + else + { + return false; + } + } + + internal bool GetTargetFilter(int bitIndex) + { + int bit = 1 << bitIndex; + int andResult = bit & vesselFilterValue; + + return (andResult != 0); + } + + internal bool SetTargetFilter(int bitIndex) + { + int bit = 1 << bitIndex; + int oldValue = vesselFilterValue; + vesselFilterValue |= bit; + + if (oldValue != vesselFilterValue) + { + activeVesselFilter.Add(vesselBitRemap[bitIndex]); + SetPersistent(vesselFilterLabel, vesselFilterValue.ToString("X")); + return true; + } + else + { + return false; + } + } + + internal void ToggleTargetFilter(int bitIndex) + { + int bit = 1 << bitIndex; + if ((vesselFilterValue & bit) != 0) + { + activeVesselFilter.Remove(vesselBitRemap[bitIndex]); + } + else + { + activeVesselFilter.Add(vesselBitRemap[bitIndex]); + } + vesselFilterValue ^= bit; + SetPersistent(vesselFilterLabel, vesselFilterValue.ToString("X")); + } + #endregion + + #region Monobehaviour +#if MEASURE_FC_FIXEDUPDATE + Stopwatch fixedupdateTimer = new Stopwatch(); +#endif + /// + /// Process updates to tracked variables. + /// + public void FixedUpdate() + { + try + { + if (initialized && vc.vesselCrewed && vc.vesselActive) + { +#if MEASURE_FC_FIXEDUPDATE + fixedupdateTimer.Reset(); + fixedupdateTimer.Start(); +#endif + // Realistically, this block of code won't be triggered very + // often. Once per scene change per pod. + if (mutableVariablesChanged) + { + nativeVariables = new Variable[nativeVariableCount]; + luaVariables = new Variable[luaVariableCount]; + dependentVariables = new Variable[dependentVariableCount]; + int nativeIdx = 0, luaIdx = 0, dependentIdx = 0; + foreach (Variable var in mutableVariablesList) + { + if (var.variableType == Variable.VariableType.LuaScript) + { + luaVariables[luaIdx] = var; + ++luaIdx; + } + else if (var.variableType == Variable.VariableType.Func) + { + nativeVariables[nativeIdx] = var; + ++nativeIdx; + } + else if (var.variableType == Variable.VariableType.Dependent) + { + dependentVariables[dependentIdx] = var; + ++dependentIdx; + } + else + { + throw new ArgumentException(string.Format("Unexpected variable type {0} for variable {1} in mutableVariablesList", var.variableType, var.name)); + } + } + Utility.LogMessage(this, "Resizing variables lists to N:{0} L:{1} D:{2}", nativeVariableCount, luaVariableCount, dependentVariableCount); + mutableVariablesChanged = false; + } + + fcProxy.Update(); + farProxy.Update(); + kacProxy.Update(); + keProxy.Update(); + mjProxy.Update(); + navProxy.Update(); + parachuteProxy.Update(); + transferProxy.Update(); + UpdateRadios(); +#if MEASURE_FC_FIXEDUPDATE + TimeSpan updatesTime = fixedupdateTimer.Elapsed; +#endif + + anyThrottleKeysPressed = GameSettings.THROTTLE_CUTOFF.GetKey() || GameSettings.THROTTLE_FULL.GetKey() || GameSettings.THROTTLE_UP.GetKey() || GameSettings.THROTTLE_DOWN.GetKey(); + + // Precompute the disruption chances. + if (electricChargeIndex == -1) + { + // We have to poll it here because the value may not be initialized + // when we're in Start(). + electricChargeIndex = vc.GetResourceIndex(MASConfig.ElectricCharge); + try + { + PartResourceDefinition def = PartResourceLibrary.Instance.resourceDefinitions[MASConfig.ElectricCharge]; + resourceId = def.id; + } + catch + { + Utility.LogError(this, "Unable to resolve resource '{0}', flight computer can not require power.", MASConfig.ElectricCharge); + requiresPower = false; + } + } + + isPowered = (!requiresPower || vc.ResourceCurrentDirect(electricChargeIndex) > 0.0001) && powerOnValid; + + if (requiresPower && (rate + additionalEC) > 0.0f) + { + double requested = (rate + additionalEC) * TimeWarp.fixedDeltaTime; + double supplied = part.RequestResource(resourceId, requested); + if (supplied < requested * 0.5) + { + isPowered = false; + } + } + + currentKerbalBlackedOut = false; + currentKerbal = FindCurrentKerbal(); + if (currentKerbal != null && currentKerbal.protoCrewMember.outDueToG) + { + // Always black-out instruments from blackouts. + disruptionChance = 1.1f; + currentKerbalBlackedOut = true; + } + else if (vessel.geeForce_immediate > gLimit) + { + disruptionChance = baseDisruptionChance * Mathf.Sqrt((float)vessel.geeForce_immediate - gLimit); + } + else + { + disruptionChance = 0.0f; + } + + // TODO: Add a heuristic to adjust the loop so not all variables + // update every fixed update if the average update time is too high. + // Need to decide if it's going to be an absolute time (max # ms/update) + // or a relative time. + // NOTE: 128 Lua "variables" average about 1.7ms/update! And there's + // a LOT of garbage collection going on. + // 229 variables -> 2.6-2.7ms/update, so 70-80 variables per ms on + // a reasonable mid-upper range CPU (3.6GHz). + nativeStopwatch.Start(); + int count = nativeVariables.Length; + for (int i = 0; i < count; ++i) + { + try + { + nativeVariables[i].Evaluate(true); + } + catch (Exception e) + { + Utility.LogError(this, "FixedUpdate exception on variable {0}:", nativeVariables[i].name); + Utility.LogError(this, e.ToString()); + //throw e; + } + } + nativeEvaluationCount += count; + nativeStopwatch.Stop(); +#if MEASURE_FC_FIXEDUPDATE + TimeSpan nativeTime = fixedupdateTimer.Elapsed; +#endif + + luaStopwatch.Start(); + // Update some Lua variables - user configurable, so lower- + // spec machines aren't as badly affected. + int startLuaIdx, endLuaIdx; + if (MASConfig.LuaUpdatePriority == 1) + { + startLuaIdx = 0; + endLuaIdx = luaVariables.Length; + } + else + { + long modulo = samplecount % MASConfig.LuaUpdatePriority; + int span = luaVariables.Length / MASConfig.LuaUpdatePriority; + startLuaIdx = (int)modulo * span; + + if (modulo == MASConfig.LuaUpdatePriority - 1) + { + endLuaIdx = luaVariables.Length; + } + else + { + endLuaIdx = startLuaIdx + span; + } + } + count = endLuaIdx - startLuaIdx; + for (int i = startLuaIdx; i < endLuaIdx; ++i) + { + try + { + luaVariables[i].Evaluate(true); + } + catch (Exception e) + { + Utility.LogError(this, "FixedUpdate exception on variable {0}", luaVariables[i].name); + luaStopwatch.Stop(); + throw e; + } + } + luaEvaluationCount += count; + luaStopwatch.Stop(); +#if MEASURE_FC_FIXEDUPDATE + TimeSpan luaTime = fixedupdateTimer.Elapsed; +#endif + + dependentStopwatch.Start(); + count = dependentVariables.Length; + for (int i = 0; i < count; ++i) + { + try + { + dependentVariables[i].Evaluate(true); + } + catch (Exception e) + { + Utility.LogError(this, "FixedUpdate exception on variable {0}:", dependentVariables[i].name); + Utility.LogError(this, e.ToString()); + //throw e; + } + } + dependentEvaluationCount += count; + dependentStopwatch.Stop(); + ++samplecount; +#if MEASURE_FC_FIXEDUPDATE + TimeSpan finalTime = fixedupdateTimer.Elapsed; + if (finalTime.Ticks > (TimeSpan.TicksPerMillisecond / 2)) + { + double ticksPerMillisecond = (double)TimeSpan.TicksPerMillisecond; + Utility.LogMessage(this, "FixedUpdate proxiestes {0,7:0.000} ms", + ((double)updatesTime.Ticks) / ticksPerMillisecond); + Utility.LogMessage(this, "FixedUpdate native var {0,7:0.000} ms", + ((double)(nativeTime.Ticks - updatesTime.Ticks)) / ticksPerMillisecond); + Utility.LogMessage(this, "FixedUpdate lua var {0,7:0.000} ms", + ((double)(luaTime.Ticks - nativeTime.Ticks)) / ticksPerMillisecond); + Utility.LogMessage(this, "FixedUpdate dep var {0,7:0.000} ms", + ((double)(finalTime.Ticks - luaTime.Ticks)) / ticksPerMillisecond); + Utility.LogMessage(this, "FixedUpdate net {0,7:0.000} ms", + ((double)finalTime.Ticks) / ticksPerMillisecond); + } +#endif + } + else if (HighLogic.LoadedSceneIsEditor && EditorLogic.fetch.shipDescriptionField != null) + { + string newDescr = EditorLogic.fetch.shipDescriptionField.text.Replace(Utility.EditorNewLine, "$$$"); + if (newDescr != shipDescription) + { + shipDescription = newDescr; + } + } + } + catch (Exception e) + { + Utility.LogError(this, "MASFlightComputer.FixedUpdate exception: {0}", e); + } + } + + /// + /// Shut down, unregister, etc. + /// + public void OnDestroy() + { + script = null; + fcProxy = null; + chattererProxy = null; + engineProxy = null; + mjProxy = null; + navProxy = null; + farProxy = null; + kacProxy = null; + keProxy = null; + parachuteProxy = null; + transferProxy = null; + vtolProxy = null; + if (initialized) + { + Utility.LogMessage(this, "OnDestroy for {0}", flightComputerId); + StopAllCoroutines(); + GameEvents.onVesselWasModified.Remove(onVesselChanged); + GameEvents.onVesselChange.Remove(onVesselChanged); + GameEvents.onVesselCrewWasModified.Remove(onVesselChanged); + GameEvents.OnCameraChange.Remove(onCameraChange); + GameEvents.OnIVACameraKerbalChange.Remove(OnIVACameraKerbalChange); + GameEvents.OnControlPointChanged.Remove(OnControlPointChanged); + + Utility.LogInfo(this, "{3} variables created: {0} constant variables, {1} delegate variables, {2} Lua variables, and {4} expression variables", + constantVariableCount, nativeVariableCount, luaVariableCount, variables.Count, dependentVariableCount); + if (samplecount > 0) + { + double msPerFixedUpdate = 1000.0 * (double)(nativeStopwatch.ElapsedTicks) / (double)(samplecount * Stopwatch.Frequency); + double samplesPerMs = (double)nativeEvaluationCount / (1000.0 * (double)(nativeStopwatch.ElapsedTicks) / (double)(Stopwatch.Frequency)); + Utility.LogInfo(this, "FixedUpdate Delegate average = {0:0.00}ms/FixedUpdate or {1:0.0} variables/ms", msPerFixedUpdate, samplesPerMs); + + msPerFixedUpdate = 1000.0 * (double)(luaStopwatch.ElapsedTicks) / (double)(samplecount * Stopwatch.Frequency); + samplesPerMs = (double)luaEvaluationCount / (1000.0 * (double)(luaStopwatch.ElapsedTicks) / (double)(Stopwatch.Frequency)); + Utility.LogInfo(this, "FixedUpdate Lua average = {0:0.00}ms/FixedUpdate or {1:0.0} variables/ms", msPerFixedUpdate, samplesPerMs); + + msPerFixedUpdate = 1000.0 * (double)(dependentStopwatch.ElapsedTicks) / (double)(samplecount * Stopwatch.Frequency); + samplesPerMs = (double)dependentEvaluationCount / (1000.0 * (double)(dependentStopwatch.ElapsedTicks) / (double)(Stopwatch.Frequency)); + Utility.LogInfo(this, "FixedUpdate Expr average = {0:0.00}ms/FixedUpdate or {1:0.0} variables/ms", msPerFixedUpdate, samplesPerMs); + } +#if LIST_LUA_VARIABLES + // Lua variables are costly to evaluate - ideally, we minimize the number created for use as variables. + Utility.LogMessage(this, "{0} Lua variables were created:", luaVariables.Length); + for(int i=0; i + /// Start up this module. + /// + public void Start() + { + if (HighLogic.LoadedSceneIsFlight) + { + additionalEC = 0.0f; + rate = Mathf.Max(0.0f, rate); + commandModule = part.FindModuleImplementing(); + UpdateControlPoint(commandModule.ActiveControlPointName); + colorChangerModule = part.FindModuleImplementing(); + if (colorChangerModule != null && colorChangerModule.toggleInFlight == false && colorChangerModule.toggleAction == false) + { + // Not a controllable light + colorChangerModule = null; + } + + if (dependentLuaMethods.Count == 0) + { + // Maybe a bit kludgy -- I want to list the Lua table methods that I know + // are dependent variables, so they can be treated as such instead of polled + // every FixedUpdate. But I want a fast search, so I use a List, sort it, + // and BSearch it. + dependentLuaMethods.Add("math.abs"); + dependentLuaMethods.Add("math.acos"); + dependentLuaMethods.Add("math.asin"); + dependentLuaMethods.Add("math.atan"); + dependentLuaMethods.Add("math.atan2"); + dependentLuaMethods.Add("math.ceil"); + dependentLuaMethods.Add("math.cos"); + dependentLuaMethods.Add("math.cosh"); + dependentLuaMethods.Add("math.deg"); + dependentLuaMethods.Add("math.exp"); + dependentLuaMethods.Add("math.floor"); + dependentLuaMethods.Add("math.fmod"); + dependentLuaMethods.Add("math.frexp"); + dependentLuaMethods.Add("math.ldexp"); + dependentLuaMethods.Add("math.log"); + dependentLuaMethods.Add("math.log10"); + dependentLuaMethods.Add("math.max"); + dependentLuaMethods.Add("math.min"); + dependentLuaMethods.Add("math.modf"); + dependentLuaMethods.Add("math.pow"); + dependentLuaMethods.Add("math.rad"); + dependentLuaMethods.Add("math.sin"); + dependentLuaMethods.Add("math.sinh"); + dependentLuaMethods.Add("math.sqrt"); + dependentLuaMethods.Add("math.tan"); + dependentLuaMethods.Add("math.tanh"); + dependentLuaMethods.Add("string.format"); + dependentLuaMethods.Add("string.len"); + dependentLuaMethods.Add("string.lower"); + dependentLuaMethods.Add("string.rep"); + dependentLuaMethods.Add("string.reverse"); + dependentLuaMethods.Add("string.upper"); + + dependentLuaMethods.Sort(); + } + + if (dpaiChecked == false) + { + dpaiChecked = true; + + Type dpaiMDNNType = Utility.GetExportedType("ModuleDockingNodeNamed", "NavyFish.ModuleDockingNodeNamed"); + if (dpaiMDNNType != null) + { + Utility.LogMessage(this, "Found DPAI"); + FieldInfo portName = dpaiMDNNType.GetField("portName", BindingFlags.Instance | BindingFlags.Public); + GetDpaiName = DynamicMethodFactory.CreateGetField(portName); + } + } + + Vessel vessel = this.vessel; + + if (string.IsNullOrEmpty(flightComputerId)) + { + fcId = Guid.NewGuid(); + flightComputerId = fcId.ToString(); + + Utility.LogMessage(this, "Creating new flight computer {0}", flightComputerId); + } + else + { + fcId = new Guid(flightComputerId); + + Utility.LogMessage(this, "Restoring flight computer {0}", flightComputerId); + } + + parentVesselId = vessel.id; + + // TODO: Review the CoreModule settings - are they tight enough? + // Add the LoadMethods to allow dynamic script compilation. + script = new Script(CoreModules.Preset_HardSandbox | CoreModules.LoadMethods); + + UserData.DefaultAccessMode = InteropAccessMode.Preoptimized; + + // Global State (set up links to the proxy). Note that we + // don't use the proxy system included in MoonSharp, since it + // creates a proxy object for every single script.Call(), which + // means plenty of garbage... + string whichProxy = string.Empty; + try + { + whichProxy = "chatterer"; + chattererProxy = new MASIChatterer(); + UserData.RegisterType(); + script.Globals["chatterer"] = chattererProxy; + registeredTables.Add("chatterer", new MASRegisteredTable(chattererProxy)); + + whichProxy = "engine"; + engineProxy = new MASIEngine(); + UserData.RegisterType(); + script.Globals["engine"] = engineProxy; + registeredTables.Add("engine", new MASRegisteredTable(engineProxy)); + + whichProxy = "far"; + farProxy = new MASIFAR(vessel); + UserData.RegisterType(); + script.Globals["far"] = farProxy; + registeredTables.Add("far", new MASRegisteredTable(farProxy)); + + whichProxy = "kac"; + kacProxy = new MASIKAC(vessel); + UserData.RegisterType(); + script.Globals["kac"] = kacProxy; + registeredTables.Add("kac", new MASRegisteredTable(kacProxy)); + + whichProxy = "ke"; + keProxy = new MASIKerbalEngineer(); + UserData.RegisterType(); + script.Globals["ke"] = keProxy; + registeredTables.Add("ke", new MASRegisteredTable(keProxy)); + + whichProxy = "mechjeb"; + mjProxy = new MASIMechJeb(); + UserData.RegisterType(); + script.Globals["mechjeb"] = mjProxy; + registeredTables.Add("mechjeb", new MASRegisteredTable(mjProxy)); + + whichProxy = "nav"; + navProxy = new MASINavigation(vessel, this); + UserData.RegisterType(); + script.Globals["nav"] = navProxy; + registeredTables.Add("nav", new MASRegisteredTable(navProxy)); + + whichProxy = "parachute"; + parachuteProxy = new MASIParachute(vessel); + UserData.RegisterType(); + script.Globals["parachute"] = parachuteProxy; + registeredTables.Add("parachute", new MASRegisteredTable(parachuteProxy)); + + whichProxy = "transfer"; + transferProxy = new MASITransfer(vessel); + UserData.RegisterType(); + script.Globals["transfer"] = transferProxy; + registeredTables.Add("transfer", new MASRegisteredTable(transferProxy)); + + whichProxy = "vtol"; + vtolProxy = new MASIVTOL(); + UserData.RegisterType(); + script.Globals["vtol"] = vtolProxy; + registeredTables.Add("vtol", new MASRegisteredTable(vtolProxy)); + + whichProxy = "fc"; + fcProxy = new MASFlightComputerProxy(this, farProxy, keProxy, mjProxy); + UserData.RegisterType(); + script.Globals["fc"] = fcProxy; + registeredTables.Add("fc", new MASRegisteredTable(fcProxy)); + } + catch (Exception e) + { + Utility.LogError(this, "Proxy object {0} configuration failed:", whichProxy); + Utility.LogError(this, e.ToString()); + Utility.ComplainLoudly("Initialization Failed. Please check KSP.log"); + return; + } + + vc = MASPersistent.FetchVesselComputer(vessel); + ap = MASAutoPilot.Get(vessel); + // Initialize the resourceConverterList with ElectricCharge at index 0 + if (vc.resourceConverterList.Count > 0) + { + vc.resourceConverterList.Clear(); + } + var rc = new MASVesselComputer.GeneralPurposeResourceConverter(); + rc.id = 0; + rc.outputResource = MASConfig.ElectricCharge; + vc.resourceConverterList.Add(rc); + // The resourceConverterList changes - trigger a rebuild + vc.modulesInvalidated = true; + + if (!MASPersistent.PersistentsLoaded) + { + throw new ArgumentNullException("MASPersistent.PersistentsLoaded has not loaded!"); + } + persistentVars = MASPersistent.RestoreDictionary(fcId, persistentVars); + navRadioFrequency = MASPersistent.RestoreNavRadio(fcId, navRadioFrequency); + foreach (var radio in navRadioFrequency) + { + ReloadRadio(radio.Key, radio.Value); + } + + // Always make sure we set the vessel ID in the persistent table + // based on what it currently is. + SetPersistent(vesselIdLabel, parentVesselId.ToString()); + + object activeFilters; + if (persistentVars.TryGetValue(vesselFilterLabel, out activeFilters) && (activeFilters is string)) + { + vesselFilterValue = int.Parse((activeFilters as string), System.Globalization.NumberStyles.HexNumber); + } + else + { + // Initial values + vesselFilterValue = 0; + vesselFilterValue |= 1 << bitVesselRemap[VesselType.Probe]; + vesselFilterValue |= 1 << bitVesselRemap[VesselType.Relay]; + vesselFilterValue |= 1 << bitVesselRemap[VesselType.Rover]; + vesselFilterValue |= 1 << bitVesselRemap[VesselType.Lander]; + vesselFilterValue |= 1 << bitVesselRemap[VesselType.Ship]; + vesselFilterValue |= 1 << bitVesselRemap[VesselType.Plane]; + vesselFilterValue |= 1 << bitVesselRemap[VesselType.Station]; + vesselFilterValue |= 1 << bitVesselRemap[VesselType.Base]; + vesselFilterValue |= 1 << bitVesselRemap[VesselType.EVA]; + vesselFilterValue |= 1 << bitVesselRemap[VesselType.Flag]; + + SetPersistent(vesselFilterLabel, vesselFilterValue.ToString("X")); + } + + activeVesselFilter.Clear(); + for (int i = 1; i < 14; ++i) + { + if ((vesselFilterValue & (1 << i)) != 0) + { + activeVesselFilter.Add(vesselBitRemap[i]); + } + } + + // TODO: Don't need to set vessel for all of these guys if I just now init'd them. + fcProxy.vc = vc; + fcProxy.vessel = vessel; + engineProxy.vc = vc; + mjProxy.UpdateVessel(vessel, vc); + parachuteProxy.vc = vc; + transferProxy.vc = vc; + vtolProxy.UpdateVessel(vessel, vc); + + // Add User scripts + try + { + for (int i = MASLoader.userScripts.Count - 1; i >= 0; --i) + { + script.DoString(MASLoader.userScripts[i]); + } + + Utility.LogInfo(this, "{1}: Loaded {0} user scripts", MASLoader.userScripts.Count, flightComputerId); + } + catch (MoonSharp.Interpreter.SyntaxErrorException e) + { + Utility.ComplainLoudly("User Script Loading error"); + Utility.LogError(this, " - {0}", e.DecoratedMessage); + Utility.LogError(this, e.ToString()); + } + catch (Exception e) + { + Utility.ComplainLoudly("User Script Loading error"); + Utility.LogError(this, e.ToString()); + } + + // Parse action group labels: + if (!string.IsNullOrEmpty(shipDescription)) + { + string[] rows = shipDescription.Replace("$$$", Environment.NewLine).Split(Utility.LineSeparator, StringSplitOptions.RemoveEmptyEntries); + StringBuilder sb = StringBuilderCache.Acquire(); + for (int i = 0; i < rows.Length; ++i) + { + if (rows[i].StartsWith("AG")) + { + string[] row = rows[i].Split('='); + int groupID; + if (int.TryParse(row[0].Substring(2), out groupID)) + { + if (groupID >= 0 && groupID <= 9) + { + if (row.Length == 2) + { + string[] memo = row[1].Split('|'); + if (memo.Length == 1) + { + agMemoOn[groupID] = memo[0].Trim(); + agMemoOff[groupID] = agMemoOn[groupID]; + } + else if (memo.Length == 2) + { + agMemoOn[groupID] = memo[0].Trim(); + agMemoOff[groupID] = memo[1].Trim(); + } + } + } + } + } + else if (sb.Length == 0) + { + sb.Append(rows[i].Trim()); + } + else + { + sb.Append("$$$").Append(rows[i].Trim()); + } + } + if (sb.Length > 0) + { + vesselDescription = sb.ToStringAndRelease(); + } + } + + // Initialize persistent vars ... note that save game values have + // already been restored, so check if the persistent already exists, + // first. + // Also restore named color per-part overrides + ConfigNode myNode = Utility.GetPartModuleConfigNode(part, typeof(MASFlightComputer).Name, 0); + try + { + ConfigNode persistentSeed = myNode.GetNode("PERSISTENT_VARIABLES"); + if (persistentSeed != null) + { + var values = persistentSeed.values; + int valueCount = values.Count; + for (int i = 0; i < valueCount; ++i) + { + var persistentVal = values[i]; + + if (!persistentVars.ContainsKey(persistentVal.name)) + { + double doubleVal; + if (double.TryParse(persistentVal.value, out doubleVal)) + { + persistentVars[persistentVal.name] = doubleVal; + } + else + { + persistentVars[persistentVal.name] = persistentVal.value; + } + } + } + } + + ConfigNode overrideColorNodes = myNode.GetNode("RPM_COLOROVERRIDE"); + ConfigNode[] colorConfig = overrideColorNodes.GetNodes("COLORDEFINITION"); + for (int defIdx = 0; defIdx < colorConfig.Length; ++defIdx) + { + if (colorConfig[defIdx].HasValue("name") && colorConfig[defIdx].HasValue("color")) + { + string name = "COLOR_" + (colorConfig[defIdx].GetValue("name").Trim()); + Color32 color = ConfigNode.ParseColor32(colorConfig[defIdx].GetValue("color").Trim()); + namedColors[name] = color; + } + } + } + catch + { + + } + try + { + ConfigNode[] actionGroupConfig = myNode.GetNodes("MAS_ACTION_GROUP"); + for (int agIdx = 0; agIdx < actionGroupConfig.Length; ++agIdx) + { + MASActionGroup ag = new MASActionGroup(actionGroupConfig[agIdx]); + if (masActionGroup.ContainsKey(ag.actionGroupId)) + { + Utility.LogError(this, "Found duplicate MAS_ACTION_GROUP id {0} ... duplicate AG was discarded", ag.actionGroupId); + } + else + { + ag.Rebuild(vessel.Parts); + masActionGroup.Add(ag.actionGroupId, ag); + } + } + } + catch + { + + } + + audioObject.name = "MASFlightComputerAudio-" + flightComputerId; + audioSource = audioObject.AddComponent(); + audioSource.spatialBlend = 0.0f; + morseAudioObject.name = "MASFlightComputerMorseAudio-" + flightComputerId; + morseAudioSource = audioObject.AddComponent(); + morseAudioSource.spatialBlend = 0.0f; + + UpdateLocalCrew(); + + GameEvents.onVesselWasModified.Add(onVesselChanged); + GameEvents.onVesselChange.Add(onVesselChanged); + GameEvents.onVesselCrewWasModified.Add(onVesselChanged); + GameEvents.OnCameraChange.Add(onCameraChange); + GameEvents.OnIVACameraKerbalChange.Add(OnIVACameraKerbalChange); + GameEvents.OnControlPointChanged.Add(OnControlPointChanged); + + if (!string.IsNullOrEmpty(powerOnVariable)) + { + RegisterVariableChangeCallback(powerOnVariable, null, (double newValue) => powerOnValid = (newValue > 0.0)); + } + + // All the things are initialized ... Let's see if there's a startupScript + if (!string.IsNullOrEmpty(startupScript)) + { + DynValue dv = script.LoadString(startupScript); + + if (dv.IsNil() == false) + { + try + { + script.Call(dv); + } + catch (Exception e) + { + Utility.ComplainLoudly("MASFlightComputer startupScript triggered an exception"); + Utility.LogError(this, "MASFlightComputer startupScript triggered an exception:"); + Utility.LogError(this, e.ToString()); + } + } + } + + if (!string.IsNullOrEmpty(onEnterIVA)) + { + enterIvaScript = script.LoadString(onEnterIVA); + if (enterIvaScript.IsNil()) + { + Utility.LogError(this, "Failed to process onEnterIVA script"); + } + } + + if (!string.IsNullOrEmpty(onExitIVA)) + { + exitIvaScript = script.LoadString(onExitIVA); + if (exitIvaScript.IsNil()) + { + Utility.LogError(this, "Failed to process onExitIVA script"); + } + } + + initialized = true; + } + } + + /// + /// Display name to show in the VAB when part description is expanded. + /// + /// + public override string GetModuleDisplayName() + { + return "#MAS_FlightComputer_Module_DisplayName"; + } + + /// + /// Text to show in the VAB when part description is expanded. + /// + /// + public override string GetInfo() + { + return "#MAS_FlightComputer_GetInfo"; + } + + #endregion + + #region Private Methods + private void UpdateControlPoint(string cpName) + { + if (commandModule != null && commandModule.controlPoints.Count > 1) + { + for (int i = commandModule.controlPoints.Count - 1; i >= 0; --i) + { + if (commandModule.controlPoints.At(i).name == cpName) + { + activeControlPoint = i; + return; + } + } + } + else + { + activeControlPoint = 0; + } + } + + private void UpdateLocalCrew() + { + // part.internalModel may be null if the craft is loaded but isn't the active/IVA craft + if (part.internalModel != null) + { + int seatCount = part.internalModel.seats.Count; + if (seatCount != localCrew.Length) + { + // This can happen when an internalModel is loaded when + // it wasn't previously, which appears to occur on docking + // for instance. + localCrew = new ProtoCrewMember[seatCount]; + localCrewMedical = new kerbalExpressionSystem[seatCount]; + } + + // Note that we set localCrewMedical to null because the + // crewMedical ends up being going null sometime between + // when the crew changed callback fires and when we start + // checking variables. Thus, we still have to poll the + // crew medical. + for (int i = 0; i < seatCount; i++) + { + localCrew[i] = part.internalModel.seats[i].crew; + localCrewMedical[i] = null; + } + } + else if (localCrew.Length > 0) + { + localCrew = new ProtoCrewMember[0]; + localCrewMedical = new kerbalExpressionSystem[0]; + } + } + #endregion + + #region Audio Player + GameObject audioObject = new GameObject(); + AudioSource audioSource; + GameObject morseAudioObject = new GameObject(); + AudioSource morseAudioSource; + string morseSequence; + float morseVolume; + bool playingSequence; + + private IEnumerator MorsePlayerCoroutine() + { + while (morseSequence.Length > 0) + { + AudioClip clip; + char first = morseSequence[0]; + if (first == ' ') + { + yield return new WaitForSecondsRealtime(0.25f); + } + else if (MASLoader.morseCode.TryGetValue(first, out clip)) + { + audioSource.clip = clip; + audioSource.volume = morseVolume; + audioSource.Play(); + yield return new WaitForSecondsRealtime(clip.length + 0.05f); + } + + morseSequence = morseSequence.Substring(1); + } + + playingSequence = false; + yield return null; + } + + /// + /// Play a morse code sequence. + /// + /// + /// + /// + /// + internal double PlayMorseSequence(string sequence, float volume, bool stopCurrent) + { + if (stopCurrent) + { + morseAudioSource.Stop(); + } + else if (morseAudioSource.isPlaying) + { + return 0.0; + } + + morseSequence = sequence.ToUpper(); + morseVolume = GameSettings.SHIP_VOLUME * volume; + + if (!playingSequence) + { + StartCoroutine(MorsePlayerCoroutine()); + playingSequence = true; + } + + return 1.0; + } + + /// + /// Select and play an audio clip. + /// + /// If stopCurrent is true, any current audio is stopped, first. If stopCurrent is + /// false and audio is playing, the new clip does not play. + /// + /// URI of the clip to load & play. + /// Volume (clamped to [0, 1]) for playback. + /// Whether current audio should be stopped. + /// true if the clip was loaded and is playing, false if the clip was not played. + internal bool PlayAudio(string clipName, float volume, bool stopCurrent) + { + AudioClip clip = GameDatabase.Instance.GetAudioClip(clipName); + if (clip == null) + { + return false; + } + + if (stopCurrent) + { + audioSource.Stop(); + } + else if (audioSource.isPlaying) + { + return false; + } + + audioSource.clip = clip; + audioSource.volume = GameSettings.SHIP_VOLUME * volume; + audioSource.Play(); + return true; + } + + #endregion + + /// + /// Identify the current IVA Kerbal. If the kerbal is not in the current part, + /// return null. + /// + /// + internal Kerbal FindCurrentKerbal() + { + if (vessel.GetCrewCount() == 0) + { + return null; + } + Kerbal activeKerbal = CameraManager.Instance.IVACameraActiveKerbal; + if (activeKerbal.InPart == part) + { + return activeKerbal; + } + else + { + return null; + } + } + + #region GameEvent Callbacks + /// + /// Callback when the player changes camera modes. + /// + /// + private void onCameraChange(CameraManager.CameraMode newMode) + { + // newMode == Flight -> heading to external view. + // newMode == Map -> heading to Map view + // newMode == IVA -> heading to IVA view. + if (newMode == CameraManager.CameraMode.IVA) + { + Kerbal activeKerbal = FindCurrentKerbal(); + if (activeKerbal != null) + { + // There are situations where InternalCamera is null - like during staging + // from IVA when the IVA isn't in the new active vessel. + if (InternalCamera.Instance != null) + { + InternalCamera.Instance.maxRot = maxRot; + InternalCamera.Instance.minPitch = minPitch; + InternalCamera.Instance.maxPitch = maxPitch; + } + } + if (enterIvaScript != null) + { + try + { + script.Call(enterIvaScript); + } + catch (Exception e) + { + Utility.ComplainLoudly("MASFlightComputer onEnterIVA triggered an exception"); + Utility.LogError(this, "MASFlightComputer onEnterIVA triggered an exception:"); + Utility.LogError(this, e.ToString()); + enterIvaScript = null; + } + } + } + else + { + // There are situations where InternalCamera is null - like during staging + // from IVA when the IVA isn't in the new active vessel. + if (InternalCamera.Instance != null) + { + // Reset to defaults + InternalCamera.Instance.maxRot = 80.0f; + InternalCamera.Instance.minPitch = -80.0f; + InternalCamera.Instance.maxPitch = 45.0f; + } + if (exitIvaScript != null) + { + try + { + script.Call(exitIvaScript); + } + catch (Exception e) + { + Utility.ComplainLoudly("MASFlightComputer onExitIVA triggered an exception"); + Utility.LogError(this, "MASFlightComputer onExitIVA triggered an exception:"); + Utility.LogError(this, e.ToString()); + exitIvaScript = null; + } + } + } + } + + /// + /// Callback when the player changes control points. + /// + private void OnControlPointChanged(Part who, ControlPoint where) + { + if (who == part && commandModule != null) + { + UpdateControlPoint(where.name); + } + } + + /// + /// Callback when the player changes IVA cameras. Note that the Kerbal passed in + /// is *not* the current Kerbal. It appears to be the *next* Kerbal, I think. So we + /// are forced to call FindCurrentKerbal. + /// + /// + private void OnIVACameraKerbalChange(Kerbal dontCare) + { + Kerbal newKerbal = FindCurrentKerbal(); + if (newKerbal != null) + { + // There are situations where InternalCamera is null - like during staging + // from IVA when the IVA isn't in the new active vessel. + if (InternalCamera.Instance != null) + { + InternalCamera.Instance.maxRot = maxRot; + InternalCamera.Instance.minPitch = minPitch; + InternalCamera.Instance.maxPitch = maxPitch; + } + } + } + + /// + /// General-purpose callback to make sure we refresh our data when + /// something changes. + /// + /// The Vessel being changed + private void onVesselChanged(Vessel who) + { + if (who.id == this.vessel.id) + { + // TODO: Do something different if parentVesselID != vessel.id? + Vessel vessel = this.vessel; + if (vessel.id != parentVesselId) + { + parentVesselId = vessel.id; + SetPersistent(vesselIdLabel, parentVesselId.ToString()); + } + + vc = MASPersistent.FetchVesselComputer(vessel); + ap = MASAutoPilot.Get(vessel); + fcProxy.vc = vc; + fcProxy.vessel = vessel; + chattererProxy.UpdateVessel(); + engineProxy.vc = vc; + farProxy.vessel = vessel; + kacProxy.vessel = vessel; + mjProxy.UpdateVessel(vessel, vc); + navProxy.UpdateVessel(vessel); + parachuteProxy.vc = vc; + parachuteProxy.vessel = vessel; + transferProxy.vc = vc; + transferProxy.vessel = vessel; + vtolProxy.UpdateVessel(vessel, vc); + + List parts = vessel.parts; + foreach (var pair in masActionGroup) + { + pair.Value.Rebuild(parts); + } + } + UpdateLocalCrew(); + } + #endregion + } +} diff --git a/ModifiedCode/MASFlightComputerProxy.cs b/ModifiedCode/MASFlightComputerProxy.cs new file mode 100644 index 00000000..e3efeb65 --- /dev/null +++ b/ModifiedCode/MASFlightComputerProxy.cs @@ -0,0 +1,5462 @@ +/***************************************************************************** + * The MIT License (MIT) + * + * Copyright (c) 2016-2020 MOARdV + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + ****************************************************************************/ +using MoonSharp.Interpreter; +using System; +using System.Collections.Generic; +using System.Text; +using UnityEngine; + +namespace AvionicsSystems +{ + // ΔV - put this somewhere where I can find it easily to copy/paste + + /// + /// The flight computer proxy provides the interface between the flight + /// computer module and the variable / Lua environment. + /// + /// While it is a wrapper for MASFlightComputer, not all + /// values are plumbed through to the flight computer (for instance, the + /// action group control and state are all handled in this class). + /// + /// fc + /// + /// The `fc` group contains the core interface between KSP, Avionics + /// Systems, and props in an IVA. It consists of many 'information' functions + /// that can be used to get information as well as numerous 'action' functions + /// that are used to do things. + /// + /// Due to the number of methods in the `fc` group, this document has been split + /// across three pages: + /// + /// * [[MASFlightComputerProxy]] (Abort - Lights), + /// * [[MASFlightComputerProxy2]] (Maneuver Node - Reaction Wheel), and + /// * [[MASFlightComputerProxy3]] (Resources - Vessel Info). + /// + /// **NOTE 1:** If a function listed below includes an entry for 'Supported Mod(s)', + /// then that function will automatically use one of the mods listed to + /// generate the data. In some cases, it is possible that the function does not work without + /// one of the required mods. Those instances are noted in the function's description. + /// + /// **NOTE 2:** Many descriptions make use of mathetmatical short-hand to describe + /// a range of values. This short-hand consists of using square brackets `[` and `]` + /// to denote "inclusive range", while parentheses `(` and `)` indicate exclusive range. + /// + /// For example, if a parameter says "an integer between [0, `fc.ExperimentCount()`)", it + /// means that the parameter must be an integer greater than or equal to 0, but less + /// than `fc.ExperimentCount()`. + /// + /// For another example, if a parameter says "a number in the range [0, 1]", it means that + /// the number must be at least zero, and it must not be larger than 1. + /// + internal partial class MASFlightComputerProxy + { + internal const double KelvinToCelsius = -273.15; + + private MASFlightComputer fc; + internal MASVesselComputer vc; + internal MASIFAR farProxy; + internal MASIKerbalEngineer keProxy; + internal MASIMechJeb mjProxy; + internal Vessel vessel; + + private VesselAutopilot.AutopilotMode autopilotMode = VesselAutopilot.AutopilotMode.StabilityAssist; + private int vesselSituationConverted; + + private ApproachSolver nodeApproachSolver; + + private CommNet.CommLink lastLink; + + [MoonSharpHidden] + public MASFlightComputerProxy(MASFlightComputer fc, MASIFAR farProxy, MASIKerbalEngineer keProxy, MASIMechJeb mjProxy) + { + this.fc = fc; + this.farProxy = farProxy; + this.keProxy = keProxy; + this.mjProxy = mjProxy; + this.nodeApproachSolver = new ApproachSolver(); + } + + ~MASFlightComputerProxy() + { + fc = null; + vc = null; + farProxy = null; + keProxy = null; + mjProxy = null; + vessel = null; + } + + /// + /// Helper function to convert a vessel situation into a number. + /// + /// + /// + [MoonSharpHidden] + static internal int ConvertVesselSituation(Vessel.Situations vesselSituation) + { + int situation = (int)vesselSituation; + for (int i = 0; i < 0x10; ++i) + { + if ((situation & (1 << i)) != 0) + { + return i; + } + } + + return 0; + } + + /// + /// Per-FixedUpdate updater method to read some of those values that are used a lot. + /// + [MoonSharpHidden] + internal void Update() + { + autopilotMode = vessel.Autopilot.Mode; + nodeApproachSolver.ResetComputation(); + + vesselSituationConverted = ConvertVesselSituation(vessel.situation); + + for (int i = neighboringVessels.Length - 1; i >= 0; --i) + { + neighboringVessels[i] = null; + } + neighboringVesselsCurrent = false; + + try + { + lastLink = vessel.connection.ControlPath.Last; + } + catch + { + lastLink = null; + } + } + + private kerbalExpressionSystem[] crewExpression = new kerbalExpressionSystem[0]; + [MoonSharpHidden] + private kerbalExpressionSystem GetVesselCrewExpression(int index) + { + if (crewExpression.Length != vessel.GetCrewCount()) + { + crewExpression = new kerbalExpressionSystem[vessel.GetCrewCount()]; + } + + vessel.GetVesselCrew()[index].KerbalRef.GetComponentCached(ref crewExpression[index]); + + return crewExpression[index]; + } + + /// + /// Private method to map a string or number to a CelestialBody. + /// + /// A string or number identifying the celestial body. + [MoonSharpHidden] + private CelestialBody SelectBody(object id) + { + CelestialBody cb = null; + + if (id is double) + { + int idx = (int)(double)id; + if (idx >= 0 && idx < FlightGlobals.Bodies.Count) + { + cb = FlightGlobals.Bodies[idx]; + } + } + else if (id is string) + { + string bodyName = id as string; + cb = FlightGlobals.Bodies.Find(x => (x.bodyName == bodyName)); + } + + return cb; + } + + + // Keep a scratch list handy. The members of the array are null'd after TargetNextVessel + // executes to make sure we're not holding dangling references. This could be written more + // efficiently, but I don't see this being used extensively. + private Vessel[] neighboringVessels = new Vessel[0]; + private VesselDistanceComparer distanceComparer = new VesselDistanceComparer(); + private List localVessels = new List(); + private bool neighboringVesselsCurrent = false; + + [MoonSharpHidden] + private bool EnabledType(global::VesselType type) + { + return fc.activeVesselFilter.FindIndex(x => x == type) != -1; + } + + [MoonSharpHidden] + private void UpdateNeighboringVessels() + { + if (!neighboringVesselsCurrent) + { + // Populate + var allVessels = FlightGlobals.fetch.vessels; + int allVesselCount = allVessels.Count; + CelestialBody mainBody = vessel.mainBody; + for (int i = 0; i < allVesselCount; ++i) + { + Vessel v = allVessels[i]; + if (v.mainBody == mainBody && EnabledType(v.vesselType) && v != vessel) + { + localVessels.Add(v); + } + } + + int arrayLength = neighboringVessels.Length; + if (arrayLength != localVessels.Count) + { + neighboringVessels = localVessels.ToArray(); + } + else + { + for (int i = 0; i < arrayLength; ++i) + { + neighboringVessels[i] = localVessels[i]; + } + } + localVessels.Clear(); + + distanceComparer.vesselPosition = vessel.GetTransform().position; + try + { + Array.Sort(neighboringVessels, distanceComparer); + } + catch (Exception e) + { + throw new ArgumentException("Error in UpdateNeighboringVessels due to distanceComparer: \"" + e.Source + e.TargetSite + e.Data + e.StackTrace + neighboringVessels + "\"", e); + } + + neighboringVesselsCurrent = true; + } + } + + private class VesselDistanceComparer : IComparer + { + internal Vector3 vesselPosition; + public int Compare(Vessel a, Vessel b) + { + float distA = Vector3.SqrMagnitude(a.GetTransform().position - vesselPosition); + float distB = Vector3.SqrMagnitude(b.GetTransform().position - vesselPosition); + return (int)(distA - distB); + } + } + + /// + /// The Abort action and the GetAbort query belong in this category. + /// + #region Abort + /// + /// Trigger the Abort action group. + /// + /// 1 (abort is always a SET, not a toggle). + public double Abort() + { + vessel.ActionGroups.SetGroup(KSPActionGroup.Abort, true); + return 1.0; + } + + /// + /// Returns 1 if the Abort action has been triggered. + /// + /// + public double GetAbort() + { + return (vessel.ActionGroups[KSPActionGroup.Abort]) ? 1.0 : 0.0; + } + #endregion + + /// + /// Variables and actions related to player-configured action groups are in this + /// category. + /// + #region Action Groups + private static readonly KSPActionGroup[] ags = { KSPActionGroup.Custom10, KSPActionGroup.Custom01, KSPActionGroup.Custom02, KSPActionGroup.Custom03, KSPActionGroup.Custom04, KSPActionGroup.Custom05, KSPActionGroup.Custom06, KSPActionGroup.Custom07, KSPActionGroup.Custom08, KSPActionGroup.Custom09 }; + + /// + /// Returns 1 if there is at least one action associated with the action + /// group. 0 otherwise, or if an invalid action group is specified. + /// + /// A number between 0 and 9 (inclusive) for stock action groups, 10 or larger for MAS action groups. + /// 1 if there are actions for this action group, 0 otherwise. + public double ActionGroupHasActions(double groupID) + { + if (groupID >= 10.0) + { + MASActionGroup ag; + if (fc.masActionGroup.TryGetValue((int)groupID, out ag)) + { + return (ag.HasActions()) ? 1.0 : 0.0; + } + else + { + return 0.0; + } + } + else if (groupID < 0.0) + { + return 0.0; + } + else + { + return (vc.GroupHasActions(ags[(int)groupID])) ? 1.0 : 0.0; + } + } + + /// + /// Returns the current memo from the action group selected by groupID. If + /// the memo was configured with active and inactive descriptions, this memo + /// will change. If an invalid groupID is provided, the result is an + /// empty string. If no memo was specified, the result is "AG0" for action + /// group 0, "AG1" for action group 1, etc. + /// + /// A number between 0 and 9 (inclusive). Note that MAS action groups do not have an AG Memo. + /// The memo for the requested group, or an empty string. + public string ActionGroupActiveMemo(double groupID) + { + int ag = (int)groupID; + if (ag < 0 || ag > 9) + { + return string.Empty; + } + else if (vessel.ActionGroups[ags[ag]]) + { + return fc.agMemoOn[ag]; + } + else + { + return fc.agMemoOff[ag]; + } + } + + /// + /// Returns the action group memo specified by the groupID, with active + /// selecting whether the memo is for the active mode or the inactive mode. + /// If the selected memo does not differentiate between active and inactive, + /// the result is the same. If an invalid groupID is provided, the result is an + /// empty string. If no memo was specified, the result is "AG0" for action + /// group 0, "AG1" for action group 1, etc. + /// + /// A number between 0 and 9 (inclusive). Note that MAS action groups do not have an AG Memo. + /// Whether the memo is for the active (true) or inactive (false) setting. + /// The memo for the requested group and state, or an empty string. + public string ActionGroupMemo(double groupID, bool active) + { + if (groupID < 0.0 || groupID > 9.0) + { + return string.Empty; + } + else if (active) + { + return fc.agMemoOn[(int)groupID]; + } + else + { + return fc.agMemoOff[(int)groupID]; + } + } + + /// + /// Get the current state of the specified action group. + /// + /// A number between 0 and 9 (inclusive) for stock action groups, 10 or larger for MAS action groups. + /// 1 if active, 0 if inactive + public double GetActionGroup(double groupID) + { + if (groupID >= 10.0) + { + MASActionGroup actionGroup; + if (fc.masActionGroup.TryGetValue((int)groupID, out actionGroup)) + { + return (actionGroup.GetState()) ? 1.0 : 0.0; + } + else + { + return 0.0; + } + } + else if (groupID < 0.0) + { + return 0.0; + } + else + { + return (vessel.ActionGroups[ags[(int)groupID]]) ? 1.0 : 0.0; + } + } + + /// + /// Set the specified action group to the requested state. + /// + /// A number between 0 and 9 (inclusive) for stock action groups, 10 or larger for MAS action groups. + /// true or false to set the state. + /// 1 if the action group ID was valid, 0 otherwise. + public double SetActionGroup(double groupID, bool active) + { + if (groupID >= 10.0) + { + MASActionGroup actionGroup; + if (fc.masActionGroup.TryGetValue((int)groupID, out actionGroup)) + { + actionGroup.SetState(active); + + return 1.0; + } + } + else if (groupID >= 0.0) + { + vessel.ActionGroups.SetGroup(ags[(int)groupID], active); + return 1.0; + } + + return 0.0; + } + + /// + /// Toggle the selected action group or MAS action group. + /// + /// A number between 0 and 9 (inclusive) for stock action groups, 10 or larger for MAS action groups. + /// 1 if the action group ID was valid, 0 otherwise. + public double ToggleActionGroup(double groupID) + { + if (groupID >= 10.0) + { + MASActionGroup actionGroup; + if (fc.masActionGroup.TryGetValue((int)groupID, out actionGroup)) + { + actionGroup.Toggle(); + return 1.0; + } + } + else if (groupID >= 0.0) + { + vessel.ActionGroups.ToggleGroup(ags[(int)groupID]); + return 1.0; + } + + return 0.0; + } + #endregion + + /// + /// Variables relating to the current vessel's altitude are found in this category. + /// + #region Altitudes + /// + /// Returns the vessel's altitude above the datum (sea level where + /// applicable), in meters. + /// + /// + public double Altitude() + { + return vc.altitudeASL; + } + + /// + /// Returns altitude above datum (or sea level) for vessels in an + /// atmosphere. Returns 0 otherwise. Altitude in meters. + /// + /// + public double AltitudeAtmospheric() + { + if (vc.mainBody.atmosphere) + { + return (vc.altitudeASL < vc.mainBody.atmosphereDepth) ? vc.altitudeASL : 0.0; + } + else + { + return 0.0; + } + } + + /// + /// Returns the distance from the lowest point of the craft to the + /// surface of the planet. Ocean is treated as surface for this + /// purpose. Precision reporting sets in at 500m (above 500m it + /// reports the same as AltitudeTerrain(false)). Distance in + /// meters. + /// + /// + public double AltitudeBottom() + { + return vc.altitudeBottom; + } + + /// + /// Returns the height above the ground, optionally treating the ocean + /// surface as ground. Altitude in meters. + /// + /// When false, returns height above sea level + /// when over the ocean; when true, always returns ground height. + /// Altitude above the terrain in meters. + public double AltitudeTerrain(bool ignoreOcean) + { + return (ignoreOcean) ? vc.altitudeTerrain : Math.Min(vc.altitudeASL, vc.altitudeTerrain); + } + + /// + /// Returns the terrain height beneath the vessel relative to the planet's datum (sea + /// level or equivalent). Height in meters. + /// + /// + public double TerrainHeight() + { + return vessel.terrainAltitude; + } + #endregion + + /// + /// Atmosphere and airflow variables are found in this category. + /// + #region Atmosphere + /// + /// Returns the atmospheric depth as reported by the KSP atmosphere + /// gauge, a number ranging between 0 and 1. + /// + /// + public double AtmosphereDepth() + { + return vc.atmosphereDepth; + } + + /// + /// Returns the altitude of the top of atmosphere, or 0 if there is no + /// atmo. Altitude in meters. + /// + /// + public double AtmosphereTop() + { + return vc.mainBody.atmosphereDepth; + } + + /// + /// Returns the atmospheric density. + /// + /// + public double AtmosphericDensity() + { + return vessel.atmDensity; + } + + /// + /// Returns the drag force on the vessel. If FAR is installed, this variable uses + /// FAR's computation for drag. + /// + /// Drag in kN. + public double Drag() + { + if (vc.mainBody.atmosphere == false || vc.altitudeASL > vc.mainBody.atmosphereDepth) + { + return 0.0; + } + + if (MASIFAR.farFound) + { + return farProxy.DragForce(); + } + else + { + return vc.DragForce(); + } + } + + /// + /// Returns the drag effect on the vessel as acceleration. If FAR is installed, this variable uses + /// FAR's computation for drag. + /// + /// Drag acceleration in m/s^2. + public double DragAccel() + { + if (vc.mainBody.atmosphere == false || vc.altitudeASL > vc.mainBody.atmosphereDepth) + { + return 0.0; + } + + if (MASIFAR.farFound) + { + return farProxy.DragForce() / vessel.totalMass; + } + else + { + return vc.DragForce() / vessel.totalMass; + } + } + + /// + /// Returns the current dynamic pressure on the vessel in kPa. If FAR + /// is installed, this variable uses FAR's computation instead. + /// + /// Dynamic pressure in kPa. + public double DynamicPressure() + { + if (MASIFAR.farFound) + { + return farProxy.DynamicPressure(); + } + else + { + return vessel.dynamicPressurekPa; + } + } + + /// + /// Returns the force of gravity affecting the vessel. + /// + /// Force of gravity in kN. + public double GravityForce() + { + return vc.GravForce(); + } + + /// + /// Returns 1 if the body the vessel is orbiting has an atmosphere. + /// + /// 1 if there is an atmosphere, 0 otherwise. + public double HasAtmosphere() + { + return (vc.mainBody.atmosphere) ? 1.0 : 0.0; + } + + /// + /// Returns the lift force on the vessel. If FAR is installed, this variable uses + /// FAR's computation for lift. + /// + /// Lift in kN. + public double Lift() + { + if (vc.mainBody.atmosphere == false || vc.altitudeASL > vc.mainBody.atmosphereDepth) + { + return 0.0; + } + + if (MASIFAR.farFound) + { + return farProxy.LiftForce(); + } + else + { + return vc.LiftForce(); + } + } + + /// + /// Returns the force of lift opposed to gravity. If FAR is installed, this variable uses + /// FAR's computations for lift. + /// + /// Lift opposed to gravity in kN. + public double LiftUpForce() + { + if (vc.mainBody.atmosphere == false || vc.altitudeASL > vc.mainBody.atmosphereDepth) + { + return 0.0; + } + + if (MASIFAR.farFound) + { + return farProxy.LiftForce() * Vector3d.Dot(vc.up, vc.top); + } + else + { + return vc.LiftUpForce(); + } + } + + /// + /// Returns the static atmospheric pressure outside the vessel in standard atmospheres. + /// + /// + public double StaticPressureAtm() + { + return vessel.staticPressurekPa * PhysicsGlobals.KpaToAtmospheres; + } + + /// + /// Returns the static atmospheric pressure outside the vessel in kiloPascals. + /// + /// Static pressure in kPa. + public double StaticPressureKPa() + { + return vessel.staticPressurekPa; + } + + /// + /// Returns the current terminal velocity of the vessel. If the vessel is not in + /// an atmosphere, returns 0. If FAR is installed, returns FAR's terminal velocity + /// result. + /// + /// Terminal velocity in m/s. + public double TerminalVelocity() + { + if (vc.mainBody.atmosphere == false || vc.altitudeASL > vc.mainBody.atmosphereDepth) + { + return 0.0; + } + + if (MASIFAR.farFound) + { + return farProxy.TerminalVelocity(); + } + else + { + return vc.TerminalVelocity(); + } + } + + #endregion + + /// + /// The Autopilot region provides information about and control over the MAS Vessel + /// Computer Control system (which needs a cool name amenable to acronyms). + /// + /// The attitude control pilot is very similar to MechJeb's advanced SASS modes, but + /// it uses the stock SAS module to provide steering control. + /// + /// Some caveats about the autopilot subsystems: + /// + /// The attitude control pilot uses the stock SAS feature to provide steering control. + /// When it is engaged, SAS is usually in Stability Control mode. If SAS is changed to a + /// different mode (such as Prograde), the attitude control pilot is disengaged. + /// Likewise, if Stability Control is selected, the attitude pilot disengages. Turning + /// off SAS will disengage the pilots. + /// + /// Other MAS autopilots may use the attitude control system to steer the vessel. If + /// the attitude control pilot is disengaged, the other autopilot is also disengaged. + /// + /// There are several supported references available to the MAS autopilot system, as detailed + /// here. "Forward" is defined as the front of the vessel (nose of a space plane, or the top + /// of a vertically-launched rocket). "Up" is the direction of the heads of kerbals sitting + /// in a conventional orientation relative to the forward direction, such that their heads point + /// away from the surface of the planet in horizontal flight. + /// + /// **TODO:** Fully document these reference frames. + /// + /// * 0 - Inertial Frame: The reference frame of the universe. + /// * 1 - Orbital Prograde: Forward = orbital prograde, Up = surface-relative up (radial out). + /// * 2 - Orbital Prograde Horizontal: Forward = horizontal, aligned towards orbital prograde, Up = surface-relative up (radial out). + /// * 3 - Surface Prograde: Forward = surface-relative prograde, Up = surface-relative up (radial out). + /// * 4 - Surface Prograde Horizontal: Forward = horizontal, aligned towards surface-relative prograde, Up = surface-relative up (radial out). + /// * 5 - Surface North: Forward = planetary north, Up = surface-relative up. + /// * 6 - Target: Forward = direction towards the target, Up = perpendicular to the target direction and orbit normal. + /// * 7 - Target Prograde: Forward = towards the target-relative velocity vector, Up = perpendicular to the velocity vector and orbit normal. + /// * 8 - Target Orientation: Forward = target's forward direction, Up = target's up direction. + /// * 9 - Maneuver Node: Forward = facing towards the maneuver vector, Up = radial out at the time of the burn. + /// * 10 - Sun: Forward = facing Kerbol, Up = orbital normal of the body orbiting Kerbol. + /// * 11 - Up: Forward = surface-relative up, Up = planetary north. + /// + #region Autopilot + + /// + /// Disengage the Ascent Control Pilot. + /// + /// 1 if the pilot was on, 0 if it was already disengaged. + public double DisengageAscentPilot() + { + return (fc.ap.DisengageAscentPilot()) ? 1.0 : 0.0; + } + + /// + /// Engage the Ascent Control Pilot. + /// + /// **NOTE: This function is not enabled, and it always returns 0.** + /// + /// If invalid parameters are supplied, or the vessel is in flight, the pilot will + /// not activate. + /// + /// Goal apoapsis, in meters. + /// Goal periapsis, in meters. + /// Orbital inclination. + /// Horizon-relative roll to maintain during ascent. + /// 1 if the pilot is engaged, 0 if it failed to engage. + public double EngageAscentPilot(double apoapsis, double periapsis, double inclination, double roll) + { + //return (fc.ap.EngageAscentPilot(apoapsis, periapsis, inclination, roll)) ? 1.0 : 0.0; + return 0.0; + } + + /// + /// Engage the MAS Attitude Control Pilot to hold the vessel's heading towards + /// the reference direction vector. The `reference` field must be one of: + /// + /// * 0 - Inertial Frame + /// * 1 - Orbital Prograde + /// * 2 - Orbital Prograde Horizontal + /// * 3 - Surface Prograde + /// * 4 - Surface Prograde Horizontal + /// * 5 - Surface North + /// * 6 - Target + /// * 7 - Target Prograde + /// * 8 - Target Orientation + /// * 9 - Maneuver Node + /// * 10 - Sun + /// * 11 - Up + /// + /// This function is equivalent of `fc.EngageAttitudePilot(reference, 0, 0)`. + /// + /// Reference vector, as described in the summary. + /// 1 if the pilot was engaged, otherwise 0. + public double EngageAttitudePilot(double reference) + { + int refAtt = (int)reference; + if (refAtt < 0 || refAtt >= MASAutoPilot.referenceAttitudes.Length) + { + return 0.0; + } + + return fc.ap.EngageAttitudePilot(MASAutoPilot.referenceAttitudes[refAtt], 0.0f, 0.0f) ? 1.0 : 0.0; + } + + /// + /// Engage the MAS Attitude Control Pilot to hold the vessel's heading towards + /// an offset relative to the reference direction vector. The `reference` field must be one of: + /// + /// * 0 - Inertial Frame + /// * 1 - Orbital Prograde + /// * 2 - Orbital Prograde Horizontal + /// * 3 - Surface Prograde + /// * 4 - Surface Prograde Horizontal + /// * 5 - Surface North + /// * 6 - Target + /// * 7 - Target Prograde + /// * 8 - Target Orientation + /// * 9 - Maneuver Node + /// * 10 - Sun + /// * 11 - Up + /// + /// This version does not lock the roll of the vessel to a particular orientation. + /// + /// Reference vector, as described in the summary. + /// 1 if the pilot was engaged, otherwise 0. + public double EngageAttitudePilot(double reference, double heading, double pitch) + { + int refAtt = (int)reference; + if (refAtt < 0 || refAtt >= MASAutoPilot.referenceAttitudes.Length) + { + return 0.0; + } + + return fc.ap.EngageAttitudePilot(MASAutoPilot.referenceAttitudes[refAtt], (float)heading, (float)pitch) ? 1.0 : 0.0; + } + + /// + /// Engages SAS and sets the vessel's heading based on the reference attitude, heading, pitch, and roll. + /// The reference attitude is one of the following: + /// + /// * 0 - Inertial Frame + /// * 1 - Orbital Prograde + /// * 2 - Orbital Prograde Horizontal + /// * 3 - Surface Prograde + /// * 4 - Surface Prograde Horizontal + /// * 5 - Surface North + /// * 6 - Target + /// * 7 - Target Prograde + /// * 8 - Target Orientation + /// * 9 - Maneuver Node + /// * 10 - Sun + /// * 11 - Up + /// + /// Reference attitude, as described in the summary. + /// Heading (yaw) relative to the reference attitude. + /// Pitch relative to the reference attitude. + /// Roll relative to the reference attitude. + /// 1 if the SetHeading command succeeded, 0 otherwise. + public double EngageAttitudePilot(double reference, double heading, double pitch, double roll) + { + int refAtt = (int)reference; + if (refAtt < 0 || refAtt >= MASAutoPilot.referenceAttitudes.Length) + { + return 0.0; + } + + return (fc.ap.EngageAttitudePilot(MASAutoPilot.referenceAttitudes[refAtt], new Vector3((float)heading, (float)pitch, (float)roll))) ? 1.0 : 0.0; + } + + /// + /// Returns 1 if the MAS ascent autopilot is active, 0 if it is idle. + /// + /// + public double GetAscentPilotActive() + { + return (fc.ap.ascentPilotEngaged) ? 1.0 : 0.0; + } + + /// + /// Reports if the attitude control pilot is actively attempting to control + /// the vessel's heading. This pilot could be active if the crew used + /// `fc.SetHeading()` to set the vessel's heading, or if another pilot module + /// is using the attitude pilot's service. + /// + /// 1 if the attitude control pilot is active, 0 otherwise. + public double GetAttitudePilotActive() + { + return (fc.ap.attitudePilotEngaged) ? 1.0 : 0.0; + } + + /// + /// Returns the currently stored heading offset in the atittude control pilot. + /// + /// Heading relative to the reference attitude, in degrees. + public double GetAttitudePilotHeading() + { + return fc.ap.relativeHPR.x; + } + + /// + /// Returns the currently stored pitch offset in the atittude control pilot. + /// + /// Pitch relative to the reference attitude, in degrees. + public double GetAttitudePilotPitch() + { + return fc.ap.relativeHPR.y; + } + + /// + /// Returns the currently stored roll offset in the atittude control pilot. + /// + /// Roll relative to the reference attitude, in degrees. + public double GetAttitudePilotRoll() + { + return fc.ap.relativeHPR.z; + } + + /// + /// Returns the current attitude reference mode. This value may be one of + /// the following: + /// + /// * 0 - Inertial Frame + /// * 1 - Orbital Prograde + /// * 2 - Orbital Prograde Horizontal + /// * 3 - Surface Prograde + /// * 4 - Surface Prograde Horizontal + /// * 5 - Surface North + /// * 6 - Target + /// * 7 - Target Prograde + /// * 8 - Target Orientation + /// * 9 - Maneuver Node + /// * 10 - Sun + /// * 11 - Up + /// + /// This reference mode does not indicate whether the attitude control pilot is + /// active, but it does indicate which reference attitude will take effect if the + /// pilot is engaged. Refer to the documentation for `fc.SetHeading()` for a + /// detailed explanation of the attitude references. + /// + /// One of the numbers listed in the summary. + public double GetAttitudeReference() + { + return (int)fc.ap.activeReference; + } + + /// + /// Returns 1 if the MAS maneuver autopilot is active, 0 if it is idle. + /// + /// + public double GetManeuverPilotActive() + { + return (fc.ap.maneuverPilotEngaged) ? 1.0 : 0.0; + } + + /// + /// Returns 1 if any MAS autopilot is active, 0 if all are idle. + /// + /// + public double GetPilotActive() + { + return (fc.ap.PilotActive()) ? 1.0 : 0.0; + } + + /// + /// Set the attitude pilot to the selected state. If another pilot is using + /// the attitude pilot (such as the launch pilot), switching off the attitude + /// pilot will disengage the other pilot as well. + /// + /// **CAUTION:** If the attitude system has not been initialized, this function + /// may select the orbital prograde + /// attitude, which may cause problems during launch or reentry. + /// + /// If true, engage the autopilot and restore the previous attitude. + /// Returns 1 if the autopilot is now on, 0 if it is now off. + public double SetAttitudePilotActive(bool active) + { + if (active != fc.ap.attitudePilotEngaged) + { + if (!active) + { + // Shutoff is easy. + fc.ap.DisengageAutopilots(); + } + else + { + fc.ap.ResumeAttitudePilot(); + } + } + + return (fc.ap.attitudePilotEngaged) ? 1.0 : 0.0; + } + + /// + /// Sets the maneuver autopilot state to active or not based on 'active'. + /// If no valid maneuver node exists, activating the maneuver pilot has no effect. + /// + /// If true, attempts to activate the maneuver autopilot; if false, deactivates it. + /// 1 if the maneuver autopilot is active, 0 if it is not active. + public double SetManeuverPilotActive(bool active) + { + if (active != fc.ap.maneuverPilotEngaged) + { + if (!active) + { + // Shutoff is easy. + fc.ap.DisengageAutopilots(); + } + else + { + // Engaging takes a couple of extra steps + fc.ap.EngageManeuverPilot(); + } + } + + return (fc.ap.maneuverPilotEngaged) ? 1.0 : 0.0; + } + + /// + /// Toggle the MAS attitude pilot. The exisiting reference attitude and heading, pitch, and roll + /// are restored. If another pilot is using + /// the attitude pilot (such as the launch pilot), switching off the attitude + /// pilot will disengage the other pilot as well. + /// + /// **CAUTION:** If the attitude system has not been initialized, it defaults to a orbital + /// prograde, which may not be desired. + /// + /// Returns 1 if the autopilot is now on, 0 if it is now off. + public double ToggleAttitudePilot() + { + if (fc.ap.attitudePilotEngaged) + { + fc.ap.DisengageAutopilots(); + } + else + { + fc.ap.ResumeAttitudePilot(); + } + + return (fc.ap.attitudePilotEngaged) ? 1.0 : 0.0; + } + + /// + /// Toggles the maneuver autopilot. + /// + /// 1 if the maneuver pilot is now active, 0 if it is now inactive. + public double ToggleManeuverPilot() + { + if (fc.ap.maneuverPilotEngaged) + { + fc.ap.DisengageAutopilots(); + } + else + { + fc.ap.EngageManeuverPilot(); + } + + return (fc.ap.maneuverPilotEngaged) ? 1.0 : 0.0; + } + #endregion + + /// + /// Information about the Celestial Bodies may be fetched using the + /// methods in this category. + /// + /// Most of these methods function in one of two ways: by name or + /// by number. By name means using the name of the body to select + /// it (eg, `fc.BodyMass("Jool")`). However, since strings are + /// slightly slower than numbers for lookups, these methods will + /// accept numbers. The number for a world can be fetched using + /// `fc.BodyIndex()`, `fc.CurrentBodyIndex()`, or `fc.TargetBodyIndex()`. + /// + #region Body + [MASProxy(Dependent = true)] + /// + /// Returns the surface area of the selected body. + /// + /// The name or index of the body of interest. + /// Surface area of the planet in m^2. + public double BodyArea(object id) + { + CelestialBody cb = SelectBody(id); + + return (cb == null) ? 0.0 : cb.SurfaceArea; + } + + [MASProxy(Dependent = true)] + /// + /// Returns the altitude of the top of the selected body's + /// atmosphere. If the body does not have an atmosphere, or + /// an invalid body is selected, 0 is returned. + /// + /// The name or index of the body of interest. + /// The altitude of the top of the atmosphere, in meters, or 0. + public double BodyAtmosphereTop(object id) + { + CelestialBody cb = SelectBody(id); + + double atmoTop = 0.0; + if (cb != null && cb.atmosphere) + { + atmoTop = cb.atmosphereDepth; + } + + return atmoTop; + } + + [MASProxy(Dependent = true)] + /// + /// Returns the name of the biome at the given location on the selected body. + /// + /// The name or index of the body of interest. + /// Latitude of the location of interest. + /// Longitude of the location of interest. + /// The name of the biome at the specified location, or an empty string. + public string BodyBiome(object id, double latitude, double longitude) + { + CelestialBody cb = SelectBody(id); + if (cb != null) + { + string biome = ScienceUtil.GetExperimentBiome(cb, latitude, longitude); + //ScienceUtil.GetBiomedisplayName(cb, biome); + // string biome = ScienceUtil.GetExperimentBiomeLocalized(cb, latitude, longitude); + if (ScienceUtil.BiomeIsUnlisted(cb, biome)) + { + // What causes this? And what action should I take? + Utility.LogWarning(this, "BodyBiome(): biome '{0}' is unlisted", biome); + } + return biome; + } + + return string.Empty; + } + + [MASProxy(Immutable = true)] + /// + /// The number of Celestial Bodies in the database. + /// + /// + public double BodyCount() + { + return FlightGlobals.Bodies.Count; + } + + [MASProxy(Dependent = true)] + /// + /// Returns the distance to the requested body, in meters. + /// + /// The name or index of the body of interest. + /// Distance in meters, or 0 if invalid. + public double BodyDistance(object id) + { + CelestialBody cb = SelectBody(id); + + return (cb != null) ? Vector3d.Distance(vessel.GetTransform().position, cb.GetTransform().position) : 0.0; + } + + [MASProxy(Dependent = true)] + /// + /// Returns the escape velocity of the body. + /// + /// The name or index of the body of interest. + /// Escape velocity in m/s, or 0 if the body was invalid. + public double BodyEscapeVelocity(object id) + { + CelestialBody cb = SelectBody(id); + + double escapeVelocity = 0.0; + if (cb != null) + { + escapeVelocity = Math.Sqrt(2.0 * cb.gravParameter / cb.Radius); + } + + return escapeVelocity; + } + + [MASProxy(Dependent = true)] + /// + /// Returns the acceleration from gravity as the surface + /// for the selected body. + /// + /// The name or index of the body of interest. + /// Acceleration in G's, or 0 if the body was invalid. + public double BodyGeeASL(object id) + { + CelestialBody cb = SelectBody(id); + + return (cb != null) ? cb.GeeASL : 0.0; + } + + [MASProxy(Dependent = true)] + /// + /// Returns the standard gravitational parameter of the body. + /// + /// The name or index of the body of interest. + /// GM in m^3/s^2, or 0 if the body is invalid. + public double BodyGM(object id) + { + CelestialBody cb = SelectBody(id); + + return (cb != null) ? cb.gravParameter : 0.0; + } + + [MASProxy(Dependent = true)] + /// + /// Indicates if the selected body has an atmosphere. + /// + /// The name or index of the body of interest. + /// 1 if the body has an atmosphere, 0 if it does not, or an invalid body was selected. + public double BodyHasAtmosphere(object id) + { + CelestialBody cb = SelectBody(id); + bool atmo = (cb != null) ? cb.atmosphere : false; + + return (atmo) ? 1.0 : 0.0; + } + + [MASProxy(Dependent = true)] + /// + /// Indicates if the selected body's atmosphere has oxygen. + /// + /// The name or index of the body of interest. + /// 1 if the body has an atmosphere that contains oxygen, 0 if it does not, or an invalid body was selected. + public double BodyHasOxygen(object id) + { + CelestialBody cb = SelectBody(id); + bool atmo = (cb != null) ? cb.atmosphere : false; + bool oxy = atmo && ((cb != null) ? cb.atmosphereContainsOxygen : false); + + return (oxy) ? 1.0 : 0.0; + } + + [MASProxy(Dependent = true)] + /// + /// Returns the numeric identifier for the named body. If the name is invalid + /// (no such body exists), returns -1. May also use the index, which is useful + /// for -1 and -2. + /// + /// The name of the body, eg. `"Kerbin"` or one of the indices (including -1 and -2). + /// An index from 0 to (number of Celestial Bodies - 1), or -1 if the named body was not found. + public double BodyIndex(object id) + { + string bodyName = string.Empty; + if (id is double) + { + CelestialBody cb = SelectBody(id); + if (cb != null) + { + bodyName = cb.bodyName; + } + } + else if (id is string) + { + bodyName = id as string; + } + + return (double)FlightGlobals.Bodies.FindIndex(x => x.bodyName == bodyName); + } + + [MASProxy(Dependent = true)] + /// + /// Returns 1 if the selected body is "Home" (Kerbin in un-modded KSP). + /// + /// The name or index of the body of interest. + /// 1 if the body is home, 0 otherwise. + public double BodyIsHome(object id) + { + CelestialBody cb = SelectBody(id); + if (cb != null && cb.GetName() == Planetarium.fetch.Home.GetName()) + { + return 1.0; + } + else + { + return 0.0; + } + } + + [MASProxy(Dependent = true)] + /// + /// Returns 0 if the selected body orbits the star; returns 1 if the + /// body is a moon of another body. + /// + /// The name or index of the body of interest. + /// 1 if the body is a moon, 0 if it is a planet. + public double BodyIsMoon(object id) + { + CelestialBody cb = SelectBody(id); + if (cb != null && cb.referenceBody != null && cb.referenceBody.GetName() != Planetarium.fetch.Sun.GetName()) + { + return 1.0; + } + else + { + return 0.0; + } + } + + [MASProxy(Dependent = true)] + /// + /// Returns the mass of the requested body. + /// + /// The name or index of the body of interest. + /// Mass in kg. + public double BodyMass(object id) + { + CelestialBody cb = SelectBody(id); + + return (cb != null) ? cb.Mass : 0.0; + } + + [MASProxy(Dependent = true)] + /// + /// Returns the number of worlds orbiting the selected body. If the body + /// is a planet, this is the number of moons. If the body is the Sun, this + /// number is the number of planets. + /// + /// The name or index of the body of interest. + /// The number of moons, or 0 if an invalid value was provided. + public double BodyMoonCount(object id) + { + CelestialBody cb = SelectBody(id); + if (cb != null && cb.orbitingBodies != null) + { + return cb.orbitingBodies.Count; + } + else + { + return 0.0; + } + } + + [MASProxy(Dependent = true)] + /// + /// Returns the numeric ID of the moon selected by moonIndex that orbits the body + /// selected by 'id'. If 'id' is the Sun, moonIndex selects the planet. + /// + /// The name or index of the body of interest. + /// The index of the moon to select, between 0 and 'fc.BodyMoonCount(id)' - 1. + /// Returns an index 0 or greater, or -1 for an invalid combination of 'id' and 'moonIndex'. + public double BodyMoonId(object id, double moonIndex) + { + int moonIdx = (int)moonIndex; + CelestialBody cb = SelectBody(id); + if (cb != null && cb.orbitingBodies != null && moonIdx >= 0 && moonIdx < cb.orbitingBodies.Count) + { + return (double)FlightGlobals.Bodies.FindIndex(x => x.bodyName == cb.orbitingBodies[moonIdx].bodyName); + } + else + { + return -1.0; + } + } + + [MASProxy(Dependent = true)] + /// + /// Returns the name of the requested body. While this method can be used + /// with a name for its parameter, that is kind of pointless. + /// + /// The name or index of the body of interest. + /// The name of the body, or an empty string if invalid. + public string BodyName(object id) + { + CelestialBody cb = SelectBody(id); + return (cb != null) ? cb.bodyName : string.Empty; + } + + [MASProxy(Dependent = true)] + /// + /// Returns the radius of the selected body. + /// + /// The name or index of the body of interest. + /// Radius in meters, or 0 if the body is invalid. + public double BodyRadius(object id) + { + CelestialBody cb = SelectBody(id); + + return (cb != null) ? cb.Radius : 0.0; + } + + [MASProxy(Dependent = true)] + /// + /// Returns the index of the parent of the selected body. Returns 0 (the Sun) + /// on an invalid id. + /// + /// The name or index of the body of interest. + /// Returns the index of the body that the current body orbits. + public double BodyParent(object id) + { + CelestialBody cb = SelectBody(id); + + if (cb != null && cb.referenceBody != null) + { + string bodyName = cb.referenceBody.bodyName; + + return (double)FlightGlobals.Bodies.FindIndex(x => x.bodyName == bodyName); + } + else + { + return 0.0; + } + } + + [MASProxy(Dependent = true)] + /// + /// Returns the rotation period of the body. If the body does not + /// rotate, 0 is returned. + /// + /// The name or index of the body of interest. + /// Rotation period in seconds, or 0. + public double BodyRotationPeriod(object id) + { + CelestialBody cb = SelectBody(id); + + return (cb != null && cb.rotates) ? cb.rotationPeriod : 0.0; + } + + [MASProxy(Dependent = true)] + /// + /// Returns the radius of the body's Sphere of Influence. + /// + /// The name or index of the body of interest. + /// SoI in meters + public double BodySoI(object id) + { + CelestialBody cb = SelectBody(id); + + return (cb != null) ? cb.sphereOfInfluence : 0.0; + } + + /// + /// Returns the longitude on the body that is directly below the sun (longitude of local noon). + /// + /// The name or index of the body of interest. + /// Longitude of local noon, or 0 if it could not be determined. + public double BodySunLongitude(object id) + { + CelestialBody cb = SelectBody(id); + + if (cb != null) + { + CelestialBody sun = Planetarium.fetch.Sun; + + Vector3d sunDirection = sun.position - cb.position; + + if (sunDirection.sqrMagnitude > 0.0) + { + sunDirection.Normalize(); + + return Utility.NormalizeLongitude(cb.GetLongitude(cb.position + sunDirection * cb.Radius)); + } + } + + return 0.0; + } + + [MASProxy(Dependent = true)] + /// + /// Returns the temperature of the body at sea level. + /// + /// The name or index of the body of interest. + /// If true, temperature is in Kelvin; if false, temperature is in Celsius. + /// Surface temperature in K or C; 0 if the selected object was invalid + public double BodySurfaceTemp(object id, bool useKelvin) + { + CelestialBody cb = SelectBody(id); + + double temperature = 0.0; + + if (cb != null) + { + temperature = cb.atmosphereTemperatureSeaLevel; + if (!useKelvin) + { + temperature += KelvinToCelsius; + } + } + + return temperature; + } + + [MASProxy(Dependent = true)] + /// + /// Returns the semi-major axis of a synchronous orbit with the selected body. + /// When a vessel's SMA matches the sync orbit SMA, a craft is in a synchronous + /// orbit. + /// + /// The name or index of the body of interest. + /// SMA in meters, or 0 if the body is invalid, or the synchronous orbit + /// is out side the body's SoI. + public double BodySyncOrbitSMA(object id) + { + CelestialBody cb = SelectBody(id); + + double syncOrbit = 0.0; + + if (cb != null) + { + syncOrbit = Math.Pow(cb.gravParameter * Math.Pow(cb.rotationPeriod / (2.0 * Math.PI), 2.0), 1.0 / 3.0); + + if (syncOrbit > cb.sphereOfInfluence) + { + syncOrbit = 0.0; + } + } + + return syncOrbit; + } + + [MASProxy(Dependent = true)] + /// + /// Returns the speed of a synchronous orbit. Provided an orbit is + /// perfectly circular, an orbit that has this velocity will be + /// synchronous. + /// + /// The name or index of the body of interest. + /// Velocity in m/s, or 0 if there is no synchronous orbit. + public double BodySyncOrbitVelocity(object id) + { + CelestialBody cb = SelectBody(id); + + double syncOrbitPeriod = 0.0; + + if (cb != null && cb.rotates) + { + double syncOrbit = Math.Pow(cb.gravParameter * Math.Pow(cb.rotationPeriod / (2.0 * Math.PI), 2.0), 1.0 / 3.0); + + if (syncOrbit > cb.sphereOfInfluence) + { + syncOrbit = 0.0; + } + + // Determine the circumference of the orbit. + syncOrbit *= 2.0 * Math.PI; + + syncOrbitPeriod = syncOrbit / cb.rotationPeriod; + } + + return syncOrbitPeriod; + } + + [MASProxy(Dependent = true)] + /// + /// Returns the terrain height at a given latitude and longitude relative to the + /// planet's datum (sea level or equivalent). Height in meters. + /// + /// The name or index of the body of interest. + /// Latitude of the location of interest in degrees. + /// Longitude of the location of interest in degrees. + /// Terrain height in meters. Will return negative values for locations below sea level. + public double BodyTerrainHeight(object id, double latitude, double longitude) + { + CelestialBody cb = SelectBody(id); + if (cb != null) + { + if (cb.pqsController != null) + { + return cb.pqsController.GetSurfaceHeight(QuaternionD.AngleAxis(longitude, Vector3d.down) * QuaternionD.AngleAxis(latitude, Vector3d.forward) * Vector3d.right) - cb.Radius; + } + else + { + return cb.TerrainAltitude(latitude, longitude, true); + } + } + else + { + return 0.0; + } + } + + [MASProxy(Dependent = true)] + /// + /// Returns an estimate of the terrain slope at a given latitude and longitude. + /// If the location is beneath the ocean, it provides the slope of the ocean floor. + /// Values near the poles may be very inaccurate. + /// + /// The name or index of the body of interest. + /// Latitude of the location of interest in degrees. + /// Longitude of the location of interest in degrees. + /// Slope in degrees. + public double BodyTerrainSlope(object id, double latitude, double longitude) + { + CelestialBody cb = SelectBody(id); + if (cb != null) + { + double displacementInMeters = 5.0; + + // We compute a simple normal for the point of interest by sampling + // altitudes approximately 5m meters away from the location in the four + // cardinal directions and computing the cross product of the two vectors + // we generate. + + double displacementInDegreesLatitude = 360.0 * displacementInMeters / (2.0 * Math.PI * cb.Radius); + // Clamp latitude + latitude = Math.Max(-90.0 + (displacementInDegreesLatitude * 1.5), Math.Min(90.0 - (displacementInDegreesLatitude * 1.5), latitude)); + // Account for longitudinal compression. + double displacementInDegreesLongitude = displacementInDegreesLatitude / Math.Cos(latitude * Mathf.Deg2Rad); + + PQS pqs = cb.pqsController; + if (pqs != null) + { + double westAltitude = pqs.GetSurfaceHeight(QuaternionD.AngleAxis(longitude - displacementInDegreesLongitude, Vector3d.down) * QuaternionD.AngleAxis(latitude, Vector3d.forward) * Vector3d.right); + double eastAltitude = pqs.GetSurfaceHeight(QuaternionD.AngleAxis(longitude + displacementInDegreesLongitude, Vector3d.down) * QuaternionD.AngleAxis(latitude, Vector3d.forward) * Vector3d.right); + Vector3d westEastSlope = new Vector3d(displacementInMeters * 2.0, 0.0, eastAltitude - westAltitude); + westEastSlope.Normalize(); + + double southAltitude = pqs.GetSurfaceHeight(QuaternionD.AngleAxis(longitude, Vector3d.down) * QuaternionD.AngleAxis(latitude - displacementInDegreesLatitude, Vector3d.forward) * Vector3d.right); + double northAltitude = pqs.GetSurfaceHeight(QuaternionD.AngleAxis(longitude, Vector3d.down) * QuaternionD.AngleAxis(latitude + displacementInDegreesLatitude, Vector3d.forward) * Vector3d.right); + Vector3d southNorthSlope = new Vector3d(0.0, displacementInMeters * 2.0, northAltitude - southAltitude); + southNorthSlope.Normalize(); + + Vector3d normal = Vector3d.Cross(westEastSlope, southNorthSlope); + + return Vector3d.Angle(normal, Vector3d.forward); + } + else + { + // No PQS controller? Have to use TerrainAltitude(), which seems to report bogus values. + double westAltitude = cb.TerrainAltitude(longitude - displacementInDegreesLongitude, latitude, true); + double eastAltitude = cb.TerrainAltitude(longitude + displacementInDegreesLongitude, latitude, true); + Vector3d westEastSlope = new Vector3d(displacementInMeters * 2.0, 0.0, eastAltitude - westAltitude); + westEastSlope.Normalize(); + + double southAltitude = cb.TerrainAltitude(longitude, latitude - displacementInDegreesLatitude, true); + double northAltitude = cb.TerrainAltitude(longitude, latitude + displacementInDegreesLatitude, true); + Vector3d southNorthSlope = new Vector3d(0.0, displacementInMeters * 2.0, northAltitude - southAltitude); + southNorthSlope.Normalize(); + + Vector3d normal = Vector3d.Cross(westEastSlope, southNorthSlope); + + return Vector3d.Angle(normal, Vector3d.forward); + } + } + else + { + return 0.0; + } + } + + /// + /// Returns the index of the body currently being orbited, for use as an input for other body query functions.. + /// + /// The index of the current body, used as the 'id' parameter in other body query functions. + public double CurrentBodyIndex() + { + string bodyName = vc.mainBody.bodyName; + return (double)FlightGlobals.Bodies.FindIndex(x => x.bodyName == bodyName); + } + + /// + /// Set the vessel's target to the selected body. + /// + /// The name or index of the body of interest. + /// 1 if the command succeeds, 0 if an invalid body name or index was provided. + public double SetBodyTarget(object id) + { + CelestialBody cb = SelectBody(id); + if (cb != null) + { + FlightGlobals.fetch.SetVesselTarget(cb); + + return 1.0; + } + else + { + return 0.0; + } + } + + /// + /// Returns the body index of the current target, provided the target is a celestial body. + /// If there is no target, or the current target is not a body, returns -1. + /// + /// The index of the targeted body, or -1. + public double BodyTargetIndex() + { + if (vc.targetType == MASVesselComputer.TargetType.CelestialBody) + { + string bodyName = (vc.activeTarget as CelestialBody).bodyName; + return (double)FlightGlobals.Bodies.FindIndex(x => x.bodyName == bodyName); + } + return -1.0; + } + + #endregion + + /// + /// Variables related to a vessel's brakes and air brakes are in this category. + /// + #region Brakes + /// + /// Returns 1 if the brakes action group has at least one action assigned to it. + /// + /// + public double BrakesHasActions() + { + return (vc.GroupHasActions(KSPActionGroup.Brakes)) ? 1.0 : 0.0; + } + + /// + /// Returns the number of air brakes installed on the vessel. + /// + /// Air brakes are defined as parts that have ModuleAeroSurface installed. + /// + /// 0 or more. + public double GetAirBrakeCount() + { + int i = 0; + foreach (var bob in vc.moduleAirBrake) + { + Utility.LogMessage(this, "ab{1}: deploy = {0}", + bob.deploy, + i + ); + } + return vc.moduleAirBrake.Length; + } + + /// + /// Returns 1 if any air brakes are deployed. Returns 0 if no air brakes are deployed. + /// + /// A future update *may* return a number between 0 and 1 to report the amount of + /// brake deployment. + /// + /// 1 for air brakes deployed, 0 for no air brakes deployed, or no air brakes. + public double GetAirBrakes() + { + for (int i = vc.moduleAirBrake.Length - 1; i >= 0; --i) + { + if (vc.moduleAirBrake[i].deploy) + { + return 1.0; + } + } + + return 0.0; + } + + /// + /// Returns the average brake force setting of all wheel brakes installed on the vessel. + /// + /// The brake force as a percentage of maximum, in the range of [0, 2]. + public double GetBrakeForce() + { + int numBrakes = vc.moduleBrakes.Length; + if (numBrakes > 0) + { + float netBrakeForce = 0.0f; + for (int i = 0; i < numBrakes; ++i) + { + netBrakeForce += vc.moduleBrakes[i].brakeTweakable; + } + return netBrakeForce / (float)(100 * numBrakes); + } + else + { + return 0.0; + } + } + + /// + /// Returns the current state of the Brakes action group + /// + /// 1 if the brake action group is active, 0 otherwise. + public double GetBrakes() + { + return (vessel.ActionGroups[KSPActionGroup.Brakes]) ? 1.0 : 0.0; + } + + /// + /// Sets the brake force setting of all wheel brakes installed on the vessel. + /// + /// The new brake force setting, in the range of 0 to 2. + /// The brake force as a percentage of maximum, in the range of [0, 2]. + public double SetBrakeForce(double force) + { + int numBrakes = vc.moduleBrakes.Length; + if (numBrakes > 0) + { + float clampedForce = Mathf.Clamp((float)force, 0.0f, 2.0f); + float newForce = clampedForce * 100.0f; + + for (int i = 0; i < numBrakes; ++i) + { + vc.moduleBrakes[i].brakeTweakable = newForce; + } + + return clampedForce; + } + else + { + return 0.0; + } + } + + /// + /// Set the state of the air brakes. + /// + /// 1 if air brakes are now deployed, 0 if they are now retracted or if there are no air brakes. + public double SetAirBrakes(bool active) + { + int numBrakes = vc.moduleAirBrake.Length; + if (numBrakes > 0) + { + for (int i = 0; i < numBrakes; ++i) + { + vc.moduleAirBrake[i].deploy = active; + } + + return active ? 1.0 : 0.0; + } + else + { + return 0.0; + } + } + + /// + /// Set the brake action group to the specified state. + /// + /// Sets the state of the brakes + /// 1 if the brake action group is active, 0 otherwise. + public double SetBrakes(bool active) + { + vessel.ActionGroups.SetGroup(KSPActionGroup.Brakes, active); + return (active) ? 1.0 : 0.0; + } + + /// + /// Toggle the state of the air brakes. + /// + /// 1 if air brakes are now deployed, 0 if they are now retracted or if there are no air brakes. + public double ToggleAirBrakes() + { + int numBrakes = vc.moduleAirBrake.Length; + bool nowDeployed = false; + if (numBrakes > 0) + { + nowDeployed = !vc.moduleAirBrake[0].deploy; + for (int i = 0; i < numBrakes; ++i) + { + vc.moduleAirBrake[i].deploy = nowDeployed; + } + } + + return nowDeployed ? 1.0 : 0.0; + } + + /// + /// Toggle the state of the brake action group. + /// + /// 1 if the brake action group is active, 0 otherwise. + public double ToggleBrakes() + { + vessel.ActionGroups.ToggleGroup(KSPActionGroup.Brakes); + return (vessel.ActionGroups[KSPActionGroup.Brakes]) ? 1.0 : 0.0; + } + #endregion + + /// + /// The methods in this section are focused around controlling external + /// cameras installed on the vessel. They provide an interface between + /// the MASCamera part module and CAMERA nodes in a monitor page. + /// + #region Cameras + + /// + /// Returns the name of the camera (if any) attached to the current reference docking port. + /// If the reference transform is not a docking port, but there is a primary docking port on + /// the vessel, the primary docking port's camera name is returned. + /// If there is no camera on the reference docking port, or no docking ports, an empty string is returned. + /// + /// The name of the MASCamera on the reference docking port, or an empty string. + public string ActiveDockingPortCamera() + { + if (vc.dockingCamera != null) + { + return vc.dockingCamera.cameraName; + } + + return string.Empty; + } + + /// + /// Returns the index of the camera (if any) attached to the current reference docking port. + /// If the reference transform is not a docking port, but there is a primary docking port on + /// the vessel, the primary docking port's camera index is returned. + /// If there is no camera on the reference docking port, or no docking ports, -1 is returned. + /// + /// The index between 0 and `fc.CameraCount()` - 1, or -1 if there is no camera on the current docking port, or a docking port camera is not active. + public double ActiveDockingPortCameraIndex() + { + if (vc.dockingCamera != null) + { + int idx = Array.FindIndex(vc.moduleCamera, x => x == vc.dockingCamera); + return (double)idx; + } + + return -1.0; + } + + /// + /// Adjusts the field of view setting on the selected camera. The change in `deltaFoV` is clamped + /// to the selected camera's range. A negative value reduces the FoV, which is equivalent to zooming + /// in; a positive value increases the FoV (zooms out). + /// + /// A number between 0 and `fc.CameraCount()` - 1. + /// The number of degrees to add or subtract from the current FoV. + /// The new field of view setting, or 0 if an invalid index was supplied. + public double AddFoV(double index, double deltaFoV) + { + double pan = 0.0; + + int i = (int)index; + if (i >= 0 && i < vc.moduleCamera.Length) + { + pan = vc.moduleCamera[i].AddFoV((float)deltaFoV); + } + + return pan; + } + + /// + /// Adjusts the pan setting on the selected camera. `deltaPan` is clamped to the + /// pan range of the selected camera. A negative value pans the camera left, while a + /// positive value pans right. + /// + /// A number between 0 and `fc.CameraCount()` - 1. + /// The number of degrees to increase or decrease the pan position. + /// The new pan setting, or 0 if an invalid index was supplied. + public double AddPan(double index, double deltaPan) + { + double pan = 0.0; + + int i = (int)index; + if (i >= 0 && i < vc.moduleCamera.Length) + { + pan = vc.moduleCamera[i].AddPan((float)deltaPan); + } + + return pan; + } + + /// + /// Adjusts the tilt setting on the selected camera. `deltaTilt` is clamped to the + /// tilt range of the selected camera. A negative value tilts the camera up, while a + /// positive value tils the camera down. **Verify these directions** + /// + /// A number between 0 and `fc.CameraCount()` - 1. + /// The number of degrees to increase or decrease the pan position. + /// The new tilt setting, or 0 if an invalid index was supplied. + public double AddTilt(double index, double deltaTilt) + { + double tilt = 0.0; + + int i = (int)index; + if (i >= 0 && i < vc.moduleCamera.Length) + { + tilt = vc.moduleCamera[i].AddTilt((float)deltaTilt); + } + + return tilt; + } + + /// + /// Returns 1 if the camera is capable of panning left/right. + /// + /// A number between 0 and `fc.CameraCount()` - 1. + /// 1 if the camera can pan, 0 if it cannot pan or an invalid `index` is provided. + public double GetCameraCanPan(double index) + { + int i = (int)index; + if (i >= 0 && i < vc.moduleCamera.Length) + { + return (vc.moduleCamera[i].panRange.x == vc.moduleCamera[i].panRange.y) ? 0.0 : 1.0; + } + return 0.0; + } + + /// + /// Returns 1 if the camera is capable of tilting up/down. + /// + /// A number between 0 and `fc.CameraCount()` - 1. + /// 1 if the camera can tilt, 0 if it cannot tilt or an invalid `index` is provided. + public double GetCameraCanTilt(double index) + { + int i = (int)index; + if (i >= 0 && i < vc.moduleCamera.Length) + { + return (vc.moduleCamera[i].tiltRange.x == vc.moduleCamera[i].tiltRange.y) ? 0.0 : 1.0; + } + return 0.0; + } + + /// + /// Returns 1 if the camera is capable of zooming. + /// + /// A number between 0 and `fc.CameraCount()` - 1. + /// 1 if the camera can zoom, 0 if it cannot zoom or an invalid `index` is provided. + public double GetCameraCanZoom(double index) + { + int i = (int)index; + if (i >= 0 && i < vc.moduleCamera.Length) + { + return (vc.moduleCamera[i].fovRange.x == vc.moduleCamera[i].fovRange.y) ? 0.0 : 1.0; + } + return 0.0; + } + + /// + /// Returns a count of the valid MASCamera modules found on this vessel. + /// + /// The number of valid MASCamera modules installed on this vessel. + public double CameraCount() + { + return vc.moduleCamera.Length; + } + + /// + /// Returns 1 if the selected camera is damaged, 0 otherwise. Deployable cameras may be damaged. + /// + /// A number between 0 and `fc.CameraCount()` - 1. + /// + public double GetCameraDamaged(double index) + { + int i = (int)index; + if (i >= 0 && i < vc.moduleCamera.Length) + { + return vc.moduleCamera[i].IsDamaged() ? 1.0 : 0.0; + } + + return 0.0; + } + + /// + /// Returns 1 if the selected camera is deployable, 0 otherwise. + /// + /// A number between 0 and `fc.CameraCount()` - 1. + /// + public double GetCameraDeployable(double index) + { + int i = (int)index; + if (i >= 0 && i < vc.moduleCamera.Length) + { + return vc.moduleCamera[i].GetDeployable() ? 1.0 : 0.0; + } + + return 0.0; + } + + /// + /// Returns 1 if the selected camera is deployed, 0 otherwise. + /// + /// A number between 0 and `fc.CameraCount()` - 1. + /// + public double GetCameraDeployed(double index) + { + int i = (int)index; + if (i >= 0 && i < vc.moduleCamera.Length) + { + return vc.moduleCamera[i].IsDeployed() ? 1.0 : 0.0; + } + + return 0.0; + } + + /// + /// Returns the maximum field of view supported by the selected camera. + /// + /// A number between 0 and `fc.CameraCount()` - 1. + /// The maximum field of view in degrees, or 0 for an invalid camera index. + public double GetCameraMaxFoV(double index) + { + int i = (int)index; + if (i >= 0 && i < vc.moduleCamera.Length) + { + return vc.moduleCamera[i].fovRange.y; + } + return 0.0; + } + + /// + /// Returns the maximum pan angle supported by the selected camera. + /// + /// A number between 0 and `fc.CameraCount()` - 1. + /// The maximum pan in degrees, or 0 for an invalid camera index. + public double GetCameraMaxPan(double index) + { + int i = (int)index; + if (i >= 0 && i < vc.moduleCamera.Length) + { + return vc.moduleCamera[i].GetPanRange().y; + } + return 0.0; + } + + /// + /// Returns the maximum tilt angle supported by the selected camera. + /// + /// A number between 0 and `fc.CameraCount()` - 1. + /// The maximum tilt in degrees, or 0 for an invalid camera index. + public double GetCameraMaxTilt(double index) + { + int i = (int)index; + if (i >= 0 && i < vc.moduleCamera.Length) + { + return vc.moduleCamera[i].GetTiltRange().y; + } + return 0.0; + } + + /// + /// Returns the minimum field of view supported by the selected camera. + /// + /// A number between 0 and `fc.CameraCount()` - 1. + /// The minimum field of view in degrees, or 0 for an invalid camera index. + public double GetCameraMinFoV(double index) + { + int i = (int)index; + if (i >= 0 && i < vc.moduleCamera.Length) + { + return vc.moduleCamera[i].fovRange.x; + } + return 0.0; + } + + /// + /// Returns the minimum pan angle supported by the selected camera. + /// + /// A number between 0 and `fc.CameraCount()` - 1. + /// The minimum pan in degrees, or 0 for an invalid camera index. + public double GetCameraMinPan(double index) + { + int i = (int)index; + if (i >= 0 && i < vc.moduleCamera.Length) + { + return vc.moduleCamera[i].GetPanRange().x; + } + return 0.0; + } + + /// + /// Returns the minimum tilt angle supported by the selected camera. + /// + /// A number between 0 and `fc.CameraCount()` - 1. + /// The minimum tilt in degrees, or 0 for an invalid camera index. + public double GetCameraMinTilt(double index) + { + int i = (int)index; + if (i >= 0 && i < vc.moduleCamera.Length) + { + return vc.moduleCamera[i].GetTiltRange().x; + } + return 0.0; + } + + /// + /// Returns the id number of the currently-active mode on the MASCamera selected by `index`. + /// + /// A number between 0 and `fc.CameraCount()` - 1. + /// The number of the modes (between 0 and fc.GetCameraModeCount(index)-1), or 0 if an invalid camera was selected. + public double GetCameraMode(double index) + { + int i = (int)index; + if (i >= 0 && i < vc.moduleCamera.Length) + { + return vc.moduleCamera[i].GetMode(); + } + + return 0.0; + } + + /// + /// Returns the number of modes available to the MASCamera selected by `index`. + /// + /// A number between 0 and `fc.CameraCount()` - 1. + /// The number of modes (1 or higher), or 0 if an invalid camera was selected. + public double GetCameraModeCount(double index) + { + int i = (int)index; + if (i >= 0 && i < vc.moduleCamera.Length) + { + return vc.moduleCamera[i].GetModeCount(); + } + + return 0.0; + } + + /// + /// Returns the id number of the currently-active mode on the MASCamera selected by `index`. + /// + /// A number between 0 and `fc.CameraCount()` - 1. + /// A number between 0 and `fc.GetCameraModeCount(index)` - 1. + /// The name of the selected mode, or an empty string if an invalid camera or mode was selected. + public string GetCameraModeName(double index, double mode) + { + int i = (int)index; + int whichMode = (int)mode; + if (i >= 0 && i < vc.moduleCamera.Length) + { + return vc.moduleCamera[i].GetModeName(whichMode); + } + + return string.Empty; + } + + /// + /// Returns -1 if the selected camera is retracting, +1 if it is extending, + /// or 0 for any other situation (including non-deployable cameras). + /// + /// A number between 0 and `fc.CameraCount()` - 1. + /// -1, 0, or +1. + public double GetCameraMoving(double index) + { + int i = (int)index; + if (i >= 0 && i < vc.moduleCamera.Length) + { + return vc.moduleCamera[i].IsMoving(); + } + + return 0.0; + } + + /// + /// Returns the name of the camera selected by `index`, or an empty string + /// if the index is invalid. + /// + /// A number between 0 and `fc.CameraCount()` - 1. + /// The name of the camera, or an empty string. + public string GetCameraName(double index) + { + int i = (int)index; + if (i >= 0 && i < vc.moduleCamera.Length) + { + return vc.moduleCamera[i].cameraName; + } + return string.Empty; + } + + /// + /// Retrieve the current field of view setting on the selected camera. + /// + /// A number between 0 and `fc.CameraCount()` - 1. + /// The current field of view setting, or 0 if an invalid index was supplied. + public double GetFoV(double index) + { + double fov = 0.0; + + int i = (int)index; + if (i >= 0 && i < vc.moduleCamera.Length) + { + fov = vc.moduleCamera[i].currentFov; + } + + return fov; + } + + /// + /// Retrieve the current pan setting on the selected camera. + /// + /// A number between 0 and `fc.CameraCount()` - 1. + /// The current pan setting, or 0 if an invalid index was supplied. + public double GetPan(double index) + { + double pan = 0.0; + + int i = (int)index; + if (i >= 0 && i < vc.moduleCamera.Length) + { + pan = vc.moduleCamera[i].GetPan(); + } + + return pan; + } + + /// + /// Retrieve the current tilt setting on the selected camera. + /// + /// A number between 0 and `fc.CameraCount()` - 1. + /// The current tilt setting, or 0 if an invalid index was supplied. + public double GetTilt(double index) + { + double tilt = 0.0; + + int i = (int)index; + if (i >= 0 && i < vc.moduleCamera.Length) + { + tilt = vc.moduleCamera[i].GetTilt(); + } + + return tilt; + } + + /// + /// Sets the selected camera's pan and tilt values to 0. + /// + /// A number between 0 and `fc.CameraCount()` - 1. + /// 1 if the camera is seeking home, 0 if an invalid camera was selected. + public double SeekCameraHome(double index) + { + int i = (int)index; + if (i >= 0 && i < vc.moduleCamera.Length) + { + vc.moduleCamera[i].SetTilt(0.0f); + vc.moduleCamera[i].SetPan(0.0f); + + return 1.0; + } + + return 0.0; + } + + /// + /// Extends or retracts a deployable camera. Has + /// no effect on non-deployable cameras. + /// + /// A number between 0 and `fc.CameraCount()` - 1. + /// true to deploy the camera, false to retract it. + /// 1 if the camera deploys / undeploys, 0 otherwise. + public double SetCameraDeployment(double index, bool deploy) + { + int i = (int)index; + if (i >= 0 && i < vc.moduleCamera.Length && vc.moduleCamera[i].IsDeployed() != deploy) + { + return vc.moduleCamera[i].ToggleDeployment() ? 1.0 : 0.0; + } + + return 0.0; + } + + /// + /// Returns the id number of the currently-active mode on the MASCamera selected by `index`. + /// + /// A number between 0 and `fc.CameraCount()` - 1. + /// A number between 0 and `fc.GetCameraModeCount(index)` - 1. + /// The mode that was selected, or 0 if an invalid camera was selected. + public double SetCameraMode(double index, double mode) + { + int i = (int)index; + int newMode = (int)mode; + if (i >= 0 && i < vc.moduleCamera.Length) + { + return vc.moduleCamera[i].SetMode(newMode); + } + + return 0.0; + } + + /// + /// Adjusts the field of view setting on the selected camera. `newFoV` is clamped to + /// the FoV range. + /// + /// A number between 0 and `fc.CameraCount()` - 1. + /// The new field of view of the selected camera. + /// The new field of view setting, or 0 if an invalid index was supplied. + public double SetFoV(double index, double newFoV) + { + double fov = 0.0; + + int i = (int)index; + if (i >= 0 && i < vc.moduleCamera.Length) + { + fov = vc.moduleCamera[i].SetFoV((float)newFoV); + } + + return fov; + } + + /// + /// Adjusts the pan setting on the selected camera. `newPan` is clamped to + /// the pan range. + /// + /// A number between 0 and `fc.CameraCount()` - 1. + /// The new pan position of the camera. Use 0 to send it to the camera's home position. + /// The new pan setting, or 0 if an invalid index was supplied. + public double SetPan(double index, double newPan) + { + double pan = 0.0; + + int i = (int)index; + if (i >= 0 && i < vc.moduleCamera.Length) + { + pan = vc.moduleCamera[i].SetPan((float)newPan); + } + + return pan; + } + + /// + /// Adjusts the tilt setting on the selected camera. `newTilt` is clamped + /// to the camera's tilt range. + /// + /// A number between 0 and `fc.CameraCount()` - 1. + /// The new tilt position for the camera. Use 0 to send it to the home position. + /// The new tilt setting, or 0 if an invalid index was supplied. + public double SetTilt(double index, double newTilt) + { + double tilt = 0.0; + + int i = (int)index; + if (i >= 0 && i < vc.moduleCamera.Length) + { + tilt = vc.moduleCamera[i].SetTilt((float)newTilt); + } + + return tilt; + } + + /// + /// Toggles a deployable camera (retracts it if extended, extends it if retracted). Has + /// no effect on non-deployable cameras. + /// + /// A number between 0 and `fc.CameraCount()` - 1. + /// 1 if the camera deploys / undeploys, 0 otherwise. + public double ToggleCameraDeployment(double index) + { + int i = (int)index; + if (i >= 0 && i < vc.moduleCamera.Length) + { + return vc.moduleCamera[i].ToggleDeployment() ? 1.0 : 0.0; + } + + return 0.0; + } + #endregion + + /// + /// The methods in this section provide information on cargo bays, including the + /// number of such bays and their deployment state. There are also methods to + /// open and close such bays. + /// + /// Note that, for the purpose of this section, cargo bays are defined as parts + /// that use ModuleAnimateGeneric to control access to the cargo bay. The + /// ModuleServiceModule introduced for the KSP Making History expansion is not + /// counted, since it does not provide a method that MAS can use to deploy the + /// covers. + /// + #region Cargo Bay + + /// + /// Returns a count of the number of controllable cargo bays on the vessel. + /// + /// The number of controllable cargo bays on the vessel. + public double CargoBayCount() + { + return vc.moduleCargoBay.Length; + } + + /// + /// Returns -1 if any cargo bay doors are closing, +1 if any are opening, or + /// 0 if none are moving. + /// + /// -1, 0, or +1. + public double CargoBayMoving() + { + return vc.cargoBayDirection; + } + + /// + /// Returns a number representing the average position of cargo bay doors. + /// + /// * 0 - No cargo bays, or all cargo bays are closed. + /// * 1 - All cargo bays open. + /// + /// If the cargo bays are moving, a number between 0 and 1 is returned. + /// + /// A number between 0 and 1 as described in the summary. + public double CargoBayPosition() + { + return vc.cargoBayPosition; + } + + /// + /// Open or close cargo bays. + /// Will not affect any cargo bays that are already in motion. + /// + /// 1 if at least one cargo bay is now opening or closing, 0 otherwise. + public double SetCargoBay(bool open) + { + bool anyMoved = false; + + for (int i = vc.moduleCargoBay.Length - 1; i >= 0; --i) + { + ModuleCargoBay me = vc.moduleCargoBay[i]; + PartModule deployer = me.part.Modules[me.DeployModuleIndex]; + if (deployer is ModuleAnimateGeneric) + { + ModuleAnimateGeneric mag = deployer as ModuleAnimateGeneric; + if (mag.CanMove && open != mag.Extended()) + { + mag.Toggle(); + anyMoved = true; + } + } + } + + return (anyMoved) ? 1.0 : 0.0; + } + + /// + /// Opens closed cargo bays, closes open cargo bays. Will not try to toggle any cargo bays + /// that are already in motion. + /// + /// 1 if at least one cargo bay is now moving, 0 otherwise. + public double ToggleCargoBay() + { + bool anyToggled = false; + + for (int i = vc.moduleCargoBay.Length - 1; i >= 0; --i) + { + ModuleCargoBay me = vc.moduleCargoBay[i]; + PartModule deployer = me.part.Modules[me.DeployModuleIndex]; + if (deployer is ModuleAnimateGeneric) + { + ModuleAnimateGeneric mag = deployer as ModuleAnimateGeneric; + if (mag.CanMove) + { + mag.Toggle(); + anyToggled = true; + } + } + } + + return (anyToggled) ? 1.0 : 0.0; + } + #endregion + + /// + /// The Color Changer category controls the ModuleColorChanger module on parts. This module + /// is most commonly used to toggle emissives representing portholes and windows on occupied parts, + /// as well as the charring effect on heat shields. + /// + /// There are two groups of functions in this category. Those functions that contain the + /// name `PodColorChanger` only affect the current command pod / part. This allows external + /// cockpit glows to be coordinated with interior lighting, for instance. The other group of + /// functions contain `ColorChanger` without the `Pod` prefix. Those functions are used to control + /// all ModuleColorChanger installations on the current vessel. + /// + /// The `ColorChanger` functions may return inconsistent results if modules are controlled through + /// other interfaces, such as the `PodColorChanger` functions or action groups. + /// + /// The Color Changer category only interacts with those modules that have toggleInFlight and toggleAction set + /// to true. + /// + #region Color Changer + + /// + /// Returns the total number of ModuleColorChanger installed on the vessel. + /// + /// An integer 0 or larger. + public double ColorChangerCount() + { + return vc.moduleColorChanger.Length; + } + + /// + /// Returns the module ID for the selected color changer module. + /// + /// Returns an empty string if an invalid `ccId` is provided. + /// + /// An integer between 0 and `fc.ColorChangerCount()` - 1. + /// The `moduleID` field of the selected ModuleColorChanger, or an empty string.. + public string ColorChangerId(double ccId) + { + int id = (int)ccId; + if (id >= 0 && id < vc.moduleColorChanger.Length) + { + return vc.moduleColorChanger[id].moduleID; + } + + return string.Empty; + } + + /// + /// Returns the current state of the vessel color changer modules. + /// + /// 1 if the color changers are on, 0 if they are off, or there are no color changer modules. + public double GetColorChanger() + { + if (vc.moduleColorChanger.Length > 0) + { + return (vc.moduleColorChanger[0].animState) ? 1.0 : 0.0; + } + return 0.0; + } + + /// + /// Returns the current state of the current part's color changer module. + /// + /// 1 if the color changer is on, 0 if it is off, or there is no color changer module. + public double GetPodColorChanger() + { + if (fc.colorChangerModule != null) + { + return (fc.colorChangerModule.animState) ? 1.0 : 0.0; + } + return 0.0; + } + + /// + /// Returns 1 if the pod's color changer module can currently be changed. + /// Returns 0 if it cannot, or there is no color changer module. + /// + /// + public double PodColorChangerCanChange() + { + return (fc.colorChangerModule != null && fc.colorChangerModule.CanMove) ? 1.0 : 0.0; + } + + [MASProxy(Immutable = true)] + /// + /// Returns 1 if the current IVA has a color changer module. + /// + /// 1 or 0. + public double PodColorChangerExists() + { + return (fc.colorChangerModule != null) ? 1.0 : 0.0; + } + + /// + /// Set the state of all color changer modules. + /// + /// Some color changers may not be able to update under some circumstances, + /// so this function could return 0 with a valid color changer. + /// + /// true to switch on the color changers, false to switch them off. + /// 1 if any color changers were updated. + public double SetColorChanger(bool newState) + { + bool anyUpdated = false; + + for (int i = vc.moduleColorChanger.Length - 1; i >= 0; --i) + { + if (vc.moduleColorChanger[i].CanMove && vc.moduleColorChanger[i].animState != newState) + { + vc.moduleColorChanger[i].ToggleEvent(); + anyUpdated = true; + } + } + + return anyUpdated ? 1.0 : 0.0; + } + + /// + /// Set the state of the pod color changer. + /// + /// Some color changers may not be able to update under some circumstances, + /// so this function could return 0 with a valid color changer. Query + /// `fc.PodColorChangerCanChange()` to determine in advance if the color + /// changer is able to be updated. + /// + /// true to switch on the color changer, false to switch it off. + /// 1 if the color changer has been updated, 0 if did not change, or it cannot currently change, or there is no color changer module. + public double SetPodColorChanger(bool newState) + { + if (fc.colorChangerModule != null && fc.colorChangerModule.CanMove) + { + if (fc.colorChangerModule.animState != newState) + { + fc.colorChangerModule.ToggleEvent(); + return 1.0; + } + } + + return 0.0; + } + + /// + /// Toggle the color changers on the vessel. + /// + /// If the current pod has a color changer, its setting is used to decide the toggled + /// setting. For instance, if the pod's color changer is on, then all color changers + /// will be switched off. + /// + /// If the current pod does not have a color changer, the first color changer detected + /// will decide what the new state will be. + /// + /// 1 if any color changers were updated. + public double ToggleColorChanger() + { + bool anyUpdated = false; + + bool newState; + if (fc.colorChangerModule != null) + { + newState = !fc.colorChangerModule.animState; + } + else if (vc.moduleColorChanger.Length > 0) + { + newState = !vc.moduleColorChanger[0].animState; + } + else + { + newState = false; + } + + for (int i = vc.moduleColorChanger.Length - 1; i >= 0; --i) + { + if (vc.moduleColorChanger[i].animState != newState) + { + vc.moduleColorChanger[i].ToggleEvent(); + anyUpdated = true; + } + } + + return anyUpdated ? 1.0 : 0.0; + } + + /// + /// Toggle the pod's color changer. + /// + /// 1 if the color changer is switching on, 0 if it is switching off, or there is no color changer. + public double TogglePodColorChanger() + { + if (fc.colorChangerModule != null && fc.colorChangerModule.CanMove) + { + bool newState = !fc.colorChangerModule.animState; + fc.colorChangerModule.ToggleEvent(); + return (newState) ? 1.0 : 0.0; + } + + return 0.0; + } + + #endregion + + /// + /// Functions related to CommNet connectivity are in this category, as are functions + /// related to the Kerbal Deep Space Network ground stations. + /// + #region CommNet + + /// + /// Returns the number of antennae on the vessel. + /// + /// The number of deployable antennae on the vessel. + public double AntennaCount() + { + return vc.moduleAntenna.Length; + } + + /// + /// Returns 1 if any antennae are damaged. + /// + /// 1 if all antennae are damaged; 0 otherwise. + public double AntennaDamaged() + { + return (vc.antennaDamaged) ? 1.0 : 0.0; + } + + /// + /// Returns 1 if at least one antenna may be deployed. + /// + /// 1 if any antenna is retracted and available to deploy; 0 otherwise. + public double AntennaDeployable() + { + return (vc.antennaDeployable) ? 1.0 : 0.0; + } + + /// + /// Returns -1 if an antenna is retracting, +1 if an antenna is extending, or 0 if no + /// antennas are moving. + /// + /// -1, 0, or +1. + public double AntennaMoving() + { + return vc.antennaMoving; + } + + /// + /// Returns a number representing the average position of undamaged deployable antennae. + /// + /// * 0 - No antennae, no undamaged antennae, or all undamaged antennae are retracted. + /// * 1 - All deployable antennae extended. + /// + /// If the antennae are moving, a number between 0 and 1 is returned. + /// + /// A number between 0 and 1 as described in the summary. + public double AntennaPosition() + { + float numAntenna = 0.0f; + float lerpPosition = 0.0f; + for (int i = vc.moduleAntenna.Length - 1; i >= 0; --i) + { + if (vc.moduleAntenna[i].useAnimation && vc.moduleAntenna[i].deployState != ModuleDeployablePart.DeployState.BROKEN) + { + numAntenna += 1.0f; + + lerpPosition += vc.moduleAntenna[i].GetScalar; + } + } + + if (numAntenna > 1.0f) + { + return lerpPosition / numAntenna; + } + else if (numAntenna == 1.0f) + { + return lerpPosition; + } + + return 0.0; + } + + /// + /// Returns 1 if at least one antenna is retractable. + /// + /// 1 if a antenna is deployed, and it may be retracted; 0 otherwise. + public double AntennaRetractable() + { + return (vc.antennaRetractable) ? 1.0 : 0.0; + } + + /// + /// Reports if the vessel can communicate. + /// + /// 1 if the vessel can communicate, 0 otherwise. + public double CommNetCanCommunicate() + { + return (vessel.connection.CanComm && vessel.connection.Signal != CommNet.SignalStrength.None) ? 1.0 : 0.0; + } + + /// + /// Reports if the vessel can transmit science. + /// + /// 1 if the vessel can transmit science, 0 otherwise. + public double CommNetCanScience() + { + return (vessel.connection.CanScience && vessel.connection.Signal != CommNet.SignalStrength.None) ? 1.0 : 0.0; + } + + /// + /// Reports if the vessel is connected to CommNet. + /// + /// 1 if the vessel is connected, 0 otherwise. + public double CommNetConnected() + { + return (vessel.connection.IsConnected && vessel.connection.Signal != CommNet.SignalStrength.None) ? 1.0 : 0.0; + } + + /// + /// Reports if the vessel has a connection home. + /// + /// 1 if the vessel can talk to home, 0 otherwise. + public double CommNetConnectedHome() + { + return (vessel.connection.IsConnectedHome && vessel.connection.Signal != CommNet.SignalStrength.None) ? 1.0 : 0.0; + } + + /// + /// Returns the current control state of the vessel: + /// + /// * 2: Full Kerbal control + /// * 1: Partial Kerbal control + /// * 0: No Kerbal control + /// * -1: Other control state + /// + /// A value between -1 and +2 + public double CommNetControlState() + { + switch (vessel.connection.ControlState) + { + case CommNet.VesselControlState.KerbalFull: + return 2.0; + case CommNet.VesselControlState.KerbalPartial: + return 1.0; + case CommNet.VesselControlState.KerbalNone: + return 0.0; + } + return -1.0; + } + + /// + /// Returns the name of the endpoint of the CommNet connection. + /// + /// The name of the endpoint. + public string CommNetEndpoint() + { + if (lastLink != null) + { + return lastLink.b.name; + } + + return string.Empty; + } + + /// + /// Returns the latitude on Kerbin of the current CommNet deep space relay. + /// If there is no link home, returns 0. + /// + /// Latitude of the DSN relay, or 0. + public double CommNetLatitude() + { + if (lastLink != null && lastLink.hopType == CommNet.HopType.Home) + { + int idx = Array.FindIndex(MASLoader.deepSpaceNetwork, x => x.name == lastLink.b.name); + if (idx >= 0) + { + return MASLoader.deepSpaceNetwork[idx].latitude; + } + } + + return 0.0; + } + + /// + /// Returns the longitude on Kerbin of the current CommNet deep space relay. + /// If there is no link home, returns 0. + /// + /// Longitude of the DSN relay, or 0. + public double CommNetLongitude() + { + if (lastLink != null && lastLink.hopType == CommNet.HopType.Home) + { + int idx = Array.FindIndex(MASLoader.deepSpaceNetwork, x => x.name == lastLink.b.name); + if (idx >= 0) + { + return MASLoader.deepSpaceNetwork[idx].longitude; + } + } + + return 0.0; + } + + /// + /// Returns the signal delay between the vessel and its CommNet endpoint. + /// + /// Delay in seconds. + public double CommNetSignalDelay() + { + return vessel.connection.SignalDelay; + } + + /// + /// Returns a quality value for the CommNet signal. The quality value correlates to + /// the "signal bars" display on the KSP UI. + /// + /// * 0 - No signal + /// * 1 - Red + /// * 2 - Orange + /// * 3 - Yellow + /// * 4 - Green + /// + /// A value from 0 to 4 as described in the summary. + public double CommNetSignalQuality() + { + switch (vessel.connection.Signal) + { + case CommNet.SignalStrength.None: + return 0.0; + case CommNet.SignalStrength.Red: + return 1.0; + case CommNet.SignalStrength.Orange: + return 2.0; + case CommNet.SignalStrength.Yellow: + return 3.0; + case CommNet.SignalStrength.Green: + return 4.0; + } + + return 0.0; + } + + /// + /// Returns the signal strength of the CommNet signal. + /// + /// A value between 0 (no signal) and 1 (maximum signal strength). + public double CommNetSignalStrength() + { + return vessel.connection.SignalStrength; + } + + /// + /// Returns the altitude of the selected ground station. + /// + /// A value between 0 and `fc.GroundStationCount()` - 1. + /// The altitude of the station, or 0 if an invalid station index was specified. + public double GroundStationAltitude(double dsnIndex) + { + int idx = (int)dsnIndex; + if (idx >= 0 && idx < MASLoader.deepSpaceNetwork.Length) + { + return MASLoader.deepSpaceNetwork[idx].altitude; + } + + return 0.0; + } + + /// + /// Returns the number of ground stations in the Kerbal deep space network. + /// + /// + public double GroundStationCount() + { + return MASLoader.deepSpaceNetwork.Length; + } + + /// + /// Returns the latitude of the selected ground station. + /// + /// A value between 0 and `fc.GroundStationCount()` - 1. + /// The latitude of the station, or 0 if an invalid station index was specified. + public double GroundStationLatitude(double dsnIndex) + { + int idx = (int)dsnIndex; + if (idx >= 0 && idx < MASLoader.deepSpaceNetwork.Length) + { + return MASLoader.deepSpaceNetwork[idx].latitude; + } + + return 0.0; + } + + /// + /// Returns the longitude of the selected ground station. + /// + /// A value between 0 and `fc.GroundStationCount()` - 1. + /// The longitude of the station, or 0 if an invalid station index was specified. + public double GroundStationLongitude(double dsnIndex) + { + int idx = (int)dsnIndex; + if (idx >= 0 && idx < MASLoader.deepSpaceNetwork.Length) + { + return MASLoader.deepSpaceNetwork[idx].longitude; + } + + return 0.0; + } + + /// + /// Returns the name of the selected ground station. + /// + /// A value between 0 and `fc.GroundStationCount()` - 1. + /// The name of the station, or an empty string if an invalid station index was specified. + public string GroundStationName(double dsnIndex) + { + int idx = (int)dsnIndex; + if (idx >= 0 && idx < MASLoader.deepSpaceNetwork.Length) + { + return MASLoader.deepSpaceNetwork[idx].name; + } + + return string.Empty; + } + + /// + /// Deploys antennae (when 'deployed' is true) or undeploys antennae (when 'deployed' is false). + /// + /// Whether the function should deploy the antennae or undeploy them. + /// 1 if any antenna changes, 0 if all are already in the specified state. + public double SetAntenna(bool deploy) + { + bool anyMoved = false; + + if (deploy) + { + for (int i = vc.moduleAntenna.Length - 1; i >= 0; --i) + { + if (vc.moduleAntenna[i].useAnimation && vc.moduleAntenna[i].deployState == ModuleDeployablePart.DeployState.RETRACTED) + { + vc.moduleAntenna[i].Extend(); + anyMoved = true; + } + } + } + else + { + for (int i = vc.moduleAntenna.Length - 1; i >= 0; --i) + { + if (vc.moduleAntenna[i].useAnimation && vc.moduleAntenna[i].retractable && vc.moduleAntenna[i].deployState == ModuleDeployablePart.DeployState.EXTENDED) + { + vc.moduleAntenna[i].Retract(); + anyMoved = true; + } + } + } + + return (anyMoved) ? 1.0 : 0.0; + } + + /// + /// Deploys / undeploys antennae. + /// + /// 1 if any antennas were toggled, 0 otherwise + public double ToggleAntenna() + { + bool anyMoved = false; + if (vc.antennaDeployable) + { + for (int i = vc.moduleAntenna.Length - 1; i >= 0; --i) + { + if (vc.moduleAntenna[i].useAnimation && vc.moduleAntenna[i].deployState == ModuleDeployablePart.DeployState.RETRACTED) + { + vc.moduleAntenna[i].Extend(); + anyMoved = true; + } + } + } + else if (vc.antennaRetractable) + { + for (int i = vc.moduleAntenna.Length - 1; i >= 0; --i) + { + if (vc.moduleAntenna[i].useAnimation && vc.moduleAntenna[i].retractable && vc.moduleAntenna[i].deployState == ModuleDeployablePart.DeployState.EXTENDED) + { + vc.moduleAntenna[i].Retract(); + anyMoved = true; + } + } + } + + return (anyMoved) ? 1.0 : 0.0; + } + #endregion + + /// + /// Variables and actions related to the controls (roll / pitch / yaw / translation) + /// are in this category. + /// + #region Control Input State + /// + /// Returns 1 when roll/translation controls are near neutral. + /// + /// + public double ControlNeutral() + { + float netinputs = Math.Abs(vessel.ctrlState.pitch) + Math.Abs(vessel.ctrlState.roll) + Math.Abs(vessel.ctrlState.yaw) + Math.Abs(vessel.ctrlState.X) + Math.Abs(vessel.ctrlState.Y) + Math.Abs(vessel.ctrlState.Z); + + return (netinputs > 0.01) ? 0.0 : 1.0; + } + + /// + /// Returns the current pitch control state. + /// + /// + public double StickPitch() + { + return vessel.ctrlState.pitch; + } + + /// + /// Returns the current roll control state. + /// + /// + public double StickRoll() + { + return vessel.ctrlState.roll; + } + + /// + /// Returns the current X translation state. Note that this value is the direction + /// the thrust is firing, not the direction the vessel will move. + /// + /// A value between -1 (full left) and +1 (full right). + public double StickTranslationX() + { + return vessel.ctrlState.X; + } + + /// + /// Returns the current Y translation state. Note that this value is the direction + /// the thrust is firing, not the direction the vessel will move. + /// + /// A value between -1 (full top) and +1 (full bottom). + public double StickTranslationY() + { + return vessel.ctrlState.Y; + } + + /// + /// Returns the current Z translation state. Note that this value is the direction + /// the thrust is firing, not the direction the vessel will move. + /// + /// A value between -1 (full aft) and +1 (full forward). + public double StickTranslationZ() + { + return vessel.ctrlState.Z; + } + + /// + /// Returns the current pitch trim setting. + /// + /// Trim setting, between -1 and +1 + public double StickTrimPitch() + { + return vessel.ctrlState.pitchTrim; + } + + /// + /// Returns the current roll trim setting. + /// + /// Trim setting, between -1 and +1 + public double StickTrimRoll() + { + return vessel.ctrlState.rollTrim; + } + + /// + /// Returns the current yaw trim setting. + /// + /// Trim setting, between -1 and +1 + public double StickTrimYaw() + { + return vessel.ctrlState.yawTrim; + } + + /// + /// Returns the current yaw control state. + /// + /// + public double StickYaw() + { + return vessel.ctrlState.yaw; + } + #endregion + + /// + /// This category allows the current IVA's control point to be changed, and it provides + /// information about the available control points on this part. + /// + /// Note that control points must be defined in ModuleCommand. If the current part + /// is not a command pod (no ModuleCommand), then the control point cannot be updated. + /// + #region Control Point + + /// + /// Returns the index of the current control point, or 0 if there are no control points. + /// + /// An integer between 0 and `fc.GetNumControlPoints()` - 1. + public double GetCurrentControlPoint() + { + return fc.GetCurrentControlPoint(); + } + + /// + /// Get the name for the selected control point. If there are no control points, + /// or an invalid `controlPoint` is specified, returns an empty string. + /// + /// If `controlPoint` is -1, the current control point's name is returned. + /// + /// An integer between 0 and `fc.GetNumControlPoints()` - 1, or -1. + /// The name of the control point, or an empty string. + public string GetControlPointName(double controlPoint) + { + return fc.GetControlPointName((int)controlPoint); + } + + /// + /// Returns the number of control points on the current part. If there is no ModuleCommand on + /// the part, returns 0. + /// + /// 0, or the number of available control points. + public double GetNumControlPoints() + { + return fc.GetNumControlPoints(); + } + + /// + /// Set the control point to the index selected by `newControlPoint`. If `newControlPoint` + /// is not valid, or there is no ModuleCommand, nothing happens. + /// + /// An integer between 0 and `fc.GetNumControlPoints()` - 1. + /// 1 if the control point was updated, 0 otherwise. + public float SetCurrentControlPoint(double newControlPoint) + { + return fc.SetCurrentControlPoint((int)newControlPoint); + } + + #endregion + + /// + /// The Crew category provides information about the crew aboard the vessel. + /// + /// `seatNumber` is a 0-based index to select which seat is being queried. This + /// means that a 3-seat pod has valid seat numbers 0, 1, and 2. A single-seat + /// pod has a valid seat number 0. If a seat is unoccupied, it will return results + /// just like an invalid `seatNumber`. For example, if a 3-seat pod as two seats + /// occupied - the first and the last - then `seatNumber` 0 and 2 will provide information + /// about those crew members, but 1 (the empty seat) will return results the same + /// as an invalid seat. + /// + /// One difference to be aware of between RPM and MAS: The full-vessel crew info + /// (those methods starting 'VesselCrew') provide info on crew members without + /// regards to seating arrangements. For instance, if the command pod has 2 of 3 + /// seats occupied, and a passenger pod as 1 of 4 seats occupied, VesselCrewCount + /// will return 3, and the crew info (eg, VesselCrewName) will provide values for + /// indices 0, 1, and 2. + /// + /// By the same token, if a 3-seat pod has only 2 seats occupied, then the + /// 'VesselCrew' functions will all return info for the 0 and 1 `crewIndex` + /// values, even if the first or second seat of the pod is the empty seat. + /// + #region Crew + /// + /// Returns 1 if the crew in `seatNumber` has the 'BadS' trait. Returns 0 if + /// `seatNumber` is invalid or there is no crew in that seat, or the crew does + /// not possess the 'BadS' trait. + /// + /// The index of the seat to check. Indices start at 0. + /// 1 or 0 (see summary) + public double CrewBadS(double seatNumber) + { + int seatIdx = (int)seatNumber; + + return (seatIdx >= 0 && seatIdx < fc.localCrew.Length && fc.localCrew[seatIdx] != null && fc.localCrew[seatIdx].isBadass) ? 1.0 : 0.0; + } + + /// + /// Returns 1 if the crew in `seatNumber` is conscious. Returns 0 if the crew member has passed out due to G-forces, + /// or if no one is in that seat. + /// + /// The index of the seat to check. Indices start at 0. Use -1 to check the kerbal in the current seat. + /// 1 if the crew member is conscious, 0 if the crew member is unconscious. + public double CrewConscious(double seatNumber) + { + int seatIdx = (int)seatNumber; + if (seatIdx == -1) + { + return (!fc.currentKerbalBlackedOut) ? 1.0 : 0.0; + } + else + { + return (seatIdx >= 0 && seatIdx < fc.localCrew.Length && fc.localCrew[seatIdx] != null && (!fc.localCrew[seatIdx].outDueToG)) ? 1.0 : 0.0; + } + } + + /// + /// Returns the courage rating of the selected crew member. + /// + /// The index of the seat to check. Indices start at 0. + /// A number between 0 and 1; 0 if the requested seat is invalid or empty. + public double CrewCourage(double seatNumber) + { + int seatIdx = (int)seatNumber; + if (seatIdx >= 0 && seatIdx < fc.localCrew.Length && fc.localCrew[seatIdx] != null) + { + return fc.localCrew[seatIdx].courage; + } + + return 0.0; + } + + /// + /// Ejects ... or sends ... the selected Kerbal to EVA. + /// + /// If `seatNumber` is a negative value, the Kerbal who is currently viewing the + /// control will go on EVA. If `seatNumber` is between 0 and `fc.NumberSeats()`, + /// and the seat is currently occupied, the selected Kerbal goes on EVA. + /// + /// In all cases, the camera shifts to EVA view if a Kerbal is successfully expelled + /// from the command pod. + /// + /// A negative number, or a number in the range [0, fc.NumberSeats()-1]. + /// 1 if a Kerbal is ejected, 0 if no Kerbal was ejected. + public double CrewEva(double seatNumber) + { + int requestedSeatIdx = (int)seatNumber; + Kerbal selectedKerbal = null; + // Figure out who's trying to leave. + if (seatNumber < 0.0) + { + selectedKerbal = fc.FindCurrentKerbal(); + } + else if (requestedSeatIdx < fc.localCrew.Length) + { + if (fc.localCrew[requestedSeatIdx] != null) + { + selectedKerbal = fc.localCrew[requestedSeatIdx].KerbalRef; + } + } + + // Figure out if he/she *can* leave. + if (selectedKerbal != null) + { + float acLevel = ScenarioUpgradeableFacilities.GetFacilityLevel(SpaceCenterFacility.AstronautComplex); + bool evaUnlocked = GameVariables.Instance.UnlockedEVA(acLevel); + bool evaPossible = GameVariables.Instance.EVAIsPossible(evaUnlocked, vessel); + if (evaPossible && HighLogic.CurrentGame.Parameters.Flight.CanEVA && selectedKerbal.protoCrewMember.type != ProtoCrewMember.KerbalType.Tourist) + { + // No-op + } + else + { + selectedKerbal = null; + } + } + + // Kick him/her out of the pod. + if (selectedKerbal != null) + { + FlightEVA.SpawnEVA(selectedKerbal); + CameraManager.Instance.SetCameraFlight(); + return 1.0; + } + else + { + return 0.0; + } + } + + /// + /// Returns the number of experience points for the selected crew member. + /// + /// The index of the seat to check. Indices start at 0. + /// A number 0 or higher; 0 if the requested seat is invalid or empty. + public double CrewExperience(double seatNumber) + { + int seatIdx = (int)seatNumber; + if (seatIdx >= 0 && seatIdx < fc.localCrew.Length && fc.localCrew[seatIdx] != null) + { + return fc.localCrew[seatIdx].experience; + } + + return 0.0; + } + + /// + /// Returns a number representing the gender of the selected crew member. + /// + /// The index of the seat to check. Indices start at 0. + /// 1 if the crew is male, 2 if the crew is female, 0 if the seat is empty. + public double CrewGender(double seatNumber) + { + int seatIdx = (int)seatNumber; + if (seatIdx >= 0 && seatIdx < fc.localCrew.Length && fc.localCrew[seatIdx] != null) + { + return (fc.localCrew[seatIdx].gender == ProtoCrewMember.Gender.Male) ? 1.0 : 2.0; + } + + return 0.0; + } + + /// + /// Returns the experience level of the selected crew member. + /// + /// The index of the seat to check. Indices start at 0. + /// A number 0-5; 0 if the requested seat is invalid or empty. + public double CrewLevel(double seatNumber) + { + int seatIdx = (int)seatNumber; + if (seatIdx >= 0 && seatIdx < fc.localCrew.Length && fc.localCrew[seatIdx] != null) + { + return fc.localCrew[seatIdx].experienceLevel; + } + + return 0.0; + } + + /// + /// Returns the name of the crew member seated in `seatNumber`. If + /// the number is invalid, or no Kerbal is in the seat, returns an + /// empty string. + /// + /// The index of the seat to check. Indices start at 0. + /// The crew name, or an empty string if there is no crew in the + /// given seat. + public string CrewName(double seatNumber) + { + int seatIdx = (int)seatNumber; + if (seatIdx >= 0 && seatIdx < fc.localCrew.Length && fc.localCrew[seatIdx] != null) + { + return fc.localCrew[seatIdx].name; + } + + return string.Empty; + } + + /// + /// Returns the 'PANIC' level of the selected crew member. + /// + /// The index of the seat to check. Indices start at 0. + /// A number between 0 and 1; 0 if the requested seat is invalid or empty. + public double CrewPanic(double seatNumber) + { + int seatIdx = (int)seatNumber; + var expression = fc.GetLocalKES(seatIdx); + if (expression != null) + { + return expression.panicLevel; + } + + return 0.0; + } + + /// + /// Returns the stupidity rating of the selected crew member. + /// + /// The index of the seat to check. Indices start at 0. + /// A number between 0 and 1; 0 if the requested seat is invalid or empty. + public double CrewStupidity(double seatNumber) + { + int seatIdx = (int)seatNumber; + if (seatIdx >= 0 && seatIdx < fc.localCrew.Length && fc.localCrew[seatIdx] != null) + { + return fc.localCrew[seatIdx].stupidity; + } + + return 0.0; + } + + /// + /// Returns the job title of the selected crew member. + /// + /// The index of the seat to check. Indices start at 0. + /// The name of the job title, or an empty string if `seatNumber` is invalid or + /// unoccupied. + public string CrewTitle(double seatNumber) + { + int seatIdx = (int)seatNumber; + if (seatIdx >= 0 && seatIdx < fc.localCrew.Length && fc.localCrew[seatIdx] != null) + { + return fc.localCrew[seatIdx].experienceTrait.Title; + } + + return string.Empty; + } + + /// + /// Returns the 'WHEE' level of the selected crew member. + /// + /// The index of the seat to check. Indices start at 0. + /// A number between 0 and 1; 0 if the requested seat is invalid or empty. + public double CrewWhee(double seatNumber) + { + int seatIdx = (int)seatNumber; + var expression = fc.GetLocalKES(seatIdx); + if (expression != null) + { + return expression.wheeLevel; + } + + return 0.0; + } + + /// + /// Returns the number of seats in the current IVA pod. + /// + /// The number of seats in the current IVA (1 or more). + public double NumberSeats() + { + return fc.localCrew.Length; + } + + /// + /// Indicates whether a given seat is occupied by a Kerbal. Returns 1 when `seatNumber` is + /// valid and there is a Kerbal in the given seat, and returns 0 in all other instances. + /// + /// The index of the seat to check. Indices start at 0. + /// 1 if `seatNumber` is a valid seat; 0 otherwise. + public double SeatOccupied(double seatNumber) + { + int seatIdx = (int)seatNumber; + return (seatIdx >= 0 && seatIdx < fc.localCrew.Length && fc.localCrew[seatIdx] != null) ? 1.0 : 0.0; + } + + /// + /// Returns 1 if the selected crew has the 'BadS' trait. Returns 0 if + /// `crewIndex` is invalid or the crew does + /// not possess the 'BadS' trait. + /// + /// The index of the crewmember to check. Indices start at 0. + /// 1 or 0 (see summary) + public double VesselCrewBadS(double crewIndex) + { + int index = (int)crewIndex; + + if (index >= 0 && index < vessel.GetCrewCount()) + { + return (vessel.GetVesselCrew()[index].isBadass) ? 1.0 : 0.0; + } + + return 0.0; + } + + /// + /// Capacity of all crewed locations on the vessel. + /// + /// 0 or higher. + public double VesselCrewCapacity() + { + return vessel.GetCrewCapacity(); + } + + /// + /// Total count of crew aboard the vessel. + /// + /// 0 or higher. + public double VesselCrewCount() + { + return vessel.GetCrewCount(); + } + + /// + /// Returns the courage rating of the selected crew member. + /// + /// The index of the crewmember to check. Indices start at 0, and go to `fc.VesselCrewCount() - 1`. + /// A number between 0 and 1; 0 if the requested seat is invalid or empty. + public double VesselCrewCourage(double crewIndex) + { + int index = (int)crewIndex; + if (index >= 0 && index < vessel.GetCrewCount()) + { + return vessel.GetVesselCrew()[index].courage; + } + + return 0.0; + } + + /// + /// Returns the number of experience points for the selected crew member. + /// + /// The index of the crewmember to check. Indices start at 0, and go to `fc.VesselCrewCount() - 1`. + /// A number 0 or higher; 0 if the requested seat is invalid. + public double VesselCrewExperience(double crewIndex) + { + int index = (int)crewIndex; + + if (index >= 0 && index < vessel.GetCrewCount()) + { + return vessel.GetVesselCrew()[index].experience; + } + + return 0.0; + } + + /// + /// Returns a number representing the gender of the selected crew member. + /// + /// The index of the crewmember to check. Indices start at 0, and go to `fc.VesselCrewCount() - 1`. + /// 1 if the crew is male, 2 if the crew is female, 0 if the index is invalid. + public double VesselCrewGender(double crewIndex) + { + int index = (int)crewIndex; + + if (index >= 0 && index < vessel.GetCrewCount()) + { + return (vessel.GetVesselCrew()[index].gender == ProtoCrewMember.Gender.Male) ? 1.0 : 2.0; + } + + return 0.0; + } + + /// + /// Returns the experience level of the selected crew member. + /// + /// The index of the crewmember to check. Indices start at 0, and go to `fc.VesselCrewCount() - 1`. + /// A number 0-5; 0 if the requested index is invalid. + public double VesselCrewLevel(double crewIndex) + { + int index = (int)crewIndex; + + if (index >= 0 && index < vessel.GetCrewCount()) + { + return vessel.GetVesselCrew()[index].experienceLevel; + } + + return 0.0; + } + + /// + /// Returns the name of the crew member seated in `seatNumber`. If + /// the number is invalid, or no Kerbal is in the seat, returns an + /// empty string. + /// + /// The index of the crewmember to check. Indices start at 0, and go to `fc.VesselCrewCount() - 1`. + /// The crew name, or an empty string if index is invalid. + public string VesselCrewName(double crewIndex) + { + int index = (int)crewIndex; + + if (index >= 0 && index < vessel.GetCrewCount()) + { + return vessel.GetVesselCrew()[index].name; + } + + return string.Empty; + } + + /// + /// Returns the 'PANIC' level of the selected crew member. + /// + /// The index of the crewmember to check. Indices start at 0, and go to `fc.VesselCrewCount() - 1`. + /// A number between 0 and 1; 0 if the requested index is invalid. + public double VesselCrewPanic(double crewIndex) + { + int index = (int)crewIndex; + + if (index >= 0 && index < vessel.GetCrewCount()) + { + return GetVesselCrewExpression(index).panicLevel; + } + + return 0.0; + } + + /// + /// Returns the stupidity rating of the selected crew member. + /// + /// The index of the crewmember to check. Indices start at 0, and go to `fc.VesselCrewCount() - 1`. + /// A number between 0 and 1; 0 if the requested index is invalid. + public double VesselCrewStupidity(double crewIndex) + { + int index = (int)crewIndex; + + if (index >= 0 && index < vessel.GetCrewCount()) + { + return vessel.GetVesselCrew()[index].stupidity; + } + + return 0.0; + } + + /// + /// Returns the job title of the selected crew member. + /// + /// The index of the crewmember to check. Indices start at 0, and go to `fc.VesselCrewCount() - 1`. + /// The name of the job title, or an empty string if `crewIndex` is invalid. + public string VesselCrewTitle(double crewIndex) + { + int index = (int)crewIndex; + + if (index >= 0 && index < vessel.GetCrewCount()) + { + return vessel.GetVesselCrew()[index].experienceTrait.Title; + } + + return string.Empty; + } + + /// + /// Returns the 'WHEE' level of the selected crew member. + /// + /// The index of the crewmember to check. Indices start at 0, and go to `fc.VesselCrewCount() - 1`. + /// A number between 0 and 1; 0 if the requested index is invalid. + public double VesselCrewWhee(double crewIndex) + { + int index = (int)crewIndex; + + if (index >= 0 && index < vessel.GetCrewCount()) + { + + return GetVesselCrewExpression(index).wheeLevel; + } + + return 0.0; + } + + #endregion + + /// + /// Docking control and status are in the Docking category. + /// + /// Many of these methods use the concept of "Primary Docking Port". + /// The primary docking port is defined as the first or only docking + /// port found on the vessel. These features are primarily centered + /// around CTV / OTV type spacecraft where there is a single dock, + /// not space stations or large craft with many docks. + /// + #region Docking + /// + /// Return 1 if the dock is attached to something (this includes parts that + /// are not compatible to the docking port, such as boost protective covers or + /// launch escape systems that are attached in the VAB). + /// + /// To determine if the dock is connected to a compatible docking port, use fc.Docked(). + /// + /// + public double DockConnected() + { + return (vc.dockingNodeState == MASVesselComputer.DockingNodeState.DOCKED || vc.dockingNodeState == MASVesselComputer.DockingNodeState.PREATTACHED) ? 1.0 : 0.0; + } + + /// + /// Returns the name of the object docked to the vessel. Returns an empty string if fc.Docked() returns 0. + /// + /// The name of the docked vessel. + public string DockedObjectName() + { + if (vc.dockingNodeState == MASVesselComputer.DockingNodeState.DOCKED) + { + if (vc.dockingNode.vesselInfo != null) + { + string l10n = string.Empty; + if (KSP.Localization.Localizer.TryGetStringByTag(vc.dockingNode.vesselInfo.name, out l10n)) + { + return l10n; + } + else + { + return vc.dockingNode.vesselInfo.name; + } + } + } + return string.Empty; + } + + /// + /// Return 1 if the dock is attached to a compatible dock; return 0 otherwise. + /// + /// Note that this function will return 0 if the compatible dock was connected in the + /// VAB. fc.Docked() only detects docking events that take place during Flight. + /// + /// + public double Docked() + { + return (vc.dockingNodeState == MASVesselComputer.DockingNodeState.DOCKED) ? 1.0 : 0.0; + } + + /// + /// Return 1 if the dock is ready; return 0 otherwise. + /// + /// + public double DockReady() + { + return (vc.dockingNodeState == MASVesselComputer.DockingNodeState.READY) ? 1.0 : 0.0; + } + + /// + /// Returns 1 if the primary docking port on the vessel is the current reference transform. + /// + /// Returns 0 if the primary docking port is not the reference transform, or if there is no docking port, + /// or if a docking port other than the primary port is the reference transform. + /// + /// + public double GetDockIsReference() + { + return (vc.dockingNode != null && vc.dockingNode.part == vessel.GetReferenceTransformPart()) ? 1.0 : 0.0; + } + + /// + /// Returns 1 if the primary grapple on the vessel is the current reference transform. + /// + /// Returns 0 if the grapple is not the reference transform, or if there is no grapple, + /// or if a grapple other than the primary grapple is the reference transform. + /// + /// + public double GetGrappleIsReference() + { + return (vc.clawNode != null && vc.clawNode.part == vessel.GetReferenceTransformPart()) ? 1.0 : 0.0; + } + + /// + /// Returns 1 if the current IVA pod is the reference transform. Returns 0 otherwise. + /// + /// + public double GetPodIsReference() + { + return (fc.part == vessel.GetReferenceTransformPart()) ? 1.0 : 0.0; + } + + /// + /// Returns the index of the docking port currently tracked by the vessel. + /// + /// If no port is being tracked, returns -1. + /// + /// For a valid docking port, a number between 0 and 'fc.TargetDockCount()' - 1. Otherwise, -1. + public double GetTargetDockIndex() + { + if (vc.targetType == MASVesselComputer.TargetType.DockingPort) + { + if (vc.targetDockingPorts.Length == 1) + { + // The only docking port. + return 0.0; + } + + ModuleDockingNode activeNode = vc.activeTarget as ModuleDockingNode; + + return Array.FindIndex(vc.targetDockingPorts, x => x == activeNode); + } + + return -1.0; + } + + /// + /// Indicates whether a primary docking port was found on this vessel. + /// + /// 1 if a node was found, 0 otherwise. + public double HasDock() + { + return (vc.dockingNode != null) ? 1.0 : 0.0; + } + + /// + /// Set the primary docking port to be the reference transform. + /// + /// 1 if the reference was changed, 0 otherwise. + public double SetDockToReference() + { + if (vc.dockingNode != null) + { + vessel.SetReferenceTransform(vc.dockingNode.part); + return 1.0; + } + else + { + return 0.0; + } + } + /// + /// Set the next docking port to be the reference transform. + /// + /// 1 if the reference was changed, 0 otherwise. + public double SetNextDockToReference() + { + /*if (vc.dockingNode != null) + { + vessel.SetReferenceTransform(vc.dockingNode.part); + return 1.0; + } + else + { + return 0.0; + }*/ + + if (vc.ownDockingPorts.Length == 0) + { + return 0.0; + } + else if (fc.part == vessel.GetReferenceTransformPart()) + { + vessel.SetReferenceTransform(vc.ownDockingPorts[0].part); + return 1.0; + } + else if (vc.referenceTransformType == MASVesselComputer.ReferenceType.DockingPort) + { + if (vc.ownDockingPorts.Length == 1) + { + // We're already referencing the only docking port. + return 1.0; + } + + ModuleDockingNode activeOwnNode = vc.dockingNode as ModuleDockingNode; + int currentOwnIndex = Array.FindIndex(vc.ownDockingPorts, x => x == activeOwnNode); + if (currentOwnIndex == -1) + { + vessel.SetReferenceTransform(vc.ownDockingPorts[0].part); + } + else + { + vessel.SetReferenceTransform(vc.ownDockingPorts[(currentOwnIndex + 1) % vc.ownDockingPorts.Length].part); + } + return 1.0; + } + + return 0.0; + } + + /// + /// Set the primary grapple to be the reference transform. + /// + /// 1 if the reference was changed, 0 otherwise. + public double SetGrappleToReference() + { + if (vc.clawNode != null) + { + vessel.SetReferenceTransform(vc.clawNode.part); + return 1.0; + } + else + { + return 0.0; + } + } + + /// + /// Set the current IVA pod to be the reference transform. + /// + /// 1 if the reference was changed. + public double SetPodToReference() + { + vessel.SetReferenceTransform(fc.part); + return 1.0; + } + + /// + /// Targets the docking port on the target vessel identified by 'idx'. + /// + /// Note that KSP only allows targeting docking ports within a certain range (typically + /// 200 meters). + /// + /// A value between 0 and `fc.TargetDockCount()` - 1. + /// 1 the dock could be targeted, 0 otherwise. + public double SetTargetDock(double idx) + { + int index = (int)idx; + if (index < 0 || index >= vc.targetDockingPorts.Length) + { + return 0.0; + } + + FlightGlobals.fetch.SetVesselTarget(vc.targetDockingPorts[index]); + + return 1.0; + } + + /// + /// Returns the number of available docking ports found on the target vessel when the following + /// conditions are met: + /// + /// 1) There are docking ports on the target vessel compatible with the designated + /// docking port on the current vessel. The designated docking port is either the + /// only docking port on the current vessel, or it is one of the docking ports selected + /// arbitrarily. + /// + /// 2) There are no docking ports on the current vessel. In this case, all docking + /// ports on the target vessel are counted. + /// + /// Note that if the target is unloaded, this method will return 0. If the target is + /// not a vessel, it also returns 0. + /// + /// This function is identical to `fc.TargetAvailableDockingPorts()`. + /// + /// Number of available compatible docking ports, or total available docking ports, or 0. + public double TargetDockCount() + { + return vc.targetDockingPorts.Length; + } + + /// + /// Returns the angle in degrees that the vessel must roll in order to align with the + /// currently-targeted docking port. + /// + /// If no dock is targeted, there are no alignment requirements, or a compatible docking port + /// is not the reference part, this function returns 0. + /// + /// The roll required to align docking ports, or 0. + public double TargetDockError() + { + if (vc.dockingNode != null && vc.dockingNode.part.transform == vc.referenceTransform && vc.targetType == MASVesselComputer.TargetType.DockingPort && vc.targetDockingTransform != null) + { + ModuleDockingNode activeNode = vc.activeTarget as ModuleDockingNode; + // TODO: If either is false, does that disable snapRotation? Or do both need to be false? + if (vc.dockingNode.snapRotation == false && activeNode.snapRotation == false) + { + return 0.0; + } + + float snapOffset; + if (vc.dockingNode.snapRotation) + { + if (activeNode.snapRotation) + { + snapOffset = Mathf.Min(activeNode.snapOffset, vc.dockingNode.snapOffset); + } + else + { + snapOffset = vc.dockingNode.snapOffset; + } + } + else + { + snapOffset = activeNode.snapOffset; + } + + Vector3 projectedVector = Vector3.ProjectOnPlane(vc.targetDockingTransform.up, vc.referenceTransform.up); + projectedVector.Normalize(); + + float dotLateral = Vector3.Dot(projectedVector, vc.referenceTransform.right); + float dotLongitudinal = Vector3.Dot(projectedVector, vc.referenceTransform.forward); + + // Taking arc tangent of x/y lets us treat the front of the vessel + // as the 0 degree location. + float roll = Mathf.Atan2(dotLateral, dotLongitudinal) * Mathf.Rad2Deg; + // Normalize it + if (roll < 0.0f) + { + roll += 360.0f; + } + + float rollError = roll % snapOffset; + if (rollError > (snapOffset * 0.5f)) + { + rollError -= snapOffset; + } + + return rollError; + } + + return 0.0; + } + + /// + /// Returns the name of the target dock selected by 'idx'. If the Docking Port Alignment + /// Indicator mod is installed, the name given to the dock from that mod is returned. Otherwise, + /// only the part's name is returned. + /// + /// The name of the selected dock, or an empty string. + public string TargetDockName(double idx) + { + int index = (int)idx; + if (index >= 0 && idx < vc.targetDockingPorts.Length) + { + return fc.GetDockingPortName(vc.targetDockingPorts[index].part); + } + + return string.Empty; + } + + /// + /// Targets the next valid docking port on the target vessel. + /// + /// Note that KSP only allows targeting docking ports within a certain range (typically + /// 200 meters). + /// + /// 1 if a dock could be targeted, 0 otherwise. + public double TargetNextDock() + { + if (vc.targetDockingPorts.Length == 0) + { + return 0.0; + } + else if (vc.targetType == MASVesselComputer.TargetType.Vessel) + { + FlightGlobals.fetch.SetVesselTarget(vc.targetDockingPorts[0]); + return 1.0; + } + else if (vc.targetType == MASVesselComputer.TargetType.DockingPort) + { + if (vc.targetDockingPorts.Length == 1) + { + // We're already targeting the only docking port. + return 1.0; + } + + ModuleDockingNode activeNode = vc.activeTarget as ModuleDockingNode; + int currentIndex = Array.FindIndex(vc.targetDockingPorts, x => x == activeNode); + if (currentIndex == -1) + { + FlightGlobals.fetch.SetVesselTarget(vc.targetDockingPorts[0]); + } + else + { + FlightGlobals.fetch.SetVesselTarget(vc.targetDockingPorts[(currentIndex + 1) % vc.targetDockingPorts.Length]); + } + return 1.0; + } + return 0.0; + } + + /// + /// Undock / detach (if pre-attached) the active docking node. + /// + /// 1 if the active dock undocked from something, 0 otherwise. + public double Undock() + { + if (vc.dockingNode != null) + { + if (vc.dockingNodeState == MASVesselComputer.DockingNodeState.DOCKED) + { + vc.dockingNode.Undock(); + return 1.0; + } + else if (vc.dockingNodeState == MASVesselComputer.DockingNodeState.PREATTACHED) + { + vc.dockingNode.Decouple(); + return 1.0; + } + } + + return 0.0; + } + #endregion + + /// + /// Engine status and control methods are in the Engine category. + /// + #region Engine + + /// + /// Returns the current fuel flow in grams/second + /// + /// + public double CurrentFuelFlow() + { + return vc.currentEngineFuelFlow; + } + + /// + /// Returns the average deflection of active, unlocked gimbals, from 0 (no deflection) to 1 (max deflection). + /// + /// The direction of the deflection is ignored, but the value accounts for assymetrical gimbal configurations, + /// eg, if X+ is 5.0, and X- is -3.0, the deflection percentage accounts for this difference. + /// + /// + public double CurrentGimbalDeflection() + { + return vc.gimbalDeflection; + } + + /// + /// Return the current specific impulse in seconds. + /// + /// The current Isp. + public double CurrentIsp() + { + return vc.currentIsp; + } + + /// + /// Returns the current thrust output relative to the + /// current stage's max rated thrust, from 0.0 to 1.0. + /// + /// Thrust output, ranging from 0 to 1. + public double CurrentRatedThrust() + { + if (vc.currentThrust > 0.0f) + { + return vc.currentThrust / vc.maxRatedThrust; + } + else + { + return 0.0f; + } + } + + /// + /// Returns the current thrust output, from 0.0 to 1.0. + /// + /// Thrust output, ranging from 0 to 1. + public double CurrentThrust(bool useThrottleLimits) + { + if (vc.currentThrust > 0.0f) + { + return vc.currentThrust / ((useThrottleLimits) ? vc.currentLimitedMaxThrust : vc.currentMaxThrust); + } + else + { + return 0.0f; + } + } + + /// + /// Returns the current thrust in kiloNewtons + /// + /// + public double CurrentThrustkN() + { + return vc.currentThrust; + } + + /// + /// Returns the current thrust-to-weight ratio. + /// + /// + public double CurrentTWR() + { + if (vc.currentThrust > 0.0f) + { + return vc.currentThrust / (vessel.totalMass * vc.surfaceAccelerationFromGravity); + } + else + { + return 0.0; + } + } + + /// + /// Returns the total delta-V remaining for the vessel based on its current + /// altitude. + /// + /// If Kerbal Engineer or MechJeb is installed, those mods are used for the computation. + /// + /// MechJeb, Kerbal Engineer Redux + /// Remaining delta-V in m/s. + public double DeltaV() + { + if (MASIKerbalEngineer.keFound) + { + return keProxy.DeltaV(); + } + else if (mjProxy.mjAvailable) + { + return mjProxy.DeltaV(); + } + else + { + VesselDeltaV vdV = vessel.VesselDeltaV; + if (vdV.IsReady) + { + return vdV.TotalDeltaVActual; + } + else + { + return 0.0; + } + } + } + + /// + /// Returns an estimate of the delta-V remaining for the current stage. This computation uses + /// the current ISP. + /// + /// Remaining delta-V for this stage in m/s. + public double DeltaVStage() + { + // mass in tonnes. + double stagePropellantMass = vc.enginePropellant.currentStage * vc.enginePropellant.density; + + if (stagePropellantMass > 0.0) + { + return vc.currentIsp * PhysicsGlobals.GravitationalAcceleration * Math.Log(vessel.totalMass / (vessel.totalMass - stagePropellantMass)); + } + + return 0.0; + } + + /// + /// Returns an estimate of the maximum delta-V for the current stage. This computation uses + /// the current ISP. + /// + /// Maximum delta-V for this stage in m/s. + public double DeltaVStageMax() + { + // mass in tonnes. + double stagePropellantMass = vc.enginePropellant.currentStage * vc.enginePropellant.density; + + if (stagePropellantMass > 0.0) + { + double startingMass = vessel.totalMass + (vc.enginePropellant.maxStage - vc.enginePropellant.currentStage) * vc.enginePropellant.density; + + return vc.currentIsp * PhysicsGlobals.GravitationalAcceleration * Math.Log(startingMass / (vessel.totalMass - stagePropellantMass)); + } + + return 0.0; + } + + /// + /// Returns a count of the total number of engines that are active. + /// + /// + public double EngineCountActive() + { + return vc.activeEngineCount; + } + + /// + /// Returns a count of the total number of engines tracked. This + /// count includes engines that have not staged. + /// + /// + public double EngineCountTotal() + { + return vc.moduleEngines.Length; + } + + /// + /// Returns 1 if any active engines are in a flameout condition. + /// + /// + public double EngineFlameout() + { + return (vc.anyEnginesFlameout) ? 1.0 : 0.0; + } + + /// + /// Returns 1 if at least one engine is enabled. + /// + /// + public double GetEnginesEnabled() + { + return (vc.anyEnginesEnabled) ? 1.0 : 0.0; + } + + /// + /// Returns 1 if any currently-active engines have gimbals. Returns 0 if no active engine has a gimbal. + /// + /// + public double GetActiveEnginesGimbal() + { + return (vc.activeEnginesGimbal) ? 1.0 : 0.0; + } + + /// + /// Returns the normalized (-1 to +1) gimbal deflection of unlocked gimbals along their local + /// X axis. + /// + /// Note that if two engines are deflected in opposite directions during a roll + /// maneuver, this value could be zero. + /// + /// A value in the range [-1, 1]. + public double GetGimbalDeflectionX() + { + return vc.gimbalAxisDeflection.x; + } + + /// + /// Returns the normalized (-1 to +1) gimbal deflection of unlocked gimbals along their local + /// Y axis. + /// + /// Note that if two engines are deflected in opposite directions during a roll + /// maneuver, this value could be zero. + /// + /// A value in the range [-1, 1]. + public double GetGimbalDeflectionY() + { + return vc.gimbalAxisDeflection.y; + } + + /// + /// Returns the currently-configured limit of active gimbals, as set in the right-click part menus. + /// This value ranges between 0 (no gimbal) and 1 (100% gimbal). + /// + /// + public double GetGimbalLimit() + { + return vc.gimbalLimit; + } + + /// + /// Returns 1 if any active, unlocked gimbals are configured to provide pitch control. + /// Returns 0 otherwise. + /// + /// 1 if active gimbals support pitch, 0 otherwise. + public double GetGimbalPitch() + { + return (vc.anyGimbalsPitch) ? 1.0 : 0.0; + } + + /// + /// Returns 1 if any active, unlocked gimbals are configured to provide roll control. + /// Returns 0 otherwise. + /// + /// 1 if active gimbals support roll, 0 otherwise. + public double GetGimbalRoll() + { + return (vc.anyGimbalsRoll) ? 1.0 : 0.0; + } + + /// + /// Returns 1 if any active, unlocked gimbals are configured to provide yaw control. + /// Returns 0 otherwise. + /// + /// 1 if active gimbals support yaw, 0 otherwise. + public double GetGimbalYaw() + { + return (vc.anyGimbalsYaw) ? 1.0 : 0.0; + } + + /// + /// Returns 1 if any gimbals are currently active. + /// + /// + public double GetGimbalsActive() + { + return (vc.anyGimbalsActive) ? 1.0 : 0.0; + } + + /// + /// Returns 1 if at least one active gimbal is locked. + /// + /// + public double GetGimbalsLocked() + { + return (vc.anyGimbalsLocked) ? 1.0 : 0.0; + } + + /// + /// Returns 1 if any multi-mode engine is in secondary mode, 0 if no engines are, + /// or there are no multi-mode engines. + /// + /// + public double GetMultiModeEngineMode() + { + for (int i = vc.multiModeEngines.Length - 1; i >= 0; --i) + { + if (vc.multiModeEngines[i].runningPrimary == false) + { + return 1.0; + } + } + + return 0.0; + } + + /// + /// Returns the current main throttle setting, from 0.0 to 1.0. + /// + /// + public double GetThrottle() + { + return vessel.ctrlState.mainThrottle; + } + + /// + /// Returns the average of the throttle limit for the active engines, + /// ranging from 0 (no thrust) to 1 (maximum thrust). + /// + /// + public double GetThrottleLimit() + { + return vc.throttleLimit; + } + + /// + /// Returns the maximum fuel flow in grams/second + /// + /// + public double MaxFuelFlow() + { + return vc.maxEngineFuelFlow; + } + + /// + /// Returns the maximum specific impulse in seconds. + /// + /// + public double MaxIsp() + { + return vc.maxIsp; + } + + /// + /// Returns the maximum rated thrust in kN for the active engines. + /// + /// Maximum thrust in kN + public double MaxRatedThrustkN() + { + return vc.maxRatedThrust; + } + + /// + /// Returns the maximum thrust in kN for the current altitude. + /// + /// Apply throttle limits? + /// Maximum thrust in kN + public double MaxThrustkN(bool useThrottleLimits) + { + return (useThrottleLimits) ? vc.currentLimitedMaxThrust : vc.currentMaxThrust; + } + + /// + /// Returns the maximum thrust-to-weight ratio. + /// + /// Apply throttle limits? + /// Thrust-to-weight ratio, between 0 and 1. + public double MaxTWR(bool useThrottleLimits) + { + double thrust = ((useThrottleLimits) ? vc.currentLimitedMaxThrust : vc.currentMaxThrust); + if (thrust > 0.0) + { + return ((useThrottleLimits) ? vc.currentLimitedMaxThrust : vc.currentMaxThrust) / (vessel.totalMass * vc.surfaceAccelerationFromGravity); + } + else + { + return 0.0; + } + } + + /// + /// Turns on/off engines for the current stage. + /// + /// 1 if engines are now enabled, 0 if they are disabled. + public double SetEnginesEnabled(bool enable) + { + return (vc.SetEnginesEnabled(enable)) ? 1.0 : 0.0; + } + + /// + /// Change the gimbal limit for active gimbals. Values less than 0 or greater than 1 are + /// clamped to that range. + /// + /// The new gimbal limit, between 0 and 1. + /// 1 if any gimbals were updated, 0 otherwise. + public double SetGimbalLimit(double newLimit) + { + float limit = Mathf.Clamp01((float)newLimit) * 100.0f; + bool updated = false; + + for (int i = vc.moduleGimbals.Length - 1; i >= 0; --i) + { + if (vc.moduleGimbals[i].gimbalActive) + { + vc.moduleGimbals[i].gimbalLimiter = limit; + } + } + + return (updated) ? 1.0 : 0.0; + } + + /// + /// Locks or unlocks engine gimbals for the current stage. + /// + /// 1 if any gimbals changed, 0 if none changed. + public double SetGimbalLock(bool locked) + { + bool changed = false; + for (int i = vc.moduleGimbals.Length - 1; i >= 0; --i) + { + if (vc.moduleGimbals[i].gimbalActive && vc.moduleGimbals[i].gimbalLock != locked) + { + changed = true; + vc.moduleGimbals[i].gimbalLock = locked; + } + } + + return (changed) ? 1.0 : 0.0; + } + + /// + /// Controls whether active, unlocked gimbals provide pitch control. + /// + /// If true, enables gimbal pitch control. If false, disables gimbal pitch control. + /// 1 if any gimbal pitch control changed, 0 otherwise. + public double SetGimbalPitch(bool enable) + { + bool changed = false; + for (int i = vc.moduleGimbals.Length - 1; i >= 0; --i) + { + if (vc.moduleGimbals[i].gimbalActive && !vc.moduleGimbals[i].gimbalLock && vc.moduleGimbals[i].enablePitch != enable) + { + changed = true; + vc.moduleGimbals[i].enablePitch = enable; + } + } + + return (changed) ? 1.0 : 0.0; + } + + /// + /// Controls whether active, unlocked gimbals provide roll control. + /// + /// If true, enables gimbal roll control. If false, disables gimbal roll control. + /// 1 if any gimbal roll control changed, 0 otherwise. + public double SetGimbalRoll(bool enable) + { + bool changed = false; + for (int i = vc.moduleGimbals.Length - 1; i >= 0; --i) + { + if (vc.moduleGimbals[i].gimbalActive && !vc.moduleGimbals[i].gimbalLock && vc.moduleGimbals[i].enableRoll != enable) + { + changed = true; + vc.moduleGimbals[i].enableRoll = enable; + } + } + + return (changed) ? 1.0 : 0.0; + } + + /// + /// Controls whether active, unlocked gimbals provide yaw control. + /// + /// If true, enables gimbal yaw control. If false, disables gimbal yaw control. + /// 1 if any gimbal yaw control changed, 0 otherwise. + public double SetGimbalYaw(bool enable) + { + bool changed = false; + for (int i = vc.moduleGimbals.Length - 1; i >= 0; --i) + { + if (vc.moduleGimbals[i].gimbalActive && !vc.moduleGimbals[i].gimbalLock && vc.moduleGimbals[i].enableYaw != enable) + { + changed = true; + vc.moduleGimbals[i].enableYaw = enable; + } + } + + return (changed) ? 1.0 : 0.0; + } + + /// + /// Selects the primary or secondary mode for multi-mode engines. + /// + /// Selects the primary mode when true, the secondary mode when false. + /// 1 if any engines were toggled, 0 if no multi-mode engines are installed. + public double SetMultiModeEngineMode(bool runPrimary) + { + bool anyChanged = false; + for (int i = vc.multiModeEngines.Length - 1; i >= 0; --i) + { + if (vc.multiModeEngines[i].runningPrimary != runPrimary) + { + vc.multiModeEngines[i].ToggleMode(); + anyChanged = true; + } + } + + if (anyChanged) + { + vc.InvalidateModules(); + } + + return (anyChanged) ? 1.0 : 0.0; + } + + /// + /// Set the throttle. May be set to any value between 0 and 1. Values outside + /// that range are clamped to [0, 1]. + /// + /// Throttle setting, between 0 and 1. + /// The new throttle setting. + public double SetThrottle(double throttlePercentage) + { + float throttle = Mathf.Clamp01((float)throttlePercentage); + try + { + FlightInputHandler.state.mainThrottle = throttle; + } + catch (Exception e) + { + // RPM had a try-catch. Why? + Utility.LogError(this, "SetThrottle({0:0.00}) threw {1}", throttle, e); + } + return throttle; + } + + /// + /// Set the throttle limit. May be set to any value between 0 and 1. Values outside + /// that range are clamped to [0, 1]. + /// + /// + /// + public double SetThrottleLimit(double newLimit) + { + float limit = Mathf.Clamp01((float)newLimit) * 100.0f; + bool updated = vc.SetThrottleLimit(limit); + + return (updated) ? 1.0 : 0.0; + } + + /// + /// Returns the total delta-V remaining for the vessel based on its current + /// altitude. + /// + /// This version uses only the stock KSP delta-V computations. + /// + /// Remaining delta-V in m/s. + public double StockDeltaV() + { + VesselDeltaV vdV = vessel.VesselDeltaV; + if (vdV.IsReady) + { + return vdV.TotalDeltaVActual; + } + else + { + return 0.0; + } + } + + /// + /// Returns an estimate of the delta-V remaining for the current stage. This computation uses + /// the current ISP. + /// + /// This version uses only the stock KSP delta-V computations. + /// + /// Remaining delta-V for this stage in m/s. + public double StockDeltaVStage() + { + VesselDeltaV vdV = vessel.VesselDeltaV; + if (vdV.IsReady && vdV.currentStageActivated) + { + DeltaVStageInfo stageInfo = vdV.OperatingStageInfo[0]; + return stageInfo.deltaVActual; + } + else + { + return 0.0; + } + } + + /// + /// Turns on/off engines for the current stage + /// + /// 1 if engines are now enabled, 0 if they are disabled. + public double ToggleEnginesEnabled() + { + return (vc.SetEnginesEnabled(!vc.anyEnginesEnabled)) ? 1.0 : 0.0; + } + + /// + /// Toggles gimbal lock on/off for the current stage. + /// + /// 1 if active gimbals are now locked, 0 if they are unlocked. + public double ToggleGimbalLock() + { + bool newState = !vc.anyGimbalsLocked; + for (int i = vc.moduleGimbals.Length - 1; i >= 0; --i) + { + if (vc.moduleGimbals[i].gimbalActive) + { + vc.moduleGimbals[i].gimbalLock = newState; + } + } + + return (newState) ? 1.0 : 0.0; + } + + /// + /// Toggles pitch control for active, unlocked gimbals. + /// + /// 1 if any gimbal pitch control changed, 0 otherwise. + public double ToggleGimbalPitch() + { + bool changed = false; + bool enable = !vc.anyGimbalsPitch; + for (int i = vc.moduleGimbals.Length - 1; i >= 0; --i) + { + if (vc.moduleGimbals[i].gimbalActive && !vc.moduleGimbals[i].gimbalLock && vc.moduleGimbals[i].enablePitch != enable) + { + changed = true; + vc.moduleGimbals[i].enablePitch = enable; + } + } + + return (changed) ? 1.0 : 0.0; + } + + /// + /// Toggles roll control for active, unlocked gimbals. + /// + /// 1 if any gimbal roll control changed, 0 otherwise. + public double ToggleGimbalRoll() + { + bool changed = false; + bool enable = !vc.anyGimbalsRoll; + for (int i = vc.moduleGimbals.Length - 1; i >= 0; --i) + { + if (vc.moduleGimbals[i].gimbalActive && !vc.moduleGimbals[i].gimbalLock && vc.moduleGimbals[i].enableRoll != enable) + { + changed = true; + vc.moduleGimbals[i].enableRoll = enable; + } + } + + return (changed) ? 1.0 : 0.0; + } + + /// + /// Toggles yaw control for active, unlocked gimbals. + /// + /// 1 if any gimbal yaw control changed, 0 otherwise. + public double ToggleGimbalYaw() + { + bool changed = false; + bool enable = !vc.anyGimbalsYaw; + for (int i = vc.moduleGimbals.Length - 1; i >= 0; --i) + { + if (vc.moduleGimbals[i].gimbalActive && !vc.moduleGimbals[i].gimbalLock && vc.moduleGimbals[i].enableYaw != enable) + { + changed = true; + vc.moduleGimbals[i].enableYaw = enable; + } + } + + return (changed) ? 1.0 : 0.0; + } + + /// + /// Toggles the mode for any multi-mode engines. + /// + /// 1 if any engines were toggled, 0 if no multi-mode engines are installed. + public double ToggleMultiModeEngineMode() + { + for (int i = vc.multiModeEngines.Length - 1; i >= 0; --i) + { + vc.multiModeEngines[i].ToggleMode(); + } + + vc.InvalidateModules(); + + return (vc.multiModeEngines.Length > 0) ? 1.0 : 0.0; + } + #endregion + + /// + /// Flight status variables are in this category. + /// + #region Flight Status + /// + /// Returns 1 if the vessel is in a landed state (LANDED, SPLASHED, + /// or PRELAUNCH); 0 otherwise + /// + /// + public double VesselLanded() + { + return (vesselSituationConverted < 3) ? 1.0 : 0.0; + } + + /// + /// Returns 1 if the vessel is in a flying state (FLYING, SUB_ORBITAL, + /// ORBITING, ESCAPING, DOCKED). + /// + /// + public double VesselFlying() + { + if ((vessel.Landed || vessel.Splashed) != (vesselSituationConverted <= 2)) + { + Utility.LogMessage(this, "vessel.Landed {0} and vesselSituationConverted {1} disagree! - vessel.situation is {2}", vessel.Landed, vesselSituationConverted, vessel.situation); + } + return (vesselSituationConverted > 2) ? 1.0 : 0.0; + } + + /// + /// Returns the vessel's situation, based on the KSP variable: + /// + /// * 0 - LANDED + /// * 1 - SPLASHED + /// * 2 - PRELAUNCH + /// * 3 - FLYING + /// * 4 - SUB_ORBITAL + /// * 5 - ORBITING + /// * 6 - ESCAPING + /// * 7 - DOCKED + /// + /// A number between 0 and 7 (inclusive). + public double VesselSituation() + { + return vesselSituationConverted; + } + + /// + /// Returns the name of the vessel's situation. + /// + /// + public string VesselSituationName() + { + return vessel.SituationString; + } + #endregion + + /// + /// Variables and control methods for the Gear action group are in this + /// category. In addition, status and information methods for deployable + /// landing gear and wheels are in this category. For simplicity, landing gear + /// and wheels may be simply called "landing gear" in the descriptions. + /// + #region Gear + + /// + /// Returns the number of deployable landing gear on the craft. + /// this function only counts the parts using ModuleWheelDeployment. + /// + /// Number of deployable gear, or 0. + public double DeployableGearCount() + { + return vc.moduleWheelDeployment.Length; + } + + /// + /// Returns the number of landing gear or wheels that are broken. Returns 0 if none are, or if there + /// are no gear. + /// + /// The number of landing gear that are broken. + public double GearBrokenCount() + { + int brokenCount = 0; + + for (int i = vc.moduleWheelDamage.Length - 1; i >= 0; --i) + { + if (vc.moduleWheelDamage[i].isDamaged) + { + return ++brokenCount; + } + } + + return (double)brokenCount; + } + + /// + /// Returns the number of wheels / landing gear installed on the craft. This function counts all + /// landing gear and wheels, including those that do not deploy. + /// + /// Number of gear, or 0. + public double GearCount() + { + return vc.moduleWheelBase.Length; + } + + /// + /// Returns 1 if there are actions assigned to the landing gear AG. + /// + /// + public double GearHasActions() + { + return (vc.GroupHasActions(KSPActionGroup.Gear)) ? 1.0 : 0.0; + } + + /// + /// Returns -1 if any deployable landing gear or wheels are retracting, + /// +1 if they are extending. Otherwise returns 0. + /// + /// -1, 0, or +1. + public double GearMoving() + { + return vc.wheelDirection; + } + + /// + /// Returns a number representing the average position of undamaged deployable landing gear or wheels. + /// + /// * 0 - No deployable gear, no undamaged gear, or all undamaged gear are retracted. + /// * 1 - All deployable gear extended. + /// + /// If the gear are moving, a number between 0 and 1 is returned. + /// + /// An number between 0 and 1 as described in the summary. + public double GearPosition() + { + return vc.wheelPosition; + } + + /// + /// Returns the highest stress percentage of any non-broken landing gear in the + /// range [0, 1]. + /// + /// Highest stress percentage, or 0 if no gear/wheels. + public double GearStress() + { + float maxStress = 0.0f; + for (int i = vc.moduleWheelDamage.Length - 1; i >= 0; --i) + { + if (!vc.moduleWheelDamage[i].isDamaged) + { + maxStress = Mathf.Max(maxStress, vc.moduleWheelDamage[i].stressPercent); + } + } + + // stressPercent is a [0, 100] - convert it here for consistency + return maxStress * 0.01f; + } + + /// + /// Returns 1 if the landing gear action group is active. + /// + /// + public double GetGear() + { + return (vessel.ActionGroups[KSPActionGroup.Gear]) ? 1.0 : 0.0; + } + + /// + /// Set the landing gear action group to the specified state. + /// + /// + /// 1 if active is true, 0 otherwise. + public double SetGear(bool active) + { + vessel.ActionGroups.SetGroup(KSPActionGroup.Gear, active); + return (active) ? 1.0 : 0.0; + } + + /// + /// Toggle the landing gear action group + /// + /// 1 if the gear action group is active, 0 if not. + public double ToggleGear() + { + vessel.ActionGroups.ToggleGroup(KSPActionGroup.Gear); + return (vessel.ActionGroups[KSPActionGroup.Gear]) ? 1.0 : 0.0; + } + #endregion + + /// + /// The Grapple category controls grappling nodes ("The Claw"). + /// + /// Like the Dock category, many of these methods use the concept of "Primary Grapple". + /// The primary grapple is defined as the first or only grapple + /// found on the vessel. + /// + /// Grapples may be made reference transforms. The functions related to that are found + /// under the Dock category to be consistent with the existing reference transform + /// functionality. + /// + #region Grapple + + /// + /// Returns 1 if the primary grapple's pivot is locked, returns 0 if it is unlocked, or + /// there is no grapple. + /// + public double GetGrapplePivotLocked() + { + return (vc.clawNode != null && !vc.clawNode.IsLoose()) ? 1.0 : 0.0; + } + + /// + /// Returns 1 if the primary grapple is armed and ready for use. Returns 0 otherwise. + /// + /// 1 if the primary grapple is ready, 0 if it is not ready (in use, not armed), or there is no grapple. + public double GrappleArmed() + { + if (vc.clawNode != null) + { + return (vc.clawNodeState == MASVesselComputer.DockingNodeState.DISABLED) ? 0.0 : 1.0; + } + + return 0.0; + } + + /// + /// Returns 1 if the primary grapple is holding something. Returns 0 if it is not, or no grapple + /// is installed. + /// + /// + public double Grappled() + { + return (vc.clawNodeState == MASVesselComputer.DockingNodeState.DOCKED) ? 1.0 : 0.0; + } + + /// + /// Returns the name of the object grappled by the vessel. Returns an empty string if fc.Grappled() returns 0. + /// + /// The name of the grappled object. + public string GrappledObjectName() + { + if (vc.clawNodeState == MASVesselComputer.DockingNodeState.DOCKED) + { + if (vc.clawNode.vesselInfo != null) + { + string l10n = string.Empty; + if (KSP.Localization.Localizer.TryGetStringByTag(vc.clawNode.vesselInfo.name, out l10n)) + { + return l10n; + } + else + { + return vc.clawNode.vesselInfo.name; + } + } + } + return string.Empty; + } + + /// + /// Returns a number representing whether the primary grapple is arming, or disarming, or not changing. + /// + /// -1 if the grapple is disarming, +1 if the grapple is arming, 0 otherwise. + public double GrappleMoving() + { + if (vc.clawNode != null) + { + try + { + ModuleAnimateGeneric clawAnimation = (vc.clawNode.part.Modules[vc.clawNode.deployAnimationController] as ModuleAnimateGeneric); + if (clawAnimation != null) + { + if (clawAnimation.IsMoving()) + { + return (clawAnimation.animSpeed > 0.0f) ? 1.0 : -1.0; + } + } + } + catch + { + return 0.0; + } + } + + return 0.0; + } + + /// + /// Returns 1 if the primary grapple is armed and ready for use. Returns 0 otherwise. + /// + /// 1 if the primary grapple is ready, 0 if it is not ready, or there is no grapple. + public double GrappleReady() + { + if (vc.clawNode != null) + { + return (vc.clawNodeState == MASVesselComputer.DockingNodeState.READY) ? 1.0 : 0.0; + } + + return 0.0; + } + + /// + /// Release the primary grapple from whatever it's connected to. + /// + /// 1 if the grapple released, 0 if it did not, or there is no grapple. + public double GrappleRelease() + { + if (vc.clawNode != null) + { + if (vc.clawNodeState == MASVesselComputer.DockingNodeState.DOCKED) + { + vc.clawNode.Release(); + return 1.0; + } + else if (vc.clawNodeState == MASVesselComputer.DockingNodeState.PREATTACHED) + { + // Not sure this actually happens. + vc.clawNode.Decouple(); + return 1.0; + } + } + + return 0.0; + } + + /// + /// Indicates whether a primary grapple was found on the vessel. + /// + /// 1 if a grapple is available, 0 if none were detected. + public double HasGrapple() + { + return (vc.clawNode != null) ? 1.0 : 0.0; + } + + /// + /// Sets the arming state of the primary grapple. Has no effect if there is no grapple, or if it can not + /// be armed or disarmed, such as when it is grappling something. + /// + /// If true, and the grapple is disarmed, arm the grapple. If false, and the grapple can be disarmed, disarm the grapple. + /// 1 if the state was changed. 0 otherwise. + public double SetGrappleArmed(bool armGrapple) + { + if (vc.clawNode != null) + { + bool applyChange = false; + if (vc.clawNodeState == MASVesselComputer.DockingNodeState.DISABLED && armGrapple == true) + { + applyChange = true; + } + else if (vc.clawNodeState == MASVesselComputer.DockingNodeState.READY && armGrapple == false) + { + applyChange = true; + } + + if (applyChange) + { + try + { + ModuleAnimateGeneric clawAnimation = (vc.clawNode.part.Modules[vc.clawNode.deployAnimationController] as ModuleAnimateGeneric); + if (clawAnimation != null) + { + clawAnimation.Toggle(); + } + } + catch + { + return 0.0; + } + + return 1.0; + } + } + + return 0.0; + } + + /// + /// Change whether the primary grapple's pivot is locked on loose. + /// + /// If true, the joint is locked. If loose, it is unlocked. + /// 1 if the state is changed, 0 otherwise. + public double SetGrapplePivot(bool locked) + { + if (vc.clawNode != null) + { + if (locked && vc.clawNode.IsJointUnlocked()) + { + vc.clawNode.LockPivot(); + return 1.0; + } + else if (!locked && !vc.clawNode.IsJointUnlocked()) + { + vc.clawNode.SetLoose(); + return 1.0; + } + } + + return 0.0; + } + + /// + /// Arm or disarm the installed primary grapple. + /// + /// 1 if the grapple is arming or disarming, 0 if the grapple does not have an arming behavior, or if there is no grapple. + public double ToggleGrappleArmed() + { + if (vc.clawNode != null) + { + try + { + ModuleAnimateGeneric clawAnimation = (vc.clawNode.part.Modules[vc.clawNode.deployAnimationController] as ModuleAnimateGeneric); + if (clawAnimation != null) + { + clawAnimation.Toggle(); + } + } + catch + { + return 0.0; + } + + return 1.0; + } + + return 0.0; + } + + /// + /// Toggle the state of the primary grapple's pivot + /// + /// 1 if the state qas changed, 0 otherwise. + public double ToggleGrapplePivot() + { + if (vc.clawNode != null) + { + if (vc.clawNode.IsJointUnlocked()) + { + vc.clawNode.LockPivot(); + } + else + { + vc.clawNode.SetLoose(); + } + + return 1.0; + } + + return 0.0; + } + #endregion + + /// + /// The Lights action group can be controlled and queried through this category. + /// + #region Lights + /// + /// Returns 1 if the Lights action group has at least one action assigned to it. + /// + /// + public double LightsHasActions() + { + return (vc.GroupHasActions(KSPActionGroup.Light)) ? 1.0 : 0.0; + } + + /// + /// Returns 1 if the Lights action group is active. + /// + /// 1 if the lights action group is active, 0 otherwise. + public double GetLights() + { + return (vessel.ActionGroups[KSPActionGroup.Light]) ? 1.0 : 0.0; + } + + /// + /// Set the state of the lights action group. + /// + /// + /// 1 if the lights action group is active, 0 otherwise. + public double SetLights(bool active) + { + vessel.ActionGroups.SetGroup(KSPActionGroup.Light, active); + return (active) ? 1.0 : 0.0; + } + + /// + /// Toggle the lights action group. + /// + /// 1 if the lights action group is active, 0 otherwise. + public double ToggleLights() + { + vessel.ActionGroups.ToggleGroup(KSPActionGroup.Light); + return (vessel.ActionGroups[KSPActionGroup.Light]) ? 1.0 : 0.0; + } + #endregion + } + + /// + /// The MASProxyAttribute class is used to mark specific methods in the various + /// proxy classes as either Immutable or Uncacheable (both would be nonsensical). + /// + /// A method flagged as Immutable is evaluated once when it's created, and never + /// again (useful for values that never change in a game session). + /// + /// A method flagged as Dependent is a value that can change, but it does not need + /// to be queried each FixedUpdate. + /// + /// A method flagged as Persistent is a persistent value query. Provided the string + /// parameter is a constant, it will only be re-evaluated when the persistent value + /// is updated. + /// + /// A method flagged as Uncacheable is expected to change each time it's called, + /// such as random number generators. + /// + /// These attributes affect only variables that can be transformed to a + /// native evaluator - Lua scripts are always cacheable + mutable. + /// + [AttributeUsage(AttributeTargets.Method)] + public class MASProxyAttribute : System.Attribute + { + private bool immutable; + private bool dependent; + private bool persistent; + private bool uncacheable; + + public bool Immutable + { + get + { + return immutable; + } + set + { + immutable = value; + } + } + + public bool Dependent + { + get + { + return dependent; + } + set + { + dependent = value; + } + } + + public bool Persistent + { + get + { + return persistent; + } + set + { + persistent = value; + } + } + + public bool Uncacheable + { + get + { + return uncacheable; + } + set + { + uncacheable = value; + } + } + } +} diff --git a/ModifiedCode/MASFlightComputerProxy2.cs b/ModifiedCode/MASFlightComputerProxy2.cs new file mode 100644 index 00000000..c121d970 --- /dev/null +++ b/ModifiedCode/MASFlightComputerProxy2.cs @@ -0,0 +1,4187 @@ +/***************************************************************************** + * The MIT License (MIT) + * + * Copyright (c) 2016-2020 MOARdV + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + ****************************************************************************/ +using KSP.UI; +using KSP.UI.Screens; +using MoonSharp.Interpreter; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Text; +using UnityEngine; + +namespace AvionicsSystems +{ + // ΔV - put this somewhere where I can find it easily to copy/paste + + /// + /// The flight computer proxy provides the interface between the flight + /// computer module and the variable / Lua environment. + /// + /// While it is a wrapper for MASFlightComputer, not all + /// values are plumbed through to the flight computer (for instance, the + /// action group control and state are all handled in this class). + /// + /// fc + /// + /// The `fc` group contains the core interface between KSP, Avionics + /// Systems, and props in an IVA. It consists of many 'information' functions + /// that can be used to get information as well as numerous 'action' functions + /// that are used to do things. + /// + /// Due to the number of methods in the `fc` group, this document has been split + /// across three pages: + /// + /// * [[MASFlightComputerProxy]] (Abort - Lights), + /// * [[MASFlightComputerProxy2]] (Maneuver Node - Reaction Wheel), and + /// * [[MASFlightComputerProxy3]] (Resources - Vessel Info). + /// + /// **NOTE 1:** If a function listed below includes an entry for 'Supported Mod(s)', + /// then that function will automatically use one of the mods listed to + /// generate the data. In some cases, it is possible that the function does not work without + /// one of the required mods. Those instances are noted in the function's description. + /// + /// **NOTE 2:** Many descriptions make use of mathetmatical short-hand to describe + /// a range of values. This short-hand consists of using square brackets `[` and `]` + /// to denote "inclusive range", while parentheses `(` and `)` indicate exclusive range. + /// + /// For example, if a parameter says "an integer between [0, `fc.ExperimentCount()`)", it + /// means that the parameter must be an integer greater than or equal to 0, but less + /// than `fc.ExperimentCount()`. + /// + /// For another example, if a parameter says "a number in the range [0, 1]", it means that + /// the number must be at least zero, and it must not be larger than 1. + /// + internal partial class MASFlightComputerProxy + { + private const string siPrefixes = " kMGTPEZY"; + + [MoonSharpHidden] + private IEnumerator RecoverVesselLater() + { + yield return MASConfig.waitForFixedUpdate; + // Let the FixedUpdate finish before we recover + GameEvents.OnVesselRecoveryRequested.Fire(vessel); + } + + [MoonSharpHidden] + private string DoSIFormatLessThan1(double value, int length, int minDecimal, string delimiter, bool forceSign, bool showPrefix) + { + // '#.' + int nonDecimalChars = 2; + // Sign + if (forceSign || value < 0.0) + { + ++nonDecimalChars; + } + // add the blank prefix char + if (showPrefix) + { + ++nonDecimalChars; + --length; + } + + var result = StringBuilderCache.Acquire(); + result.AppendFormat("{{0,{0}:0", length); + int netDecimal = Math.Max(minDecimal, length - nonDecimalChars); + if (netDecimal > 0) + { + result.Append('.').Append('0', netDecimal); + } + result.Append("}"); + if (showPrefix) + { + result.Append(" "); + } + return string.Format(result.ToStringAndRelease(), value); + } + + [MoonSharpHidden] + private string DoSIFormat(double value, int length, int minDecimal, string delimiter, bool forceSign, bool showPrefix) + { + if (double.IsInfinity(value) || double.IsNaN(value)) + { + // Force illegal values to 0. + value = 0.0; + } + + //Utility.LogMessage(this, "DoSIFormat {0}, {1}, {2}, x, {3}, {4}", value, length, minDecimal, forceSign, showPrefix); + if (Math.Abs(value) < 1.0) + { + // special case: abs(value) < 1 + return DoSIFormatLessThan1(value, length, minDecimal, delimiter, forceSign, showPrefix); + } + int leadingDigitExponent; + leadingDigitExponent = (int)Math.Floor(Math.Log10(Math.Abs(value))); + if (leadingDigitExponent / 3 >= siPrefixes.Length) + { + // If the number is too big to handle, treat it as zero. Note that float infinity + // makes it past the tests above. + return DoSIFormatLessThan1(0.0, length, minDecimal, delimiter, forceSign, showPrefix); + } + + // How many characters need to be set aside? + int reservedCharacters = (minDecimal > 0) ? (1 + minDecimal) : 0; + if (showPrefix) + { + ++reservedCharacters; + } + if (value < 0.0 || forceSign) + { + ++reservedCharacters; + } + + // How much space is there to work with? + int freeCharacters = length - reservedCharacters; + + // Nearest SI exponent to the value. + int siExponent = Math.Min(leadingDigitExponent / 3, siPrefixes.Length - 1) * 3; + + int digits = leadingDigitExponent - siExponent; + double scaledInputValue = Math.Round(value / Math.Pow(10.0, siExponent), minDecimal); + int scaledLength = (Math.Abs(scaledInputValue) > 0.0) ? (int)Math.Floor(Math.Log10(Math.Abs(scaledInputValue))) + 1 : 1; + int groupLen = (string.IsNullOrEmpty(delimiter)) ? 3 : 4; + + int freeCh2 = freeCharacters - ((scaledLength == 4) ? groupLen + 1 : scaledLength); + //Utility.LogMessage(this, "freeCh2 => {0}, si = {1}", freeCh2, siExponent); + while (freeCh2 >= groupLen && siExponent >= 3) + { + freeCh2 -= groupLen; + siExponent -= 3; + //Utility.LogMessage(this, "freeCh2 => {0}, si = {1}", freeCh2, siExponent); + } + if (minDecimal == 0 && freeCh2 > 0) + { + // Reserve a space for the decimal + --freeCh2; + } + minDecimal += freeCh2; + scaledInputValue = Math.Round(value / Math.Pow(10.0, siExponent), minDecimal); + + // TODO: Make a cache of format strings based on length, minDecimal (as adjusted at this point), and showPrefix. + + //Utility.LogMessage(this, "leadExp = {1}, siExp = {2}, reserve = {0}, freeCh = {3}, scaledIn = {6}, scaledLen = {7}, freeCh2 = {5}", + // reservedCharacters, + // leadingDigitExponent, + // siExponent, + // freeCharacters, + // digits, + // freeCh2, + // scaledInputValue, + // scaledLength); + var result = StringBuilderCache.Acquire(); + result.AppendFormat("{{0,{0}:", length); + if (groupLen == 4) + { + result.Append("#,#"); + } + result.Append("0"); + if (minDecimal > 0) + { + result.Append('.').Append('0', minDecimal); + } + result.Append("}"); + if (showPrefix) + { + result.Append("{1}"); + } + + if (siExponent < 0 || siExponent >= siPrefixes.Length) + { + Utility.LogWarning(this, "Formatting the value {0}, got an exponent of {1} (out of bounds). Jamming the value to 0 to avoid an exception.", + value, siExponent); + scaledInputValue = 0.0; + siExponent = 0; + } + //Utility.LogMessage(this, "\"{0}\" -> \"{1}\"", result.ToString(), string.Format(result.ToString(), scaledInputValue, 'Q')); + return string.Format(result.ToStringAndRelease(), scaledInputValue, siPrefixes[siExponent / 3]); + } + + /// + /// Methods for querying and controlling maneuver nodes are in this category. + /// + #region Maneuver Node + + /// + /// Replace any scheduled maneuver nodes with this maneuver node. + /// + /// ΔV in the prograde direction at the time of the maneuver, in m/s. + /// ΔV in the normal direction at the time of the maneuver, in m/s. + /// ΔV in the radial direction at the time of the maneuver, in m/s. + /// UT to schedule the maneuver, in seconds. + /// 1 if the manuever node was created, 0 on any errors. + public double AddManeuverNode(double progradedV, double normaldV, double radialdV, double timeUT) + { + if (vessel.patchedConicSolver != null) + { + if (double.IsNaN(progradedV) || double.IsInfinity(progradedV) || + double.IsNaN(normaldV) || double.IsInfinity(normaldV) || + double.IsNaN(radialdV) || double.IsInfinity(radialdV) || + double.IsNaN(timeUT) || double.IsInfinity(timeUT)) + { + // bad parameters? + return 0.0; + } + + Vector3d dV = new Vector3d(radialdV, normaldV, progradedV); + + // No living in the past. + timeUT = Math.Max(timeUT, vc.universalTime); + + vessel.patchedConicSolver.maneuverNodes.Clear(); + ManeuverNode mn = vessel.patchedConicSolver.AddManeuverNode(timeUT); + mn.OnGizmoUpdated(dV, timeUT); + + return 1.0; + } + + return 0.0; + } + + /// + /// Clear all scheduled maneuver nodes. + /// + /// 1 if any nodes were cleared, 0 if no nodes were cleared. + public double ClearManeuverNode() + { + if (vessel.patchedConicSolver != null) + { + int nodeCount = vessel.patchedConicSolver.maneuverNodes.Count; + // TODO: what is vessel.patchedConicSolver.flightPlan? And do I care? + for (int i = nodeCount - 1; i >= 0; --i) + { + vessel.patchedConicSolver.RemoveManeuverNode(vessel.patchedConicSolver.maneuverNodes[i]); + } + + return (nodeCount > 0) ? 1.0 : 0.0; + } + + return 0.0; + } + + /// + /// Clear first scheduled maneuver node. + /// + /// 1 if any nodes were cleared, 0 if no nodes were cleared. + public double ClearOneManeuverNode() + { + if (vessel.patchedConicSolver != null) + { + int nodeCount = vessel.patchedConicSolver.maneuverNodes.Count; + // TODO: what is vessel.patchedConicSolver.flightPlan? And do I care? + vessel.patchedConicSolver.maneuverNodes[0].RemoveSelf(); + + return (nodeCount > 0) ? 1.0 : 0.0; + } + + return 0.0; + } + + /// + /// Returns the apoapsis of the orbit that results from the scheduled maneuver. + /// + /// New Ap in meters, or 0 if no node is scheduled. + public double ManeuverNodeAp() + { + if (vc.maneuverNodeValid) + { + return vc.nodeOrbit.ApA; + } + else + { + return 0.0; + } + } + + /// + /// Returns an estimate of the maneuver node burn time, in seconds. + /// + /// Approximate burn time in seconds, or 0 if no node is scheduled. + public double ManeuverNodeBurnTime() + { + return vc.NodeBurnTime(); + } + + /// + /// Delta-V remaining for the next scheduled node. + /// + /// ΔV in m/s, or 0 if no node is scheduled. + public double ManeuverNodeDV() + { + return vc.maneuverNodeDeltaV; + } + + /// + /// The normal component of the next scheduled maneuver. + /// + /// ΔV in m/s; negative values indicate anti-normal. + public double ManeuverNodeDVNormal() + { + if (vc.maneuverNodeValid && vc.nodeOrbit != null) + { + return vc.maneuverNodeComponent.y; + } + return 0.0; + } + + /// + /// The prograde component of the next scheduled maneuver. + /// + /// ΔV in m/s; negative values indicate retrograde. + public double ManeuverNodeDVPrograde() + { + if (vc.maneuverNodeValid && vc.nodeOrbit != null) + { + return vc.maneuverNodeComponent.x; + } + return 0.0; + } + + /// + /// The radial component of the next scheduled maneuver. + /// + /// ΔV in m/s; negative values indicate anti-radial. + public double ManeuverNodeDVRadial() + { + if (vc.maneuverNodeValid && vc.nodeOrbit != null) + { + return vc.maneuverNodeComponent.z; + } + return 0.0; + } + + /// + /// Returns the eccentricity of the orbit that results from the scheduled maneuver. + /// + /// New eccentricity, or 0 if no node is scheduled. + public double ManeuverNodeEcc() + { + if (vc.maneuverNodeValid) + { + return vc.nodeOrbit.eccentricity; + } + else + { + return 0.0; + } + } + + /// + /// Returns 1 if there is a valid maneuver node; 0 otherwise + /// + /// + public double ManeuverNodeExists() + { + return (vc.maneuverNodeValid) ? 1.0 : 0.0; + } + + /// + /// Returns the inclination of the orbit that results from the scheduled maneuver. + /// + /// New inclination in degrees, or 0 if no node is scheduled. + public double ManeuverNodeInc() + { + if (vc.maneuverNodeValid) + { + return vc.nodeOrbit.inclination; + } + else + { + return 0.0; + } + } + + /// + /// Returns the index to the body that the vessel will encounter after a change in SoI caused by + /// a scheduled maneuver node. If there is no maneuver node, or the vessel does not change SoI + /// because of the maneuver, returns -1 (current body). + /// + /// + public double ManeuverNodeNextBody() + { + if (vc.maneuverNodeValid && vesselSituationConverted > 2) + { + if (vc.nodeOrbit.patchEndTransition == Orbit.PatchTransitionType.ENCOUNTER || vc.nodeOrbit.patchEndTransition == Orbit.PatchTransitionType.ESCAPE) + { + Orbit o = vc.nodeOrbit.nextPatch; + if (o != null) + { + int bodiesCount = FlightGlobals.Bodies.Count; + for (int i = 0; i < bodiesCount; ++i) + { + if (FlightGlobals.Bodies[i].name == o.referenceBody.name) + { + return i; + } + } + } + } + } + + return -1.0; + } + + /// + /// Returns 1 if the SoI change after a scheduled maneuver is an 'encounter', -1 if it is an + /// 'escape', and 0 if the scheduled orbit does not change SoI. + /// + /// 0 if the orbit does not transition. 1 if the vessel will encounter a body, -1 if the vessel will escape the current body. + public double ManeuverNodeNextSoI() + { + if (vc.maneuverNodeValid && vesselSituationConverted > 2) + { + if (vc.nodeOrbit.patchEndTransition == Orbit.PatchTransitionType.ENCOUNTER) + { + return 1.0; + } + else if (vc.nodeOrbit.patchEndTransition == Orbit.PatchTransitionType.ESCAPE) + { + return -1.0; + } + } + + return 0.0; + } + + /// + /// Returns the periapsis of the orbit that results from the scheduled maneuver. + /// + /// New Pe in meters, or 0 if no node is scheduled. + public double ManeuverNodePe() + { + if (vc.maneuverNodeValid) + { + return vc.nodeOrbit.PeA; + } + else + { + return 0.0; + } + } + + /// + /// Returns the relative inclination of the target that will result from the + /// scheduled maneuver. + /// + /// New relative inclination in degrees, or 0 if there is no maneuver node, + /// no target, or the target orbits a different body. + public double ManeuverNodeRelativeInclination() + { + if (vc.maneuverNodeValid && vc.targetType > 0 && vc.targetOrbit.referenceBody == vc.nodeOrbit.referenceBody) + { + return Vector3.Angle(vc.nodeOrbit.GetOrbitNormal(), vc.targetOrbit.GetOrbitNormal()); + } + else + { + return 0.0; + } + } + + /// + /// Closest approach to the target after the next maneuver completes, in meters. If there + /// is no maneuver scheduled, or no target, returns 0. + /// + /// Closest approach to the target, or 0. + public double ManeuverNodeTargetClosestApproachDistance() + { + if (vc.maneuverNodeValid && vc.targetType > 0) + { + if (!nodeApproachSolver.resultsReady) + { + if (vc.targetType == MASVesselComputer.TargetType.CelestialBody) + { + nodeApproachSolver.SolveBodyIntercept(vc.nodeOrbit, vc.activeTarget as CelestialBody); + } + else + { + nodeApproachSolver.SolveOrbitIntercept(vc.nodeOrbit, vc.targetOrbit); + } + } + + if (vc.targetType == MASVesselComputer.TargetType.CelestialBody) + { + return Math.Max(0.0, nodeApproachSolver.targetClosestDistance - (vc.activeTarget as CelestialBody).Radius); + } + else + { + return nodeApproachSolver.targetClosestDistance; + } + } + else + { + return 0.0; + } + } + + /// + /// Relative speed of the target at closest approach after the scheduled maneuver, in m/s. If there + /// is no maneuver scheduled, or no target, returns 0. + /// + /// Relative speed of the target at closest approach after the maneuver, m/s. + public double ManeuverNodeTargetClosestApproachSpeed() + { + if (vc.maneuverNodeValid && vc.targetType > 0) + { + if (!nodeApproachSolver.resultsReady) + { + if (vc.targetType == MASVesselComputer.TargetType.CelestialBody) + { + nodeApproachSolver.SolveBodyIntercept(vc.nodeOrbit, vc.activeTarget as CelestialBody); + } + else + { + nodeApproachSolver.SolveOrbitIntercept(vc.nodeOrbit, vc.targetOrbit); + } + } + return nodeApproachSolver.targetClosestSpeed; + } + else + { + return 0.0; + } + } + + /// + /// Time when the closest approach with a target occurs after the scheduled maneuver, in seconds. If there + /// is no maneuver scheduled, or no target, returns 0. + /// + /// Time until closest approach after the maneuver. + public double ManeuverNodeTargetClosestApproachTime() + { + if (vc.maneuverNodeValid && vc.targetType > 0) + { + if (!nodeApproachSolver.resultsReady) + { + if (vc.targetType == MASVesselComputer.TargetType.CelestialBody) + { + nodeApproachSolver.SolveBodyIntercept(vc.nodeOrbit, vc.activeTarget as CelestialBody); + } + else + { + nodeApproachSolver.SolveOrbitIntercept(vc.nodeOrbit, vc.targetOrbit); + } + } + return Math.Max(nodeApproachSolver.targetClosestUT - Planetarium.GetUniversalTime(), 0.0); + } + else + { + return 0.0; + } + } + + /// + /// Returns time in seconds until the maneuver node; 0 if no node is + /// valid. + /// + /// + public double ManeuverNodeTime() + { + return vc.maneuverNodeTime; + } + + /// + /// Returns the time to the next SoI transition in the scheduled maneuver, in seconds. If the planned orbit does not change + /// SoI, or no node is scheduled, returns 0. + /// + /// Time until the next SoI after the scheduled maneuver, or 0 + public double ManeuverNodeTimeToNextSoI() + { + if (vc.maneuverNodeValid && + vc.nodeOrbit.patchEndTransition == Orbit.PatchTransitionType.ENCOUNTER || + vc.nodeOrbit.patchEndTransition == Orbit.PatchTransitionType.ESCAPE) + { + return vc.nodeOrbit.UTsoi - Planetarium.GetUniversalTime(); + } + return 0.0; + } + + /// + /// Total Delta-V required for the next scheduled node. This value does not account for any + /// maneuvering. + /// + /// ΔV in m/s, or 0 if no node is scheduled. + public double ManeuverNodeTotalDV() + { + return vc.maneuverNodeTotalDeltaV; + } + #endregion + + /// + /// Vessel mass may be queried with these methods. + /// + #region Mass + /// + /// Returns the mass of the vessel in tonnes. + /// + /// wet mass if true, dry mass otherwise + /// Vessel mass in tonnes. + public double Mass(bool wetMass) + { + if (wetMass) + { + return vessel.totalMass; + } + else + { + return vessel.totalMass - vc.totalResourceMass; + } + } + #endregion + + /// + /// Provides MAS-native methods for common math primitives. These methods generally + /// duplicate the functions in the Lua math table, but by placing them in MAS, MAS + /// can use native delegates instead of having to call into Lua (which is slower). + /// + /// This region also contains other useful mathematical methods. + /// + #region Math + + [MASProxy(Dependent = true)] + /// + /// Returns the absolute value of `value`. + /// + /// The absolute value of `value`. + public double Abs(double value) + { + return Math.Abs(value); + } + + [MASProxy(Dependent = true)] + /// + /// Returns 1 if `value` is at least equal to `lowerBound` and not greater + /// than `upperBound`. Returns 0 otherwise. + /// + /// In other words, + /// * If `value` >= `lowerBound` and `value` <= `upperBound`, return 1. + /// * Otherwise, reutrn 0. + /// + /// The value to test. + /// The lower bound (inclusive) of the range to test. + /// The upper bound (inclusive) of the range to test. + /// 1 if `value` is between `lowerBound` and `upperBound`, 0 otherwise. + public double Between(double value, double lowerBound, double upperBound) + { + return (value >= lowerBound && value <= upperBound) ? 1.0 : 0.0; + } + + [MASProxy(Dependent = true)] + /// + /// Converts `value1` and `value2` to 32 bit integers and applies a bitwise-AND + /// operation. + /// + /// An integer value. + /// An integer value. + /// value1 AND value2 + public double BitwiseAnd(double value1, double value2) + { + int v1 = (int)value1; + int v2 = (int)value2; + + return (double)(v1 & v2); + } + + [MASProxy(Dependent = true)] + /// + /// Converts `value1` and `value2` to 32 bit integers and applies a bitwise-OR + /// operation. + /// + /// An integer value. + /// An integer value. + /// value1 OR value2 + public double BitwiseOr(double value1, double value2) + { + int v1 = (int)value1; + int v2 = (int)value2; + + return (double)(v1 | v2); + } + + [MASProxy(Dependent = true)] + /// + /// Converts `value` to a 32 bit value and applies a bitwise-negation + /// operation. + /// + /// An integer value. + /// ~value + public double BitwiseNegate(double value) + { + int v1 = (int)value; + + return (double)(~v1); + } + + [MASProxy(Dependent = true)] + /// + /// Converts `value1` and `value2` to 32 bit integers and applies a bitwise-XOR + /// operation. + /// + /// An integer value. + /// An integer value. + /// value1 XOR value2 + public double BitwiseXor(double value1, double value2) + { + int v1 = (int)value1; + int v2 = (int)value2; + + return (double)(v1 ^ v2); + } + + [MASProxy(Dependent = true)] + /// + /// Rounds a number up to the next integer. + /// + /// The value to round + /// + public double Ceiling(double value) + { + return Math.Ceiling(value); + } + + [MASProxy(Dependent = true)] + /// + /// Clamps `value` to stay within the range `a` to `b`, inclusive. `a` does not + /// have to be less than `b`. + /// + /// The value to clamp. + /// The first bound. + /// The second bound. + /// The clamped value. + public double Clamp(double value, double a, double b) + { + double max = Math.Max(a, b); + double min = Math.Min(a, b); + return Math.Max(Math.Min(value, max), min); + } + + [MASProxy(Dependent = true)] + /// + /// Rounds a number down to the next integer. + /// + /// The value to round + /// + public double Floor(double value) + { + return Math.Floor(value); + } + + [MASProxy(Dependent = true)] + /// + /// Provides an "inverse lerp" of value, returning a value between 0 and 1 + /// depending on where `value` falls between `range1` and `range2`. + /// + /// If `range1` == `range2`, or if `value` < `range1`, it returns 0. + /// + /// If `value` > `range2`, it returns 1. + /// + /// The value to evaluate. + /// The first bound of the range. + /// The second bound of the range. + /// A value in the range [0, 1] as described in the summary. + public double InverseLerp(double value, double range1, double range2) + { + if (range1 == range2) + { + return 0.0; + } + else if (range1 > range2) + { + value = -value; + range1 = -range1; + range2 = -range2; + } + + if (value <= range1) + { + return 0.0; + } + else if (value >= range2) + { + return 1.0; + } + + return (value - range1) / (range2 - range1); + } + + [MASProxy(Dependent = true)] + /// + /// Return the larger value + /// + /// The first value to test. + /// The second value to test. + /// `a` if `a` is larger than `b`; `b` otherwise. + public double Max(double a, double b) + { + return Math.Max(a, b); + } + + [MASProxy(Dependent = true)] + /// + /// Return the smaller value + /// + /// The first value to test. + /// The second value to test. + /// `a` if `a` is smaller than `b`; `b` otherwise. + public double Min(double a, double b) + { + return Math.Min(a, b); + } + + [MASProxy(Dependent = true)] + /// + /// Normalizes an angle to the range [0, 360). + /// + /// The de-normalized angle to correct. + /// A value between 0 (inclusive) and 360 (exclusive). + public double NormalizeAngle(double angle) + { + return Utility.NormalizeAngle(angle); + } + + [MASProxy(Dependent = true)] + /// + /// Normalizes an angle to the range [-180, 180). + /// + /// The de-normalized angle to correct. + /// A value between -180 (inclusive) and +180 (exclusive). + public double NormalizeLongitude(double angle) + { + return Utility.NormalizeLongitude(angle); + } + + [MASProxy(Dependent = true)] + /// + /// Converts an angle to a pitch value in the range of [-90, +90]. + /// + /// The angle to convert + /// A value between -90 and +90, inclusive. + public double NormalizePitch(double angle) + { + double pitch = Utility.NormalizeLongitude(angle); + if (pitch > 90.0) + { + return 180.0 - pitch; + } + else if (pitch < -90.0) + { + return -180.0 - pitch; + } + else + { + return pitch; + } + } + + [MASProxy(Dependent = true)] + /// + /// Apply a log10-like curve to the value. + /// + /// The exact formula is: + /// + /// ``` + /// if (abs(sourceValue) < 1.0) + /// return sourceValue; + /// else + /// return (1 + Log10(abs(sourceValue))) * Sign(sourceValue); + /// end + /// ``` + /// + /// An input number + /// A Log10-like representation of the input value. + public double PseudoLog10(double sourceValue) + { + double absValue = Math.Abs(sourceValue); + if (absValue <= 1.0) + { + return sourceValue; + } + else + { + return (1.0f + Math.Log10(absValue)) * Math.Sign(sourceValue); + } + } + + [MASProxy(Dependent = true)] + /// + /// Round the given value towards zero (round down for positive values, + /// round up for negative values). + /// + /// + /// + public double RoundZero(double sourceValue) + { + if (sourceValue < 0.0) + { + return Math.Ceiling(sourceValue); + } + else + { + return Math.Floor(sourceValue); + } + } + + [MASProxy(Dependent = true)] + /// + /// Divides `numerator` by `denominator`. If the denominator is zero, this method + /// returns 0 instead of infinity or throwing a divide-by-zero exception. + /// + /// The numerator + /// The denominator + /// numerator / denominator, or 0 if the denominator is zero. + public double SafeDivide(double numerator, double denominator) + { + if (Math.Abs(denominator) > 0.0) + { + return numerator / denominator; + } + else + { + return 0.0; + } + } + + [MASProxy(Dependent = true)] + /// + /// Returns the remainder of `numerator` divided by `denominator`. If the denominator is zero, this method + /// returns 0 instead of infinity or throwing a divide-by-zero exception. + /// + /// The numerator + /// The denominator + /// A value between 0 and `denominator`, or 0 if the denominator is zero. + public double SafeModulo(double numerator, double denominator) + { + if (Math.Abs(denominator) > 0.0) + { + return numerator % denominator; + } + else + { + return 0.0; + } + } + + #endregion + + /// + /// Meta variables and functions are variables provide information about the + /// game, as opposed to the vessel. They also include the `fc.Conditioned()` + /// functions, which can provide some realism by disrupting lighting under + /// low power or high G situations. + /// + #region Meta + + [MASProxyAttribute(Dependent = true)] + /// + /// Checks for the existence of the named assembly (eg, `fc.AssemblyLoaded("MechJeb2")`). + /// This can be used to determine + /// if a particular mod has been installed when that mod is not directly supported by + /// Avionics Systems. + /// + /// 1 if the named assembly is loaded, 0 otherwise. + public double AssemblyLoaded(string assemblyName) + { + return MASLoader.knownAssemblies.Contains(assemblyName) ? 1.0 : 0.0; + } + + /// + /// Cancel time warp. + /// + /// If true, time warp is immediately set to x1. If false, time warp counts downward like in normal gameplay. + /// 1 if time warp was successfully adjusted, 0 if it could not be adjusted. + public double CancelTimeWarp(bool instantCancel) + { + if (TimeWarp.fetch != null) + { + TimeWarp.fetch.CancelAutoWarp(); + TimeWarp.SetRate(0, instantCancel); + + return 1.0; + } + else + { + return 0.0; + } + } + + [MASProxy(Dependent = true)] + /// + /// Returns the requested color channel for the `namedColor` specified. The parameter + /// `channel` may be 0 (for red), 1 (for green), 2 (for blue), or 3 (for alpha). + /// + /// Invalid channels or invalid named colors will result in a 255 being returned. + /// + /// The named color to look up. + /// The channel to return (0, 1, 2, or 3). + /// A value between [0, 255]. + public double ColorComponent(string namedColor, double channel) + { + int i_channel = (int)channel; + Color32 namedColorValue; + if (fc.TryGetNamedColor(namedColor, out namedColorValue) && i_channel >= 0 && i_channel < 4) + { + switch (i_channel) + { + case 0: + return namedColorValue.r; + case 1: + return namedColorValue.g; + case 2: + return namedColorValue.b; + case 3: + return namedColorValue.a; + } + } + + return 255.0; + } + + [MASProxy(Dependent = true)] + /// + /// Converts the supplied RGB value into a MAS text color tag + /// (eg, `fc.ColorTag(255, 255, 0)` returns "[#ffff00]"). + /// This value is slightly more efficient if you do not need to + /// change the alpha channel. + /// + /// All values are clamped to the range 0 to 255. + /// + /// Red channel value, [0, 255] + /// Green channel value, [0, 255] + /// Blue channel value, [0, 255] + /// The color tag that represents the requested color. + public string ColorTag(double red, double green, double blue) + { + int r = Mathf.Clamp((int)(red + 0.5), 0, 255); + int g = Mathf.Clamp((int)(green + 0.5), 0, 255); + int b = Mathf.Clamp((int)(blue + 0.5), 0, 255); + return string.Format("[#{0:X2}{1:X2}{2:X2}]", r, g, b); + } + + [MASProxy(Dependent = true)] + /// + /// Converts the supplied RGBA value into a MAS text color tag + /// (eg, `fc.ColorTag(255, 255, 0, 255)` returns "[#ffff00ff]"). + /// + /// All values are clamped to the range 0 to 255. + /// + /// Red channel value, [0, 255] + /// Green channel value, [0, 255] + /// Blue channel value, [0, 255] + /// Alpha channel value, [0, 255] + /// The color tag that represents the requested color. + public string ColorTag(double red, double green, double blue, double alpha) + { + int r = Mathf.Clamp((int)(red + 0.5), 0, 255); + int g = Mathf.Clamp((int)(green + 0.5), 0, 255); + int b = Mathf.Clamp((int)(blue + 0.5), 0, 255); + int a = Mathf.Clamp((int)(alpha + 0.5), 0, 255); + return string.Format("[#{0:X2}{1:X2}{2:X2}{3:X2}]", r, g, b, a); + } + + [MASProxy(Dependent = true)] + /// + /// Looks up the [Named Color](https://github.com/MOARdV/AvionicsSystems/wiki/Named-Colors) `namedColor`, and returns its value as a color tag. + /// For instance, `fc.ColorTag("COLOR_XKCD_KSPUNNAMEDCYAN")` will return "[#5fbdb9ff]". + /// + /// If an invalid color is selected, this function returns bright magenta "[#ff00ff]". + /// + /// The named color to look up. + /// The color tag that represents the named color. + public string ColorTag(string namedColor) + { + Color32 namedColorValue; + if (fc.TryGetNamedColor(namedColor, out namedColorValue)) + { + return string.Format("[#{0:X2}{1:X2}{2:X2}{3:X2}]", namedColorValue.r, namedColorValue.g, namedColorValue.b, namedColorValue.a); + } + else + { + return "[#ff0ff]"; + } + } + + /// + /// Applies some "realism" conditions to the variable to cause it to + /// return zero under the following conditions: + /// + /// 1) When there is no power available (the config-file-specified + /// power variable is below 0.0001), or + /// + /// 2) The craft is under high g-loading. G-loading limits are defined + /// in the per-pod config file. When these limits are exceeded, there + /// is a chance (also defined in the config file) of the variable being + /// interrupted. This chance increases as the g-forces exceed the + /// threshold using a square-root curve, or + /// + /// 3) The optional variable `powerOnVariable` in MASFlightComputer returns + /// 0 or less. + /// + /// The variable `fc.Conditioned(1)` behaves the same as the RasterPropMonitor + /// ASET Props custom variable `CUSTOM_ALCOR_POWEROFF`, with an inverted + /// value (`CUSTOM_ALCOR_POWEROFF` returns 1 to indicate "disrupt", but + /// `fc.Conditioned(1)` returns 0 instead). + /// + /// For boolean parameters, `true` is treated as 1, and `false` is treated + /// as 0. + /// + /// A numeric value or a boolean + /// `value` if the conditions above are not met. + public double Conditioned(double value) + { + if (fc.isPowered && UnityEngine.Random.value > fc.disruptionChance) + { + return value; + } + else + { + return 0.0; + } + } + public double Conditioned(bool value) + { + if (value && fc.isPowered && UnityEngine.Random.value > fc.disruptionChance) + { + return 1.0; + } + else + { + return 0.0; + } + } + + /// + /// Applies some "realism" conditions to the variable to cause it to + /// return 'defaultValue' under the following conditions: + /// + /// 1) When there is no power available (the config-file-specified + /// power variable is below 0.0001), or + /// + /// 2) The craft is under high g-loading. G-loading limits are defined + /// in the per-pod config file. When these limits are exceeded, there + /// is a chance (also defined in the config file) of the variable being + /// interrupted. This chance increases as the g-forces exceed the + /// threshold using a square-root curve, or + /// + /// 3) The optional variable `powerOnVariable` in MASFlightComputer returns + /// 0 or less. + /// + /// This variant of 'fc.Conditioned()' is intended to be used with ANIMATION or + /// other nodes where the 'off' position is not the same as the 0 position. If the + /// off position should be 0, use the single-parameter fc.Conditioned. + /// + /// A numeric value or a boolean + /// The value that is returned if the conditions described + /// in the summary are not met. + /// `value` if the conditions above are not met. + public double Conditioned(double value, double defaultValue) + { + if (fc.isPowered && UnityEngine.Random.value > fc.disruptionChance) + { + return value; + } + else + { + return defaultValue; + } + } + + /// + /// Returns the value of a debug 'register' in MAS. + /// + /// **NOTE:** The debug registers are used for development and debugging purposes. + /// Bugs filed against this method will be summarily dismissed. + /// + /// An integer between 0 and the length of the debug array. + /// Contents of the debug value, or an empty string. + public object DebugValue(double index) + { + int idx = (int)index; + if ((idx >= 0 && idx < vc.debugValue.Length) || vc.debugValue[idx] == null) + { + return vc.debugValue[idx]; + } + else + { + return string.Empty; + } + } + + /// + /// Returns the current Flight UI Mode. + /// + /// The Flight UI mode is the UI typically in the lower-left corner of the + /// screen during flight when not in IVA. It may be one of the following + /// values: + /// * 0 - Staging Mode + /// * 1 - Docking Mode + /// * 2 = Map Mode + /// * 3 = Maneuver Edit Mode + /// * 4 = Maneuver Info Mode + /// + /// A mode number as described above, or -1 if the mode cannot be queried. + public double FlightUIMode() + { + var ui = FlightUIModeController.Instance; + if (ui != null) + { + return (double)ui.Mode; + } + + return -1.0; + } + + [MASProxy(Dependent = true)] + /// + /// Applies `parameter` to `formatString`, returning the result. + /// + /// The `formatString` parameter uses the MAS custom delimeters <= and =>. + /// Only one variable may be substituted using `formatString`. + /// + /// *Ex 1:* `fc.FormatString("<=0:0.0=>", fc.GetAltitude())` + /// + /// *Ex 2:* `fc.FormatString(fc.Select(fc.GetPersistentAsNumber("Precision"), "<=0:0.0=>", "<=0:0.000=>"), fc.GetAltitude())` + /// + /// The C# format string, with at most one variable field (eg, <=0=>). + /// The argument to formatString + /// The formatted string, or an empty string if an invalid parameter was supplied. + public string FormatString(string format, object arg0) + { + string result = string.Empty; + try + { + result = string.Format(MdVTextMesh.formatter, format, arg0); + } + catch + { + result = string.Empty; + } + + return result; + } + + /// + /// Returns 1 if any of the throttle keys (full throttle, cut throttle, throttle up, throttle down) + /// were pressed on the keyboard since the last Fixed Update, or if any of those keys are still + /// being pressed. + /// + /// 1 if any throttle keys are being pressed, 0 otherwise. + public double GetThrottleKeyPressed() + { + return fc.anyThrottleKeysPressed ? 1.0 : 0.0; + } + + /// + /// Returns the number of hours per day on Kerbin. With a stock installation, this is 6 hours or + /// 24 hours, depending on whether the Kerbin calendar or Earth calendar is selected. Mods + /// may change this to a different value. + /// + /// 6 for Kerbin time, 24 for Earth time, or another value for modded installations. + public double HoursPerDay() + { + return KSPUtil.dateTimeFormatter.Day / 3600; + } + + /// + /// Returns 1 if the KSP UI is configured for the Kerbin calendar (6 hour days); + /// returns 0 for Earth days (24 hour). + /// + /// + public double KerbinTime() + { + return (GameSettings.KERBIN_TIME) ? 1.0 : 0.0; + } + + /// + /// Log messages to the KSP.log. Messages will be prefixed with + /// [MASFlightComputerProxy]. + /// + /// The string to write. Strings may be formatted using the Lua string library, or using the `..` concatenation operator. + public double LogMessage(string message) + { + Utility.LogMessage(this, message); + return 1.0; + } + + [MASProxy(Dependent = true)] + /// + /// Returns the U texture shift required to display the map icon listed below. + /// This function is intended to be used in conjunction with the '%MAP_ICON%' texture in + /// MASMonitor IMAGE nodes. + /// + /// * 0 - Invalid (not one of the below types) + /// * 1 - Ship target + /// * 2 - Plane target + /// * 3 - Probe target + /// * 4 - Lander target + /// * 5 - Station target + /// * 6 - Relay target + /// * 7 - Rover target + /// * 8 - Base target + /// * 9 - EVA target + /// * 10 - Flag target + /// * 11 - Debris target + /// * 12 - Space Object target + /// * 13 - Unknown target + /// * 14 - Celestial Body (planet) + /// * 15 - Ap Icon + /// * 16 - Pe Icon + /// * 17 - AN Icon + /// * 18 - DN Icon + /// * 19 - Maneuver Node Icon + /// * 20 - Ship location at intercept + /// * 21 - Target location at intercept + /// * 22 - Enter an SoI + /// * 23 - Exit an SoI + /// * 24 - Point of Impact (?) + /// + /// Note that entries 0 - 14 correspond to the results of `fc.TargetTypeId()`. + /// + /// The Icon Id - either `fc.TargetTypeId()` or one of the numbers listed in the description. + /// The U shift to select the icon from the '%MAP_ICON%' texture. + public double MapIconU(double iconId) + { + int index = (int)iconId; + switch (index) + { + case 0: + return 0.6; // WRONG - using Unknown Target + case 1: + return 0.0; + case 2: + return 0.8; + case 3: + return 0.2; + case 4: + return 0.6; + case 5: + return 0.6; + case 6: + return 0.8; + case 7: + return 0.0; + case 8: + return 0.4; + case 9: + return 0.4; + case 10: + return 0.8; + case 11: + return 0.2; + case 12: + return 0.8; + case 13: + return 0.6; + case 14: + return 0.4; + case 15: + return 0.2; + case 16: + return 0.0; + case 17: + return 0.4; + case 18: + return 0.6; + case 19: + return 0.4; + case 20: + return 0.0; + case 21: + return 0.2; + case 22: + return 0.0; + case 23: + return 0.2; + case 24: + return 0.8; + } + + return 0.0; + } + + [MASProxy(Dependent = true)] + /// + /// Returns the V texture shift required to display the map icon listed below. + /// This function is intended to be used in conjunction with the '%MAP_ICON%' texture in + /// MASMonitor IMAGE nodes. + /// + /// * 0 - Invalid (not one of the below types) + /// * 1 - Ship target + /// * 2 - Plane target + /// * 3 - Probe target + /// * 4 - Lander target + /// * 5 - Station target + /// * 6 - Relay target + /// * 7 - Rover target + /// * 8 - Base target + /// * 9 - EVA target + /// * 10 - Flag target + /// * 11 - Debris target + /// * 12 - Space Object target + /// * 13 - Unknown target + /// * 14 - Celestial Body (planet) + /// * 15 - Ap Icon + /// * 16 - Pe Icon + /// * 17 - AN Icon + /// * 18 - DN Icon + /// * 19 - Maneuver Node Icon + /// * 20 - Ship location at intercept + /// * 21 - Target location at intercept + /// * 22 - Enter an SoI + /// * 23 - Exit an SoI + /// * 24 - Point of Impact (?) + /// + /// Note that entries 0 - 14 correspond to the results of `fc.TargetTypeId()`. + /// + /// The Icon Id - either `fc.TargetTypeId()` or one of the numbers listed in the description. + /// The V shift to select the icon from the '%MAP_ICON%' texture. + public double MapIconV(double iconId) + { + int index = (int)iconId; + switch (index) + { + case 0: + return 0.6; // WRONG - Using Unknown Target + case 1: + return 0.6; + case 2: + return 0.8; + case 3: + return 0.0; + case 4: + return 0.0; + case 5: + return 0.2; + case 6: + return 0.6; + case 7: + return 0.0; + case 8: + return 0.0; + case 9: + return 0.4; + case 10: + return 0.0; + case 11: + return 0.6; + case 12: + return 0.2; + case 13: + return 0.6; + case 14: + return 0.6; + case 15: + return 0.8; + case 16: + return 0.8; + case 17: + return 0.8; + case 18: + return 0.8; + case 19: + return 0.2; + case 20: + return 0.2; + case 21: + return 0.2; + case 22: + return 0.4; + case 23: + return 0.4; + case 24: + return 0.4; + } + + return 0.0; + } + + [MASProxyAttribute(Immutable = true)] + /// + /// Returns the version number of the MAS plugin, as a string, + /// such as `1.0.1.12331`. + /// + /// MAS Version in string format. + public string MASVersion() + { + return MASLoader.masVersion; + } + + [MASProxy(Dependent = true)] + /// + /// Returns the B color value for the navball marker icon selected. + /// This function is intended to be used in conjunction with the '%NAVBALL_ICON%' texture in + /// MASMonitor IMAGE nodes. + /// + /// * 0 - Prograde + /// * 1 - Retrograde + /// * 2 - Radial Out + /// * 3 - Radial In + /// * 4 - Normal + + /// * 5 - Normal - (anti-normal) + /// * 6 - Maneuver Node + /// * 7 - Target + + /// * 8 - Target - (anti-target) + /// + /// If an invalid number is supplied, this function treats is as "Prograde". + /// + /// The icon as explained in the description. + /// The B value to use in `passiveColor` or `activeColor`. + public double NavballB(double iconId) + { + int index = (int)iconId; + switch (index) + { + case 1: + return 0.0; + case 2: + return 255.0; + case 3: + return 255.0; + case 4: + return 206.0; + case 5: + return 206.0; + case 6: + return 249.0; + case 7: + return 255.0; + case 8: + return 255.0; + } + return 0.0; + } + + [MASProxy(Dependent = true)] + /// + /// Returns the G color value for the navball marker icon selected. + /// This function is intended to be used in conjunction with the '%NAVBALL_ICON%' texture in + /// MASMonitor IMAGE nodes. + /// + /// * 0 - Prograde + /// * 1 - Retrograde + /// * 2 - Radial Out + /// * 3 - Radial In + /// * 4 - Normal + + /// * 5 - Normal - (anti-normal) + /// * 6 - Maneuver Node + /// * 7 - Target + + /// * 8 - Target - (anti-target) + /// + /// If an invalid number is supplied, this function treats is as "Prograde". + /// + /// The icon as explained in the description. + /// The G value to use in `passiveColor` or `activeColor`. + public double NavballG(double iconId) + { + int index = (int)iconId; + switch (index) + { + case 1: + return 203.0; + case 2: + return 155.0; + case 3: + return 155.0; + case 4: + return 0.0; + case 5: + return 0.0; + case 6: + return 102.0; + case 7: + return 0.0; + case 8: + return 0.0; + } + return 203.0; + } + + [MASProxy(Dependent = true)] + /// + /// Returns the R color value for the navball marker icon selected. + /// This function is intended to be used in conjunction with the '%NAVBALL_ICON%' texture in + /// MASMonitor IMAGE nodes. + /// + /// * 0 - Prograde + /// * 1 - Retrograde + /// * 2 - Radial Out + /// * 3 - Radial In + /// * 4 - Normal + + /// * 5 - Normal - (anti-normal) + /// * 6 - Maneuver Node + /// * 7 - Target + + /// * 8 - Target - (anti-target) + /// + /// If an invalid number is supplied, this function treats is as "Prograde". + /// + /// The icon as explained in the description. + /// The R value to use in `passiveColor` or `activeColor`. + public double NavballR(double iconId) + { + int index = (int)iconId; + switch (index) + { + case 1: + return 255.0; + case 2: + return 0.0; + case 3: + return 0.0; + case 4: + return 156.0; + case 5: + return 156.0; + case 6: + return 0.0; + case 7: + return 255.0; + case 8: + return 255.0; + } + return 255.0; + } + + [MASProxy(Dependent = true)] + /// + /// Returns the U texture shift to select the navball marker icon as listed below. + /// This function is intended to be used in conjunction with the '%NAVBALL_ICON%' texture in + /// MASMonitor IMAGE nodes. + /// + /// * 0 - Prograde + /// * 1 - Retrograde + /// * 2 - Radial Out + /// * 3 - Radial In + /// * 4 - Normal + + /// * 5 - Normal - (anti-normal) + /// * 6 - Maneuver Node + /// * 7 - Target + + /// * 8 - Target - (anti-target) + /// + /// If an invalid number is supplied, this function treats is as "Prograde". + /// + /// The icon as explained in the description. + /// The U value to use in `uvShift`. + public double NavballU(double iconId) + { + int index = (int)iconId; + switch (index) + { + case 1: + return (1.0 / 3.0); + case 2: + return (1.0 / 3.0); + case 3: + return 0.0; + case 4: + return 0.0; + case 5: + return (1.0 / 3.0); + case 6: + return (2.0 / 3.0); + case 7: + return (2.0 / 3.0); + case 8: + return (2.0 / 3.0); + } + return 0.0; + } + + [MASProxy(Dependent = true)] + /// + /// Returns the V texture shift to select the navball marker icon as listed below. + /// This function is intended to be used in conjunction with the '%NAVBALL_ICON%' texture in + /// MASMonitor IMAGE nodes. + /// + /// * 0 - Prograde + /// * 1 - Retrograde + /// * 2 - Radial Out + /// * 3 - Radial In + /// * 4 - Normal + + /// * 5 - Normal - (anti-normal) + /// * 6 - Maneuver Node + /// * 7 - Target + + /// * 8 - Target - (anti-target) + /// + /// If an invalid number is supplied, this function treats is as "Prograde". + /// + /// The icon as explained in the description. + /// The V value to use in `uvShift`. + public double NavballV(double iconId) + { + int index = (int)iconId; + switch (index) + { + case 1: + return (2.0 / 3.0); + case 2: + return (1.0 / 3.0); + case 3: + return (1.0 / 3.0); + case 4: + return 0.0; + case 5: + return 0.0; + case 6: + return 0.0; + case 7: + return (2.0 / 3.0); + case 8: + return (1.0 / 3.0); + } + return 2.0 / 3.0; + } + + /// + /// Play the audio file specified in `sound`, at the volume specified in `volume`. + /// If `stopCurrent` is true, any current sound clip is canceled first. If `stopCurrent` + /// is false, and an audio clip is currently playing, this call to PlayAudio does nothing. + /// + /// The name (URI) of the sound to play. + /// The volume to use for playback, between 0 and 1 (inclusive). + /// If 'true', stops any current audio clip being played. + /// Returns 1 if the audio was played, 0 if it was not found or otherwise not played. + public double PlayAudio(string sound, double volume, bool stopCurrent) + { + return fc.PlayAudio(sound, Mathf.Clamp01((float)volume), stopCurrent) ? 1.0 : 0.0; + } + + /// + /// Play the sequence of letters as a Morse Code sequence. Letters play automatically, + /// and a space ' ' inserts a pause in the sequence. All other characters are skipped. + /// + /// The sequence of letters to play as a Morse Code. + /// The volume to use for playback, between 0 and 1 (inclusive). + /// If 'true', stops any current audio clip being played. + /// + public double PlayMorseSequence(string sequence, double volume, bool stopCurrent) + { + return fc.PlayMorseSequence(sequence, Mathf.Clamp01((float)volume), stopCurrent); + } + + /// + /// Recover the vessel if it is recoverable. Has no effect if the craft can not be + /// recovered. + /// + /// 1 if the craft can be recovered (although it is also recovered immediately), 0 otherwise. + public double RecoverVessel() + { + if (vessel.IsRecoverable) + { + fc.StartCoroutine(RecoverVesselLater()); + return 1.0; + } + else + { + return 0.0; + } + } + + /// + /// Run the `startupScript` on every monitor and prop in the pod that has a defined `startupScript`. + /// + /// The number of scripts executed. + public double RunMonitorStartupScript() + { + double scriptCount = 0.0; + List props = fc.part.internalModel.props; + int numProps = props.Count; + for (int propIndex = 0; propIndex < numProps; ++propIndex) + { + List modules = props[propIndex].internalModules; + int numModules = modules.Count; + for (int moduleIndex = 0; moduleIndex < numModules; ++moduleIndex) + { + if (modules[moduleIndex] is MASMonitor) + { + if ((modules[moduleIndex] as MASMonitor).RunStartupScript(fc)) + { + scriptCount += 1.0; + } + } + else if (modules[moduleIndex] is MASComponent) + { + if ((modules[moduleIndex] as MASComponent).RunStartupScript(fc)) + { + scriptCount += 1.0; + } + } + } + } + + return scriptCount; + } + + /// + /// The ScrollingMarquee function takes a string, `input`, and it returns a substring + /// of maximum length `maxChars`. The substring that is returned changes every + /// `scrollRate` seconds if the string length is greater than `maxChars`, allowing + /// for a scrolling marquee effect. Using this method with the Repetition Scrolling + /// font can simulate an LED / LCD display. + /// + /// Note that characters advance one character width at a time - it is not a smooth + /// sliding movement. + /// + /// The string to use for the marquee. + /// The maximum number of characters in the string to display. + /// The frequency, in seconds, that the marquee advances. + /// A substring of no more than `maxChars` length. + public string ScrollingMarquee(string inputString, double maxChars, double scrollRate) + { + int maxCh = (int)maxChars; + int strlen = inputString.Length; + if (strlen <= maxCh) + { + return inputString; + } + else if (scrollRate <= 0.0) + { + return inputString.Substring(0, maxCh); + } + else + { + double adjustedTime = vc.universalTime / scrollRate; + double startD = adjustedTime % (double)(strlen + 1); + int start = (int)startD; + + if (start + maxCh <= strlen) + { + return inputString.Substring(start, maxCh); + } + else + { + int tail = maxCh - strlen + start - 1; + + StringBuilder sb = StringBuilderCache.Acquire(); + sb.Append(inputString.Substring(start)).Append(' ').Append(inputString.Substring(0, tail)); + + return sb.ToStringAndRelease(); + } + } + } + + /// + /// Attempt to set time warp rate to warpRate. Because KSP has specific + /// supported rates, the rate selected may not match the rate + /// requested. Warp rates are not changed when physics warp is enabled + /// (such as while flying in the atmosphere). + /// + /// The desired warp rate, such as '50'. + /// Should the rate be changed instantly? + /// The warp rate selected. This may not be the exact value requested. + public double SetWarp(double warpRate, bool instantChange) + { + if (TimeWarp.fetch != null) + { + if (warpRate <= 1.0) + { + TimeWarp.fetch.CancelAutoWarp(); + TimeWarp.SetRate(0, instantChange); + + return 1.0; + } + else if (warpRate != TimeWarp.CurrentRate && TimeWarp.WarpMode == TimeWarp.Modes.HIGH) + { + int rateIndex; + int maxRate = (vessel.situation == Vessel.Situations.LANDED || vessel.situation == Vessel.Situations.PRELAUNCH || vessel.situation == Vessel.Situations.SPLASHED) ? TimeWarp.fetch.warpRates.Length : TimeWarp.fetch.GetMaxRateForAltitude(vessel.altitude, vessel.mainBody); + + if (maxRate > 0) + { + + for (rateIndex = 0; rateIndex <= maxRate; ++rateIndex) + { + if (TimeWarp.fetch.warpRates[rateIndex] > warpRate) + { + break; + } + } + --rateIndex; + TimeWarp.SetRate(rateIndex, instantChange); + + return TimeWarp.fetch.warpRates[rateIndex]; + } + } + } + + return TimeWarp.CurrentRate; + } + + [MASProxy(Dependent = true)] + /// + /// Provides a custom SI formatter with more control than the basic SIP format. + /// + /// **NOT IMPLEMENTED:** Custom delimiter. + /// + /// The number to format. + /// The total length of the string. + /// The minimum decimal precision of the string. + /// A custom delimiter, or an empty string "" to indicate no delimiter + /// Require a space for the sign, even if the value is positive + /// Whether the SI prefix should be appended to the string. + /// The formatted string. + public string SIFormatValue(double value, double totalLength, double minDecimal, string delimiter, bool forceSign, bool showPrefix) + { + return DoSIFormat(value, (int)totalLength, (int)minDecimal, delimiter, forceSign, showPrefix); + } + + /// + /// Returns 1 if the vessel is recoverable, 0 otherwise. + /// + /// 1 if the craft can be recovered, 0 otherwise. + public double VesselRecoverable() + { + return (vessel.IsRecoverable) ? 1.0 : 0.0; + } + + /// + /// Warp to the specified universal time. If the time is in the past, or + /// the warp is otherwise not possible, nothing happens. + /// + /// The Universal Time to warp to. Must be in the future. + /// 1 if the warp to time is successfully set, 0 if it was not. + public double WarpTo(double UT) + { + if (TimeWarp.fetch != null && UT > Planetarium.GetUniversalTime()) + { + TimeWarp.fetch.CancelAutoWarp(); + TimeWarp.fetch.WarpTo(UT); + + return 1.0; + } + else + { + return 0.0; + } + } + #endregion + + /// + /// Information on the vessel's current orbit are available in this category. + /// + #region Orbit Parameters + /// + /// Returns the orbit's apoapsis (from datum) in meters. + /// + /// + public double Apoapsis() + { + return vc.apoapsis; + } + + /// + /// Returns the speed in m/s required to have a circular orbit at the provided altitude. + /// + /// Altitude in meters + /// The orbital speed in m/s needed for a circular orbit. + public double CircularOrbitSpeed(double altitude) + { + double GM = vc.mainBody.gravParameter; + double rA = altitude + vc.mainBody.Radius; + double Vi = Math.Sqrt(GM / rA); // Velocity of a circular orbit at radius A + + return Vi; + } + + /// + /// Return the eccentricity of the orbit. + /// + /// + public double Eccentricity() + { + return vc.orbit.eccentricity; + } + + /// + /// Return the vessel's orbital inclination. + /// + /// Inclination in degrees. + public double Inclination() + { + return vc.orbit.inclination; + } + + /// + /// Returns the name of the body that the vessel will be orbiting after the + /// next SoI change. If the craft is not changing SoI, returns an empty string. + /// + /// Name of the body, or an empty string if the orbit does not change SoI. + public string NextBodyName() + { + if (vesselSituationConverted > 2) + { + if (vc.orbit.patchEndTransition == Orbit.PatchTransitionType.ENCOUNTER) + { + return vessel.orbit.nextPatch.referenceBody.bodyName; + } + else if (vc.orbit.patchEndTransition == Orbit.PatchTransitionType.ESCAPE) + { + return vessel.mainBody.referenceBody.bodyName; + } + } + + return string.Empty; + } + + /// + /// Returns the time to the next SoI transition. If the current orbit does not change + /// SoI, returns 0. + /// + /// + public double TimeToNextSoI() + { + if (vessel.orbit.patchEndTransition == Orbit.PatchTransitionType.ENCOUNTER || + vessel.orbit.patchEndTransition == Orbit.PatchTransitionType.ESCAPE) + { + return vessel.orbit.UTsoi - Planetarium.GetUniversalTime(); + } + return 0.0; + } + + /// + /// Returns 1 if the next SoI change is an 'encounter', -1 if it is an + /// 'escape', and 0 if the orbit is not changing SoI. + /// + /// 0 if the orbit does not transition. 1 if the vessel will encounter a body, -1 if the vessel will escape the current body. + public double NextSoI() + { + if (vesselSituationConverted > 2) + { + if (vc.orbit.patchEndTransition == Orbit.PatchTransitionType.ENCOUNTER) + { + return 1.0; + } + else if (vc.orbit.patchEndTransition == Orbit.PatchTransitionType.ESCAPE) + { + return -1.0; + } + } + + return 0.0; + } + + /// + /// Returns the orbital period, in seconds. + /// + /// Orbital period, seconds. Zero if the craft is not in flight. + public double OrbitPeriod() + { + return (vesselSituationConverted > 2) ? vc.orbit.period : 0.0; + } + + /// + /// Returns the orbits periapsis (from datum) in meters. + /// + /// + public double Periapsis() + { + return vc.periapsis; + } + + /// + /// Returns the semi-major axis of the current orbit. When the SMA + /// matches a body's synchronous orbit SMA, the vessel is in a synchronous orbit. + /// + /// SMA in meters. + public double SemiMajorAxis() + { + return vc.orbit.semiMajorAxis; + } + #endregion + + /// + /// Variables related to the vessel's orientation in space, relative to a target, + /// or relative to the surface, are here. + /// + #region Orientation + + /// + /// Returns the angle of attack of the vessel. If FAR is installed, + /// FAR's results are used. + /// + /// The angle of attack, in degrees + public double AngleOfAttack() + { + if (MASIFAR.farFound) + { + return farProxy.AngleOfAttack(); + } + else + { + return vc.GetRelativePitch(vc.surfacePrograde) * (Math.Min(2.0, vessel.srfSpeed) * 0.5); + } + } + + /// + /// Returns the angle between the prograde vector of the orbit and the horizon when + /// the vessel crosses the specified altitude. If an invalid altitude is specified, + /// returns 0. + /// + /// This computation is based on unpowered flight, and it does not account for any effects + /// caused by atmospheric lift. It also does not account for surface-relative motion, so it + /// is not a good indicator for impact angle. + /// + /// The altitude above datum / sea level, in meters, at which the angle will be computed. + /// The flight path angle, in degrees, or 0. + public double FlightPathAngle(double altitude) + { + if (vc.orbit.ApA >= altitude && vc.orbit.PeA <= altitude && vc.orbit.eccentricity < 1.0) + { + double timeAtRadius = Utility.NextTimeToRadius(vc.orbit, altitude + vc.orbit.referenceBody.Radius); + + if (timeAtRadius > 0.0) + { + Vector3d position, vel; + double whatsThis = vc.orbit.GetOrbitalStateVectorsAtUT(timeAtRadius + vc.universalTime, out position, out vel); + + return 90.0 - Vector3d.Angle(position, vel); + } + } + + return 0.0; + } + + /// + /// Return heading relative to the surface in degrees [0, 360) + /// + /// + public double Heading() + { + return vc.heading; + } + + /// + /// Return the heading of the surface velocity vector relative to the surface in degrees [0, 360) + /// + /// + public double HeadingPrograde() + { + return vc.progradeHeading; + } + + /// + /// Return the instantaneous rate of change of the vessel heading in degrees per second. + /// + /// Note that extreme rates may be misreported. + /// + /// A value in the range of [-180, 180). + public double HeadingRate() + { + return vc.headingRate; + } + + /// + /// Return pitch relative to the surface [-90, 90] + /// + /// + public double Pitch() + { + return vc.pitch; + } + + /// + /// Pitch of the vessel relative to the current SAS prograde mode (orbit, surface, or target). + /// + /// + public double PitchActivePrograde() + { + var mode = FlightGlobals.speedDisplayMode; + + if (mode == FlightGlobals.SpeedDisplayModes.Orbit) + { + return vc.GetRelativePitch(vc.prograde); + } + else if (mode == FlightGlobals.SpeedDisplayModes.Target) + { + return vc.GetRelativePitch(vc.targetRelativeVelocity.normalized); + } + else + { + return vc.GetRelativePitch(vc.surfacePrograde); + } + } + + /// + /// Pitch of the vessel relative to the orbit anti-normal vector. + /// + /// + public double PitchAntiNormal() + { + return vc.GetRelativePitch(-vc.normal); + } + + /// + /// Pitch of the vessel relative to the vector pointing away from the target. + /// + /// + public double PitchAntiTarget() + { + if (vc.activeTarget == null) + { + return 0.0; + } + else + { + return vc.GetRelativePitch(-vc.targetDirection); + } + } + + /// + /// Returns the pitch component of the angle between a target docking + /// port and a reference (on Vessel) docking port; 0 if the target is + /// not a docking port or if the reference transform is not a docking + /// port. + /// + /// + public double PitchDockingAlignment() + { + if (vc.targetType == MASVesselComputer.TargetType.DockingPort && vc.targetDockingTransform != null) + { + Vector3 projectedVector = Vector3.ProjectOnPlane(-vc.targetDockingTransform.forward, vc.referenceTransform.right); + projectedVector.Normalize(); + + // Dot the projected vector with the 'top' direction so we can find + // the relative pitch. + float dotPitch = Vector3.Dot(projectedVector, vc.referenceTransform.forward); + float pitch = Mathf.Asin(dotPitch); + if (float.IsNaN(pitch)) + { + pitch = (dotPitch > 0.0f) ? 90.0f : -90.0f; + } + else + { + pitch *= Mathf.Rad2Deg; + } + + return pitch; + } + + return 0.0; + } + + /// + /// Pitch of the vessel relative to the next scheduled maneuver vector. + /// + /// + public double PitchManeuver() + { + if (vc.maneuverNodeValid) + { + return vc.GetRelativePitch(vc.maneuverNodeVector.normalized); + } + else + { + return 0.0; + } + } + + /// + /// Pitch of the vessel relative to the orbit normal vector. + /// + /// + public double PitchNormal() + { + return vc.GetRelativePitch(vc.normal); + } + + /// + /// Returns the pitch rate of the vessel in degrees/sec + /// + /// + public double PitchRate() + { + return -vessel.angularVelocity.x * Mathf.Rad2Deg; + } + + /// + /// Pitch of the vessel relative to the orbital prograde vector. + /// + /// + public double PitchPrograde() + { + return vc.GetRelativePitch(vc.prograde); + } + + /// + /// Pitch of the vessel relative to the orbital Radial In vector. + /// + /// + public double PitchRadialIn() + { + return vc.GetRelativePitch(-vc.radialOut); + } + + /// + /// Pitch of the vessel relative to the orbital Radial Out vector. + /// + /// + public double PitchRadialOut() + { + return vc.GetRelativePitch(vc.radialOut); + } + + /// + /// Pitch of the vessel relative to the orbital retrograde vector. + /// + /// + public double PitchRetrograde() + { + return vc.GetRelativePitch(-vc.prograde); + } + + /// + /// Pitch of the vessel relative to the current active SAS mode. If SAS + /// is not enabled, or the current mode is Stability Assist, returns 0. + /// + /// Pitch in the range [+90, -90] + public double PitchSAS() + { + double relativePitch = 0.0; + if (vessel.ActionGroups[KSPActionGroup.SAS] && vessel.Autopilot != null && vessel.Autopilot.SAS != null && autopilotMode != VesselAutopilot.AutopilotMode.StabilityAssist) + { + relativePitch = vc.GetRelativePitch(vessel.Autopilot.SAS.targetOrientation); + } + + return relativePitch; + } + + /// + /// Pitch of the vessel relative to the surface prograde vector. + /// + /// + public double PitchSurfacePrograde() + { + return vc.GetRelativePitch(vc.surfacePrograde); + } + + /// + /// Pitch of the vessel relative to the surface retrograde vector. + /// + /// + public double PitchSurfaceRetrograde() + { + return vc.GetRelativePitch(-vc.surfacePrograde); + } + + /// + /// Pitch of the vessel relative to the vector pointing at the target. + /// + /// + public double PitchTarget() + { + if (vc.activeTarget == null) + { + return 0.0; + } + else + { + return vc.GetRelativePitch(vc.targetDirection); + } + } + + /// + /// Pitch of the vessel relative to the target relative prograde vector. + /// + /// + public double PitchTargetPrograde() + { + if (vc.activeTarget == null) + { + return 0.0; + } + else + { + return vc.GetRelativePitch(vc.targetRelativeVelocity.normalized); + } + } + + /// + /// Pitch of the vessel relative to the target relative retrograde vector. + /// + /// + public double PitchTargetRetrograde() + { + if (vc.activeTarget == null) + { + return 0.0; + } + else + { + return vc.GetRelativePitch(-vc.targetRelativeVelocity.normalized); + } + } + + /// + /// Pitch of the vessel relative to the active waypoint. 0 if no active waypoint. + /// + /// + public double PitchWaypoint() + { + if (NavWaypoint.fetch.IsActive) + { + Vector3d srfPos = vessel.mainBody.GetWorldSurfacePosition(NavWaypoint.fetch.Latitude, NavWaypoint.fetch.Longitude, NavWaypoint.fetch.Altitude); + + return vc.GetRelativePitch(srfPos.normalized); + } + return 0.0; + } + + /// + /// Returns a number identifying what the current reference transform is: + /// * 1: The current IVA pod (if in IVA) + /// * 2: A command pod or probe control part. + /// * 3: A docking port + /// * 4: A Grapple Node (Claw) + /// * 0: Unknown. + /// + /// A number as specified in the summary. + public double ReferenceTransformType() + { + switch (vc.referenceTransformType) + { + case MASVesselComputer.ReferenceType.Unknown: + return 0.0; + case MASVesselComputer.ReferenceType.Self: + return 1.0; + case MASVesselComputer.ReferenceType.RemoteCommand: + return 2.0; + case MASVesselComputer.ReferenceType.DockingPort: + return 3.0; + case MASVesselComputer.ReferenceType.Claw: + return 4.0; + default: + return 0.0; + } + } + + /// + /// Returns roll relative to the surface, in degrees. [-180, 180] + /// + /// + public double Roll() + { + return vc.roll; + } + + /// + /// Returns the roll angle in degrees between the vessel's reference transform and a targeted docking port. + /// If the target is not a docking port, returns 0. Some docks have alignment requirements. To + /// determine whether these docks are suitably aligned for docking, use `fc.TargetDockError()`. + /// + /// The relative roll between the vessel and the currently-targeted docking port, or 0. + public double RollDockingAlignment() + { + if (vc.targetType == MASVesselComputer.TargetType.DockingPort && vc.targetDockingTransform != null) + { + Vector3 projectedVector = Vector3.ProjectOnPlane(-vc.targetDockingTransform.up, vc.referenceTransform.up); + projectedVector.Normalize(); + + float dotLateral = Vector3.Dot(projectedVector, vc.referenceTransform.right); + float dotLongitudinal = Vector3.Dot(projectedVector, vc.referenceTransform.forward); + + // Taking arc tangent of x/y lets us treat the front of the vessel + // as the 0 degree location. + float roll = Mathf.Atan2(dotLateral, dotLongitudinal); + roll *= Mathf.Rad2Deg; + + return roll; + } + + return 0.0; + } + + /// + /// Returns the roll rate of the vessel in degrees/sec + /// + /// + public double RollRate() + { + return -vessel.angularVelocity.y * Mathf.Rad2Deg; + } + + /// + /// Returns the vessel's current sideslip. If FAR is installed, + /// it will use FAR's computation of sideslip. + /// + /// Sideslip in degrees. + public double Sideslip() + { + if (MASIFAR.farFound) + { + return farProxy.Sideslip(); + } + else + { + return vc.GetRelativeYaw(vc.surfacePrograde) * (Math.Min(2.0, vessel.srfSpeed) * 0.5); + } + } + + /// + /// Returns the slope of the terrain directly below the vessel. If the vessel's altitude + /// is too high to read the slope, returns 0. + /// + /// Slope of the terrain below the vessel, or 0 if the slope cannot be read. + public double SlopeAngle() + { + return vc.GetSlopeAngle(); + } + + /// + /// Yaw of the vessel relative to the current SAS prograde mode (orbit, surface, or target). + /// + /// + public double YawActivePrograde() + { + var mode = FlightGlobals.speedDisplayMode; + + if (mode == FlightGlobals.SpeedDisplayModes.Orbit) + { + return vc.GetRelativeYaw(vc.prograde); + } + else if (mode == FlightGlobals.SpeedDisplayModes.Target) + { + return vc.GetRelativeYaw(vc.targetRelativeVelocity.normalized); + } + else + { + return vc.GetRelativeYaw(vc.surfacePrograde); + } + } + + /// + /// Yaw of the vessel relative to the orbit's anti-normal vector. + /// + /// + public double YawAntiNormal() + { + return vc.GetRelativeYaw(-vc.normal); + } + + /// + /// Yaw of the vessel relative to the vector pointing away from the target. + /// + /// + public double YawAntiTarget() + { + if (vc.activeTarget == null) + { + return 0.0; + } + else + { + return vc.GetRelativeYaw(vc.targetDirection); + } + } + + /// + /// Returns the yaw angle between the vessel's reference transform and a targeted docking port. + /// If the target is not a docking port, returns 0; + /// + /// + public double YawDockingAlignment() + { + if (vc.targetType == MASVesselComputer.TargetType.DockingPort && vc.targetDockingTransform != null) + { + Vector3 projectedVector = Vector3.ProjectOnPlane(-vc.targetDockingTransform.forward, vc.referenceTransform.forward); + projectedVector.Normalize(); + + // Determine the lateral displacement by dotting the vector with + // the 'right' vector... + float dotLateral = Vector3.Dot(projectedVector, vc.referenceTransform.right); + // And the forward/back displacement by dotting with the forward vector. + float dotLongitudinal = Vector3.Dot(projectedVector, vc.referenceTransform.up); + + // Taking arc tangent of x/y lets us treat the front of the vessel + // as the 0 degree location. + float yaw = Mathf.Atan2(dotLateral, dotLongitudinal); + yaw *= Mathf.Rad2Deg; + + return yaw; + } + + return 0.0; + } + + /// + /// Yaw of the vessel relative to the next scheduled maneuver vector. + /// + /// + public double YawManeuver() + { + if (vc.maneuverNodeValid) + { + return vc.GetRelativeYaw(vc.maneuverNodeVector.normalized); + } + else + { + return 0.0; + } + } + + /// + /// Yaw of the vessel relative to the orbit's normal vector. + /// + /// + public double YawNormal() + { + return vc.GetRelativeYaw(vc.normal); + } + + /// + /// Returns the yaw rate of the vessel in degrees/sec + /// + /// + public double YawRate() + { + return -vessel.angularVelocity.z * Mathf.Rad2Deg; + } + + /// + /// Yaw of the vessel relative to the orbital prograde vector. + /// + /// + public double YawPrograde() + { + return vc.GetRelativeYaw(vc.prograde); + } + + /// + /// Yaw of the vessel relative to the radial in vector. + /// + /// + public double YawRadialIn() + { + return vc.GetRelativeYaw(-vc.radialOut); + } + + /// + /// Yaw of the vessel relative to the radial out vector. + /// + /// + public double YawRadialOut() + { + return vc.GetRelativeYaw(vc.radialOut); + } + + /// + /// Yaw of the vessel relative to the orbital retrograde vector. + /// + /// + public double YawRetrograde() + { + return vc.GetRelativeYaw(-vc.prograde); + } + + /// + /// Yaw of the vessel relative to the current active SAS mode. If SAS + /// is not enabled, or the current mode is Stability Assist, returns 0. + /// + /// Yaw in the range [+180, -180] + public double YawSAS() + { + double relativeYaw = 0.0; + if (vessel.ActionGroups[KSPActionGroup.SAS] && vessel.Autopilot != null && vessel.Autopilot.SAS != null && autopilotMode != VesselAutopilot.AutopilotMode.StabilityAssist) + { + relativeYaw = vc.GetRelativeYaw(vessel.Autopilot.SAS.targetOrientation); + } + + return relativeYaw; + } + + /// + /// Yaw of the vessel relative to the surface prograde vector. + /// + /// + public double YawSurfacePrograde() + { + return vc.GetRelativeYaw(vc.surfacePrograde); + } + + /// + /// Yaw of the vessel relative to the surface retrograde vector. + /// + /// + public double YawSurfaceRetrograde() + { + return vc.GetRelativeYaw(-vc.surfacePrograde); + } + + /// + /// Yaw of the vessel relative to the vector pointing at the target. + /// + /// + public double YawTarget() + { + if (vc.activeTarget == null) + { + return 0.0; + } + else + { + return vc.GetRelativeYaw(vc.targetDirection); + } + } + + /// + /// Yaw of the vessel relative to the target relative prograde vector. + /// + /// + public double YawTargetPrograde() + { + if (vc.activeTarget == null) + { + return 0.0; + } + else + { + return vc.GetRelativeYaw(vc.targetRelativeVelocity.normalized); + } + } + + /// + /// Yaw of the vessel relative to the target relative retrograde vector. + /// + /// + public double YawTargetRetrograde() + { + if (vc.activeTarget == null) + { + return 0.0; + } + else + { + return vc.GetRelativeYaw(-vc.targetRelativeVelocity.normalized); + } + } + + /// + /// Yaw of the vessel relative to the active waypoint. 0 if no active waypoint. + /// + /// + public double YawWaypoint() + { + if (NavWaypoint.fetch.IsActive) + { + Vector3d srfPos = vessel.mainBody.GetWorldSurfacePosition(NavWaypoint.fetch.Latitude, NavWaypoint.fetch.Longitude, NavWaypoint.fetch.Altitude); + + return vc.GetRelativeYaw(srfPos.normalized); + } + return 0.0; + } + #endregion + + /// + /// Periodic variables change value over time, based on a requested + /// frequency. + /// + #region Periodic Variables + + /// + /// Returns a periodic variable that counts upwards from 0 to 'countTo'-1 before repeating, + /// with each change based on the 'period'. Note that the counter is not guaranteed to start + /// at zero, since it is based on universal time. + /// + /// The period required to increase the counter, in cycles/second (Hertz). + /// The exclusive upper limit of the count. + /// An integer between [0 and countTo). + public double PeriodCount(double period, double countTo) + { + if (period > 0.0 && countTo >= 2.0) + { + double invPeriod = Math.Floor(countTo) / period; + + double remainder = period * (vc.universalTime % invPeriod); + + return Math.Floor(remainder); + } + + return 0.0; + } + + /// + /// Returns a random number in the range [0, 1] that updates based on `period`. + /// + /// Note that all props using the same `period` will see the same value from this function. + /// + /// The period between updates, in cycles/second (Hertz). + /// A number between 0 and 1, inclusive. 0 if period is not a positive value. + public double PeriodRandom(double period) + { + if (period > 0.0) + { + return fc.PeriodRandom((float)(1.0 / period)); + } + + return 0.0; + } + + /// + /// Returns a periodic variable that follows a sine-wave curve. + /// + /// The period of the change, in cycles/second (Hertz). + /// A number between -1 and +1. + public double PeriodSine(double period) + { + if (period > 0.0) + { + double invPeriod = 1.0 / period; + + double remainder = vc.universalTime % invPeriod; + + return Math.Sin(remainder * period * Math.PI * 2.0); + } + + return 0.0; + } + + /// + /// Returns a stair-step periodic variable (changes from 0 to 1 to 0 with + /// no ramps between values). + /// + /// The period of the change, in cycles/second (Hertz). + /// 0 or 1 + public double PeriodStep(double period) + { + if (period > 0.0) + { + double invPeriod = 1.0 / period; + + double remainder = vc.universalTime % invPeriod; + + return (remainder > invPeriod * 0.5) ? 1.0 : 0.0; + } + + return 0.0; + } + #endregion + + /// + /// Persistent variables are the primary means of data storage in Avionics Systems. + /// As such, there are many ways to set, alter, or query these variables. + /// + /// Persistent variables may be numbers or strings. Several of the setter and + /// getter functions in this category will convert the variable automatically + /// from one to the other (whenever possible), but it is the responsibility + /// of the prop config maker to make sure that text and numbers are not + /// intermingled when a specific persistent variable will be used as a number. + /// + #region Persistent Vars + /// + /// This method adds an amount to the named persistent. If the variable + /// did not already exist, it is created and initialized to 0 before + /// adding `amount`. If the variable was a string, it is converted to + /// a number before adding `amount`. + /// + /// If the variable cannot converted to a number, the variable's name is + /// returned, instead. + /// + /// The name of the persistent variable to change. + /// The amount to add to the persistent variable. + /// The new value of the persistent variable, or the name of the variable if it could not be converted to a number. + public object AddPersistent(string persistentName, double amount) + { + return fc.AddPersistent(persistentName, amount); + } + + /// + /// This method adds an amount to the named persistent. The result + /// is clamped to the range [minValue, maxValue]. + /// + /// If the variable + /// did not already exist, it is created and initialized to 0 before + /// adding `amount`. If the variable was a string, it is converted to + /// a number before adding `amount`. + /// + /// If the variable cannot converted to a number, the variable's name is + /// returned, instead. + /// + /// The name of the persistent variable to change. + /// The amount to add to the persistent variable. + /// The minimum value of the variable. If adding `amount` to the variable + /// causes it to be less than this value, the variable is set to this value, instead. + /// The maximum value of the variable. If adding `amount` to the variable + /// causes it to be greater than this value, the variable is set to this value, instead. + /// The new value of the persistent variable, or the name of the variable if it could not be + /// converted to a number. + public object AddPersistentClamped(string persistentName, double amount, double minValue, double maxValue) + { + return fc.AddPersistentClamped(persistentName, amount, minValue, maxValue); + } + + /// + /// This method adds an amount to the named persistent. The result + /// wraps around the range [minValue, maxValue]. This feature is used, + /// for instance, for + /// adjusting a heading between 0 and 360 degrees without having to go + /// from 359 all the way back to 0. `maxValue` is treated as an alias + /// for `minValue`, so if adding to a persistent value makes it equal + /// exactly `maxValue`, it is set to `minValue` instead. With the heading + /// example above, for instance, you would use `fc.AddPersistentWrapped("SomeVariableName", 1, 0, 360)`. + /// + /// To make a counter that runs from 0 to 2 before wrapping back to 0 + /// again, `fc.AddPersistentWrapped("SomeVariableName", 1, 0, 3)`. + /// + /// If the variable + /// did not already exist, it is created and initialized to 0 before + /// adding `amount`. If the variable was a string, it is converted to + /// a number before adding `amount`. + /// + /// If the variable cannot converted to a number, the variable's name is + /// returned, instead. + /// + /// If minValue and maxValue are the same, `amount` is treated as zero (nothing is added). + /// + /// The name of the persistent variable to change. + /// The amount to add to the persistent variable. + /// The minimum value of the variable. If adding `amount` would make the + /// variable less than `minValue`, MAS sets the variable to `maxValue` minus the + /// difference. + /// The maximum value of the variable. If adding `amount` would make the + /// variable greather than `maxValue`, MAS sets the variable to `minValue` plus the overage. + /// The new value of the persistent variable, or the name of the variable if it could not be + /// converted to a number. + public object AddPersistentWrapped(string persistentName, double amount, double minValue, double maxValue) + { + return fc.AddPersistentWrapped(persistentName, amount, minValue, maxValue); + } + + /// + /// Append the string `addon` to the persistent variable `persistentName`, but + /// only up to the specified maximum length. If the persistent does not exist, + /// it is created and initialized to `addon`. If the persistent is a numeric value, + /// it is converted to a string, and then `addon` is added. + /// + /// The name of the persistent variable to change. + /// The amount to add to the persistent variable. + /// The maximum number of characters allowed in the + /// string. Characters in excess of this amount are not added to the persistent. + /// The new string. + public object AppendPersistent(string persistentName, string addon, double maxLength) + { + return fc.AppendPersistent(persistentName, addon, (int)maxLength); + } + + /// + /// Treat the persistent value `persistentName` as a number. The integer `digit` is + /// appended to the end of the number, but only if the resulting string length remains + /// less than or equal to `maxLength`. + /// + /// This behiavor mimics the effect of typing a number on a calculator or other numeric + /// input. `digit` must be an integer between 0 and 9 (inclusive), or this function + /// has no effect. + /// + /// This method does not support adding a decimal point. The conventional `fc.AppendPersistent()` + /// must be used for that capability. + /// + /// The name of the persistent variable to change. + /// An integer in the range [0, 9]. + /// The maximum number of digits that `persistentName` may contain. + /// The new value. If the number was not updated, the old value is returned. + public double AppendPersistentDigit(string persistentName, double digit, double maxLength) + { + int dig_i = (int)digit; + int maxL = (int)maxLength; + if (dig_i >= 0 && dig_i <= 9) + { + return fc.AppendPersistentNumeric(persistentName, dig_i, maxL); + } + else + { + return fc.GetPersistentAsNumber(persistentName); + } + } + + /// + /// Clears 0 or more bits in the number stored in `persistentName`. + /// + /// The value in `persistentName` is converted to a 32 bit integer, + /// as is `bits`. `bits` is converted to its bitwise negation, and + /// A bit-wise AND is applied, and the resulting value + /// is stored in `persistentName`. + /// + /// If `persistentName` does not exist yet, or it is a string that cannot + /// be converted to an integer, it is set to 0 before the AND. + /// + /// The name of the persistent variable to change. + /// An integer representing the bits to clear. + /// Result of the bit-wise AND. + public double ClearBits(string persistentName, double bits) + { + return fc.ClearBits(persistentName, (int)bits); + } + + [MASProxy(Persistent = true)] + /// + /// Return value of the persistent. Strings are returned as strings, + /// numbers are returned as numbers. If the persistent does not exist + /// yet, the name is returned. + /// + /// The name of the persistent variable to query. + /// The value of the persistent, or its name if it does not exist. + public object GetPersistent(string persistentName) + { + return fc.GetPersistent(persistentName); + } + + [MASProxy(Persistent = true)] + /// + /// Return the value of the persistent as a number. If the persistent + /// does not exist yet, or it is a string that can not be converted to + /// a number, return 0. + /// + /// The name of the persistent variable to query. + /// The numeric value of the persistent, or 0 if it either does not + /// exist, or it cannot be converted to a number. + public double GetPersistentAsNumber(string persistentName) + { + return fc.GetPersistentAsNumber(persistentName); + } + + /// + /// Returns 1 if the named persistent variable has been initialized. Returns 0 + /// if the variable does not exist yet. + /// + /// The persistent variable name to check. + /// 1 if the variable contains initialized data, 0 if it does not. + public double GetPersistentExists(string persistentName) + { + return fc.GetPersistentExists(persistentName) ? 1.0 : 0.0; + } + + /// + /// Set the persistent value in `persistentName` to `value`, but + /// only if the persistent value does not already exist. If the + /// persistent already exists, this function does nothing. + /// + /// This function can be used to replace a script construct like + /// + /// ```Lua + /// if GetPersistentExists("MyPersistent") == 0 then + /// SetPersistent("MyPersistent", 42) + /// endif + /// ``` + /// + /// with a single initialization call. + /// + /// The name of the persistent variable to initialize. + /// The new number or text string to use for this persistent. + /// 1 if the persistent value was created, 0 if it already exists. + public double InitializePersistent(string persistentName, object value) + { + return fc.InitializePersistent(persistentName, value); + } + + /// + /// Sets 0 or more bits in the number stored in `persistentName`. + /// + /// The value in `persistentName` is converted to a 32 bit integer, + /// as is `bits`. A bit-wise OR is applied, and the resulting value + /// is stored in `persistentName`. + /// + /// If `persistentName` does not exist yet, or it is a string that cannot + /// be converted to an integer, it is set to 0 before the OR. + /// + /// The name of the persistent variable to change. + /// An integer representing the bits to set. + /// Result of the bit-wise OR. + public double SetBits(string persistentName, double bits) + { + return fc.SetBits(persistentName, (int)bits); + } + + /// + /// Set a persistent to `value`. `value` may be either a string or + /// a number. The existing value of the persistent is replaced. + /// + /// The name of the persistent variable to change. + /// The new number or text string to use for this persistent. + /// `value` + public object SetPersistent(string persistentName, object value) + { + return fc.SetPersistent(persistentName, value); + } + + /// + /// Set a persistent to `value`, but allow the persistent to change by at most `maxChangePerSecond` + /// from its initial value. + /// + /// If the persistent did not already exist, or if it was a string that could not be converted to + /// a number, it is set to value immediately. + /// + /// For other cases, the persistent's value is updated by adding or subtracting the minimum of + /// (maxChangePerSecond * timestep) or Abs(old value - value). + /// + /// While this method is a "setter" method, it is best applied where its results are displayed, + /// such as a number on an MFD, or controlling the animation of a prop. This will ensure that + /// the number continually updates, whereas using this method in a collider action will cause it + /// to only update the value when the collider is hit. + /// + /// The name of the persistent variable to change. + /// The new value for this variable. + /// The maximum amount the existing variable may change per second. + /// The resulting value. + public double SetPersistentBlended(string persistentName, double value, double maxChangePerSecond) + { + return fc.SetPersistentBlended(persistentName, value, maxChangePerSecond); + } + + /// + /// Toggle a persistent between 0 and 1. + /// + /// If the persistent is a number, it becomes 0 if it was already a + /// positive number, and it becomes 1 if it was previously <= 0. + /// + /// If the persistent is a string, it is converted to a number, and + /// the same rule is applied. + /// + /// If the persistent did not previously exist, or it is a string that + /// cannot be converted to a number, it is treated as if it + /// were zero. + /// + /// The name of the persistent variable to change. + /// 0 or 1. + public double TogglePersistent(string persistentName) + { + return fc.TogglePersistent(persistentName); + } + #endregion + + /// + /// The Position category provides information about the vessel's position + /// relative to a body (latitude and longitude) as well as landing predictions + /// and the like. + /// + /// The landing predictions will use Kerbal Engineer Redux if that mod is installed. + /// If KER is not available, MAS will fall back to using the MechJeb Landing + /// Predictions module if it is enabled. + /// Otherwise, MAS uses a very rudimentary predictor that assumes the planet + /// has no atmosphere. Because of these + /// limitations, the results will change during atmospheric braking, and they + /// may be wildly inaccurate in mountainous terrain. + /// + #region Position + /// + /// Returns the predicted altitude of the landing position. + /// + /// See the [category description](#description) for limitations on this function. + /// + /// MechJeb, Kerbal Engineer Redux + /// Predicted altitude (meters ASL) of the point of landing. + public double LandingAltitude() + { + if (MASIKerbalEngineer.keFound) + { + return keProxy.LandingAltitude(); + } + else if (mjProxy.LandingComputerActive() > 0.0) + { + return mjProxy.LandingAltitude(); + } + else + { + return vc.landingAltitude; + } + } + + /// + /// Returns the predicted latitude of the landing position. + /// + /// See the [category description](#description) for limitations on this function. + /// + /// MechJeb, Kerbal Engineer Redux + /// Latitude of estimated landing point, or 0 if the orbit does not lithobrake. + public double LandingLatitude() + { + if (MASIKerbalEngineer.keFound) + { + return keProxy.LandingLatitude(); + } + else if (mjProxy.LandingComputerActive() > 0.0 && mjProxy.LandingLatitude() != 0.0) + { + return mjProxy.LandingLatitude(); + } + else + { + return vc.landingLatitude; + } + } + + /// + /// Returns the predicted longitude of the landing position. + /// + /// See the [category description](#description) for limitations on this function. + /// + /// MechJeb, Kerbal Engineer Redux + /// Longitude of estimated landing point, or 0 if the orbit does not lithobrake. + public double LandingLongitude() + { + if (MASIKerbalEngineer.keFound) + { + return keProxy.LandingLongitude(); + } + else if (mjProxy.LandingComputerActive() > 0.0 && mjProxy.LandingLongitude() != 0.0) + { + return mjProxy.LandingLongitude(); + } + else + { + return vc.landingLongitude; + } + } + + /// + /// Returns an estimate of the speed of the vessel at landing, based on current speed + /// and current thrust. + /// + /// This is purely an estimate, and it does not account for atmospheric drag or maximum + /// available thrust. + /// + /// If the vessel will not lithobrake, or if the current thrust is adequate, the speed + /// estimate is 0. + /// + /// Impact speed in m/s, or 0 if the vessel will not intersect the planet. + public double LandingSpeed() + { + double landingTime = LandingTime(); + if (landingTime > 0.0) + { + return Math.Max(0.0, SurfaceSpeed() - AccelSurfacePrograde() * landingTime); + } + + return 0.0; + } + + /// + /// Returns the predicted time until landing in seconds. + /// + /// See the [category description](#description) for limitations on this function. + /// + /// MechJeb, Kerbal Engineer Redux + /// Estimated time until landing, or 0 if the orbit does not lithobrake. + public double LandingTime() + { + if (MASIKerbalEngineer.keFound) + { + return keProxy.LandingTime(); + } + else if (mjProxy.LandingComputerActive() > 0.0) + { + return mjProxy.LandingTime(); + } + else + { + double landingTime = vc.timeToImpact; + return (landingTime > 0.0) ? (landingTime) : 0.0; + } + } + + /// + /// Returns 1 if landing predictions are valid. If Kerbal Engineer Redux is installed, + /// MAS will use KER's predictions. Otherwise, if MechJeb is installed, MAS will + /// use MechJeb's landing computer (if it is active). If neither of those options + /// are available, MAS uses its own internal predictor. + /// + /// MechJeb, Kerbal Engineer Redux + /// + public double LandingPredictorActive() + { + if (MASIKerbalEngineer.keFound) + { + return keProxy.LandingTime() > 0.0 ? 1.0 : 0.0; + } + // Landing predictor sometimes gets weird and insists it's on, but it spews + // 0N, 0E as the landing point. + else if (mjProxy.LandingComputerActive() > 0.0 && mjProxy.LandingLongitude() != 0.0) + { + return 1.0; + } + else + { + return (vc.timeToImpact > 0.0) ? 1.0 : 0.0; + } + } + + /// + /// Return the vessel's latitude. + /// + /// + public double Latitude() + { + return vessel.latitude; + } + + /// + /// Return the vessel's longitude. + /// + /// + public double Longitude() + { + // longitude seems to be unnormalized. + return Utility.NormalizeLongitude(vessel.longitude); + } + + /// + /// Returns the predicted altitude of the landing position. + /// + /// This version will not use any mods to compute the landing site. + /// + /// See the [category description](#description) for limitations on this function. + /// + /// Predicted altitude (meters ASL) of the point of landing. + public double MASLandingAltitude() + { + return vc.landingAltitude; + } + + /// + /// Returns the predicted latitude of the landing position. + /// + /// This version will not use any mods to compute the landing site. + /// + /// See the [category description](#description) for limitations on this function. + /// + /// Latitude of estimated landing point, or 0 if the orbit does not lithobrake. + public double MASLandingLatitude() + { + return vc.landingLatitude; + } + + /// + /// Returns the predicted longitude of the landing position. + /// + /// This version will not use any mods to compute the landing site. + /// + /// See the [category description](#description) for limitations on this function. + /// + /// Longitude of estimated landing point, or 0 if the orbit does not lithobrake. + public double MASLandingLongitude() + { + return vc.landingLongitude; + } + + /// + /// Returns the predicted time until landing in seconds. + /// + /// This version will not use any mods to compute the landing site. + /// + /// See the [category description](#description) for limitations on this function. + /// + /// Estimated time until landing, or 0 if the orbit does not lithobrake. + public double MASLandingTime() + { + double landingTime = vc.timeToImpact; + return (landingTime > 0.0) ? (landingTime) : 0.0; + } + #endregion + + /// + /// Queries and controls related to power production belong in this category. + /// + /// For all of these components, if the player has changed the `ElectricCharge` field + /// in the MAS config file, these components will track that resource instead. + /// + /// The Fuel Cell methods track any ModuleResourceConverter that outputs + /// ElectricCharge, unless a different resource converter tracker has been installed + /// with a higher priority. For general-purpose ModuleResourceConverter tracking, + /// refer to Resource Converter category. + /// + #region Power Production + /// + /// Returns the number of alternators on the vessel. + /// + /// Number of alternator modules. + public double AlternatorCount() + { + return vc.moduleAlternator.Length; + } + + /// + /// Returns the current net output of the alternators. + /// + /// Units of ElectricCharge/second + public double AlternatorOutput() + { + return vc.netAlternatorOutput; + } + + /// + /// Returns the number of fuel cells on the vessel. Fuel cells are defined + /// as ModuleResourceConverter units that output `ElectricCharge` (or whatever + /// the player-selected override is in the MAS config file). + /// + /// Number of fuel cells. + public double FuelCellCount() + { + return ResourceConverterCount(0.0); + } + + /// + /// Returns the current output of installed fuel cells. + /// + /// Units of ElectricCharge/second. + public double FuelCellOutput() + { + return ResourceConverterOutput(0.0); + } + + /// + /// Returns the number of generators on the vessel. Generators + /// are and ModuleGenerator that outputs `ElectricCharge`. + /// + /// Number of generator.s + public double GeneratorCount() + { + return vc.moduleGenerator.Length; + } + + /// + /// Returns the current output of installed generators. + /// + /// Output in ElectricCharge/sec. + public double GeneratorOutput() + { + return vc.netGeneratorOutput; + } + + /// + /// Returns 1 if at least one fuel cell is enabled; 0 otherwise. + /// + /// 1 if any fuel cell is switched on; 0 otherwise. + public double GetFuelCellActive() + { + return GetResourceConverterActive(0.0); + } + + /// + /// Sets the fuel cells on or off per the 'active' parameter. Fuel cells that can + /// not be manually controlled are not changed. + /// + /// 1 if fuel cells are now active, 0 if they're off or they could not be toggled. + public double SetFuelCellActive(bool active) + { + return SetResourceConverterActive(0.0, active); + } + + /// + /// Deploys / undeploys solar panels. + /// + /// 'true' to extend solar panels, 'false' to retract them (when possible). + /// 1 if at least one panel is moving; 0 otherwise. + public double SetSolarPanelDeploy(bool deploy) + { + bool anyMoving = false; + if (vc.solarPanelsDeployable && deploy) + { + for (int i = vc.moduleSolarPanel.Length - 1; i >= 0; --i) + { + if (vc.moduleSolarPanel[i].useAnimation && vc.moduleSolarPanel[i].deployState == ModuleDeployablePart.DeployState.RETRACTED) + { + vc.moduleSolarPanel[i].Extend(); + anyMoving = true; + } + } + } + else if (vc.solarPanelsRetractable && !deploy) + { + for (int i = vc.moduleSolarPanel.Length - 1; i >= 0; --i) + { + if (vc.moduleSolarPanel[i].useAnimation && vc.moduleSolarPanel[i].retractable && vc.moduleSolarPanel[i].deployState == ModuleDeployablePart.DeployState.EXTENDED) + { + vc.moduleSolarPanel[i].Retract(); + anyMoving = true; + } + } + } + + return (anyMoving) ? 1.0 : 0.0; + } + + /// + /// Returns the number of solar panels on the vessel. + /// + /// The number of solar panel modules on the vessel. + public double SolarPanelCount() + { + return vc.moduleSolarPanel.Length; + } + + /// + /// Returns 1 if any solar panels are damaged. + /// + /// 1 is all solar panels are damaged; 0 otherwise. + public double SolarPanelDamaged() + { + return (vc.solarPanelsDamaged) ? 1.0 : 0.0; + } + + /// + /// Returns 1 if at least one solar panel may be deployed. + /// + /// 1 if any solar panel is retracted and available to deploy; 0 otherwise. + public double SolarPanelDeployable() + { + return (vc.solarPanelsDeployable) ? 1.0 : 0.0; + } + + /// + /// Returns the net efficiency of solar panels. This value can + /// provide some information about blocked solar panels, or non-rotating solar panels that + /// are not optimally positioned. Because the amount of energy delivered depends on the + /// distance from the star, maximum efficiency can be larger than 1 (or less than 1). + /// + /// The efficiency value, 0 or larger. + public double SolarPanelEfficiency() + { + return vc.solarPanelsEfficiency; + } + + /// + /// Returns -1 if a solar panel is retracting, +1 if a solar panel is extending, or 0 + /// if no solar panels are moving. + /// + /// -1, 0, or +1. + public double SolarPanelMoving() + { + return vc.solarPanelsMoving; + } + + /// + /// Returns the current output of installed solar panels. + /// + /// Solar panel output in ElectricCharge/sec. + public double SolarPanelOutput() + { + return vc.netSolarOutput; + } + + /// + /// Returns a number representing the average position of undamaged deployable solar panels. + /// + /// * 0 - No solar panels, no undamaged solar panels, or all undamaged solar panels are retracted. + /// * 1 - All deployable solar panels extended. + /// + /// If the solar panels are moving, a number between 0 and 1 is returned. Note that unretractable + /// panels will always return 1 once they have deployed. + /// + /// Panel Position (a number between 0 and 1). + public double SolarPanelPosition() + { + float numPanels = 0.0f; + float lerpPosition = 0.0f; + for (int i = vc.moduleSolarPanel.Length - 1; i >= 0; --i) + { + if (vc.moduleSolarPanel[i].useAnimation && vc.moduleSolarPanel[i].deployState != ModuleDeployablePart.DeployState.BROKEN) + { + numPanels += 1.0f; + + lerpPosition += vc.moduleSolarPanel[i].GetScalar; + } + } + + if (numPanels > 1.0f) + { + return lerpPosition / numPanels; + } + else if (numPanels == 1.0f) + { + return lerpPosition; + } + + return 0.0; + } + + /// + /// Returns 1 if at least one solar panel is retractable. + /// + /// 1 if a solar panel is deployed, and it may be retracted; 0 otherwise. + public double SolarPanelRetractable() + { + return (vc.solarPanelsRetractable) ? 1.0 : 0.0; + } + + /// + /// Toggles fuel cells from off to on or vice versa. Fuel cells that can + /// not be manually controlled are not toggled. + /// + /// 1 if fuel cells are now active, 0 if they're off or they could not be toggled. + public double ToggleFuelCellActive() + { + return SetResourceConverterActive(0.0, GetFuelCellActive() < 1.0); + } + + /// + /// Deploys / undeploys solar panels. + /// + /// 1 if at least one panel is moving; 0 otherwise. + public double ToggleSolarPanel() + { + bool anyMoving = false; + if (vc.solarPanelsDeployable) + { + for (int i = vc.moduleSolarPanel.Length - 1; i >= 0; --i) + { + if (vc.moduleSolarPanel[i].useAnimation && vc.moduleSolarPanel[i].deployState == ModuleDeployablePart.DeployState.RETRACTED) + { + vc.moduleSolarPanel[i].Extend(); + anyMoving = true; + } + } + } + else if (vc.solarPanelsRetractable) + { + for (int i = vc.moduleSolarPanel.Length - 1; i >= 0; --i) + { + if (vc.moduleSolarPanel[i].useAnimation && vc.moduleSolarPanel[i].retractable && vc.moduleSolarPanel[i].deployState == ModuleDeployablePart.DeployState.EXTENDED) + { + vc.moduleSolarPanel[i].Retract(); + anyMoving = true; + } + } + } + + return (anyMoving) ? 1.0 : 0.0; + } + #endregion + + /// + /// This section contains the functions used to interact with the stock procedural + /// fairings system. + /// + #region Procedural Fairings + + /// + /// Deploys all stock procedural fairings that are currently available to + /// deploy. + /// + /// 1 if any fairings deployed, 0 otherwise. + public double DeployFairings() + { + bool deployed = false; + + for (int i = vc.moduleProceduralFairing.Length - 1; i >= 0; --i) + { + if (vc.moduleProceduralFairing[i].CanMove) + { + vc.moduleProceduralFairing[i].DeployFairing(); + deployed = true; + } + } + + return (deployed) ? 1.0 : 0.0; + } + + /// + /// Returns 1 if at least one installed stock procedural fairing is available to + /// deploy. + /// + /// 1 if any fairings can deploy, 0 otherwise. + public double FairingsCanDeploy() + { + return (vc.fairingsCanDeploy) ? 1.0 : 0.0; + } + + /// + /// Returns the number of stock procedural fairings installed on the vessel. + /// + /// The total number of stock p-fairings on the vessel. + public double FairingsCount() + { + return vc.moduleProceduralFairing.Length; + } + #endregion + + /// + /// The Radar category provides the interface for controlling MASRadar + /// modules installed on the craft. + /// + #region Radar + /// + /// Returns 1 if any radars are turned on; 0 otherwise. + /// + /// 1 if any radar is switched on; 0 otherwise. + public double RadarActive() + { + for (int i = vc.moduleRadar.Length - 1; i >= 0; --i) + { + if (vc.moduleRadar[i].radarEnabled) + { + return 1.0; + } + } + return 0.0; + } + + /// + /// Returns the number of radar modules available on the vessel. + /// + /// The count of the number of radar units installed on the vessel, 0 or higher. + public double RadarCount() + { + return vc.moduleRadar.Length; + } + + /// + /// Activate or deactivate docking radars. + /// + /// 'true' to enable docking radars, false otherwise. + /// 1 if radars are now active, 0 otherwise. + public double SetRadarActive(bool active) + { + for (int i = vc.moduleRadar.Length - 1; i >= 0; --i) + { + vc.moduleRadar[i].radarEnabled = active; + } + + return (active) ? 1.0 : 0.0; + } + + /// + /// Toggle any installed radar from active to inactive. + /// + /// 1 if radars are now active, 0 otherwise. + public double ToggleRadar() + { + bool state = (vc.moduleRadar.Length > 0) ? !vc.moduleRadar[0].radarEnabled : false; + for (int i = vc.moduleRadar.Length - 1; i >= 0; --i) + { + vc.moduleRadar[i].radarEnabled = state; + } + + return (state) ? 1.0 : 0.0; + } + #endregion + + /// + /// Random number generators are in this category. + /// + #region Random + [MASProxyAttribute(Uncacheable = true)] + /// + /// Return a random number in the range of [0, 1] + /// + /// A uniformly-distributed pseudo-random number in the range [0, 1]. + public double Random() + { + return UnityEngine.Random.Range(0.0f, 1.0f); + } + + [MASProxyAttribute(Uncacheable = true)] + /// + /// Return an approximation of a normal distribution with a mean and + /// standard deviation as specified. The actual result falls in the + /// range of (-7, +7) for a mean of 0 and a standard deviation of 1. + /// + /// fc.RandomNormal uses a Box-Muller approximation method modified + /// to prevent a 0 in the u component (to avoid trying to take the + /// log of 0). The number was tweaked so for all practical purposes + /// the range of numbers is about (-7, +7), as explained above. + /// + /// The desired mean of the normal distribution. + /// The desired standard deviation of the normal distribution. + /// A pseudo-random number that emulates a normal distribution. See the summary for more detail. + public double RandomNormal(double mean, double stdDev) + { + // Box-Muller method tweaked to prevent a 0 in u: for a stddev of 1 + // the range is (-7, 7). + float u = UnityEngine.Random.Range(0.0009765625f, 1.0f); + float v = UnityEngine.Random.Range(0.0f, 2.0f * Mathf.PI); + double x = Mathf.Sqrt(-2.0f * Mathf.Log(u)) * Mathf.Cos(v) * stdDev; + return x + mean; + } + #endregion + + /// + /// The RCS controls may be accessed in this category along with status + /// variables. + /// + #region RCS + /// + /// Returns 1 if any RCS ports are disabled on the vessel. + /// + /// 1 if any ports are disabled; 0 if all are enabled or there are no RCS ports. + public double AnyRCSDisabled() + { + return (vc.anyRcsDisabled) ? 1.0 : 0.0; + } + + /// + /// Returns the current thrust percentage of all enabled RCS thrusters. This number counts only active + /// RCS ports. Even so, it is possible for the result to be less than 1.0. For instance, if some thrusters + /// are firing at less than full power to maintain orientation while translating, the net thrust will be + /// less than 1.0. + /// + /// The result does not account for thrust reductions in the atmosphere due to lower ISP, so sea level thrust + /// will be a fraction of full thrust. + /// + /// A value between 0.0 and 1.0. + public double CurrentRCSThrust() + { + return vc.rcsActiveThrustPercent; + } + + /// + /// Enables any RCS ports that have been disabled. + /// + /// 1 if any disabled RCS ports were enabled, 0 if there were no disabled ports. + public double EnableAllRCS() + { + double anyEnabled = 0.0; + for (int i = vc.moduleRcs.Length - 1; i >= 0; --i) + { + if (!vc.moduleRcs[i].rcsEnabled) + { + vc.moduleRcs[i].rcsEnabled = true; + anyEnabled = 1.0; + } + } + + return anyEnabled; + } + + /// + /// Returns 1 if the RCS action group has any actions attached to it. Note that + /// RCS thrusters don't neccessarily appear here. + /// + /// 1 if any actions are assigned to the RCS group. + public double RCSHasActions() + { + return (vc.GroupHasActions(KSPActionGroup.RCS)) ? 1.0 : 0.0; + } + + /// + /// Returns 1 if RCS is on, 0 otherwise. + /// + /// 1 if the RCS group is enabled, 0 otherwise. + public double GetRCS() + { + return (vessel.ActionGroups[KSPActionGroup.RCS]) ? 1.0 : 0.0; + } + + /// + /// Returns 1 if any RCS thrusters are firing, 0 otherwise. + /// + /// + public double GetRCSActive() + { + return (vc.anyRcsFiring) ? 1.0 : 0.0; + } + + /// + /// Returns 1 if any RCS thrusters are configured to allow rotation, + /// 0 otherwise. + /// + /// + public double GetRCSRotate() + { + return (vc.anyRcsRotate) ? 1.0 : 0.0; + } + + /// + /// Returns the thrust-weighted average of the RCS thrust limit for + /// all enabled RCS thrusters. + /// + /// A weighted average between 0 (no thrust) and 1 (full rated thrust). + public double GetRCSThrustLimit() + { + return vc.rcsWeightedThrustLimit; + } + + /// + /// Returns 1 if any RCS thrusters are configured to allow translation, + /// 0 otherwise. + /// + /// + public double GetRCSTranslate() + { + return (vc.anyRcsTranslate) ? 1.0 : 0.0; + } + + /// + /// Returns 1 if there is at least once RCS module on the vessel. + /// + /// + public double HasRCS() + { + return (vc.moduleRcs.Length > 0) ? 1.0 : 0.0; + } + + /// + /// Set the state of RCS. + /// + /// `true` to enable RCS, `false` to disable RCS. + /// 1 if the RCS group is enabled, 0 otherwise. + public double SetRCS(bool active) + { + vessel.ActionGroups.SetGroup(KSPActionGroup.RCS, active); + return (active) ? 1.0 : 0.0; + } + + /// + /// Enable or disable RCS rotation control. + /// + /// Whether RCS should be used for rotation. + /// The number of RCS modules updated (0 if none, more than 0 if any RCS are installed). + public double SetRCSRotate(bool active) + { + for (int i = vc.moduleRcs.Length - 1; i >= 0; --i) + { + vc.moduleRcs[i].enableRoll = active; + vc.moduleRcs[i].enableYaw = active; + vc.moduleRcs[i].enablePitch = active; + } + + return vc.moduleRcs.Length; + } + + /// + /// Enable or disable RCS translation control. + /// + /// Whether RCS should be used for translation. + /// The number of RCS modules updated (0 if none, more than 0 if any RCS are installed). + public double SetRCSTranslate(bool active) + { + for (int i = vc.moduleRcs.Length - 1; i >= 0; --i) + { + vc.moduleRcs[i].enableX = active; + vc.moduleRcs[i].enableY = active; + vc.moduleRcs[i].enableZ = active; + } + + return vc.moduleRcs.Length; + } + /// + /// Set the maximum thrust limit of the RCS thrusters. + /// + /// A value between 0 (no thrust) and 1 (full thrust). + /// The RCS thrust limit, clamped to [0, 1]. + public double SetRCSThrustLimit(double limit) + { + float clampedLimit = Mathf.Clamp01((float)limit); + float flimit = clampedLimit * 100.0f; + + for (int i = vc.moduleRcs.Length - 1; i >= 0; --i) + { + vc.moduleRcs[i].thrustPercentage = flimit; + } + return clampedLimit; + } + + /// + /// Toggle RCS off-to-on or vice versa. + /// + /// 1 if the RCS group is enabled, 0 otherwise. + public double ToggleRCS() + { + vessel.ActionGroups.ToggleGroup(KSPActionGroup.RCS); + return (vessel.ActionGroups[KSPActionGroup.RCS]) ? 1.0 : 0.0; + } + + /// + /// Toggle RCS rotation control. + /// + /// 1 if rotation is now on, 0 otherwise. + public double ToggleRCSRotate() + { + if (vc.anyRcsRotate) + { + for (int i = vc.moduleRcs.Length - 1; i >= 0; --i) + { + vc.moduleRcs[i].enableRoll = false; + vc.moduleRcs[i].enableYaw = false; + vc.moduleRcs[i].enablePitch = false; + } + return 0.0; + } + else + { + for (int i = vc.moduleRcs.Length - 1; i >= 0; --i) + { + vc.moduleRcs[i].enableRoll = true; + vc.moduleRcs[i].enableYaw = true; + vc.moduleRcs[i].enablePitch = true; + } + return 1.0; + } + } + + /// + /// Toggle RCS translation control. + /// + /// 1 if translation is now on, 0 otherwise. + public double ToggleRCSTranslate() + { + if (vc.anyRcsTranslate) + { + for (int i = vc.moduleRcs.Length - 1; i >= 0; --i) + { + vc.moduleRcs[i].enableX = false; + vc.moduleRcs[i].enableY = false; + vc.moduleRcs[i].enableZ = false; + } + return 0.0; + } + else + { + for (int i = vc.moduleRcs.Length - 1; i >= 0; --i) + { + vc.moduleRcs[i].enableX = true; + vc.moduleRcs[i].enableY = true; + vc.moduleRcs[i].enableZ = true; + } + return 1.0; + } + } + #endregion RCS + + /// + /// Methods for controlling and reporting information from reaction wheels are in this + /// category. + /// + /// Unlike other categories, the reaction wheels methods can be used to inspect the + /// reaction wheels installed in the current pod (when `currentPod` is true), or the + /// methods can be used to inspect all reaction wheels *not* in the current pod (when + /// `currentPod` is false). To inspect values for all reaction wheels (current pod + /// and rest of vessel), sum the results together (with the exception of ReactionWheelState). + /// + #region Reaction Wheels + + /// + /// Returns 1 if at least one reaction wheel is on the vessel and active. Returns 0 otherwise. + /// + /// + public double GetReactionWheelActive() + { + return (vc.reactionWheelActive) ? 1.0 : 0.0; + } + + /// + /// Returns the current reaction wheel authority as a percentage of maximum. + /// + /// Reaction wheel authority in the range of [0, 1]. + public double GetReactionWheelAuthority() + { + return vc.reactionWheelAuthority; + } + + /// + /// Returns 1 if at least one reaction wheel is damaged. Returns 0 otherwise. + /// + /// + public double GetReactionWheelDamaged() + { + return (vc.reactionWheelDamaged) ? 1.0 : 0.0; + } + + /// + /// Returns the net pitch being applied by reaction wheels as a value + /// between -1 and +1. Note that if two wheels are applying pitch in + /// opposite directions, this value will cancel out and reprot 0. + /// + /// The net pitch, between -1 and +1. + public double ReactionWheelPitch() + { + return vc.reactionWheelPitch; + } + + /// + /// Returns the net roll being applied by reaction wheels as a value + /// between -1 and +1. Note that if two wheels are applying roll in + /// opposite directions, this value will cancel out and reprot 0. + /// + /// The net roll, between -1 and +1. + public double ReactionWheelRoll() + { + return vc.reactionWheelRoll; + } + + /// + /// Returns the total torque percentage currently being applied via reaction wheels. + /// + /// A number between 0 (no torque) and 1 (maximum torque). + public double ReactionWheelTorque() + { + return vc.reactionWheelNetTorque; + } + + /// + /// Returns the net yaw being applied by reaction wheels as a value + /// between -1 and +1. Note that if two wheels are applying yaw in + /// opposite directions, this value will cancel out and reprot 0. + /// + /// The net yaw, between -1 and +1. + public double ReactionWheelYaw() + { + return vc.reactionWheelYaw; + } + + /// + /// Enable or disable reaction wheels. + /// + /// If true, reaction wheels are activated. If false, they are deactivated. + /// 1 if any reaction wheels are installed, otherwise 0. + public double SetReactionWheelActive(bool active) + { + for (int i = vc.moduleReactionWheel.Length - 1; i >= 0; --i) + { + if (vc.moduleReactionWheel[i].wheelState == ModuleReactionWheel.WheelState.Active && active == false) + { + vc.moduleReactionWheel[i].OnToggle(); + } + else if (vc.moduleReactionWheel[i].wheelState == ModuleReactionWheel.WheelState.Disabled && active == true) + { + vc.moduleReactionWheel[i].OnToggle(); + } + } + + return (vc.moduleReactionWheel.Length > 0) ? 1.0 : 0.0; + } + + /// + /// Update all active reaction wheels' authority. + /// + /// The new authority percentage, between 0 and 1. Value is clamped if it is outside that range. + /// The new reaction wheel authority, or 0 if no wheels are available. + public double SetReactionWheelAuthority(double authority) + { + float newAuthority = Mathf.Clamp01((float)authority); + for (int i = vc.moduleReactionWheel.Length - 1; i >= 0; --i) + { + vc.moduleReactionWheel[i].authorityLimiter = newAuthority * 100.0f; + } + + return (vc.moduleReactionWheel.Length > 0) ? newAuthority : 0.0; + } + + /// + /// Toggle the reaction wheels. + /// + /// 1 if any reaction wheels are installed, otherwise 0. + public double ToggleReactionWheel() + { + for (int i = vc.moduleReactionWheel.Length - 1; i >= 0; --i) + { + vc.moduleReactionWheel[i].OnToggle(); + } + + return (vc.moduleReactionWheel.Length > 0) ? 1.0 : 0.0; + } + #endregion + } +} diff --git a/ModifiedCode/MASIKAC.cs b/ModifiedCode/MASIKAC.cs new file mode 100644 index 00000000..5b5bb4f4 --- /dev/null +++ b/ModifiedCode/MASIKAC.cs @@ -0,0 +1,266 @@ +/***************************************************************************** + * The MIT License (MIT) + * + * Copyright (c) 2016-2017 MOARdV + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + ****************************************************************************/ +using MoonSharp.Interpreter; +using System; +using System.Collections.Generic; +using System.Text; + +namespace AvionicsSystems +{ + /// + /// The MASIKAC class interfaces with the KACWrapper, which is imported + /// verbatim from KAC's source. + /// + /// kac + /// + /// The MASIKAC object encapsulates the interface with Kerbal Alarm Clock. + /// + internal class MASIKAC + { + internal Vessel vessel; + private double[] alarms; + private string[] alarmIDs = new string[0]; + + [MoonSharpHidden] + public MASIKAC(Vessel vessel) + { + this.vessel = vessel; + this.alarms = new double[0]; + } + + ~MASIKAC() + { + vessel = null; + } + + [MoonSharpHidden] + internal void Update() + { + if (KACWrapper.InstanceExists) + { + KACWrapper.KACAPI.KACAlarmList alarms = KACWrapper.KAC.Alarms; + + int alarmCount = alarms.Count; + + if (alarmCount > 0) + { + double UT = Planetarium.GetUniversalTime(); + int vesselAlarmCount = 0; + string id = vessel.id.ToString(); + for (int i = 0; i < alarmCount; ++i) + { + if (alarms[i].VesselID == id && alarms[i].AlarmTime > UT) + { + ++vesselAlarmCount; + } + } + + if (this.alarms.Length != vesselAlarmCount) + { + this.alarms = new double[vesselAlarmCount]; + alarmIDs = new string[vesselAlarmCount]; + } + + if (vesselAlarmCount > 0) + { + for (int i = 0; i < alarmCount; ++i) + { + if (alarms[i].VesselID == id && alarms[i].AlarmTime > UT) + { + --vesselAlarmCount; + alarmIDs[vesselAlarmCount] = alarms[i].ID; + this.alarms[vesselAlarmCount] = alarms[i].AlarmTime - UT; + } + } + + // Sort the array so the next alarm is in [0]. + Array.Sort(this.alarms); + } + } + else if (this.alarms.Length > 0) + { + this.alarms = new double[0]; + alarmIDs = new string[0]; + } + } + } + + /// + /// The functions for interacting with the Kerbal Alarm Clock are listed in this category. + /// + #region Kerbal Alarm + + /// + /// Returns the number of future alarms scheduled for this vessel. + /// Alarms that are in the past, or for other vessels, are not + /// counted. If Kerbal Alarm Clock is not installed, this value + /// is zero. + /// + /// Count of alarms for this vessel; 0 or more. + public double AlarmCount() + { + return alarms.Length; + } + + /// + /// Scans the list of alarms assigned to this vessel to see if the alarm identified by `alarmID` + /// exists. Returns 1 if it is found, 0 otherwise. + /// + /// The ID of the alarm, typically from the return value of `fc.CreateAlarm()`. + /// 1 if the alarm exists, 0 otherwise. + public double AlarmExists(string alarmID) + { + if (alarmIDs.Length > 0) + { + int idx = alarmIDs.IndexOf(alarmID); + + return (idx == -1) ? 0.0 : 1.0; + } + + return 0.0; + } + + [MASProxyAttribute(Immutable = true)] + /// + /// Returns 1 if Kerbal Alarm Clock is installed and available on this craft, 0 if it + /// is not available. + /// + /// + public double Available() + { + return (KACWrapper.InstanceExists) ? 1.0 : 0.0; + } + + /// + /// Create a Raw alarm at the time specified by `UT`, using the name `name`. This alarm is + /// assigned to the current vessel ID. + /// + /// The short name to apply to the alarm. + /// The UT when the alarm should fire. + /// The alarm ID (a string), or an empty string if the method failed. + public string CreateAlarm(string name, double UT) + { + if (KACWrapper.InstanceExists) + { + string alarmID = KACWrapper.KAC.CreateAlarm(KACWrapper.KACAPI.AlarmTypeEnum.Raw, name, UT); + + if (string.IsNullOrEmpty(alarmID)) + { + return string.Empty; + } + else + { + var newAlarm = KACWrapper.KAC.Alarms.Find(x => x.ID == alarmID); + if (newAlarm != null) + { + newAlarm.VesselID = vessel.id.ToString(); + } + + return alarmID; + } + + } + + return string.Empty; + } + + /// + /// Create an alarm of specified at the time specified by `UT`, using the name `name`. This alarm is + /// assigned to the current vessel ID. + /// + /// The type of alarm to create." + /// The short name to apply to the alarm. + /// The UT when the alarm should fire. + /// The alarm ID (a string), or an empty string if the method failed. + public string CreateTypeAlarm(string alarmTypeStr, string name, double UT) + { + if (KACWrapper.InstanceExists) + { + KACWrapper.KACAPI.AlarmTypeEnum alarmType = (KACWrapper.KACAPI.AlarmTypeEnum) Enum.Parse(typeof(KACWrapper.KACAPI.AlarmTypeEnum), alarmTypeStr); + + string alarmID = KACWrapper.KAC.CreateAlarm(alarmType, name, UT); + + if (string.IsNullOrEmpty(alarmID)) + { + return string.Empty; + } + else + { + var newAlarm = KACWrapper.KAC.Alarms.Find(x => x.ID == alarmID); + if (newAlarm != null) + { + newAlarm.VesselID = vessel.id.ToString(); + } + + return alarmID; + } + + } + + return string.Empty; + } + + /// + /// Attempts to remove the alarm identified by `alarmID`. Normally, `alarmID` is the return + /// value from `fc.CreateAlarm()`. + /// + /// The ID of the alarm to remove + /// 1 if the alarm was removed, 0 if it was not, or it did not exist, or the Kerbal Alarm Clock is not available. + public double DeleteAlarm(string alarmID) + { + if (alarms.Length > 0) + { + return (KACWrapper.KAC.DeleteAlarm(alarmID)) ? 1.0 : 0.0; + } + return 0.0; + } + + /// + /// Returns the time to the next alarm scheduled in Kerbal Alarm Clock + /// for this vessel. If no alarm is scheduled, or all alarms occurred in + /// the past, this value is 0. + /// + /// Time to the next alarm for the current vessel, in seconds; 0 if there are no alarms. + public double TimeToAlarm() + { + if (alarms.Length > 0) + { + return alarms[0]; + } + else + { + return 0.0; + } + } + #endregion + + #region Reflection Configuration + static MASIKAC() + { + KACWrapper.InitKACWrapper(); + } + #endregion + } +} diff --git a/ModifiedCode/MASITransfer.cs b/ModifiedCode/MASITransfer.cs new file mode 100644 index 00000000..0ac59e65 --- /dev/null +++ b/ModifiedCode/MASITransfer.cs @@ -0,0 +1,1746 @@ +//#define COMPARE_PHASE_ANGLE_PROTRACTOR +//#define DEBUG_CHANGE_ALTITUDE +/***************************************************************************** + * The MIT License (MIT) + * + * Copyright (c) 2016-2018 MOARdV + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + ****************************************************************************/ +using MoonSharp.Interpreter; +using System; +using System.Collections.Generic; +using System.Text; +using UnityEngine; + +namespace AvionicsSystems +{ + // ΔV - put this somewhere where I can find it easily to copy/paste + + /// + /// The MASITransfer class contains functionality equivalent to the KSP + /// mod Protractor. However, the code here was written from scratch, since + /// Protractor is GPL. + /// + /// transfer + /// The MASITransfer module does calculations to find phase angles + /// and ejection angles for Hohmann transfer orbits. It provides functionality + /// equivalent to the Protractor mod, but focused strictly on computations + /// involving the current vessel and a target (either another vessel or a + /// Celestial Body). + /// + /// Note that MASITransfer assumes the target has a small relative inclination. + /// It will generate erroneous results for high inclination and retrograde relative + /// orbits. + /// + /// In addition to providing orbital transfer information, the MASITransfer module + /// can create maneuver nodes for basic orbital operations (changing Ap or Pe, + /// circularizing at a specific altitude, plotting Hohmann transfers, etc). + /// + internal class MASITransfer + { + private bool invalid = true; + + internal Vessel vessel; + internal MASVesselComputer vc; + + private bool hohmannTransfer; + private double currentPhaseAngle; + private double transferPhaseAngle; + private double timeUntilTransfer; + + private double ejectionDeltaV; + private double currentEjectionAngle; + private double transferEjectionAngle; + private double timeUntilEjection; + + private double oberthAltitude; + private double oberthEjectionVelocity; + private double oberthCurrentEjectionAngle; + private double oberthTransferEjectionAngle; + private double oberthTimeUntilEjection; + + private double initialDeltaV; + private double finalDeltaV; + + private Orbit oUpper = new Orbit(); + private Orbit oLower = new Orbit(); + private Orbit oMid = new Orbit(); + + [MoonSharpHidden] + public MASITransfer(Vessel vessel) + { + this.vessel = vessel; + } + + /// + /// The Delta-V section provides information on the amount of velocity + /// change needed to change orbits. This information can be computed + /// based on the current target, or a target altitude, depending on the + /// specific method called. + /// + /// These values are estimates based on circular orbits, assuming + /// no plane change is required. Eccentric orbits, or non-coplanar + /// orbits, will not reflect the total ΔV required. + /// + #region Transfer Delta-V + + /// + /// Returns an estimate of the ΔV required to circularize a Hohmann transfer at + /// the target's orbit. + /// + /// Negative values indicate a retrograde burn. Positive values indicate a + /// prograde burn. + /// + /// The ΔV in m/s to finialize the transfer. + public double DeltaVFinal() + { + if (vc.activeTarget != null) + { + if (invalid) + { + UpdateTransferParameters(); + } + + return finalDeltaV; + } + else + { + return 0.0; + } + } + + /// + /// Returns and estimate of the ΔV required to circularize the vessel's orbit + /// at the altitude provided. + /// + /// Negative values indicate a retrograde burn. Positive values indicate a + /// prograde burn. + /// + /// Destination altitude, in meters. + /// ΔV in m/s to circularize at the requested altitude, or 0 if the vessel is not in flight. + public double DeltaVFinal(double destinationAltitude) + { + if (!(vessel.Landed || vessel.Splashed)) + { + double GM = vessel.mainBody.gravParameter; + double rA = vessel.orbit.semiMajorAxis; + double rB = destinationAltitude + vessel.mainBody.Radius; + + double atx = 0.5 * (rA + rB); + double Vf = Math.Sqrt(GM / rB); + + double Vtxf = Math.Sqrt(GM * (2.0 / rB - 1.0 / atx)); + + return Vf - Vtxf; + } + else + { + return 0.0; + } + } + + /// + /// Returns an estimate of the ΔV required to start a Hohmann transfer to + /// the target's orbit. + /// + /// Negative values indicate a retrograde burn. Positive values indicate a + /// prograde burn. + /// + /// The ΔV in m/s to start the transfer. + public double DeltaVInitial() + { + if (vc.activeTarget != null) + { + if (invalid) + { + UpdateTransferParameters(); + } + + return initialDeltaV; + } + else + { + return 0.0; + } + } + + /// + /// Returns and estimate of the ΔV required to change the vessel's orbit + /// to the altitude provided. + /// + /// Negative values indicate a retrograde burn. Positive values indicate a + /// prograde burn. + /// + /// Destination altitude, in meters. + /// ΔV in m/s to reach the requested altitude, or 0 if the vessel is not in flight. + public double DeltaVInitial(double destinationAltitude) + { + if (!(vessel.Landed || vessel.Splashed)) + { + return DeltaVInitial(vessel.orbit.semiMajorAxis, destinationAltitude + vessel.mainBody.Radius, vessel.mainBody.gravParameter); + } + else + { + return 0.0; + } + } + #endregion + + /// + /// The Ejection Angle region provides information on the ejection angle. The + /// ejection angle is used on interplanetary transfers to determine when + /// the vessel should start its burn to escape the world it currently orbits. + /// + /// When the vessel is orbiting a moon in preparation for an interplanetary + /// transfer, the target ejection angle will reflect the ejection angle + /// required to take advantage of the Oberth effect during the ejection. + /// + #region Transfer Ejection Angle + + /// + /// Reports the vessel's current ejection angle. When this value matches + /// the transfer ejection angle, it is time to start an interplanetary burn. + /// + /// This angle is a measurement of the vessel from the planet's prograde direction. + /// + /// Current ejection angle in degrees, or 0 if there is no ejection angle. + public double CurrentEjectionAngle() + { + if (vc.activeTarget != null) + { + if (invalid) + { + UpdateTransferParameters(); + } + + return currentEjectionAngle; + } + else + { + return 0.0; + } + } + + /// + /// The ΔV required to reach the correct exit velocity for an interplanetary transfer. + /// + /// ΔV in m/s, or 0 if there is no exit velocity required. + public double EjectionVelocity() + { + if (vc.activeTarget != null) + { + if (invalid) + { + UpdateTransferParameters(); + } + + return ejectionDeltaV; + } + else + { + return 0.0; + } + } + + /// + /// Reports the difference between the vessel's current ejection angle + /// and the transfer ejection angle. When this value is 0, it is time to + /// start an interplanetary burn. + /// + /// Relative ejection angle in degrees, or 0 if there is no ejection angle. + public double RelativeEjectionAngle() + { + if (vc.activeTarget != null) + { + if (invalid) + { + UpdateTransferParameters(); + } + + return Utility.NormalizeAngle(currentEjectionAngle - transferEjectionAngle); + } + else + { + return 0.0; + } + } + + /// + /// Provides the time until the vessel reaches the transfer ejection angle. + /// + /// Time until the relative ejection angle is 0, in seconds, or 0 if there is no ejection angle. + public double TimeUntilEjection() + { + if (vc.activeTarget != null) + { + if (invalid) + { + UpdateTransferParameters(); + } + + return timeUntilEjection; + } + else + { + return 0.0; + } + } + + /// + /// Reports the ejection angle when an interplanetary Hohmann transfer + /// orbit should begin. This is of use for transfers from one planet + /// to another - once the transfer phase angle has been reached, the + /// vessel should launch when the next transfer ejection angle is reached. + /// + /// Transfer ejection angle in degrees, or 0 if there is no ejection angle. + public double TransferEjectionAngle() + { + if (vc.activeTarget != null) + { + if (invalid) + { + UpdateTransferParameters(); + } + + return transferEjectionAngle; + } + else + { + return 0.0; + } + } + + #endregion + + /// + /// The Maneuver Planning region provides functions that can be used to generate + /// maneuver nodes to accomplish basic orbital tasks. This capability + /// does not include autopilot functionality - it is simply a set of helper functions to create + /// maneuver nodes. + /// + /// The MAS Maneuver Planner is not as full-featured as MechJeb - it does not + /// work with parabolic / hyperbolic orbits, for instance. + /// + #region Transfer Maneuver Planning + + /// + /// Raise or lower the altitude of the apoapsis. The maneuver node is placed at + /// periapsis to minimize fuel requirements. If an invalid apoapsis is supplied + /// (either by being below the periapsis, or above the SoI of the planet), this function does + /// nothing. + /// + /// The new altitude for the apoapsis, in meters. + /// 1 if a valid maneuver node was created, 0 if it was not. + public double ChangeApoapsis(double newAltitude) + { + Orbit current = vessel.orbit; + double newApR = newAltitude + current.referenceBody.Radius; + if (newApR >= current.PeR && newApR <= current.referenceBody.sphereOfInfluence && vessel.patchedConicSolver != null) + { + CelestialBody referenceBody = current.referenceBody; + double ut = current.timeToPe + Planetarium.GetUniversalTime(); + Vector3d posAtUt = current.getRelativePositionAtUT(ut); + Vector3d velAtUt = current.getOrbitalVelocityAtUT(ut); + Vector3d fwdAtUt = velAtUt.normalized; + + double dVUpper; + double dVLower; + + dVLower = DeltaVInitial(current.PeR, newApR, referenceBody.gravParameter, velAtUt.magnitude); + if (current.eccentricity >= 1.0 || newApR > current.ApR) + { + // Could possibly set dvUpper to 0? + dVUpper = DeltaVInitial(Math.Min(2.0 * newApR, current.referenceBody.sphereOfInfluence), newApR, referenceBody.gravParameter); + } + else + { + // Our current orbit brackets the desired altitude, so we'll initialize our search with the + // Ap and Pe. Since the maneuver starts at the Pe, we'll account for actual velocity with + // our lower bound value, which should allow us to converge to the solution quicker (or immediately). + dVUpper = DeltaVInitial(current.ApR, newApR, referenceBody.gravParameter); + } + oUpper.UpdateFromStateVectors(posAtUt, velAtUt + fwdAtUt * dVUpper, referenceBody, ut); + oLower.UpdateFromStateVectors(posAtUt, velAtUt + fwdAtUt * dVLower, referenceBody, ut); + + double dVMid = (dVUpper + dVLower) * 0.5; + double apUpper = (oUpper.ApR < 0.0) ? current.referenceBody.sphereOfInfluence : oUpper.ApR; + double apLower = (oLower.ApR < 0.0) ? current.referenceBody.sphereOfInfluence : oLower.ApR; + +#if DEBUG_CHANGE_ALTITUDE + Utility.LogMessage(this, "Change Ap {0:0.000}:", newAltitude * 0.001); +#endif + if (Math.Abs(apLower - newApR) < 1.0) + { +#if DEBUG_CHANGE_ALTITUDE + Utility.LogMessage(this, "Close enough - apLower"); +#endif + // Within 1m of the desired altitude - good enough. + // apLower is most likely to be right on, since we're using + // the actual orbital velocity @ Pe to determine dV. + dVMid = dVLower; + } + else if (Math.Abs(apUpper - newApR) < 1.0) + { +#if DEBUG_CHANGE_ALTITUDE + Utility.LogMessage(this, "Close enough - apUpper"); +#endif + // Within 1m of the desired altitude - good enough. + dVMid = dVUpper; + } + else + { + oMid.UpdateFromStateVectors(posAtUt, velAtUt + fwdAtUt * dVMid, referenceBody, ut); + double apMid = (oMid.ApR < 0.0) ? current.referenceBody.sphereOfInfluence : oMid.ApR; + + while (Math.Abs(dVUpper - dVLower) > 0.015625) + { +#if DEBUG_CHANGE_ALTITUDE + Utility.LogMessage(this, " - Upper = {0,6:0.0}m/s -> Pe {1,9:0.000}, Ap {2,9:0.000}", dVUpper, oUpper.PeA * 0.001, oUpper.ApA * 0.001); + Utility.LogMessage(this, " - Lower = {0,6:0.0}m/s -> Pe {1,9:0.000}, Ap {2,9:0.000}", dVLower, oLower.PeA * 0.001, oLower.ApA * 0.001); + Utility.LogMessage(this, " - Mid = {0,6:0.0}m/s -> Pe {1,9:0.000}, Ap {2,9:0.000}", dVMid, oMid.PeA * 0.001, oMid.ApA * 0.001); +#endif + if (Math.Abs(apLower - newApR) < 1.0) + { + // Within 1m of the desired altitude - good enough. + dVMid = dVLower; + // Only for debug printing: +#if DEBUG_CHANGE_ALTITUDE + oMid.UpdateFromStateVectors(posAtUt, velAtUt + fwdAtUt * dVMid, referenceBody, ut); +#endif + break; + } + else if (Math.Abs(apUpper - newApR) < 1.0) + { + // Within 1m of the desired altitude - good enough. + dVMid = dVUpper; + // Only for debug printing: +#if DEBUG_CHANGE_ALTITUDE + oMid.UpdateFromStateVectors(posAtUt, velAtUt + fwdAtUt * dVMid, referenceBody, ut); +#endif + break; + } + else if (Math.Abs(apUpper - newApR) < Math.Abs(apLower - newApR)) + { + apLower = apMid; + dVLower = dVMid; + + Orbit tmp = oLower; + oLower = oMid; + oMid = tmp; + } + else + { + apUpper = apMid; + dVUpper = dVMid; + + Orbit tmp = oUpper; + oUpper = oMid; + oMid = tmp; + } + dVMid = (dVUpper + dVLower) * 0.5; + oMid.UpdateFromStateVectors(posAtUt, velAtUt + fwdAtUt * dVMid, referenceBody, ut); + apMid = (oMid.ApR < 0.0) ? current.referenceBody.sphereOfInfluence : oMid.ApR; + } +#if DEBUG_CHANGE_ALTITUDE + Utility.LogMessage(this, " - Final = {0,6:0.0}m/s -> Pe {1,9:0.000}, Ap {2,9:0.000}", dVMid, oMid.PeA * 0.001, oMid.ApA * 0.001); +#endif + } + + Vector3d dV = Utility.ManeuverNode(dVMid, 0.0, 0.0); + + vessel.patchedConicSolver.maneuverNodes.Clear(); + ManeuverNode mn = vessel.patchedConicSolver.AddManeuverNode(ut); + mn.OnGizmoUpdated(dV, ut); + + return 1.0; + } + + return 0.0; + } + + /// + /// Change the inclination of the orbit. The change will be scheduled at either the AN or DN + /// of the orbit, whichever results in a less expensive maneuver. + /// + /// The new inclination, in degrees. + /// 1 if the maneuver was scheduled, 0 if it could not be scheduled. + public double ChangeInclination(double newInclination) + { + Orbit current = vessel.orbit; + + if (MASFlightComputerProxy.ConvertVesselSituation(vessel.situation) > 2 && vessel.patchedConicSolver != null && current.eccentricity < 1.0) + { + double deltaI = newInclination - current.inclination; + double deltaI2 = Utility.NormalizeLongitude(deltaI); + + if (Math.Abs(deltaI2) > 0.0) + { + Vector3d ANVector = current.GetANVector(); + ANVector.Normalize(); + + double taAN = current.GetTrueAnomalyOfZupVector(ANVector); + double timeAN = Utility.NormalizeOrbitTime(current.GetUTforTrueAnomaly(taAN, current.period) - vc.universalTime, current) + vc.universalTime; + Vector3d vAN = current.getOrbitalVelocityAtUT(timeAN); + + double taDN = taAN + Math.PI; + double timeDN = Utility.NormalizeOrbitTime(current.GetUTforTrueAnomaly(taDN, current.period) - vc.universalTime, current) + vc.universalTime; + Vector3d vDN = current.getOrbitalVelocityAtUT(timeDN); + + double deltaVScale = Math.Sin(0.5 * Utility.Deg2Rad * deltaI2); + + // Pick the slower of the two nodes + double mTime; + double speedAtUt; + if (vAN.sqrMagnitude < vDN.sqrMagnitude) + { + mTime = timeAN; + speedAtUt = vAN.magnitude; + } + else + { + mTime = timeDN; + speedAtUt = vDN.magnitude; + } + + double planeChangedV = 2.0 * speedAtUt * deltaVScale; + + // This is interesting ... Eq. 4.73 at http://www.braeunig.us/space/ describes the ΔV + // needed to change inclination. However, if I apply that strictly as a ΔV on the + // normal, the resulting orbit is not correct. Looking at the results MechJeb generates, + // I notice that there's a retrograde ΔV equal to sin(Θ/2) (which MJ doesn't directly compute - + // it's an artifact of its technique). So, first I determine the prograde component of + // the maneuver, and then I apply the balance of the ΔV to the normal. + double progradedV = -Math.Abs(deltaVScale * planeChangedV); + double normaldV = Math.Sqrt(planeChangedV * planeChangedV - progradedV * progradedV) * Math.Sign(deltaVScale); + Vector3d maneuver = Utility.ManeuverNode(progradedV, normaldV, 0.0); + + vessel.patchedConicSolver.maneuverNodes.Clear(); + ManeuverNode mn = vessel.patchedConicSolver.AddManeuverNode(mTime); + mn.OnGizmoUpdated(maneuver, mTime); + + return 1.0; + } + } + + return 0.0; + } + + /// + /// Raise or lower the altitude of the periapsis. The maneuver node is placed at + /// apoapsis to minimize fuel requirements. If an invalid periapsis is supplied + /// (either by being above the apoapsis, or lower than the center of the planet, or + /// if the current orbit is hyperbolic), this function does + /// nothing. + /// + /// The new altitude for the periapsis, in meters. + /// 1 if a valid maneuver node was created, 0 if it was not. + public double ChangePeriapsis(double newAltitude) + { + Orbit current = vessel.orbit; + double newPeR = newAltitude + current.referenceBody.Radius; + if (newPeR >= 0.0 && newPeR <= current.ApR && vessel.patchedConicSolver != null && current.eccentricity < 1.0) + { + CelestialBody referenceBody = current.referenceBody; + double ut = current.timeToAp + Planetarium.GetUniversalTime(); + Vector3d posAtUt = current.getRelativePositionAtUT(ut); + Vector3d velAtUt = current.getOrbitalVelocityAtUT(ut); + Vector3d fwdAtUt = velAtUt.normalized; + + double dVUpper; + double dVLower; + + dVUpper = DeltaVInitial(current.ApR, newPeR, referenceBody.gravParameter, velAtUt.magnitude); + if (newPeR < current.PeR) + { + // Our current Pe is higher than the target, so we treat it as the upper bound and half the + // target Pe as the lower bound. + dVLower = DeltaVInitial(newPeR * 0.5, newPeR, referenceBody.gravParameter); + } + else + { + // Our current orbit brackets the desired altitude, so we'll initialize our search with the + // Ap and Pe. + dVLower = DeltaVInitial(current.PeR, newPeR, referenceBody.gravParameter); + } + oUpper.UpdateFromStateVectors(posAtUt, velAtUt + fwdAtUt * dVUpper, referenceBody, ut); + oLower.UpdateFromStateVectors(posAtUt, velAtUt + fwdAtUt * dVLower, referenceBody, ut); + + double dVMid = (dVUpper + dVLower) * 0.5; + double peUpper = oUpper.PeR; + double peLower = oLower.PeR; + +#if DEBUG_CHANGE_ALTITUDE + Utility.LogMessage(this, "Change Pe {0:0.000}:", newAltitude * 0.001); +#endif + + if (Math.Abs(peUpper - newPeR) < 1.0) + { +#if DEBUG_CHANGE_ALTITUDE + Utility.LogMessage(this, "Close enough - peUpper"); +#endif + // Within 1m of the desired altitude - good enough. + // peUpper is most likely to be right on, since we're using + // the actual orbital velocity @ Ap to determine dV. + dVMid = dVUpper; + } + else if (Math.Abs(peLower - newPeR) < 1.0) + { +#if DEBUG_CHANGE_ALTITUDE + Utility.LogMessage(this, "Close enough - peLower"); +#endif + // Within 1m of the desired altitude - good enough. + dVMid = dVLower; + } + else + { + oMid.UpdateFromStateVectors(posAtUt, velAtUt + fwdAtUt * dVMid, referenceBody, ut); + double peMid = oMid.PeR; + + while (Math.Abs(dVUpper - dVLower) > 0.015625) + { +#if DEBUG_CHANGE_ALTITUDE + Utility.LogMessage(this, " - Upper = {0,6:0.0}m/s -> Pe {1,9:0.000}, Ap {2,9:0.000}", dVUpper, oUpper.PeA * 0.001, oUpper.ApA * 0.001); + Utility.LogMessage(this, " - Lower = {0,6:0.0}m/s -> Pe {1,9:0.000}, Ap {2,9:0.000}", dVLower, oLower.PeA * 0.001, oLower.ApA * 0.001); + Utility.LogMessage(this, " - Mid = {0,6:0.0}m/s -> Pe {1,9:0.000}, Ap {2,9:0.000}", dVMid, oMid.PeA * 0.001, oMid.ApA * 0.001); +#endif + if (Math.Abs(peUpper - newPeR) < 1.0) + { + // Within 1m of the desired altitude - good enough. + dVMid = dVLower; + // Only for debug printing: +#if DEBUG_CHANGE_ALTITUDE + oMid.UpdateFromStateVectors(posAtUt, velAtUt + fwdAtUt * dVMid, referenceBody, ut); +#endif + break; + } + else if (Math.Abs(peLower - newPeR) < 1.0) + { + // Within 1m of the desired altitude - good enough. + dVMid = dVUpper; + // Only for debug printing: +#if DEBUG_CHANGE_ALTITUDE + oMid.UpdateFromStateVectors(posAtUt, velAtUt + fwdAtUt * dVMid, referenceBody, ut); +#endif + break; + } + else if (Math.Abs(peUpper - newPeR) < Math.Abs(peLower - newPeR)) + { + peLower = peMid; + dVLower = dVMid; + + Orbit tmp = oLower; + oLower = oMid; + oMid = tmp; + } + else + { + peUpper = peMid; + dVUpper = dVMid; + + Orbit tmp = oUpper; + oUpper = oMid; + oMid = tmp; + } + dVMid = (dVUpper + dVLower) * 0.5; + oMid.UpdateFromStateVectors(posAtUt, velAtUt + fwdAtUt * dVMid, referenceBody, ut); + peMid = oMid.PeR; + } +#if DEBUG_CHANGE_ALTITUDE + Utility.LogMessage(this, " - Final = {0,6:0.0}m/s -> Pe {1,9:0.000}, Ap {2,9:0.000}", dVMid, oMid.PeA * 0.001, oMid.ApA * 0.001); +#endif + } + + Vector3d dV = Utility.ManeuverNode(dVMid, 0.0, 0.0); + + vessel.patchedConicSolver.maneuverNodes.Clear(); + ManeuverNode mn = vessel.patchedConicSolver.AddManeuverNode(ut); + mn.OnGizmoUpdated(dV, ut); + + return 1.0; + } + + return 0.0; + } + + /// + /// Circularize the vessel's orbit at the specified altitude, in meters. This new altitude + /// must be between the current periapsis and apoapsis, and the current orbit must not be hyperbolic. + /// + /// The altitude at which the orbit will be circularized, in meters. + /// 1 if a node was created, 0 otherwise. + public double CircularizeAltitude(double newAltitude) + { + Orbit current = vessel.orbit; + double newSMA = newAltitude + current.referenceBody.Radius; + if (newSMA >= current.PeR && newSMA <= current.ApR && vessel.patchedConicSolver != null && current.eccentricity < 1.0) + { + CelestialBody referenceBody = current.referenceBody; + double vNew = Math.Sqrt(referenceBody.gravParameter / newSMA); + double maneuverUt = Planetarium.GetUniversalTime() + Utility.NextTimeToRadius(current, newSMA); + + Vector3d velAtUt = current.getOrbitalVelocityAtUT(maneuverUt); + + Vector3d relativePosition = current.getRelativePositionAtUT(maneuverUt); + + Vector3d prograde; + Vector3d normal; + Vector3d radial; + Utility.GetOrbitalBasisVectors(velAtUt, relativePosition, out prograde, out radial, out normal); + + Vector3d upAtUt = relativePosition.xzy.normalized; + Vector3d fwdAtUt = Vector3d.Cross(upAtUt, normal); + Vector3d maneuverVel = fwdAtUt * vNew; + + Vector3d deltaV = maneuverVel - velAtUt.xzy; + //Utility.LogMessage(this, "dV = {0} because {1} - {2}", deltaV, maneuverVel, velAtUt); + //Utility.LogMessage(this, "prograde (dot) fwd = {0:0.000}", Vector3d.Dot(prograde, fwdAtUt)); + + Vector3d maneuverdV = Utility.ManeuverNode(Vector3d.Dot(deltaV, prograde), Vector3d.Dot(deltaV, normal), Vector3d.Dot(deltaV, radial)); + + vessel.patchedConicSolver.maneuverNodes.Clear(); + ManeuverNode mn = vessel.patchedConicSolver.AddManeuverNode(maneuverUt); + mn.OnGizmoUpdated(maneuverdV, maneuverUt); + + //Vector3d posAtUt = current.getRelativePositionAtUT(maneuverUt); + //oUpper.UpdateFromStateVectors(posAtUt, maneuverVel.xzy, referenceBody, maneuverUt); + //Utility.LogMessage(this, "Circularize at {0:0.000}km: {1:0.000} x {2:0.000} @ {3:0.0}", + // newAltitude * 0.001, + // oUpper.ApA * 0.001, oUpper.PeA * 0.001, + // oUpper.inclination); + + return 1.0; + } + + return 0.0; + } + + /// + /// Circularize the vessel's orbit at the specified altitude, in meters. This new altitude + /// must be between the current periapsis and the SoI limit. If the orbit is hyperbolic and the craft + /// is past the periapsis, the altitude must also be greater than the current altitude. + /// + /// The altitude at which the orbit will be circularized, in meters. + /// 1 if a node was created, 0 otherwise. + public double CircularizeAltitudeHyp(double newAltitude) + { + Orbit current = vessel.orbit; + double newSMA = newAltitude + current.referenceBody.Radius; + + Utility.LogMessage(this, "newSMA = {0} because {1} + {2}", newSMA, newAltitude, current.referenceBody.Radius); + + if ((newSMA >= current.PeR && vc.orbit.timeToPe >= 0 || newSMA >= vessel.altitude) && newSMA <= vessel.mainBody.sphereOfInfluence && vessel.patchedConicSolver != null) + { + CelestialBody referenceBody = current.referenceBody; + double vNew = Math.Sqrt(referenceBody.gravParameter / newSMA); + double tAAtNewSMA = current.TrueAnomalyAtRadius(newSMA); + //double maneuverUt = Planetarium.GetUniversalTime() + current.GetUTforTrueAnomaly(tAAtNewSMA, 0); + double maneuverUt = current.GetUTforTrueAnomaly(tAAtNewSMA, 0); + + Utility.LogMessage(this, "maneuverUt = {0} because {1}", maneuverUt, current.GetUTforTrueAnomaly(tAAtNewSMA, 0)); + + Vector3d velAtUt = current.getOrbitalVelocityAtUT(maneuverUt); + + Utility.LogMessage(this, "velAtUt = {0} ", velAtUt); + + Vector3d relativePosition = current.getRelativePositionAtUT(maneuverUt); + + Utility.LogMessage(this, "relativePosition = {0}", relativePosition); + + Vector3d prograde; + Vector3d normal; + Vector3d radial; + Utility.GetOrbitalBasisVectors(velAtUt, relativePosition, out prograde, out radial, out normal); + + Utility.LogMessage(this, "maneuverUt = {0} because {1} - {2}", maneuverUt, Planetarium.GetUniversalTime(), current.GetUTforTrueAnomaly(tAAtNewSMA, 0)); + + Vector3d upAtUt = relativePosition.xzy.normalized; + Vector3d fwdAtUt = Vector3d.Cross(upAtUt, normal); + Vector3d maneuverVel = fwdAtUt * vNew; + + Utility.LogMessage(this, "maneuverVel = {0} because {1} - {2}", maneuverVel, upAtUt, fwdAtUt); + + Vector3d deltaV = maneuverVel - velAtUt.xzy; + Utility.LogMessage(this, "dV = {0} because {1} - {2}", deltaV, maneuverVel, velAtUt); + Utility.LogMessage(this, "prograde (dot) fwd = {0:0.000}", Vector3d.Dot(prograde, fwdAtUt)); + + Vector3d maneuverdV = Utility.ManeuverNode(Vector3d.Dot(deltaV, prograde), Vector3d.Dot(deltaV, normal), Vector3d.Dot(deltaV, radial)); + + vessel.patchedConicSolver.maneuverNodes.Clear(); + ManeuverNode mn = vessel.patchedConicSolver.AddManeuverNode(maneuverUt); + mn.OnGizmoUpdated(maneuverdV, maneuverUt); + + //Vector3d posAtUt = current.getRelativePositionAtUT(maneuverUt); + //oUpper.UpdateFromStateVectors(posAtUt, maneuverVel.xzy, referenceBody, maneuverUt); + //Utility.LogMessage(this, "Circularize at {0:0.000}km: {1:0.000} x {2:0.000} @ {3:0.0}", + // newAltitude * 0.001, + // oUpper.ApA * 0.001, oUpper.PeA * 0.001, + // oUpper.inclination); + + return 1.0; + } + + return 0.0; + } + + /// + /// Circularize the vessel's orbit at the specified altitude, in meters. This new altitude + /// must be between the current periapsis and the SoI limit. If the orbit is hyperbolic and the craft + /// is past the periapsis, the altitude must also be greater than the current altitude. This method + /// uses calculations derived from first principles rather than the KSP orbit functions. + /// + /// The altitude at which the orbit will be circularized, in meters. + /// 1 if a node was created, 0 otherwise. + public double CircularizeAltitudeHypVis(double newAltitude) + { + Orbit current = vessel.orbit; + double newSMA = newAltitude + current.referenceBody.Radius; + + Utility.LogMessage(this, "newSMA = {0} because {1} + {2}", newSMA, newAltitude, current.referenceBody.Radius); + + if ((newSMA >= current.PeR && vc.orbit.timeToPe >= 0 || newSMA >= vessel.altitude) && newSMA <= vessel.mainBody.sphereOfInfluence && vessel.patchedConicSolver != null) + { + CelestialBody referenceBody = current.referenceBody; + double vNew = Math.Sqrt(referenceBody.gravParameter / newSMA); + double tAAtNewSMA = current.TrueAnomalyAtRadius(newSMA); + //double maneuverUt = Planetarium.GetUniversalTime() + current.GetUTforTrueAnomaly(tAAtNewSMA, 0); + double maneuverUt = current.GetUTforTrueAnomaly(tAAtNewSMA, 0); + + Utility.LogMessage(this, "maneuverUt = {0} because {1}", maneuverUt, current.GetUTforTrueAnomaly(tAAtNewSMA, 0)); + + double vPreBurn = Math.Sqrt(referenceBody.gravParameter * (2 / newSMA - 1 / current.semiMajorAxis)); + + Utility.LogMessage(this, "v before burn = {0} ", vPreBurn); + + /* double spAngMom = current.PeR * current.getOrbitalSpeedAt(current.GetTimeToPeriapsis() + Planetarium.GetUniversalTime()); + + Utility.LogMessage(this, "specific angular momentum = {0} PeR = {1} time to PE = {2} UT at PE = {3} speed at PE = {4}", + spAngMom, current.PeR, current.GetTimeToPeriapsis(), current.GetTimeToPeriapsis() + Planetarium.GetUniversalTime(), + current.getOrbitalSpeedAt(current.GetTimeToPeriapsis() + Planetarium.GetUniversalTime()));*/ + + double spAngMom = current.PeR * current.getOrbitalSpeedAtDistance(current.PeR); + + Utility.LogMessage(this, "specific angular momentum = {0} PeR = {1} PeA = {2} Speed at PeR = {3} speed at PeA = {4}", + spAngMom, current.PeR, current.PeA, current.getOrbitalSpeedAtDistance(current.PeR), current.getOrbitalSpeedAtDistance(current.PeA)); + + double vPreBurnCirc = spAngMom / newSMA; + + Utility.LogMessage(this, "preburn circumferential velocity = {0}", vPreBurnCirc); + + double elevationAngle = Math.Acos(vPreBurnCirc / vPreBurn); + + Utility.LogMessage(this, "Elevation angle = {0}", elevationAngle); + + double deltaVTotal = Math.Sqrt(Math.Pow(vNew, 2) + Math.Pow(vPreBurn, 2) - 2 * vNew * vPreBurn * Math.Cos(elevationAngle)); + + Utility.LogMessage(this, "deltaVTotal = {0}", deltaVTotal); + + /*double angleOffRetrograde = Math.Asin(vNew / vPreBurn); + + Utility.LogMessage(this, "angle off retrograde = {0}", angleOffRetrograde);*/ + + /*double deltaVPrograde = -deltaVTotal * Math.Cos(angleOffRetrograde); + double deltaVRadial = -deltaVTotal * Math.Sin(angleOffRetrograde);*/ + + double deltaVPrograde = - (vPreBurn - vNew * Math.Cos(elevationAngle)); + double deltaVRadial = -vNew * Math.Sin(elevationAngle); + + Utility.LogMessage(this, "prograde = {0}, radial = {1}", deltaVPrograde, deltaVRadial); + + Vector3d maneuverdV = Utility.ManeuverNode(deltaVPrograde, 0, deltaVRadial); + + Utility.LogMessage(this, "maneuverdV = {0}", maneuverdV); + + vessel.patchedConicSolver.maneuverNodes.Clear(); + ManeuverNode mn = vessel.patchedConicSolver.AddManeuverNode(maneuverUt); + mn.OnGizmoUpdated(maneuverdV, maneuverUt); + + //Vector3d posAtUt = current.getRelativePositionAtUT(maneuverUt); + //oUpper.UpdateFromStateVectors(posAtUt, maneuverVel.xzy, referenceBody, maneuverUt); + //Utility.LogMessage(this, "Circularize at {0:0.000}km: {1:0.000} x {2:0.000} @ {3:0.0}", + // newAltitude * 0.001, + // oUpper.ApA * 0.001, oUpper.PeA * 0.001, + // oUpper.inclination); + + return 1.0; + } + + return 0.0; + } + + /// + /// Generate a maneuver to conduct a Hohmann transfer to the current target. If there is no + /// target, or the transfer is not a simple transfer to a nearly co-planar target, nothing happens. + /// + /// Results are best when both the vessel and the target are in low-eccentricity orbits. + /// + /// Returns 1 if a transfer was successfully plotted, 0 otherwise. + public double HohmannTransfer() + { + if (hohmannTransfer) + { + Orbit current = vessel.orbit; + CelestialBody referenceBody = current.referenceBody; + double maneuverUt = Planetarium.GetUniversalTime() + timeUntilTransfer; + + Vector3d velAtUt = current.getOrbitalVelocityAtUT(maneuverUt); + + Vector3d relativePosition = current.getRelativePositionAtUT(maneuverUt); + + Vector3d prograde; + Vector3d normal; + Vector3d radial; + Utility.GetOrbitalBasisVectors(velAtUt, relativePosition, out prograde, out radial, out normal); + + Vector3d upAtUt = relativePosition.xzy.normalized; + Vector3d fwdAtUt = Vector3d.Cross(upAtUt, normal); + Vector3d maneuverVel = fwdAtUt * (initialDeltaV + Math.Sqrt(referenceBody.gravParameter / relativePosition.magnitude)); + //Utility.LogMessage(this, "insertion v = {0:0.0}, v @ insertion = {1:0.0}", initialDeltaV, velAtUt.magnitude); + + Vector3d deltaV = maneuverVel - velAtUt.xzy; +#if DEBUG_CHANGE_ALTITUDE + Utility.LogMessage(this, "deltaV = {0} because {1} - {2}", deltaV, maneuverVel, velAtUt.xzy); +#endif + //Utility.LogMessage(this, "prograde (dot) fwd = {0:0.000}", Vector3d.Dot(prograde, fwdAtUt)); + + Vector3d maneuverdV = Utility.ManeuverNode(Vector3d.Dot(deltaV, prograde), Vector3d.Dot(deltaV, normal), Vector3d.Dot(deltaV, radial)); +#if DEBUG_CHANGE_ALTITUDE + Utility.LogMessage(this, "maneuverdV = {0}", maneuverdV); +#endif + + vessel.patchedConicSolver.maneuverNodes.Clear(); + ManeuverNode mn = vessel.patchedConicSolver.AddManeuverNode(maneuverUt); + mn.OnGizmoUpdated(maneuverdV, maneuverUt); + + return 1.0; + } + + return 0.0; + } + + /// + /// Match the plane of the target's orbit. If there is no target, or the target is in a different sphere + /// of influence, this operation has no effect. + /// + /// 1 if a maneuver was plotted, 0 otherwise. + public double MatchPlane() + { + Orbit current = vessel.orbit; + Orbit target = vc.targetOrbit; + + if (MASFlightComputerProxy.ConvertVesselSituation(vessel.situation) > 2 && vessel.patchedConicSolver != null && current.eccentricity < 1.0 && target.referenceBody == current.referenceBody) + { + // In the test case I ran, this seemed to be the opposite of the ChangeInclination call. + // I may need to tweak the sign of deltaI2 based on whether we're changing inclination + // at the AN or DN. + double deltaI = current.inclination - target.inclination; + double deltaI2 = Utility.NormalizeLongitude(deltaI); + + if (Math.Abs(deltaI2) > 0.0) + { + Vector3d vesselNormal = vc.orbit.GetOrbitNormal(); + Vector3d targetNormal = vc.activeTarget.GetOrbit().GetOrbitNormal(); + Vector3d cross = Vector3d.Cross(vesselNormal, targetNormal); + double taAN = vc.orbit.GetTrueAnomalyOfZupVector(-cross); + double timeAN = Utility.NormalizeOrbitTime(vc.orbit.GetUTforTrueAnomaly(taAN, vc.orbit.period) - vc.universalTime, current) + vc.universalTime; + double taDN = vc.orbit.GetTrueAnomalyOfZupVector(cross); + double timeDN = Utility.NormalizeOrbitTime(vc.orbit.GetUTforTrueAnomaly(taDN, vc.orbit.period) - vc.universalTime, current) + vc.universalTime; + + Vector3d vAN = current.getOrbitalVelocityAtUT(timeAN); + + Vector3d vDN = current.getOrbitalVelocityAtUT(timeDN); + + double deltaVScale = Math.Sin(0.5 * Utility.Deg2Rad * deltaI2); + + // Pick the slower of the two nodes + double mTime; + double speedAtUt; + if (vAN.sqrMagnitude < vDN.sqrMagnitude) + { + mTime = timeAN; + speedAtUt = vAN.magnitude; + } + else + { + mTime = timeDN; + speedAtUt = vDN.magnitude; + } + + double planeChangedV = 2.0 * speedAtUt * deltaVScale; + + // This is interesting ... Eq. 4.73 at http://www.braeunig.us/space/ describes the ΔV + // needed to change inclination. However, if I apply that strictly as a ΔV on the + // normal, the resulting orbit is not correct. Looking at the results MechJeb generates, + // I notice that there's a retrograde ΔV equal to sin(Θ/2) (which MJ doesn't directly compute - + // it's an artifact of its technique). So, first I determine the prograde component of + // the maneuver, and then I apply the balance of the ΔV to the normal. + double progradedV = -Math.Abs(deltaVScale * planeChangedV); + double normaldV = Math.Sqrt(planeChangedV * planeChangedV - progradedV * progradedV) * Math.Sign(deltaVScale); + Vector3d maneuver = Utility.ManeuverNode(progradedV, normaldV, 0.0); + + vessel.patchedConicSolver.maneuverNodes.Clear(); + ManeuverNode mn = vessel.patchedConicSolver.AddManeuverNode(mTime); + mn.OnGizmoUpdated(maneuver, mTime); + + // TODO: When I'm more awake, I should do this analytically by deciding whether the change + // in inclination is normal or anti-normal at the time of the maneuver. + Vector3d newVesselNormal = vessel.patchedConicSolver.maneuverNodes[0].nextPatch.GetOrbitNormal(); + if (Vector3.Angle(newVesselNormal, targetNormal) > Mathf.Abs((float)deltaI)) + { + maneuver.y = -maneuver.y; + vessel.patchedConicSolver.maneuverNodes.Clear(); + mn = vessel.patchedConicSolver.AddManeuverNode(mTime); + mn.OnGizmoUpdated(maneuver, mTime); + } + + return 1.0; + } + } + + return 0.0; + } + + /// + /// Match velocities with the target at the moment of closest approach. If there is no target, + /// or the target is in a different sphere of influence, the operation has no effect. + /// + /// 1 if a maneuver was plotted, 0 otherwise. + public double MatchVelocities() + { + if (vc.activeTarget != null && vc.targetOrbit.referenceBody == vessel.mainBody && vessel.patchedConicSolver != null && vc.targetClosestUT > Planetarium.GetUniversalTime()) + { + double maneuverUt = vc.targetClosestUT; + Orbit current = vessel.orbit; + Vector3d velAtUt = current.getOrbitalVelocityAtUT(maneuverUt); + Vector3d tgtAtUt = vc.targetOrbit.getOrbitalVelocityAtUT(maneuverUt); + + Vector3d relativePosition = current.getRelativePositionAtUT(maneuverUt); + + Vector3d prograde; + Vector3d normal; + Vector3d radial; + Utility.GetOrbitalBasisVectors(velAtUt, relativePosition, out prograde, out radial, out normal); + + Vector3d deltaV = tgtAtUt - velAtUt; + + Vector3d maneuverdV = Utility.ManeuverNode(Vector3d.Dot(deltaV, prograde), Vector3d.Dot(deltaV, normal), Vector3d.Dot(deltaV, radial)); +#if DEBUG_CHANGE_ALTITUDE + Utility.LogMessage(this, "maneuverdV = {0}", maneuverdV); +#endif + + vessel.patchedConicSolver.maneuverNodes.Clear(); + ManeuverNode mn = vessel.patchedConicSolver.AddManeuverNode(maneuverUt); + mn.OnGizmoUpdated(maneuverdV, maneuverUt); + + return 1.0; + } + + return 0.0; + } + + /// + /// Return from a moon with a resulting altitude over the planet of 'newAltitude', in meters. + /// If the vessel is orbiting the Sun, this command has no effect. + /// + /// This maneuver may be used to eject from Kerbin (or any other planet) to a solar orbit with + /// a specified altitude over the sun. + /// + /// Altitude of the desired orbit around the parent world, in meters. + /// 1 if the maneuver was plotted, 0 otherwise. + public double ReturnFromMoon(double newAltitude) + { + // The last condition listed here is because the sun's referenceBody is + // itself. + if (vessel.mainBody.referenceBody != null && vessel.patchedConicSolver != null && vessel.mainBody.referenceBody != vessel.mainBody) + { + CelestialBody moon = vessel.mainBody; + CelestialBody planet = moon.referenceBody; + + double newRadius = newAltitude + planet.Radius; + double returnDeltaV = DeltaVInitial(moon.orbit.semiMajorAxis, newRadius, planet.gravParameter); + double currentEjectionAngle = ComputeEjectionAngle(vessel.orbit); + + double ejectiondV, returnEjectionAngle, timeUntilReturn; + UpdateEjectionParameters(vessel.orbit, returnDeltaV, currentEjectionAngle, out ejectiondV, out returnEjectionAngle, out timeUntilReturn); + + double maneuverUt = timeUntilReturn + Planetarium.GetUniversalTime(); + + Vector3d maneuverdV = Utility.ManeuverNode(ejectiondV, 0.0, 0.0); + + vessel.patchedConicSolver.maneuverNodes.Clear(); + ManeuverNode mn = vessel.patchedConicSolver.AddManeuverNode(maneuverUt); + mn.OnGizmoUpdated(maneuverdV, maneuverUt); + } + + return 0.0; + } + + #endregion + + /// + /// The Oberth Effect region provides information specific to taking advantage + /// of the Oberth Effect when transferring from a moon to another planet. These + /// fields assume the vessel will eject from the moon to the `OberthAltitude` + /// over the planet, + /// from which it will fire the interplanetary ejection burn. + /// + /// If the vessel is not in a situtation where the Oberth Effect would be applicable, + /// these fields all return 0. + /// + #region Transfer Oberth Effect + + /// + /// The current ejection angle for the moon in degrees. When this value matches + /// `TransferOberthEjectionAngle()`, it is time to do the moon ejection burn. + /// + /// Current ejection angle over the moon in degrees, or 0. + public double CurrentOberthEjectionAngle() + { + if (vc.activeTarget != null) + { + if (invalid) + { + UpdateTransferParameters(); + } + + return oberthCurrentEjectionAngle; + } + else + { + return 0.0; + } + } + + /// + /// The preferred altitude over the parent planet for the + /// interplanetary ejection burn, in meters. + /// If the vessel's target + /// is not another world, or the vessel does not currently orbit a moon, returns 0. + /// + /// Altitude in meters, or 0. + public double OberthAltitude() + { + if (vc.activeTarget != null) + { + if (invalid) + { + UpdateTransferParameters(); + } + + return oberthAltitude; + } + else + { + return 0.0; + } + } + + /// + /// Returns the ΔV in m/s required for the Oberth effect transfer ejection burn. + /// The vessel's altitude orbiting the moon's parent should match `OberthAltitude()` + /// after this burn. + /// If the vessel's target + /// is not another world, or the vessel does not currently orbit a moon, returns 0. + /// + /// ΔV in m/s or 0. + public double OberthEjectionDeltaV() + { + if (vc.activeTarget != null) + { + if (invalid) + { + UpdateTransferParameters(); + } + + return oberthEjectionVelocity; + } + else + { + return 0.0; + } + } + + /// + /// Returns the relative ejection angle for an Oberth effect tranfers. When this + /// value reaches 0, it is time to burn. + /// If the vessel's target + /// is not another world, or the vessel does not currently orbit a moon, returns 0. + /// + /// Relative angle in degrees, or 0. + public double RelativeOberthEjectionAngle() + { + if (vc.activeTarget != null) + { + if (invalid) + { + UpdateTransferParameters(); + } + + return Utility.NormalizeAngle(oberthCurrentEjectionAngle - oberthTransferEjectionAngle); + } + else + { + return 0.0; + } + } + + /// + /// Returns the time until the ejection burn must begin for an Oberth effect tranfer, in seconds. + /// If the vessel's target + /// is not another world, or the vessel does not currently orbit a moon, returns 0. + /// + /// Time in seconds until the burn, or 0. + public double TimeUntilOberthEjectionAngle() + { + if (vc.activeTarget != null) + { + if (invalid) + { + UpdateTransferParameters(); + } + + return oberthTimeUntilEjection; + } + else + { + return 0.0; + } + } + + /// + /// Returns the ejection angle required to initiate an ejection from the moon's orbit + /// to the moon's parent world for an interplanetary transfer. If the vessel's target + /// is not another world, or the vessel does not currently orbit a moon, returns 0. + /// + /// Required ejection angle in degrees, or 0. + public double TransferOberthEjectionAngle() + { + if (vc.activeTarget != null) + { + if (invalid) + { + UpdateTransferParameters(); + } + + return oberthTransferEjectionAngle; + } + else + { + return 0.0; + } + } + #endregion + + /// + /// The Phase Angle section provides measurements of the phase angle, the + /// measure of the angle created by drawing lines from the body being + /// orbited to the vessel and to the target. This angle shows relative + /// position of the two objects, and it is continuously changing as long as + /// the craft are not in the same orbit. + /// + /// To do a Hohmann transfer between orbits, the vessel should initiate a + /// burn when its current phase angle reaches the transfer phase angle. + /// Alternatively, when the relative phase angle reaches 0, initiate a burn. + /// + #region Transfer Phase Angle + /// + /// Returns the current phase angle between the vessel and its target. + /// + /// Current phase angle in degrees, from 0 to 360. + public double CurrentPhaseAngle() + { + if (vc.activeTarget != null) + { + if (invalid) + { + UpdateTransferParameters(); + } + + return currentPhaseAngle; + } + else + { + return 0.0; + } + } + + /// + /// Returns the difference (in degrees) between the current phase angle + /// and the transfer phase angle. When this value reaches 0, it is time + /// to start the transfer burn. If there is no valid target, this value + /// is 0. + /// + /// The difference between the transfer phase angle and the current + /// phase angle in degrees, ranging from 0 to 360. + public double RelativePhaseAngle() + { + if (vc.activeTarget != null) + { + if (invalid) + { + UpdateTransferParameters(); + } + + return Utility.NormalizeAngle(currentPhaseAngle - transferPhaseAngle); + } + else + { + return 0.0; + } + } + + /// + /// Returns the time in seconds until the vessel reaches the correct + /// phase angle for initiating a burn to transfer to the target. + /// + /// Time until transfer, in seconds, or 0 if there is no solution. + public double TimeUntilPhaseAngle() + { + if (vc.activeTarget != null) + { + if (invalid) + { + UpdateTransferParameters(); + } + + return timeUntilTransfer; + } + else + { + return 0.0; + } + } + + /// + /// Returns the phase angle required to initiate a Hohmann + /// transfer orbit. This is the absolute phase angle, so it + /// does not vary over time when comparing two stable orbits. + /// Use `fc.RelativePhaseAngle()` to count down to a transfer. + /// + /// Returns 0 if there is no active target. + /// + /// Required phase angle in degrees (always betweeen 0 + /// and 180). + public double TransferPhaseAngle() + { + if (vc.activeTarget != null) + { + if (invalid) + { + UpdateTransferParameters(); + } + + return transferPhaseAngle; + } + else + { + return 0.0; + } + } + + #endregion + +#if COMPARE_PHASE_ANGLE_PROTRACTOR + private static double ProjectAngle2D(Vector3d a, Vector3d b) + { + Vector3d ray1 = Vector3d.Project(new Vector3d(a.x, 0.0, a.z), a); + Vector3d ray2 = Vector3d.Project(new Vector3d(b.x, 0.0, b.z), b); + + double phase = Vector3d.Angle(ray1, ray2); + + Vector3d ap = Quaternion.AngleAxis(90.0f, Vector3d.forward) * a; + Vector3d ray1p = Vector3d.Project(new Vector3d(ap.x, 0.0, ap.z), ap); + if (Vector3d.Angle(ray1p, ray2) > 90.0) + { + phase = 360.0 - phase; + } + + return Utility.NormalizeAngle(phase); + } + private static double Angle2d(Vector3d vector1, Vector3d vector2) + { + Vector3d v1 = Vector3d.Project(new Vector3d(vector1.x, 0, vector1.z), vector1); + Vector3d v2 = Vector3d.Project(new Vector3d(vector2.x, 0, vector2.z), vector2); + return Vector3d.Angle(v1, v2); + } +#endif + + // For a given orbit, find the orbit of the object that orbits the sun. + // + // If the orbit provided orbits the sun, this orbit is returned. If the + // orbit is around a body that orbits the sun, the body's orbit is returned. + // If the orbit is around a moon, return the orbit of the moon's parent. + static private Orbit GetSolarOrbit(Orbit orbit, out int numHops) + { + // Does this object orbit the sun? + if (orbit.referenceBody == Planetarium.fetch.Sun) + { + numHops = 0; + return orbit; + } + // Does this object orbit something that orbits the sun? + else if (orbit.referenceBody.GetOrbit().referenceBody == Planetarium.fetch.Sun) + { + numHops = 1; + return orbit.referenceBody.GetOrbit(); + } + // Does this object orbit the moon of something that orbits the sun? + else if (orbit.referenceBody.GetOrbit().referenceBody.GetOrbit().referenceBody == Planetarium.fetch.Sun) + { + numHops = 2; + return orbit.referenceBody.GetOrbit().referenceBody.GetOrbit(); + } + else + { + // Nothing in stock KSP orbits more than two levels deep. + throw new ArgumentException("GetSolarOrbit(): Unable to find a valid solar orbit."); + } + } + + // Find the orbits we can use to determine phase angle. These orbits + // need to share a common reference body. We also report how many parent + // bodies we had to look at to find the returned vesselOrbit, so we can + // compute the correct ejection angle (either ejection angle, or moon ejection + // angle for Oberth transfers). + static private void GetCommonOrbits(ref Orbit vesselOrbit, ref Orbit destinationOrbit, out int vesselOrbitSteps) + { + if (vesselOrbit.referenceBody == destinationOrbit.referenceBody) + { + // Orbiting the same body. Easy case. We're done. + vesselOrbitSteps = 0; + } + else if (vesselOrbit.referenceBody == Planetarium.fetch.Sun) + { + // We orbit the sun. Find the orbit of whichever parent + // of the target orbits the sun: + int dontCare; + destinationOrbit = GetSolarOrbit(destinationOrbit, out dontCare); + vesselOrbitSteps = 0; + } + else if (destinationOrbit.referenceBody == Planetarium.fetch.Sun) + { + // The target orbits the sun, but we don't. + vesselOrbit = GetSolarOrbit(vesselOrbit, out vesselOrbitSteps); + } + else + { + // Complex case... + int dontCare; + Orbit newVesselOrbit = GetSolarOrbit(vesselOrbit, out vesselOrbitSteps); + Orbit newDestinationOrbit = GetSolarOrbit(destinationOrbit, out dontCare); + + if (newVesselOrbit == newDestinationOrbit) + { + // Even more complex case. Source and destination orbit are in the + // same planetary system, but one or both orbit moons. + if (vesselOrbitSteps == 2) + { + // Vessel orbits a moon. + vesselOrbit = vesselOrbit.referenceBody.GetOrbit(); + vesselOrbitSteps = 1; + } + if (dontCare == 2) + { + destinationOrbit = destinationOrbit.referenceBody.GetOrbit(); + } + } + else + { + vesselOrbit = newVesselOrbit; + destinationOrbit = newDestinationOrbit; + } + } + } + + // Compute the delta-V required for the injection and circularization burns for the + // given orbits. + // + // Equation from http://www.braeunig.us/space/ + private void UpdateTransferDeltaV(Orbit startOrbit, Orbit destinationOrbit) + { + double GM = startOrbit.referenceBody.gravParameter; + double rA = startOrbit.semiMajorAxis; + double rB = destinationOrbit.semiMajorAxis; + + double atx = 0.5 * (rA + rB); + double Vi = Math.Sqrt(GM / rA); // Velocity of a circular orbit at radius A + double Vf = Math.Sqrt(GM / rB); // Velocity of a circular orbit at radius B + + double Vtxi = Math.Sqrt(GM * (2.0 / rA - 1.0 / atx)); + double Vtxf = Math.Sqrt(GM * (2.0 / rB - 1.0 / atx)); + + initialDeltaV = Vtxi - Vi; + finalDeltaV = Vf - Vtxf; + } + + // Computes the delta-V for the initial burn of a Hohmann transfer. Assumes circular + // initial orbit. + private static double DeltaVInitial(double startRadius, double endRadius, double GM) + { + double atx = 0.5 * (startRadius + endRadius); + double Vi = Math.Sqrt(GM / startRadius); + + double Vtxi = Math.Sqrt(GM * (2.0 / startRadius - 1.0 / atx)); + + return Vtxi - Vi; + } + + // Allows actual velocity at startRadius to be applied, which may lead to quicker convergence + // on the desired dV. + private static double DeltaVInitial(double startRadius, double endRadius, double GM, double velocityAtStartRadius) + { + double atx = 0.5 * (startRadius + endRadius); + //double Vi = Math.Sqrt(GM / startRadius); + + double Vtxi = Math.Sqrt(GM * (2.0 / startRadius - 1.0 / atx)); + + return Vtxi - velocityAtStartRadius; + } + + // Determine the current ejection angle (angle from parent body's prograde) + private static double ComputeEjectionAngle(Orbit o) + { + Vector3d vesselPos = o.pos.xzy; + vesselPos.Normalize(); + Vector3d bodyProgradeVec = o.referenceBody.orbit.vel.xzy; + bodyProgradeVec.Normalize(); + Vector3d bodyPosVec = o.referenceBody.orbit.pos; + double currentEjectionAngle = Vector3d.Angle(vesselPos, bodyProgradeVec); + if (Vector3d.Dot(vesselPos, bodyPosVec) > 0.0) + { + currentEjectionAngle = Utility.NormalizeAngle(360.0 - currentEjectionAngle); + } + return currentEjectionAngle; + } + + // Update ejection parameters + private static void UpdateEjectionParameters(Orbit o, double departureDeltaV, double currentEjectionAngle, out double ejectionDeltaV, out double transferEjectionAngle, out double timeUntilEjection) + { + double r1 = o.semiMajorAxis; + double r2 = o.referenceBody.sphereOfInfluence; + double GM = o.referenceBody.gravParameter; + double v2 = departureDeltaV; + + bool raiseAltitude = (departureDeltaV > 0.0); + + // Absolute velocity required, not delta-V. + double ejectionVelocity = Math.Sqrt((r1 * (r2 * v2 * v2 - 2.0 * GM) + 2.0 * r2 * GM) / (r1 * r2)); + + double eps = ejectionVelocity * ejectionVelocity * 0.5 - GM / r1; + double h = r1 * ejectionVelocity; + double e = Math.Sqrt(1.0 + 2.0 * eps * h * h / (GM * GM)); + double theta = Math.Acos(1.0 / e) * Utility.Rad2Deg; + transferEjectionAngle = Utility.NormalizeAngle(((raiseAltitude) ? 180.0 : 360.0) - theta); + + // Figure out how long until we cross that angle + double orbitFraction = Utility.NormalizeAngle(currentEjectionAngle - transferEjectionAngle) / 360.0; + timeUntilEjection = orbitFraction * o.period; + + try + { + double oVel = o.getOrbitalSpeedAt(Planetarium.GetUniversalTime() + timeUntilEjection); + + // Convert ejectionVelocity into ejection delta-V. + ejectionDeltaV = ejectionVelocity - oVel; + } + catch + { + // Orbit.getOrbitalSpeedAt() can fail for high eccentricity. Trap those and + // jam ejectionDeltaV to 0 (we must already be ejecting?). + ejectionDeltaV = 0.0; + } + } + + // Updater method - called at most once per FixedUpdate when the + // transfer parameters are being queried. + private void UpdateTransferParameters() + { + // Initialize values + hohmannTransfer = false; + currentPhaseAngle = 0.0; + transferPhaseAngle = 0.0; + timeUntilTransfer = 0.0; + + ejectionDeltaV = 0.0; + currentEjectionAngle = 0.0; + transferEjectionAngle = 0.0; + timeUntilEjection = 0.0; + + oberthAltitude = 0.0; + oberthEjectionVelocity = 0.0; + oberthCurrentEjectionAngle = 0.0; + oberthTransferEjectionAngle = 0.0; + oberthTimeUntilEjection = 0.0; + + initialDeltaV = 0.0; + finalDeltaV = 0.0; + + if (vc.activeTarget != null) + { + Orbit vesselOrbit = vessel.orbit; + Orbit destinationOrbit = vc.activeTarget.GetOrbit(); + + if (vesselOrbit.eccentricity >= 1.0 || destinationOrbit.eccentricity >= 1.0) + { + // One or both orbits are escape orbits. We can't work with them. + return; + } + + // Orbit steps counts how many levels of orbital parents we have to step across + // to find a common orbit with our target. + // 0 means both orbit the same body (simple Hohmann transfer case). + // 1 means the vessel orbits a planet, and it must transfer to another planet, + // OR a vessel orbits a moon, and it must transfer to another moon. + // 2 means the vessel orbits a moon, and it must transfer to another planet. + int vesselOrbitSteps; + GetCommonOrbits(ref vesselOrbit, ref destinationOrbit, out vesselOrbitSteps); + + // Figure out what sort of transfer we're doing. + if (vesselOrbit.referenceBody != destinationOrbit.referenceBody) + { + // We can't find a common orbit? + Utility.LogError(this, "Bailing out compute transfer parameters: unable to reconcile orbits"); + return; + } + + // TODO: At what relative inclination should it bail out? + if (Vector3.Angle(vesselOrbit.GetOrbitNormal(), destinationOrbit.GetOrbitNormal()) > 30.0) + { + // Relative inclination is very out-of-spec. Bail out. + return; + } + + hohmannTransfer = (vessel.orbit.referenceBody == vc.activeTarget.GetOrbit().referenceBody); + UpdateTransferDeltaV(vesselOrbit, destinationOrbit); + + // transfer phase angle from https://en.wikipedia.org/wiki/Hohmann_transfer_orbit + // This does not do anything special for Oberth effect transfers + transferPhaseAngle = 180.0 * (1.0 - 0.35355339 * Math.Pow(vesselOrbit.semiMajorAxis / destinationOrbit.semiMajorAxis + 1.0, 1.5)); + +#if COMPARE_PHASE_ANGLE_PROTRACTOR + // current phase angle: the angle between the two positions as projected onto a 2D plane. + Vector3d pos1 = vesselOrbit.getRelativePositionAtUT(vc.universalTime); + Vector3d pos2 = destinationOrbit.getRelativePositionAtUT(vc.universalTime); + + double protractorPhaseAngle = ProjectAngle2D(pos1, pos2); +#endif + // Use orbital parameters. Note that the argumentOfPeriapsis and LAN + // are both in degrees, while true anomaly is in radians. + double tA1 = (vesselOrbit.trueAnomaly * Orbit.Rad2Deg + vesselOrbit.argumentOfPeriapsis + vesselOrbit.LAN); + double tA2 = (destinationOrbit.trueAnomaly * Orbit.Rad2Deg + destinationOrbit.argumentOfPeriapsis + destinationOrbit.LAN); + currentPhaseAngle = Utility.NormalizeAngle(tA2 - tA1); + +#if COMPARE_PHASE_ANGLE_PROTRACTOR + if (Math.Abs(currentPhaseAngle - protractorPhaseAngle) > 0.5) + { + Utility.LogMessage(this, "Protractor phase angle = {0,7:0.00}; trueAnomaly pa = {1,7:0.00}, diff = {2,7:0.00}", protractorPhaseAngle, currentPhaseAngle, currentPhaseAngle - protractorPhaseAngle); + } +#endif + + // The difference in mean motion tells us how quickly the phase angle is changing. + // Since Orbit.meanMotion is in rad/sec, we need to convert the difference to deg/sec. + double deltaRelativePhaseAngle = (vesselOrbit.meanMotion - destinationOrbit.meanMotion) * Orbit.Rad2Deg; + + if (deltaRelativePhaseAngle > 0.0) + { + timeUntilTransfer = Utility.NormalizeAngle(currentPhaseAngle - transferPhaseAngle) / deltaRelativePhaseAngle; + } + else if (deltaRelativePhaseAngle < 0.0) + { + timeUntilTransfer = Utility.NormalizeAngle(transferPhaseAngle - currentPhaseAngle) / deltaRelativePhaseAngle; + } + // else can't compute it - the orbits have the exact same period. Time already zero'd. + + // Compute current ejection angle +#if COMPARE_PHASE_ANGLE_PROTRACTOR + //--- PROTRACTOR + Vector3d vesselvec = vessel.orbit.getRelativePositionAtUT(Planetarium.GetUniversalTime()); + + // get planet's position relative to universe + Vector3d bodyvec = vessel.mainBody.orbit.getRelativePositionAtUT(Planetarium.GetUniversalTime()); + + Vector3d forwardVec = Quaternion.AngleAxis(90.0f, Vector3d.forward) * bodyvec; + forwardVec.Normalize(); + double protractorEject = Angle2d(vesselvec, Quaternion.AngleAxis(90.0f, Vector3d.forward) * bodyvec); + + if (Angle2d(vesselvec, Quaternion.AngleAxis(180.0f, Vector3d.forward) * bodyvec) > Angle2d(vesselvec, bodyvec)) + { + protractorEject = 360.0 - protractorEject;//use cross vector to determine up or down + } + //--- PROTRACTOR +#endif + + if (vesselOrbitSteps == 1) + { + //Vector3d vesselPos = vessel.orbit.pos; + //vesselPos.Normalize(); + //Vector3d bodyProgradeVec = vessel.mainBody.orbit.vel; + //bodyProgradeVec.Normalize(); + //Vector3d bodyPosVec = vessel.mainBody.orbit.pos; + //currentEjectionAngle = Vector3d.Angle(vesselPos, bodyProgradeVec); + //if (Vector3d.Dot(vesselPos, bodyPosVec) > 0.0) + //{ + // currentEjectionAngle = Utility.NormalizeAngle(360.0 - currentEjectionAngle); + //} + + currentEjectionAngle = ComputeEjectionAngle(vessel.orbit); +#if COMPARE_PHASE_ANGLE_PROTRACTOR + Utility.LogMessage(this, "Protractor ejection angle = {0,5:0.0} , computed = {1,5:0.0}", protractorEject, currentEjectionAngle); +#endif + + UpdateEjectionParameters(vessel.orbit, initialDeltaV, currentEjectionAngle, out ejectionDeltaV, out transferEjectionAngle, out timeUntilEjection); + } + else if (vesselOrbitSteps == 2) + { + CelestialBody moon = vessel.orbit.referenceBody; + CelestialBody planet = moon.referenceBody; + + currentEjectionAngle = ComputeEjectionAngle(moon.orbit); + + // Compute ejection parameters based on the orbital parameters of the moon we are orbiting. + UpdateEjectionParameters(moon.orbit, initialDeltaV, currentEjectionAngle, out ejectionDeltaV, out transferEjectionAngle, out timeUntilEjection); + + // TODO: Compute moon ejection angle to take advantage of the Oberth effect. + oberthAltitude = 0.05 * (planet.Radius + planet.atmosphereDepth); + + double oberthAltitudeDeltaV = DeltaVInitial(moon.orbit.semiMajorAxis, oberthAltitude + planet.Radius, planet.gravParameter); + + // Compute the moon-specific parameters + oberthCurrentEjectionAngle = ComputeEjectionAngle(vessel.orbit); + UpdateEjectionParameters(vessel.orbit, oberthAltitudeDeltaV, oberthCurrentEjectionAngle, out oberthEjectionVelocity, out oberthTransferEjectionAngle, out oberthTimeUntilEjection); + + // Delta-V required to target the ejection altitude. + //oberthEjectionVelocity = 0.0; + } + } + + invalid = false; + } + + /// + /// Called per-FixedUpdate to invalidate computations. + /// + [MoonSharpHidden] + internal void Update() + { + invalid = true; + } + } +} diff --git a/ModifiedCode/MASVesselComputer.cs b/ModifiedCode/MASVesselComputer.cs new file mode 100644 index 00000000..3fd63cef --- /dev/null +++ b/ModifiedCode/MASVesselComputer.cs @@ -0,0 +1,1499 @@ +/***************************************************************************** + * The MIT License (MIT) + * + * Copyright (c) 2016-2020 MOARdV + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + ****************************************************************************/ +using System; +using System.Collections.Generic; +using System.Text; +using UnityEngine; + +namespace AvionicsSystems +{ + /// + /// The MASVesselComputer encompasses all of the per-vessel data tracking + /// used in Avionics Systems. As such, it's entirely concerned with keeping + /// tabs on data, but not much else. + /// + internal partial class MASVesselComputer : MonoBehaviour + { + internal enum ReferenceType + { + Unknown, + Self, + RemoteCommand, + DockingPort, + Claw + }; + + /// + /// Our current reference transform. + /// + private Transform _referenceTransform; + internal Transform referenceTransform + { + get + { + if (_referenceTransform == null) + { + UpdateReferenceTransform(vessel.GetReferenceTransformPart(), true); + } + return _referenceTransform; + } + } + + /// + /// Type of object that the reference transform is attached to. + /// + internal ReferenceType referenceTransformType; + + /// + /// Local copy of the current orbit. This is updated per fixed-update + /// so we're not querying an indeterminate-cost property of Vessel. + /// + internal Orbit orbit; + + /// + /// The vessel this module represents. + /// + private Vessel vessel; + + /// + /// A copy of the module's vessel ID, in case vessel is null'd before OnDestroy fires. + /// + private Guid vesselId; + + /// + /// Whether the vessel needs MASVC support (has at least one crew). + /// + internal bool vesselCrewed; + + /// + /// Whether the vessel is actually loaded and active. + /// + internal bool vesselActive; + + /// + /// Boolean used to detect double-clicks during IVA, which would cause + /// the vessel to lose target track. + /// + private bool doubleClickDetected = false; + + /// + /// What world / star are we orbiting? + /// + internal CelestialBody mainBody; + + /// + /// Current UT. + /// + internal double universalTime; + + /// + /// Debug 'registers'. Used so I can draw values out of code for viewing in real- + /// time without using message logging. + /// + internal object[] debugValue = new object[4]; + + /// + /// Wrapper method for all of the subcategories of data that are + /// refreshed per-FixedUpdate. + /// + private void RefreshData() + { + // First step: + PrepareResourceData(); + + UpdateModuleData(); + try + { + UpdateAttitude(); + } + catch(Exception e) + { + throw new ArgumentException("Error in UpdateAttitude:" + e.Source + e.TargetSite + e.Data + e.StackTrace, e); + } + UpdateAltitudes(); + UpdateManeuverNode(); + UpdateOwnDockingPorts(); + UpdateTarget(); + UpdateMisc(); + // Last step: + ProcessResourceData(); + } + + private void UpdateThermals() + { + double difference = float.MaxValue; + + double currentHottest = 0.0; + for (int partIdx = vessel.parts.Count - 1; partIdx >= 0; --partIdx) + { + Part part = vessel.parts[partIdx]; + double maxTemp = part.maxTemp; + double currentTemp = part.temperature; + if (maxTemp - currentTemp < difference) + { + _hottestPartMax = maxTemp; + currentHottest = currentTemp; + difference = maxTemp - currentTemp; + } + maxTemp = part.skinMaxTemp; + currentTemp = part.skinTemperature; + if (maxTemp - currentTemp < difference) + { + _hottestPartMax = maxTemp; + currentHottest = currentTemp; + difference = maxTemp - currentTemp; + } + } + _hottestPartSign = Math.Sign(_hottestPart - currentHottest); + _hottestPart = currentHottest; + } + private double _hottestPart; + private double _hottestPartMax; + private float _hottestPartSign; + internal double hottestPart + { + get + { + if (_hottestPartMax == -1.0) + { + UpdateThermals(); + } + return _hottestPart; + } + } + internal double hottestPartSign + { + get + { + if (_hottestPartMax == -1.0) + { + UpdateThermals(); + } + return _hottestPartSign; + } + } + internal double hottestPartMax + { + get + { + if (_hottestPartMax == -1.0) + { + UpdateThermals(); + } + return _hottestPartMax; + } + } + + #region Monobehaviour + /// + /// Update per-Vessel fields. + /// + private void FixedUpdate() + { + // Compute delta-t from Planetarium time so if the vessel goes + // inactive for a while, it won't resume with an inaccurate + // time ... or is that even worth the extra effort? + if (vesselCrewed && vesselActive) + { + // TODO: Can I make these two update by callback? + mainBody = vessel.mainBody; + orbit = vessel.orbit; + _hottestPartMax = -1.0; + + universalTime = Planetarium.GetUniversalTime(); + + // GetReferenceTransformPart() seems to be pointing at the + // previous part when the callback fires, so I use this hack + // to manually recompute it here. + UpdateReferenceTransform(vessel.GetReferenceTransformPart(), false); + + // If there was a mouse double-click event, and we think there's + // a target, and KSP says there isn't a target, the user likely + // double-clicked in the IVA and accidentally cleared the active + // target. Let's fix that for them. + // + // However, there seems to be a one-update delay in the change + // registering: + // 1) LateUpdate sees a double-click. + // 2) FixedUpdate shows a VesselTarget in FlightGlobals. + // 3) LateUpdate does not see a double-click. + // 4) FixedUpdate shows the target is cleared. + // + // I could do a countdown timer instead of a boolean, I suppose. + if (doubleClickDetected) + { + if (activeTarget == null) + { + //Utility.LogMessage(this, "doubleClick corrector: no target expected (active = {0}, FG = {1})", + // activeTarget != null, FlightGlobals.fetch.VesselTarget != null); + doubleClickDetected = false; + } + else if (activeTarget != null && FlightGlobals.fetch.VesselTarget == null) + { + FlightGlobals.fetch.SetVesselTarget(activeTarget); + //Utility.LogMessage(this, "doubleClick corrector: resetting"); + doubleClickDetected = false; + } + //else + //{ + // Utility.LogMessage(this, "doubleClick corrector: no-op (active = {0}, FG = {1})", + // activeTarget != null, FlightGlobals.fetch.VesselTarget != null); + //} + } + + RefreshData(); + + //Utility.LogMessage(this, "FixedUpdate for {0}", vessel.id); + } + } + + /// + /// We use the LateUpdate() (why? - RPM did it here, but I don't know if + /// it *needs* to be here) to look for double-click events, which happen + /// too easily when playing in IVA. The double-click clears targets, which + /// can be a problem. + /// + public void LateUpdate() + { + if (vesselActive) + { + doubleClickDetected |= Mouse.Left.GetDoubleClick(); + //Utility.LogMessage(this, "LateUpdate: doubleClick = {0} ({1})", doubleClickDetected, Mouse.Left.GetDoubleClick()); + } + else + { + doubleClickDetected = false; + } + } + + /// + /// Initialize some fields to safe values (or expected unchanging values). + /// The vessel fields of VesselComputer doesn't have good values yet, so this + /// step is only good for non-specific initial values. + /// + public void Awake() + { + if (HighLogic.LoadedSceneIsFlight == false) + { + Utility.LogWarning(this, "Someone is creating a vessel computer outside of flight!"); + } + + vessel = gameObject.GetComponent(); + if (vessel == null) + { + throw new ArgumentNullException("[MASVesselComputer] Awake(): Could not find the vessel!"); + } + //Utility.LogMessage(this, "Awake() for {0}", vessel.id); + + mainBody = vessel.mainBody; + vesselId = vessel.id; + orbit = vessel.orbit; + + universalTime = Planetarium.GetUniversalTime(); + + InitResourceData(); + + UpdateReferenceTransform(vessel.GetReferenceTransformPart(), true); + //vesselCrewed = (vessel.GetCrewCount() > 0); + vesselCrewed = true; + vesselActive = ActiveVessel(vessel); + if (vesselCrewed) + { + RefreshData(); + } + + GameEvents.OnCameraChange.Add(onCameraChange); + GameEvents.onStageActivate.Add(onStageActivate); + GameEvents.onVesselChange.Add(onVesselChange); + GameEvents.onVesselSOIChanged.Add(onVesselSOIChanged); + GameEvents.onVesselWasModified.Add(onVesselWasModified); + + // onDominantBodyChange + } + + /// + /// This vessel is being scrapped. Release modules. + /// + private void OnDestroy() + { + //Utility.LogMessage(this, "OnDestroy for {0}", vesselId); + + GameEvents.OnCameraChange.Remove(onCameraChange); + GameEvents.onStageActivate.Remove(onStageActivate); + GameEvents.onVesselChange.Remove(onVesselChange); + GameEvents.onVesselSOIChanged.Remove(onVesselSOIChanged); + GameEvents.onVesselWasModified.Remove(onVesselWasModified); + + TeardownResourceData(); + + vesselId = Guid.Empty; + orbit = null; + mainBody = null; + activeTarget = null; + } + #endregion + + #region Vessel Data + + /// + /// Helper method to determine if the vessel is the active IVA vessel, + /// since we don't want to burn cycles on vessels whose IVA doesn't exist. + /// + /// + /// + private static bool ActiveVessel(Vessel vessel) + { + // This does not account for the stock overlays. However, that + // required iterating over the cameras list to find + // "InternalSpaceOverlay Host". At least, in 1.1.3. + return vessel.isActiveVessel && (CameraManager.Instance.currentCameraMode == CameraManager.CameraMode.IVA || CameraManager.Instance.currentCameraMode == CameraManager.CameraMode.Internal); + } + + internal ModuleDockingNode[] ownDockingPorts = new ModuleDockingNode[0]; + + private void UpdateOwnDockingPorts() + { + //Vessel targetVessel = (targetType == TargetType.Vessel) ? (activeTarget as Vessel) : (activeTarget as ModuleDockingNode).vessel; + //if (!targetVessel.packed && targetVessel.loaded) + //{ + List potentialOwnDocks = vessel.FindPartModulesImplementing(); + List validOwnDocks = new List(); + + if (dockingNode != null) + { + for (int i = potentialOwnDocks.Count - 1; i >= 0; --i) + { + ModuleDockingNode checkDock = potentialOwnDocks[i]; + // Only lock on to an available dock of the same type that is either ungendered or the opposite gender. + //if (otherDock.state == "Ready" && (string.IsNullOrEmpty(dockingNode.nodeType) || dockingNode.nodeType == otherDock.nodeType) && (dockingNode.gendered == false || dockingNode.genderFemale != otherDock.genderFemale)) + if (checkDock.state == "Ready") + { + validOwnDocks.Add(checkDock); + } + } + } +/* else + { + for (int i = potentialOwnDocks.Count - 1; i >= 0; --i) + { + ModuleDockingNode otherDock = potentialOwnDocks[i]; + // Only lock on to an available dock of the same type that is either ungendered or the opposite gender. + if (otherDock.state == "Ready") + { + validOwnDocks.Add(otherDock); + } + } + }*/ + if (ownDockingPorts.Length != validOwnDocks.Count) + { + ownDockingPorts = validOwnDocks.ToArray(); + } + else + { + for (int i = ownDockingPorts.Length - 1; i >= 0; --i) + { + ownDockingPorts[i] = validOwnDocks[i]; + } + } + //} + + //else if ((targetVessel.packed || !targetVessel.loaded) && targetDockingPorts.Length > 0) + //{ + // targetDockingPorts = new ModuleDockingNode[0]; + //} + } + + // Time in seconds until impact. 0 if there is no impact. + private double timeToImpact_; + internal double timeToImpact + { + get + { + if (timeToImpact_ < 0.0) + { + RefreshLandingEstimate(); + } + return timeToImpact_; + } + } + private double landingAltitude_; + internal double landingAltitude + { + get + { + if (timeToImpact_ < 0.0) + { + RefreshLandingEstimate(); + } + return landingAltitude_; + } + } + private double landingLongitude_; + internal double landingLongitude + { + get + { + if (timeToImpact_ < 0.0) + { + RefreshLandingEstimate(); + } + return landingLongitude_; + } + } + private double landingLatitude_; + internal double landingLatitude + { + get + { + if (timeToImpact_ < 0.0) + { + RefreshLandingEstimate(); + } + return landingLatitude_; + } + } + + private void RefreshLandingEstimate() + { + if (orbit.PeA < 0.0 && orbit.eccentricity < 1.0 && !(vessel.Landed || vessel.Splashed)) + { + // Initial estimate: + landingAltitude_ = 0.0; + + timeToImpact_ = Utility.NextTimeToRadius(orbit, landingAltitude_ + orbit.referenceBody.Radius); + Vector3d pos = orbit.getPositionAtUT(timeToImpact_ + Planetarium.GetUniversalTime()); + + Vector2d latlon = orbit.referenceBody.GetLatitudeAndLongitude(pos); + landingAltitude_ = orbit.referenceBody.TerrainAltitude(latlon.x, latlon.y); + landingLatitude_ = latlon.x; + landingLongitude_ = latlon.y; + + landingAltitude_ = Math.Min(orbit.ApA, Math.Max(orbit.PeA, Math.Max(landingAltitude_, 0.0))); + + double lastImpact = timeToImpact_; + + //Utility.LogMessage(this, "RefreshLandingEstimate():"); + for (int i = 0; i < 6; ++i) + { + timeToImpact_ = Utility.NextTimeToRadius(orbit, landingAltitude_ + orbit.referenceBody.Radius); + + pos = orbit.getPositionAtUT(timeToImpact_ + Planetarium.GetUniversalTime()); + latlon = orbit.referenceBody.GetLatitudeAndLongitude(pos); + landingAltitude_ = orbit.referenceBody.TerrainAltitude(latlon.x, latlon.y); + landingLatitude_ = latlon.x; + landingLongitude_ = latlon.y; + + landingAltitude_ = Math.Min(orbit.ApA, Math.Max(orbit.PeA, Math.Max(landingAltitude_, 0.0))); + + //Utility.LogMessage(this, "[{2}]: {0:0}m in {1:0}s", landingAltitude_, timeToImpact_, i); + + if (Math.Abs(timeToImpact_ - lastImpact) < 2.0) + { + break; + } + else + { + lastImpact = timeToImpact_; + } + } + } + else + { + timeToImpact_ = 0.0; + landingAltitude_ = 0.0; + landingLongitude_ = 0.0; + landingLatitude_ = 0.0; + } + } + + internal double altitudeASL; + internal double altitudeTerrain; + internal double altitudeTerrainRate; + private double altitudeBottom_; + internal double altitudeBottom + { + get + { + // This is expensive to compute, so we + // don't until we're close to the ground, + // and never until it's requested. + if (altitudeBottom_ < 0.0) + { + altitudeBottom_ = Math.Min(altitudeASL, altitudeTerrain); + + // Precision isn't *that* important ... until we get close. + if (altitudeBottom_ < 500.0) + { + double lowestPoint = altitudeASL; + + for (int i = vessel.parts.Count - 1; i >= 0; --i) + { + if (vessel.parts[i].collider != null) + { + Vector3d bottomPoint = vessel.parts[i].collider.ClosestPointOnBounds(mainBody.position); + double partBottomAlt = mainBody.GetAltitude(bottomPoint); + lowestPoint = Math.Min(lowestPoint, partBottomAlt); + } + } + lowestPoint -= altitudeASL; + altitudeBottom_ += lowestPoint; + + altitudeBottom_ = Math.Max(0.0, altitudeBottom_); + } + } + + return altitudeBottom_; + } + } + internal double atmosphereDepth + { + get + { + return (mainBody.atmosphere) ? Mathf.Clamp01((float)(vessel.atmDensity / mainBody.atmDensityASL)) : 0.0; + } + } + internal double apoapsis; + internal double periapsis; + void UpdateAltitudes() + { + double previousAltitudeTerrain = Math.Min(altitudeTerrain, altitudeASL); + altitudeASL = vessel.altitude; + altitudeTerrain = vessel.altitude - vessel.terrainAltitude; + + // Apply exponential smoothing - terrain rate is very noisy. + const float alpha = 0.0625f; + altitudeTerrainRate = altitudeTerrainRate * (1.0 - alpha) + ((Math.Min(altitudeTerrain, altitudeASL) - previousAltitudeTerrain) / TimeWarp.fixedDeltaTime) * alpha; + + altitudeBottom_ = -1.0; + timeToImpact_ = -1.0; + apoapsis = orbit.ApA; + periapsis = orbit.PeA; + } + + #region Attitudes + /// + /// Surface Attitude is pitch, heading, roll + /// + private Vector3 surfaceAttitude; + internal float heading + { + get + { + return surfaceAttitude.y; + } + } + internal float pitch + { + get + { + return surfaceAttitude.x; + } + } + internal float roll + { + get + { + return surfaceAttitude.z; + } + } + private readonly Quaternion navballYRotate = Quaternion.Euler(0.0f, 180.0f, 0.0f); + private Quaternion navballRelativeGimbal; + internal Quaternion navBallRelativeGimbal + { + get + { + return navballRelativeGimbal; + } + } + private Quaternion navballAttitudeGimbal; + internal Quaternion navBallAttitudeGimbal + { + get + { + return navballAttitudeGimbal; + } + } + // Surface-relative vectors. + internal Vector3 up; // local world "up" + internal Vector3 surfaceRight; // vessel right projected onto the plane described by "up" + internal Vector3 surfaceForward; // vessel forward projected onto the plane described by "up"; special handling when 'forward' is near 'up'. + + internal Vector3 prograde; + internal Vector3 surfacePrograde; + internal Vector3 radialOut; + internal Vector3 normal; + + // Vessel-relative right, forward, and "top" unit vectors. + internal Vector3 right; + internal Vector3 forward; + internal Vector3 top; + + internal float progradeHeading; + + private float lastHeading; + internal double headingRate = 0.0; + + /// + /// Because the gimbal is reflected for presentation, we need to + /// mirror the value here so the gimbal is correct. + /// + /// + /// + static internal Quaternion MirrorXAxis(Quaternion input) + { + return new Quaternion(input.x, -input.y, -input.z, input.w); + } + + /// + /// Do a ray-cast to determine slope beneath the vessel. + /// + /// Slope, or 0 if it can not be computed. + internal double GetSlopeAngle() + { + RaycastHit sfc; + if (Physics.Raycast(vessel.CoM, -up, out sfc, (float)altitudeASL + 1000.0f, 1 << 15)) + { + return Vector3.Angle(up, sfc.normal); + } + else + { + return 0.0; + } + } + + // The vessel's transform is rotated about the x axis 90 degrees, which is why "forward" uses the "up" transform, for instance; + // we need to correct the reference transform's orientation using this transform. + private readonly Quaternion vesselOrientationCorrection = Quaternion.Euler(90.0f, 0.0f, 0.0f); + + void UpdateAttitude() + { + if (vessel.GetReferenceTransformPart() == null) + { + /*try + { + UpdateReferenceTransform(vessel.GetReferenceTransformPart(), true); + } + catch (Exception e) + { + throw new ArgumentException("Error in UpdateReferenceTransform in UpdateAttitude: \"" + e.Source + " " + e.TargetSite + " " + e.Data + " " + e.StackTrace + " Transform: " + vessel.GetReferenceTransformPart() + "\"", e); + }*/ + + UpdateReferenceTransform(vessel.GetReferenceTransformPart(), true); + } + /* try + { + navballAttitudeGimbal = vesselOrientationCorrection * Quaternion.Inverse(referenceTransform.rotation); + } + catch (Exception e) + { + throw new ArgumentException("Error in navballAttitudeGimbal: \"" + e.Source + " " + e.TargetSite + " " + e.Data + " " + e.StackTrace + " Transform: " + referenceTransform + "\"", e); + }*/ + + navballAttitudeGimbal = vesselOrientationCorrection * Quaternion.Inverse(referenceTransform.rotation); + + Vector3 relativePositionVector = (referenceTransform.position - mainBody.position).normalized; + //Utility.LogMessage(this, "Past gate 1"); + + Quaternion relativeGimbal = navballAttitudeGimbal * Quaternion.LookRotation( + Vector3.ProjectOnPlane(relativePositionVector + mainBody.transform.up, (relativePositionVector)), + relativePositionVector); + + //Utility.LogMessage(this, "Past gate 2"); + + // We have to do all sorts of voodoo to get the navball + // gimbal rotated so the rendered navball behaves the same + // as navballs. + navballRelativeGimbal = navballYRotate * MirrorXAxis(relativeGimbal); + surfaceAttitude = Quaternion.Inverse(relativeGimbal).eulerAngles; + if (surfaceAttitude.x > 180.0f) + { + surfaceAttitude.x = 360.0f - surfaceAttitude.x; + } + else + { + surfaceAttitude.x = -surfaceAttitude.x; + } + + if (surfaceAttitude.z > 180.0f) + { + surfaceAttitude.z = 360.0f - surfaceAttitude.z; + } + else + { + surfaceAttitude.z = -surfaceAttitude.z; + } + + //Utility.LogMessage(this, "Past gate 3"); + + double headingChange = Utility.NormalizeLongitude(surfaceAttitude.y - lastHeading); + lastHeading = surfaceAttitude.y; + headingRate = 0.875 * headingRate + 0.125 * headingChange / TimeWarp.fixedDeltaTime; + + //Utility.LogMessage(this, "Past gate 4"); + + up = vessel.upAxis; + prograde = vessel.obt_velocity.normalized; + surfacePrograde = vessel.srf_vel_direction; + radialOut = Vector3.ProjectOnPlane(up, prograde).normalized; + normal = -Vector3.Cross(radialOut, prograde).normalized; + // TODO: does Vector3.OrthoNormalize do anything for me here? + + //Utility.LogMessage(this, "Past gate 5"); + + right = vessel.GetTransform().right; + forward = vessel.GetTransform().up; + top = vessel.GetTransform().forward; + + //Utility.LogMessage(this, "Past gate 6"); + + // We base our surface vector off of UP and RIGHT, unless roll is extreme. + if (Mathf.Abs(Vector3.Dot(right, up)) > 0.995f) + { + surfaceRight = Vector3.Cross(forward, up); + surfaceForward = Vector3.Cross(up, surfaceRight); + } + else + { + surfaceForward = Vector3.Cross(up, right); + surfaceRight = Vector3.Cross(surfaceForward, up); + } + + //Utility.LogMessage(this, "Past gate 7"); + + Vector3 surfaceProgradeProjected = Vector3.ProjectOnPlane(surfacePrograde, up); + progradeHeading = Vector3.Angle(surfaceProgradeProjected, vessel.north); + if (Vector3.Dot(surfaceProgradeProjected, vessel.east) < 0.0) + { + progradeHeading = 360.0f - progradeHeading; + } + + //Utility.LogMessage(this, "Past gate 8"); + + // TODO: Am I computing normal wrong? + // TODO: orbit.GetOrbitNormal() appears to return a vector in the opposite + // direction when in the inertial frame of reference, but it's perpendicular + // in the rotating reference frame. Is there a way to always get the inertial + // frame? Or can I get away with working in whatever framework KSP is working + // in? + //Orbit.SolveClosestApproach(); + //orbit.GetTimeToPeriapsis(); + //orbit.timeToAp; + //orbit.timeToPe; + //orbit.getRelativePositionAtUT(); + // Trajectory object? + //Utility.LogMessage(this, "orb Ap {0:0} / Pe {1:0}, end type {2}", orbit.ApA, orbit.PeA, orbit.patchEndTransition.ToString()); + } + internal double GetRelativePitch(Vector3 direction) + { + // Project the direction vector onto the plane YZ plane + Vector3 projectedVector = Vector3.ProjectOnPlane(direction, right); + projectedVector.Normalize(); + + // Dot the projected vector with the 'top' direction so we can find + // the relative pitch. + float dotPitch = Vector3.Dot(projectedVector, top); + float pitch = Mathf.Asin(dotPitch); + if (float.IsNaN(pitch)) + { + pitch = (dotPitch > 0.0f) ? 90.0f : -90.0f; + } + else + { + pitch *= Mathf.Rad2Deg; + } + + return pitch; + } + internal double GetRelativeYaw(Vector3 direction) + { + // Project the direction vector onto the plane XZ plane + Vector3 projectedVector = Vector3.ProjectOnPlane(direction, top); + projectedVector.Normalize(); + + // Determine the lateral displacement by dotting the vector with + // the 'right' vector... + float dotLateral = Vector3.Dot(projectedVector, right); + // And the forward/back displacement by dotting with the forward vector. + float dotLongitudinal = Vector3.Dot(projectedVector, forward); + + // Taking arc tangent of x/y lets us treat the front of the vessel + // as the 0 degree location. + float yaw = Mathf.Atan2(dotLateral, dotLongitudinal); + yaw *= Mathf.Rad2Deg; + + return yaw; + } + #endregion + + #region Maneuver + private ManeuverNode node; + internal Orbit nodeOrbit; + private double nodeDV = -1.0; + private double nodeTotalDV = 0.0; + private void RefreshNodeValues() + { + // Per KSP API wiki http://docuwiki-kspapi.rhcloud.com/#/classes/ManeuverNode: + // The x-component of DeltaV represents the delta-V in the radial-plus direction. + // The y-component of DeltaV represents the delta-V in the normal-plus direction. + // The z-component of DeltaV represents the delta-V in the prograde direction. + // However... it is not returned in the basis of the orbit at the time of the + // maneuver. It needs transformed into the right basis. + maneuverVector = node.GetBurnVector(orbit); + nodeDV = maneuverVector.magnitude; + nodeTotalDV = node.DeltaV.magnitude; + + // Swizzle these into the right order. + Vector3d mnvrVel = orbit.getOrbitalVelocityAtUT(node.UT).xzy; + Vector3d mnvrPos = orbit.getRelativePositionAtUT(node.UT).xzy; + + Vector3d mnvrPrograde = mnvrVel.normalized; // Prograde vector at maneuver time + Vector3d mnvrNml = Vector3d.Cross(mnvrVel, mnvrPos).normalized; + Vector3d mnvrRadial = Vector3d.Cross(mnvrNml, mnvrPrograde); + + maneuverNodeComponentVector.x = Vector3d.Dot(maneuverVector, mnvrPrograde); + maneuverNodeComponentVector.y = Vector3d.Dot(maneuverVector, mnvrNml); + maneuverNodeComponentVector.z = Vector3d.Dot(maneuverVector, mnvrRadial); + } + + private Vector3d maneuverNodeComponentVector = Vector3d.zero; + internal Vector3d maneuverNodeComponent + { + get + { + if (nodeDV < 0.0) + { + if (node != null && orbit != null) + { + RefreshNodeValues(); + } + else + { + nodeDV = 0.0; + } + } + + return maneuverNodeComponentVector; + } + } + internal double maneuverNodeDeltaV + { + get + { + if (nodeDV < 0.0) + { + if (node != null && orbit != null) + { + RefreshNodeValues(); + } + else + { + nodeDV = 0.0; + } + } + + return nodeDV; + } + } + internal double maneuverNodeTotalDeltaV + { + get + { + if (nodeDV < 0.0) + { + if (node != null && orbit != null) + { + RefreshNodeValues(); + } + else + { + nodeDV = 0.0; + } + } + + return nodeTotalDV; + } + } + private Vector3d maneuverVector; + internal Vector3d maneuverNodeVector + { + get + { + if (nodeDV < 0.0) + { + if (node != null && orbit != null) + { + RefreshNodeValues(); + } + else + { + nodeDV = 0.0; + } + } + + return maneuverVector; + } + } + internal bool maneuverNodeValid + { + get + { + return node != null; + } + } + internal double maneuverNodeTime + { + get + { + if (node != null) + { + return (universalTime - node.UT); + } + else + { + return 0.0; + } + } + } + + internal double NodeBurnTime() + { + if (maneuverNodeValid && currentIsp > 0.0 && currentMaxThrust > 0.0) + { + return currentIsp * (1.0f - Math.Exp(-maneuverNodeDeltaV / currentIsp / PhysicsGlobals.GravitationalAcceleration)) / (currentMaxThrust / (vessel.totalMass * PhysicsGlobals.GravitationalAcceleration)); + } + else + { + return 0.0; + } + } + + void UpdateManeuverNode() + { + if (vessel.patchedConicSolver != null) + { + node = vessel.patchedConicSolver.maneuverNodes.Count > 0 ? vessel.patchedConicSolver.maneuverNodes[0] : null; + + if (node != null) + { + nodeOrbit = node.nextPatch; + } + else + { + nodeOrbit = null; + } + } + else + { + node = null; + nodeOrbit = null; + } + nodeDV = -1.0; + nodeTotalDV = 0.0; + maneuverVector = Vector3d.zero; + maneuverNodeComponentVector = Vector3d.zero; + } + #endregion + + #region Target + public enum TargetType + { + None, + Vessel, + DockingPort, + CelestialBody, + PositionTarget, + Asteroid, + }; + internal ITargetable activeTarget = null; + internal ApproachSolver approachSolver = new ApproachSolver(); + internal Vector3 targetDisplacement = Vector3.zero; + internal Vector3 targetDirection = Vector3.zero; + internal Vector3d targetRelativeVelocity = Vector3.zero; + internal TargetType targetType = TargetType.None; + internal string targetName = string.Empty; + internal Transform targetDockingTransform; // Docking node transform - valid only for docking port targets. + internal ModuleDockingNode[] targetDockingPorts = new ModuleDockingNode[0]; + internal Orbit targetOrbit; + internal double targetClosestUT + { + get + { + if (activeTarget != null && !approachSolver.resultsReady) + { + if (targetType == MASVesselComputer.TargetType.CelestialBody) + { + approachSolver.SolveBodyIntercept(orbit, activeTarget as CelestialBody); + } + else + { + approachSolver.SolveOrbitIntercept(orbit, targetOrbit); + } + } + return approachSolver.resultsReady ? approachSolver.targetClosestUT : 0.0; + } + } + internal double targetClosestSpeed + { + get + { + if (activeTarget != null && !approachSolver.resultsReady) + { + if (targetType == MASVesselComputer.TargetType.CelestialBody) + { + approachSolver.SolveBodyIntercept(orbit, activeTarget as CelestialBody); + } + else + { + approachSolver.SolveOrbitIntercept(orbit, targetOrbit); + } + } + return approachSolver.resultsReady ? approachSolver.targetClosestSpeed : 0.0; + } + } + internal double targetClosestDistance + { + get + { + if (activeTarget != null && !approachSolver.resultsReady) + { + if (targetType == MASVesselComputer.TargetType.CelestialBody) + { + approachSolver.SolveBodyIntercept(orbit, activeTarget as CelestialBody); + } + else + { + approachSolver.SolveOrbitIntercept(orbit, targetOrbit); + } + } + if (approachSolver.resultsReady) + { + if (targetType == TargetType.CelestialBody) + { + // If we are targeting a body, account for the radius of the planet when describing closest approach. + // That is, targetClosestDistance is effectively PeA. + return Math.Max(0.0, approachSolver.targetClosestDistance - (activeTarget as CelestialBody).Radius); + } + else + { + return approachSolver.targetClosestDistance; + } + } + else + { + return 0.0; + } + } + } + internal bool targetValid + { + get + { + return (activeTarget != null); + } + } + private double targetCmpSpeed = -1.0; + internal double targetSpeed + { + get + { + if (targetCmpSpeed < 0.0) + { + targetCmpSpeed = targetRelativeVelocity.magnitude; + } + return targetCmpSpeed; + } + } + private void UpdateTargetDockingPorts() + { + Vessel targetVessel = (targetType == TargetType.Vessel) ? (activeTarget as Vessel) : (activeTarget as ModuleDockingNode).vessel; + if (!targetVessel.packed && targetVessel.loaded) + { + List potentialDocks = targetVessel.FindPartModulesImplementing(); + List validDocks = new List(); + + if (dockingNode != null) + { + for (int i = potentialDocks.Count - 1; i >= 0; --i) + { + ModuleDockingNode otherDock = potentialDocks[i]; + // Only lock on to an available dock of the same type that is either ungendered or the opposite gender. + if (otherDock.state == "Ready" && (string.IsNullOrEmpty(dockingNode.nodeType) || dockingNode.nodeType == otherDock.nodeType) && (dockingNode.gendered == false || dockingNode.genderFemale != otherDock.genderFemale)) + { + validDocks.Add(otherDock); + } + } + } + else + { + for (int i = potentialDocks.Count - 1; i >= 0; --i) + { + ModuleDockingNode otherDock = potentialDocks[i]; + // Only lock on to an available dock of the same type that is either ungendered or the opposite gender. + if (otherDock.state == "Ready") + { + validDocks.Add(otherDock); + } + } + } + if (targetDockingPorts.Length != validDocks.Count) + { + targetDockingPorts = validDocks.ToArray(); + } + else + { + for (int i = targetDockingPorts.Length - 1; i >= 0; --i) + { + targetDockingPorts[i] = validDocks[i]; + } + } + } + + else if ((targetVessel.packed || !targetVessel.loaded) && targetDockingPorts.Length > 0) + { + targetDockingPorts = new ModuleDockingNode[0]; + } + } + private void UpdateTarget() + { + activeTarget = FlightGlobals.fetch.VesselTarget; + if (activeTarget != null) + { + targetDisplacement = activeTarget.GetTransform().position - vessel.GetTransform().position; + targetDirection = targetDisplacement.normalized; + + targetRelativeVelocity = vessel.obt_velocity - activeTarget.GetObtVelocity(); + targetCmpSpeed = -1.0; + targetDockingTransform = null; + + if (activeTarget is Vessel) + { + targetType = ((activeTarget as Vessel).vesselType == VesselType.SpaceObject) ? TargetType.Asteroid : TargetType.Vessel; + } + else if (activeTarget is CelestialBody) + { + targetType = TargetType.CelestialBody; + } + else if (activeTarget is ModuleDockingNode) + { + targetType = TargetType.DockingPort; + targetDockingTransform = (activeTarget as ModuleDockingNode).GetTransform(); + } + else if (activeTarget is PositionTarget) + { + targetType = TargetType.PositionTarget; + } + else + { + Utility.LogError(this, "UpdateTarget() - unable to classify target {0}", activeTarget.GetType().Name); + targetType = TargetType.None; + } + + if (targetType == TargetType.Vessel || targetType == TargetType.DockingPort) + { + UpdateTargetDockingPorts(); + } + + targetName = activeTarget.GetName(); + targetOrbit = activeTarget.GetOrbit(); + } + else + { + targetCmpSpeed = 0.0; + targetType = TargetType.None; + targetDisplacement = Vector3.zero; + targetRelativeVelocity = Vector3d.zero; + targetDirection = forward; + targetDockingTransform = null; + targetName = string.Empty; + targetOrbit = null; + if (targetDockingPorts.Length > 0) + { + targetDockingPorts = new ModuleDockingNode[0]; + } + } + approachSolver.ResetComputation(); + } + #endregion + + private bool aeroDataValid = false; + private double dragForce; + private double gravForce; + private double liftForce; + private double liftUpForce; + private double terminalVelocity; + + private void UpdateAeroForces() + { + if (aeroDataValid) + { + return; + } + + aeroDataValid = true; + + gravForce = 1000.0 * vessel.GetTotalMass() * FlightGlobals.getGeeForceAtPosition(vessel.CoM).magnitude; // force of gravity + + // Short-circuit these computations if there's no atmosphere. + if (vessel.atmDensity == 0.0) + { + liftForce = 0.0; + dragForce = 0.0; + terminalVelocity = 0.0; + liftUpForce = 0.0; + + return; + } + + // Code substantially from NathanKell's AeroGUI mod, + // https://github.com/NathanKell/AeroGUI/blob/ccfd5e2e40fdf13e6ce66517ceb1db418689a5f0/AeroGUI/AeroGUI.cs#L301 + + Vector3d vLift = Vector3d.zero; // the sum of lift from all parts + Vector3d vDrag = Vector3d.zero; // the sum of drag from all parts + double areaDrag = 0.0; + + for (int i = vessel.Parts.Count - 1; i >= 0; --i) + { + Part p = vessel.Parts[i]; + + // get part drag (but not wing/surface drag) + vDrag += -p.dragVectorDir * p.dragScalar; + if (!p.hasLiftModule) + { + Vector3 bodyLift = p.transform.rotation * (p.bodyLiftScalar * p.DragCubes.LiftForce); + bodyLift = Vector3.ProjectOnPlane(bodyLift, -p.dragVectorDir); + vLift += bodyLift; + } + + ModuleLiftingSurface wing = p.FindModuleImplementing(); + if (wing != null) + { + vLift += wing.liftForce; + vDrag += wing.dragForce; + } + + areaDrag += p.DragCubes.AreaDrag * PhysicsGlobals.DragCubeMultiplier * PhysicsGlobals.DragMultiplier; + } + + Vector3d force = vLift + vDrag; // sum of all forces on the craft + Vector3d nVel = vessel.srf_velocity.normalized; + Vector3d liftDir = -Vector3d.Cross(vessel.transform.right, nVel); // we need the "lift" direction, which + // is "up" from our current velocity vector and roll angle. + + // Now we can compute the dots. + liftForce = Vector3d.Dot(force, liftDir); // just the force in the 'lift' direction + + dragForce = Vector3d.Dot(force, -nVel); // drag force, = pDrag + lift-induced drag + + liftUpForce = Vector3d.Dot(force, up); + + terminalVelocity = Math.Sqrt(2.0 * gravForce / (areaDrag * vessel.atmDensity)); + } + + internal double DragForce() + { + if (!aeroDataValid) + { + UpdateAeroForces(); + } + + return dragForce; + } + internal double GravForce() + { + if (!aeroDataValid) + { + UpdateAeroForces(); + } + + return gravForce; + } + internal double LiftForce() + { + if (!aeroDataValid) + { + UpdateAeroForces(); + } + + return liftForce; + } + internal double LiftUpForce() + { + if (!aeroDataValid) + { + UpdateAeroForces(); + } + + return liftUpForce; + } + internal double TerminalVelocity() + { + if (!aeroDataValid) + { + UpdateAeroForces(); + } + + return terminalVelocity; + } + + internal double surfaceAccelerationFromGravity; + private void UpdateMisc() + { + // Convert to m/2^s + surfaceAccelerationFromGravity = orbit.referenceBody.GeeASL * PhysicsGlobals.GravitationalAcceleration; + aeroDataValid = false; + } + + private void UpdateReferenceTransform(Part referencePart, bool forceEvaluate) + { + if (referencePart == null) + { + // During staging, it's possible for referencePart to be null. If it is, let's skip + // this processing. Things will sort out later. + Utility.LogMessage(this, "Null referencePart for {0}; setting to root!", vessel.id); + vessel.SetReferenceTransform(vessel.rootPart); + referencePart = vessel.GetReferenceTransformPart(); + //return; + } + + Transform newRefXform = referencePart.GetReferenceTransform(); + if (_referenceTransform == newRefXform && !forceEvaluate) + { + return; + } + + _referenceTransform = newRefXform; + referenceTransformType = ReferenceType.Unknown; + + if (referencePart.Modules.GetModule() != null) + { + referenceTransformType = ReferenceType.RemoteCommand; + if (CameraManager.Instance.currentCameraMode == CameraManager.CameraMode.IVA) + { + Kerbal refKerbal = CameraManager.Instance.IVACameraActiveKerbal; + if (refKerbal != null && refKerbal.InPart == referencePart) + { + referenceTransformType = ReferenceType.Self; + } + } + } + else if (referencePart.Modules.GetModule() != null) + { + referenceTransformType = ReferenceType.DockingPort; + } + else if (referencePart.Modules.GetModule() != null) + { + referenceTransformType = ReferenceType.Claw; + } + + UpdateDockingNode(referencePart); + } + #endregion + + #region GameEvent Callbacks + /// + /// The player changed camera modes. If we're going from 'outside' to + /// 'inside', we can figure out which part we're in, and thus whether + /// there are docks available. To do that, we have to reprocess the + /// reference transform. + /// + /// + private void onCameraChange(CameraManager.CameraMode newMode) + { + vesselActive = ActiveVessel(vessel); + UpdateReferenceTransform(vessel.GetReferenceTransformPart(), vesselActive); + } + + /// + /// We staged - time to refresh our resource tracking. + /// + /// + private void onStageActivate(int stage) + { + InvalidateModules(); + } + + private void onVesselChange(Vessel who) + { + if (who.id == vesselId) + { + vesselCrewed = true && HighLogic.LoadedSceneIsFlight; + vesselActive = ActiveVessel(vessel); + InvalidateModules(); + Utility.LogMessage(this, "onVesselChange for {0}", who.id); + } + } + + private void onVesselSOIChanged(GameEvents.HostedFromToAction what) + { + if (what.host.id == vesselId) + { + Utility.LogMessage(this, "onVesselSOIChanged: to {0}", what.to.bodyName); + mainBody = what.to; + } + } + + private void onVesselWasModified(Vessel who) + { + if (who.id == vesselId) + { + vesselCrewed = true && HighLogic.LoadedSceneIsFlight; + vesselActive = ActiveVessel(vessel); + InvalidateModules(); + Utility.LogMessage(this, "onVesselWasModified for {0}", who.id); + } + } + + private void onVesselDestroy(Vessel who) + { + if (who.id == vesselId) + { + vesselCrewed = true && HighLogic.LoadedSceneIsFlight; + vesselActive = ActiveVessel(vessel); + Utility.LogMessage(this, "onVesselDestroy for {0}", who.id); + } + } + + private void onVesselCreate(Vessel who) + { + if (who.id == vesselId) + { + vesselCrewed = true && HighLogic.LoadedSceneIsFlight; + vesselActive = ActiveVessel(vessel); + Utility.LogMessage(this, "onVesselCreate for {0}", who.id); + } + } + + private void onVesselCrewWasModified(Vessel who) + { + if (who.id == vessel.id) + { + vesselCrewed = true && HighLogic.LoadedSceneIsFlight; + vesselActive = ActiveVessel(vessel); + Utility.LogMessage(this, "onVesselCrewWasModified for {0}", who.id); + } + } + #endregion + } +} diff --git a/ModifiedCode/MASVesselComputerModules.cs b/ModifiedCode/MASVesselComputerModules.cs new file mode 100644 index 00000000..cec5b9eb --- /dev/null +++ b/ModifiedCode/MASVesselComputerModules.cs @@ -0,0 +1,1815 @@ +//#define TIME_REBUILD +//#define TIME_UPDATES +/***************************************************************************** + * The MIT License (MIT) + * + * Copyright (c) 2016-2020 MOARdV + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + ****************************************************************************/ +using KSP.UI.Screens; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Text; +using UnityEngine; + +namespace AvionicsSystems +{ + internal partial class MASVesselComputer : MonoBehaviour + { + // Tracks per-module data. + + internal bool modulesInvalidated = true; + // There appears to be a quirk when entering flight. The MASVesselComputer will + // initialize before all of the ModuleScienceExperiment modules are ready, and MAS + // will see some ScienceExperiment fields that aren't initialized yet. To manage + // that problem, this boolean is exposed such that the fc proxy can set it. + // When true, the vc will reprocess the MSE entries and rebuild its table of known + // science types. + internal bool scienceInvalidated = false; + + #region Action Groups + //---Action Groups + private bool[] hasActionGroup = new bool[17]; + + /// + /// Returns true if there is at least one action in this action group. + /// + /// + /// + internal bool GroupHasActions(KSPActionGroup ag) + { + int index = -1; + for (int ui = (int)ag; ui != 0; ui >>= 1) + { + ++index; + } + + if (index < 0 || index > hasActionGroup.Length) + { + // Should never happen! + throw new ArgumentOutOfRangeException("MASVesselComputer.GroupHasActions() called with invalid action group!"); + } + + return hasActionGroup[index]; + } + + /// + /// Sets the indicated action group to true (meaning there is an action) + /// + /// + private void SetActionGroup(KSPActionGroup ag) + { + int index = 0; + for (int ui = (int)ag; ui != 0; ui >>= 1) + { + if ((ui & 0x1) != 0) + { + hasActionGroup[index] = true; + } + ++index; + } + } + #endregion + + #region Air Brakes + private List airBrakeList = new List(); + internal ModuleAeroSurface[] moduleAirBrake = new ModuleAeroSurface[0]; + #endregion + + #region Aircraft Engines + private List thrustReverserList = new List(); + internal MASThrustReverser[] moduleThrustReverser = new MASThrustReverser[0]; + private List resourceIntakeList = new List(); + internal ModuleResourceIntake[] moduleResourceIntake = new ModuleResourceIntake[0]; + #endregion + + #region Brakes + private List brakesList = new List(); + internal ModuleWheels.ModuleWheelBrakes[] moduleBrakes = new ModuleWheels.ModuleWheelBrakes[0]; + #endregion + + #region Cameras + private List cameraList = new List(4); + internal MASCamera[] moduleCamera = new MASCamera[0]; + private int dockCamCount = 0; + private int camCount = 0; + private bool camerasReset = true; + private void UpdateCamera() + { + if (camerasReset) + { + camerasReset = false; + for (int i = moduleCamera.Length - 1; i >= 0; --i) + { + if (string.IsNullOrEmpty(moduleCamera[i].cameraName)) + { + if (moduleCamera[i].isDockingPortCamera) + { + ++dockCamCount; + moduleCamera[i].cameraName = string.Format("Dock {0}", dockCamCount); + } + else + { + ++camCount; + moduleCamera[i].cameraName = string.Format("Camera {0}", camCount); + } + } + else + { + MASCamera[] dupeNames = Array.FindAll(moduleCamera, x => x.cameraName == moduleCamera[i].cameraName); + + if (dupeNames.Length > 1) + { + for (int dupeIndex = 0; dupeIndex < dupeNames.Length; ++dupeIndex) + { + dupeNames[dupeIndex].cameraName = string.Format("{0} {1}", dupeNames[dupeIndex].cameraName, dupeIndex + 1); + } + } + } + } + } + } + + /// + /// Search the list of camera modules to find the named camera. + /// + /// The name to search for + /// The camera module, or null if it was not found. + internal MASCamera FindCameraModule(string cameraName) + { + for (int i = moduleCamera.Length - 1; i >= 0; --i) + { + if (moduleCamera[i].cameraName == cameraName) + { + return moduleCamera[i]; + } + } + + return null; + } + #endregion + + #region Cargo Bay + private List cargoBayList = new List(2); + internal ModuleCargoBay[] moduleCargoBay = new ModuleCargoBay[0]; + internal float cargoBayDirection = 0.0f; + internal float cargoBayPosition = 0.0f; + void UpdateCargoBay() + { + if (moduleCargoBay.Length > 0) + { + float newPosition = 0.0f; + float numBays = 0.0f; + for (int i = moduleCargoBay.Length - 1; i >= 0; --i) + { + ModuleCargoBay me = moduleCargoBay[i]; + PartModule deployer = me.part.Modules[me.DeployModuleIndex]; + /*if (deployer is ModuleServiceModule) + { + ModuleServiceModule msm = deployer as ModuleServiceModule; + if (msm.IsDeployed) + { + cargoBayDeployed = true; + } + else + { + cargoBayRetracted = true; + } + } + else*/ + if (deployer is ModuleAnimateGeneric) + { + ModuleAnimateGeneric mag = deployer as ModuleAnimateGeneric; + newPosition += Mathf.InverseLerp(me.closedPosition, Mathf.Abs(1.0f - me.closedPosition), mag.animTime); + } + } + + if (numBays > 1.0f) + { + newPosition /= numBays; + } + + cargoBayDirection = newPosition - cargoBayPosition; + if (cargoBayDirection < 0.0f) + { + cargoBayDirection = -1.0f; + } + else if (cargoBayDirection > 0.0f) + { + cargoBayDirection = 1.0f; + } + cargoBayPosition = newPosition; + } + } + #endregion + + #region Color Changer + private List colorChangerList = new List(2); + internal ModuleColorChanger[] moduleColorChanger = new ModuleColorChanger[0]; + #endregion + + #region Communications + private List antennaList = new List(2); + internal ModuleDeployableAntenna[] moduleAntenna = new ModuleDeployableAntenna[0]; + internal bool antennaDeployable; + internal bool antennaRetractable; + internal int antennaMoving; + internal bool antennaDamaged; + + private List transmitterList = new List(2); + internal ModuleDataTransmitter[] moduleTransmitter = new ModuleDataTransmitter[0]; + private void UpdateAntenna() + { + // TODO: What about detecting if dynamic pressure is low enough to deploy antennae? + antennaDeployable = false; + antennaRetractable = false; + antennaMoving = 0; + antennaDamaged = false; + + for (int i = moduleAntenna.Length - 1; i >= 0; --i) + { + if (moduleAntenna[i].useAnimation) + { + antennaRetractable |= (moduleAntenna[i].retractable && moduleAntenna[i].deployState == ModuleDeployablePart.DeployState.EXTENDED); + antennaDeployable |= (moduleAntenna[i].deployState == ModuleDeployablePart.DeployState.RETRACTED); + if (moduleAntenna[i].deployState == ModuleDeployablePart.DeployState.RETRACTING) + { + antennaMoving = -1; + } + else if (moduleAntenna[i].deployState == ModuleDeployablePart.DeployState.EXTENDING) + { + antennaMoving = 1; + } + antennaDamaged |= (moduleAntenna[i].deployState == ModuleDeployablePart.DeployState.BROKEN); + } + } + } + + #endregion + + #region Docking + // Unlike some of the other sections here, the Dock section focuses on + // a single ModuleDockingNode that the vessel computer designates as + // the "main" docking node. We do this because we do NOT want an + // undock event, for instance, to disassemble an entire space + // station, and we never want to affect more than one docking port with + // such transactions. Code substantially imported from what I wrote for + // RasterPropMonitor. + // This concept has also been extended to the Claw / grabber units. + internal MASCamera dockingCamera; + internal ModuleDockingNode dockingNode; + internal DockingNodeState dockingNodeState = DockingNodeState.UNKNOWN; + internal enum DockingNodeState + { + // Indeterminate state + UNKNOWN, + // Docked to an object + DOCKED, + // Pre-attached (docked in the VAB/SPH) + PREATTACHED, + // Ready to dock + READY, + // Disabled (such as a grapple that is not armed) + DISABLED + }; + internal ModuleGrappleNode clawNode; + internal DockingNodeState clawNodeState = DockingNodeState.UNKNOWN; + private void UpdateDockingNode(Part referencePart) + { + // Our candidate for docking node. Called from UpdateReferenceTransform() + ModuleDockingNode dockingNode = null; + ModuleGrappleNode clawNode = null; + + if (referencePart != null) + { + // See if the reference part is a docking port. + if (referenceTransformType == ReferenceType.DockingPort) + { + // If it's a docking port, this should be all we need to do. + dockingNode = referencePart.FindModuleImplementing(); + } + else if (referenceTransformType == ReferenceType.Claw) + { + clawNode = referencePart.FindModuleImplementing(); + } + else + { + uint shipFlightNumber = 0; + if (referenceTransformType == ReferenceType.Self || referenceTransformType == ReferenceType.RemoteCommand) + { + // If the reference transform is the current IVA, we need + // to look for another part that has a docking node and the + // same ID as our part. + shipFlightNumber = referencePart.launchID; + } + else + { + if (CameraManager.Instance.currentCameraMode == CameraManager.CameraMode.IVA) + { + Kerbal refKerbal = CameraManager.Instance.IVACameraActiveKerbal; + if (refKerbal != null) + { + shipFlightNumber = refKerbal.InPart.launchID; + } + } + } + + if (shipFlightNumber > 0) + { + List vesselParts = vessel.Parts; + for (int i = vesselParts.Count - 1; i >= 0; --i) + { + Part p = vesselParts[i]; + if (p.launchID == shipFlightNumber) + { + dockingNode = p.FindModuleImplementing(); + if (dockingNode != null) + { + break; + } + clawNode = p.FindModuleImplementing(); + if (clawNode != null) + { + break; + } + } + } + } + } + } + + this.dockingNode = dockingNode; + if (this.dockingNode != null) + { + this.dockingCamera = this.dockingNode.part.FindModuleImplementing(); + } + else + { + this.dockingCamera = null; + } + this.clawNode = clawNode; + } + + private void UpdateDockingNodeState() + { + if (dockingNode != null) + { + switch (dockingNode.state) + { + case "PreAttached": + dockingNodeState = DockingNodeState.PREATTACHED; + break; + case "Docked (docker)": + dockingNodeState = DockingNodeState.DOCKED; + break; + case "Docked (dockee)": + dockingNodeState = DockingNodeState.DOCKED; + break; + case "Ready": + dockingNodeState = DockingNodeState.READY; + break; + default: + dockingNodeState = DockingNodeState.UNKNOWN; + break; + } + } + else + { + dockingNodeState = DockingNodeState.UNKNOWN; + } + + if (clawNode != null) + { + switch (clawNode.state) + { + case "Disabled": + clawNodeState = DockingNodeState.DISABLED; + break; + case "Ready": + clawNodeState = DockingNodeState.READY; + break; + case "Grappled": + clawNodeState = DockingNodeState.DOCKED; + break; + default: + Utility.LogMessage(this, "Claw = {0}, unlocked {1}, loose {2}", clawNode.state, clawNode.IsJointUnlocked(), clawNode.IsLoose()); + clawNodeState = DockingNodeState.UNKNOWN; + break; + } + } + else + { + clawNodeState = DockingNodeState.UNKNOWN; + } + } + #endregion + + #region Engines + //---Engines + private List idEnginesList = new List(); + internal MASIdEngine[] moduleIdEngines = new MASIdEngine[0]; + private List enginesList = new List(8); + internal ModuleEngines[] moduleEngines = new ModuleEngines[0]; + private List multiModeEngineList = new List(); + internal MultiModeEngine[] multiModeEngines = new MultiModeEngine[0]; + private List engineGroupList = new List(); + internal MASIdEngineGroup[] engineGroup = new MASIdEngineGroup[0]; + internal PartModule moduleGraviticEngine = null; + private float[] invMaxISP = new float[0]; + internal float currentThrust; // current net thrust, kN + internal float currentLimitedMaxThrust; // Max thrust, accounting for throttle limits, kN + internal float currentMaxThrust; // Max possible thrust at current altitude, kN + internal float maxRatedThrust; // Max possible thrust, kN + internal float maxEngineFuelFlow; // max fuel flow, g/s + internal float currentEngineFuelFlow; // current fuel flow, g/s + internal float currentIsp; + internal float maxIsp; + internal float hottestEngineTemperature; + internal float hottestEngineMaxTemperature; + internal float hottestEngineSign; + internal int currentEngineCount; + internal int activeEngineCount; + internal float throttleLimit; + internal bool anyEnginesFlameout; + internal bool anyEnginesEnabled; + private List visitedParts = new List(); + private bool UpdateEngines() + { + this.currentThrust = 0.0f; + this.maxRatedThrust = 0.0f; + currentLimitedMaxThrust = 0.0f; + currentMaxThrust = 0.0f; + hottestEngineMaxTemperature = 0.0f; + hottestEngineSign = 0.0f; + maxEngineFuelFlow = 0.0f; + currentEngineFuelFlow = 0.0f; + throttleLimit = 0.0f; + float throttleCount = 0.0f; + anyEnginesFlameout = false; + anyEnginesEnabled = false; + activeEngineCount = 0; + + float hottestEngine = float.MaxValue; + float currentHottestEngine = 0.0f; + float maxIspContribution = 0.0f; + float averageIspContribution = 0.0f; + + visitedParts.Clear(); + + bool requestReset = false; + for (int i = moduleEngines.Length - 1; i >= 0; --i) + { + ModuleEngines me = moduleEngines[i]; + requestReset |= (!me.isEnabled); + + Part thatPart = me.part; + if (thatPart.inverseStage == StageManager.CurrentStage) + { + if (!visitedParts.Contains(thatPart)) + { + currentEngineCount++; + if (me.getIgnitionState) + { + activeEngineCount++; + } + visitedParts.Add(thatPart); + } + } + + anyEnginesEnabled |= me.allowShutdown && me.getIgnitionState; + anyEnginesFlameout |= (me.isActiveAndEnabled && me.flameout); + + if (me.EngineIgnited && me.isEnabled) + { + throttleLimit += me.thrustPercentage; + throttleCount += 1.0f; + + float currentThrust = me.finalThrust; + this.currentThrust += currentThrust; + this.maxRatedThrust += me.GetMaxThrust(); + float rawMaxThrust = me.GetMaxThrust() * me.realIsp * invMaxISP[i]; + currentMaxThrust += rawMaxThrust; + float maxThrust = rawMaxThrust * me.thrustPercentage * 0.01f; + currentLimitedMaxThrust += maxThrust; + float realIsp = me.realIsp; + + if (realIsp > 0.0f) + { + averageIspContribution += maxThrust / realIsp; + + // Compute specific fuel consumption and + // multiply by thrust to get grams/sec fuel flow + float specificFuelConsumption = 101972f / realIsp; + maxEngineFuelFlow += specificFuelConsumption * rawMaxThrust; + currentEngineFuelFlow += specificFuelConsumption * currentThrust; + } + if (invMaxISP[i] > 0.0f) + { + maxIspContribution += maxThrust * invMaxISP[i]; + } + + List propellants = me.GetConsumedResources(); + for (int res = propellants.Count - 1; res >= 0; --res) + { + MarkActiveEnginePropellant(propellants[res].id); + } + } + + if (thatPart.skinMaxTemp - thatPart.skinTemperature < hottestEngine) + { + currentHottestEngine = (float)thatPart.skinTemperature; + hottestEngineMaxTemperature = (float)thatPart.skinMaxTemp; + hottestEngine = hottestEngineMaxTemperature - currentHottestEngine; + } + if (thatPart.maxTemp - thatPart.temperature < hottestEngine) + { + currentHottestEngine = (float)thatPart.temperature; + hottestEngineMaxTemperature = (float)thatPart.maxTemp; + hottestEngine = hottestEngineMaxTemperature - currentHottestEngine; + } + } + + hottestEngineSign = Mathf.Sign(currentHottestEngine - hottestEngineTemperature); + hottestEngineTemperature = currentHottestEngine; + + if (throttleCount > 0.0f) + { + throttleLimit /= (throttleCount * 100.0f); + } + + if (averageIspContribution > 0.0f) + { + currentIsp = currentLimitedMaxThrust / averageIspContribution; + } + else + { + currentIsp = 0.0f; + } + + if (maxIspContribution > 0.0f) + { + maxIsp = currentLimitedMaxThrust / maxIspContribution; + } + else + { + maxIsp = 0.0f; + } + + return requestReset; + } + internal bool SetEnginesEnabled(bool newState) + { + for (int i = moduleEngines.Length - 1; i >= 0; --i) + { + Part thatPart = moduleEngines[i].part; + + if (thatPart.inverseStage == StageManager.CurrentStage || !newState) + { + if (moduleEngines[i].EngineIgnited != newState) + { + if (newState && moduleEngines[i].allowRestart) + { + moduleEngines[i].Activate(); + } + else if (moduleEngines[i].allowShutdown) + { + moduleEngines[i].Shutdown(); + } + } + } + } + + return newState; + } + + internal bool SetThrottleLimit(float newLimit) + { + bool anyUpdated = false; + for (int i = moduleEngines.Length - 1; i >= 0; --i) + { + Part thatPart = moduleEngines[i].part; + + if (thatPart.inverseStage == StageManager.CurrentStage) + { + if (moduleEngines[i].EngineIgnited) + { + moduleEngines[i].thrustPercentage = newLimit; + anyUpdated = true; + } + } + } + + return anyUpdated; + } + #endregion + + #region Launch Clamp + private List launchClampList = new List(); + internal LaunchClamp[] moduleLaunchClamp = new LaunchClamp[0]; + #endregion + + #region Gimbal + private List gimbalsList = new List(8); + internal ModuleGimbal[] moduleGimbals = new ModuleGimbal[0]; + internal bool anyGimbalsLocked = false; + internal bool anyGimbalsRoll = false; + internal bool anyGimbalsPitch = false; + internal bool anyGimbalsYaw = false; + internal bool anyGimbalsActive = false; + internal bool activeEnginesGimbal = false; + internal float gimbalDeflection = 0.0f; + internal Vector2 gimbalAxisDeflection = Vector2.zero; + internal float gimbalLimit = 0.0f; + void UpdateGimbals() + { + gimbalLimit = 0.0f; + gimbalDeflection = 0.0f; + anyGimbalsLocked = false; + anyGimbalsActive = false; + anyGimbalsRoll = false; + anyGimbalsPitch = false; + anyGimbalsYaw = false; + Vector2 localDeflection = Vector2.zero; + gimbalAxisDeflection = Vector2.zero; + float gimbalCount = 0.0f; + + for (int i = moduleGimbals.Length - 1; i >= 0; --i) + { + if (moduleGimbals[i].gimbalLock) + { + anyGimbalsLocked = true; + } + else if (moduleGimbals[i].gimbalActive) + { + anyGimbalsActive = true; + + anyGimbalsRoll |= moduleGimbals[i].enableRoll; + anyGimbalsPitch |= moduleGimbals[i].enablePitch; + anyGimbalsYaw |= moduleGimbals[i].enableYaw; + + gimbalCount += 1.0f; + + gimbalLimit += moduleGimbals[i].gimbalLimiter; + + localDeflection.x = moduleGimbals[i].actuationLocal.x; + // all gimbal range values are positive scalars. + if (localDeflection.x < 0.0f) + { + localDeflection.x /= moduleGimbals[i].gimbalRangeXN; + } + else + { + localDeflection.x /= moduleGimbals[i].gimbalRangeXP; + } + + localDeflection.y = moduleGimbals[i].actuationLocal.y; + if (localDeflection.y < 0.0f) + { + localDeflection.y /= moduleGimbals[i].gimbalRangeYN; + } + else + { + localDeflection.y /= moduleGimbals[i].gimbalRangeYP; + } + + gimbalAxisDeflection += localDeflection; + gimbalDeflection += localDeflection.magnitude; + } + } + + if (gimbalCount > 0.0f) + { + float invGimbalCount = 1.0f / gimbalCount; + + gimbalDeflection *= invGimbalCount; + // Must convert from [0, 100] to [0, 1], so multiply by 0.01. + gimbalLimit *= invGimbalCount * 0.01f; + gimbalAxisDeflection.x *= invGimbalCount; + gimbalAxisDeflection.y *= invGimbalCount; + } + } + #endregion + + #region Parachutes + private List realchuteList = new List(8); + internal PartModule[] moduleRealChute = new PartModule[0]; + private List parachuteList = new List(8); + internal ModuleParachute[] moduleParachute = new ModuleParachute[0]; + #endregion + + #region Power Production + private List alternatorList = new List(); + internal ModuleAlternator[] moduleAlternator = new ModuleAlternator[0]; + private List generatorList = new List(); + internal ModuleGenerator[] moduleGenerator = new ModuleGenerator[0]; + internal List generatorOutputList = new List(); + internal float[] generatorOutput = new float[0]; + private List solarPanelList = new List(); + internal ModuleDeployableSolarPanel[] moduleSolarPanel = new ModuleDeployableSolarPanel[0]; + internal bool generatorActive; + internal bool solarPanelsDeployable; + internal float solarPanelsEfficiency; + internal bool solarPanelsRetractable; + internal int solarPanelsMoving; + internal bool solarPanelsDamaged; + internal float netAlternatorOutput; + internal float netGeneratorOutput; + internal float netSolarOutput; + private void UpdatePower() + { + // TODO: What about detecting if dynamic pressure is low enough to deploy solar panels? + netAlternatorOutput = 0.0f; + netGeneratorOutput = 0.0f; + netSolarOutput = 0.0f; + solarPanelsEfficiency = 0.0f; + + generatorActive = false; + solarPanelsDeployable = false; + solarPanelsRetractable = false; + solarPanelsMoving = 0; + solarPanelsDamaged = false; + + for (int i = moduleGenerator.Length - 1; i >= 0; --i) + { + generatorActive |= (moduleGenerator[i].generatorIsActive && !moduleGenerator[i].isAlwaysActive); + + if (moduleGenerator[i].generatorIsActive) + { + float output = moduleGenerator[i].efficiency * generatorOutput[i]; + if (moduleGenerator[i].isThrottleControlled) + { + output *= moduleGenerator[i].throttle; + } + netGeneratorOutput += output; + } + } + + for (int i = moduleAlternator.Length - 1; i >= 0; --i) + { + // I assume there's only one ElectricCharge output in a given ModuleAlternator + netAlternatorOutput += moduleAlternator[i].outputRate; + } + + for (int i = moduleSolarPanel.Length - 1; i >= 0; --i) + { + netSolarOutput += moduleSolarPanel[i].flowRate; + solarPanelsEfficiency += moduleSolarPanel[i].flowRate / moduleSolarPanel[i].chargeRate; + + if (moduleSolarPanel[i].useAnimation) + { + solarPanelsRetractable |= (moduleSolarPanel[i].retractable && moduleSolarPanel[i].deployState == ModuleDeployablePart.DeployState.EXTENDED); + solarPanelsDeployable |= (moduleSolarPanel[i].deployState == ModuleDeployablePart.DeployState.RETRACTED); + if (moduleSolarPanel[i].deployState == ModuleDeployablePart.DeployState.RETRACTING) + { + solarPanelsMoving = -1; + } + else if (moduleSolarPanel[i].deployState == ModuleDeployablePart.DeployState.EXTENDING) + { + solarPanelsMoving = 1; + } + solarPanelsDamaged |= (moduleSolarPanel[i].deployState == ModuleDeployablePart.DeployState.BROKEN); + } + } + if (solarPanelsEfficiency > 0.0f) + { + solarPanelsEfficiency /= (float)moduleSolarPanel.Length; + } + } + #endregion + + #region Procedural Fairings + private List proceduralFairingList = new List(); + internal ModuleProceduralFairing[] moduleProceduralFairing = new ModuleProceduralFairing[0]; + internal bool fairingsCanDeploy = false; + private void UpdateProceduralFairing() + { + fairingsCanDeploy = false; + + for (int i = moduleProceduralFairing.Length - 1; i >= 0; --i) + { + if (moduleProceduralFairing[i].CanMove) + { + fairingsCanDeploy = true; + } + //Utility.LogMessage(this, "fairing {0}: CanMove {1}", + // i, moduleProceduralFairing[i].CanMove); + } + } + #endregion + + #region Radar + private List radarList = new List(); + internal MASRadar[] moduleRadar = new MASRadar[0]; + #endregion + + #region RCS + private List rcsList = new List(8); + internal ModuleRCS[] moduleRcs = new ModuleRCS[0]; + internal bool anyRcsDisabled = false; + internal bool anyRcsFiring = false; + internal bool anyRcsRotate = false; + internal bool anyRcsTranslate = false; + internal float rcsWeightedThrustLimit; + internal float rcsActiveThrustPercent; + private void UpdateRcs() + { + anyRcsDisabled = false; + anyRcsFiring = false; + anyRcsRotate = false; + anyRcsTranslate = false; + float netThrust = 0.0f; + rcsWeightedThrustLimit = 0.0f; + rcsActiveThrustPercent = 0.0f; + float numActiveThrusters = 0.0f; + + for (int i = moduleRcs.Length - 1; i >= 0; --i) + { + if (moduleRcs[i].rcsEnabled == false) + { + anyRcsDisabled = true; + } + else + { + if (moduleRcs[i].enableX || moduleRcs[i].enableY || moduleRcs[i].enableZ) + { + anyRcsTranslate = true; + } + if (moduleRcs[i].enableRoll || moduleRcs[i].enableYaw || moduleRcs[i].enablePitch) + { + anyRcsRotate = true; + } + if (moduleRcs[i].rcs_active) + { + for (int q = 0; q < moduleRcs[i].thrustForces.Length; ++q) + { + if (moduleRcs[i].thrustForces[q] > 0.0f) + { + rcsActiveThrustPercent += moduleRcs[i].thrustForces[q] / moduleRcs[i].thrusterPower; + numActiveThrusters += 1.0f; + anyRcsFiring = true; + } + } + } + netThrust += moduleRcs[i].thrusterPower; + rcsWeightedThrustLimit += moduleRcs[i].thrusterPower * moduleRcs[i].thrustPercentage; + + List propellants = moduleRcs[i].GetConsumedResources(); + for (int res = propellants.Count - 1; res >= 0; --res) + { + MarkActiveRcsPropellant(propellants[res].id); + } + } + } + + if (numActiveThrusters > 0.0f) + { + rcsActiveThrustPercent /= numActiveThrusters; + } + + if (netThrust > 0.0f) + { + rcsWeightedThrustLimit = rcsWeightedThrustLimit / (netThrust * 100.0f); + } + } + #endregion + + #region Reaction Wheels + private List reactionWheelList = new List(4); + internal ModuleReactionWheel[] moduleReactionWheel = new ModuleReactionWheel[0]; + internal float reactionWheelNetTorque = 0.0f; + internal float reactionWheelPitch = 0.0f; + internal float reactionWheelRoll = 0.0f; + internal float reactionWheelYaw = 0.0f; + internal float reactionWheelAuthority = 0.0f; + internal bool reactionWheelActive = false; + internal bool reactionWheelDamaged = false; + private void UpdateReactionWheels() + { + // wheelState == Disabled, unit is disabled. + // wheelState == Active and inputSum == 0, unit is idle + // wheelState == Active and inputSum > 0, unit is torquing. + // inputVector provides current torque demand as ( pitch, roll, yaw ) + reactionWheelNetTorque = 0.0f; + reactionWheelPitch = 0.0f; + reactionWheelRoll = 0.0f; + reactionWheelYaw = 0.0f; + reactionWheelAuthority = 0.0f; + reactionWheelActive = false; + reactionWheelDamaged = false; + + float activeWheels = 0.0f; + float activePitch = 0.0f; + float activeRoll = 0.0f; + float activeYaw = 0.0f; + + for (int i = moduleReactionWheel.Length - 1; i >= 0; --i) + { + if (moduleReactionWheel[i].wheelState == ModuleReactionWheel.WheelState.Active) + { + reactionWheelAuthority += moduleReactionWheel[i].authorityLimiter; + + if (moduleReactionWheel[i].inputVector.sqrMagnitude > 0.0f) + { + float partMaxTorque = 0.0f; + if (moduleReactionWheel[i].PitchTorque > 0.0f) + { + float torque = moduleReactionWheel[i].inputVector.x / moduleReactionWheel[i].PitchTorque; + if (Mathf.Abs(torque) > 0.0f) + { + reactionWheelPitch += torque; + activePitch += 1.0f; + partMaxTorque = Mathf.Max(partMaxTorque, Mathf.Abs(torque)); + } + } + if (moduleReactionWheel[i].RollTorque > 0.0f) + { + float torque = moduleReactionWheel[i].inputVector.y / moduleReactionWheel[i].RollTorque; + if (Mathf.Abs(torque) > 0.0f) + { + reactionWheelRoll += torque; + activeRoll += 1.0f; + partMaxTorque = Mathf.Max(partMaxTorque, Mathf.Abs(torque)); + } + } + if (moduleReactionWheel[i].YawTorque > 0.0f) + { + float torque = moduleReactionWheel[i].inputVector.z / moduleReactionWheel[i].YawTorque; + if (Mathf.Abs(torque) > 0.0f) + { + reactionWheelYaw += torque; + activeYaw += 1.0f; + partMaxTorque = Mathf.Max(partMaxTorque, Mathf.Abs(torque)); + } + } + + reactionWheelActive = true; + reactionWheelNetTorque += partMaxTorque; + activeWheels += 1.0f; + } + } + else if (moduleReactionWheel[i].wheelState == ModuleReactionWheel.WheelState.Broken) + { + reactionWheelDamaged = true; + } + } + + if (reactionWheelAuthority > 0.0f) + { + reactionWheelAuthority = reactionWheelAuthority / (100.0f * moduleReactionWheel.Length); + } + + if (activeWheels > 1.0f) + { + reactionWheelNetTorque /= activeWheels; + } + if (activePitch > 1.0f) + { + reactionWheelPitch /= activePitch; + } + if (activeRoll > 1.0f) + { + reactionWheelRoll /= activeRoll; + } + if (activeYaw > 1.0f) + { + reactionWheelYaw /= activeYaw; + } + } + #endregion + + #region Resource Converters + internal class GeneralPurposeResourceConverter + { + internal List converterList = new List(); + internal ModuleResourceConverter[] moduleConverter = new ModuleResourceConverter[0]; + internal List outputRatioList = new List(); + internal float[] outputRatio = new float[0]; + internal string outputResource = string.Empty; + internal int id; + internal float netOutput = 0.0f; + internal bool converterActive = false; + } + internal List resourceConverterList = new List(); + private void UpdateResourceConverter() + { + for (int rsrc = resourceConverterList.Count - 1; rsrc >= 0; --rsrc) + { + GeneralPurposeResourceConverter rc = resourceConverterList[rsrc]; + rc.converterActive = false; + rc.netOutput = 0.0f; + + for (int i = rc.moduleConverter.Length - 1; i >= 0; --i) + { + rc.converterActive |= (rc.moduleConverter[i].IsActivated && !rc.moduleConverter[i].AlwaysActive); + + if (rc.moduleConverter[i].IsActivated) + { + rc.netOutput += (float)rc.moduleConverter[i].lastTimeFactor * rc.outputRatio[i]; + } + } + } + } + #endregion + + #region Resource Harvestor + private List scannerList = new List(); + internal ModuleAnalysisResource[] moduleScanner = new ModuleAnalysisResource[0]; + + private List harvesterList = new List(); + internal ModuleResourceHarvester[] moduleHarvester = new ModuleResourceHarvester[0]; + + internal bool surfaceScannerInstalled; + internal double harvesterCoreTemp; + internal double activeHarvesterCoreTemp; + internal double thermalEfficiency; + internal int activeDrillCount; + + private void UpdateOreHarvesters() + { + harvesterCoreTemp = 0.0d; + activeHarvesterCoreTemp = 0.0f; + thermalEfficiency = 0.0f; + activeDrillCount = 0; + if (moduleHarvester.Length > 0) + { + for (int i = moduleHarvester.Length - 1; i >= 0; --i) + { + harvesterCoreTemp += moduleHarvester[i].GetCoreTemperature(); + if (moduleHarvester[i].IsActivated) + { + activeHarvesterCoreTemp += moduleHarvester[i].GetCoreTemperature(); + thermalEfficiency += moduleHarvester[i].ThermalEfficiency.Evaluate((float)moduleHarvester[i].GetCoreTemperature()); + activeDrillCount++; + } + //moduleHarvester[i].status //OreRate i.e. X% Load (only while running) + } + if (activeDrillCount > 0) + { + activeHarvesterCoreTemp /= activeDrillCount; + harvesterCoreTemp = activeHarvesterCoreTemp; + thermalEfficiency /= activeDrillCount; + } + else + { + harvesterCoreTemp /= moduleHarvester.Length; + } + + } + + surfaceScannerInstalled = false; + if (moduleScanner.Length > 0) + { + surfaceScannerInstalled = true; + } + } + + #endregion + + #region Science + // For a bootstrap to interpreting science values, see https://github.com/KerboKatz/AutomatedScienceSampler/blob/master/source/AutomatedScienceSampler/DefaultActivator.cs + private List scienceExperimentList = new List(); + internal ModuleScienceExperiment[] moduleScienceExperiment = new ModuleScienceExperiment[0]; + private List scienceContainerList = new List(); + internal ModuleScienceContainer[] scienceContainer = new ModuleScienceContainer[0]; + + private class ModuleScienceExperimentComparer : IComparer + { + public int Compare(ModuleScienceExperiment a, ModuleScienceExperiment b) + { + return string.Compare(a.experiment.id, b.experiment.id); + } + } + ModuleScienceExperimentComparer mseComparer = new ModuleScienceExperimentComparer(); + internal class ScienceType + { + internal ScienceExperiment type; + internal List experiments = new List(); + }; + private List scienceTypeList = new List(); + internal ScienceType[] scienceType = new ScienceType[0]; + private void RebuildScienceTypes() + { + // Sort the array. + Array.Sort(moduleScienceExperiment, mseComparer); + int numExperiments = moduleScienceExperiment.Length; + for (int i = 0; i < numExperiments; ++i) + { + int idx = scienceTypeList.FindIndex(x => x.type.id == moduleScienceExperiment[i].experiment.id); + if (idx == -1) + { + ScienceType st = new ScienceType(); + st.type = moduleScienceExperiment[i].experiment; + st.experiments.Add(moduleScienceExperiment[i]); + scienceTypeList.Add(st); + } + else + { + scienceTypeList[idx].experiments.Add(moduleScienceExperiment[i]); + } + } + + // TransferModules does not copy values if the array isn't resized. However, we + // may need to do that here. For some reason, not every experiment has a + // valid ScienceExperiment the first time this code runs. + //TransferModules(scienceTypeList, ref scienceType); + if (scienceTypeList.Count != scienceType.Length) + { + scienceType = new ScienceType[scienceTypeList.Count]; + } + for (int i = scienceTypeList.Count - 1; i >= 0; --i) + { + scienceType[i] = scienceTypeList[i]; + } + scienceTypeList.Clear(); + } + + internal class ExperimentData + { + internal CelestialBody body; + internal ExperimentSituations situation; + internal ScienceSubject subject; + internal string biomeDisplayName; + internal string experimentResults; + }; + private Dictionary processedExperimentData = new Dictionary(); + internal ExperimentData GetExperimentData(string subjectID, ScienceExperiment experiment) + { + ExperimentData ed; + if (!processedExperimentData.TryGetValue(subjectID, out ed)) + { + ed = new ExperimentData(); + + string bodyName; + string biome; + ScienceUtil.GetExperimentFieldsFromScienceID(subjectID, out bodyName, out ed.situation, out biome); + ed.body = FlightGlobals.GetBodyByName(bodyName); + ed.biomeDisplayName = ScienceUtil.GetBiomedisplayName(ed.body, biome); + ed.subject = ResearchAndDevelopment.GetExperimentSubject(experiment, ed.situation, ed.body, biome, ed.biomeDisplayName); + ed.experimentResults = ResearchAndDevelopment.GetResults(subjectID); + + processedExperimentData.Add(subjectID, ed); + } + + return ed; + } + internal ScienceData GenerateScienceData(ScienceExperiment experiment, float xmitDataScalar) + { + string biome = string.Empty; + ExperimentSituations situation = ScienceUtil.GetExperimentSituation(vessel); + if (experiment.BiomeIsRelevantWhile(situation)) + { + biome = ScienceUtil.GetExperimentBiome(vessel.mainBody, vessel.latitude, vessel.longitude); + } + + ScienceSubject subject = ResearchAndDevelopment.GetExperimentSubject(experiment, situation, vessel.mainBody, biome, null); + return new ScienceData(experiment.baseValue * subject.dataScale, xmitDataScalar, 0.0f, + subject.id, subject.title); + } + internal bool CanRunExperiment(ModuleScienceExperiment exp) + { + int requirements = exp.usageReqMaskInternal; + // ExperimentUsageReqs.Always = 0, ExperimentUsageReqs.VesselControl = 1 << 0, ExperimentUsageReqs.CrewInVessel = 1 << 1 + if (requirements == 0 || (requirements & 3) != 0) + { + return true; + } + // ExperimentUsageReqs.CrewInPart = 1 << 2 + if ((requirements & 4) != 0) + { + if (exp.part.protoModuleCrew != null && exp.part.protoModuleCrew.Count > 0) + { + return true; + } + } + // ExperimentUsageReqs.ScientistCrew = 1 << 3 + if ((requirements & 8) != 0) + { + int idx = vessel.GetVesselCrew().FindIndex(x => x.trait == KerbalRoster.scientistTrait); + if (idx >= 0) + { + return true; + } + } + + return false; + } + + #endregion + + #region Thermal Management + private List radiatorList = new List(); + internal ModuleActiveRadiator[] moduleRadiator = new ModuleActiveRadiator[0]; + private List ablatorList = new List(); + internal ModuleAblator[] moduleAblator = new ModuleAblator[0]; + private List deployableRadiatorList = new List(); + internal ModuleDeployableRadiator[] moduleDeployableRadiator = new ModuleDeployableRadiator[0]; + internal double currentEnergyTransfer; + internal double maxEnergyTransfer; + internal bool radiatorActive; + internal bool radiatorInactive; + internal bool radiatorDeployable; + internal bool radiatorRetractable; + internal int radiatorMoving; + internal bool radiatorDamaged; + internal float hottestAblator; + internal float hottestAblatorMax; + internal float hottestAblatorSign = 0.0f; + private void UpdateRadiators() + { + radiatorActive = false; + radiatorInactive = false; + radiatorDeployable = false; + radiatorRetractable = false; + radiatorMoving = 0; + radiatorDamaged = false; + maxEnergyTransfer = 0.0; + currentEnergyTransfer = 0.0; + + if (moduleAblator.Length == 0) + { + hottestAblator = 0.0f; + hottestAblatorMax = 0.0f; + hottestAblatorSign = 0.0f; + } + else + { + float currentHottestAblatorDiff = float.MaxValue; + float currentMaxAblator = 0.0f; + float currentAblator = 0.0f; + for (int i = moduleAblator.Length - 1; i >= 0; --i) + { + Part ablatorPart = moduleAblator[i].part; + if ((ablatorPart.skinMaxTemp - ablatorPart.skinTemperature) < currentHottestAblatorDiff) + { + currentHottestAblatorDiff = (float)(ablatorPart.skinMaxTemp - ablatorPart.skinTemperature); + currentMaxAblator = (float)ablatorPart.skinMaxTemp; + currentAblator = (float)ablatorPart.skinTemperature; + } + } + + if (currentHottestAblatorDiff < float.MaxValue) + { + hottestAblatorSign = Math.Sign(currentAblator - hottestAblator); + hottestAblator = currentAblator; + hottestAblatorMax = currentMaxAblator; + } + else + { + hottestAblator = 0.0f; + hottestAblatorMax = 0.0f; + hottestAblatorSign = 0.0f; + } + } + + string tempString; + for (int i = moduleRadiator.Length - 1; i >= 0; --i) + { + if (moduleRadiator[i].IsCooling) + { + radiatorActive = true; + maxEnergyTransfer += moduleRadiator[i].maxEnergyTransfer; + float xv; + // Hack: I can't coax this information out through another + // public field. + tempString = moduleRadiator[i].status.Substring(0, moduleRadiator[i].status.Length - 1); + if (float.TryParse(tempString, out xv)) + { + currentEnergyTransfer += xv * 0.01 * moduleRadiator[i].maxEnergyTransfer; + } + } + else + { + radiatorInactive = true; + } + } + + for (int i = moduleDeployableRadiator.Length - 1; i >= 0; --i) + { + if (moduleDeployableRadiator[i].useAnimation) + { + radiatorRetractable |= (moduleDeployableRadiator[i].retractable && moduleDeployableRadiator[i].deployState == ModuleDeployablePart.DeployState.EXTENDED); + radiatorDeployable |= (moduleDeployableRadiator[i].deployState == ModuleDeployablePart.DeployState.RETRACTED); + if (moduleDeployableRadiator[i].deployState == ModuleDeployablePart.DeployState.RETRACTING) + { + radiatorMoving = -1; + } + else if (moduleDeployableRadiator[i].deployState == ModuleDeployablePart.DeployState.EXTENDING) + { + radiatorMoving = 1; + } + radiatorDamaged |= (moduleDeployableRadiator[i].deployState == ModuleDeployablePart.DeployState.BROKEN); + } + } + } + #endregion + + #region Wheels + private List wheelDamageList = new List(); + internal ModuleWheels.ModuleWheelDamage[] moduleWheelDamage = new ModuleWheels.ModuleWheelDamage[0]; + private List wheelDeploymentList = new List(); + internal ModuleWheels.ModuleWheelDeployment[] moduleWheelDeployment = new ModuleWheels.ModuleWheelDeployment[0]; + private List wheelBaseList = new List(); + internal ModuleWheelBase[] moduleWheelBase = new ModuleWheelBase[0]; + internal float wheelPosition = 0.0f; + internal float wheelDirection = 0.0f; + private void UpdateGear() + { + if (moduleWheelDeployment.Length > 0) + { + float newPosition = 0.0f; + float numWheels = 0.0f; + for (int i = moduleWheelDeployment.Length - 1; i >= 0; --i) + { + if (!moduleWheelDamage[i].isDamaged) + { + numWheels += 1.0f; + + newPosition += Mathf.InverseLerp(moduleWheelDeployment[i].retractedPosition, moduleWheelDeployment[i].deployedPosition, moduleWheelDeployment[i].position); + } + } + + if (numWheels > 1.0f) + { + newPosition /= numWheels; + } + + wheelDirection = newPosition - wheelPosition; + if (wheelDirection < 0.0f) + { + wheelDirection = -1.0f; + } + else if (wheelDirection > 0.0f) + { + wheelDirection = 1.0f; + } + wheelPosition = newPosition; + } + } + #endregion + + #region Modules Management + /// + /// Mark modules as potentially invalid to force reiterating over the + /// part and module lists. + /// + internal void InvalidateModules() + { + modulesInvalidated = true; + camerasReset = true; + } + + /// + /// Helper method to transfer a list to an array without creating a new + /// array (if the existing array is the same size as the list needs). + /// + /// + /// + /// + static void TransferModules(List sourceList, ref T[] destArray) + { + if (sourceList.Count != destArray.Length) + { + destArray = new T[sourceList.Count]; + + // Assume the list can't change without changing size - + // I shouldn't be able to add a module and remove a module + // of the same type in a single transaction, right? + // For whatever reason, this copy is faster than List.CopyTo() + for (int i = sourceList.Count - 1; i >= 0; --i) + { + destArray[i] = sourceList[i]; + } + } + sourceList.Clear(); + } + +#if TIME_REBUILD + private Stopwatch rebuildStopwatch = new Stopwatch(); +#endif + + /// + /// Iterate over all of everything to update tracked modules. + /// + private void RebuildModules() + { + // Merkur: + // LOAD TIME: + // [LOG 09:41:38.560] [MASVesselComputer] Scan parts in 0.172 ms, transfer modules in 0.776 ms + // STAGING: + // [LOG 09:42:11.132] [MASVesselComputer] Scan parts in 0.044 ms, transfer modules in 0.044 ms + +#if TIME_REBUILD + rebuildStopwatch.Reset(); + rebuildStopwatch.Start(); +#endif + + activeResources.Clear(); + for (int agIndex = hasActionGroup.Length - 1; agIndex >= 0; --agIndex) + { + hasActionGroup[agIndex] = false; + } + + activeEnginesGimbal = false; + moduleGraviticEngine = null; + + // Update the lists of modules + for (int partIdx = vessel.parts.Count - 1; partIdx >= 0; --partIdx) + { + PartResourceList resources = vessel.parts[partIdx].Resources; + UpdateResourceList(resources); + + PartModuleList Modules = vessel.parts[partIdx].Modules; + for (int moduleIdx = Modules.Count - 1; moduleIdx >= 0; --moduleIdx) + { + PartModule module = Modules[moduleIdx]; + if (module.isEnabled) + { + if (module is ModuleEngines) + { + ModuleEngines engine = module as ModuleEngines; + enginesList.Add(engine); + + // This is something we only need to worry about when rebulding the list. + if (activeEnginesGimbal == false && engine.EngineIgnited && engine.isOperational && Modules.GetModule() != null) + { + activeEnginesGimbal = true; + } + // Crazy Mode controllers are a special-case engine. + if (MASIVTOL.wbiWBIGraviticEngine_t != null && MASIVTOL.wbiWBIGraviticEngine_t.IsAssignableFrom(module.GetType())) + { + if (MASIVTOL.wbiCrazyModeIsActive(module)) + { + moduleGraviticEngine = module; + } + } + } + else if (module is ModuleAblator) + { + ablatorList.Add(module as ModuleAblator); + } + else if (module is ModuleGimbal) + { + gimbalsList.Add(module as ModuleGimbal); + } + else if (module is ModuleParachute) + { + parachuteList.Add(module as ModuleParachute); + } + else if (module is ModuleAlternator) + { + ModuleAlternator alternator = module as ModuleAlternator; + for (int i = alternator.resHandler.outputResources.Count - 1; i >= 0; --i) + { + if (alternator.resHandler.outputResources[i].name == MASConfig.ElectricCharge) + { + alternatorList.Add(alternator); + break; + } + } + } + else if (module is ModuleDeployableSolarPanel) + { + solarPanelList.Add(module as ModuleDeployableSolarPanel); + } + else if (module is ModuleGenerator) + { + ModuleGenerator generator = module as ModuleGenerator; + for (int i = generator.resHandler.outputResources.Count - 1; i >= 0; --i) + { + if (generator.resHandler.outputResources[i].name == MASConfig.ElectricCharge) + { + generatorList.Add(generator); + generatorOutputList.Add((float)generator.resHandler.outputResources[i].rate); + break; + } + } + } + else if (module is ModuleResourceConverter) + { + ModuleResourceConverter gen = module as ModuleResourceConverter; + List outputs = gen.Recipe.Outputs; + int outputCount = outputs.Count; + for (int rsrc = resourceConverterList.Count - 1; rsrc >= 0; --rsrc) + { + int rrIdx = outputs.FindIndex(x => x.ResourceName == resourceConverterList[rsrc].outputResource); + if (rrIdx > -1) + { + resourceConverterList[rsrc].converterList.Add(gen); + resourceConverterList[rsrc].outputRatioList.Add((float)outputs[rrIdx].Ratio); + } + } + } + else if (module is ModuleResourceHarvester) + { + harvesterList.Add(module as ModuleResourceHarvester); + } + else if (module is ModuleAnalysisResource) + { + scannerList.Add(module as ModuleAnalysisResource); + } + else if (module is ModuleDeployableAntenna) + { + antennaList.Add(module as ModuleDeployableAntenna); + } + else if (module is MASRadar) + { + radarList.Add(module as MASRadar); + } + else if (module is ModuleDeployableRadiator) + { + deployableRadiatorList.Add(module as ModuleDeployableRadiator); + } + else if (module is ModuleActiveRadiator) + { + radiatorList.Add(module as ModuleActiveRadiator); + } + else if (module is ModuleRCS) + { + rcsList.Add(module as ModuleRCS); + } + else if (module is ModuleReactionWheel) + { + reactionWheelList.Add(module as ModuleReactionWheel); + } + else if (MASIParachute.realChuteFound && module.GetType() == MASIParachute.rcAPI_t) + { + realchuteList.Add(module); + } + else if (module is MASCamera) + { + // Do not add broken cameras to the control list. + if ((module as MASCamera).IsDamaged() == false) + { + cameraList.Add(module as MASCamera); + } + } + else if (module is ModuleProceduralFairing) + { + proceduralFairingList.Add(module as ModuleProceduralFairing); + } + else if (module is MASThrustReverser) + { + thrustReverserList.Add(module as MASThrustReverser); + } + else if (module is MASIdEngine) + { + MASIdEngine idE = module as MASIdEngine; + if (idE.partId > 0 && idEnginesList.FindIndex(x => x.partId == idE.partId) == -1) + { + idEnginesList.Add(idE); + } + } + else if (module is ModuleWheelBase) + { + wheelBaseList.Add(module as ModuleWheelBase); + } + else if (module is ModuleWheels.ModuleWheelDamage) + { + wheelDamageList.Add(module as ModuleWheels.ModuleWheelDamage); + } + else if (module is ModuleWheels.ModuleWheelDeployment) + { + wheelDeploymentList.Add(module as ModuleWheels.ModuleWheelDeployment); + } + else if (module is ModuleCargoBay) + { + ModuleCargoBay cb = module as ModuleCargoBay; + if (Modules[cb.DeployModuleIndex] is ModuleAnimateGeneric) + { + // ModuleCargoBay has been used with one of 3 deploy modules: + // 1) ModuleProceduralFairing - we don't count these, since they're already covered by fairing controls. + // 2) ModuleServiceModule - KSP 1.4.x, used so far only in the expansion. Doesn't appear to have a way + // to control the service module deployment outside of staging. + // 3) ModuleAnimateGeneric - we can control these. + cargoBayList.Add(cb); + } + } + else if (module is MultiModeEngine) + { + multiModeEngineList.Add(module as MultiModeEngine); + } + else if (module is MASIdEngineGroup) + { + var group = module as MASIdEngineGroup; + if (group.partId != 0 && group.engine != null) + { + engineGroupList.Add(group); + } + } + else if (module is ModuleWheels.ModuleWheelBrakes) + { + brakesList.Add(module as ModuleWheels.ModuleWheelBrakes); + } + else if (module is ModuleResourceIntake) + { + resourceIntakeList.Add(module as ModuleResourceIntake); + } + else if (module is LaunchClamp) + { + launchClampList.Add(module as LaunchClamp); + } + else if (module is ModuleScienceExperiment) + { + scienceExperimentList.Add(module as ModuleScienceExperiment); + } + else if (module is ModuleScienceContainer) + { + scienceContainerList.Add(module as ModuleScienceContainer); + } + else if (module is ModuleAeroSurface) + { + airBrakeList.Add(module as ModuleAeroSurface); + } + else if (module is ModuleColorChanger) + { + ModuleColorChanger cc = module as ModuleColorChanger; + if (cc.toggleInFlight && cc.toggleAction) + { + colorChangerList.Add(cc); + } + } + else if (module is ModuleDataTransmitter) + { + ModuleDataTransmitter mdt = module as ModuleDataTransmitter; + // We currently only care about transmitters that can send science. + if (mdt.CanTransmit()) + { + transmitterList.Add(mdt); + } + } + + foreach (BaseAction ba in module.Actions) + { + if (ba.actionGroup != KSPActionGroup.None) + { + SetActionGroup(ba.actionGroup); + } + } + } + } + + // While we're here, update active resources + if (vessel.parts[partIdx].inverseStage >= vessel.currentStage) + { + activeResources.UnionWith(vessel.parts[partIdx].crossfeedPartSet.GetParts()); + } + } +#if TIME_REBUILD + TimeSpan scanTime = rebuildStopwatch.Elapsed; +#endif + + // Rebuild the part set. + if (partSet == null) + { + partSet = new PartSet(activeResources); + } + else + { + partSet.RebuildParts(activeResources); + } + + idEnginesList.Sort((a, b) => { return a.partId - b.partId; }); + + // Transfer the modules to an array, since the array is cheaper to + // iterate over, and we're going to be iterating over it a lot. + TransferModules(enginesList, ref moduleEngines); + if (invMaxISP.Length != moduleEngines.Length) + { + invMaxISP = new float[moduleEngines.Length]; + } + for (int i = moduleEngines.Length - 1; i >= 0; --i) + { + // MOARdV TODO: This ignores the velocity ISP curve of jets. + float maxIsp, minIsp; + moduleEngines[i].atmosphereCurve.FindMinMaxValue(out minIsp, out maxIsp); + invMaxISP[i] = 1.0f / maxIsp; + } + + TransferModules(airBrakeList, ref moduleAirBrake); + TransferModules(alternatorList, ref moduleAlternator); + TransferModules(ablatorList, ref moduleAblator); + TransferModules(cargoBayList, ref moduleCargoBay); + TransferModules(colorChangerList, ref moduleColorChanger); + TransferModules(transmitterList, ref moduleTransmitter); + TransferModules(antennaList, ref moduleAntenna); + TransferModules(deployableRadiatorList, ref moduleDeployableRadiator); + TransferModules(engineGroupList, ref engineGroup); + TransferModules(idEnginesList, ref moduleIdEngines); + TransferModules(generatorList, ref moduleGenerator); + TransferModules(generatorOutputList, ref generatorOutput); + TransferModules(gimbalsList, ref moduleGimbals); + TransferModules(multiModeEngineList, ref multiModeEngines); + TransferModules(parachuteList, ref moduleParachute); + TransferModules(radarList, ref moduleRadar); + TransferModules(radiatorList, ref moduleRadiator); + TransferModules(rcsList, ref moduleRcs); + TransferModules(realchuteList, ref moduleRealChute); + TransferModules(reactionWheelList, ref moduleReactionWheel); + TransferModules(resourceIntakeList, ref moduleResourceIntake); + TransferModules(solarPanelList, ref moduleSolarPanel); + TransferModules(cameraList, ref moduleCamera); + TransferModules(proceduralFairingList, ref moduleProceduralFairing); + TransferModules(thrustReverserList, ref moduleThrustReverser); + TransferModules(wheelDamageList, ref moduleWheelDamage); + TransferModules(wheelDeploymentList, ref moduleWheelDeployment); + TransferModules(wheelBaseList, ref moduleWheelBase); + TransferModules(brakesList, ref moduleBrakes); + TransferModules(scienceExperimentList, ref moduleScienceExperiment); + TransferModules(scienceContainerList, ref scienceContainer); + TransferModules(launchClampList, ref moduleLaunchClamp); + + for (int i = resourceConverterList.Count - 1; i >= 0; --i) + { + TransferModules(resourceConverterList[i].converterList, ref resourceConverterList[i].moduleConverter); + TransferModules(resourceConverterList[i].outputRatioList, ref resourceConverterList[i].outputRatio); + } + TransferModules(harvesterList, ref moduleHarvester); + TransferModules(scannerList, ref moduleScanner); + RebuildScienceTypes(); + +#if TIME_REBUILD + TimeSpan transferTime = rebuildStopwatch.Elapsed - scanTime; + + Utility.LogMessage(this, "Scan parts in {0:0.000} ms, transfer modules in {1:0.000} ms", + ((double)scanTime.Ticks) / ((double)TimeSpan.TicksPerMillisecond), + ((double)transferTime.Ticks) / ((double)TimeSpan.TicksPerMillisecond)); +#endif + UpdateDockingNode(vessel.GetReferenceTransformPart()); + } + +#if TIME_UPDATES + private Stopwatch updateStopwatch = new Stopwatch(); +#endif + /// + /// Update per-module data after refreshing the module lists, if needed. + /// + private void UpdateModuleData() + { + if (modulesInvalidated) + { + InitRebuildPartResources(); + + RebuildModules(); + + modulesInvalidated = false; + scienceInvalidated = false; + } + if (scienceInvalidated) + { + RebuildScienceTypes(); + scienceInvalidated = false; + } + +#if TIME_UPDATES + updateStopwatch.Reset(); + updateStopwatch.Start(); +#endif + bool requestReset = false; + UpdateAntenna(); + UpdateCamera(); + UpdateCargoBay(); + UpdateDockingNodeState(); + requestReset |= UpdateEngines(); + UpdateGimbals(); + UpdatePower(); + UpdateProceduralFairing(); + UpdateRadiators(); + UpdateGear(); + UpdateRcs(); + UpdateReactionWheels(); + UpdateResourceConverter(); + UpdateOreHarvesters(); + + if (requestReset) + { + InvalidateModules(); + } +#if TIME_UPDATES + TimeSpan updateTime = updateStopwatch.Elapsed; + + Utility.LogMessage(this, "UpdateModuleData in {0:0.000} ms", + ((double)updateTime.Ticks) / ((double)TimeSpan.TicksPerMillisecond)); +#endif + } + #endregion + } +} diff --git a/Source/ApproachSolverBW.cs b/Source/ApproachSolverBW.cs index 358953e3..f69e6e8b 100644 --- a/Source/ApproachSolverBW.cs +++ b/Source/ApproachSolverBW.cs @@ -346,14 +346,22 @@ internal void SolveOrbitIntercept(Orbit vesselOrbit, Orbit targetOrbit) //Utility.LogMessage(this, "target1: {0:0} to {1:0}, transitions = {3} / {2}", target1.StartUT, target1.EndUT, target1.patchEndTransition, target1.patchStartTransition); //Utility.LogMessage(this, "target2: {0:0} to {1:0}, transitions = {3} / {2}", target2.StartUT, target2.EndUT, target2.patchEndTransition, target2.patchStartTransition); - while (target1 != target2) + while (target1 != target2 && !Double.IsInfinity(vessel.EndUT)) { + + Utility.LogInfo(this, "ApproachSolver first FindClosest: vessel {0}, target1 {1}, target1.StartUT {2}, target1.EndUT{3}, closestDistance{4}, closestUT{5}", + vessel, target1, target1.StartUT, target1.EndUT, closestDistance, closestUT); FindClosest(vessel, target1, Math.Max(now, target1.StartUT), target1.EndUT, 0, ref closestDistance, ref closestUT); + target1 = target1.nextPatch; } - FindClosest(vessel, target1, now, then, 0, ref closestDistance, ref closestUT); - + Utility.LogInfo(this, "ApproachSolver second FindClosest: vessel {0}, target1 {1}, now {2}, then {3}, closestDistance{4}, closestUT{5}", + vessel, target1, now, then, closestDistance, closestUT); + if (!Double.IsInfinity(then)) + { + FindClosest(vessel, target1, now, then, 0, ref closestDistance, ref closestUT); + } vessel = vessel.nextPatch; } @@ -369,14 +377,23 @@ internal void SolveOrbitIntercept(Orbit vesselOrbit, Orbit targetOrbit) target1 = FindOrbit(targetOrbit, now); target2 = FindOrbit(target1, then); - while (target1 != target2) + while (target1 != target2 && !Double.IsInfinity(vessel.EndUT)) { + Utility.LogInfo(this, "ApproachSolver third FindClosest: vessel {0}, target1 {1}, target1.StartUT {2}, target1.EndUT{3}, closestDistance{4}, closestUT{5}", + vessel, target1, target1.StartUT, target1.EndUT, closestDistance, closestUT); FindClosest(vessel, target1, Math.Max(now, target1.StartUT), target1.EndUT, 0, ref closestDistance, ref closestUT); + target1 = target1.nextPatch; } - FindClosest(vessel, target1, now, then, 0, ref closestDistance, ref closestUT); - now = then; + if (!Double.IsInfinity(then) && !Double.IsInfinity(now)) + { + Utility.LogInfo(this, "ApproachSolver fourth FindClosest: vessel {0}, target1 {1}, now {2}, then {3}, closestDistance{4}, closestUT{5}", + vessel, target1, now, then, closestDistance, closestUT); + FindClosest(vessel, target1, now, then, 0, ref closestDistance, ref closestUT); + } + + now = then; then += vessel.period; } diff --git a/Source/MASComponentAudioPlayer.cs b/Source/MASComponentAudioPlayer.cs index 55e5b4b8..c211a5bf 100644 --- a/Source/MASComponentAudioPlayer.cs +++ b/Source/MASComponentAudioPlayer.cs @@ -153,7 +153,7 @@ internal MASComponentAudioPlayer(ConfigNode config, InternalProp prop, MASFlight } variableName = variableName.Trim(); - audioSource.mute = (CameraManager.Instance.currentCameraMode != CameraManager.CameraMode.IVA); + audioSource.mute = ((CameraManager.Instance.currentCameraMode != CameraManager.CameraMode.IVA) && (CameraManager.Instance.currentCameraMode != CameraManager.CameraMode.Internal)); GameEvents.OnCameraChange.Add(OnCameraChange); @@ -182,7 +182,14 @@ internal MASComponentAudioPlayer(ConfigNode config, InternalProp prop, MASFlight /// private void OnCameraChange(CameraManager.CameraMode newCameraMode) { - audioSource.mute = (newCameraMode != CameraManager.CameraMode.IVA); + try + { + audioSource.mute = ((CameraManager.Instance.currentCameraMode != CameraManager.CameraMode.IVA) && (CameraManager.Instance.currentCameraMode != CameraManager.CameraMode.Internal)); + } + catch (Exception e) + { + throw new ArgumentException("Error in AudioPlayer OnCameraChange: \"" + e.Source + e.TargetSite + e.Data + e.StackTrace + CameraManager.Instance.currentCameraMode + "\"", e); + } } /// diff --git a/Source/MASFlightComputer.cs b/Source/MASFlightComputer.cs index 2fbf9bfd..e1b1c972 100644 --- a/Source/MASFlightComputer.cs +++ b/Source/MASFlightComputer.cs @@ -1825,6 +1825,10 @@ internal bool PlayAudio(string clipName, float volume, bool stopCurrent) /// internal Kerbal FindCurrentKerbal() { + if (vessel.GetCrewCount() == 0) + { + return null; + } Kerbal activeKerbal = CameraManager.Instance.IVACameraActiveKerbal; if (activeKerbal.InPart == part) { diff --git a/Source/MASFlightComputerProxy.cs b/Source/MASFlightComputerProxy.cs index 6504a293..27df2055 100644 --- a/Source/MASFlightComputerProxy.cs +++ b/Source/MASFlightComputerProxy.cs @@ -243,7 +243,14 @@ private void UpdateNeighboringVessels() localVessels.Clear(); distanceComparer.vesselPosition = vessel.GetTransform().position; - Array.Sort(neighboringVessels, distanceComparer); + try + { + Array.Sort(neighboringVessels, distanceComparer); + } + catch (Exception e) + { + throw new ArgumentException("Error in UpdateNeighboringVessels due to distanceComparer: \"" + e.Source + e.TargetSite + e.Data + e.StackTrace + neighboringVessels + "\"", e); + } neighboringVesselsCurrent = true; } diff --git a/Source/MASFlightComputerProxy2.cs b/Source/MASFlightComputerProxy2.cs index 8f809676..f65d4cb3 100644 --- a/Source/MASFlightComputerProxy2.cs +++ b/Source/MASFlightComputerProxy2.cs @@ -280,6 +280,24 @@ public double ClearManeuverNode() return 0.0; } + /// + /// Clear first scheduled maneuver node. + /// + /// 1 if any nodes were cleared, 0 if no nodes were cleared. + public double ClearOneManeuverNode() + { + if (vessel.patchedConicSolver != null) + { + int nodeCount = vessel.patchedConicSolver.maneuverNodes.Count; + // TODO: what is vessel.patchedConicSolver.flightPlan? And do I care? + vessel.patchedConicSolver.maneuverNodes[0].RemoveSelf(); + + return (nodeCount > 0) ? 1.0 : 0.0; + } + + return 0.0; + } + /// /// Returns the apoapsis of the orbit that results from the scheduled maneuver. /// @@ -963,6 +981,8 @@ public double SafeModulo(double numerator, double denominator) } } + + #endregion /// diff --git a/Source/MASIAtmosphereAutopilot.cs b/Source/MASIAtmosphereAutopilot.cs new file mode 100644 index 00000000..97d9b1e6 --- /dev/null +++ b/Source/MASIAtmosphereAutopilot.cs @@ -0,0 +1,214 @@ +/***************************************************************************** + * The MIT License (MIT) + * + * Copyright (c) 2021 Sovetskysoyuz + * based on code by MoarDV + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + ****************************************************************************/ +using MoonSharp.Interpreter; +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Text; + +namespace AvionicsSystems +{ + /// + /// MASIAtmosphereAutopilot is the interface with the Atmosphere Autopilot mod. + /// + /// atmosphereAutopilot + /// + /// The MASIAtmosphereAutopilot class provides an interface between the Atmosphere Autopilot mod + /// and Avionics Systems. It provides two informational variables and one + /// action. + /// + internal class MASIAtmosphereAutopilot + { + static private bool aaFound = false; + + // basic mod control functions + static private Type aaAPI_t; + // control over which controller is active + static private Type aaTopModuleAPI_t; + // static private MethodInfo txMethod_t; + // static private MethodInfo rxMethod_t; + // static private MethodInfo chatterMethod_t; + // replace these with MethodInfo for the requisite AA methods + static private MethodInfo activateAutopilot_t; + + // internal Func chattererTx; + // internal Func chattererRx; + // internal Action chattererStartTalking; + // Figure out what these need to be replaced with + internal Action aaActivateAutopilot; + + [MoonSharpHidden] + public MASIAtmosphereAutopilot() + { + InitAtmosphereAutopilotMethods(); + } + + ~MASIAtmosphereAutopilot() + { + } + + private void InitAtmosphereAutopilotMethods() + { + if (!aaFound) + { + return; + } + + object atmosphereAutopilot = UnityEngine.Object.FindObjectOfType(aaAPI_t); + object topModuleManager = UnityEngine.Object.FindObjectOfType(aaTopModuleAPI_t); + + // chattererTx = (Func)Delegate.CreateDelegate(typeof(Func), chatterer, txMethod_t); + // chattererRx = (Func)Delegate.CreateDelegate(typeof(Func), chatterer, rxMethod_t); + // chattererStartTalking = (Action)Delegate.CreateDelegate(typeof(Action), chatterer, chatterMethod_t); + // replace these with whatever functions wound up being defined above. + + aaActivateAutopilot = (Action)Delegate.CreateDelegate(typeof(Action), topModuleManager, activateAutopilot_t); + } + + /// + /// The AtmosphereAutopilot category provides the interface with the AtmosphereAutopilot mod (when installed). + /// + #region AtmosphereAutopilot + + [MASProxyAttribute(Immutable = true)] + /// + /// Reports whether the AtmosphereAutopilot mod is installed. + /// + /// 1 if AtmosphereAutopilot is installed, 0 otherwise + public double Available() + { + return (aaFound) ? 1.0 : 0.0; + } + + /// + /// Reports on whether or not Mission Control is communicating with the capsule. + /// + /// 1 if ground control is talking, 0 otherwise. + // public double Receiving() + // { + // if (chattererFound) + // { + // return (chattererRx()) ? 1.0 : 0.0; + // } + // else + // { + // return 0.0; + // } + // } + + /// + /// If the comm channel is idle, start a chatter sequence. If there is + /// already an exchange active, do nothing. + /// + /// 1 if Chatterer starts transmitting; 0 otherwise. + // public double StartTalking() + // { + // if (chattererFound) + // { + // if (!chattererTx() && !chattererRx()) + // { + // chattererStartTalking(); + // return 1.0; + // } + // } + + // return 0.0; + // } + + public double AAActivateAutopilot() + { + if(aaFound) + { + aaActivateAutopilot(); + return 1.0; + } + + return 0.0; + } + + /// + /// Reports whether or not the vessel is transmitting to Mission Control. + /// + /// 1 if the vessel is transmitting, 0 otherwise. + // public double Transmitting() + // { + // if (chattererFound) + // { + // return (chattererTx()) ? 1.0 : 0.0; + // } + // else + // { + // return 0.0; + // } + // } + #endregion + + [MoonSharpHidden] + internal void UpdateVessel() + { + InitAtmosphereAutopilotMethods(); + } + + #region Reflection Configuration + static MASIAtmosphereAutopilot() + { + aaFound = false; + aaAPI_t = Utility.GetExportedType("AtmosphereAutopilot", "AtmosphereAutopilot.AtmosphereAutopilot"); + aaTopModuleAPI_t = Utility.GetExportedType("AtmosphereAutopilot", "AtmosphereAutopilot.TopModuleManager"); + if(aaAPI_t != null) + { + // txMethod_t = chattererAPI_t.GetMethod("VesselIsTransmitting", BindingFlags.Instance | BindingFlags.Public); + // if (txMethod_t == null) + // { + // throw new NotImplementedException("txMethod_t"); + // } + + // rxMethod_t = chattererAPI_t.GetMethod("VesselIsReceiving", BindingFlags.Instance | BindingFlags.Public); + // if (rxMethod_t == null) + // { + // throw new NotImplementedException("rxMethod_t"); + // } + + // chatterMethod_t = chattererAPI_t.GetMethod("InitiateChatter", BindingFlags.Instance | BindingFlags.Public); + // if (chatterMethod_t == null) + // { + // throw new NotImplementedException("chatterMethod_t"); + // } + + aaFound = true; + } + if(aaTopModuleAPI_t != null) + { + activateAutopilot_t = aaTopModuleAPI_t.GetMethod("activateAutopilot", BindingFlags.Instance | BindingFlags.Public); + if (activateAutopilot_t == null) + { + throw new NotImplementedException("activateAutopilot_t"); + } + } + } + #endregion + } +} diff --git a/Source/MASIKAC.cs b/Source/MASIKAC.cs index cf5037e6..115aa547 100644 --- a/Source/MASIKAC.cs +++ b/Source/MASIKAC.cs @@ -186,6 +186,42 @@ public string CreateAlarm(string name, double UT) return string.Empty; } + /// + /// Create an alarm of specified at the time specified by `UT`, using the name `name`. This alarm is + /// assigned to the current vessel ID. + /// + /// The type of alarm to create." + /// The short name to apply to the alarm. + /// The UT when the alarm should fire. + /// The alarm ID (a string), or an empty string if the method failed. + public string CreateTypeAlarm(string alarmTypeStr, string name, double UT) + { + if (KACWrapper.InstanceExists) + { + KACWrapper.KACAPI.AlarmTypeEnum alarmType = (KACWrapper.KACAPI.AlarmTypeEnum)Enum.Parse(typeof(KACWrapper.KACAPI.AlarmTypeEnum), alarmTypeStr); + + string alarmID = KACWrapper.KAC.CreateAlarm(alarmType, name, UT); + + if (string.IsNullOrEmpty(alarmID)) + { + return string.Empty; + } + else + { + var newAlarm = KACWrapper.KAC.Alarms.Find(x => x.ID == alarmID); + if (newAlarm != null) + { + newAlarm.VesselID = vessel.id.ToString(); + } + + return alarmID; + } + + } + + return string.Empty; + } + /// /// Attempts to remove the alarm identified by `alarmID`. Normally, `alarmID` is the return /// value from `fc.CreateAlarm()`. diff --git a/Source/MASITransfer.cs b/Source/MASITransfer.cs index 31a6264b..09128139 100644 --- a/Source/MASITransfer.cs +++ b/Source/MASITransfer.cs @@ -732,6 +732,159 @@ public double CircularizeAltitude(double newAltitude) return 0.0; } + /// + /// Circularize the vessel's orbit at the specified altitude, in meters. This new altitude + /// must be between the current periapsis and the SoI limit. If the orbit is hyperbolic and the craft + /// is past the periapsis, the altitude must also be greater than the current altitude. + /// + /// The altitude at which the orbit will be circularized, in meters. + /// 1 if a node was created, 0 otherwise. + public double CircularizeAltitudeHyp(double newAltitude) + { + Orbit current = vessel.orbit; + double newSMA = newAltitude + current.referenceBody.Radius; + + Utility.LogMessage(this, "newSMA = {0} because {1} + {2}", newSMA, newAltitude, current.referenceBody.Radius); + + if ((newSMA >= current.PeR && vc.orbit.timeToPe >= 0 || newSMA >= vessel.altitude) && newSMA <= vessel.mainBody.sphereOfInfluence && vessel.patchedConicSolver != null) + { + CelestialBody referenceBody = current.referenceBody; + double vNew = Math.Sqrt(referenceBody.gravParameter / newSMA); + double tAAtNewSMA = current.TrueAnomalyAtRadius(newSMA); + //double maneuverUt = Planetarium.GetUniversalTime() + current.GetUTforTrueAnomaly(tAAtNewSMA, 0); + double maneuverUt = current.GetUTforTrueAnomaly(tAAtNewSMA, 0); + + Utility.LogMessage(this, "maneuverUt = {0} because {1}", maneuverUt, current.GetUTforTrueAnomaly(tAAtNewSMA, 0)); + + Vector3d velAtUt = current.getOrbitalVelocityAtUT(maneuverUt); + + Utility.LogMessage(this, "velAtUt = {0} ", velAtUt); + + Vector3d relativePosition = current.getRelativePositionAtUT(maneuverUt); + + Utility.LogMessage(this, "relativePosition = {0}", relativePosition); + + Vector3d prograde; + Vector3d normal; + Vector3d radial; + Utility.GetOrbitalBasisVectors(velAtUt, relativePosition, out prograde, out radial, out normal); + + Utility.LogMessage(this, "maneuverUt = {0} because {1} - {2}", maneuverUt, Planetarium.GetUniversalTime(), current.GetUTforTrueAnomaly(tAAtNewSMA, 0)); + + Vector3d upAtUt = relativePosition.xzy.normalized; + Vector3d fwdAtUt = Vector3d.Cross(upAtUt, normal); + Vector3d maneuverVel = fwdAtUt * vNew; + + Utility.LogMessage(this, "maneuverVel = {0} because {1} - {2}", maneuverVel, upAtUt, fwdAtUt); + + Vector3d deltaV = maneuverVel - velAtUt.xzy; + Utility.LogMessage(this, "dV = {0} because {1} - {2}", deltaV, maneuverVel, velAtUt); + Utility.LogMessage(this, "prograde (dot) fwd = {0:0.000}", Vector3d.Dot(prograde, fwdAtUt)); + + Vector3d maneuverdV = Utility.ManeuverNode(Vector3d.Dot(deltaV, prograde), Vector3d.Dot(deltaV, normal), Vector3d.Dot(deltaV, radial)); + + vessel.patchedConicSolver.maneuverNodes.Clear(); + ManeuverNode mn = vessel.patchedConicSolver.AddManeuverNode(maneuverUt); + mn.OnGizmoUpdated(maneuverdV, maneuverUt); + + //Vector3d posAtUt = current.getRelativePositionAtUT(maneuverUt); + //oUpper.UpdateFromStateVectors(posAtUt, maneuverVel.xzy, referenceBody, maneuverUt); + //Utility.LogMessage(this, "Circularize at {0:0.000}km: {1:0.000} x {2:0.000} @ {3:0.0}", + // newAltitude * 0.001, + // oUpper.ApA * 0.001, oUpper.PeA * 0.001, + // oUpper.inclination); + + return 1.0; + } + + return 0.0; + } + + /// + /// Circularize the vessel's orbit at the specified altitude, in meters. This new altitude + /// must be between the current periapsis and the SoI limit. If the orbit is hyperbolic and the craft + /// is past the periapsis, the altitude must also be greater than the current altitude. This method + /// uses calculations derived from first principles rather than the KSP orbit functions. + /// + /// The altitude at which the orbit will be circularized, in meters. + /// 1 if a node was created, 0 otherwise. + public double CircularizeAltitudeHypVis(double newAltitude) + { + Orbit current = vessel.orbit; + double newSMA = newAltitude + current.referenceBody.Radius; + + Utility.LogMessage(this, "newSMA = {0} because {1} + {2}", newSMA, newAltitude, current.referenceBody.Radius); + + if ((newSMA >= current.PeR && vc.orbit.timeToPe >= 0 || newSMA >= vessel.altitude) && newSMA <= vessel.mainBody.sphereOfInfluence && vessel.patchedConicSolver != null) + { + CelestialBody referenceBody = current.referenceBody; + double vNew = Math.Sqrt(referenceBody.gravParameter / newSMA); + double tAAtNewSMA = current.TrueAnomalyAtRadius(newSMA); + //double maneuverUt = Planetarium.GetUniversalTime() + current.GetUTforTrueAnomaly(tAAtNewSMA, 0); + double maneuverUt = current.GetUTforTrueAnomaly(tAAtNewSMA, 0); + + Utility.LogMessage(this, "maneuverUt = {0} because {1}", maneuverUt, current.GetUTforTrueAnomaly(tAAtNewSMA, 0)); + + double vPreBurn = Math.Sqrt(referenceBody.gravParameter * (2 / newSMA - 1 / current.semiMajorAxis)); + + Utility.LogMessage(this, "v before burn = {0} ", vPreBurn); + + /* double spAngMom = current.PeR * current.getOrbitalSpeedAt(current.GetTimeToPeriapsis() + Planetarium.GetUniversalTime()); + + Utility.LogMessage(this, "specific angular momentum = {0} PeR = {1} time to PE = {2} UT at PE = {3} speed at PE = {4}", + spAngMom, current.PeR, current.GetTimeToPeriapsis(), current.GetTimeToPeriapsis() + Planetarium.GetUniversalTime(), + current.getOrbitalSpeedAt(current.GetTimeToPeriapsis() + Planetarium.GetUniversalTime()));*/ + + double spAngMom = current.PeR * current.getOrbitalSpeedAtDistance(current.PeR); + + Utility.LogMessage(this, "specific angular momentum = {0} PeR = {1} PeA = {2} Speed at PeR = {3} speed at PeA = {4}", + spAngMom, current.PeR, current.PeA, current.getOrbitalSpeedAtDistance(current.PeR), current.getOrbitalSpeedAtDistance(current.PeA)); + + double vPreBurnCirc = spAngMom / newSMA; + + Utility.LogMessage(this, "preburn circumferential velocity = {0}", vPreBurnCirc); + + double elevationAngle = Math.Acos(vPreBurnCirc / vPreBurn); + + Utility.LogMessage(this, "Elevation angle = {0}", elevationAngle); + + double deltaVTotal = Math.Sqrt(Math.Pow(vNew, 2) + Math.Pow(vPreBurn, 2) - 2 * vNew * vPreBurn * Math.Cos(elevationAngle)); + + Utility.LogMessage(this, "deltaVTotal = {0}", deltaVTotal); + + /*double angleOffRetrograde = Math.Asin(vNew / vPreBurn); + + Utility.LogMessage(this, "angle off retrograde = {0}", angleOffRetrograde);*/ + + /*double deltaVPrograde = -deltaVTotal * Math.Cos(angleOffRetrograde); + double deltaVRadial = -deltaVTotal * Math.Sin(angleOffRetrograde);*/ + + double deltaVPrograde = -(vPreBurn - vNew * Math.Cos(elevationAngle)); + double deltaVRadial = -vNew * Math.Sin(elevationAngle); + + Utility.LogMessage(this, "prograde = {0}, radial = {1}", deltaVPrograde, deltaVRadial); + + Vector3d maneuverdV = Utility.ManeuverNode(deltaVPrograde, 0, deltaVRadial); + + Utility.LogMessage(this, "maneuverdV = {0}", maneuverdV); + + vessel.patchedConicSolver.maneuverNodes.Clear(); + ManeuverNode mn = vessel.patchedConicSolver.AddManeuverNode(maneuverUt); + mn.OnGizmoUpdated(maneuverdV, maneuverUt); + + //Vector3d posAtUt = current.getRelativePositionAtUT(maneuverUt); + //oUpper.UpdateFromStateVectors(posAtUt, maneuverVel.xzy, referenceBody, maneuverUt); + //Utility.LogMessage(this, "Circularize at {0:0.000}km: {1:0.000} x {2:0.000} @ {3:0.0}", + // newAltitude * 0.001, + // oUpper.ApA * 0.001, oUpper.PeA * 0.001, + // oUpper.inclination); + + return 1.0; + } + + return 0.0; + } + /// /// Generate a maneuver to conduct a Hohmann transfer to the current target. If there is no /// target, or the transfer is not a simple transfer to a nearly co-planar target, nothing happens. diff --git a/Source/MASVesselComputer.cs b/Source/MASVesselComputer.cs index 0341bc5b..ea792ed1 100644 --- a/Source/MASVesselComputer.cs +++ b/Source/MASVesselComputer.cs @@ -124,9 +124,17 @@ private void RefreshData() PrepareResourceData(); UpdateModuleData(); - UpdateAttitude(); + try + { + UpdateAttitude(); + } + catch (Exception e) + { + throw new ArgumentException("Error in UpdateAttitude:" + e.Source + e.TargetSite + e.Data + e.StackTrace, e); + } UpdateAltitudes(); UpdateManeuverNode(); + UpdateOwnDockingPorts(); UpdateTarget(); UpdateMisc(); // Last step: @@ -308,7 +316,8 @@ public void Awake() InitResourceData(); UpdateReferenceTransform(vessel.GetReferenceTransformPart(), true); - vesselCrewed = (vessel.GetCrewCount() > 0); + //vesselCrewed = (vessel.GetCrewCount() > 0); + vesselCrewed = true; vesselActive = ActiveVessel(vessel); if (vesselCrewed) { @@ -362,6 +371,60 @@ private static bool ActiveVessel(Vessel vessel) return vessel.isActiveVessel && (CameraManager.Instance.currentCameraMode == CameraManager.CameraMode.IVA || CameraManager.Instance.currentCameraMode == CameraManager.CameraMode.Internal); } + internal ModuleDockingNode[] ownDockingPorts = new ModuleDockingNode[0]; + + private void UpdateOwnDockingPorts() + { + //Vessel targetVessel = (targetType == TargetType.Vessel) ? (activeTarget as Vessel) : (activeTarget as ModuleDockingNode).vessel; + //if (!targetVessel.packed && targetVessel.loaded) + //{ + List potentialOwnDocks = vessel.FindPartModulesImplementing(); + List validOwnDocks = new List(); + + if (dockingNode != null) + { + for (int i = potentialOwnDocks.Count - 1; i >= 0; --i) + { + ModuleDockingNode checkDock = potentialOwnDocks[i]; + // Only lock on to an available dock of the same type that is either ungendered or the opposite gender. + //if (otherDock.state == "Ready" && (string.IsNullOrEmpty(dockingNode.nodeType) || dockingNode.nodeType == otherDock.nodeType) && (dockingNode.gendered == false || dockingNode.genderFemale != otherDock.genderFemale)) + if (checkDock.state == "Ready") + { + validOwnDocks.Add(checkDock); + } + } + } + /* else + { + for (int i = potentialOwnDocks.Count - 1; i >= 0; --i) + { + ModuleDockingNode otherDock = potentialOwnDocks[i]; + // Only lock on to an available dock of the same type that is either ungendered or the opposite gender. + if (otherDock.state == "Ready") + { + validOwnDocks.Add(otherDock); + } + } + }*/ + if (ownDockingPorts.Length != validOwnDocks.Count) + { + ownDockingPorts = validOwnDocks.ToArray(); + } + else + { + for (int i = ownDockingPorts.Length - 1; i >= 0; --i) + { + ownDockingPorts[i] = validOwnDocks[i]; + } + } + //} + + //else if ((targetVessel.packed || !targetVessel.loaded) && targetDockingPorts.Length > 0) + //{ + // targetDockingPorts = new ModuleDockingNode[0]; + //} + } + // Time in seconds until impact. 0 if there is no impact. private double timeToImpact_; internal double timeToImpact @@ -626,14 +689,39 @@ internal double GetSlopeAngle() void UpdateAttitude() { + if (vessel.GetReferenceTransformPart() == null) + { + /*try + { + UpdateReferenceTransform(vessel.GetReferenceTransformPart(), true); + } + catch (Exception e) + { + throw new ArgumentException("Error in UpdateReferenceTransform in UpdateAttitude: \"" + e.Source + " " + e.TargetSite + " " + e.Data + " " + e.StackTrace + " Transform: " + vessel.GetReferenceTransformPart() + "\"", e); + }*/ + + UpdateReferenceTransform(vessel.GetReferenceTransformPart(), true); + } + /* try + { + navballAttitudeGimbal = vesselOrientationCorrection * Quaternion.Inverse(referenceTransform.rotation); + } + catch (Exception e) + { + throw new ArgumentException("Error in navballAttitudeGimbal: \"" + e.Source + " " + e.TargetSite + " " + e.Data + " " + e.StackTrace + " Transform: " + referenceTransform + "\"", e); + }*/ + navballAttitudeGimbal = vesselOrientationCorrection * Quaternion.Inverse(referenceTransform.rotation); Vector3 relativePositionVector = (referenceTransform.position - mainBody.position).normalized; + //Utility.LogMessage(this, "Past gate 1"); Quaternion relativeGimbal = navballAttitudeGimbal * Quaternion.LookRotation( Vector3.ProjectOnPlane(relativePositionVector + mainBody.transform.up, (relativePositionVector)), relativePositionVector); - + + //Utility.LogMessage(this, "Past gate 2"); + // We have to do all sorts of voodoo to get the navball // gimbal rotated so the rendered navball behaves the same // as navballs. @@ -657,10 +745,14 @@ void UpdateAttitude() surfaceAttitude.z = -surfaceAttitude.z; } + //Utility.LogMessage(this, "Past gate 3"); + double headingChange = Utility.NormalizeLongitude(surfaceAttitude.y - lastHeading); lastHeading = surfaceAttitude.y; headingRate = 0.875 * headingRate + 0.125 * headingChange / TimeWarp.fixedDeltaTime; + //Utility.LogMessage(this, "Past gate 4"); + up = vessel.upAxis; prograde = vessel.obt_velocity.normalized; surfacePrograde = vessel.srf_vel_direction; @@ -668,10 +760,14 @@ void UpdateAttitude() normal = -Vector3.Cross(radialOut, prograde).normalized; // TODO: does Vector3.OrthoNormalize do anything for me here? + //Utility.LogMessage(this, "Past gate 5"); + right = vessel.GetTransform().right; forward = vessel.GetTransform().up; top = vessel.GetTransform().forward; + //Utility.LogMessage(this, "Past gate 6"); + // We base our surface vector off of UP and RIGHT, unless roll is extreme. if (Mathf.Abs(Vector3.Dot(right, up)) > 0.995f) { @@ -684,6 +780,8 @@ void UpdateAttitude() surfaceRight = Vector3.Cross(surfaceForward, up); } + //Utility.LogMessage(this, "Past gate 7"); + Vector3 surfaceProgradeProjected = Vector3.ProjectOnPlane(surfacePrograde, up); progradeHeading = Vector3.Angle(surfaceProgradeProjected, vessel.north); if (Vector3.Dot(surfaceProgradeProjected, vessel.east) < 0.0) @@ -691,6 +789,8 @@ void UpdateAttitude() progradeHeading = 360.0f - progradeHeading; } + //Utility.LogMessage(this, "Past gate 8"); + // TODO: Am I computing normal wrong? // TODO: orbit.GetOrbitNormal() appears to return a vector in the opposite // direction when in the inertial frame of reference, but it's perpendicular @@ -1271,7 +1371,10 @@ private void UpdateReferenceTransform(Part referencePart, bool forceEvaluate) { // During staging, it's possible for referencePart to be null. If it is, let's skip // this processing. Things will sort out later. - return; + Utility.LogMessage(this, "Null referencePart for {0}; setting to root!", vessel.id); + vessel.SetReferenceTransform(vessel.rootPart); + referencePart = vessel.GetReferenceTransformPart(); + //return; } Transform newRefXform = referencePart.GetReferenceTransform(); @@ -1335,9 +1438,10 @@ private void onVesselChange(Vessel who) { if (who.id == vesselId) { - vesselCrewed = (vessel.GetCrewCount() > 0) && HighLogic.LoadedSceneIsFlight; + vesselCrewed = true && HighLogic.LoadedSceneIsFlight; vesselActive = ActiveVessel(vessel); InvalidateModules(); + Utility.LogMessage(this, "onVesselChange for {0}", who.id); } } @@ -1354,9 +1458,10 @@ private void onVesselWasModified(Vessel who) { if (who.id == vesselId) { - vesselCrewed = (vessel.GetCrewCount() > 0) && HighLogic.LoadedSceneIsFlight; + vesselCrewed = true && HighLogic.LoadedSceneIsFlight; vesselActive = ActiveVessel(vessel); InvalidateModules(); + Utility.LogMessage(this, "onVesselWasModified for {0}", who.id); } } @@ -1364,8 +1469,9 @@ private void onVesselDestroy(Vessel who) { if (who.id == vesselId) { - vesselCrewed = (vessel.GetCrewCount() > 0) && HighLogic.LoadedSceneIsFlight; + vesselCrewed = true && HighLogic.LoadedSceneIsFlight; vesselActive = ActiveVessel(vessel); + Utility.LogMessage(this, "onVesselDestroy for {0}", who.id); } } @@ -1373,8 +1479,9 @@ private void onVesselCreate(Vessel who) { if (who.id == vesselId) { - vesselCrewed = (vessel.GetCrewCount() > 0) && HighLogic.LoadedSceneIsFlight; + vesselCrewed = true && HighLogic.LoadedSceneIsFlight; vesselActive = ActiveVessel(vessel); + Utility.LogMessage(this, "onVesselCreate for {0}", who.id); } } @@ -1382,8 +1489,9 @@ private void onVesselCrewWasModified(Vessel who) { if (who.id == vessel.id) { - vesselCrewed = (vessel.GetCrewCount() > 0) && HighLogic.LoadedSceneIsFlight; + vesselCrewed = true && HighLogic.LoadedSceneIsFlight; vesselActive = ActiveVessel(vessel); + Utility.LogMessage(this, "onVesselCrewWasModified for {0}", who.id); } } #endregion diff --git a/Source/MASVesselComputerModules.cs b/Source/MASVesselComputerModules.cs index 7591856c..cec5b9eb 100644 --- a/Source/MASVesselComputerModules.cs +++ b/Source/MASVesselComputerModules.cs @@ -314,7 +314,7 @@ private void UpdateDockingNode(Part referencePart) else { uint shipFlightNumber = 0; - if (referenceTransformType == ReferenceType.Self) + if (referenceTransformType == ReferenceType.Self || referenceTransformType == ReferenceType.RemoteCommand) { // If the reference transform is the current IVA, we need // to look for another part that has a docking node and the From 49a0715d29eb4bad2f540bbec942446ed8a9b717 Mon Sep 17 00:00:00 2001 From: Sovetskysoyuz Date: Thu, 11 Feb 2021 14:14:35 -0500 Subject: [PATCH 02/48] Add function for circularizing that accepts hyperbolic orbits --- Source/MASITransfer.cs | 56 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/Source/MASITransfer.cs b/Source/MASITransfer.cs index 31a6264b..14205702 100644 --- a/Source/MASITransfer.cs +++ b/Source/MASITransfer.cs @@ -732,6 +732,62 @@ public double CircularizeAltitude(double newAltitude) return 0.0; } + /// + /// /// + /// Circularize the vessel's orbit at the specified altitude, in meters. This new altitude + /// must be between the current periapsis and the SoI limit. If the orbit is hyperbolic and the craft + /// is past the periapsis, the altitude must also be greater than the current altitude. This method + /// uses calculations derived from first principles rather than the KSP orbit functions. + /// + /// The altitude at which the orbit will be circularized, in meters. + /// 1 if a node was created, 0 otherwise. + public double CircularizeAltitudeHypVis(double newAltitude) + { + Orbit current = vessel.orbit; + double newSMA = newAltitude + current.referenceBody.Radius; + + if ((newSMA >= current.PeR && vc.orbit.timeToPe >= 0 || newSMA >= vessel.altitude) && newSMA <= vessel.mainBody.sphereOfInfluence && vessel.patchedConicSolver != null) + { + CelestialBody referenceBody = current.referenceBody; + + // required velocity is entirely circumferential and equal to circular orbit speed + + double vNew = Math.Sqrt(referenceBody.gravParameter / newSMA); + + // get true anomaly when passing through altitude, and time at which that happens + + double tAAtNewSMA = current.TrueAnomalyAtRadius(newSMA); + double maneuverUt = current.GetUTforTrueAnomaly(tAAtNewSMA, 0); + + // pre-burn speed found using vis-viva equation. + double vPreBurn = Math.Sqrt(referenceBody.gravParameter * (2 / newSMA - 1 / current.semiMajorAxis)); + + // get specific angular momentum and use to find circumferential speed at the burn, + // and the elevation angle at the burn + double spAngMom = current.PeR * current.getOrbitalSpeedAtDistance(current.PeR); + double vPreBurnCirc = spAngMom / newSMA; + double elevationAngle = Math.Acos(vPreBurnCirc / vPreBurn); + + // delta-V magnitude is the magnitude of the vector difference between pre- and post-burn velocities + double deltaVTotal = Math.Sqrt(Math.Pow(vNew, 2) + Math.Pow(vPreBurn, 2) - 2 * vNew * vPreBurn * Math.Cos(elevationAngle)); + + // orthogonal components of the delta-V (no normal since never going to need inclination change) + double deltaVPrograde = -(vPreBurn - vNew * Math.Cos(elevationAngle)); + double deltaVRadial = -vNew * Math.Sin(elevationAngle); + + // create the maneuver node + Vector3d maneuverdV = Utility.ManeuverNode(deltaVPrograde, 0, deltaVRadial); + + vessel.patchedConicSolver.maneuverNodes.Clear(); + ManeuverNode mn = vessel.patchedConicSolver.AddManeuverNode(maneuverUt); + mn.OnGizmoUpdated(maneuverdV, maneuverUt); + + return 1.0; + } + + return 0.0; + } + /// /// Generate a maneuver to conduct a Hohmann transfer to the current target. If there is no /// target, or the transfer is not a simple transfer to a nearly co-planar target, nothing happens. From 5822f003153386b86d001fcce0b0bc6063dd1c42 Mon Sep 17 00:00:00 2001 From: Sovetskysoyuz Date: Thu, 11 Feb 2021 14:14:35 -0500 Subject: [PATCH 03/48] Add function for circularizing that accepts hyperbolic orbits --- AvionicsSystems.csproj | 1 + Source/MASITransfer.cs | 56 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+) diff --git a/AvionicsSystems.csproj b/AvionicsSystems.csproj index 72fb0026..bac57402 100644 --- a/AvionicsSystems.csproj +++ b/AvionicsSystems.csproj @@ -83,6 +83,7 @@ + diff --git a/Source/MASITransfer.cs b/Source/MASITransfer.cs index 31a6264b..14205702 100644 --- a/Source/MASITransfer.cs +++ b/Source/MASITransfer.cs @@ -732,6 +732,62 @@ public double CircularizeAltitude(double newAltitude) return 0.0; } + /// + /// /// + /// Circularize the vessel's orbit at the specified altitude, in meters. This new altitude + /// must be between the current periapsis and the SoI limit. If the orbit is hyperbolic and the craft + /// is past the periapsis, the altitude must also be greater than the current altitude. This method + /// uses calculations derived from first principles rather than the KSP orbit functions. + /// + /// The altitude at which the orbit will be circularized, in meters. + /// 1 if a node was created, 0 otherwise. + public double CircularizeAltitudeHypVis(double newAltitude) + { + Orbit current = vessel.orbit; + double newSMA = newAltitude + current.referenceBody.Radius; + + if ((newSMA >= current.PeR && vc.orbit.timeToPe >= 0 || newSMA >= vessel.altitude) && newSMA <= vessel.mainBody.sphereOfInfluence && vessel.patchedConicSolver != null) + { + CelestialBody referenceBody = current.referenceBody; + + // required velocity is entirely circumferential and equal to circular orbit speed + + double vNew = Math.Sqrt(referenceBody.gravParameter / newSMA); + + // get true anomaly when passing through altitude, and time at which that happens + + double tAAtNewSMA = current.TrueAnomalyAtRadius(newSMA); + double maneuverUt = current.GetUTforTrueAnomaly(tAAtNewSMA, 0); + + // pre-burn speed found using vis-viva equation. + double vPreBurn = Math.Sqrt(referenceBody.gravParameter * (2 / newSMA - 1 / current.semiMajorAxis)); + + // get specific angular momentum and use to find circumferential speed at the burn, + // and the elevation angle at the burn + double spAngMom = current.PeR * current.getOrbitalSpeedAtDistance(current.PeR); + double vPreBurnCirc = spAngMom / newSMA; + double elevationAngle = Math.Acos(vPreBurnCirc / vPreBurn); + + // delta-V magnitude is the magnitude of the vector difference between pre- and post-burn velocities + double deltaVTotal = Math.Sqrt(Math.Pow(vNew, 2) + Math.Pow(vPreBurn, 2) - 2 * vNew * vPreBurn * Math.Cos(elevationAngle)); + + // orthogonal components of the delta-V (no normal since never going to need inclination change) + double deltaVPrograde = -(vPreBurn - vNew * Math.Cos(elevationAngle)); + double deltaVRadial = -vNew * Math.Sin(elevationAngle); + + // create the maneuver node + Vector3d maneuverdV = Utility.ManeuverNode(deltaVPrograde, 0, deltaVRadial); + + vessel.patchedConicSolver.maneuverNodes.Clear(); + ManeuverNode mn = vessel.patchedConicSolver.AddManeuverNode(maneuverUt); + mn.OnGizmoUpdated(maneuverdV, maneuverUt); + + return 1.0; + } + + return 0.0; + } + /// /// Generate a maneuver to conduct a Hohmann transfer to the current target. If there is no /// target, or the transfer is not a simple transfer to a nearly co-planar target, nothing happens. From e8851b085b7f9bdb8d9522448516ee05852d732d Mon Sep 17 00:00:00 2001 From: Sovetskysoyuz Date: Thu, 11 Feb 2021 14:20:40 -0500 Subject: [PATCH 04/48] Catch up --- AvionicsSystems.csproj | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/AvionicsSystems.csproj b/AvionicsSystems.csproj index bac57402..5bbec038 100644 --- a/AvionicsSystems.csproj +++ b/AvionicsSystems.csproj @@ -83,7 +83,6 @@ - @@ -187,7 +186,9 @@ - + + + copy $(TargetPath) "D:\Games\KSP-IVA\GameData\MOARdV\AvionicsSystems" From f86b40fcaa06309f284d292b019a1c08e43a508d Mon Sep 17 00:00:00 2001 From: Sovetskysoyuz Date: Thu, 11 Feb 2021 14:26:12 -0500 Subject: [PATCH 05/48] Reverting hyperbolic changes split into own branch --- Source/MASITransfer.cs | 154 +---------------------------------------- 1 file changed, 1 insertion(+), 153 deletions(-) diff --git a/Source/MASITransfer.cs b/Source/MASITransfer.cs index 09128139..65cd30db 100644 --- a/Source/MASITransfer.cs +++ b/Source/MASITransfer.cs @@ -732,159 +732,7 @@ public double CircularizeAltitude(double newAltitude) return 0.0; } - /// - /// Circularize the vessel's orbit at the specified altitude, in meters. This new altitude - /// must be between the current periapsis and the SoI limit. If the orbit is hyperbolic and the craft - /// is past the periapsis, the altitude must also be greater than the current altitude. - /// - /// The altitude at which the orbit will be circularized, in meters. - /// 1 if a node was created, 0 otherwise. - public double CircularizeAltitudeHyp(double newAltitude) - { - Orbit current = vessel.orbit; - double newSMA = newAltitude + current.referenceBody.Radius; - - Utility.LogMessage(this, "newSMA = {0} because {1} + {2}", newSMA, newAltitude, current.referenceBody.Radius); - - if ((newSMA >= current.PeR && vc.orbit.timeToPe >= 0 || newSMA >= vessel.altitude) && newSMA <= vessel.mainBody.sphereOfInfluence && vessel.patchedConicSolver != null) - { - CelestialBody referenceBody = current.referenceBody; - double vNew = Math.Sqrt(referenceBody.gravParameter / newSMA); - double tAAtNewSMA = current.TrueAnomalyAtRadius(newSMA); - //double maneuverUt = Planetarium.GetUniversalTime() + current.GetUTforTrueAnomaly(tAAtNewSMA, 0); - double maneuverUt = current.GetUTforTrueAnomaly(tAAtNewSMA, 0); - - Utility.LogMessage(this, "maneuverUt = {0} because {1}", maneuverUt, current.GetUTforTrueAnomaly(tAAtNewSMA, 0)); - - Vector3d velAtUt = current.getOrbitalVelocityAtUT(maneuverUt); - - Utility.LogMessage(this, "velAtUt = {0} ", velAtUt); - - Vector3d relativePosition = current.getRelativePositionAtUT(maneuverUt); - - Utility.LogMessage(this, "relativePosition = {0}", relativePosition); - - Vector3d prograde; - Vector3d normal; - Vector3d radial; - Utility.GetOrbitalBasisVectors(velAtUt, relativePosition, out prograde, out radial, out normal); - - Utility.LogMessage(this, "maneuverUt = {0} because {1} - {2}", maneuverUt, Planetarium.GetUniversalTime(), current.GetUTforTrueAnomaly(tAAtNewSMA, 0)); - - Vector3d upAtUt = relativePosition.xzy.normalized; - Vector3d fwdAtUt = Vector3d.Cross(upAtUt, normal); - Vector3d maneuverVel = fwdAtUt * vNew; - - Utility.LogMessage(this, "maneuverVel = {0} because {1} - {2}", maneuverVel, upAtUt, fwdAtUt); - - Vector3d deltaV = maneuverVel - velAtUt.xzy; - Utility.LogMessage(this, "dV = {0} because {1} - {2}", deltaV, maneuverVel, velAtUt); - Utility.LogMessage(this, "prograde (dot) fwd = {0:0.000}", Vector3d.Dot(prograde, fwdAtUt)); - - Vector3d maneuverdV = Utility.ManeuverNode(Vector3d.Dot(deltaV, prograde), Vector3d.Dot(deltaV, normal), Vector3d.Dot(deltaV, radial)); - - vessel.patchedConicSolver.maneuverNodes.Clear(); - ManeuverNode mn = vessel.patchedConicSolver.AddManeuverNode(maneuverUt); - mn.OnGizmoUpdated(maneuverdV, maneuverUt); - - //Vector3d posAtUt = current.getRelativePositionAtUT(maneuverUt); - //oUpper.UpdateFromStateVectors(posAtUt, maneuverVel.xzy, referenceBody, maneuverUt); - //Utility.LogMessage(this, "Circularize at {0:0.000}km: {1:0.000} x {2:0.000} @ {3:0.0}", - // newAltitude * 0.001, - // oUpper.ApA * 0.001, oUpper.PeA * 0.001, - // oUpper.inclination); - - return 1.0; - } - - return 0.0; - } - - /// - /// Circularize the vessel's orbit at the specified altitude, in meters. This new altitude - /// must be between the current periapsis and the SoI limit. If the orbit is hyperbolic and the craft - /// is past the periapsis, the altitude must also be greater than the current altitude. This method - /// uses calculations derived from first principles rather than the KSP orbit functions. - /// - /// The altitude at which the orbit will be circularized, in meters. - /// 1 if a node was created, 0 otherwise. - public double CircularizeAltitudeHypVis(double newAltitude) - { - Orbit current = vessel.orbit; - double newSMA = newAltitude + current.referenceBody.Radius; - - Utility.LogMessage(this, "newSMA = {0} because {1} + {2}", newSMA, newAltitude, current.referenceBody.Radius); - - if ((newSMA >= current.PeR && vc.orbit.timeToPe >= 0 || newSMA >= vessel.altitude) && newSMA <= vessel.mainBody.sphereOfInfluence && vessel.patchedConicSolver != null) - { - CelestialBody referenceBody = current.referenceBody; - double vNew = Math.Sqrt(referenceBody.gravParameter / newSMA); - double tAAtNewSMA = current.TrueAnomalyAtRadius(newSMA); - //double maneuverUt = Planetarium.GetUniversalTime() + current.GetUTforTrueAnomaly(tAAtNewSMA, 0); - double maneuverUt = current.GetUTforTrueAnomaly(tAAtNewSMA, 0); - - Utility.LogMessage(this, "maneuverUt = {0} because {1}", maneuverUt, current.GetUTforTrueAnomaly(tAAtNewSMA, 0)); - - double vPreBurn = Math.Sqrt(referenceBody.gravParameter * (2 / newSMA - 1 / current.semiMajorAxis)); - - Utility.LogMessage(this, "v before burn = {0} ", vPreBurn); - - /* double spAngMom = current.PeR * current.getOrbitalSpeedAt(current.GetTimeToPeriapsis() + Planetarium.GetUniversalTime()); - - Utility.LogMessage(this, "specific angular momentum = {0} PeR = {1} time to PE = {2} UT at PE = {3} speed at PE = {4}", - spAngMom, current.PeR, current.GetTimeToPeriapsis(), current.GetTimeToPeriapsis() + Planetarium.GetUniversalTime(), - current.getOrbitalSpeedAt(current.GetTimeToPeriapsis() + Planetarium.GetUniversalTime()));*/ - - double spAngMom = current.PeR * current.getOrbitalSpeedAtDistance(current.PeR); - - Utility.LogMessage(this, "specific angular momentum = {0} PeR = {1} PeA = {2} Speed at PeR = {3} speed at PeA = {4}", - spAngMom, current.PeR, current.PeA, current.getOrbitalSpeedAtDistance(current.PeR), current.getOrbitalSpeedAtDistance(current.PeA)); - - double vPreBurnCirc = spAngMom / newSMA; - - Utility.LogMessage(this, "preburn circumferential velocity = {0}", vPreBurnCirc); - - double elevationAngle = Math.Acos(vPreBurnCirc / vPreBurn); - - Utility.LogMessage(this, "Elevation angle = {0}", elevationAngle); - - double deltaVTotal = Math.Sqrt(Math.Pow(vNew, 2) + Math.Pow(vPreBurn, 2) - 2 * vNew * vPreBurn * Math.Cos(elevationAngle)); - - Utility.LogMessage(this, "deltaVTotal = {0}", deltaVTotal); - - /*double angleOffRetrograde = Math.Asin(vNew / vPreBurn); - - Utility.LogMessage(this, "angle off retrograde = {0}", angleOffRetrograde);*/ - - /*double deltaVPrograde = -deltaVTotal * Math.Cos(angleOffRetrograde); - double deltaVRadial = -deltaVTotal * Math.Sin(angleOffRetrograde);*/ - - double deltaVPrograde = -(vPreBurn - vNew * Math.Cos(elevationAngle)); - double deltaVRadial = -vNew * Math.Sin(elevationAngle); - - Utility.LogMessage(this, "prograde = {0}, radial = {1}", deltaVPrograde, deltaVRadial); - - Vector3d maneuverdV = Utility.ManeuverNode(deltaVPrograde, 0, deltaVRadial); - - Utility.LogMessage(this, "maneuverdV = {0}", maneuverdV); - - vessel.patchedConicSolver.maneuverNodes.Clear(); - ManeuverNode mn = vessel.patchedConicSolver.AddManeuverNode(maneuverUt); - mn.OnGizmoUpdated(maneuverdV, maneuverUt); - - //Vector3d posAtUt = current.getRelativePositionAtUT(maneuverUt); - //oUpper.UpdateFromStateVectors(posAtUt, maneuverVel.xzy, referenceBody, maneuverUt); - //Utility.LogMessage(this, "Circularize at {0:0.000}km: {1:0.000} x {2:0.000} @ {3:0.0}", - // newAltitude * 0.001, - // oUpper.ApA * 0.001, oUpper.PeA * 0.001, - // oUpper.inclination); - - return 1.0; - } - - return 0.0; - } - + /// /// Generate a maneuver to conduct a Hohmann transfer to the current target. If there is no /// target, or the transfer is not a simple transfer to a nearly co-planar target, nothing happens. From 68265673eace3f463b7fa01ccfa8e6658f0e9836 Mon Sep 17 00:00:00 2001 From: Sovetskysoyuz Date: Thu, 11 Feb 2021 14:52:56 -0500 Subject: [PATCH 06/48] Changes to enable using MAS in ProbeControlRoom IVA --- Source/MASComponentAudioPlayer.cs | 11 +++++++++-- Source/MASVesselComputer.cs | 31 +++++++++++++++++++++--------- Source/MASVesselComputerModules.cs | 4 +++- 3 files changed, 34 insertions(+), 12 deletions(-) diff --git a/Source/MASComponentAudioPlayer.cs b/Source/MASComponentAudioPlayer.cs index 55e5b4b8..c211a5bf 100644 --- a/Source/MASComponentAudioPlayer.cs +++ b/Source/MASComponentAudioPlayer.cs @@ -153,7 +153,7 @@ internal MASComponentAudioPlayer(ConfigNode config, InternalProp prop, MASFlight } variableName = variableName.Trim(); - audioSource.mute = (CameraManager.Instance.currentCameraMode != CameraManager.CameraMode.IVA); + audioSource.mute = ((CameraManager.Instance.currentCameraMode != CameraManager.CameraMode.IVA) && (CameraManager.Instance.currentCameraMode != CameraManager.CameraMode.Internal)); GameEvents.OnCameraChange.Add(OnCameraChange); @@ -182,7 +182,14 @@ internal MASComponentAudioPlayer(ConfigNode config, InternalProp prop, MASFlight /// private void OnCameraChange(CameraManager.CameraMode newCameraMode) { - audioSource.mute = (newCameraMode != CameraManager.CameraMode.IVA); + try + { + audioSource.mute = ((CameraManager.Instance.currentCameraMode != CameraManager.CameraMode.IVA) && (CameraManager.Instance.currentCameraMode != CameraManager.CameraMode.Internal)); + } + catch (Exception e) + { + throw new ArgumentException("Error in AudioPlayer OnCameraChange: \"" + e.Source + e.TargetSite + e.Data + e.StackTrace + CameraManager.Instance.currentCameraMode + "\"", e); + } } /// diff --git a/Source/MASVesselComputer.cs b/Source/MASVesselComputer.cs index 0341bc5b..92749bad 100644 --- a/Source/MASVesselComputer.cs +++ b/Source/MASVesselComputer.cs @@ -308,7 +308,8 @@ public void Awake() InitResourceData(); UpdateReferenceTransform(vessel.GetReferenceTransformPart(), true); - vesselCrewed = (vessel.GetCrewCount() > 0); + //vesselCrewed = (vessel.GetCrewCount() > 0); + vesselCrewed = true; vesselActive = ActiveVessel(vessel); if (vesselCrewed) { @@ -626,6 +627,10 @@ internal double GetSlopeAngle() void UpdateAttitude() { + if (vessel.GetReferenceTransformPart() == null) + { + UpdateReferenceTransform(vessel.GetReferenceTransformPart(), true); + } navballAttitudeGimbal = vesselOrientationCorrection * Quaternion.Inverse(referenceTransform.rotation); Vector3 relativePositionVector = (referenceTransform.position - mainBody.position).normalized; @@ -1269,9 +1274,12 @@ private void UpdateReferenceTransform(Part referencePart, bool forceEvaluate) { if (referencePart == null) { - // During staging, it's possible for referencePart to be null. If it is, let's skip - // this processing. Things will sort out later. - return; + // During staging, it's possible for referencePart to be null. If it is, must set to + // root part in order to avoid failure to initialize. + Utility.LogMessage(this, "Null referencePart for {0}; setting to root!", vessel.id); + vessel.SetReferenceTransform(vessel.rootPart); + referencePart = vessel.GetReferenceTransformPart(); + //return; } Transform newRefXform = referencePart.GetReferenceTransform(); @@ -1335,9 +1343,10 @@ private void onVesselChange(Vessel who) { if (who.id == vesselId) { - vesselCrewed = (vessel.GetCrewCount() > 0) && HighLogic.LoadedSceneIsFlight; + vesselCrewed = true && HighLogic.LoadedSceneIsFlight; vesselActive = ActiveVessel(vessel); InvalidateModules(); + Utility.LogMessage(this, "onVesselChange for {0}", who.id); } } @@ -1354,9 +1363,10 @@ private void onVesselWasModified(Vessel who) { if (who.id == vesselId) { - vesselCrewed = (vessel.GetCrewCount() > 0) && HighLogic.LoadedSceneIsFlight; + vesselCrewed = true && HighLogic.LoadedSceneIsFlight; vesselActive = ActiveVessel(vessel); InvalidateModules(); + Utility.LogMessage(this, "onVesselWasModified for {0}", who.id); } } @@ -1364,8 +1374,9 @@ private void onVesselDestroy(Vessel who) { if (who.id == vesselId) { - vesselCrewed = (vessel.GetCrewCount() > 0) && HighLogic.LoadedSceneIsFlight; + vesselCrewed = true && HighLogic.LoadedSceneIsFlight; vesselActive = ActiveVessel(vessel); + Utility.LogMessage(this, "onVesselDestroy for {0}", who.id); } } @@ -1373,8 +1384,9 @@ private void onVesselCreate(Vessel who) { if (who.id == vesselId) { - vesselCrewed = (vessel.GetCrewCount() > 0) && HighLogic.LoadedSceneIsFlight; + vesselCrewed = true && HighLogic.LoadedSceneIsFlight; vesselActive = ActiveVessel(vessel); + Utility.LogMessage(this, "onVesselCreate for {0}", who.id); } } @@ -1382,8 +1394,9 @@ private void onVesselCrewWasModified(Vessel who) { if (who.id == vessel.id) { - vesselCrewed = (vessel.GetCrewCount() > 0) && HighLogic.LoadedSceneIsFlight; + vesselCrewed = true && HighLogic.LoadedSceneIsFlight; vesselActive = ActiveVessel(vessel); + Utility.LogMessage(this, "onVesselCrewWasModified for {0}", who.id); } } #endregion diff --git a/Source/MASVesselComputerModules.cs b/Source/MASVesselComputerModules.cs index 7591856c..d3032c02 100644 --- a/Source/MASVesselComputerModules.cs +++ b/Source/MASVesselComputerModules.cs @@ -314,11 +314,13 @@ private void UpdateDockingNode(Part referencePart) else { uint shipFlightNumber = 0; - if (referenceTransformType == ReferenceType.Self) + if (referenceTransformType == ReferenceType.Self || referenceTransformType == ReferenceType.RemoteCommand) { // If the reference transform is the current IVA, we need // to look for another part that has a docking node and the // same ID as our part. + // If using ProbeControlRoom, referenceTransform isn't self, + // it's RemoteCommand. shipFlightNumber = referencePart.launchID; } else From cf3fb2198be40ec4450da510b5ef61c3edc8393a Mon Sep 17 00:00:00 2001 From: Sovetskysoyuz Date: Thu, 11 Feb 2021 15:38:09 -0500 Subject: [PATCH 07/48] Addition to FlightComputer --- Source/MASFlightComputer.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Source/MASFlightComputer.cs b/Source/MASFlightComputer.cs index 2fbf9bfd..e1b1c972 100644 --- a/Source/MASFlightComputer.cs +++ b/Source/MASFlightComputer.cs @@ -1825,6 +1825,10 @@ internal bool PlayAudio(string clipName, float volume, bool stopCurrent) /// internal Kerbal FindCurrentKerbal() { + if (vessel.GetCrewCount() == 0) + { + return null; + } Kerbal activeKerbal = CameraManager.Instance.IVACameraActiveKerbal; if (activeKerbal.InPart == part) { From 03502a38804aeca151b30bca943a33e80e75cd1b Mon Sep 17 00:00:00 2001 From: Sovetskysoyuz Date: Thu, 11 Feb 2021 15:04:13 -0500 Subject: [PATCH 08/48] Removing changes split into PCR branch --- Source/MASComponentAudioPlayer.cs | 11 ++------ Source/MASFlightComputer.cs | 4 --- Source/MASVesselComputer.cs | 45 +++++------------------------- Source/MASVesselComputerModules.cs | 2 +- 4 files changed, 10 insertions(+), 52 deletions(-) diff --git a/Source/MASComponentAudioPlayer.cs b/Source/MASComponentAudioPlayer.cs index c211a5bf..55e5b4b8 100644 --- a/Source/MASComponentAudioPlayer.cs +++ b/Source/MASComponentAudioPlayer.cs @@ -153,7 +153,7 @@ internal MASComponentAudioPlayer(ConfigNode config, InternalProp prop, MASFlight } variableName = variableName.Trim(); - audioSource.mute = ((CameraManager.Instance.currentCameraMode != CameraManager.CameraMode.IVA) && (CameraManager.Instance.currentCameraMode != CameraManager.CameraMode.Internal)); + audioSource.mute = (CameraManager.Instance.currentCameraMode != CameraManager.CameraMode.IVA); GameEvents.OnCameraChange.Add(OnCameraChange); @@ -182,14 +182,7 @@ internal MASComponentAudioPlayer(ConfigNode config, InternalProp prop, MASFlight /// private void OnCameraChange(CameraManager.CameraMode newCameraMode) { - try - { - audioSource.mute = ((CameraManager.Instance.currentCameraMode != CameraManager.CameraMode.IVA) && (CameraManager.Instance.currentCameraMode != CameraManager.CameraMode.Internal)); - } - catch (Exception e) - { - throw new ArgumentException("Error in AudioPlayer OnCameraChange: \"" + e.Source + e.TargetSite + e.Data + e.StackTrace + CameraManager.Instance.currentCameraMode + "\"", e); - } + audioSource.mute = (newCameraMode != CameraManager.CameraMode.IVA); } /// diff --git a/Source/MASFlightComputer.cs b/Source/MASFlightComputer.cs index e1b1c972..2fbf9bfd 100644 --- a/Source/MASFlightComputer.cs +++ b/Source/MASFlightComputer.cs @@ -1825,10 +1825,6 @@ internal bool PlayAudio(string clipName, float volume, bool stopCurrent) /// internal Kerbal FindCurrentKerbal() { - if (vessel.GetCrewCount() == 0) - { - return null; - } Kerbal activeKerbal = CameraManager.Instance.IVACameraActiveKerbal; if (activeKerbal.InPart == part) { diff --git a/Source/MASVesselComputer.cs b/Source/MASVesselComputer.cs index ea792ed1..da84eb17 100644 --- a/Source/MASVesselComputer.cs +++ b/Source/MASVesselComputer.cs @@ -316,8 +316,7 @@ public void Awake() InitResourceData(); UpdateReferenceTransform(vessel.GetReferenceTransformPart(), true); - //vesselCrewed = (vessel.GetCrewCount() > 0); - vesselCrewed = true; + vesselCrewed = (vessel.GetCrewCount() > 0); vesselActive = ActiveVessel(vessel); if (vesselCrewed) { @@ -689,28 +688,6 @@ internal double GetSlopeAngle() void UpdateAttitude() { - if (vessel.GetReferenceTransformPart() == null) - { - /*try - { - UpdateReferenceTransform(vessel.GetReferenceTransformPart(), true); - } - catch (Exception e) - { - throw new ArgumentException("Error in UpdateReferenceTransform in UpdateAttitude: \"" + e.Source + " " + e.TargetSite + " " + e.Data + " " + e.StackTrace + " Transform: " + vessel.GetReferenceTransformPart() + "\"", e); - }*/ - - UpdateReferenceTransform(vessel.GetReferenceTransformPart(), true); - } - /* try - { - navballAttitudeGimbal = vesselOrientationCorrection * Quaternion.Inverse(referenceTransform.rotation); - } - catch (Exception e) - { - throw new ArgumentException("Error in navballAttitudeGimbal: \"" + e.Source + " " + e.TargetSite + " " + e.Data + " " + e.StackTrace + " Transform: " + referenceTransform + "\"", e); - }*/ - navballAttitudeGimbal = vesselOrientationCorrection * Quaternion.Inverse(referenceTransform.rotation); Vector3 relativePositionVector = (referenceTransform.position - mainBody.position).normalized; @@ -1371,10 +1348,7 @@ private void UpdateReferenceTransform(Part referencePart, bool forceEvaluate) { // During staging, it's possible for referencePart to be null. If it is, let's skip // this processing. Things will sort out later. - Utility.LogMessage(this, "Null referencePart for {0}; setting to root!", vessel.id); - vessel.SetReferenceTransform(vessel.rootPart); - referencePart = vessel.GetReferenceTransformPart(); - //return; + return; } Transform newRefXform = referencePart.GetReferenceTransform(); @@ -1438,10 +1412,9 @@ private void onVesselChange(Vessel who) { if (who.id == vesselId) { - vesselCrewed = true && HighLogic.LoadedSceneIsFlight; + vesselCrewed = (vessel.GetCrewCount() > 0) && HighLogic.LoadedSceneIsFlight; vesselActive = ActiveVessel(vessel); InvalidateModules(); - Utility.LogMessage(this, "onVesselChange for {0}", who.id); } } @@ -1458,10 +1431,9 @@ private void onVesselWasModified(Vessel who) { if (who.id == vesselId) { - vesselCrewed = true && HighLogic.LoadedSceneIsFlight; + vesselCrewed = (vessel.GetCrewCount() > 0) && HighLogic.LoadedSceneIsFlight; vesselActive = ActiveVessel(vessel); InvalidateModules(); - Utility.LogMessage(this, "onVesselWasModified for {0}", who.id); } } @@ -1469,9 +1441,8 @@ private void onVesselDestroy(Vessel who) { if (who.id == vesselId) { - vesselCrewed = true && HighLogic.LoadedSceneIsFlight; + vesselCrewed = (vessel.GetCrewCount() > 0) && HighLogic.LoadedSceneIsFlight; vesselActive = ActiveVessel(vessel); - Utility.LogMessage(this, "onVesselDestroy for {0}", who.id); } } @@ -1479,9 +1450,8 @@ private void onVesselCreate(Vessel who) { if (who.id == vesselId) { - vesselCrewed = true && HighLogic.LoadedSceneIsFlight; + vesselCrewed = (vessel.GetCrewCount() > 0) && HighLogic.LoadedSceneIsFlight; vesselActive = ActiveVessel(vessel); - Utility.LogMessage(this, "onVesselCreate for {0}", who.id); } } @@ -1489,9 +1459,8 @@ private void onVesselCrewWasModified(Vessel who) { if (who.id == vessel.id) { - vesselCrewed = true && HighLogic.LoadedSceneIsFlight; + vesselCrewed = (vessel.GetCrewCount() > 0) && HighLogic.LoadedSceneIsFlight; vesselActive = ActiveVessel(vessel); - Utility.LogMessage(this, "onVesselCrewWasModified for {0}", who.id); } } #endregion diff --git a/Source/MASVesselComputerModules.cs b/Source/MASVesselComputerModules.cs index cec5b9eb..7591856c 100644 --- a/Source/MASVesselComputerModules.cs +++ b/Source/MASVesselComputerModules.cs @@ -314,7 +314,7 @@ private void UpdateDockingNode(Part referencePart) else { uint shipFlightNumber = 0; - if (referenceTransformType == ReferenceType.Self || referenceTransformType == ReferenceType.RemoteCommand) + if (referenceTransformType == ReferenceType.Self) { // If the reference transform is the current IVA, we need // to look for another part that has a docking node and the From 6b236e3e8c274209efa3098ea2ecc3540c92fd92 Mon Sep 17 00:00:00 2001 From: Sovetskysoyuz Date: Thu, 11 Feb 2021 15:40:38 -0500 Subject: [PATCH 09/48] Change to FlightComputer --- Source/MASFlightComputer.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Source/MASFlightComputer.cs b/Source/MASFlightComputer.cs index 2fbf9bfd..e1b1c972 100644 --- a/Source/MASFlightComputer.cs +++ b/Source/MASFlightComputer.cs @@ -1825,6 +1825,10 @@ internal bool PlayAudio(string clipName, float volume, bool stopCurrent) /// internal Kerbal FindCurrentKerbal() { + if (vessel.GetCrewCount() == 0) + { + return null; + } Kerbal activeKerbal = CameraManager.Instance.IVACameraActiveKerbal; if (activeKerbal.InPart == part) { From af94b004fd9bb429a65fcf5ccd23dfd496a05b7d Mon Sep 17 00:00:00 2001 From: Sovetskysoyuz Date: Thu, 11 Feb 2021 15:41:24 -0500 Subject: [PATCH 10/48] x --- Source/MASFlightComputer.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Source/MASFlightComputer.cs b/Source/MASFlightComputer.cs index e1b1c972..2fbf9bfd 100644 --- a/Source/MASFlightComputer.cs +++ b/Source/MASFlightComputer.cs @@ -1825,10 +1825,6 @@ internal bool PlayAudio(string clipName, float volume, bool stopCurrent) /// internal Kerbal FindCurrentKerbal() { - if (vessel.GetCrewCount() == 0) - { - return null; - } Kerbal activeKerbal = CameraManager.Instance.IVACameraActiveKerbal; if (activeKerbal.InPart == part) { From ecab447dc2adb0161a22681e25939b54f6ce4092 Mon Sep 17 00:00:00 2001 From: Sovetskysoyuz Date: Thu, 11 Feb 2021 15:45:25 -0500 Subject: [PATCH 11/48] Remove extra files --- AvionicsSystems.csproj | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/AvionicsSystems.csproj b/AvionicsSystems.csproj index 191b8cd2..718c1a68 100644 --- a/AvionicsSystems.csproj +++ b/AvionicsSystems.csproj @@ -193,9 +193,7 @@ - - - + copy $(TargetPath) "D:\Games\KSP-IVA\GameData\MOARdV\AvionicsSystems" From dda156d150d84f260b2aeb02701e9d1f95c4cabd Mon Sep 17 00:00:00 2001 From: Sovetskysoyuz Date: Thu, 11 Feb 2021 15:51:33 -0500 Subject: [PATCH 12/48] Add UpdateOwnDockingPorts() --- Source/MASVesselComputer.cs | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/Source/MASVesselComputer.cs b/Source/MASVesselComputer.cs index 0341bc5b..19f6f3b1 100644 --- a/Source/MASVesselComputer.cs +++ b/Source/MASVesselComputer.cs @@ -127,6 +127,7 @@ private void RefreshData() UpdateAttitude(); UpdateAltitudes(); UpdateManeuverNode(); + UpdateOwnDockingPorts(); UpdateTarget(); UpdateMisc(); // Last step: @@ -362,6 +363,41 @@ private static bool ActiveVessel(Vessel vessel) return vessel.isActiveVessel && (CameraManager.Instance.currentCameraMode == CameraManager.CameraMode.IVA || CameraManager.Instance.currentCameraMode == CameraManager.CameraMode.Internal); } + internal ModuleDockingNode[] ownDockingPorts = new ModuleDockingNode[0]; + + private void UpdateOwnDockingPorts() + { + + List potentialOwnDocks = vessel.FindPartModulesImplementing(); + List validOwnDocks = new List(); + + if (dockingNode != null) + { + for (int i = potentialOwnDocks.Count - 1; i >= 0; --i) + { + ModuleDockingNode checkDock = potentialOwnDocks[i]; + + if (checkDock.state == "Ready") + { + validOwnDocks.Add(checkDock); + } + } + } + + if (ownDockingPorts.Length != validOwnDocks.Count) + { + ownDockingPorts = validOwnDocks.ToArray(); + } + else + { + for (int i = ownDockingPorts.Length - 1; i >= 0; --i) + { + ownDockingPorts[i] = validOwnDocks[i]; + } + } + + } + // Time in seconds until impact. 0 if there is no impact. private double timeToImpact_; internal double timeToImpact From fdb0dd509e9a7010d6c6cd5b64128d5e87a8c994 Mon Sep 17 00:00:00 2001 From: Sovetskysoyuz Date: Thu, 11 Feb 2021 15:52:28 -0500 Subject: [PATCH 13/48] Remove UpdateOwnDockingPorts --- Source/MASVesselComputer.cs | 55 ------------------------------------- 1 file changed, 55 deletions(-) diff --git a/Source/MASVesselComputer.cs b/Source/MASVesselComputer.cs index da84eb17..bd3031ee 100644 --- a/Source/MASVesselComputer.cs +++ b/Source/MASVesselComputer.cs @@ -134,7 +134,6 @@ private void RefreshData() } UpdateAltitudes(); UpdateManeuverNode(); - UpdateOwnDockingPorts(); UpdateTarget(); UpdateMisc(); // Last step: @@ -370,60 +369,6 @@ private static bool ActiveVessel(Vessel vessel) return vessel.isActiveVessel && (CameraManager.Instance.currentCameraMode == CameraManager.CameraMode.IVA || CameraManager.Instance.currentCameraMode == CameraManager.CameraMode.Internal); } - internal ModuleDockingNode[] ownDockingPorts = new ModuleDockingNode[0]; - - private void UpdateOwnDockingPorts() - { - //Vessel targetVessel = (targetType == TargetType.Vessel) ? (activeTarget as Vessel) : (activeTarget as ModuleDockingNode).vessel; - //if (!targetVessel.packed && targetVessel.loaded) - //{ - List potentialOwnDocks = vessel.FindPartModulesImplementing(); - List validOwnDocks = new List(); - - if (dockingNode != null) - { - for (int i = potentialOwnDocks.Count - 1; i >= 0; --i) - { - ModuleDockingNode checkDock = potentialOwnDocks[i]; - // Only lock on to an available dock of the same type that is either ungendered or the opposite gender. - //if (otherDock.state == "Ready" && (string.IsNullOrEmpty(dockingNode.nodeType) || dockingNode.nodeType == otherDock.nodeType) && (dockingNode.gendered == false || dockingNode.genderFemale != otherDock.genderFemale)) - if (checkDock.state == "Ready") - { - validOwnDocks.Add(checkDock); - } - } - } - /* else - { - for (int i = potentialOwnDocks.Count - 1; i >= 0; --i) - { - ModuleDockingNode otherDock = potentialOwnDocks[i]; - // Only lock on to an available dock of the same type that is either ungendered or the opposite gender. - if (otherDock.state == "Ready") - { - validOwnDocks.Add(otherDock); - } - } - }*/ - if (ownDockingPorts.Length != validOwnDocks.Count) - { - ownDockingPorts = validOwnDocks.ToArray(); - } - else - { - for (int i = ownDockingPorts.Length - 1; i >= 0; --i) - { - ownDockingPorts[i] = validOwnDocks[i]; - } - } - //} - - //else if ((targetVessel.packed || !targetVessel.loaded) && targetDockingPorts.Length > 0) - //{ - // targetDockingPorts = new ModuleDockingNode[0]; - //} - } - // Time in seconds until impact. 0 if there is no impact. private double timeToImpact_; internal double timeToImpact From 29fb75e9c0a216277e0da2898af005d58092054d Mon Sep 17 00:00:00 2001 From: Sovetskysoyuz Date: Thu, 11 Feb 2021 15:55:58 -0500 Subject: [PATCH 14/48] Removed logging --- Source/MASVesselComputer.cs | 24 +----------------------- 1 file changed, 1 insertion(+), 23 deletions(-) diff --git a/Source/MASVesselComputer.cs b/Source/MASVesselComputer.cs index bd3031ee..a998883c 100644 --- a/Source/MASVesselComputer.cs +++ b/Source/MASVesselComputer.cs @@ -124,14 +124,7 @@ private void RefreshData() PrepareResourceData(); UpdateModuleData(); - try - { - UpdateAttitude(); - } - catch (Exception e) - { - throw new ArgumentException("Error in UpdateAttitude:" + e.Source + e.TargetSite + e.Data + e.StackTrace, e); - } + UpdateAttitude(); UpdateAltitudes(); UpdateManeuverNode(); UpdateTarget(); @@ -636,14 +629,11 @@ void UpdateAttitude() navballAttitudeGimbal = vesselOrientationCorrection * Quaternion.Inverse(referenceTransform.rotation); Vector3 relativePositionVector = (referenceTransform.position - mainBody.position).normalized; - //Utility.LogMessage(this, "Past gate 1"); Quaternion relativeGimbal = navballAttitudeGimbal * Quaternion.LookRotation( Vector3.ProjectOnPlane(relativePositionVector + mainBody.transform.up, (relativePositionVector)), relativePositionVector); - //Utility.LogMessage(this, "Past gate 2"); - // We have to do all sorts of voodoo to get the navball // gimbal rotated so the rendered navball behaves the same // as navballs. @@ -667,14 +657,10 @@ void UpdateAttitude() surfaceAttitude.z = -surfaceAttitude.z; } - //Utility.LogMessage(this, "Past gate 3"); - double headingChange = Utility.NormalizeLongitude(surfaceAttitude.y - lastHeading); lastHeading = surfaceAttitude.y; headingRate = 0.875 * headingRate + 0.125 * headingChange / TimeWarp.fixedDeltaTime; - //Utility.LogMessage(this, "Past gate 4"); - up = vessel.upAxis; prograde = vessel.obt_velocity.normalized; surfacePrograde = vessel.srf_vel_direction; @@ -682,14 +668,10 @@ void UpdateAttitude() normal = -Vector3.Cross(radialOut, prograde).normalized; // TODO: does Vector3.OrthoNormalize do anything for me here? - //Utility.LogMessage(this, "Past gate 5"); - right = vessel.GetTransform().right; forward = vessel.GetTransform().up; top = vessel.GetTransform().forward; - //Utility.LogMessage(this, "Past gate 6"); - // We base our surface vector off of UP and RIGHT, unless roll is extreme. if (Mathf.Abs(Vector3.Dot(right, up)) > 0.995f) { @@ -702,8 +684,6 @@ void UpdateAttitude() surfaceRight = Vector3.Cross(surfaceForward, up); } - //Utility.LogMessage(this, "Past gate 7"); - Vector3 surfaceProgradeProjected = Vector3.ProjectOnPlane(surfacePrograde, up); progradeHeading = Vector3.Angle(surfaceProgradeProjected, vessel.north); if (Vector3.Dot(surfaceProgradeProjected, vessel.east) < 0.0) @@ -711,8 +691,6 @@ void UpdateAttitude() progradeHeading = 360.0f - progradeHeading; } - //Utility.LogMessage(this, "Past gate 8"); - // TODO: Am I computing normal wrong? // TODO: orbit.GetOrbitNormal() appears to return a vector in the opposite // direction when in the inertial frame of reference, but it's perpendicular From bc6f2eaed527d223550c4ec03df53ea5c9ff0dd0 Mon Sep 17 00:00:00 2001 From: Sovetskysoyuz Date: Thu, 11 Feb 2021 16:01:25 -0500 Subject: [PATCH 15/48] Add CreateTypeAlarm --- Source/MASIKAC.cs | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/Source/MASIKAC.cs b/Source/MASIKAC.cs index cf5037e6..115aa547 100644 --- a/Source/MASIKAC.cs +++ b/Source/MASIKAC.cs @@ -186,6 +186,42 @@ public string CreateAlarm(string name, double UT) return string.Empty; } + /// + /// Create an alarm of specified at the time specified by `UT`, using the name `name`. This alarm is + /// assigned to the current vessel ID. + /// + /// The type of alarm to create." + /// The short name to apply to the alarm. + /// The UT when the alarm should fire. + /// The alarm ID (a string), or an empty string if the method failed. + public string CreateTypeAlarm(string alarmTypeStr, string name, double UT) + { + if (KACWrapper.InstanceExists) + { + KACWrapper.KACAPI.AlarmTypeEnum alarmType = (KACWrapper.KACAPI.AlarmTypeEnum)Enum.Parse(typeof(KACWrapper.KACAPI.AlarmTypeEnum), alarmTypeStr); + + string alarmID = KACWrapper.KAC.CreateAlarm(alarmType, name, UT); + + if (string.IsNullOrEmpty(alarmID)) + { + return string.Empty; + } + else + { + var newAlarm = KACWrapper.KAC.Alarms.Find(x => x.ID == alarmID); + if (newAlarm != null) + { + newAlarm.VesselID = vessel.id.ToString(); + } + + return alarmID; + } + + } + + return string.Empty; + } + /// /// Attempts to remove the alarm identified by `alarmID`. Normally, `alarmID` is the return /// value from `fc.CreateAlarm()`. From 0dae671aede66770b0f5739734a1703bd16125f4 Mon Sep 17 00:00:00 2001 From: Sovetskysoyuz Date: Thu, 11 Feb 2021 16:02:56 -0500 Subject: [PATCH 16/48] Removed KAC changes split into other branch --- Source/MASIKAC.cs | 36 ------------------------------------ 1 file changed, 36 deletions(-) diff --git a/Source/MASIKAC.cs b/Source/MASIKAC.cs index 115aa547..cf5037e6 100644 --- a/Source/MASIKAC.cs +++ b/Source/MASIKAC.cs @@ -186,42 +186,6 @@ public string CreateAlarm(string name, double UT) return string.Empty; } - /// - /// Create an alarm of specified at the time specified by `UT`, using the name `name`. This alarm is - /// assigned to the current vessel ID. - /// - /// The type of alarm to create." - /// The short name to apply to the alarm. - /// The UT when the alarm should fire. - /// The alarm ID (a string), or an empty string if the method failed. - public string CreateTypeAlarm(string alarmTypeStr, string name, double UT) - { - if (KACWrapper.InstanceExists) - { - KACWrapper.KACAPI.AlarmTypeEnum alarmType = (KACWrapper.KACAPI.AlarmTypeEnum)Enum.Parse(typeof(KACWrapper.KACAPI.AlarmTypeEnum), alarmTypeStr); - - string alarmID = KACWrapper.KAC.CreateAlarm(alarmType, name, UT); - - if (string.IsNullOrEmpty(alarmID)) - { - return string.Empty; - } - else - { - var newAlarm = KACWrapper.KAC.Alarms.Find(x => x.ID == alarmID); - if (newAlarm != null) - { - newAlarm.VesselID = vessel.id.ToString(); - } - - return alarmID; - } - - } - - return string.Empty; - } - /// /// Attempts to remove the alarm identified by `alarmID`. Normally, `alarmID` is the return /// value from `fc.CreateAlarm()`. From c0d64dc6857547e4495aef9e901c423ab40a34ea Mon Sep 17 00:00:00 2001 From: Sovetskysoyuz Date: Thu, 11 Feb 2021 16:08:04 -0500 Subject: [PATCH 17/48] Address infinite UT bug --- Source/ApproachSolverBW.cs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/Source/ApproachSolverBW.cs b/Source/ApproachSolverBW.cs index 358953e3..95f125bc 100644 --- a/Source/ApproachSolverBW.cs +++ b/Source/ApproachSolverBW.cs @@ -346,13 +346,16 @@ internal void SolveOrbitIntercept(Orbit vesselOrbit, Orbit targetOrbit) //Utility.LogMessage(this, "target1: {0:0} to {1:0}, transitions = {3} / {2}", target1.StartUT, target1.EndUT, target1.patchEndTransition, target1.patchStartTransition); //Utility.LogMessage(this, "target2: {0:0} to {1:0}, transitions = {3} / {2}", target2.StartUT, target2.EndUT, target2.patchEndTransition, target2.patchStartTransition); - while (target1 != target2) + while (target1 != target2 && !Double.IsInfinity(vessel.EndUT)) { FindClosest(vessel, target1, Math.Max(now, target1.StartUT), target1.EndUT, 0, ref closestDistance, ref closestUT); target1 = target1.nextPatch; } - FindClosest(vessel, target1, now, then, 0, ref closestDistance, ref closestUT); + if (!Double.IsInfinity(then)) + { + FindClosest(vessel, target1, now, then, 0, ref closestDistance, ref closestUT); + } vessel = vessel.nextPatch; } @@ -369,13 +372,16 @@ internal void SolveOrbitIntercept(Orbit vesselOrbit, Orbit targetOrbit) target1 = FindOrbit(targetOrbit, now); target2 = FindOrbit(target1, then); - while (target1 != target2) + while (target1 != target2 && !Double.IsInfinity(vessel.EndUT)) { FindClosest(vessel, target1, Math.Max(now, target1.StartUT), target1.EndUT, 0, ref closestDistance, ref closestUT); target1 = target1.nextPatch; } - FindClosest(vessel, target1, now, then, 0, ref closestDistance, ref closestUT); + if (!Double.IsInfinity(then) && !Double.IsInfinity(now)) + { + FindClosest(vessel, target1, now, then, 0, ref closestDistance, ref closestUT); + } now = then; then += vessel.period; } From b2c561495236825455265d352f2e7bcff31ebc09 Mon Sep 17 00:00:00 2001 From: Sovetskysoyuz Date: Thu, 11 Feb 2021 16:12:23 -0500 Subject: [PATCH 18/48] Add logging for UpdateNeighboringVessels --- Source/MASFlightComputerProxy.cs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/Source/MASFlightComputerProxy.cs b/Source/MASFlightComputerProxy.cs index 6504a293..812467b3 100644 --- a/Source/MASFlightComputerProxy.cs +++ b/Source/MASFlightComputerProxy.cs @@ -243,8 +243,14 @@ private void UpdateNeighboringVessels() localVessels.Clear(); distanceComparer.vesselPosition = vessel.GetTransform().position; - Array.Sort(neighboringVessels, distanceComparer); - + try + { + Array.Sort(neighboringVessels, distanceComparer); + } + catch (Exception e) + { + throw new ArgumentException("Error in UpdateNeighboringVessels due to distanceComparer: \"" + e.Source + e.TargetSite + e.Data + e.StackTrace + neighboringVessels + "\"", e); + } neighboringVesselsCurrent = true; } } From b588f3bf07806318ff3d3a7cc09467da70e81f22 Mon Sep 17 00:00:00 2001 From: Sovetskysoyuz Date: Thu, 11 Feb 2021 16:09:36 -0500 Subject: [PATCH 19/48] Undo changes split into Debugging --- Source/ApproachSolverBW.cs | 27 +++++++-------------------- Source/MASFlightComputerProxy.cs | 9 +-------- 2 files changed, 8 insertions(+), 28 deletions(-) diff --git a/Source/ApproachSolverBW.cs b/Source/ApproachSolverBW.cs index f69e6e8b..e567c8c7 100644 --- a/Source/ApproachSolverBW.cs +++ b/Source/ApproachSolverBW.cs @@ -346,22 +346,16 @@ internal void SolveOrbitIntercept(Orbit vesselOrbit, Orbit targetOrbit) //Utility.LogMessage(this, "target1: {0:0} to {1:0}, transitions = {3} / {2}", target1.StartUT, target1.EndUT, target1.patchEndTransition, target1.patchStartTransition); //Utility.LogMessage(this, "target2: {0:0} to {1:0}, transitions = {3} / {2}", target2.StartUT, target2.EndUT, target2.patchEndTransition, target2.patchStartTransition); - while (target1 != target2 && !Double.IsInfinity(vessel.EndUT)) + while (target1 != target2) { - Utility.LogInfo(this, "ApproachSolver first FindClosest: vessel {0}, target1 {1}, target1.StartUT {2}, target1.EndUT{3}, closestDistance{4}, closestUT{5}", - vessel, target1, target1.StartUT, target1.EndUT, closestDistance, closestUT); FindClosest(vessel, target1, Math.Max(now, target1.StartUT), target1.EndUT, 0, ref closestDistance, ref closestUT); target1 = target1.nextPatch; } - Utility.LogInfo(this, "ApproachSolver second FindClosest: vessel {0}, target1 {1}, now {2}, then {3}, closestDistance{4}, closestUT{5}", - vessel, target1, now, then, closestDistance, closestUT); - if (!Double.IsInfinity(then)) - { - FindClosest(vessel, target1, now, then, 0, ref closestDistance, ref closestUT); - } + FindClosest(vessel, target1, now, then, 0, ref closestDistance, ref closestUT); + vessel = vessel.nextPatch; } @@ -377,23 +371,16 @@ internal void SolveOrbitIntercept(Orbit vesselOrbit, Orbit targetOrbit) target1 = FindOrbit(targetOrbit, now); target2 = FindOrbit(target1, then); - while (target1 != target2 && !Double.IsInfinity(vessel.EndUT)) + while (target1 != target2) { - Utility.LogInfo(this, "ApproachSolver third FindClosest: vessel {0}, target1 {1}, target1.StartUT {2}, target1.EndUT{3}, closestDistance{4}, closestUT{5}", - vessel, target1, target1.StartUT, target1.EndUT, closestDistance, closestUT); + FindClosest(vessel, target1, Math.Max(now, target1.StartUT), target1.EndUT, 0, ref closestDistance, ref closestUT); target1 = target1.nextPatch; } - if (!Double.IsInfinity(then) && !Double.IsInfinity(now)) - { - Utility.LogInfo(this, "ApproachSolver fourth FindClosest: vessel {0}, target1 {1}, now {2}, then {3}, closestDistance{4}, closestUT{5}", - vessel, target1, now, then, closestDistance, closestUT); - FindClosest(vessel, target1, now, then, 0, ref closestDistance, ref closestUT); - } - - now = then; + FindClosest(vessel, target1, now, then, 0, ref closestDistance, ref closestUT); + now = then; then += vessel.period; } diff --git a/Source/MASFlightComputerProxy.cs b/Source/MASFlightComputerProxy.cs index 27df2055..6504a293 100644 --- a/Source/MASFlightComputerProxy.cs +++ b/Source/MASFlightComputerProxy.cs @@ -243,14 +243,7 @@ private void UpdateNeighboringVessels() localVessels.Clear(); distanceComparer.vesselPosition = vessel.GetTransform().position; - try - { - Array.Sort(neighboringVessels, distanceComparer); - } - catch (Exception e) - { - throw new ArgumentException("Error in UpdateNeighboringVessels due to distanceComparer: \"" + e.Source + e.TargetSite + e.Data + e.StackTrace + neighboringVessels + "\"", e); - } + Array.Sort(neighboringVessels, distanceComparer); neighboringVesselsCurrent = true; } From eee22f3a402eb77f5c08cd43216e577f8cbaaadf Mon Sep 17 00:00:00 2001 From: Sovetskysoyuz Date: Thu, 11 Feb 2021 16:15:31 -0500 Subject: [PATCH 20/48] Add ClearOneManeuverNode --- Source/MASFlightComputerProxy2.cs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/Source/MASFlightComputerProxy2.cs b/Source/MASFlightComputerProxy2.cs index 8f809676..8927df3c 100644 --- a/Source/MASFlightComputerProxy2.cs +++ b/Source/MASFlightComputerProxy2.cs @@ -280,6 +280,23 @@ public double ClearManeuverNode() return 0.0; } + /// + /// Clear first scheduled maneuver node. + /// + /// 1 if any nodes were cleared, 0 if no nodes were cleared. + public double ClearOneManeuverNode() + { + if (vessel.patchedConicSolver != null) + { + int nodeCount = vessel.patchedConicSolver.maneuverNodes.Count; + vessel.patchedConicSolver.maneuverNodes[0].RemoveSelf(); + + return (nodeCount > 0) ? 1.0 : 0.0; + } + + return 0.0; + } + /// /// Returns the apoapsis of the orbit that results from the scheduled maneuver. /// From ba6a2093aecc773798c4d6f99e0fe26338616256 Mon Sep 17 00:00:00 2001 From: Sovetskysoyuz Date: Thu, 11 Feb 2021 16:16:10 -0500 Subject: [PATCH 21/48] Undo more split changes (ClearOneNode) --- Source/MASFlightComputerProxy2.cs | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/Source/MASFlightComputerProxy2.cs b/Source/MASFlightComputerProxy2.cs index f65d4cb3..43f1f314 100644 --- a/Source/MASFlightComputerProxy2.cs +++ b/Source/MASFlightComputerProxy2.cs @@ -280,24 +280,6 @@ public double ClearManeuverNode() return 0.0; } - /// - /// Clear first scheduled maneuver node. - /// - /// 1 if any nodes were cleared, 0 if no nodes were cleared. - public double ClearOneManeuverNode() - { - if (vessel.patchedConicSolver != null) - { - int nodeCount = vessel.patchedConicSolver.maneuverNodes.Count; - // TODO: what is vessel.patchedConicSolver.flightPlan? And do I care? - vessel.patchedConicSolver.maneuverNodes[0].RemoveSelf(); - - return (nodeCount > 0) ? 1.0 : 0.0; - } - - return 0.0; - } - /// /// Returns the apoapsis of the orbit that results from the scheduled maneuver. /// From f4e4c4ed3a46e87cc92f7a4438528b2c41f0b6b7 Mon Sep 17 00:00:00 2001 From: Sovetskysoyuz Date: Thu, 11 Feb 2021 16:17:34 -0500 Subject: [PATCH 22/48] Remove extra files --- ModifiedCode/ApproachSolverBW.cs | 418 -- ModifiedCode/MASComponentAudioPlayer.cs | 284 -- ModifiedCode/MASFlightComputer.cs | 1986 -------- ModifiedCode/MASFlightComputerProxy.cs | 5462 ---------------------- ModifiedCode/MASFlightComputerProxy2.cs | 4187 ----------------- ModifiedCode/MASIKAC.cs | 266 -- ModifiedCode/MASITransfer.cs | 1746 ------- ModifiedCode/MASVesselComputer.cs | 1499 ------ ModifiedCode/MASVesselComputerModules.cs | 1815 ------- 9 files changed, 17663 deletions(-) delete mode 100644 ModifiedCode/ApproachSolverBW.cs delete mode 100644 ModifiedCode/MASComponentAudioPlayer.cs delete mode 100644 ModifiedCode/MASFlightComputer.cs delete mode 100644 ModifiedCode/MASFlightComputerProxy.cs delete mode 100644 ModifiedCode/MASFlightComputerProxy2.cs delete mode 100644 ModifiedCode/MASIKAC.cs delete mode 100644 ModifiedCode/MASITransfer.cs delete mode 100644 ModifiedCode/MASVesselComputer.cs delete mode 100644 ModifiedCode/MASVesselComputerModules.cs diff --git a/ModifiedCode/ApproachSolverBW.cs b/ModifiedCode/ApproachSolverBW.cs deleted file mode 100644 index ec0a4cbf..00000000 --- a/ModifiedCode/ApproachSolverBW.cs +++ /dev/null @@ -1,418 +0,0 @@ -//#define USE_OLD_SOLVER -/***************************************************************************** - * The MIT License (MIT) - * - * Copyright (c) 2016-2018 MOARdV - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to - * deal in the Software without restriction, including without limitation the - * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or - * sell copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - * - ****************************************************************************/ -using System; -using System.Collections.Generic; - -namespace AvionicsSystems -{ - internal class ApproachSolver - { - /// - /// How many subdivisions do we want to use per iteration to find the - /// local minimum? - /// - private static readonly int NumSubdivisions = 64; - - /// - /// How many recursions do we want to allow before we punt? - /// - private static readonly int MaxRecursions = 16; - - /// - /// How many orbits into the future do we want to look for the closest - /// approach? - /// - private static readonly int NumOrbitsLookAhead = 6; - - /// - /// Iterate through the next several patches on the orbit to find the - /// first one that shares the same reference body as the supplied - /// parameter. - /// - /// The orbit we're starting from. - /// The CelestialBody that we're heading for. - /// -#if USE_OLD_SOLVER - static private Orbit SelectClosestOrbit(Orbit startOrbit, CelestialBody referenceBody) - { - Orbit checkorbit = startOrbit; - int orbitcount = 0; - - while (checkorbit.nextPatch != null && checkorbit.patchEndTransition != Orbit.PatchTransitionType.FINAL && orbitcount < 3) - { - checkorbit = checkorbit.nextPatch; - orbitcount++; - if (checkorbit.referenceBody == referenceBody) - { - return checkorbit; - } - } - - return startOrbit; - } -#endif - - /// - /// Iterate across one step of the search. - /// - static private void FindClosest(Orbit sourceOrbit, Orbit targetOrbit, double startUT, double endUT, int recursionDepth, ref double closestDistance, ref double closestUT) - { - double deltaT = (endUT - startUT) / (double)NumSubdivisions; - double closestDistSq = closestDistance * closestDistance; - double closestTime = (startUT + endUT) * 0.5; - bool foundClosest = false; - for (double t = startUT; t <= endUT; t += deltaT) - { - Vector3d vesselPos = sourceOrbit.getPositionAtUT(t); - Vector3d targetPos = targetOrbit.getPositionAtUT(t); - - double distSq = (vesselPos - targetPos).sqrMagnitude; - if (distSq < closestDistSq) - { - closestDistSq = distSq; - closestTime = t; - foundClosest = true; - } - } - - if (foundClosest) - { - closestDistance = Math.Sqrt(closestDistSq); - closestUT = closestTime; - - if (deltaT < 1.0) - { - // If our timesteps are this small, I think - // this is an accurate enough estimate. - return; - } - if (recursionDepth == MaxRecursions) - { - // Hit recursion limit. Done. - return; - } - - FindClosest(sourceOrbit, targetOrbit, Math.Max(closestTime - deltaT, startUT), Math.Min(closestTime + deltaT, endUT), recursionDepth + 1, ref closestDistance, ref closestUT); - } - - // Did not improve on the previous iteration. Don't recurse. - } - - /// - /// Find the first orbit / orbit patch that is valid at the specified validUT. - /// - /// - /// - /// -#if !USE_OLD_SOLVER - private static Orbit FindOrbit(Orbit startingOrbit, double validUT) - { - if ((startingOrbit.StartUT <= validUT && startingOrbit.EndUT >= validUT) || startingOrbit.patchEndTransition == Orbit.PatchTransitionType.FINAL) - { - return startingOrbit; - } - - Orbit o = startingOrbit; - while (o.patchEndTransition != Orbit.PatchTransitionType.FINAL) - { - if (o.nextPatch == null) - { - // Not sure this is actually feasible. - return o; - } - o = o.nextPatch; - - if ((o.StartUT <= validUT && o.EndUT >= validUT) || o.patchEndTransition == Orbit.PatchTransitionType.FINAL) - { - return o; - } - } - - Utility.LogStaticError("... Wait! Exception because o.nextPath {0} and endTransition is {1}", - (o.nextPatch != null) ? "!null" : "null", o.patchEndTransition); - - throw new ArgumentNullException("FindOrbit failed... no valid orbits existed during validUT"); - } -#endif - - /// - /// Reset the "I'm ready" flag. - /// - internal void ResetComputation() - { - resultsReady = false; - } - - internal bool resultsReady { get; private set; } - internal double targetClosestDistance { get; private set; } - internal double targetClosestUT { get; private set; } - internal double targetClosestSpeed { get; private set; } - - /// - /// Find the closest approach to a given body. - /// - /// We do this by looking for every orbit patch that lists the targetBody - /// as its referenceBody, and then looking for the lowest periapsis amongst - /// those patches. We short-circuit the search at the first patch whose - /// Pe is below the datum (or sea-level). - /// - /// If no patches orbit the target body, we fall back to the conventional - /// orbital segments search. - /// - /// The starting orbit (either the vessel.orbit, or the first patch of a maneuver). - /// The body we are intercepting. - internal void SolveBodyIntercept(Orbit vesselOrbit, CelestialBody targetBody) - { - targetClosestDistance = double.MaxValue; - - double targetRadius = targetBody.Radius; - bool resultsFound = false; - - if (vesselOrbit.referenceBody == targetBody) - { - targetClosestDistance = vesselOrbit.PeR; - targetClosestUT = vesselOrbit.StartUT + vesselOrbit.timeToPe; - targetClosestSpeed = vesselOrbit.getOrbitalSpeedAt(targetClosestUT); - - if (targetClosestDistance < targetRadius) - { - resultsReady = true; - return; // Early return: the first segment we tested impacts the surface. - } - - resultsFound = true; - } - - Orbit checkorbit = vesselOrbit; - - while (checkorbit.nextPatch != null && checkorbit.patchEndTransition != Orbit.PatchTransitionType.FINAL) - { - checkorbit = checkorbit.nextPatch; - if (checkorbit.referenceBody == targetBody) - { - if (checkorbit.PeR < targetClosestDistance) - { - targetClosestDistance = checkorbit.PeR; - targetClosestUT = checkorbit.StartUT + checkorbit.timeToPe; - targetClosestSpeed = checkorbit.getOrbitalSpeedAt(targetClosestUT); - - if (checkorbit.PeR < targetRadius) - { - resultsReady = true; - return; // Early return: the first segment we tested impacts the surface. - } - - resultsFound = true; - } - } - } - - if (!resultsFound) - { - // None of the orbits orbit the target body. Fall back to the standard - // solver. - SolveOrbitIntercept(vesselOrbit, targetBody.orbit); - } - } - - /// - /// Find the closest approach between two objects in orbit(s). - /// - /// - /// - internal void SolveOrbitIntercept(Orbit vesselOrbit, Orbit targetOrbit) - { -#if USE_OLD_SOLVER - try - { - double now = Planetarium.GetUniversalTime(); - - Orbit startOrbit = SelectClosestOrbit(vesselOrbit, targetOrbit.referenceBody); - - if (startOrbit.eccentricity >= 1.0 || targetOrbit.eccentricity >= 1.0) - { - // Hyperbolic orbit is involved. Don't check multiple orbits. - // This is a fairly quick search. - - double startTime = now; - startTime = Math.Max(startTime, startOrbit.StartUT); - startTime = Math.Max(startTime, targetOrbit.StartUT); - - double endTime; - if (startOrbit.eccentricity >= 1.0) - { - endTime = startOrbit.EndUT; - } - else - { - endTime = startTime + startOrbit.period * 6.0; - } - if (targetOrbit.eccentricity >= 1.0) - { - endTime = Math.Min(endTime, targetOrbit.EndUT); - } - else - { - endTime = Math.Min(endTime, targetOrbit.period * 6.0); - } - - double targetClosestDistance = float.MaxValue; - double targetClosestTime = float.MaxValue; - FindClosest(startOrbit, targetOrbit, startTime, endTime, 0, ref targetClosestDistance, ref targetClosestTime); - - this.targetClosestDistance = targetClosestDistance; - this.targetClosestUT = targetClosestTime; - Vector3d relativeVelocity = targetOrbit.getOrbitalVelocityAtUT(targetClosestTime) - startOrbit.getOrbitalVelocityAtUT(targetClosestTime); - this.targetClosestSpeed = relativeVelocity.magnitude; - } - else - { - double startTime = now; - double endTime = startTime + startOrbit.period; - targetClosestDistance = float.MaxValue; - - for (int i = 0; i < NumOrbitsLookAhead; ++i) - { - double closestDistance = float.MaxValue; - double closestTime = float.MaxValue; - - FindClosest(startOrbit, targetOrbit, startTime, endTime, 0, ref closestDistance, ref closestTime); - startTime += startOrbit.period; - endTime += startOrbit.period; - - if (closestDistance < this.targetClosestDistance) - { - this.targetClosestDistance = closestDistance; - this.targetClosestUT = closestTime; - } - } - - Vector3d relativeVelocity = targetOrbit.getOrbitalVelocityAtUT(this.targetClosestUT) - startOrbit.getOrbitalVelocityAtUT(this.targetClosestUT); - this.targetClosestSpeed = relativeVelocity.magnitude; - - //this.targetClosestDistance = closestDistance; - //this.targetClosestUT = closestTime; - } - this.resultsReady = true; - } - catch (Exception e) - { - Utility.LogInfo("ApproachSolver threw {0}", e); - } -#else - // Set up initial conditions - Orbit vessel = vesselOrbit; - Orbit target1; - Orbit target2; - - double now; - double then; - - double closestDistance = float.MaxValue; - double closestUT = 0.0; - - while (vessel.patchEndTransition != Orbit.PatchTransitionType.FINAL) - { - now = vessel.StartUT; - then = vessel.EndUT; - - target1 = FindOrbit(targetOrbit, vessel.StartUT); - target2 = FindOrbit(targetOrbit, vessel.EndUT); - - //Utility.LogMessage(this, "vessel : {0:0} to {1:0}, transitions = {3} / {2}", vessel.StartUT, vessel.EndUT, vessel.patchEndTransition, vessel.patchStartTransition); - //Utility.LogMessage(this, "target1: {0:0} to {1:0}, transitions = {3} / {2}", target1.StartUT, target1.EndUT, target1.patchEndTransition, target1.patchStartTransition); - //Utility.LogMessage(this, "target2: {0:0} to {1:0}, transitions = {3} / {2}", target2.StartUT, target2.EndUT, target2.patchEndTransition, target2.patchStartTransition); - - while (target1 != target2 && !Double.IsInfinity(vessel.EndUT)) - { - - Utility.LogInfo(this, "ApproachSolver first FindClosest: vessel {0}, target1 {1}, target1.StartUT {2}, target1.EndUT{3}, closestDistance{4}, closestUT{5}", - vessel, target1, target1.StartUT, target1.EndUT, closestDistance, closestUT); - FindClosest(vessel, target1, Math.Max(now, target1.StartUT), target1.EndUT, 0, ref closestDistance, ref closestUT); - - target1 = target1.nextPatch; - } - - Utility.LogInfo(this, "ApproachSolver second FindClosest: vessel {0}, target1 {1}, now {2}, then {3}, closestDistance{4}, closestUT{5}", - vessel, target1, now, then, closestDistance, closestUT); - if (!Double.IsInfinity(then)) - { - FindClosest(vessel, target1, now, then, 0, ref closestDistance, ref closestUT); - } - vessel = vessel.nextPatch; - } - - // Final transition. - now = vessel.StartUT; - then = vessel.EndUT; - - // Don't bother searching ahead with hyperbolic orbits - int orbitsToCheck = (vessel.eccentricity < 1.0) ? NumOrbitsLookAhead : 1; - - for (int i = 0; i < orbitsToCheck; ++i) - { - target1 = FindOrbit(targetOrbit, now); - target2 = FindOrbit(target1, then); - - while (target1 != target2 && !Double.IsInfinity(vessel.EndUT)) - { - Utility.LogInfo(this, "ApproachSolver third FindClosest: vessel {0}, target1 {1}, target1.StartUT {2}, target1.EndUT{3}, closestDistance{4}, closestUT{5}", - vessel, target1, target1.StartUT, target1.EndUT, closestDistance, closestUT); - FindClosest(vessel, target1, Math.Max(now, target1.StartUT), target1.EndUT, 0, ref closestDistance, ref closestUT); - - target1 = target1.nextPatch; - } - - if (!Double.IsInfinity(then) && !Double.IsInfinity(now)) - { - Utility.LogInfo(this, "ApproachSolver fourth FindClosest: vessel {0}, target1 {1}, now {2}, then {3}, closestDistance{4}, closestUT{5}", - vessel, target1, now, then, closestDistance, closestUT); - FindClosest(vessel, target1, now, then, 0, ref closestDistance, ref closestUT); - } - - now = then; - then += vessel.period; - } - - resultsReady = true; - - if (closestUT > 0.0) - { - targetClosestUT = closestUT; - targetClosestDistance = closestDistance; - targetClosestSpeed = (vesselOrbit.GetFrameVelAtUT(closestUT) - targetOrbit.GetFrameVelAtUT(closestUT)).magnitude; - } - else - { - // Did not solve. Use "now" as closest approach. - targetClosestUT = vesselOrbit.StartUT; - targetClosestDistance = (vesselOrbit.getPositionAtUT(targetClosestUT) - targetOrbit.getPositionAtUT(targetClosestUT)).magnitude; - targetClosestSpeed = (vesselOrbit.GetFrameVelAtUT(vesselOrbit.StartUT) - targetOrbit.GetFrameVelAtUT(vesselOrbit.StartUT)).magnitude; - } -#endif - } - } -} diff --git a/ModifiedCode/MASComponentAudioPlayer.cs b/ModifiedCode/MASComponentAudioPlayer.cs deleted file mode 100644 index 587d5edb..00000000 --- a/ModifiedCode/MASComponentAudioPlayer.cs +++ /dev/null @@ -1,284 +0,0 @@ -/***************************************************************************** - * The MIT License (MIT) - * - * Copyright (c) 2016-2018 MOARdV - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to - * deal in the Software without restriction, including without limitation the - * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or - * sell copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - * - ****************************************************************************/ -using System; -using System.Collections.Generic; -using System.Text; -using UnityEngine; - -namespace AvionicsSystems -{ - internal class MASComponentAudioPlayer : IMASSubComponent - { - private float pitch = 1.0f; - private float volume = 1.0f; - private Variable soundVariable; - private AudioSource audioSource; - private readonly bool mustPlayOnce = false; - private bool hasAudioClip = false; - private bool currentState = false; - private readonly PlaybackMode playbackTrigger = PlaybackMode.ON; - - private enum PlaybackMode - { - ON, - OFF, - BOTH, - LOOP - }; - - internal MASComponentAudioPlayer(ConfigNode config, InternalProp prop, MASFlightComputer comp) - : base(config, prop, comp) - { - string variableName = string.Empty; - string pitchVariableName = string.Empty; - string volumeVariableName = string.Empty; - if (!config.TryGetValue("volume", ref volumeVariableName)) - { - volumeVariableName = "1"; - } - - if (!config.TryGetValue("pitch", ref pitchVariableName)) - { - pitchVariableName = "1"; - } - - string sound = string.Empty; - string soundVariableName = string.Empty; - if (!config.TryGetValue("sound", ref sound) || string.IsNullOrEmpty(sound)) - { - if (!config.TryGetValue("variableSound", ref soundVariableName) || string.IsNullOrEmpty(soundVariableName)) - { - throw new ArgumentException("Missing or invalid parameters 'sound' and/or 'soundVariable' in AUDIO_PLAYER " + name); - } - } - - if (!config.TryGetValue("mustPlayOnce", ref mustPlayOnce)) - { - mustPlayOnce = false; - } - - //Try Load audio - AudioClip clip = null; - if (!string.IsNullOrEmpty(sound)) - { - clip = GameDatabase.Instance.GetAudioClip(sound); - if (clip == null) - { - throw new ArgumentException("Unable to load 'sound' " + sound + " in AUDIO_PLAYER " + name); - } - else - { - hasAudioClip = true; - } - } - - string playbackTrigger = string.Empty; - config.TryGetValue("trigger", ref playbackTrigger); - if (string.IsNullOrEmpty(playbackTrigger)) - { - throw new ArgumentException("Missing parameter 'trigger' in AUDIO_PLAYER " + name); - } - else - { - playbackTrigger = playbackTrigger.Trim(); - if (playbackTrigger == PlaybackMode.ON.ToString()) - { - this.playbackTrigger = PlaybackMode.ON; - } - else if (playbackTrigger == PlaybackMode.OFF.ToString()) - { - this.playbackTrigger = PlaybackMode.OFF; - } - else if (playbackTrigger == PlaybackMode.BOTH.ToString()) - { - this.playbackTrigger = PlaybackMode.BOTH; - } - else if (playbackTrigger == PlaybackMode.LOOP.ToString()) - { - if (mustPlayOnce) - { - throw new ArgumentException("Cannot use 'mustPlayOnce' with looping audio in AUDIO_PLAYER" + name); - } - this.playbackTrigger = PlaybackMode.LOOP; - } - else - { - throw new ArgumentException("Unrecognized parameter 'trigger = " + playbackTrigger + "' in AUDIO_PLAYER " + name); - } - } - - Transform audioTransform = new GameObject().transform; - audioTransform.gameObject.name = Utility.ComposeObjectName(this.GetType().Name, name, prop.propID); - audioTransform.gameObject.layer = prop.transform.gameObject.layer; - audioTransform.SetParent(prop.transform, false); - audioSource = audioTransform.gameObject.AddComponent(); - - audioSource.clip = clip; - audioSource.Stop(); - audioSource.volume = GameSettings.SHIP_VOLUME; - audioSource.rolloffMode = AudioRolloffMode.Logarithmic; - audioSource.maxDistance = 8.0f; - audioSource.minDistance = 2.0f; - audioSource.dopplerLevel = 0.0f; - audioSource.panStereo = 0.0f; - audioSource.playOnAwake = false; - audioSource.loop = (this.playbackTrigger == PlaybackMode.LOOP); - audioSource.pitch = 1.0f; - - if (!config.TryGetValue("variable", ref variableName) || string.IsNullOrEmpty(variableName)) - { - throw new ArgumentException("Invalid or missing 'variable' in ANIMATION_PLAYER " + name); - } - variableName = variableName.Trim(); - - audioSource.mute = ((CameraManager.Instance.currentCameraMode != CameraManager.CameraMode.IVA) && (CameraManager.Instance.currentCameraMode != CameraManager.CameraMode.Internal)); - - GameEvents.OnCameraChange.Add(OnCameraChange); - - variableRegistrar.RegisterVariableChangeCallback(pitchVariableName, (double newPitch) => - { - pitch = (float)newPitch; - audioSource.pitch = pitch; - }); - variableRegistrar.RegisterVariableChangeCallback(volumeVariableName, (double newVolume) => - { - volume = Mathf.Clamp01((float)newVolume); - audioSource.volume = GameSettings.SHIP_VOLUME * volume; - }); - variableRegistrar.RegisterVariableChangeCallback(variableName, VariableCallback); - if (!string.IsNullOrEmpty(soundVariableName)) - { - soundVariable = variableRegistrar.RegisterVariableChangeCallback(soundVariableName, SoundClipCallback, false); - // Initialize the audio. - SoundClipCallback(0.0); - } - } - - /// - /// Callback used when camera switches (so I can mute audio when not in craft). - /// - /// - private void OnCameraChange(CameraManager.CameraMode newCameraMode) - { - try - { - audioSource.mute = ((CameraManager.Instance.currentCameraMode != CameraManager.CameraMode.IVA) && (CameraManager.Instance.currentCameraMode != CameraManager.CameraMode.Internal)); - } - catch (Exception e) - { - throw new ArgumentException("Error in AudioPlayer OnCameraChange: \"" + e.Source + e.TargetSite + e.Data + e.StackTrace + CameraManager.Instance.currentCameraMode + "\"", e); - } - } - - /// - /// Callback that allows changing the audio clip attached to this player. - /// - private void SoundClipCallback(double dontCare) - { - audioSource.Stop(); - - AudioClip clip = GameDatabase.Instance.GetAudioClip(soundVariable.AsString()); - if (clip == null) - { - Utility.LogError(this, "Unable to load audio clip '{0}'.", soundVariable.AsString()); - hasAudioClip = false; - } - else - { - audioSource.clip = clip; - hasAudioClip = true; - PlayAudio(); - } - } - - /// - /// Update the audio play state. - /// - private void PlayAudio() - { - if (currentState) - { - if (playbackTrigger != PlaybackMode.OFF) - { - if (hasAudioClip) - { - audioSource.Play(); - } - } - else - { - if (!mustPlayOnce) - { - audioSource.Stop(); - } - } - } - else if (playbackTrigger == PlaybackMode.ON || playbackTrigger == PlaybackMode.LOOP) - { - if (!mustPlayOnce) - { - audioSource.Stop(); - } - } - else if (hasAudioClip) - { - audioSource.Play(); - } - } - - /// - /// Variable callback used to update the audio source when it is playing. - /// - /// - private void VariableCallback(double newValue) - { - bool newState = (newValue > 0.0); - - if (newState != currentState) - { - currentState = newState; - if (hasAudioClip == true) - { - // No audio clip: return early. - PlayAudio(); - } - } - } - - /// - /// Release resources - /// - public override void ReleaseResources(MASFlightComputer comp, InternalProp prop) - { - GameEvents.OnCameraChange.Remove(OnCameraChange); - - variableRegistrar.ReleaseResources(); - - audioSource.Stop(); - audioSource.clip = null; - audioSource = null; - } - } -} diff --git a/ModifiedCode/MASFlightComputer.cs b/ModifiedCode/MASFlightComputer.cs deleted file mode 100644 index e1b1c972..00000000 --- a/ModifiedCode/MASFlightComputer.cs +++ /dev/null @@ -1,1986 +0,0 @@ -//#define MEASURE_FC_FIXEDUPDATE -//#define LIST_LUA_VARIABLES -/***************************************************************************** - * The MIT License (MIT) - * - * Copyright (c) 2016-2020 MOARdV - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to - * deal in the Software without restriction, including without limitation the - * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or - * sell copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - * - ****************************************************************************/ -using MoonSharp.Interpreter; -using System; -using System.Collections; -using System.Collections.Generic; -using System.Diagnostics; -using System.Reflection; -using System.Text; -using UnityEngine; - -namespace AvionicsSystems -{ - /// - /// The MASFlightComputer manages the components attached to props inside - /// the part this computer is attached to. It is a central clearing-house - /// for the data requsted by the props, and it manages data that persists - /// across game sessions. - /// - public partial class MASFlightComputer : PartModule - { - /// - /// ID that is stored automatically on-save by KSP. This value is the - /// string version of fcId, so on load we can restore the Guid. - /// - [KSPField(isPersistant = true)] - public string flightComputerId = string.Empty; - - /// - /// Ship's description string copied from the editor. At flight time, we - /// will parse this to formulate the action group fields (equivalent to - /// AGMEMO in RPM). - /// - [KSPField(isPersistant = true)] - public string shipDescription = string.Empty; - internal string[] agMemoOff = { "AG0", "AG1", "AG2", "AG3", "AG4", "AG5", "AG6", "AG7", "AG8", "AG9" }; - internal string[] agMemoOn = { "AG0", "AG1", "AG2", "AG3", "AG4", "AG5", "AG6", "AG7", "AG8", "AG9" }; - internal string vesselDescription = string.Empty; - - /// - /// The maximum g-loading the command pod can sustain without disrupting - /// power. - /// - [KSPField] - public float gLimit = float.MaxValue; - - /// - /// The chance that power is disrupted at (gLimit + 1) Gs. This number - /// scales with G forces. A 1 represents 100% chance of a power disruption. - /// - [KSPField] - public float baseDisruptionChance = 0.0f; - internal float disruptionChance = 0.0f; - - /// - /// Does the command pod's instruments require power to function? - /// - [KSPField] - public bool requiresPower = false; - internal bool isPowered = true; - - /// - /// How much power does the MASFlightComputer draw on its own? Ignored if `requiresPower` is false. - /// - [KSPField] - public float rate = 0.0f; - - [KSPField] - public string powerOnVariable = string.Empty; - internal bool powerOnValid = true; - - [KSPField] - public float maxRot = 80.0f; - - [KSPField] - public float minPitch = -80.0f; - - [KSPField] - public float maxPitch = 45.0f; - - [KSPField] - public string startupScript = string.Empty; - - [KSPField] - public string onEnterIVA = string.Empty; - private DynValue enterIvaScript = null; - - [KSPField] - public string onExitIVA = string.Empty; - private DynValue exitIvaScript = null; - - /// - /// Our module ID (so each FC can be distinguished in a save file). - /// - private Guid fcId = Guid.Empty; - - /// - /// ID of our parent vessel (for some sanity checking) - /// - private Guid parentVesselId = Guid.Empty; - - /// - /// This flight computer's Lua context - /// - private Script script; - - /// - /// Instance of the flight computer proxy used in the Lua context. - /// - private MASFlightComputerProxy fcProxy; - - /// - /// Instance of the Chatterer proxy class. - /// - private MASIChatterer chattererProxy; - - /// - /// Instance of the aircraft engines proxy class. - /// - private MASIEngine engineProxy; - - /// - /// Instance of the FAR proxy class. - /// - private MASIFAR farProxy; - - /// - /// Instance of the Kerbal Alarm Clock proxy class. - /// - private MASIKAC kacProxy; - - /// - /// Instance of the Kerbal Engineer proxy class; - /// - private MASIKerbalEngineer keProxy; - - /// - /// Instance of the MechJeb proxy class. - /// - private MASIMechJeb mjProxy; - - /// - /// Instance of the Navigation proxy class. - /// - private MASINavigation navProxy; - - /// - /// Instance of the parachute proxy class. - /// - private MASIParachute parachuteProxy; - - /// - /// Instance of the Transfer proxy class. - /// - private MASITransfer transferProxy; - - /// - /// Instance of the VTOL manager proxy class. - /// - private MASIVTOL vtolProxy; - - /// - /// Have we initialized? - /// - internal bool initialized { get; private set; } - - /// - /// Has there been an addition or subtraction to the mutable variables list? - /// - private bool mutableVariablesChanged = false; - - /// - /// Dictionary of known RasterPropComputer-compatible named colors. - /// - private Dictionary namedColors = new Dictionary(); - - /// - /// Dictionary of loaded Action Lua wrappers. - /// - private Dictionary actions = new Dictionary(); - - /// - /// Dictionary of named monitors, for routing softkeys. - /// - private Dictionary monitors = new Dictionary(); - - /// - /// Set to true when any of the throttle keys are pressed. - /// - internal bool anyThrottleKeysPressed; - - /// - /// Custom MAS Action Group dictionary. - /// - internal Dictionary masActionGroup = new Dictionary(); - - /// - /// Reference to the current vessel computer. - /// - internal MASVesselComputer vc; - - /// - /// Reference to the current vessel autopilot. - /// - internal MASAutoPilot ap; - - /// - /// Additional EC required by subcomponents via `fc.IncreasePowerDraw(rate)`. Ignored if requiresPower is false. - /// - private float additionalEC = 0.0f; - - /// - /// Resource ID of the electric charge. - /// - private int resourceId = -1; - - /// - /// Reference to the current IVA Kerbal. - /// - internal Kerbal currentKerbal; - - /// - /// Is the current Kerbal suffering the effects of GLOC? - /// - internal bool currentKerbalBlackedOut; - - /// - /// Direct index to the vessel resource list. - /// - private int electricChargeIndex = -1; - - /// - /// The ModuleCommand on this IVA. - /// - private ModuleCommand commandModule = null; - private int activeControlPoint = 0; - - internal ModuleColorChanger colorChangerModule = null; - - internal static readonly string vesselIdLabel = "__vesselId"; - internal static readonly string vesselFilterLabel = "__vesselFilter"; - - internal int vesselFilterValue = 0; - internal List activeVesselFilter = new List(); - private readonly Dictionary vesselBitRemap = new Dictionary - { - { 1, VesselType.Ship}, - { 2, VesselType.Plane}, - { 3, VesselType.Probe}, - { 4, VesselType.Lander}, - { 5, VesselType.Station}, - { 6, VesselType.Relay}, - { 7, VesselType.Rover}, - { 8, VesselType.Base}, - { 9, VesselType.EVA}, - { 10, VesselType.Flag}, - { 11, VesselType.Debris}, - { 12, VesselType.SpaceObject}, - { 13, VesselType.Unknown} - }; - private readonly Dictionary bitVesselRemap = new Dictionary - { - { VesselType.Ship, 1}, - { VesselType.Plane, 2}, - { VesselType.Probe, 3}, - { VesselType.Lander, 4}, - { VesselType.Station, 5}, - { VesselType.Relay, 6}, - { VesselType.Rover, 7}, - { VesselType.Base, 8}, - { VesselType.EVA, 9}, - { VesselType.Flag, 10}, - { VesselType.Debris, 11}, - { VesselType.SpaceObject, 12}, - { VesselType.Unknown, 13} - }; - - internal ProtoCrewMember[] localCrew = new ProtoCrewMember[0]; - private kerbalExpressionSystem[] localCrewMedical = new kerbalExpressionSystem[0]; - - - private Stopwatch nativeStopwatch = new Stopwatch(); - private Stopwatch luaStopwatch = new Stopwatch(); - private Stopwatch dependentStopwatch = new Stopwatch(); - long samplecount = 0; - long nativeEvaluationCount = 0; - long luaEvaluationCount = 0; - long dependentEvaluationCount = 0; - - private static bool dpaiChecked = false; - private static Func GetDpaiName = null; - - #region Internal Interface - /// - /// Return the MASFlightComputer attached to the given part, or null if - /// none is found. - /// - /// The part where the flight computer should live - /// MASFlightComputer or null - static internal MASFlightComputer Instance(Part part) - { - for (int i = part.Modules.Count - 1; i >= 0; --i) - { - if (part.Modules[i].ClassName == typeof(MASFlightComputer).Name) - { - return part.Modules[i] as MASFlightComputer; - } - } - - return null; - } - - /// - /// Convert a name to a - /// - /// - /// Prop that is associated with the variable request; - /// null indicates "Do not condition this variable" - /// - private string ConditionVariableName(string initialName, InternalProp prop) - { - initialName = initialName.Trim(); - double numeric; - if (double.TryParse(initialName, out numeric)) - { - // If the variable is a numeric constant, we want to - // canonicalize it so we don't create multiple variables that - // all have the same value, eg "0" and "0.0" and "0.00" - initialName = string.Format("{0:R}", numeric); - } - - if (prop == null) - { - return initialName; - } - else - { - if (initialName.Contains("%AUTOID%")) - { - string replacementString = string.Format("PROP-{0}-{1}", prop.propName, prop.propID); - initialName = initialName.Replace("%AUTOID%", replacementString); - } - if (initialName.Contains("%PROPID%")) - { - initialName = initialName.Replace("%PROPID%", prop.propID.ToString()); - } - if (initialName.Contains("%PROPCOUNT%")) - { - if (prop.internalModel != null) - { - initialName = initialName.Replace("%PROPCOUNT%", prop.internalModel.props.Count.ToString()); - } - else - { - initialName = initialName.Replace("%PROPCOUNT%", "1"); - } - } - return initialName; - } - } - - /// - /// Register to receive on-changed callback notification for - /// a variable. - /// - /// Un-massaged name of the variable. - /// The prop associated with this variable. - /// The callback to invoke when this variable changes. - /// Should the callback be invoked immediately? - /// The variable created, or null. - internal Variable RegisterVariableChangeCallback(string variableName, InternalProp prop, Action callback, bool initializeNow = true) - { - Variable v = null; - try - { - v = GetVariable(variableName, prop); - - v.RegisterNumericCallback(callback); - - if (initializeNow) - { - callback(v.AsDouble()); - } - } - catch (Exception e) - { - throw new ArgumentException("Error parsing variable \"" + variableName + "\"", e); - } - - return v; - } - - /// - /// Register a monitor so it will receive softkey events. If the monitor's name - /// already exists, this is a no-op. - /// - /// Name of the monitor - /// - internal void RegisterMonitor(string monitorName, InternalProp prop, MASMonitor monitor) - { - monitorName = ConditionVariableName(monitorName, prop); - - if (!monitors.ContainsKey(monitorName)) - { - monitors[monitorName] = monitor; - } - } - - /// - /// Unregister a monitor so it will no longer receive softkey events. - /// - /// - /// - internal void UnregisterMonitor(string monitorName, InternalProp prop, MASMonitor monitor) - { - monitorName = ConditionVariableName(monitorName, prop); - - if (monitors.ContainsKey(monitorName)) - { - monitors.Remove(monitorName); - } - } - - /// - /// Handle a softkey event by forwarding it to the named monitor (if it exists). - /// - /// - /// - /// - internal bool HandleSoftkey(string monitorName, int key) - { - MASMonitor monitor; - if (monitors.TryGetValue(monitorName, out monitor)) - { - return monitor.HandleSoftkey(key); - } - - return false; - } - - /// - /// Function to handle a touchscreen (COLLIDER_ADVANCED) event. - /// - /// The monitor that will receive the event. - /// The x and y coordinates of the event, as processed by the COLLIDER_ADVANCED - internal void HandleTouchEvent(string monitorName, Vector2 hitCoordinate, EventType eventType) - { - MASMonitor monitor; - if (monitors.TryGetValue(monitorName, out monitor)) - { - monitor.HandleTouchEvent(hitCoordinate, eventType); - } - } - - /// - /// Convert an RPM-compatible named color to a Color32. Returns true - /// if successful. - /// - /// Color to convert - /// True if the namedColor was a COLOR_ field, false otherwise. - internal bool TryGetNamedColor(string namedColor, out Color32 color) - { - namedColor = namedColor.Trim(); - if (namedColor.StartsWith("#")) - { - return Utility.ParseHexColor(namedColor, out color); - } - else if (namedColor.StartsWith("COLOR_")) - { - if (namedColors.TryGetValue(namedColor, out color)) - { - return true; - } - else if (MASLoader.namedColors.TryGetValue(namedColor, out color)) - { - namedColors.Add(namedColor, color); - return true; - } - } - - color = Color.black; - return false; - } - - /// - /// Converts an RPM-compatible named color (COLOR_*) to a Color32. If - /// a part-local override exists, it will be chosen; otherwise, a check - /// of the global table is made. If the named color is not found, an - /// exception is thrown. - /// - /// - /// - internal Color32 GetNamedColor(string namedColor) - { - Color32 colorValue; - if (namedColors.TryGetValue(namedColor, out colorValue)) - { - return colorValue; - } - else - { - if (MASLoader.namedColors.TryGetValue(namedColor, out colorValue)) - { - namedColors.Add(namedColor, colorValue); - return colorValue; - } - } - - Utility.ComplainLoudly("GetNamedColor with unknown named color"); - throw new ArgumentException("[MASFlightComputer] Unknown named color '" + namedColor + "'."); - } - - /// - /// Returns or generates an Action that encapsulates the supplied actionName, - /// which is simply the Lua function(s) that are to be executed. - /// - /// The action (Lua code snippet) to execute. - /// The Action. - internal Action GetAction(string actionName, InternalProp prop) - { - actionName = ConditionVariableName(actionName, prop); - - Action action; - if (actions.TryGetValue(actionName, out action)) - { - return action; - } - else - { - try - { - DynValue dv = script.LoadString(actionName); - - action = () => - { - try - { - script.Call(dv); - } - catch (Exception e) - { - Utility.ComplainLoudly("Action " + actionName + " triggered an exception"); - Utility.LogError(this, "Action {0} triggered exception:", actionName); - Utility.LogError(this, e.ToString()); - } - }; - - //Utility.LogMessage(this, "Adding new Action '{0}'", actionName); - actions.Add(actionName, action); - return action; - } - catch - { - return null; - } - } - } - - /// - /// Creates a Lua function that uses 'actionName' as its body, then generates an Action that takes - /// a double for its parameter that is used to call the newly-created function. - /// - /// - /// - /// - /// - internal Action GetDragAction(string actionName, string componentName, InternalProp prop) - { - string preppedActionName = actionName.Replace("%DRAG%", "dragDelta"); - string propName = ConditionVariableName(string.Format("%AUTOID%_{0}", componentName), prop); - propName = propName.Replace('-', '_').Replace(' ', '_'); - - StringBuilder sb = StringBuilderCache.Acquire(); - sb.AppendFormat("function {0}_drag(dragDelta)", propName).AppendLine().AppendFormat(" {0}", preppedActionName).AppendLine().AppendLine("end"); - preppedActionName = ConditionVariableName(sb.ToStringAndRelease(), prop); - - // Compile the script. - script.DoString(preppedActionName); - // Get the function. - DynValue closure = script.Globals.Get(string.Format("{0}_drag", propName)); - if (closure.Type == DataType.Function) - { - return (double newValue) => - { - DynValue parm = DynValue.NewNumber(newValue); - DynValue result = script.Call(closure, parm); - }; - } - else - { - Utility.LogError(this, "Failed to compile \"{0}\" into a Lua function", actionName); - return null; - } - } - - /// - /// Returns an action that the COLLIDER_ADVANCED object can use to pass click information to a monitor. - /// - /// ID of the monitor. - /// - /// - internal Action GetHitAction(string monitorID, InternalProp prop, Action hitAction) - { - string conditionedId = ConditionVariableName(monitorID, prop); - - Action ev = (xy, eventType) => hitAction(conditionedId, xy, eventType); - return ev; - } - - internal Action GetColliderAction(string actionName, int hitboxID, string actionType, InternalProp prop) - { - actionName = ConditionVariableName(actionName, prop); - - string propName = ConditionVariableName(string.Format("%AUTOID%_{0}", hitboxID), prop); - propName = propName.Replace('-', '_').Replace(' ', '_'); - - Action act = null; - if (actionName.Contains("%X%") || actionName.Contains("%Y%")) - { - // Case where the location within the hitbox matters. - - actionName = actionName.Replace("%X%", "x").Replace("%Y%", "y"); - StringBuilder sb = StringBuilderCache.Acquire(); - sb.AppendFormat("function {0}_{1}(x, y)", propName, actionType).AppendLine().AppendFormat(" {0}", actionName).AppendLine().AppendLine("end"); - string preppedActionName = ConditionVariableName(sb.ToStringAndRelease(), prop); - - // Compile the script. - script.DoString(preppedActionName); - // Get the function. - DynValue closure = script.Globals.Get(string.Format("{0}_{1}", propName, actionType)); - if (closure.Type == DataType.Function) - { - return (loc) => - { - DynValue xIn = DynValue.NewNumber(loc.x); - DynValue yIn = DynValue.NewNumber(loc.y); - try - { - script.Call(closure, xIn, yIn); - } - catch (Exception e) - { - Utility.ComplainLoudly("Action " + actionName + " triggered an exception"); - Utility.LogError(this, "Action {0} triggered exception:", actionName); - Utility.LogError(this, e.ToString()); - } - }; - } - else - { - Utility.LogError(this, "Failed to compile \"{0}\" into a Lua function", actionName); - return null; - } - } - else - { - // Simple case - doesn't use X or Y parameter. - try - { - DynValue dv = script.LoadString(actionName); - - act = (loc) => - { - try - { - script.Call(dv); - } - catch (Exception e) - { - Utility.ComplainLoudly("Action " + actionName + " triggered an exception"); - Utility.LogError(this, "Action {0} triggered exception:", actionName); - Utility.LogError(this, e.ToString()); - } - }; - } - catch - { - return null; - } - } - - return act; - } - - /// - /// Write a Lua script to transform x, y, z normalized Collider coordinates into an x, y value - /// that is sent to the monitor. - /// - /// - /// - /// - /// - internal Func GetColliderTransformation(string xTransformation, string yTransformation, string componentName, InternalProp prop) - { - string propName = ConditionVariableName(string.Format("%AUTOID%_{0}", componentName), prop); - propName = propName.Replace('-', '_').Replace(' ', '_'); - - string conditionedX = ConditionVariableName(xTransformation, prop).Replace("%X%", "x").Replace("%Y%", "y").Replace("%Z%", "z"); - string conditionedY = ConditionVariableName(yTransformation, prop).Replace("%X%", "x").Replace("%Y%", "y").Replace("%Z%", "z"); - StringBuilder sb = StringBuilderCache.Acquire(); - - sb.AppendFormat("function {0}_transform(x, y, z)", propName).AppendLine().AppendFormat(" local x1 = {0}", conditionedX).AppendFormat(" local y1 = {0}", conditionedY).AppendLine().AppendLine(" return x1, y1").AppendLine("end"); - - string preppedFunction = sb.ToStringAndRelease(); - script.DoString(preppedFunction); - - // Get the function. - DynValue closure = script.Globals.Get(string.Format("{0}_transform", propName)); - - Func f = (x, y, z) => - { - DynValue xIn = DynValue.NewNumber(x); - DynValue yIn = DynValue.NewNumber(y); - DynValue zIn = DynValue.NewNumber(z); - DynValue result = script.Call(closure, xIn, yIn, zIn); - if (result.Type == DataType.Tuple) - { - DynValue[] multiret = result.Tuple; - double x1 = multiret[0].Number; - double y1 = multiret[1].Number; - - return new Vector2((float)x1, (float)y1); - } - else - { - Utility.LogError(this, "Called script - result = {0}, not DataType.Tuple", result.Type); - - return new Vector2(x, y); - } - }; - return f; - } - - /// - /// Fetch the kerbalExpressionSystem for a local crew seat. - /// - /// - /// - internal kerbalExpressionSystem GetLocalKES(int crewSeat) - { - if (crewSeat >= 0 && crewSeat < localCrew.Length && localCrew[crewSeat] != null) - { - localCrew[crewSeat].KerbalRef.GetComponentCached(ref localCrewMedical[crewSeat]); - - return localCrewMedical[crewSeat]; - } - - return null; - } - - internal string GetDockingPortName(Part dockingNodePart) - { - if (GetDpaiName == null) - { - return dockingNodePart.partInfo.title; - } - else - { - PartModule namedDockModule = dockingNodePart.Modules["ModuleDockingNodeNamed"]; - - return (namedDockModule != null) ? GetDpaiName(namedDockModule) : dockingNodePart.partInfo.title; - } - } - - internal float GetPowerDraw() - { - return (requiresPower) ? (additionalEC + rate) : 0.0f; - } - - internal float ChangePowerDraw(float amount) - { - if (requiresPower) - { - additionalEC = Mathf.Max(0.0f, amount + additionalEC); - return additionalEC + rate; - } - else - { - return 0.0f; - } - } - - internal int GetCurrentControlPoint() - { - return activeControlPoint; - } - - internal string GetControlPointName(int controlPoint) - { - if (controlPoint == -1) - { - controlPoint = activeControlPoint; - } - - if (commandModule != null && controlPoint >= 0 && controlPoint < commandModule.controlPoints.Count) - { - return commandModule.controlPoints.At(controlPoint).displayName; - } - return string.Empty; - } - - internal int GetNumControlPoints() - { - if (commandModule != null) - { - return commandModule.controlPoints.Count; - } - return 0; - } - - internal float SetCurrentControlPoint(int newControlPoint) - { - if (commandModule != null && newControlPoint >= 0 && newControlPoint < commandModule.controlPoints.Count) - { - commandModule.SetControlPoint(commandModule.controlPoints.At(newControlPoint).name); - return 1.0f; - } - return 0.0f; - } - #endregion - - #region Target Tracking - internal bool ClearTargetFilter(int bitIndex) - { - int bit = 1 << bitIndex; - int oldValue = vesselFilterValue; - vesselFilterValue &= ~bit; - - if (oldValue != vesselFilterValue) - { - activeVesselFilter.Remove(vesselBitRemap[bitIndex]); - SetPersistent(vesselFilterLabel, vesselFilterValue.ToString("X")); - return true; - } - else - { - return false; - } - } - - internal bool GetTargetFilter(int bitIndex) - { - int bit = 1 << bitIndex; - int andResult = bit & vesselFilterValue; - - return (andResult != 0); - } - - internal bool SetTargetFilter(int bitIndex) - { - int bit = 1 << bitIndex; - int oldValue = vesselFilterValue; - vesselFilterValue |= bit; - - if (oldValue != vesselFilterValue) - { - activeVesselFilter.Add(vesselBitRemap[bitIndex]); - SetPersistent(vesselFilterLabel, vesselFilterValue.ToString("X")); - return true; - } - else - { - return false; - } - } - - internal void ToggleTargetFilter(int bitIndex) - { - int bit = 1 << bitIndex; - if ((vesselFilterValue & bit) != 0) - { - activeVesselFilter.Remove(vesselBitRemap[bitIndex]); - } - else - { - activeVesselFilter.Add(vesselBitRemap[bitIndex]); - } - vesselFilterValue ^= bit; - SetPersistent(vesselFilterLabel, vesselFilterValue.ToString("X")); - } - #endregion - - #region Monobehaviour -#if MEASURE_FC_FIXEDUPDATE - Stopwatch fixedupdateTimer = new Stopwatch(); -#endif - /// - /// Process updates to tracked variables. - /// - public void FixedUpdate() - { - try - { - if (initialized && vc.vesselCrewed && vc.vesselActive) - { -#if MEASURE_FC_FIXEDUPDATE - fixedupdateTimer.Reset(); - fixedupdateTimer.Start(); -#endif - // Realistically, this block of code won't be triggered very - // often. Once per scene change per pod. - if (mutableVariablesChanged) - { - nativeVariables = new Variable[nativeVariableCount]; - luaVariables = new Variable[luaVariableCount]; - dependentVariables = new Variable[dependentVariableCount]; - int nativeIdx = 0, luaIdx = 0, dependentIdx = 0; - foreach (Variable var in mutableVariablesList) - { - if (var.variableType == Variable.VariableType.LuaScript) - { - luaVariables[luaIdx] = var; - ++luaIdx; - } - else if (var.variableType == Variable.VariableType.Func) - { - nativeVariables[nativeIdx] = var; - ++nativeIdx; - } - else if (var.variableType == Variable.VariableType.Dependent) - { - dependentVariables[dependentIdx] = var; - ++dependentIdx; - } - else - { - throw new ArgumentException(string.Format("Unexpected variable type {0} for variable {1} in mutableVariablesList", var.variableType, var.name)); - } - } - Utility.LogMessage(this, "Resizing variables lists to N:{0} L:{1} D:{2}", nativeVariableCount, luaVariableCount, dependentVariableCount); - mutableVariablesChanged = false; - } - - fcProxy.Update(); - farProxy.Update(); - kacProxy.Update(); - keProxy.Update(); - mjProxy.Update(); - navProxy.Update(); - parachuteProxy.Update(); - transferProxy.Update(); - UpdateRadios(); -#if MEASURE_FC_FIXEDUPDATE - TimeSpan updatesTime = fixedupdateTimer.Elapsed; -#endif - - anyThrottleKeysPressed = GameSettings.THROTTLE_CUTOFF.GetKey() || GameSettings.THROTTLE_FULL.GetKey() || GameSettings.THROTTLE_UP.GetKey() || GameSettings.THROTTLE_DOWN.GetKey(); - - // Precompute the disruption chances. - if (electricChargeIndex == -1) - { - // We have to poll it here because the value may not be initialized - // when we're in Start(). - electricChargeIndex = vc.GetResourceIndex(MASConfig.ElectricCharge); - try - { - PartResourceDefinition def = PartResourceLibrary.Instance.resourceDefinitions[MASConfig.ElectricCharge]; - resourceId = def.id; - } - catch - { - Utility.LogError(this, "Unable to resolve resource '{0}', flight computer can not require power.", MASConfig.ElectricCharge); - requiresPower = false; - } - } - - isPowered = (!requiresPower || vc.ResourceCurrentDirect(electricChargeIndex) > 0.0001) && powerOnValid; - - if (requiresPower && (rate + additionalEC) > 0.0f) - { - double requested = (rate + additionalEC) * TimeWarp.fixedDeltaTime; - double supplied = part.RequestResource(resourceId, requested); - if (supplied < requested * 0.5) - { - isPowered = false; - } - } - - currentKerbalBlackedOut = false; - currentKerbal = FindCurrentKerbal(); - if (currentKerbal != null && currentKerbal.protoCrewMember.outDueToG) - { - // Always black-out instruments from blackouts. - disruptionChance = 1.1f; - currentKerbalBlackedOut = true; - } - else if (vessel.geeForce_immediate > gLimit) - { - disruptionChance = baseDisruptionChance * Mathf.Sqrt((float)vessel.geeForce_immediate - gLimit); - } - else - { - disruptionChance = 0.0f; - } - - // TODO: Add a heuristic to adjust the loop so not all variables - // update every fixed update if the average update time is too high. - // Need to decide if it's going to be an absolute time (max # ms/update) - // or a relative time. - // NOTE: 128 Lua "variables" average about 1.7ms/update! And there's - // a LOT of garbage collection going on. - // 229 variables -> 2.6-2.7ms/update, so 70-80 variables per ms on - // a reasonable mid-upper range CPU (3.6GHz). - nativeStopwatch.Start(); - int count = nativeVariables.Length; - for (int i = 0; i < count; ++i) - { - try - { - nativeVariables[i].Evaluate(true); - } - catch (Exception e) - { - Utility.LogError(this, "FixedUpdate exception on variable {0}:", nativeVariables[i].name); - Utility.LogError(this, e.ToString()); - //throw e; - } - } - nativeEvaluationCount += count; - nativeStopwatch.Stop(); -#if MEASURE_FC_FIXEDUPDATE - TimeSpan nativeTime = fixedupdateTimer.Elapsed; -#endif - - luaStopwatch.Start(); - // Update some Lua variables - user configurable, so lower- - // spec machines aren't as badly affected. - int startLuaIdx, endLuaIdx; - if (MASConfig.LuaUpdatePriority == 1) - { - startLuaIdx = 0; - endLuaIdx = luaVariables.Length; - } - else - { - long modulo = samplecount % MASConfig.LuaUpdatePriority; - int span = luaVariables.Length / MASConfig.LuaUpdatePriority; - startLuaIdx = (int)modulo * span; - - if (modulo == MASConfig.LuaUpdatePriority - 1) - { - endLuaIdx = luaVariables.Length; - } - else - { - endLuaIdx = startLuaIdx + span; - } - } - count = endLuaIdx - startLuaIdx; - for (int i = startLuaIdx; i < endLuaIdx; ++i) - { - try - { - luaVariables[i].Evaluate(true); - } - catch (Exception e) - { - Utility.LogError(this, "FixedUpdate exception on variable {0}", luaVariables[i].name); - luaStopwatch.Stop(); - throw e; - } - } - luaEvaluationCount += count; - luaStopwatch.Stop(); -#if MEASURE_FC_FIXEDUPDATE - TimeSpan luaTime = fixedupdateTimer.Elapsed; -#endif - - dependentStopwatch.Start(); - count = dependentVariables.Length; - for (int i = 0; i < count; ++i) - { - try - { - dependentVariables[i].Evaluate(true); - } - catch (Exception e) - { - Utility.LogError(this, "FixedUpdate exception on variable {0}:", dependentVariables[i].name); - Utility.LogError(this, e.ToString()); - //throw e; - } - } - dependentEvaluationCount += count; - dependentStopwatch.Stop(); - ++samplecount; -#if MEASURE_FC_FIXEDUPDATE - TimeSpan finalTime = fixedupdateTimer.Elapsed; - if (finalTime.Ticks > (TimeSpan.TicksPerMillisecond / 2)) - { - double ticksPerMillisecond = (double)TimeSpan.TicksPerMillisecond; - Utility.LogMessage(this, "FixedUpdate proxiestes {0,7:0.000} ms", - ((double)updatesTime.Ticks) / ticksPerMillisecond); - Utility.LogMessage(this, "FixedUpdate native var {0,7:0.000} ms", - ((double)(nativeTime.Ticks - updatesTime.Ticks)) / ticksPerMillisecond); - Utility.LogMessage(this, "FixedUpdate lua var {0,7:0.000} ms", - ((double)(luaTime.Ticks - nativeTime.Ticks)) / ticksPerMillisecond); - Utility.LogMessage(this, "FixedUpdate dep var {0,7:0.000} ms", - ((double)(finalTime.Ticks - luaTime.Ticks)) / ticksPerMillisecond); - Utility.LogMessage(this, "FixedUpdate net {0,7:0.000} ms", - ((double)finalTime.Ticks) / ticksPerMillisecond); - } -#endif - } - else if (HighLogic.LoadedSceneIsEditor && EditorLogic.fetch.shipDescriptionField != null) - { - string newDescr = EditorLogic.fetch.shipDescriptionField.text.Replace(Utility.EditorNewLine, "$$$"); - if (newDescr != shipDescription) - { - shipDescription = newDescr; - } - } - } - catch (Exception e) - { - Utility.LogError(this, "MASFlightComputer.FixedUpdate exception: {0}", e); - } - } - - /// - /// Shut down, unregister, etc. - /// - public void OnDestroy() - { - script = null; - fcProxy = null; - chattererProxy = null; - engineProxy = null; - mjProxy = null; - navProxy = null; - farProxy = null; - kacProxy = null; - keProxy = null; - parachuteProxy = null; - transferProxy = null; - vtolProxy = null; - if (initialized) - { - Utility.LogMessage(this, "OnDestroy for {0}", flightComputerId); - StopAllCoroutines(); - GameEvents.onVesselWasModified.Remove(onVesselChanged); - GameEvents.onVesselChange.Remove(onVesselChanged); - GameEvents.onVesselCrewWasModified.Remove(onVesselChanged); - GameEvents.OnCameraChange.Remove(onCameraChange); - GameEvents.OnIVACameraKerbalChange.Remove(OnIVACameraKerbalChange); - GameEvents.OnControlPointChanged.Remove(OnControlPointChanged); - - Utility.LogInfo(this, "{3} variables created: {0} constant variables, {1} delegate variables, {2} Lua variables, and {4} expression variables", - constantVariableCount, nativeVariableCount, luaVariableCount, variables.Count, dependentVariableCount); - if (samplecount > 0) - { - double msPerFixedUpdate = 1000.0 * (double)(nativeStopwatch.ElapsedTicks) / (double)(samplecount * Stopwatch.Frequency); - double samplesPerMs = (double)nativeEvaluationCount / (1000.0 * (double)(nativeStopwatch.ElapsedTicks) / (double)(Stopwatch.Frequency)); - Utility.LogInfo(this, "FixedUpdate Delegate average = {0:0.00}ms/FixedUpdate or {1:0.0} variables/ms", msPerFixedUpdate, samplesPerMs); - - msPerFixedUpdate = 1000.0 * (double)(luaStopwatch.ElapsedTicks) / (double)(samplecount * Stopwatch.Frequency); - samplesPerMs = (double)luaEvaluationCount / (1000.0 * (double)(luaStopwatch.ElapsedTicks) / (double)(Stopwatch.Frequency)); - Utility.LogInfo(this, "FixedUpdate Lua average = {0:0.00}ms/FixedUpdate or {1:0.0} variables/ms", msPerFixedUpdate, samplesPerMs); - - msPerFixedUpdate = 1000.0 * (double)(dependentStopwatch.ElapsedTicks) / (double)(samplecount * Stopwatch.Frequency); - samplesPerMs = (double)dependentEvaluationCount / (1000.0 * (double)(dependentStopwatch.ElapsedTicks) / (double)(Stopwatch.Frequency)); - Utility.LogInfo(this, "FixedUpdate Expr average = {0:0.00}ms/FixedUpdate or {1:0.0} variables/ms", msPerFixedUpdate, samplesPerMs); - } -#if LIST_LUA_VARIABLES - // Lua variables are costly to evaluate - ideally, we minimize the number created for use as variables. - Utility.LogMessage(this, "{0} Lua variables were created:", luaVariables.Length); - for(int i=0; i - /// Start up this module. - /// - public void Start() - { - if (HighLogic.LoadedSceneIsFlight) - { - additionalEC = 0.0f; - rate = Mathf.Max(0.0f, rate); - commandModule = part.FindModuleImplementing(); - UpdateControlPoint(commandModule.ActiveControlPointName); - colorChangerModule = part.FindModuleImplementing(); - if (colorChangerModule != null && colorChangerModule.toggleInFlight == false && colorChangerModule.toggleAction == false) - { - // Not a controllable light - colorChangerModule = null; - } - - if (dependentLuaMethods.Count == 0) - { - // Maybe a bit kludgy -- I want to list the Lua table methods that I know - // are dependent variables, so they can be treated as such instead of polled - // every FixedUpdate. But I want a fast search, so I use a List, sort it, - // and BSearch it. - dependentLuaMethods.Add("math.abs"); - dependentLuaMethods.Add("math.acos"); - dependentLuaMethods.Add("math.asin"); - dependentLuaMethods.Add("math.atan"); - dependentLuaMethods.Add("math.atan2"); - dependentLuaMethods.Add("math.ceil"); - dependentLuaMethods.Add("math.cos"); - dependentLuaMethods.Add("math.cosh"); - dependentLuaMethods.Add("math.deg"); - dependentLuaMethods.Add("math.exp"); - dependentLuaMethods.Add("math.floor"); - dependentLuaMethods.Add("math.fmod"); - dependentLuaMethods.Add("math.frexp"); - dependentLuaMethods.Add("math.ldexp"); - dependentLuaMethods.Add("math.log"); - dependentLuaMethods.Add("math.log10"); - dependentLuaMethods.Add("math.max"); - dependentLuaMethods.Add("math.min"); - dependentLuaMethods.Add("math.modf"); - dependentLuaMethods.Add("math.pow"); - dependentLuaMethods.Add("math.rad"); - dependentLuaMethods.Add("math.sin"); - dependentLuaMethods.Add("math.sinh"); - dependentLuaMethods.Add("math.sqrt"); - dependentLuaMethods.Add("math.tan"); - dependentLuaMethods.Add("math.tanh"); - dependentLuaMethods.Add("string.format"); - dependentLuaMethods.Add("string.len"); - dependentLuaMethods.Add("string.lower"); - dependentLuaMethods.Add("string.rep"); - dependentLuaMethods.Add("string.reverse"); - dependentLuaMethods.Add("string.upper"); - - dependentLuaMethods.Sort(); - } - - if (dpaiChecked == false) - { - dpaiChecked = true; - - Type dpaiMDNNType = Utility.GetExportedType("ModuleDockingNodeNamed", "NavyFish.ModuleDockingNodeNamed"); - if (dpaiMDNNType != null) - { - Utility.LogMessage(this, "Found DPAI"); - FieldInfo portName = dpaiMDNNType.GetField("portName", BindingFlags.Instance | BindingFlags.Public); - GetDpaiName = DynamicMethodFactory.CreateGetField(portName); - } - } - - Vessel vessel = this.vessel; - - if (string.IsNullOrEmpty(flightComputerId)) - { - fcId = Guid.NewGuid(); - flightComputerId = fcId.ToString(); - - Utility.LogMessage(this, "Creating new flight computer {0}", flightComputerId); - } - else - { - fcId = new Guid(flightComputerId); - - Utility.LogMessage(this, "Restoring flight computer {0}", flightComputerId); - } - - parentVesselId = vessel.id; - - // TODO: Review the CoreModule settings - are they tight enough? - // Add the LoadMethods to allow dynamic script compilation. - script = new Script(CoreModules.Preset_HardSandbox | CoreModules.LoadMethods); - - UserData.DefaultAccessMode = InteropAccessMode.Preoptimized; - - // Global State (set up links to the proxy). Note that we - // don't use the proxy system included in MoonSharp, since it - // creates a proxy object for every single script.Call(), which - // means plenty of garbage... - string whichProxy = string.Empty; - try - { - whichProxy = "chatterer"; - chattererProxy = new MASIChatterer(); - UserData.RegisterType(); - script.Globals["chatterer"] = chattererProxy; - registeredTables.Add("chatterer", new MASRegisteredTable(chattererProxy)); - - whichProxy = "engine"; - engineProxy = new MASIEngine(); - UserData.RegisterType(); - script.Globals["engine"] = engineProxy; - registeredTables.Add("engine", new MASRegisteredTable(engineProxy)); - - whichProxy = "far"; - farProxy = new MASIFAR(vessel); - UserData.RegisterType(); - script.Globals["far"] = farProxy; - registeredTables.Add("far", new MASRegisteredTable(farProxy)); - - whichProxy = "kac"; - kacProxy = new MASIKAC(vessel); - UserData.RegisterType(); - script.Globals["kac"] = kacProxy; - registeredTables.Add("kac", new MASRegisteredTable(kacProxy)); - - whichProxy = "ke"; - keProxy = new MASIKerbalEngineer(); - UserData.RegisterType(); - script.Globals["ke"] = keProxy; - registeredTables.Add("ke", new MASRegisteredTable(keProxy)); - - whichProxy = "mechjeb"; - mjProxy = new MASIMechJeb(); - UserData.RegisterType(); - script.Globals["mechjeb"] = mjProxy; - registeredTables.Add("mechjeb", new MASRegisteredTable(mjProxy)); - - whichProxy = "nav"; - navProxy = new MASINavigation(vessel, this); - UserData.RegisterType(); - script.Globals["nav"] = navProxy; - registeredTables.Add("nav", new MASRegisteredTable(navProxy)); - - whichProxy = "parachute"; - parachuteProxy = new MASIParachute(vessel); - UserData.RegisterType(); - script.Globals["parachute"] = parachuteProxy; - registeredTables.Add("parachute", new MASRegisteredTable(parachuteProxy)); - - whichProxy = "transfer"; - transferProxy = new MASITransfer(vessel); - UserData.RegisterType(); - script.Globals["transfer"] = transferProxy; - registeredTables.Add("transfer", new MASRegisteredTable(transferProxy)); - - whichProxy = "vtol"; - vtolProxy = new MASIVTOL(); - UserData.RegisterType(); - script.Globals["vtol"] = vtolProxy; - registeredTables.Add("vtol", new MASRegisteredTable(vtolProxy)); - - whichProxy = "fc"; - fcProxy = new MASFlightComputerProxy(this, farProxy, keProxy, mjProxy); - UserData.RegisterType(); - script.Globals["fc"] = fcProxy; - registeredTables.Add("fc", new MASRegisteredTable(fcProxy)); - } - catch (Exception e) - { - Utility.LogError(this, "Proxy object {0} configuration failed:", whichProxy); - Utility.LogError(this, e.ToString()); - Utility.ComplainLoudly("Initialization Failed. Please check KSP.log"); - return; - } - - vc = MASPersistent.FetchVesselComputer(vessel); - ap = MASAutoPilot.Get(vessel); - // Initialize the resourceConverterList with ElectricCharge at index 0 - if (vc.resourceConverterList.Count > 0) - { - vc.resourceConverterList.Clear(); - } - var rc = new MASVesselComputer.GeneralPurposeResourceConverter(); - rc.id = 0; - rc.outputResource = MASConfig.ElectricCharge; - vc.resourceConverterList.Add(rc); - // The resourceConverterList changes - trigger a rebuild - vc.modulesInvalidated = true; - - if (!MASPersistent.PersistentsLoaded) - { - throw new ArgumentNullException("MASPersistent.PersistentsLoaded has not loaded!"); - } - persistentVars = MASPersistent.RestoreDictionary(fcId, persistentVars); - navRadioFrequency = MASPersistent.RestoreNavRadio(fcId, navRadioFrequency); - foreach (var radio in navRadioFrequency) - { - ReloadRadio(radio.Key, radio.Value); - } - - // Always make sure we set the vessel ID in the persistent table - // based on what it currently is. - SetPersistent(vesselIdLabel, parentVesselId.ToString()); - - object activeFilters; - if (persistentVars.TryGetValue(vesselFilterLabel, out activeFilters) && (activeFilters is string)) - { - vesselFilterValue = int.Parse((activeFilters as string), System.Globalization.NumberStyles.HexNumber); - } - else - { - // Initial values - vesselFilterValue = 0; - vesselFilterValue |= 1 << bitVesselRemap[VesselType.Probe]; - vesselFilterValue |= 1 << bitVesselRemap[VesselType.Relay]; - vesselFilterValue |= 1 << bitVesselRemap[VesselType.Rover]; - vesselFilterValue |= 1 << bitVesselRemap[VesselType.Lander]; - vesselFilterValue |= 1 << bitVesselRemap[VesselType.Ship]; - vesselFilterValue |= 1 << bitVesselRemap[VesselType.Plane]; - vesselFilterValue |= 1 << bitVesselRemap[VesselType.Station]; - vesselFilterValue |= 1 << bitVesselRemap[VesselType.Base]; - vesselFilterValue |= 1 << bitVesselRemap[VesselType.EVA]; - vesselFilterValue |= 1 << bitVesselRemap[VesselType.Flag]; - - SetPersistent(vesselFilterLabel, vesselFilterValue.ToString("X")); - } - - activeVesselFilter.Clear(); - for (int i = 1; i < 14; ++i) - { - if ((vesselFilterValue & (1 << i)) != 0) - { - activeVesselFilter.Add(vesselBitRemap[i]); - } - } - - // TODO: Don't need to set vessel for all of these guys if I just now init'd them. - fcProxy.vc = vc; - fcProxy.vessel = vessel; - engineProxy.vc = vc; - mjProxy.UpdateVessel(vessel, vc); - parachuteProxy.vc = vc; - transferProxy.vc = vc; - vtolProxy.UpdateVessel(vessel, vc); - - // Add User scripts - try - { - for (int i = MASLoader.userScripts.Count - 1; i >= 0; --i) - { - script.DoString(MASLoader.userScripts[i]); - } - - Utility.LogInfo(this, "{1}: Loaded {0} user scripts", MASLoader.userScripts.Count, flightComputerId); - } - catch (MoonSharp.Interpreter.SyntaxErrorException e) - { - Utility.ComplainLoudly("User Script Loading error"); - Utility.LogError(this, " - {0}", e.DecoratedMessage); - Utility.LogError(this, e.ToString()); - } - catch (Exception e) - { - Utility.ComplainLoudly("User Script Loading error"); - Utility.LogError(this, e.ToString()); - } - - // Parse action group labels: - if (!string.IsNullOrEmpty(shipDescription)) - { - string[] rows = shipDescription.Replace("$$$", Environment.NewLine).Split(Utility.LineSeparator, StringSplitOptions.RemoveEmptyEntries); - StringBuilder sb = StringBuilderCache.Acquire(); - for (int i = 0; i < rows.Length; ++i) - { - if (rows[i].StartsWith("AG")) - { - string[] row = rows[i].Split('='); - int groupID; - if (int.TryParse(row[0].Substring(2), out groupID)) - { - if (groupID >= 0 && groupID <= 9) - { - if (row.Length == 2) - { - string[] memo = row[1].Split('|'); - if (memo.Length == 1) - { - agMemoOn[groupID] = memo[0].Trim(); - agMemoOff[groupID] = agMemoOn[groupID]; - } - else if (memo.Length == 2) - { - agMemoOn[groupID] = memo[0].Trim(); - agMemoOff[groupID] = memo[1].Trim(); - } - } - } - } - } - else if (sb.Length == 0) - { - sb.Append(rows[i].Trim()); - } - else - { - sb.Append("$$$").Append(rows[i].Trim()); - } - } - if (sb.Length > 0) - { - vesselDescription = sb.ToStringAndRelease(); - } - } - - // Initialize persistent vars ... note that save game values have - // already been restored, so check if the persistent already exists, - // first. - // Also restore named color per-part overrides - ConfigNode myNode = Utility.GetPartModuleConfigNode(part, typeof(MASFlightComputer).Name, 0); - try - { - ConfigNode persistentSeed = myNode.GetNode("PERSISTENT_VARIABLES"); - if (persistentSeed != null) - { - var values = persistentSeed.values; - int valueCount = values.Count; - for (int i = 0; i < valueCount; ++i) - { - var persistentVal = values[i]; - - if (!persistentVars.ContainsKey(persistentVal.name)) - { - double doubleVal; - if (double.TryParse(persistentVal.value, out doubleVal)) - { - persistentVars[persistentVal.name] = doubleVal; - } - else - { - persistentVars[persistentVal.name] = persistentVal.value; - } - } - } - } - - ConfigNode overrideColorNodes = myNode.GetNode("RPM_COLOROVERRIDE"); - ConfigNode[] colorConfig = overrideColorNodes.GetNodes("COLORDEFINITION"); - for (int defIdx = 0; defIdx < colorConfig.Length; ++defIdx) - { - if (colorConfig[defIdx].HasValue("name") && colorConfig[defIdx].HasValue("color")) - { - string name = "COLOR_" + (colorConfig[defIdx].GetValue("name").Trim()); - Color32 color = ConfigNode.ParseColor32(colorConfig[defIdx].GetValue("color").Trim()); - namedColors[name] = color; - } - } - } - catch - { - - } - try - { - ConfigNode[] actionGroupConfig = myNode.GetNodes("MAS_ACTION_GROUP"); - for (int agIdx = 0; agIdx < actionGroupConfig.Length; ++agIdx) - { - MASActionGroup ag = new MASActionGroup(actionGroupConfig[agIdx]); - if (masActionGroup.ContainsKey(ag.actionGroupId)) - { - Utility.LogError(this, "Found duplicate MAS_ACTION_GROUP id {0} ... duplicate AG was discarded", ag.actionGroupId); - } - else - { - ag.Rebuild(vessel.Parts); - masActionGroup.Add(ag.actionGroupId, ag); - } - } - } - catch - { - - } - - audioObject.name = "MASFlightComputerAudio-" + flightComputerId; - audioSource = audioObject.AddComponent(); - audioSource.spatialBlend = 0.0f; - morseAudioObject.name = "MASFlightComputerMorseAudio-" + flightComputerId; - morseAudioSource = audioObject.AddComponent(); - morseAudioSource.spatialBlend = 0.0f; - - UpdateLocalCrew(); - - GameEvents.onVesselWasModified.Add(onVesselChanged); - GameEvents.onVesselChange.Add(onVesselChanged); - GameEvents.onVesselCrewWasModified.Add(onVesselChanged); - GameEvents.OnCameraChange.Add(onCameraChange); - GameEvents.OnIVACameraKerbalChange.Add(OnIVACameraKerbalChange); - GameEvents.OnControlPointChanged.Add(OnControlPointChanged); - - if (!string.IsNullOrEmpty(powerOnVariable)) - { - RegisterVariableChangeCallback(powerOnVariable, null, (double newValue) => powerOnValid = (newValue > 0.0)); - } - - // All the things are initialized ... Let's see if there's a startupScript - if (!string.IsNullOrEmpty(startupScript)) - { - DynValue dv = script.LoadString(startupScript); - - if (dv.IsNil() == false) - { - try - { - script.Call(dv); - } - catch (Exception e) - { - Utility.ComplainLoudly("MASFlightComputer startupScript triggered an exception"); - Utility.LogError(this, "MASFlightComputer startupScript triggered an exception:"); - Utility.LogError(this, e.ToString()); - } - } - } - - if (!string.IsNullOrEmpty(onEnterIVA)) - { - enterIvaScript = script.LoadString(onEnterIVA); - if (enterIvaScript.IsNil()) - { - Utility.LogError(this, "Failed to process onEnterIVA script"); - } - } - - if (!string.IsNullOrEmpty(onExitIVA)) - { - exitIvaScript = script.LoadString(onExitIVA); - if (exitIvaScript.IsNil()) - { - Utility.LogError(this, "Failed to process onExitIVA script"); - } - } - - initialized = true; - } - } - - /// - /// Display name to show in the VAB when part description is expanded. - /// - /// - public override string GetModuleDisplayName() - { - return "#MAS_FlightComputer_Module_DisplayName"; - } - - /// - /// Text to show in the VAB when part description is expanded. - /// - /// - public override string GetInfo() - { - return "#MAS_FlightComputer_GetInfo"; - } - - #endregion - - #region Private Methods - private void UpdateControlPoint(string cpName) - { - if (commandModule != null && commandModule.controlPoints.Count > 1) - { - for (int i = commandModule.controlPoints.Count - 1; i >= 0; --i) - { - if (commandModule.controlPoints.At(i).name == cpName) - { - activeControlPoint = i; - return; - } - } - } - else - { - activeControlPoint = 0; - } - } - - private void UpdateLocalCrew() - { - // part.internalModel may be null if the craft is loaded but isn't the active/IVA craft - if (part.internalModel != null) - { - int seatCount = part.internalModel.seats.Count; - if (seatCount != localCrew.Length) - { - // This can happen when an internalModel is loaded when - // it wasn't previously, which appears to occur on docking - // for instance. - localCrew = new ProtoCrewMember[seatCount]; - localCrewMedical = new kerbalExpressionSystem[seatCount]; - } - - // Note that we set localCrewMedical to null because the - // crewMedical ends up being going null sometime between - // when the crew changed callback fires and when we start - // checking variables. Thus, we still have to poll the - // crew medical. - for (int i = 0; i < seatCount; i++) - { - localCrew[i] = part.internalModel.seats[i].crew; - localCrewMedical[i] = null; - } - } - else if (localCrew.Length > 0) - { - localCrew = new ProtoCrewMember[0]; - localCrewMedical = new kerbalExpressionSystem[0]; - } - } - #endregion - - #region Audio Player - GameObject audioObject = new GameObject(); - AudioSource audioSource; - GameObject morseAudioObject = new GameObject(); - AudioSource morseAudioSource; - string morseSequence; - float morseVolume; - bool playingSequence; - - private IEnumerator MorsePlayerCoroutine() - { - while (morseSequence.Length > 0) - { - AudioClip clip; - char first = morseSequence[0]; - if (first == ' ') - { - yield return new WaitForSecondsRealtime(0.25f); - } - else if (MASLoader.morseCode.TryGetValue(first, out clip)) - { - audioSource.clip = clip; - audioSource.volume = morseVolume; - audioSource.Play(); - yield return new WaitForSecondsRealtime(clip.length + 0.05f); - } - - morseSequence = morseSequence.Substring(1); - } - - playingSequence = false; - yield return null; - } - - /// - /// Play a morse code sequence. - /// - /// - /// - /// - /// - internal double PlayMorseSequence(string sequence, float volume, bool stopCurrent) - { - if (stopCurrent) - { - morseAudioSource.Stop(); - } - else if (morseAudioSource.isPlaying) - { - return 0.0; - } - - morseSequence = sequence.ToUpper(); - morseVolume = GameSettings.SHIP_VOLUME * volume; - - if (!playingSequence) - { - StartCoroutine(MorsePlayerCoroutine()); - playingSequence = true; - } - - return 1.0; - } - - /// - /// Select and play an audio clip. - /// - /// If stopCurrent is true, any current audio is stopped, first. If stopCurrent is - /// false and audio is playing, the new clip does not play. - /// - /// URI of the clip to load & play. - /// Volume (clamped to [0, 1]) for playback. - /// Whether current audio should be stopped. - /// true if the clip was loaded and is playing, false if the clip was not played. - internal bool PlayAudio(string clipName, float volume, bool stopCurrent) - { - AudioClip clip = GameDatabase.Instance.GetAudioClip(clipName); - if (clip == null) - { - return false; - } - - if (stopCurrent) - { - audioSource.Stop(); - } - else if (audioSource.isPlaying) - { - return false; - } - - audioSource.clip = clip; - audioSource.volume = GameSettings.SHIP_VOLUME * volume; - audioSource.Play(); - return true; - } - - #endregion - - /// - /// Identify the current IVA Kerbal. If the kerbal is not in the current part, - /// return null. - /// - /// - internal Kerbal FindCurrentKerbal() - { - if (vessel.GetCrewCount() == 0) - { - return null; - } - Kerbal activeKerbal = CameraManager.Instance.IVACameraActiveKerbal; - if (activeKerbal.InPart == part) - { - return activeKerbal; - } - else - { - return null; - } - } - - #region GameEvent Callbacks - /// - /// Callback when the player changes camera modes. - /// - /// - private void onCameraChange(CameraManager.CameraMode newMode) - { - // newMode == Flight -> heading to external view. - // newMode == Map -> heading to Map view - // newMode == IVA -> heading to IVA view. - if (newMode == CameraManager.CameraMode.IVA) - { - Kerbal activeKerbal = FindCurrentKerbal(); - if (activeKerbal != null) - { - // There are situations where InternalCamera is null - like during staging - // from IVA when the IVA isn't in the new active vessel. - if (InternalCamera.Instance != null) - { - InternalCamera.Instance.maxRot = maxRot; - InternalCamera.Instance.minPitch = minPitch; - InternalCamera.Instance.maxPitch = maxPitch; - } - } - if (enterIvaScript != null) - { - try - { - script.Call(enterIvaScript); - } - catch (Exception e) - { - Utility.ComplainLoudly("MASFlightComputer onEnterIVA triggered an exception"); - Utility.LogError(this, "MASFlightComputer onEnterIVA triggered an exception:"); - Utility.LogError(this, e.ToString()); - enterIvaScript = null; - } - } - } - else - { - // There are situations where InternalCamera is null - like during staging - // from IVA when the IVA isn't in the new active vessel. - if (InternalCamera.Instance != null) - { - // Reset to defaults - InternalCamera.Instance.maxRot = 80.0f; - InternalCamera.Instance.minPitch = -80.0f; - InternalCamera.Instance.maxPitch = 45.0f; - } - if (exitIvaScript != null) - { - try - { - script.Call(exitIvaScript); - } - catch (Exception e) - { - Utility.ComplainLoudly("MASFlightComputer onExitIVA triggered an exception"); - Utility.LogError(this, "MASFlightComputer onExitIVA triggered an exception:"); - Utility.LogError(this, e.ToString()); - exitIvaScript = null; - } - } - } - } - - /// - /// Callback when the player changes control points. - /// - private void OnControlPointChanged(Part who, ControlPoint where) - { - if (who == part && commandModule != null) - { - UpdateControlPoint(where.name); - } - } - - /// - /// Callback when the player changes IVA cameras. Note that the Kerbal passed in - /// is *not* the current Kerbal. It appears to be the *next* Kerbal, I think. So we - /// are forced to call FindCurrentKerbal. - /// - /// - private void OnIVACameraKerbalChange(Kerbal dontCare) - { - Kerbal newKerbal = FindCurrentKerbal(); - if (newKerbal != null) - { - // There are situations where InternalCamera is null - like during staging - // from IVA when the IVA isn't in the new active vessel. - if (InternalCamera.Instance != null) - { - InternalCamera.Instance.maxRot = maxRot; - InternalCamera.Instance.minPitch = minPitch; - InternalCamera.Instance.maxPitch = maxPitch; - } - } - } - - /// - /// General-purpose callback to make sure we refresh our data when - /// something changes. - /// - /// The Vessel being changed - private void onVesselChanged(Vessel who) - { - if (who.id == this.vessel.id) - { - // TODO: Do something different if parentVesselID != vessel.id? - Vessel vessel = this.vessel; - if (vessel.id != parentVesselId) - { - parentVesselId = vessel.id; - SetPersistent(vesselIdLabel, parentVesselId.ToString()); - } - - vc = MASPersistent.FetchVesselComputer(vessel); - ap = MASAutoPilot.Get(vessel); - fcProxy.vc = vc; - fcProxy.vessel = vessel; - chattererProxy.UpdateVessel(); - engineProxy.vc = vc; - farProxy.vessel = vessel; - kacProxy.vessel = vessel; - mjProxy.UpdateVessel(vessel, vc); - navProxy.UpdateVessel(vessel); - parachuteProxy.vc = vc; - parachuteProxy.vessel = vessel; - transferProxy.vc = vc; - transferProxy.vessel = vessel; - vtolProxy.UpdateVessel(vessel, vc); - - List parts = vessel.parts; - foreach (var pair in masActionGroup) - { - pair.Value.Rebuild(parts); - } - } - UpdateLocalCrew(); - } - #endregion - } -} diff --git a/ModifiedCode/MASFlightComputerProxy.cs b/ModifiedCode/MASFlightComputerProxy.cs deleted file mode 100644 index e3efeb65..00000000 --- a/ModifiedCode/MASFlightComputerProxy.cs +++ /dev/null @@ -1,5462 +0,0 @@ -/***************************************************************************** - * The MIT License (MIT) - * - * Copyright (c) 2016-2020 MOARdV - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to - * deal in the Software without restriction, including without limitation the - * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or - * sell copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - * - ****************************************************************************/ -using MoonSharp.Interpreter; -using System; -using System.Collections.Generic; -using System.Text; -using UnityEngine; - -namespace AvionicsSystems -{ - // ΔV - put this somewhere where I can find it easily to copy/paste - - /// - /// The flight computer proxy provides the interface between the flight - /// computer module and the variable / Lua environment. - /// - /// While it is a wrapper for MASFlightComputer, not all - /// values are plumbed through to the flight computer (for instance, the - /// action group control and state are all handled in this class). - /// - /// fc - /// - /// The `fc` group contains the core interface between KSP, Avionics - /// Systems, and props in an IVA. It consists of many 'information' functions - /// that can be used to get information as well as numerous 'action' functions - /// that are used to do things. - /// - /// Due to the number of methods in the `fc` group, this document has been split - /// across three pages: - /// - /// * [[MASFlightComputerProxy]] (Abort - Lights), - /// * [[MASFlightComputerProxy2]] (Maneuver Node - Reaction Wheel), and - /// * [[MASFlightComputerProxy3]] (Resources - Vessel Info). - /// - /// **NOTE 1:** If a function listed below includes an entry for 'Supported Mod(s)', - /// then that function will automatically use one of the mods listed to - /// generate the data. In some cases, it is possible that the function does not work without - /// one of the required mods. Those instances are noted in the function's description. - /// - /// **NOTE 2:** Many descriptions make use of mathetmatical short-hand to describe - /// a range of values. This short-hand consists of using square brackets `[` and `]` - /// to denote "inclusive range", while parentheses `(` and `)` indicate exclusive range. - /// - /// For example, if a parameter says "an integer between [0, `fc.ExperimentCount()`)", it - /// means that the parameter must be an integer greater than or equal to 0, but less - /// than `fc.ExperimentCount()`. - /// - /// For another example, if a parameter says "a number in the range [0, 1]", it means that - /// the number must be at least zero, and it must not be larger than 1. - /// - internal partial class MASFlightComputerProxy - { - internal const double KelvinToCelsius = -273.15; - - private MASFlightComputer fc; - internal MASVesselComputer vc; - internal MASIFAR farProxy; - internal MASIKerbalEngineer keProxy; - internal MASIMechJeb mjProxy; - internal Vessel vessel; - - private VesselAutopilot.AutopilotMode autopilotMode = VesselAutopilot.AutopilotMode.StabilityAssist; - private int vesselSituationConverted; - - private ApproachSolver nodeApproachSolver; - - private CommNet.CommLink lastLink; - - [MoonSharpHidden] - public MASFlightComputerProxy(MASFlightComputer fc, MASIFAR farProxy, MASIKerbalEngineer keProxy, MASIMechJeb mjProxy) - { - this.fc = fc; - this.farProxy = farProxy; - this.keProxy = keProxy; - this.mjProxy = mjProxy; - this.nodeApproachSolver = new ApproachSolver(); - } - - ~MASFlightComputerProxy() - { - fc = null; - vc = null; - farProxy = null; - keProxy = null; - mjProxy = null; - vessel = null; - } - - /// - /// Helper function to convert a vessel situation into a number. - /// - /// - /// - [MoonSharpHidden] - static internal int ConvertVesselSituation(Vessel.Situations vesselSituation) - { - int situation = (int)vesselSituation; - for (int i = 0; i < 0x10; ++i) - { - if ((situation & (1 << i)) != 0) - { - return i; - } - } - - return 0; - } - - /// - /// Per-FixedUpdate updater method to read some of those values that are used a lot. - /// - [MoonSharpHidden] - internal void Update() - { - autopilotMode = vessel.Autopilot.Mode; - nodeApproachSolver.ResetComputation(); - - vesselSituationConverted = ConvertVesselSituation(vessel.situation); - - for (int i = neighboringVessels.Length - 1; i >= 0; --i) - { - neighboringVessels[i] = null; - } - neighboringVesselsCurrent = false; - - try - { - lastLink = vessel.connection.ControlPath.Last; - } - catch - { - lastLink = null; - } - } - - private kerbalExpressionSystem[] crewExpression = new kerbalExpressionSystem[0]; - [MoonSharpHidden] - private kerbalExpressionSystem GetVesselCrewExpression(int index) - { - if (crewExpression.Length != vessel.GetCrewCount()) - { - crewExpression = new kerbalExpressionSystem[vessel.GetCrewCount()]; - } - - vessel.GetVesselCrew()[index].KerbalRef.GetComponentCached(ref crewExpression[index]); - - return crewExpression[index]; - } - - /// - /// Private method to map a string or number to a CelestialBody. - /// - /// A string or number identifying the celestial body. - [MoonSharpHidden] - private CelestialBody SelectBody(object id) - { - CelestialBody cb = null; - - if (id is double) - { - int idx = (int)(double)id; - if (idx >= 0 && idx < FlightGlobals.Bodies.Count) - { - cb = FlightGlobals.Bodies[idx]; - } - } - else if (id is string) - { - string bodyName = id as string; - cb = FlightGlobals.Bodies.Find(x => (x.bodyName == bodyName)); - } - - return cb; - } - - - // Keep a scratch list handy. The members of the array are null'd after TargetNextVessel - // executes to make sure we're not holding dangling references. This could be written more - // efficiently, but I don't see this being used extensively. - private Vessel[] neighboringVessels = new Vessel[0]; - private VesselDistanceComparer distanceComparer = new VesselDistanceComparer(); - private List localVessels = new List(); - private bool neighboringVesselsCurrent = false; - - [MoonSharpHidden] - private bool EnabledType(global::VesselType type) - { - return fc.activeVesselFilter.FindIndex(x => x == type) != -1; - } - - [MoonSharpHidden] - private void UpdateNeighboringVessels() - { - if (!neighboringVesselsCurrent) - { - // Populate - var allVessels = FlightGlobals.fetch.vessels; - int allVesselCount = allVessels.Count; - CelestialBody mainBody = vessel.mainBody; - for (int i = 0; i < allVesselCount; ++i) - { - Vessel v = allVessels[i]; - if (v.mainBody == mainBody && EnabledType(v.vesselType) && v != vessel) - { - localVessels.Add(v); - } - } - - int arrayLength = neighboringVessels.Length; - if (arrayLength != localVessels.Count) - { - neighboringVessels = localVessels.ToArray(); - } - else - { - for (int i = 0; i < arrayLength; ++i) - { - neighboringVessels[i] = localVessels[i]; - } - } - localVessels.Clear(); - - distanceComparer.vesselPosition = vessel.GetTransform().position; - try - { - Array.Sort(neighboringVessels, distanceComparer); - } - catch (Exception e) - { - throw new ArgumentException("Error in UpdateNeighboringVessels due to distanceComparer: \"" + e.Source + e.TargetSite + e.Data + e.StackTrace + neighboringVessels + "\"", e); - } - - neighboringVesselsCurrent = true; - } - } - - private class VesselDistanceComparer : IComparer - { - internal Vector3 vesselPosition; - public int Compare(Vessel a, Vessel b) - { - float distA = Vector3.SqrMagnitude(a.GetTransform().position - vesselPosition); - float distB = Vector3.SqrMagnitude(b.GetTransform().position - vesselPosition); - return (int)(distA - distB); - } - } - - /// - /// The Abort action and the GetAbort query belong in this category. - /// - #region Abort - /// - /// Trigger the Abort action group. - /// - /// 1 (abort is always a SET, not a toggle). - public double Abort() - { - vessel.ActionGroups.SetGroup(KSPActionGroup.Abort, true); - return 1.0; - } - - /// - /// Returns 1 if the Abort action has been triggered. - /// - /// - public double GetAbort() - { - return (vessel.ActionGroups[KSPActionGroup.Abort]) ? 1.0 : 0.0; - } - #endregion - - /// - /// Variables and actions related to player-configured action groups are in this - /// category. - /// - #region Action Groups - private static readonly KSPActionGroup[] ags = { KSPActionGroup.Custom10, KSPActionGroup.Custom01, KSPActionGroup.Custom02, KSPActionGroup.Custom03, KSPActionGroup.Custom04, KSPActionGroup.Custom05, KSPActionGroup.Custom06, KSPActionGroup.Custom07, KSPActionGroup.Custom08, KSPActionGroup.Custom09 }; - - /// - /// Returns 1 if there is at least one action associated with the action - /// group. 0 otherwise, or if an invalid action group is specified. - /// - /// A number between 0 and 9 (inclusive) for stock action groups, 10 or larger for MAS action groups. - /// 1 if there are actions for this action group, 0 otherwise. - public double ActionGroupHasActions(double groupID) - { - if (groupID >= 10.0) - { - MASActionGroup ag; - if (fc.masActionGroup.TryGetValue((int)groupID, out ag)) - { - return (ag.HasActions()) ? 1.0 : 0.0; - } - else - { - return 0.0; - } - } - else if (groupID < 0.0) - { - return 0.0; - } - else - { - return (vc.GroupHasActions(ags[(int)groupID])) ? 1.0 : 0.0; - } - } - - /// - /// Returns the current memo from the action group selected by groupID. If - /// the memo was configured with active and inactive descriptions, this memo - /// will change. If an invalid groupID is provided, the result is an - /// empty string. If no memo was specified, the result is "AG0" for action - /// group 0, "AG1" for action group 1, etc. - /// - /// A number between 0 and 9 (inclusive). Note that MAS action groups do not have an AG Memo. - /// The memo for the requested group, or an empty string. - public string ActionGroupActiveMemo(double groupID) - { - int ag = (int)groupID; - if (ag < 0 || ag > 9) - { - return string.Empty; - } - else if (vessel.ActionGroups[ags[ag]]) - { - return fc.agMemoOn[ag]; - } - else - { - return fc.agMemoOff[ag]; - } - } - - /// - /// Returns the action group memo specified by the groupID, with active - /// selecting whether the memo is for the active mode or the inactive mode. - /// If the selected memo does not differentiate between active and inactive, - /// the result is the same. If an invalid groupID is provided, the result is an - /// empty string. If no memo was specified, the result is "AG0" for action - /// group 0, "AG1" for action group 1, etc. - /// - /// A number between 0 and 9 (inclusive). Note that MAS action groups do not have an AG Memo. - /// Whether the memo is for the active (true) or inactive (false) setting. - /// The memo for the requested group and state, or an empty string. - public string ActionGroupMemo(double groupID, bool active) - { - if (groupID < 0.0 || groupID > 9.0) - { - return string.Empty; - } - else if (active) - { - return fc.agMemoOn[(int)groupID]; - } - else - { - return fc.agMemoOff[(int)groupID]; - } - } - - /// - /// Get the current state of the specified action group. - /// - /// A number between 0 and 9 (inclusive) for stock action groups, 10 or larger for MAS action groups. - /// 1 if active, 0 if inactive - public double GetActionGroup(double groupID) - { - if (groupID >= 10.0) - { - MASActionGroup actionGroup; - if (fc.masActionGroup.TryGetValue((int)groupID, out actionGroup)) - { - return (actionGroup.GetState()) ? 1.0 : 0.0; - } - else - { - return 0.0; - } - } - else if (groupID < 0.0) - { - return 0.0; - } - else - { - return (vessel.ActionGroups[ags[(int)groupID]]) ? 1.0 : 0.0; - } - } - - /// - /// Set the specified action group to the requested state. - /// - /// A number between 0 and 9 (inclusive) for stock action groups, 10 or larger for MAS action groups. - /// true or false to set the state. - /// 1 if the action group ID was valid, 0 otherwise. - public double SetActionGroup(double groupID, bool active) - { - if (groupID >= 10.0) - { - MASActionGroup actionGroup; - if (fc.masActionGroup.TryGetValue((int)groupID, out actionGroup)) - { - actionGroup.SetState(active); - - return 1.0; - } - } - else if (groupID >= 0.0) - { - vessel.ActionGroups.SetGroup(ags[(int)groupID], active); - return 1.0; - } - - return 0.0; - } - - /// - /// Toggle the selected action group or MAS action group. - /// - /// A number between 0 and 9 (inclusive) for stock action groups, 10 or larger for MAS action groups. - /// 1 if the action group ID was valid, 0 otherwise. - public double ToggleActionGroup(double groupID) - { - if (groupID >= 10.0) - { - MASActionGroup actionGroup; - if (fc.masActionGroup.TryGetValue((int)groupID, out actionGroup)) - { - actionGroup.Toggle(); - return 1.0; - } - } - else if (groupID >= 0.0) - { - vessel.ActionGroups.ToggleGroup(ags[(int)groupID]); - return 1.0; - } - - return 0.0; - } - #endregion - - /// - /// Variables relating to the current vessel's altitude are found in this category. - /// - #region Altitudes - /// - /// Returns the vessel's altitude above the datum (sea level where - /// applicable), in meters. - /// - /// - public double Altitude() - { - return vc.altitudeASL; - } - - /// - /// Returns altitude above datum (or sea level) for vessels in an - /// atmosphere. Returns 0 otherwise. Altitude in meters. - /// - /// - public double AltitudeAtmospheric() - { - if (vc.mainBody.atmosphere) - { - return (vc.altitudeASL < vc.mainBody.atmosphereDepth) ? vc.altitudeASL : 0.0; - } - else - { - return 0.0; - } - } - - /// - /// Returns the distance from the lowest point of the craft to the - /// surface of the planet. Ocean is treated as surface for this - /// purpose. Precision reporting sets in at 500m (above 500m it - /// reports the same as AltitudeTerrain(false)). Distance in - /// meters. - /// - /// - public double AltitudeBottom() - { - return vc.altitudeBottom; - } - - /// - /// Returns the height above the ground, optionally treating the ocean - /// surface as ground. Altitude in meters. - /// - /// When false, returns height above sea level - /// when over the ocean; when true, always returns ground height. - /// Altitude above the terrain in meters. - public double AltitudeTerrain(bool ignoreOcean) - { - return (ignoreOcean) ? vc.altitudeTerrain : Math.Min(vc.altitudeASL, vc.altitudeTerrain); - } - - /// - /// Returns the terrain height beneath the vessel relative to the planet's datum (sea - /// level or equivalent). Height in meters. - /// - /// - public double TerrainHeight() - { - return vessel.terrainAltitude; - } - #endregion - - /// - /// Atmosphere and airflow variables are found in this category. - /// - #region Atmosphere - /// - /// Returns the atmospheric depth as reported by the KSP atmosphere - /// gauge, a number ranging between 0 and 1. - /// - /// - public double AtmosphereDepth() - { - return vc.atmosphereDepth; - } - - /// - /// Returns the altitude of the top of atmosphere, or 0 if there is no - /// atmo. Altitude in meters. - /// - /// - public double AtmosphereTop() - { - return vc.mainBody.atmosphereDepth; - } - - /// - /// Returns the atmospheric density. - /// - /// - public double AtmosphericDensity() - { - return vessel.atmDensity; - } - - /// - /// Returns the drag force on the vessel. If FAR is installed, this variable uses - /// FAR's computation for drag. - /// - /// Drag in kN. - public double Drag() - { - if (vc.mainBody.atmosphere == false || vc.altitudeASL > vc.mainBody.atmosphereDepth) - { - return 0.0; - } - - if (MASIFAR.farFound) - { - return farProxy.DragForce(); - } - else - { - return vc.DragForce(); - } - } - - /// - /// Returns the drag effect on the vessel as acceleration. If FAR is installed, this variable uses - /// FAR's computation for drag. - /// - /// Drag acceleration in m/s^2. - public double DragAccel() - { - if (vc.mainBody.atmosphere == false || vc.altitudeASL > vc.mainBody.atmosphereDepth) - { - return 0.0; - } - - if (MASIFAR.farFound) - { - return farProxy.DragForce() / vessel.totalMass; - } - else - { - return vc.DragForce() / vessel.totalMass; - } - } - - /// - /// Returns the current dynamic pressure on the vessel in kPa. If FAR - /// is installed, this variable uses FAR's computation instead. - /// - /// Dynamic pressure in kPa. - public double DynamicPressure() - { - if (MASIFAR.farFound) - { - return farProxy.DynamicPressure(); - } - else - { - return vessel.dynamicPressurekPa; - } - } - - /// - /// Returns the force of gravity affecting the vessel. - /// - /// Force of gravity in kN. - public double GravityForce() - { - return vc.GravForce(); - } - - /// - /// Returns 1 if the body the vessel is orbiting has an atmosphere. - /// - /// 1 if there is an atmosphere, 0 otherwise. - public double HasAtmosphere() - { - return (vc.mainBody.atmosphere) ? 1.0 : 0.0; - } - - /// - /// Returns the lift force on the vessel. If FAR is installed, this variable uses - /// FAR's computation for lift. - /// - /// Lift in kN. - public double Lift() - { - if (vc.mainBody.atmosphere == false || vc.altitudeASL > vc.mainBody.atmosphereDepth) - { - return 0.0; - } - - if (MASIFAR.farFound) - { - return farProxy.LiftForce(); - } - else - { - return vc.LiftForce(); - } - } - - /// - /// Returns the force of lift opposed to gravity. If FAR is installed, this variable uses - /// FAR's computations for lift. - /// - /// Lift opposed to gravity in kN. - public double LiftUpForce() - { - if (vc.mainBody.atmosphere == false || vc.altitudeASL > vc.mainBody.atmosphereDepth) - { - return 0.0; - } - - if (MASIFAR.farFound) - { - return farProxy.LiftForce() * Vector3d.Dot(vc.up, vc.top); - } - else - { - return vc.LiftUpForce(); - } - } - - /// - /// Returns the static atmospheric pressure outside the vessel in standard atmospheres. - /// - /// - public double StaticPressureAtm() - { - return vessel.staticPressurekPa * PhysicsGlobals.KpaToAtmospheres; - } - - /// - /// Returns the static atmospheric pressure outside the vessel in kiloPascals. - /// - /// Static pressure in kPa. - public double StaticPressureKPa() - { - return vessel.staticPressurekPa; - } - - /// - /// Returns the current terminal velocity of the vessel. If the vessel is not in - /// an atmosphere, returns 0. If FAR is installed, returns FAR's terminal velocity - /// result. - /// - /// Terminal velocity in m/s. - public double TerminalVelocity() - { - if (vc.mainBody.atmosphere == false || vc.altitudeASL > vc.mainBody.atmosphereDepth) - { - return 0.0; - } - - if (MASIFAR.farFound) - { - return farProxy.TerminalVelocity(); - } - else - { - return vc.TerminalVelocity(); - } - } - - #endregion - - /// - /// The Autopilot region provides information about and control over the MAS Vessel - /// Computer Control system (which needs a cool name amenable to acronyms). - /// - /// The attitude control pilot is very similar to MechJeb's advanced SASS modes, but - /// it uses the stock SAS module to provide steering control. - /// - /// Some caveats about the autopilot subsystems: - /// - /// The attitude control pilot uses the stock SAS feature to provide steering control. - /// When it is engaged, SAS is usually in Stability Control mode. If SAS is changed to a - /// different mode (such as Prograde), the attitude control pilot is disengaged. - /// Likewise, if Stability Control is selected, the attitude pilot disengages. Turning - /// off SAS will disengage the pilots. - /// - /// Other MAS autopilots may use the attitude control system to steer the vessel. If - /// the attitude control pilot is disengaged, the other autopilot is also disengaged. - /// - /// There are several supported references available to the MAS autopilot system, as detailed - /// here. "Forward" is defined as the front of the vessel (nose of a space plane, or the top - /// of a vertically-launched rocket). "Up" is the direction of the heads of kerbals sitting - /// in a conventional orientation relative to the forward direction, such that their heads point - /// away from the surface of the planet in horizontal flight. - /// - /// **TODO:** Fully document these reference frames. - /// - /// * 0 - Inertial Frame: The reference frame of the universe. - /// * 1 - Orbital Prograde: Forward = orbital prograde, Up = surface-relative up (radial out). - /// * 2 - Orbital Prograde Horizontal: Forward = horizontal, aligned towards orbital prograde, Up = surface-relative up (radial out). - /// * 3 - Surface Prograde: Forward = surface-relative prograde, Up = surface-relative up (radial out). - /// * 4 - Surface Prograde Horizontal: Forward = horizontal, aligned towards surface-relative prograde, Up = surface-relative up (radial out). - /// * 5 - Surface North: Forward = planetary north, Up = surface-relative up. - /// * 6 - Target: Forward = direction towards the target, Up = perpendicular to the target direction and orbit normal. - /// * 7 - Target Prograde: Forward = towards the target-relative velocity vector, Up = perpendicular to the velocity vector and orbit normal. - /// * 8 - Target Orientation: Forward = target's forward direction, Up = target's up direction. - /// * 9 - Maneuver Node: Forward = facing towards the maneuver vector, Up = radial out at the time of the burn. - /// * 10 - Sun: Forward = facing Kerbol, Up = orbital normal of the body orbiting Kerbol. - /// * 11 - Up: Forward = surface-relative up, Up = planetary north. - /// - #region Autopilot - - /// - /// Disengage the Ascent Control Pilot. - /// - /// 1 if the pilot was on, 0 if it was already disengaged. - public double DisengageAscentPilot() - { - return (fc.ap.DisengageAscentPilot()) ? 1.0 : 0.0; - } - - /// - /// Engage the Ascent Control Pilot. - /// - /// **NOTE: This function is not enabled, and it always returns 0.** - /// - /// If invalid parameters are supplied, or the vessel is in flight, the pilot will - /// not activate. - /// - /// Goal apoapsis, in meters. - /// Goal periapsis, in meters. - /// Orbital inclination. - /// Horizon-relative roll to maintain during ascent. - /// 1 if the pilot is engaged, 0 if it failed to engage. - public double EngageAscentPilot(double apoapsis, double periapsis, double inclination, double roll) - { - //return (fc.ap.EngageAscentPilot(apoapsis, periapsis, inclination, roll)) ? 1.0 : 0.0; - return 0.0; - } - - /// - /// Engage the MAS Attitude Control Pilot to hold the vessel's heading towards - /// the reference direction vector. The `reference` field must be one of: - /// - /// * 0 - Inertial Frame - /// * 1 - Orbital Prograde - /// * 2 - Orbital Prograde Horizontal - /// * 3 - Surface Prograde - /// * 4 - Surface Prograde Horizontal - /// * 5 - Surface North - /// * 6 - Target - /// * 7 - Target Prograde - /// * 8 - Target Orientation - /// * 9 - Maneuver Node - /// * 10 - Sun - /// * 11 - Up - /// - /// This function is equivalent of `fc.EngageAttitudePilot(reference, 0, 0)`. - /// - /// Reference vector, as described in the summary. - /// 1 if the pilot was engaged, otherwise 0. - public double EngageAttitudePilot(double reference) - { - int refAtt = (int)reference; - if (refAtt < 0 || refAtt >= MASAutoPilot.referenceAttitudes.Length) - { - return 0.0; - } - - return fc.ap.EngageAttitudePilot(MASAutoPilot.referenceAttitudes[refAtt], 0.0f, 0.0f) ? 1.0 : 0.0; - } - - /// - /// Engage the MAS Attitude Control Pilot to hold the vessel's heading towards - /// an offset relative to the reference direction vector. The `reference` field must be one of: - /// - /// * 0 - Inertial Frame - /// * 1 - Orbital Prograde - /// * 2 - Orbital Prograde Horizontal - /// * 3 - Surface Prograde - /// * 4 - Surface Prograde Horizontal - /// * 5 - Surface North - /// * 6 - Target - /// * 7 - Target Prograde - /// * 8 - Target Orientation - /// * 9 - Maneuver Node - /// * 10 - Sun - /// * 11 - Up - /// - /// This version does not lock the roll of the vessel to a particular orientation. - /// - /// Reference vector, as described in the summary. - /// 1 if the pilot was engaged, otherwise 0. - public double EngageAttitudePilot(double reference, double heading, double pitch) - { - int refAtt = (int)reference; - if (refAtt < 0 || refAtt >= MASAutoPilot.referenceAttitudes.Length) - { - return 0.0; - } - - return fc.ap.EngageAttitudePilot(MASAutoPilot.referenceAttitudes[refAtt], (float)heading, (float)pitch) ? 1.0 : 0.0; - } - - /// - /// Engages SAS and sets the vessel's heading based on the reference attitude, heading, pitch, and roll. - /// The reference attitude is one of the following: - /// - /// * 0 - Inertial Frame - /// * 1 - Orbital Prograde - /// * 2 - Orbital Prograde Horizontal - /// * 3 - Surface Prograde - /// * 4 - Surface Prograde Horizontal - /// * 5 - Surface North - /// * 6 - Target - /// * 7 - Target Prograde - /// * 8 - Target Orientation - /// * 9 - Maneuver Node - /// * 10 - Sun - /// * 11 - Up - /// - /// Reference attitude, as described in the summary. - /// Heading (yaw) relative to the reference attitude. - /// Pitch relative to the reference attitude. - /// Roll relative to the reference attitude. - /// 1 if the SetHeading command succeeded, 0 otherwise. - public double EngageAttitudePilot(double reference, double heading, double pitch, double roll) - { - int refAtt = (int)reference; - if (refAtt < 0 || refAtt >= MASAutoPilot.referenceAttitudes.Length) - { - return 0.0; - } - - return (fc.ap.EngageAttitudePilot(MASAutoPilot.referenceAttitudes[refAtt], new Vector3((float)heading, (float)pitch, (float)roll))) ? 1.0 : 0.0; - } - - /// - /// Returns 1 if the MAS ascent autopilot is active, 0 if it is idle. - /// - /// - public double GetAscentPilotActive() - { - return (fc.ap.ascentPilotEngaged) ? 1.0 : 0.0; - } - - /// - /// Reports if the attitude control pilot is actively attempting to control - /// the vessel's heading. This pilot could be active if the crew used - /// `fc.SetHeading()` to set the vessel's heading, or if another pilot module - /// is using the attitude pilot's service. - /// - /// 1 if the attitude control pilot is active, 0 otherwise. - public double GetAttitudePilotActive() - { - return (fc.ap.attitudePilotEngaged) ? 1.0 : 0.0; - } - - /// - /// Returns the currently stored heading offset in the atittude control pilot. - /// - /// Heading relative to the reference attitude, in degrees. - public double GetAttitudePilotHeading() - { - return fc.ap.relativeHPR.x; - } - - /// - /// Returns the currently stored pitch offset in the atittude control pilot. - /// - /// Pitch relative to the reference attitude, in degrees. - public double GetAttitudePilotPitch() - { - return fc.ap.relativeHPR.y; - } - - /// - /// Returns the currently stored roll offset in the atittude control pilot. - /// - /// Roll relative to the reference attitude, in degrees. - public double GetAttitudePilotRoll() - { - return fc.ap.relativeHPR.z; - } - - /// - /// Returns the current attitude reference mode. This value may be one of - /// the following: - /// - /// * 0 - Inertial Frame - /// * 1 - Orbital Prograde - /// * 2 - Orbital Prograde Horizontal - /// * 3 - Surface Prograde - /// * 4 - Surface Prograde Horizontal - /// * 5 - Surface North - /// * 6 - Target - /// * 7 - Target Prograde - /// * 8 - Target Orientation - /// * 9 - Maneuver Node - /// * 10 - Sun - /// * 11 - Up - /// - /// This reference mode does not indicate whether the attitude control pilot is - /// active, but it does indicate which reference attitude will take effect if the - /// pilot is engaged. Refer to the documentation for `fc.SetHeading()` for a - /// detailed explanation of the attitude references. - /// - /// One of the numbers listed in the summary. - public double GetAttitudeReference() - { - return (int)fc.ap.activeReference; - } - - /// - /// Returns 1 if the MAS maneuver autopilot is active, 0 if it is idle. - /// - /// - public double GetManeuverPilotActive() - { - return (fc.ap.maneuverPilotEngaged) ? 1.0 : 0.0; - } - - /// - /// Returns 1 if any MAS autopilot is active, 0 if all are idle. - /// - /// - public double GetPilotActive() - { - return (fc.ap.PilotActive()) ? 1.0 : 0.0; - } - - /// - /// Set the attitude pilot to the selected state. If another pilot is using - /// the attitude pilot (such as the launch pilot), switching off the attitude - /// pilot will disengage the other pilot as well. - /// - /// **CAUTION:** If the attitude system has not been initialized, this function - /// may select the orbital prograde - /// attitude, which may cause problems during launch or reentry. - /// - /// If true, engage the autopilot and restore the previous attitude. - /// Returns 1 if the autopilot is now on, 0 if it is now off. - public double SetAttitudePilotActive(bool active) - { - if (active != fc.ap.attitudePilotEngaged) - { - if (!active) - { - // Shutoff is easy. - fc.ap.DisengageAutopilots(); - } - else - { - fc.ap.ResumeAttitudePilot(); - } - } - - return (fc.ap.attitudePilotEngaged) ? 1.0 : 0.0; - } - - /// - /// Sets the maneuver autopilot state to active or not based on 'active'. - /// If no valid maneuver node exists, activating the maneuver pilot has no effect. - /// - /// If true, attempts to activate the maneuver autopilot; if false, deactivates it. - /// 1 if the maneuver autopilot is active, 0 if it is not active. - public double SetManeuverPilotActive(bool active) - { - if (active != fc.ap.maneuverPilotEngaged) - { - if (!active) - { - // Shutoff is easy. - fc.ap.DisengageAutopilots(); - } - else - { - // Engaging takes a couple of extra steps - fc.ap.EngageManeuverPilot(); - } - } - - return (fc.ap.maneuverPilotEngaged) ? 1.0 : 0.0; - } - - /// - /// Toggle the MAS attitude pilot. The exisiting reference attitude and heading, pitch, and roll - /// are restored. If another pilot is using - /// the attitude pilot (such as the launch pilot), switching off the attitude - /// pilot will disengage the other pilot as well. - /// - /// **CAUTION:** If the attitude system has not been initialized, it defaults to a orbital - /// prograde, which may not be desired. - /// - /// Returns 1 if the autopilot is now on, 0 if it is now off. - public double ToggleAttitudePilot() - { - if (fc.ap.attitudePilotEngaged) - { - fc.ap.DisengageAutopilots(); - } - else - { - fc.ap.ResumeAttitudePilot(); - } - - return (fc.ap.attitudePilotEngaged) ? 1.0 : 0.0; - } - - /// - /// Toggles the maneuver autopilot. - /// - /// 1 if the maneuver pilot is now active, 0 if it is now inactive. - public double ToggleManeuverPilot() - { - if (fc.ap.maneuverPilotEngaged) - { - fc.ap.DisengageAutopilots(); - } - else - { - fc.ap.EngageManeuverPilot(); - } - - return (fc.ap.maneuverPilotEngaged) ? 1.0 : 0.0; - } - #endregion - - /// - /// Information about the Celestial Bodies may be fetched using the - /// methods in this category. - /// - /// Most of these methods function in one of two ways: by name or - /// by number. By name means using the name of the body to select - /// it (eg, `fc.BodyMass("Jool")`). However, since strings are - /// slightly slower than numbers for lookups, these methods will - /// accept numbers. The number for a world can be fetched using - /// `fc.BodyIndex()`, `fc.CurrentBodyIndex()`, or `fc.TargetBodyIndex()`. - /// - #region Body - [MASProxy(Dependent = true)] - /// - /// Returns the surface area of the selected body. - /// - /// The name or index of the body of interest. - /// Surface area of the planet in m^2. - public double BodyArea(object id) - { - CelestialBody cb = SelectBody(id); - - return (cb == null) ? 0.0 : cb.SurfaceArea; - } - - [MASProxy(Dependent = true)] - /// - /// Returns the altitude of the top of the selected body's - /// atmosphere. If the body does not have an atmosphere, or - /// an invalid body is selected, 0 is returned. - /// - /// The name or index of the body of interest. - /// The altitude of the top of the atmosphere, in meters, or 0. - public double BodyAtmosphereTop(object id) - { - CelestialBody cb = SelectBody(id); - - double atmoTop = 0.0; - if (cb != null && cb.atmosphere) - { - atmoTop = cb.atmosphereDepth; - } - - return atmoTop; - } - - [MASProxy(Dependent = true)] - /// - /// Returns the name of the biome at the given location on the selected body. - /// - /// The name or index of the body of interest. - /// Latitude of the location of interest. - /// Longitude of the location of interest. - /// The name of the biome at the specified location, or an empty string. - public string BodyBiome(object id, double latitude, double longitude) - { - CelestialBody cb = SelectBody(id); - if (cb != null) - { - string biome = ScienceUtil.GetExperimentBiome(cb, latitude, longitude); - //ScienceUtil.GetBiomedisplayName(cb, biome); - // string biome = ScienceUtil.GetExperimentBiomeLocalized(cb, latitude, longitude); - if (ScienceUtil.BiomeIsUnlisted(cb, biome)) - { - // What causes this? And what action should I take? - Utility.LogWarning(this, "BodyBiome(): biome '{0}' is unlisted", biome); - } - return biome; - } - - return string.Empty; - } - - [MASProxy(Immutable = true)] - /// - /// The number of Celestial Bodies in the database. - /// - /// - public double BodyCount() - { - return FlightGlobals.Bodies.Count; - } - - [MASProxy(Dependent = true)] - /// - /// Returns the distance to the requested body, in meters. - /// - /// The name or index of the body of interest. - /// Distance in meters, or 0 if invalid. - public double BodyDistance(object id) - { - CelestialBody cb = SelectBody(id); - - return (cb != null) ? Vector3d.Distance(vessel.GetTransform().position, cb.GetTransform().position) : 0.0; - } - - [MASProxy(Dependent = true)] - /// - /// Returns the escape velocity of the body. - /// - /// The name or index of the body of interest. - /// Escape velocity in m/s, or 0 if the body was invalid. - public double BodyEscapeVelocity(object id) - { - CelestialBody cb = SelectBody(id); - - double escapeVelocity = 0.0; - if (cb != null) - { - escapeVelocity = Math.Sqrt(2.0 * cb.gravParameter / cb.Radius); - } - - return escapeVelocity; - } - - [MASProxy(Dependent = true)] - /// - /// Returns the acceleration from gravity as the surface - /// for the selected body. - /// - /// The name or index of the body of interest. - /// Acceleration in G's, or 0 if the body was invalid. - public double BodyGeeASL(object id) - { - CelestialBody cb = SelectBody(id); - - return (cb != null) ? cb.GeeASL : 0.0; - } - - [MASProxy(Dependent = true)] - /// - /// Returns the standard gravitational parameter of the body. - /// - /// The name or index of the body of interest. - /// GM in m^3/s^2, or 0 if the body is invalid. - public double BodyGM(object id) - { - CelestialBody cb = SelectBody(id); - - return (cb != null) ? cb.gravParameter : 0.0; - } - - [MASProxy(Dependent = true)] - /// - /// Indicates if the selected body has an atmosphere. - /// - /// The name or index of the body of interest. - /// 1 if the body has an atmosphere, 0 if it does not, or an invalid body was selected. - public double BodyHasAtmosphere(object id) - { - CelestialBody cb = SelectBody(id); - bool atmo = (cb != null) ? cb.atmosphere : false; - - return (atmo) ? 1.0 : 0.0; - } - - [MASProxy(Dependent = true)] - /// - /// Indicates if the selected body's atmosphere has oxygen. - /// - /// The name or index of the body of interest. - /// 1 if the body has an atmosphere that contains oxygen, 0 if it does not, or an invalid body was selected. - public double BodyHasOxygen(object id) - { - CelestialBody cb = SelectBody(id); - bool atmo = (cb != null) ? cb.atmosphere : false; - bool oxy = atmo && ((cb != null) ? cb.atmosphereContainsOxygen : false); - - return (oxy) ? 1.0 : 0.0; - } - - [MASProxy(Dependent = true)] - /// - /// Returns the numeric identifier for the named body. If the name is invalid - /// (no such body exists), returns -1. May also use the index, which is useful - /// for -1 and -2. - /// - /// The name of the body, eg. `"Kerbin"` or one of the indices (including -1 and -2). - /// An index from 0 to (number of Celestial Bodies - 1), or -1 if the named body was not found. - public double BodyIndex(object id) - { - string bodyName = string.Empty; - if (id is double) - { - CelestialBody cb = SelectBody(id); - if (cb != null) - { - bodyName = cb.bodyName; - } - } - else if (id is string) - { - bodyName = id as string; - } - - return (double)FlightGlobals.Bodies.FindIndex(x => x.bodyName == bodyName); - } - - [MASProxy(Dependent = true)] - /// - /// Returns 1 if the selected body is "Home" (Kerbin in un-modded KSP). - /// - /// The name or index of the body of interest. - /// 1 if the body is home, 0 otherwise. - public double BodyIsHome(object id) - { - CelestialBody cb = SelectBody(id); - if (cb != null && cb.GetName() == Planetarium.fetch.Home.GetName()) - { - return 1.0; - } - else - { - return 0.0; - } - } - - [MASProxy(Dependent = true)] - /// - /// Returns 0 if the selected body orbits the star; returns 1 if the - /// body is a moon of another body. - /// - /// The name or index of the body of interest. - /// 1 if the body is a moon, 0 if it is a planet. - public double BodyIsMoon(object id) - { - CelestialBody cb = SelectBody(id); - if (cb != null && cb.referenceBody != null && cb.referenceBody.GetName() != Planetarium.fetch.Sun.GetName()) - { - return 1.0; - } - else - { - return 0.0; - } - } - - [MASProxy(Dependent = true)] - /// - /// Returns the mass of the requested body. - /// - /// The name or index of the body of interest. - /// Mass in kg. - public double BodyMass(object id) - { - CelestialBody cb = SelectBody(id); - - return (cb != null) ? cb.Mass : 0.0; - } - - [MASProxy(Dependent = true)] - /// - /// Returns the number of worlds orbiting the selected body. If the body - /// is a planet, this is the number of moons. If the body is the Sun, this - /// number is the number of planets. - /// - /// The name or index of the body of interest. - /// The number of moons, or 0 if an invalid value was provided. - public double BodyMoonCount(object id) - { - CelestialBody cb = SelectBody(id); - if (cb != null && cb.orbitingBodies != null) - { - return cb.orbitingBodies.Count; - } - else - { - return 0.0; - } - } - - [MASProxy(Dependent = true)] - /// - /// Returns the numeric ID of the moon selected by moonIndex that orbits the body - /// selected by 'id'. If 'id' is the Sun, moonIndex selects the planet. - /// - /// The name or index of the body of interest. - /// The index of the moon to select, between 0 and 'fc.BodyMoonCount(id)' - 1. - /// Returns an index 0 or greater, or -1 for an invalid combination of 'id' and 'moonIndex'. - public double BodyMoonId(object id, double moonIndex) - { - int moonIdx = (int)moonIndex; - CelestialBody cb = SelectBody(id); - if (cb != null && cb.orbitingBodies != null && moonIdx >= 0 && moonIdx < cb.orbitingBodies.Count) - { - return (double)FlightGlobals.Bodies.FindIndex(x => x.bodyName == cb.orbitingBodies[moonIdx].bodyName); - } - else - { - return -1.0; - } - } - - [MASProxy(Dependent = true)] - /// - /// Returns the name of the requested body. While this method can be used - /// with a name for its parameter, that is kind of pointless. - /// - /// The name or index of the body of interest. - /// The name of the body, or an empty string if invalid. - public string BodyName(object id) - { - CelestialBody cb = SelectBody(id); - return (cb != null) ? cb.bodyName : string.Empty; - } - - [MASProxy(Dependent = true)] - /// - /// Returns the radius of the selected body. - /// - /// The name or index of the body of interest. - /// Radius in meters, or 0 if the body is invalid. - public double BodyRadius(object id) - { - CelestialBody cb = SelectBody(id); - - return (cb != null) ? cb.Radius : 0.0; - } - - [MASProxy(Dependent = true)] - /// - /// Returns the index of the parent of the selected body. Returns 0 (the Sun) - /// on an invalid id. - /// - /// The name or index of the body of interest. - /// Returns the index of the body that the current body orbits. - public double BodyParent(object id) - { - CelestialBody cb = SelectBody(id); - - if (cb != null && cb.referenceBody != null) - { - string bodyName = cb.referenceBody.bodyName; - - return (double)FlightGlobals.Bodies.FindIndex(x => x.bodyName == bodyName); - } - else - { - return 0.0; - } - } - - [MASProxy(Dependent = true)] - /// - /// Returns the rotation period of the body. If the body does not - /// rotate, 0 is returned. - /// - /// The name or index of the body of interest. - /// Rotation period in seconds, or 0. - public double BodyRotationPeriod(object id) - { - CelestialBody cb = SelectBody(id); - - return (cb != null && cb.rotates) ? cb.rotationPeriod : 0.0; - } - - [MASProxy(Dependent = true)] - /// - /// Returns the radius of the body's Sphere of Influence. - /// - /// The name or index of the body of interest. - /// SoI in meters - public double BodySoI(object id) - { - CelestialBody cb = SelectBody(id); - - return (cb != null) ? cb.sphereOfInfluence : 0.0; - } - - /// - /// Returns the longitude on the body that is directly below the sun (longitude of local noon). - /// - /// The name or index of the body of interest. - /// Longitude of local noon, or 0 if it could not be determined. - public double BodySunLongitude(object id) - { - CelestialBody cb = SelectBody(id); - - if (cb != null) - { - CelestialBody sun = Planetarium.fetch.Sun; - - Vector3d sunDirection = sun.position - cb.position; - - if (sunDirection.sqrMagnitude > 0.0) - { - sunDirection.Normalize(); - - return Utility.NormalizeLongitude(cb.GetLongitude(cb.position + sunDirection * cb.Radius)); - } - } - - return 0.0; - } - - [MASProxy(Dependent = true)] - /// - /// Returns the temperature of the body at sea level. - /// - /// The name or index of the body of interest. - /// If true, temperature is in Kelvin; if false, temperature is in Celsius. - /// Surface temperature in K or C; 0 if the selected object was invalid - public double BodySurfaceTemp(object id, bool useKelvin) - { - CelestialBody cb = SelectBody(id); - - double temperature = 0.0; - - if (cb != null) - { - temperature = cb.atmosphereTemperatureSeaLevel; - if (!useKelvin) - { - temperature += KelvinToCelsius; - } - } - - return temperature; - } - - [MASProxy(Dependent = true)] - /// - /// Returns the semi-major axis of a synchronous orbit with the selected body. - /// When a vessel's SMA matches the sync orbit SMA, a craft is in a synchronous - /// orbit. - /// - /// The name or index of the body of interest. - /// SMA in meters, or 0 if the body is invalid, or the synchronous orbit - /// is out side the body's SoI. - public double BodySyncOrbitSMA(object id) - { - CelestialBody cb = SelectBody(id); - - double syncOrbit = 0.0; - - if (cb != null) - { - syncOrbit = Math.Pow(cb.gravParameter * Math.Pow(cb.rotationPeriod / (2.0 * Math.PI), 2.0), 1.0 / 3.0); - - if (syncOrbit > cb.sphereOfInfluence) - { - syncOrbit = 0.0; - } - } - - return syncOrbit; - } - - [MASProxy(Dependent = true)] - /// - /// Returns the speed of a synchronous orbit. Provided an orbit is - /// perfectly circular, an orbit that has this velocity will be - /// synchronous. - /// - /// The name or index of the body of interest. - /// Velocity in m/s, or 0 if there is no synchronous orbit. - public double BodySyncOrbitVelocity(object id) - { - CelestialBody cb = SelectBody(id); - - double syncOrbitPeriod = 0.0; - - if (cb != null && cb.rotates) - { - double syncOrbit = Math.Pow(cb.gravParameter * Math.Pow(cb.rotationPeriod / (2.0 * Math.PI), 2.0), 1.0 / 3.0); - - if (syncOrbit > cb.sphereOfInfluence) - { - syncOrbit = 0.0; - } - - // Determine the circumference of the orbit. - syncOrbit *= 2.0 * Math.PI; - - syncOrbitPeriod = syncOrbit / cb.rotationPeriod; - } - - return syncOrbitPeriod; - } - - [MASProxy(Dependent = true)] - /// - /// Returns the terrain height at a given latitude and longitude relative to the - /// planet's datum (sea level or equivalent). Height in meters. - /// - /// The name or index of the body of interest. - /// Latitude of the location of interest in degrees. - /// Longitude of the location of interest in degrees. - /// Terrain height in meters. Will return negative values for locations below sea level. - public double BodyTerrainHeight(object id, double latitude, double longitude) - { - CelestialBody cb = SelectBody(id); - if (cb != null) - { - if (cb.pqsController != null) - { - return cb.pqsController.GetSurfaceHeight(QuaternionD.AngleAxis(longitude, Vector3d.down) * QuaternionD.AngleAxis(latitude, Vector3d.forward) * Vector3d.right) - cb.Radius; - } - else - { - return cb.TerrainAltitude(latitude, longitude, true); - } - } - else - { - return 0.0; - } - } - - [MASProxy(Dependent = true)] - /// - /// Returns an estimate of the terrain slope at a given latitude and longitude. - /// If the location is beneath the ocean, it provides the slope of the ocean floor. - /// Values near the poles may be very inaccurate. - /// - /// The name or index of the body of interest. - /// Latitude of the location of interest in degrees. - /// Longitude of the location of interest in degrees. - /// Slope in degrees. - public double BodyTerrainSlope(object id, double latitude, double longitude) - { - CelestialBody cb = SelectBody(id); - if (cb != null) - { - double displacementInMeters = 5.0; - - // We compute a simple normal for the point of interest by sampling - // altitudes approximately 5m meters away from the location in the four - // cardinal directions and computing the cross product of the two vectors - // we generate. - - double displacementInDegreesLatitude = 360.0 * displacementInMeters / (2.0 * Math.PI * cb.Radius); - // Clamp latitude - latitude = Math.Max(-90.0 + (displacementInDegreesLatitude * 1.5), Math.Min(90.0 - (displacementInDegreesLatitude * 1.5), latitude)); - // Account for longitudinal compression. - double displacementInDegreesLongitude = displacementInDegreesLatitude / Math.Cos(latitude * Mathf.Deg2Rad); - - PQS pqs = cb.pqsController; - if (pqs != null) - { - double westAltitude = pqs.GetSurfaceHeight(QuaternionD.AngleAxis(longitude - displacementInDegreesLongitude, Vector3d.down) * QuaternionD.AngleAxis(latitude, Vector3d.forward) * Vector3d.right); - double eastAltitude = pqs.GetSurfaceHeight(QuaternionD.AngleAxis(longitude + displacementInDegreesLongitude, Vector3d.down) * QuaternionD.AngleAxis(latitude, Vector3d.forward) * Vector3d.right); - Vector3d westEastSlope = new Vector3d(displacementInMeters * 2.0, 0.0, eastAltitude - westAltitude); - westEastSlope.Normalize(); - - double southAltitude = pqs.GetSurfaceHeight(QuaternionD.AngleAxis(longitude, Vector3d.down) * QuaternionD.AngleAxis(latitude - displacementInDegreesLatitude, Vector3d.forward) * Vector3d.right); - double northAltitude = pqs.GetSurfaceHeight(QuaternionD.AngleAxis(longitude, Vector3d.down) * QuaternionD.AngleAxis(latitude + displacementInDegreesLatitude, Vector3d.forward) * Vector3d.right); - Vector3d southNorthSlope = new Vector3d(0.0, displacementInMeters * 2.0, northAltitude - southAltitude); - southNorthSlope.Normalize(); - - Vector3d normal = Vector3d.Cross(westEastSlope, southNorthSlope); - - return Vector3d.Angle(normal, Vector3d.forward); - } - else - { - // No PQS controller? Have to use TerrainAltitude(), which seems to report bogus values. - double westAltitude = cb.TerrainAltitude(longitude - displacementInDegreesLongitude, latitude, true); - double eastAltitude = cb.TerrainAltitude(longitude + displacementInDegreesLongitude, latitude, true); - Vector3d westEastSlope = new Vector3d(displacementInMeters * 2.0, 0.0, eastAltitude - westAltitude); - westEastSlope.Normalize(); - - double southAltitude = cb.TerrainAltitude(longitude, latitude - displacementInDegreesLatitude, true); - double northAltitude = cb.TerrainAltitude(longitude, latitude + displacementInDegreesLatitude, true); - Vector3d southNorthSlope = new Vector3d(0.0, displacementInMeters * 2.0, northAltitude - southAltitude); - southNorthSlope.Normalize(); - - Vector3d normal = Vector3d.Cross(westEastSlope, southNorthSlope); - - return Vector3d.Angle(normal, Vector3d.forward); - } - } - else - { - return 0.0; - } - } - - /// - /// Returns the index of the body currently being orbited, for use as an input for other body query functions.. - /// - /// The index of the current body, used as the 'id' parameter in other body query functions. - public double CurrentBodyIndex() - { - string bodyName = vc.mainBody.bodyName; - return (double)FlightGlobals.Bodies.FindIndex(x => x.bodyName == bodyName); - } - - /// - /// Set the vessel's target to the selected body. - /// - /// The name or index of the body of interest. - /// 1 if the command succeeds, 0 if an invalid body name or index was provided. - public double SetBodyTarget(object id) - { - CelestialBody cb = SelectBody(id); - if (cb != null) - { - FlightGlobals.fetch.SetVesselTarget(cb); - - return 1.0; - } - else - { - return 0.0; - } - } - - /// - /// Returns the body index of the current target, provided the target is a celestial body. - /// If there is no target, or the current target is not a body, returns -1. - /// - /// The index of the targeted body, or -1. - public double BodyTargetIndex() - { - if (vc.targetType == MASVesselComputer.TargetType.CelestialBody) - { - string bodyName = (vc.activeTarget as CelestialBody).bodyName; - return (double)FlightGlobals.Bodies.FindIndex(x => x.bodyName == bodyName); - } - return -1.0; - } - - #endregion - - /// - /// Variables related to a vessel's brakes and air brakes are in this category. - /// - #region Brakes - /// - /// Returns 1 if the brakes action group has at least one action assigned to it. - /// - /// - public double BrakesHasActions() - { - return (vc.GroupHasActions(KSPActionGroup.Brakes)) ? 1.0 : 0.0; - } - - /// - /// Returns the number of air brakes installed on the vessel. - /// - /// Air brakes are defined as parts that have ModuleAeroSurface installed. - /// - /// 0 or more. - public double GetAirBrakeCount() - { - int i = 0; - foreach (var bob in vc.moduleAirBrake) - { - Utility.LogMessage(this, "ab{1}: deploy = {0}", - bob.deploy, - i - ); - } - return vc.moduleAirBrake.Length; - } - - /// - /// Returns 1 if any air brakes are deployed. Returns 0 if no air brakes are deployed. - /// - /// A future update *may* return a number between 0 and 1 to report the amount of - /// brake deployment. - /// - /// 1 for air brakes deployed, 0 for no air brakes deployed, or no air brakes. - public double GetAirBrakes() - { - for (int i = vc.moduleAirBrake.Length - 1; i >= 0; --i) - { - if (vc.moduleAirBrake[i].deploy) - { - return 1.0; - } - } - - return 0.0; - } - - /// - /// Returns the average brake force setting of all wheel brakes installed on the vessel. - /// - /// The brake force as a percentage of maximum, in the range of [0, 2]. - public double GetBrakeForce() - { - int numBrakes = vc.moduleBrakes.Length; - if (numBrakes > 0) - { - float netBrakeForce = 0.0f; - for (int i = 0; i < numBrakes; ++i) - { - netBrakeForce += vc.moduleBrakes[i].brakeTweakable; - } - return netBrakeForce / (float)(100 * numBrakes); - } - else - { - return 0.0; - } - } - - /// - /// Returns the current state of the Brakes action group - /// - /// 1 if the brake action group is active, 0 otherwise. - public double GetBrakes() - { - return (vessel.ActionGroups[KSPActionGroup.Brakes]) ? 1.0 : 0.0; - } - - /// - /// Sets the brake force setting of all wheel brakes installed on the vessel. - /// - /// The new brake force setting, in the range of 0 to 2. - /// The brake force as a percentage of maximum, in the range of [0, 2]. - public double SetBrakeForce(double force) - { - int numBrakes = vc.moduleBrakes.Length; - if (numBrakes > 0) - { - float clampedForce = Mathf.Clamp((float)force, 0.0f, 2.0f); - float newForce = clampedForce * 100.0f; - - for (int i = 0; i < numBrakes; ++i) - { - vc.moduleBrakes[i].brakeTweakable = newForce; - } - - return clampedForce; - } - else - { - return 0.0; - } - } - - /// - /// Set the state of the air brakes. - /// - /// 1 if air brakes are now deployed, 0 if they are now retracted or if there are no air brakes. - public double SetAirBrakes(bool active) - { - int numBrakes = vc.moduleAirBrake.Length; - if (numBrakes > 0) - { - for (int i = 0; i < numBrakes; ++i) - { - vc.moduleAirBrake[i].deploy = active; - } - - return active ? 1.0 : 0.0; - } - else - { - return 0.0; - } - } - - /// - /// Set the brake action group to the specified state. - /// - /// Sets the state of the brakes - /// 1 if the brake action group is active, 0 otherwise. - public double SetBrakes(bool active) - { - vessel.ActionGroups.SetGroup(KSPActionGroup.Brakes, active); - return (active) ? 1.0 : 0.0; - } - - /// - /// Toggle the state of the air brakes. - /// - /// 1 if air brakes are now deployed, 0 if they are now retracted or if there are no air brakes. - public double ToggleAirBrakes() - { - int numBrakes = vc.moduleAirBrake.Length; - bool nowDeployed = false; - if (numBrakes > 0) - { - nowDeployed = !vc.moduleAirBrake[0].deploy; - for (int i = 0; i < numBrakes; ++i) - { - vc.moduleAirBrake[i].deploy = nowDeployed; - } - } - - return nowDeployed ? 1.0 : 0.0; - } - - /// - /// Toggle the state of the brake action group. - /// - /// 1 if the brake action group is active, 0 otherwise. - public double ToggleBrakes() - { - vessel.ActionGroups.ToggleGroup(KSPActionGroup.Brakes); - return (vessel.ActionGroups[KSPActionGroup.Brakes]) ? 1.0 : 0.0; - } - #endregion - - /// - /// The methods in this section are focused around controlling external - /// cameras installed on the vessel. They provide an interface between - /// the MASCamera part module and CAMERA nodes in a monitor page. - /// - #region Cameras - - /// - /// Returns the name of the camera (if any) attached to the current reference docking port. - /// If the reference transform is not a docking port, but there is a primary docking port on - /// the vessel, the primary docking port's camera name is returned. - /// If there is no camera on the reference docking port, or no docking ports, an empty string is returned. - /// - /// The name of the MASCamera on the reference docking port, or an empty string. - public string ActiveDockingPortCamera() - { - if (vc.dockingCamera != null) - { - return vc.dockingCamera.cameraName; - } - - return string.Empty; - } - - /// - /// Returns the index of the camera (if any) attached to the current reference docking port. - /// If the reference transform is not a docking port, but there is a primary docking port on - /// the vessel, the primary docking port's camera index is returned. - /// If there is no camera on the reference docking port, or no docking ports, -1 is returned. - /// - /// The index between 0 and `fc.CameraCount()` - 1, or -1 if there is no camera on the current docking port, or a docking port camera is not active. - public double ActiveDockingPortCameraIndex() - { - if (vc.dockingCamera != null) - { - int idx = Array.FindIndex(vc.moduleCamera, x => x == vc.dockingCamera); - return (double)idx; - } - - return -1.0; - } - - /// - /// Adjusts the field of view setting on the selected camera. The change in `deltaFoV` is clamped - /// to the selected camera's range. A negative value reduces the FoV, which is equivalent to zooming - /// in; a positive value increases the FoV (zooms out). - /// - /// A number between 0 and `fc.CameraCount()` - 1. - /// The number of degrees to add or subtract from the current FoV. - /// The new field of view setting, or 0 if an invalid index was supplied. - public double AddFoV(double index, double deltaFoV) - { - double pan = 0.0; - - int i = (int)index; - if (i >= 0 && i < vc.moduleCamera.Length) - { - pan = vc.moduleCamera[i].AddFoV((float)deltaFoV); - } - - return pan; - } - - /// - /// Adjusts the pan setting on the selected camera. `deltaPan` is clamped to the - /// pan range of the selected camera. A negative value pans the camera left, while a - /// positive value pans right. - /// - /// A number between 0 and `fc.CameraCount()` - 1. - /// The number of degrees to increase or decrease the pan position. - /// The new pan setting, or 0 if an invalid index was supplied. - public double AddPan(double index, double deltaPan) - { - double pan = 0.0; - - int i = (int)index; - if (i >= 0 && i < vc.moduleCamera.Length) - { - pan = vc.moduleCamera[i].AddPan((float)deltaPan); - } - - return pan; - } - - /// - /// Adjusts the tilt setting on the selected camera. `deltaTilt` is clamped to the - /// tilt range of the selected camera. A negative value tilts the camera up, while a - /// positive value tils the camera down. **Verify these directions** - /// - /// A number between 0 and `fc.CameraCount()` - 1. - /// The number of degrees to increase or decrease the pan position. - /// The new tilt setting, or 0 if an invalid index was supplied. - public double AddTilt(double index, double deltaTilt) - { - double tilt = 0.0; - - int i = (int)index; - if (i >= 0 && i < vc.moduleCamera.Length) - { - tilt = vc.moduleCamera[i].AddTilt((float)deltaTilt); - } - - return tilt; - } - - /// - /// Returns 1 if the camera is capable of panning left/right. - /// - /// A number between 0 and `fc.CameraCount()` - 1. - /// 1 if the camera can pan, 0 if it cannot pan or an invalid `index` is provided. - public double GetCameraCanPan(double index) - { - int i = (int)index; - if (i >= 0 && i < vc.moduleCamera.Length) - { - return (vc.moduleCamera[i].panRange.x == vc.moduleCamera[i].panRange.y) ? 0.0 : 1.0; - } - return 0.0; - } - - /// - /// Returns 1 if the camera is capable of tilting up/down. - /// - /// A number between 0 and `fc.CameraCount()` - 1. - /// 1 if the camera can tilt, 0 if it cannot tilt or an invalid `index` is provided. - public double GetCameraCanTilt(double index) - { - int i = (int)index; - if (i >= 0 && i < vc.moduleCamera.Length) - { - return (vc.moduleCamera[i].tiltRange.x == vc.moduleCamera[i].tiltRange.y) ? 0.0 : 1.0; - } - return 0.0; - } - - /// - /// Returns 1 if the camera is capable of zooming. - /// - /// A number between 0 and `fc.CameraCount()` - 1. - /// 1 if the camera can zoom, 0 if it cannot zoom or an invalid `index` is provided. - public double GetCameraCanZoom(double index) - { - int i = (int)index; - if (i >= 0 && i < vc.moduleCamera.Length) - { - return (vc.moduleCamera[i].fovRange.x == vc.moduleCamera[i].fovRange.y) ? 0.0 : 1.0; - } - return 0.0; - } - - /// - /// Returns a count of the valid MASCamera modules found on this vessel. - /// - /// The number of valid MASCamera modules installed on this vessel. - public double CameraCount() - { - return vc.moduleCamera.Length; - } - - /// - /// Returns 1 if the selected camera is damaged, 0 otherwise. Deployable cameras may be damaged. - /// - /// A number between 0 and `fc.CameraCount()` - 1. - /// - public double GetCameraDamaged(double index) - { - int i = (int)index; - if (i >= 0 && i < vc.moduleCamera.Length) - { - return vc.moduleCamera[i].IsDamaged() ? 1.0 : 0.0; - } - - return 0.0; - } - - /// - /// Returns 1 if the selected camera is deployable, 0 otherwise. - /// - /// A number between 0 and `fc.CameraCount()` - 1. - /// - public double GetCameraDeployable(double index) - { - int i = (int)index; - if (i >= 0 && i < vc.moduleCamera.Length) - { - return vc.moduleCamera[i].GetDeployable() ? 1.0 : 0.0; - } - - return 0.0; - } - - /// - /// Returns 1 if the selected camera is deployed, 0 otherwise. - /// - /// A number between 0 and `fc.CameraCount()` - 1. - /// - public double GetCameraDeployed(double index) - { - int i = (int)index; - if (i >= 0 && i < vc.moduleCamera.Length) - { - return vc.moduleCamera[i].IsDeployed() ? 1.0 : 0.0; - } - - return 0.0; - } - - /// - /// Returns the maximum field of view supported by the selected camera. - /// - /// A number between 0 and `fc.CameraCount()` - 1. - /// The maximum field of view in degrees, or 0 for an invalid camera index. - public double GetCameraMaxFoV(double index) - { - int i = (int)index; - if (i >= 0 && i < vc.moduleCamera.Length) - { - return vc.moduleCamera[i].fovRange.y; - } - return 0.0; - } - - /// - /// Returns the maximum pan angle supported by the selected camera. - /// - /// A number between 0 and `fc.CameraCount()` - 1. - /// The maximum pan in degrees, or 0 for an invalid camera index. - public double GetCameraMaxPan(double index) - { - int i = (int)index; - if (i >= 0 && i < vc.moduleCamera.Length) - { - return vc.moduleCamera[i].GetPanRange().y; - } - return 0.0; - } - - /// - /// Returns the maximum tilt angle supported by the selected camera. - /// - /// A number between 0 and `fc.CameraCount()` - 1. - /// The maximum tilt in degrees, or 0 for an invalid camera index. - public double GetCameraMaxTilt(double index) - { - int i = (int)index; - if (i >= 0 && i < vc.moduleCamera.Length) - { - return vc.moduleCamera[i].GetTiltRange().y; - } - return 0.0; - } - - /// - /// Returns the minimum field of view supported by the selected camera. - /// - /// A number between 0 and `fc.CameraCount()` - 1. - /// The minimum field of view in degrees, or 0 for an invalid camera index. - public double GetCameraMinFoV(double index) - { - int i = (int)index; - if (i >= 0 && i < vc.moduleCamera.Length) - { - return vc.moduleCamera[i].fovRange.x; - } - return 0.0; - } - - /// - /// Returns the minimum pan angle supported by the selected camera. - /// - /// A number between 0 and `fc.CameraCount()` - 1. - /// The minimum pan in degrees, or 0 for an invalid camera index. - public double GetCameraMinPan(double index) - { - int i = (int)index; - if (i >= 0 && i < vc.moduleCamera.Length) - { - return vc.moduleCamera[i].GetPanRange().x; - } - return 0.0; - } - - /// - /// Returns the minimum tilt angle supported by the selected camera. - /// - /// A number between 0 and `fc.CameraCount()` - 1. - /// The minimum tilt in degrees, or 0 for an invalid camera index. - public double GetCameraMinTilt(double index) - { - int i = (int)index; - if (i >= 0 && i < vc.moduleCamera.Length) - { - return vc.moduleCamera[i].GetTiltRange().x; - } - return 0.0; - } - - /// - /// Returns the id number of the currently-active mode on the MASCamera selected by `index`. - /// - /// A number between 0 and `fc.CameraCount()` - 1. - /// The number of the modes (between 0 and fc.GetCameraModeCount(index)-1), or 0 if an invalid camera was selected. - public double GetCameraMode(double index) - { - int i = (int)index; - if (i >= 0 && i < vc.moduleCamera.Length) - { - return vc.moduleCamera[i].GetMode(); - } - - return 0.0; - } - - /// - /// Returns the number of modes available to the MASCamera selected by `index`. - /// - /// A number between 0 and `fc.CameraCount()` - 1. - /// The number of modes (1 or higher), or 0 if an invalid camera was selected. - public double GetCameraModeCount(double index) - { - int i = (int)index; - if (i >= 0 && i < vc.moduleCamera.Length) - { - return vc.moduleCamera[i].GetModeCount(); - } - - return 0.0; - } - - /// - /// Returns the id number of the currently-active mode on the MASCamera selected by `index`. - /// - /// A number between 0 and `fc.CameraCount()` - 1. - /// A number between 0 and `fc.GetCameraModeCount(index)` - 1. - /// The name of the selected mode, or an empty string if an invalid camera or mode was selected. - public string GetCameraModeName(double index, double mode) - { - int i = (int)index; - int whichMode = (int)mode; - if (i >= 0 && i < vc.moduleCamera.Length) - { - return vc.moduleCamera[i].GetModeName(whichMode); - } - - return string.Empty; - } - - /// - /// Returns -1 if the selected camera is retracting, +1 if it is extending, - /// or 0 for any other situation (including non-deployable cameras). - /// - /// A number between 0 and `fc.CameraCount()` - 1. - /// -1, 0, or +1. - public double GetCameraMoving(double index) - { - int i = (int)index; - if (i >= 0 && i < vc.moduleCamera.Length) - { - return vc.moduleCamera[i].IsMoving(); - } - - return 0.0; - } - - /// - /// Returns the name of the camera selected by `index`, or an empty string - /// if the index is invalid. - /// - /// A number between 0 and `fc.CameraCount()` - 1. - /// The name of the camera, or an empty string. - public string GetCameraName(double index) - { - int i = (int)index; - if (i >= 0 && i < vc.moduleCamera.Length) - { - return vc.moduleCamera[i].cameraName; - } - return string.Empty; - } - - /// - /// Retrieve the current field of view setting on the selected camera. - /// - /// A number between 0 and `fc.CameraCount()` - 1. - /// The current field of view setting, or 0 if an invalid index was supplied. - public double GetFoV(double index) - { - double fov = 0.0; - - int i = (int)index; - if (i >= 0 && i < vc.moduleCamera.Length) - { - fov = vc.moduleCamera[i].currentFov; - } - - return fov; - } - - /// - /// Retrieve the current pan setting on the selected camera. - /// - /// A number between 0 and `fc.CameraCount()` - 1. - /// The current pan setting, or 0 if an invalid index was supplied. - public double GetPan(double index) - { - double pan = 0.0; - - int i = (int)index; - if (i >= 0 && i < vc.moduleCamera.Length) - { - pan = vc.moduleCamera[i].GetPan(); - } - - return pan; - } - - /// - /// Retrieve the current tilt setting on the selected camera. - /// - /// A number between 0 and `fc.CameraCount()` - 1. - /// The current tilt setting, or 0 if an invalid index was supplied. - public double GetTilt(double index) - { - double tilt = 0.0; - - int i = (int)index; - if (i >= 0 && i < vc.moduleCamera.Length) - { - tilt = vc.moduleCamera[i].GetTilt(); - } - - return tilt; - } - - /// - /// Sets the selected camera's pan and tilt values to 0. - /// - /// A number between 0 and `fc.CameraCount()` - 1. - /// 1 if the camera is seeking home, 0 if an invalid camera was selected. - public double SeekCameraHome(double index) - { - int i = (int)index; - if (i >= 0 && i < vc.moduleCamera.Length) - { - vc.moduleCamera[i].SetTilt(0.0f); - vc.moduleCamera[i].SetPan(0.0f); - - return 1.0; - } - - return 0.0; - } - - /// - /// Extends or retracts a deployable camera. Has - /// no effect on non-deployable cameras. - /// - /// A number between 0 and `fc.CameraCount()` - 1. - /// true to deploy the camera, false to retract it. - /// 1 if the camera deploys / undeploys, 0 otherwise. - public double SetCameraDeployment(double index, bool deploy) - { - int i = (int)index; - if (i >= 0 && i < vc.moduleCamera.Length && vc.moduleCamera[i].IsDeployed() != deploy) - { - return vc.moduleCamera[i].ToggleDeployment() ? 1.0 : 0.0; - } - - return 0.0; - } - - /// - /// Returns the id number of the currently-active mode on the MASCamera selected by `index`. - /// - /// A number between 0 and `fc.CameraCount()` - 1. - /// A number between 0 and `fc.GetCameraModeCount(index)` - 1. - /// The mode that was selected, or 0 if an invalid camera was selected. - public double SetCameraMode(double index, double mode) - { - int i = (int)index; - int newMode = (int)mode; - if (i >= 0 && i < vc.moduleCamera.Length) - { - return vc.moduleCamera[i].SetMode(newMode); - } - - return 0.0; - } - - /// - /// Adjusts the field of view setting on the selected camera. `newFoV` is clamped to - /// the FoV range. - /// - /// A number between 0 and `fc.CameraCount()` - 1. - /// The new field of view of the selected camera. - /// The new field of view setting, or 0 if an invalid index was supplied. - public double SetFoV(double index, double newFoV) - { - double fov = 0.0; - - int i = (int)index; - if (i >= 0 && i < vc.moduleCamera.Length) - { - fov = vc.moduleCamera[i].SetFoV((float)newFoV); - } - - return fov; - } - - /// - /// Adjusts the pan setting on the selected camera. `newPan` is clamped to - /// the pan range. - /// - /// A number between 0 and `fc.CameraCount()` - 1. - /// The new pan position of the camera. Use 0 to send it to the camera's home position. - /// The new pan setting, or 0 if an invalid index was supplied. - public double SetPan(double index, double newPan) - { - double pan = 0.0; - - int i = (int)index; - if (i >= 0 && i < vc.moduleCamera.Length) - { - pan = vc.moduleCamera[i].SetPan((float)newPan); - } - - return pan; - } - - /// - /// Adjusts the tilt setting on the selected camera. `newTilt` is clamped - /// to the camera's tilt range. - /// - /// A number between 0 and `fc.CameraCount()` - 1. - /// The new tilt position for the camera. Use 0 to send it to the home position. - /// The new tilt setting, or 0 if an invalid index was supplied. - public double SetTilt(double index, double newTilt) - { - double tilt = 0.0; - - int i = (int)index; - if (i >= 0 && i < vc.moduleCamera.Length) - { - tilt = vc.moduleCamera[i].SetTilt((float)newTilt); - } - - return tilt; - } - - /// - /// Toggles a deployable camera (retracts it if extended, extends it if retracted). Has - /// no effect on non-deployable cameras. - /// - /// A number between 0 and `fc.CameraCount()` - 1. - /// 1 if the camera deploys / undeploys, 0 otherwise. - public double ToggleCameraDeployment(double index) - { - int i = (int)index; - if (i >= 0 && i < vc.moduleCamera.Length) - { - return vc.moduleCamera[i].ToggleDeployment() ? 1.0 : 0.0; - } - - return 0.0; - } - #endregion - - /// - /// The methods in this section provide information on cargo bays, including the - /// number of such bays and their deployment state. There are also methods to - /// open and close such bays. - /// - /// Note that, for the purpose of this section, cargo bays are defined as parts - /// that use ModuleAnimateGeneric to control access to the cargo bay. The - /// ModuleServiceModule introduced for the KSP Making History expansion is not - /// counted, since it does not provide a method that MAS can use to deploy the - /// covers. - /// - #region Cargo Bay - - /// - /// Returns a count of the number of controllable cargo bays on the vessel. - /// - /// The number of controllable cargo bays on the vessel. - public double CargoBayCount() - { - return vc.moduleCargoBay.Length; - } - - /// - /// Returns -1 if any cargo bay doors are closing, +1 if any are opening, or - /// 0 if none are moving. - /// - /// -1, 0, or +1. - public double CargoBayMoving() - { - return vc.cargoBayDirection; - } - - /// - /// Returns a number representing the average position of cargo bay doors. - /// - /// * 0 - No cargo bays, or all cargo bays are closed. - /// * 1 - All cargo bays open. - /// - /// If the cargo bays are moving, a number between 0 and 1 is returned. - /// - /// A number between 0 and 1 as described in the summary. - public double CargoBayPosition() - { - return vc.cargoBayPosition; - } - - /// - /// Open or close cargo bays. - /// Will not affect any cargo bays that are already in motion. - /// - /// 1 if at least one cargo bay is now opening or closing, 0 otherwise. - public double SetCargoBay(bool open) - { - bool anyMoved = false; - - for (int i = vc.moduleCargoBay.Length - 1; i >= 0; --i) - { - ModuleCargoBay me = vc.moduleCargoBay[i]; - PartModule deployer = me.part.Modules[me.DeployModuleIndex]; - if (deployer is ModuleAnimateGeneric) - { - ModuleAnimateGeneric mag = deployer as ModuleAnimateGeneric; - if (mag.CanMove && open != mag.Extended()) - { - mag.Toggle(); - anyMoved = true; - } - } - } - - return (anyMoved) ? 1.0 : 0.0; - } - - /// - /// Opens closed cargo bays, closes open cargo bays. Will not try to toggle any cargo bays - /// that are already in motion. - /// - /// 1 if at least one cargo bay is now moving, 0 otherwise. - public double ToggleCargoBay() - { - bool anyToggled = false; - - for (int i = vc.moduleCargoBay.Length - 1; i >= 0; --i) - { - ModuleCargoBay me = vc.moduleCargoBay[i]; - PartModule deployer = me.part.Modules[me.DeployModuleIndex]; - if (deployer is ModuleAnimateGeneric) - { - ModuleAnimateGeneric mag = deployer as ModuleAnimateGeneric; - if (mag.CanMove) - { - mag.Toggle(); - anyToggled = true; - } - } - } - - return (anyToggled) ? 1.0 : 0.0; - } - #endregion - - /// - /// The Color Changer category controls the ModuleColorChanger module on parts. This module - /// is most commonly used to toggle emissives representing portholes and windows on occupied parts, - /// as well as the charring effect on heat shields. - /// - /// There are two groups of functions in this category. Those functions that contain the - /// name `PodColorChanger` only affect the current command pod / part. This allows external - /// cockpit glows to be coordinated with interior lighting, for instance. The other group of - /// functions contain `ColorChanger` without the `Pod` prefix. Those functions are used to control - /// all ModuleColorChanger installations on the current vessel. - /// - /// The `ColorChanger` functions may return inconsistent results if modules are controlled through - /// other interfaces, such as the `PodColorChanger` functions or action groups. - /// - /// The Color Changer category only interacts with those modules that have toggleInFlight and toggleAction set - /// to true. - /// - #region Color Changer - - /// - /// Returns the total number of ModuleColorChanger installed on the vessel. - /// - /// An integer 0 or larger. - public double ColorChangerCount() - { - return vc.moduleColorChanger.Length; - } - - /// - /// Returns the module ID for the selected color changer module. - /// - /// Returns an empty string if an invalid `ccId` is provided. - /// - /// An integer between 0 and `fc.ColorChangerCount()` - 1. - /// The `moduleID` field of the selected ModuleColorChanger, or an empty string.. - public string ColorChangerId(double ccId) - { - int id = (int)ccId; - if (id >= 0 && id < vc.moduleColorChanger.Length) - { - return vc.moduleColorChanger[id].moduleID; - } - - return string.Empty; - } - - /// - /// Returns the current state of the vessel color changer modules. - /// - /// 1 if the color changers are on, 0 if they are off, or there are no color changer modules. - public double GetColorChanger() - { - if (vc.moduleColorChanger.Length > 0) - { - return (vc.moduleColorChanger[0].animState) ? 1.0 : 0.0; - } - return 0.0; - } - - /// - /// Returns the current state of the current part's color changer module. - /// - /// 1 if the color changer is on, 0 if it is off, or there is no color changer module. - public double GetPodColorChanger() - { - if (fc.colorChangerModule != null) - { - return (fc.colorChangerModule.animState) ? 1.0 : 0.0; - } - return 0.0; - } - - /// - /// Returns 1 if the pod's color changer module can currently be changed. - /// Returns 0 if it cannot, or there is no color changer module. - /// - /// - public double PodColorChangerCanChange() - { - return (fc.colorChangerModule != null && fc.colorChangerModule.CanMove) ? 1.0 : 0.0; - } - - [MASProxy(Immutable = true)] - /// - /// Returns 1 if the current IVA has a color changer module. - /// - /// 1 or 0. - public double PodColorChangerExists() - { - return (fc.colorChangerModule != null) ? 1.0 : 0.0; - } - - /// - /// Set the state of all color changer modules. - /// - /// Some color changers may not be able to update under some circumstances, - /// so this function could return 0 with a valid color changer. - /// - /// true to switch on the color changers, false to switch them off. - /// 1 if any color changers were updated. - public double SetColorChanger(bool newState) - { - bool anyUpdated = false; - - for (int i = vc.moduleColorChanger.Length - 1; i >= 0; --i) - { - if (vc.moduleColorChanger[i].CanMove && vc.moduleColorChanger[i].animState != newState) - { - vc.moduleColorChanger[i].ToggleEvent(); - anyUpdated = true; - } - } - - return anyUpdated ? 1.0 : 0.0; - } - - /// - /// Set the state of the pod color changer. - /// - /// Some color changers may not be able to update under some circumstances, - /// so this function could return 0 with a valid color changer. Query - /// `fc.PodColorChangerCanChange()` to determine in advance if the color - /// changer is able to be updated. - /// - /// true to switch on the color changer, false to switch it off. - /// 1 if the color changer has been updated, 0 if did not change, or it cannot currently change, or there is no color changer module. - public double SetPodColorChanger(bool newState) - { - if (fc.colorChangerModule != null && fc.colorChangerModule.CanMove) - { - if (fc.colorChangerModule.animState != newState) - { - fc.colorChangerModule.ToggleEvent(); - return 1.0; - } - } - - return 0.0; - } - - /// - /// Toggle the color changers on the vessel. - /// - /// If the current pod has a color changer, its setting is used to decide the toggled - /// setting. For instance, if the pod's color changer is on, then all color changers - /// will be switched off. - /// - /// If the current pod does not have a color changer, the first color changer detected - /// will decide what the new state will be. - /// - /// 1 if any color changers were updated. - public double ToggleColorChanger() - { - bool anyUpdated = false; - - bool newState; - if (fc.colorChangerModule != null) - { - newState = !fc.colorChangerModule.animState; - } - else if (vc.moduleColorChanger.Length > 0) - { - newState = !vc.moduleColorChanger[0].animState; - } - else - { - newState = false; - } - - for (int i = vc.moduleColorChanger.Length - 1; i >= 0; --i) - { - if (vc.moduleColorChanger[i].animState != newState) - { - vc.moduleColorChanger[i].ToggleEvent(); - anyUpdated = true; - } - } - - return anyUpdated ? 1.0 : 0.0; - } - - /// - /// Toggle the pod's color changer. - /// - /// 1 if the color changer is switching on, 0 if it is switching off, or there is no color changer. - public double TogglePodColorChanger() - { - if (fc.colorChangerModule != null && fc.colorChangerModule.CanMove) - { - bool newState = !fc.colorChangerModule.animState; - fc.colorChangerModule.ToggleEvent(); - return (newState) ? 1.0 : 0.0; - } - - return 0.0; - } - - #endregion - - /// - /// Functions related to CommNet connectivity are in this category, as are functions - /// related to the Kerbal Deep Space Network ground stations. - /// - #region CommNet - - /// - /// Returns the number of antennae on the vessel. - /// - /// The number of deployable antennae on the vessel. - public double AntennaCount() - { - return vc.moduleAntenna.Length; - } - - /// - /// Returns 1 if any antennae are damaged. - /// - /// 1 if all antennae are damaged; 0 otherwise. - public double AntennaDamaged() - { - return (vc.antennaDamaged) ? 1.0 : 0.0; - } - - /// - /// Returns 1 if at least one antenna may be deployed. - /// - /// 1 if any antenna is retracted and available to deploy; 0 otherwise. - public double AntennaDeployable() - { - return (vc.antennaDeployable) ? 1.0 : 0.0; - } - - /// - /// Returns -1 if an antenna is retracting, +1 if an antenna is extending, or 0 if no - /// antennas are moving. - /// - /// -1, 0, or +1. - public double AntennaMoving() - { - return vc.antennaMoving; - } - - /// - /// Returns a number representing the average position of undamaged deployable antennae. - /// - /// * 0 - No antennae, no undamaged antennae, or all undamaged antennae are retracted. - /// * 1 - All deployable antennae extended. - /// - /// If the antennae are moving, a number between 0 and 1 is returned. - /// - /// A number between 0 and 1 as described in the summary. - public double AntennaPosition() - { - float numAntenna = 0.0f; - float lerpPosition = 0.0f; - for (int i = vc.moduleAntenna.Length - 1; i >= 0; --i) - { - if (vc.moduleAntenna[i].useAnimation && vc.moduleAntenna[i].deployState != ModuleDeployablePart.DeployState.BROKEN) - { - numAntenna += 1.0f; - - lerpPosition += vc.moduleAntenna[i].GetScalar; - } - } - - if (numAntenna > 1.0f) - { - return lerpPosition / numAntenna; - } - else if (numAntenna == 1.0f) - { - return lerpPosition; - } - - return 0.0; - } - - /// - /// Returns 1 if at least one antenna is retractable. - /// - /// 1 if a antenna is deployed, and it may be retracted; 0 otherwise. - public double AntennaRetractable() - { - return (vc.antennaRetractable) ? 1.0 : 0.0; - } - - /// - /// Reports if the vessel can communicate. - /// - /// 1 if the vessel can communicate, 0 otherwise. - public double CommNetCanCommunicate() - { - return (vessel.connection.CanComm && vessel.connection.Signal != CommNet.SignalStrength.None) ? 1.0 : 0.0; - } - - /// - /// Reports if the vessel can transmit science. - /// - /// 1 if the vessel can transmit science, 0 otherwise. - public double CommNetCanScience() - { - return (vessel.connection.CanScience && vessel.connection.Signal != CommNet.SignalStrength.None) ? 1.0 : 0.0; - } - - /// - /// Reports if the vessel is connected to CommNet. - /// - /// 1 if the vessel is connected, 0 otherwise. - public double CommNetConnected() - { - return (vessel.connection.IsConnected && vessel.connection.Signal != CommNet.SignalStrength.None) ? 1.0 : 0.0; - } - - /// - /// Reports if the vessel has a connection home. - /// - /// 1 if the vessel can talk to home, 0 otherwise. - public double CommNetConnectedHome() - { - return (vessel.connection.IsConnectedHome && vessel.connection.Signal != CommNet.SignalStrength.None) ? 1.0 : 0.0; - } - - /// - /// Returns the current control state of the vessel: - /// - /// * 2: Full Kerbal control - /// * 1: Partial Kerbal control - /// * 0: No Kerbal control - /// * -1: Other control state - /// - /// A value between -1 and +2 - public double CommNetControlState() - { - switch (vessel.connection.ControlState) - { - case CommNet.VesselControlState.KerbalFull: - return 2.0; - case CommNet.VesselControlState.KerbalPartial: - return 1.0; - case CommNet.VesselControlState.KerbalNone: - return 0.0; - } - return -1.0; - } - - /// - /// Returns the name of the endpoint of the CommNet connection. - /// - /// The name of the endpoint. - public string CommNetEndpoint() - { - if (lastLink != null) - { - return lastLink.b.name; - } - - return string.Empty; - } - - /// - /// Returns the latitude on Kerbin of the current CommNet deep space relay. - /// If there is no link home, returns 0. - /// - /// Latitude of the DSN relay, or 0. - public double CommNetLatitude() - { - if (lastLink != null && lastLink.hopType == CommNet.HopType.Home) - { - int idx = Array.FindIndex(MASLoader.deepSpaceNetwork, x => x.name == lastLink.b.name); - if (idx >= 0) - { - return MASLoader.deepSpaceNetwork[idx].latitude; - } - } - - return 0.0; - } - - /// - /// Returns the longitude on Kerbin of the current CommNet deep space relay. - /// If there is no link home, returns 0. - /// - /// Longitude of the DSN relay, or 0. - public double CommNetLongitude() - { - if (lastLink != null && lastLink.hopType == CommNet.HopType.Home) - { - int idx = Array.FindIndex(MASLoader.deepSpaceNetwork, x => x.name == lastLink.b.name); - if (idx >= 0) - { - return MASLoader.deepSpaceNetwork[idx].longitude; - } - } - - return 0.0; - } - - /// - /// Returns the signal delay between the vessel and its CommNet endpoint. - /// - /// Delay in seconds. - public double CommNetSignalDelay() - { - return vessel.connection.SignalDelay; - } - - /// - /// Returns a quality value for the CommNet signal. The quality value correlates to - /// the "signal bars" display on the KSP UI. - /// - /// * 0 - No signal - /// * 1 - Red - /// * 2 - Orange - /// * 3 - Yellow - /// * 4 - Green - /// - /// A value from 0 to 4 as described in the summary. - public double CommNetSignalQuality() - { - switch (vessel.connection.Signal) - { - case CommNet.SignalStrength.None: - return 0.0; - case CommNet.SignalStrength.Red: - return 1.0; - case CommNet.SignalStrength.Orange: - return 2.0; - case CommNet.SignalStrength.Yellow: - return 3.0; - case CommNet.SignalStrength.Green: - return 4.0; - } - - return 0.0; - } - - /// - /// Returns the signal strength of the CommNet signal. - /// - /// A value between 0 (no signal) and 1 (maximum signal strength). - public double CommNetSignalStrength() - { - return vessel.connection.SignalStrength; - } - - /// - /// Returns the altitude of the selected ground station. - /// - /// A value between 0 and `fc.GroundStationCount()` - 1. - /// The altitude of the station, or 0 if an invalid station index was specified. - public double GroundStationAltitude(double dsnIndex) - { - int idx = (int)dsnIndex; - if (idx >= 0 && idx < MASLoader.deepSpaceNetwork.Length) - { - return MASLoader.deepSpaceNetwork[idx].altitude; - } - - return 0.0; - } - - /// - /// Returns the number of ground stations in the Kerbal deep space network. - /// - /// - public double GroundStationCount() - { - return MASLoader.deepSpaceNetwork.Length; - } - - /// - /// Returns the latitude of the selected ground station. - /// - /// A value between 0 and `fc.GroundStationCount()` - 1. - /// The latitude of the station, or 0 if an invalid station index was specified. - public double GroundStationLatitude(double dsnIndex) - { - int idx = (int)dsnIndex; - if (idx >= 0 && idx < MASLoader.deepSpaceNetwork.Length) - { - return MASLoader.deepSpaceNetwork[idx].latitude; - } - - return 0.0; - } - - /// - /// Returns the longitude of the selected ground station. - /// - /// A value between 0 and `fc.GroundStationCount()` - 1. - /// The longitude of the station, or 0 if an invalid station index was specified. - public double GroundStationLongitude(double dsnIndex) - { - int idx = (int)dsnIndex; - if (idx >= 0 && idx < MASLoader.deepSpaceNetwork.Length) - { - return MASLoader.deepSpaceNetwork[idx].longitude; - } - - return 0.0; - } - - /// - /// Returns the name of the selected ground station. - /// - /// A value between 0 and `fc.GroundStationCount()` - 1. - /// The name of the station, or an empty string if an invalid station index was specified. - public string GroundStationName(double dsnIndex) - { - int idx = (int)dsnIndex; - if (idx >= 0 && idx < MASLoader.deepSpaceNetwork.Length) - { - return MASLoader.deepSpaceNetwork[idx].name; - } - - return string.Empty; - } - - /// - /// Deploys antennae (when 'deployed' is true) or undeploys antennae (when 'deployed' is false). - /// - /// Whether the function should deploy the antennae or undeploy them. - /// 1 if any antenna changes, 0 if all are already in the specified state. - public double SetAntenna(bool deploy) - { - bool anyMoved = false; - - if (deploy) - { - for (int i = vc.moduleAntenna.Length - 1; i >= 0; --i) - { - if (vc.moduleAntenna[i].useAnimation && vc.moduleAntenna[i].deployState == ModuleDeployablePart.DeployState.RETRACTED) - { - vc.moduleAntenna[i].Extend(); - anyMoved = true; - } - } - } - else - { - for (int i = vc.moduleAntenna.Length - 1; i >= 0; --i) - { - if (vc.moduleAntenna[i].useAnimation && vc.moduleAntenna[i].retractable && vc.moduleAntenna[i].deployState == ModuleDeployablePart.DeployState.EXTENDED) - { - vc.moduleAntenna[i].Retract(); - anyMoved = true; - } - } - } - - return (anyMoved) ? 1.0 : 0.0; - } - - /// - /// Deploys / undeploys antennae. - /// - /// 1 if any antennas were toggled, 0 otherwise - public double ToggleAntenna() - { - bool anyMoved = false; - if (vc.antennaDeployable) - { - for (int i = vc.moduleAntenna.Length - 1; i >= 0; --i) - { - if (vc.moduleAntenna[i].useAnimation && vc.moduleAntenna[i].deployState == ModuleDeployablePart.DeployState.RETRACTED) - { - vc.moduleAntenna[i].Extend(); - anyMoved = true; - } - } - } - else if (vc.antennaRetractable) - { - for (int i = vc.moduleAntenna.Length - 1; i >= 0; --i) - { - if (vc.moduleAntenna[i].useAnimation && vc.moduleAntenna[i].retractable && vc.moduleAntenna[i].deployState == ModuleDeployablePart.DeployState.EXTENDED) - { - vc.moduleAntenna[i].Retract(); - anyMoved = true; - } - } - } - - return (anyMoved) ? 1.0 : 0.0; - } - #endregion - - /// - /// Variables and actions related to the controls (roll / pitch / yaw / translation) - /// are in this category. - /// - #region Control Input State - /// - /// Returns 1 when roll/translation controls are near neutral. - /// - /// - public double ControlNeutral() - { - float netinputs = Math.Abs(vessel.ctrlState.pitch) + Math.Abs(vessel.ctrlState.roll) + Math.Abs(vessel.ctrlState.yaw) + Math.Abs(vessel.ctrlState.X) + Math.Abs(vessel.ctrlState.Y) + Math.Abs(vessel.ctrlState.Z); - - return (netinputs > 0.01) ? 0.0 : 1.0; - } - - /// - /// Returns the current pitch control state. - /// - /// - public double StickPitch() - { - return vessel.ctrlState.pitch; - } - - /// - /// Returns the current roll control state. - /// - /// - public double StickRoll() - { - return vessel.ctrlState.roll; - } - - /// - /// Returns the current X translation state. Note that this value is the direction - /// the thrust is firing, not the direction the vessel will move. - /// - /// A value between -1 (full left) and +1 (full right). - public double StickTranslationX() - { - return vessel.ctrlState.X; - } - - /// - /// Returns the current Y translation state. Note that this value is the direction - /// the thrust is firing, not the direction the vessel will move. - /// - /// A value between -1 (full top) and +1 (full bottom). - public double StickTranslationY() - { - return vessel.ctrlState.Y; - } - - /// - /// Returns the current Z translation state. Note that this value is the direction - /// the thrust is firing, not the direction the vessel will move. - /// - /// A value between -1 (full aft) and +1 (full forward). - public double StickTranslationZ() - { - return vessel.ctrlState.Z; - } - - /// - /// Returns the current pitch trim setting. - /// - /// Trim setting, between -1 and +1 - public double StickTrimPitch() - { - return vessel.ctrlState.pitchTrim; - } - - /// - /// Returns the current roll trim setting. - /// - /// Trim setting, between -1 and +1 - public double StickTrimRoll() - { - return vessel.ctrlState.rollTrim; - } - - /// - /// Returns the current yaw trim setting. - /// - /// Trim setting, between -1 and +1 - public double StickTrimYaw() - { - return vessel.ctrlState.yawTrim; - } - - /// - /// Returns the current yaw control state. - /// - /// - public double StickYaw() - { - return vessel.ctrlState.yaw; - } - #endregion - - /// - /// This category allows the current IVA's control point to be changed, and it provides - /// information about the available control points on this part. - /// - /// Note that control points must be defined in ModuleCommand. If the current part - /// is not a command pod (no ModuleCommand), then the control point cannot be updated. - /// - #region Control Point - - /// - /// Returns the index of the current control point, or 0 if there are no control points. - /// - /// An integer between 0 and `fc.GetNumControlPoints()` - 1. - public double GetCurrentControlPoint() - { - return fc.GetCurrentControlPoint(); - } - - /// - /// Get the name for the selected control point. If there are no control points, - /// or an invalid `controlPoint` is specified, returns an empty string. - /// - /// If `controlPoint` is -1, the current control point's name is returned. - /// - /// An integer between 0 and `fc.GetNumControlPoints()` - 1, or -1. - /// The name of the control point, or an empty string. - public string GetControlPointName(double controlPoint) - { - return fc.GetControlPointName((int)controlPoint); - } - - /// - /// Returns the number of control points on the current part. If there is no ModuleCommand on - /// the part, returns 0. - /// - /// 0, or the number of available control points. - public double GetNumControlPoints() - { - return fc.GetNumControlPoints(); - } - - /// - /// Set the control point to the index selected by `newControlPoint`. If `newControlPoint` - /// is not valid, or there is no ModuleCommand, nothing happens. - /// - /// An integer between 0 and `fc.GetNumControlPoints()` - 1. - /// 1 if the control point was updated, 0 otherwise. - public float SetCurrentControlPoint(double newControlPoint) - { - return fc.SetCurrentControlPoint((int)newControlPoint); - } - - #endregion - - /// - /// The Crew category provides information about the crew aboard the vessel. - /// - /// `seatNumber` is a 0-based index to select which seat is being queried. This - /// means that a 3-seat pod has valid seat numbers 0, 1, and 2. A single-seat - /// pod has a valid seat number 0. If a seat is unoccupied, it will return results - /// just like an invalid `seatNumber`. For example, if a 3-seat pod as two seats - /// occupied - the first and the last - then `seatNumber` 0 and 2 will provide information - /// about those crew members, but 1 (the empty seat) will return results the same - /// as an invalid seat. - /// - /// One difference to be aware of between RPM and MAS: The full-vessel crew info - /// (those methods starting 'VesselCrew') provide info on crew members without - /// regards to seating arrangements. For instance, if the command pod has 2 of 3 - /// seats occupied, and a passenger pod as 1 of 4 seats occupied, VesselCrewCount - /// will return 3, and the crew info (eg, VesselCrewName) will provide values for - /// indices 0, 1, and 2. - /// - /// By the same token, if a 3-seat pod has only 2 seats occupied, then the - /// 'VesselCrew' functions will all return info for the 0 and 1 `crewIndex` - /// values, even if the first or second seat of the pod is the empty seat. - /// - #region Crew - /// - /// Returns 1 if the crew in `seatNumber` has the 'BadS' trait. Returns 0 if - /// `seatNumber` is invalid or there is no crew in that seat, or the crew does - /// not possess the 'BadS' trait. - /// - /// The index of the seat to check. Indices start at 0. - /// 1 or 0 (see summary) - public double CrewBadS(double seatNumber) - { - int seatIdx = (int)seatNumber; - - return (seatIdx >= 0 && seatIdx < fc.localCrew.Length && fc.localCrew[seatIdx] != null && fc.localCrew[seatIdx].isBadass) ? 1.0 : 0.0; - } - - /// - /// Returns 1 if the crew in `seatNumber` is conscious. Returns 0 if the crew member has passed out due to G-forces, - /// or if no one is in that seat. - /// - /// The index of the seat to check. Indices start at 0. Use -1 to check the kerbal in the current seat. - /// 1 if the crew member is conscious, 0 if the crew member is unconscious. - public double CrewConscious(double seatNumber) - { - int seatIdx = (int)seatNumber; - if (seatIdx == -1) - { - return (!fc.currentKerbalBlackedOut) ? 1.0 : 0.0; - } - else - { - return (seatIdx >= 0 && seatIdx < fc.localCrew.Length && fc.localCrew[seatIdx] != null && (!fc.localCrew[seatIdx].outDueToG)) ? 1.0 : 0.0; - } - } - - /// - /// Returns the courage rating of the selected crew member. - /// - /// The index of the seat to check. Indices start at 0. - /// A number between 0 and 1; 0 if the requested seat is invalid or empty. - public double CrewCourage(double seatNumber) - { - int seatIdx = (int)seatNumber; - if (seatIdx >= 0 && seatIdx < fc.localCrew.Length && fc.localCrew[seatIdx] != null) - { - return fc.localCrew[seatIdx].courage; - } - - return 0.0; - } - - /// - /// Ejects ... or sends ... the selected Kerbal to EVA. - /// - /// If `seatNumber` is a negative value, the Kerbal who is currently viewing the - /// control will go on EVA. If `seatNumber` is between 0 and `fc.NumberSeats()`, - /// and the seat is currently occupied, the selected Kerbal goes on EVA. - /// - /// In all cases, the camera shifts to EVA view if a Kerbal is successfully expelled - /// from the command pod. - /// - /// A negative number, or a number in the range [0, fc.NumberSeats()-1]. - /// 1 if a Kerbal is ejected, 0 if no Kerbal was ejected. - public double CrewEva(double seatNumber) - { - int requestedSeatIdx = (int)seatNumber; - Kerbal selectedKerbal = null; - // Figure out who's trying to leave. - if (seatNumber < 0.0) - { - selectedKerbal = fc.FindCurrentKerbal(); - } - else if (requestedSeatIdx < fc.localCrew.Length) - { - if (fc.localCrew[requestedSeatIdx] != null) - { - selectedKerbal = fc.localCrew[requestedSeatIdx].KerbalRef; - } - } - - // Figure out if he/she *can* leave. - if (selectedKerbal != null) - { - float acLevel = ScenarioUpgradeableFacilities.GetFacilityLevel(SpaceCenterFacility.AstronautComplex); - bool evaUnlocked = GameVariables.Instance.UnlockedEVA(acLevel); - bool evaPossible = GameVariables.Instance.EVAIsPossible(evaUnlocked, vessel); - if (evaPossible && HighLogic.CurrentGame.Parameters.Flight.CanEVA && selectedKerbal.protoCrewMember.type != ProtoCrewMember.KerbalType.Tourist) - { - // No-op - } - else - { - selectedKerbal = null; - } - } - - // Kick him/her out of the pod. - if (selectedKerbal != null) - { - FlightEVA.SpawnEVA(selectedKerbal); - CameraManager.Instance.SetCameraFlight(); - return 1.0; - } - else - { - return 0.0; - } - } - - /// - /// Returns the number of experience points for the selected crew member. - /// - /// The index of the seat to check. Indices start at 0. - /// A number 0 or higher; 0 if the requested seat is invalid or empty. - public double CrewExperience(double seatNumber) - { - int seatIdx = (int)seatNumber; - if (seatIdx >= 0 && seatIdx < fc.localCrew.Length && fc.localCrew[seatIdx] != null) - { - return fc.localCrew[seatIdx].experience; - } - - return 0.0; - } - - /// - /// Returns a number representing the gender of the selected crew member. - /// - /// The index of the seat to check. Indices start at 0. - /// 1 if the crew is male, 2 if the crew is female, 0 if the seat is empty. - public double CrewGender(double seatNumber) - { - int seatIdx = (int)seatNumber; - if (seatIdx >= 0 && seatIdx < fc.localCrew.Length && fc.localCrew[seatIdx] != null) - { - return (fc.localCrew[seatIdx].gender == ProtoCrewMember.Gender.Male) ? 1.0 : 2.0; - } - - return 0.0; - } - - /// - /// Returns the experience level of the selected crew member. - /// - /// The index of the seat to check. Indices start at 0. - /// A number 0-5; 0 if the requested seat is invalid or empty. - public double CrewLevel(double seatNumber) - { - int seatIdx = (int)seatNumber; - if (seatIdx >= 0 && seatIdx < fc.localCrew.Length && fc.localCrew[seatIdx] != null) - { - return fc.localCrew[seatIdx].experienceLevel; - } - - return 0.0; - } - - /// - /// Returns the name of the crew member seated in `seatNumber`. If - /// the number is invalid, or no Kerbal is in the seat, returns an - /// empty string. - /// - /// The index of the seat to check. Indices start at 0. - /// The crew name, or an empty string if there is no crew in the - /// given seat. - public string CrewName(double seatNumber) - { - int seatIdx = (int)seatNumber; - if (seatIdx >= 0 && seatIdx < fc.localCrew.Length && fc.localCrew[seatIdx] != null) - { - return fc.localCrew[seatIdx].name; - } - - return string.Empty; - } - - /// - /// Returns the 'PANIC' level of the selected crew member. - /// - /// The index of the seat to check. Indices start at 0. - /// A number between 0 and 1; 0 if the requested seat is invalid or empty. - public double CrewPanic(double seatNumber) - { - int seatIdx = (int)seatNumber; - var expression = fc.GetLocalKES(seatIdx); - if (expression != null) - { - return expression.panicLevel; - } - - return 0.0; - } - - /// - /// Returns the stupidity rating of the selected crew member. - /// - /// The index of the seat to check. Indices start at 0. - /// A number between 0 and 1; 0 if the requested seat is invalid or empty. - public double CrewStupidity(double seatNumber) - { - int seatIdx = (int)seatNumber; - if (seatIdx >= 0 && seatIdx < fc.localCrew.Length && fc.localCrew[seatIdx] != null) - { - return fc.localCrew[seatIdx].stupidity; - } - - return 0.0; - } - - /// - /// Returns the job title of the selected crew member. - /// - /// The index of the seat to check. Indices start at 0. - /// The name of the job title, or an empty string if `seatNumber` is invalid or - /// unoccupied. - public string CrewTitle(double seatNumber) - { - int seatIdx = (int)seatNumber; - if (seatIdx >= 0 && seatIdx < fc.localCrew.Length && fc.localCrew[seatIdx] != null) - { - return fc.localCrew[seatIdx].experienceTrait.Title; - } - - return string.Empty; - } - - /// - /// Returns the 'WHEE' level of the selected crew member. - /// - /// The index of the seat to check. Indices start at 0. - /// A number between 0 and 1; 0 if the requested seat is invalid or empty. - public double CrewWhee(double seatNumber) - { - int seatIdx = (int)seatNumber; - var expression = fc.GetLocalKES(seatIdx); - if (expression != null) - { - return expression.wheeLevel; - } - - return 0.0; - } - - /// - /// Returns the number of seats in the current IVA pod. - /// - /// The number of seats in the current IVA (1 or more). - public double NumberSeats() - { - return fc.localCrew.Length; - } - - /// - /// Indicates whether a given seat is occupied by a Kerbal. Returns 1 when `seatNumber` is - /// valid and there is a Kerbal in the given seat, and returns 0 in all other instances. - /// - /// The index of the seat to check. Indices start at 0. - /// 1 if `seatNumber` is a valid seat; 0 otherwise. - public double SeatOccupied(double seatNumber) - { - int seatIdx = (int)seatNumber; - return (seatIdx >= 0 && seatIdx < fc.localCrew.Length && fc.localCrew[seatIdx] != null) ? 1.0 : 0.0; - } - - /// - /// Returns 1 if the selected crew has the 'BadS' trait. Returns 0 if - /// `crewIndex` is invalid or the crew does - /// not possess the 'BadS' trait. - /// - /// The index of the crewmember to check. Indices start at 0. - /// 1 or 0 (see summary) - public double VesselCrewBadS(double crewIndex) - { - int index = (int)crewIndex; - - if (index >= 0 && index < vessel.GetCrewCount()) - { - return (vessel.GetVesselCrew()[index].isBadass) ? 1.0 : 0.0; - } - - return 0.0; - } - - /// - /// Capacity of all crewed locations on the vessel. - /// - /// 0 or higher. - public double VesselCrewCapacity() - { - return vessel.GetCrewCapacity(); - } - - /// - /// Total count of crew aboard the vessel. - /// - /// 0 or higher. - public double VesselCrewCount() - { - return vessel.GetCrewCount(); - } - - /// - /// Returns the courage rating of the selected crew member. - /// - /// The index of the crewmember to check. Indices start at 0, and go to `fc.VesselCrewCount() - 1`. - /// A number between 0 and 1; 0 if the requested seat is invalid or empty. - public double VesselCrewCourage(double crewIndex) - { - int index = (int)crewIndex; - if (index >= 0 && index < vessel.GetCrewCount()) - { - return vessel.GetVesselCrew()[index].courage; - } - - return 0.0; - } - - /// - /// Returns the number of experience points for the selected crew member. - /// - /// The index of the crewmember to check. Indices start at 0, and go to `fc.VesselCrewCount() - 1`. - /// A number 0 or higher; 0 if the requested seat is invalid. - public double VesselCrewExperience(double crewIndex) - { - int index = (int)crewIndex; - - if (index >= 0 && index < vessel.GetCrewCount()) - { - return vessel.GetVesselCrew()[index].experience; - } - - return 0.0; - } - - /// - /// Returns a number representing the gender of the selected crew member. - /// - /// The index of the crewmember to check. Indices start at 0, and go to `fc.VesselCrewCount() - 1`. - /// 1 if the crew is male, 2 if the crew is female, 0 if the index is invalid. - public double VesselCrewGender(double crewIndex) - { - int index = (int)crewIndex; - - if (index >= 0 && index < vessel.GetCrewCount()) - { - return (vessel.GetVesselCrew()[index].gender == ProtoCrewMember.Gender.Male) ? 1.0 : 2.0; - } - - return 0.0; - } - - /// - /// Returns the experience level of the selected crew member. - /// - /// The index of the crewmember to check. Indices start at 0, and go to `fc.VesselCrewCount() - 1`. - /// A number 0-5; 0 if the requested index is invalid. - public double VesselCrewLevel(double crewIndex) - { - int index = (int)crewIndex; - - if (index >= 0 && index < vessel.GetCrewCount()) - { - return vessel.GetVesselCrew()[index].experienceLevel; - } - - return 0.0; - } - - /// - /// Returns the name of the crew member seated in `seatNumber`. If - /// the number is invalid, or no Kerbal is in the seat, returns an - /// empty string. - /// - /// The index of the crewmember to check. Indices start at 0, and go to `fc.VesselCrewCount() - 1`. - /// The crew name, or an empty string if index is invalid. - public string VesselCrewName(double crewIndex) - { - int index = (int)crewIndex; - - if (index >= 0 && index < vessel.GetCrewCount()) - { - return vessel.GetVesselCrew()[index].name; - } - - return string.Empty; - } - - /// - /// Returns the 'PANIC' level of the selected crew member. - /// - /// The index of the crewmember to check. Indices start at 0, and go to `fc.VesselCrewCount() - 1`. - /// A number between 0 and 1; 0 if the requested index is invalid. - public double VesselCrewPanic(double crewIndex) - { - int index = (int)crewIndex; - - if (index >= 0 && index < vessel.GetCrewCount()) - { - return GetVesselCrewExpression(index).panicLevel; - } - - return 0.0; - } - - /// - /// Returns the stupidity rating of the selected crew member. - /// - /// The index of the crewmember to check. Indices start at 0, and go to `fc.VesselCrewCount() - 1`. - /// A number between 0 and 1; 0 if the requested index is invalid. - public double VesselCrewStupidity(double crewIndex) - { - int index = (int)crewIndex; - - if (index >= 0 && index < vessel.GetCrewCount()) - { - return vessel.GetVesselCrew()[index].stupidity; - } - - return 0.0; - } - - /// - /// Returns the job title of the selected crew member. - /// - /// The index of the crewmember to check. Indices start at 0, and go to `fc.VesselCrewCount() - 1`. - /// The name of the job title, or an empty string if `crewIndex` is invalid. - public string VesselCrewTitle(double crewIndex) - { - int index = (int)crewIndex; - - if (index >= 0 && index < vessel.GetCrewCount()) - { - return vessel.GetVesselCrew()[index].experienceTrait.Title; - } - - return string.Empty; - } - - /// - /// Returns the 'WHEE' level of the selected crew member. - /// - /// The index of the crewmember to check. Indices start at 0, and go to `fc.VesselCrewCount() - 1`. - /// A number between 0 and 1; 0 if the requested index is invalid. - public double VesselCrewWhee(double crewIndex) - { - int index = (int)crewIndex; - - if (index >= 0 && index < vessel.GetCrewCount()) - { - - return GetVesselCrewExpression(index).wheeLevel; - } - - return 0.0; - } - - #endregion - - /// - /// Docking control and status are in the Docking category. - /// - /// Many of these methods use the concept of "Primary Docking Port". - /// The primary docking port is defined as the first or only docking - /// port found on the vessel. These features are primarily centered - /// around CTV / OTV type spacecraft where there is a single dock, - /// not space stations or large craft with many docks. - /// - #region Docking - /// - /// Return 1 if the dock is attached to something (this includes parts that - /// are not compatible to the docking port, such as boost protective covers or - /// launch escape systems that are attached in the VAB). - /// - /// To determine if the dock is connected to a compatible docking port, use fc.Docked(). - /// - /// - public double DockConnected() - { - return (vc.dockingNodeState == MASVesselComputer.DockingNodeState.DOCKED || vc.dockingNodeState == MASVesselComputer.DockingNodeState.PREATTACHED) ? 1.0 : 0.0; - } - - /// - /// Returns the name of the object docked to the vessel. Returns an empty string if fc.Docked() returns 0. - /// - /// The name of the docked vessel. - public string DockedObjectName() - { - if (vc.dockingNodeState == MASVesselComputer.DockingNodeState.DOCKED) - { - if (vc.dockingNode.vesselInfo != null) - { - string l10n = string.Empty; - if (KSP.Localization.Localizer.TryGetStringByTag(vc.dockingNode.vesselInfo.name, out l10n)) - { - return l10n; - } - else - { - return vc.dockingNode.vesselInfo.name; - } - } - } - return string.Empty; - } - - /// - /// Return 1 if the dock is attached to a compatible dock; return 0 otherwise. - /// - /// Note that this function will return 0 if the compatible dock was connected in the - /// VAB. fc.Docked() only detects docking events that take place during Flight. - /// - /// - public double Docked() - { - return (vc.dockingNodeState == MASVesselComputer.DockingNodeState.DOCKED) ? 1.0 : 0.0; - } - - /// - /// Return 1 if the dock is ready; return 0 otherwise. - /// - /// - public double DockReady() - { - return (vc.dockingNodeState == MASVesselComputer.DockingNodeState.READY) ? 1.0 : 0.0; - } - - /// - /// Returns 1 if the primary docking port on the vessel is the current reference transform. - /// - /// Returns 0 if the primary docking port is not the reference transform, or if there is no docking port, - /// or if a docking port other than the primary port is the reference transform. - /// - /// - public double GetDockIsReference() - { - return (vc.dockingNode != null && vc.dockingNode.part == vessel.GetReferenceTransformPart()) ? 1.0 : 0.0; - } - - /// - /// Returns 1 if the primary grapple on the vessel is the current reference transform. - /// - /// Returns 0 if the grapple is not the reference transform, or if there is no grapple, - /// or if a grapple other than the primary grapple is the reference transform. - /// - /// - public double GetGrappleIsReference() - { - return (vc.clawNode != null && vc.clawNode.part == vessel.GetReferenceTransformPart()) ? 1.0 : 0.0; - } - - /// - /// Returns 1 if the current IVA pod is the reference transform. Returns 0 otherwise. - /// - /// - public double GetPodIsReference() - { - return (fc.part == vessel.GetReferenceTransformPart()) ? 1.0 : 0.0; - } - - /// - /// Returns the index of the docking port currently tracked by the vessel. - /// - /// If no port is being tracked, returns -1. - /// - /// For a valid docking port, a number between 0 and 'fc.TargetDockCount()' - 1. Otherwise, -1. - public double GetTargetDockIndex() - { - if (vc.targetType == MASVesselComputer.TargetType.DockingPort) - { - if (vc.targetDockingPorts.Length == 1) - { - // The only docking port. - return 0.0; - } - - ModuleDockingNode activeNode = vc.activeTarget as ModuleDockingNode; - - return Array.FindIndex(vc.targetDockingPorts, x => x == activeNode); - } - - return -1.0; - } - - /// - /// Indicates whether a primary docking port was found on this vessel. - /// - /// 1 if a node was found, 0 otherwise. - public double HasDock() - { - return (vc.dockingNode != null) ? 1.0 : 0.0; - } - - /// - /// Set the primary docking port to be the reference transform. - /// - /// 1 if the reference was changed, 0 otherwise. - public double SetDockToReference() - { - if (vc.dockingNode != null) - { - vessel.SetReferenceTransform(vc.dockingNode.part); - return 1.0; - } - else - { - return 0.0; - } - } - /// - /// Set the next docking port to be the reference transform. - /// - /// 1 if the reference was changed, 0 otherwise. - public double SetNextDockToReference() - { - /*if (vc.dockingNode != null) - { - vessel.SetReferenceTransform(vc.dockingNode.part); - return 1.0; - } - else - { - return 0.0; - }*/ - - if (vc.ownDockingPorts.Length == 0) - { - return 0.0; - } - else if (fc.part == vessel.GetReferenceTransformPart()) - { - vessel.SetReferenceTransform(vc.ownDockingPorts[0].part); - return 1.0; - } - else if (vc.referenceTransformType == MASVesselComputer.ReferenceType.DockingPort) - { - if (vc.ownDockingPorts.Length == 1) - { - // We're already referencing the only docking port. - return 1.0; - } - - ModuleDockingNode activeOwnNode = vc.dockingNode as ModuleDockingNode; - int currentOwnIndex = Array.FindIndex(vc.ownDockingPorts, x => x == activeOwnNode); - if (currentOwnIndex == -1) - { - vessel.SetReferenceTransform(vc.ownDockingPorts[0].part); - } - else - { - vessel.SetReferenceTransform(vc.ownDockingPorts[(currentOwnIndex + 1) % vc.ownDockingPorts.Length].part); - } - return 1.0; - } - - return 0.0; - } - - /// - /// Set the primary grapple to be the reference transform. - /// - /// 1 if the reference was changed, 0 otherwise. - public double SetGrappleToReference() - { - if (vc.clawNode != null) - { - vessel.SetReferenceTransform(vc.clawNode.part); - return 1.0; - } - else - { - return 0.0; - } - } - - /// - /// Set the current IVA pod to be the reference transform. - /// - /// 1 if the reference was changed. - public double SetPodToReference() - { - vessel.SetReferenceTransform(fc.part); - return 1.0; - } - - /// - /// Targets the docking port on the target vessel identified by 'idx'. - /// - /// Note that KSP only allows targeting docking ports within a certain range (typically - /// 200 meters). - /// - /// A value between 0 and `fc.TargetDockCount()` - 1. - /// 1 the dock could be targeted, 0 otherwise. - public double SetTargetDock(double idx) - { - int index = (int)idx; - if (index < 0 || index >= vc.targetDockingPorts.Length) - { - return 0.0; - } - - FlightGlobals.fetch.SetVesselTarget(vc.targetDockingPorts[index]); - - return 1.0; - } - - /// - /// Returns the number of available docking ports found on the target vessel when the following - /// conditions are met: - /// - /// 1) There are docking ports on the target vessel compatible with the designated - /// docking port on the current vessel. The designated docking port is either the - /// only docking port on the current vessel, or it is one of the docking ports selected - /// arbitrarily. - /// - /// 2) There are no docking ports on the current vessel. In this case, all docking - /// ports on the target vessel are counted. - /// - /// Note that if the target is unloaded, this method will return 0. If the target is - /// not a vessel, it also returns 0. - /// - /// This function is identical to `fc.TargetAvailableDockingPorts()`. - /// - /// Number of available compatible docking ports, or total available docking ports, or 0. - public double TargetDockCount() - { - return vc.targetDockingPorts.Length; - } - - /// - /// Returns the angle in degrees that the vessel must roll in order to align with the - /// currently-targeted docking port. - /// - /// If no dock is targeted, there are no alignment requirements, or a compatible docking port - /// is not the reference part, this function returns 0. - /// - /// The roll required to align docking ports, or 0. - public double TargetDockError() - { - if (vc.dockingNode != null && vc.dockingNode.part.transform == vc.referenceTransform && vc.targetType == MASVesselComputer.TargetType.DockingPort && vc.targetDockingTransform != null) - { - ModuleDockingNode activeNode = vc.activeTarget as ModuleDockingNode; - // TODO: If either is false, does that disable snapRotation? Or do both need to be false? - if (vc.dockingNode.snapRotation == false && activeNode.snapRotation == false) - { - return 0.0; - } - - float snapOffset; - if (vc.dockingNode.snapRotation) - { - if (activeNode.snapRotation) - { - snapOffset = Mathf.Min(activeNode.snapOffset, vc.dockingNode.snapOffset); - } - else - { - snapOffset = vc.dockingNode.snapOffset; - } - } - else - { - snapOffset = activeNode.snapOffset; - } - - Vector3 projectedVector = Vector3.ProjectOnPlane(vc.targetDockingTransform.up, vc.referenceTransform.up); - projectedVector.Normalize(); - - float dotLateral = Vector3.Dot(projectedVector, vc.referenceTransform.right); - float dotLongitudinal = Vector3.Dot(projectedVector, vc.referenceTransform.forward); - - // Taking arc tangent of x/y lets us treat the front of the vessel - // as the 0 degree location. - float roll = Mathf.Atan2(dotLateral, dotLongitudinal) * Mathf.Rad2Deg; - // Normalize it - if (roll < 0.0f) - { - roll += 360.0f; - } - - float rollError = roll % snapOffset; - if (rollError > (snapOffset * 0.5f)) - { - rollError -= snapOffset; - } - - return rollError; - } - - return 0.0; - } - - /// - /// Returns the name of the target dock selected by 'idx'. If the Docking Port Alignment - /// Indicator mod is installed, the name given to the dock from that mod is returned. Otherwise, - /// only the part's name is returned. - /// - /// The name of the selected dock, or an empty string. - public string TargetDockName(double idx) - { - int index = (int)idx; - if (index >= 0 && idx < vc.targetDockingPorts.Length) - { - return fc.GetDockingPortName(vc.targetDockingPorts[index].part); - } - - return string.Empty; - } - - /// - /// Targets the next valid docking port on the target vessel. - /// - /// Note that KSP only allows targeting docking ports within a certain range (typically - /// 200 meters). - /// - /// 1 if a dock could be targeted, 0 otherwise. - public double TargetNextDock() - { - if (vc.targetDockingPorts.Length == 0) - { - return 0.0; - } - else if (vc.targetType == MASVesselComputer.TargetType.Vessel) - { - FlightGlobals.fetch.SetVesselTarget(vc.targetDockingPorts[0]); - return 1.0; - } - else if (vc.targetType == MASVesselComputer.TargetType.DockingPort) - { - if (vc.targetDockingPorts.Length == 1) - { - // We're already targeting the only docking port. - return 1.0; - } - - ModuleDockingNode activeNode = vc.activeTarget as ModuleDockingNode; - int currentIndex = Array.FindIndex(vc.targetDockingPorts, x => x == activeNode); - if (currentIndex == -1) - { - FlightGlobals.fetch.SetVesselTarget(vc.targetDockingPorts[0]); - } - else - { - FlightGlobals.fetch.SetVesselTarget(vc.targetDockingPorts[(currentIndex + 1) % vc.targetDockingPorts.Length]); - } - return 1.0; - } - return 0.0; - } - - /// - /// Undock / detach (if pre-attached) the active docking node. - /// - /// 1 if the active dock undocked from something, 0 otherwise. - public double Undock() - { - if (vc.dockingNode != null) - { - if (vc.dockingNodeState == MASVesselComputer.DockingNodeState.DOCKED) - { - vc.dockingNode.Undock(); - return 1.0; - } - else if (vc.dockingNodeState == MASVesselComputer.DockingNodeState.PREATTACHED) - { - vc.dockingNode.Decouple(); - return 1.0; - } - } - - return 0.0; - } - #endregion - - /// - /// Engine status and control methods are in the Engine category. - /// - #region Engine - - /// - /// Returns the current fuel flow in grams/second - /// - /// - public double CurrentFuelFlow() - { - return vc.currentEngineFuelFlow; - } - - /// - /// Returns the average deflection of active, unlocked gimbals, from 0 (no deflection) to 1 (max deflection). - /// - /// The direction of the deflection is ignored, but the value accounts for assymetrical gimbal configurations, - /// eg, if X+ is 5.0, and X- is -3.0, the deflection percentage accounts for this difference. - /// - /// - public double CurrentGimbalDeflection() - { - return vc.gimbalDeflection; - } - - /// - /// Return the current specific impulse in seconds. - /// - /// The current Isp. - public double CurrentIsp() - { - return vc.currentIsp; - } - - /// - /// Returns the current thrust output relative to the - /// current stage's max rated thrust, from 0.0 to 1.0. - /// - /// Thrust output, ranging from 0 to 1. - public double CurrentRatedThrust() - { - if (vc.currentThrust > 0.0f) - { - return vc.currentThrust / vc.maxRatedThrust; - } - else - { - return 0.0f; - } - } - - /// - /// Returns the current thrust output, from 0.0 to 1.0. - /// - /// Thrust output, ranging from 0 to 1. - public double CurrentThrust(bool useThrottleLimits) - { - if (vc.currentThrust > 0.0f) - { - return vc.currentThrust / ((useThrottleLimits) ? vc.currentLimitedMaxThrust : vc.currentMaxThrust); - } - else - { - return 0.0f; - } - } - - /// - /// Returns the current thrust in kiloNewtons - /// - /// - public double CurrentThrustkN() - { - return vc.currentThrust; - } - - /// - /// Returns the current thrust-to-weight ratio. - /// - /// - public double CurrentTWR() - { - if (vc.currentThrust > 0.0f) - { - return vc.currentThrust / (vessel.totalMass * vc.surfaceAccelerationFromGravity); - } - else - { - return 0.0; - } - } - - /// - /// Returns the total delta-V remaining for the vessel based on its current - /// altitude. - /// - /// If Kerbal Engineer or MechJeb is installed, those mods are used for the computation. - /// - /// MechJeb, Kerbal Engineer Redux - /// Remaining delta-V in m/s. - public double DeltaV() - { - if (MASIKerbalEngineer.keFound) - { - return keProxy.DeltaV(); - } - else if (mjProxy.mjAvailable) - { - return mjProxy.DeltaV(); - } - else - { - VesselDeltaV vdV = vessel.VesselDeltaV; - if (vdV.IsReady) - { - return vdV.TotalDeltaVActual; - } - else - { - return 0.0; - } - } - } - - /// - /// Returns an estimate of the delta-V remaining for the current stage. This computation uses - /// the current ISP. - /// - /// Remaining delta-V for this stage in m/s. - public double DeltaVStage() - { - // mass in tonnes. - double stagePropellantMass = vc.enginePropellant.currentStage * vc.enginePropellant.density; - - if (stagePropellantMass > 0.0) - { - return vc.currentIsp * PhysicsGlobals.GravitationalAcceleration * Math.Log(vessel.totalMass / (vessel.totalMass - stagePropellantMass)); - } - - return 0.0; - } - - /// - /// Returns an estimate of the maximum delta-V for the current stage. This computation uses - /// the current ISP. - /// - /// Maximum delta-V for this stage in m/s. - public double DeltaVStageMax() - { - // mass in tonnes. - double stagePropellantMass = vc.enginePropellant.currentStage * vc.enginePropellant.density; - - if (stagePropellantMass > 0.0) - { - double startingMass = vessel.totalMass + (vc.enginePropellant.maxStage - vc.enginePropellant.currentStage) * vc.enginePropellant.density; - - return vc.currentIsp * PhysicsGlobals.GravitationalAcceleration * Math.Log(startingMass / (vessel.totalMass - stagePropellantMass)); - } - - return 0.0; - } - - /// - /// Returns a count of the total number of engines that are active. - /// - /// - public double EngineCountActive() - { - return vc.activeEngineCount; - } - - /// - /// Returns a count of the total number of engines tracked. This - /// count includes engines that have not staged. - /// - /// - public double EngineCountTotal() - { - return vc.moduleEngines.Length; - } - - /// - /// Returns 1 if any active engines are in a flameout condition. - /// - /// - public double EngineFlameout() - { - return (vc.anyEnginesFlameout) ? 1.0 : 0.0; - } - - /// - /// Returns 1 if at least one engine is enabled. - /// - /// - public double GetEnginesEnabled() - { - return (vc.anyEnginesEnabled) ? 1.0 : 0.0; - } - - /// - /// Returns 1 if any currently-active engines have gimbals. Returns 0 if no active engine has a gimbal. - /// - /// - public double GetActiveEnginesGimbal() - { - return (vc.activeEnginesGimbal) ? 1.0 : 0.0; - } - - /// - /// Returns the normalized (-1 to +1) gimbal deflection of unlocked gimbals along their local - /// X axis. - /// - /// Note that if two engines are deflected in opposite directions during a roll - /// maneuver, this value could be zero. - /// - /// A value in the range [-1, 1]. - public double GetGimbalDeflectionX() - { - return vc.gimbalAxisDeflection.x; - } - - /// - /// Returns the normalized (-1 to +1) gimbal deflection of unlocked gimbals along their local - /// Y axis. - /// - /// Note that if two engines are deflected in opposite directions during a roll - /// maneuver, this value could be zero. - /// - /// A value in the range [-1, 1]. - public double GetGimbalDeflectionY() - { - return vc.gimbalAxisDeflection.y; - } - - /// - /// Returns the currently-configured limit of active gimbals, as set in the right-click part menus. - /// This value ranges between 0 (no gimbal) and 1 (100% gimbal). - /// - /// - public double GetGimbalLimit() - { - return vc.gimbalLimit; - } - - /// - /// Returns 1 if any active, unlocked gimbals are configured to provide pitch control. - /// Returns 0 otherwise. - /// - /// 1 if active gimbals support pitch, 0 otherwise. - public double GetGimbalPitch() - { - return (vc.anyGimbalsPitch) ? 1.0 : 0.0; - } - - /// - /// Returns 1 if any active, unlocked gimbals are configured to provide roll control. - /// Returns 0 otherwise. - /// - /// 1 if active gimbals support roll, 0 otherwise. - public double GetGimbalRoll() - { - return (vc.anyGimbalsRoll) ? 1.0 : 0.0; - } - - /// - /// Returns 1 if any active, unlocked gimbals are configured to provide yaw control. - /// Returns 0 otherwise. - /// - /// 1 if active gimbals support yaw, 0 otherwise. - public double GetGimbalYaw() - { - return (vc.anyGimbalsYaw) ? 1.0 : 0.0; - } - - /// - /// Returns 1 if any gimbals are currently active. - /// - /// - public double GetGimbalsActive() - { - return (vc.anyGimbalsActive) ? 1.0 : 0.0; - } - - /// - /// Returns 1 if at least one active gimbal is locked. - /// - /// - public double GetGimbalsLocked() - { - return (vc.anyGimbalsLocked) ? 1.0 : 0.0; - } - - /// - /// Returns 1 if any multi-mode engine is in secondary mode, 0 if no engines are, - /// or there are no multi-mode engines. - /// - /// - public double GetMultiModeEngineMode() - { - for (int i = vc.multiModeEngines.Length - 1; i >= 0; --i) - { - if (vc.multiModeEngines[i].runningPrimary == false) - { - return 1.0; - } - } - - return 0.0; - } - - /// - /// Returns the current main throttle setting, from 0.0 to 1.0. - /// - /// - public double GetThrottle() - { - return vessel.ctrlState.mainThrottle; - } - - /// - /// Returns the average of the throttle limit for the active engines, - /// ranging from 0 (no thrust) to 1 (maximum thrust). - /// - /// - public double GetThrottleLimit() - { - return vc.throttleLimit; - } - - /// - /// Returns the maximum fuel flow in grams/second - /// - /// - public double MaxFuelFlow() - { - return vc.maxEngineFuelFlow; - } - - /// - /// Returns the maximum specific impulse in seconds. - /// - /// - public double MaxIsp() - { - return vc.maxIsp; - } - - /// - /// Returns the maximum rated thrust in kN for the active engines. - /// - /// Maximum thrust in kN - public double MaxRatedThrustkN() - { - return vc.maxRatedThrust; - } - - /// - /// Returns the maximum thrust in kN for the current altitude. - /// - /// Apply throttle limits? - /// Maximum thrust in kN - public double MaxThrustkN(bool useThrottleLimits) - { - return (useThrottleLimits) ? vc.currentLimitedMaxThrust : vc.currentMaxThrust; - } - - /// - /// Returns the maximum thrust-to-weight ratio. - /// - /// Apply throttle limits? - /// Thrust-to-weight ratio, between 0 and 1. - public double MaxTWR(bool useThrottleLimits) - { - double thrust = ((useThrottleLimits) ? vc.currentLimitedMaxThrust : vc.currentMaxThrust); - if (thrust > 0.0) - { - return ((useThrottleLimits) ? vc.currentLimitedMaxThrust : vc.currentMaxThrust) / (vessel.totalMass * vc.surfaceAccelerationFromGravity); - } - else - { - return 0.0; - } - } - - /// - /// Turns on/off engines for the current stage. - /// - /// 1 if engines are now enabled, 0 if they are disabled. - public double SetEnginesEnabled(bool enable) - { - return (vc.SetEnginesEnabled(enable)) ? 1.0 : 0.0; - } - - /// - /// Change the gimbal limit for active gimbals. Values less than 0 or greater than 1 are - /// clamped to that range. - /// - /// The new gimbal limit, between 0 and 1. - /// 1 if any gimbals were updated, 0 otherwise. - public double SetGimbalLimit(double newLimit) - { - float limit = Mathf.Clamp01((float)newLimit) * 100.0f; - bool updated = false; - - for (int i = vc.moduleGimbals.Length - 1; i >= 0; --i) - { - if (vc.moduleGimbals[i].gimbalActive) - { - vc.moduleGimbals[i].gimbalLimiter = limit; - } - } - - return (updated) ? 1.0 : 0.0; - } - - /// - /// Locks or unlocks engine gimbals for the current stage. - /// - /// 1 if any gimbals changed, 0 if none changed. - public double SetGimbalLock(bool locked) - { - bool changed = false; - for (int i = vc.moduleGimbals.Length - 1; i >= 0; --i) - { - if (vc.moduleGimbals[i].gimbalActive && vc.moduleGimbals[i].gimbalLock != locked) - { - changed = true; - vc.moduleGimbals[i].gimbalLock = locked; - } - } - - return (changed) ? 1.0 : 0.0; - } - - /// - /// Controls whether active, unlocked gimbals provide pitch control. - /// - /// If true, enables gimbal pitch control. If false, disables gimbal pitch control. - /// 1 if any gimbal pitch control changed, 0 otherwise. - public double SetGimbalPitch(bool enable) - { - bool changed = false; - for (int i = vc.moduleGimbals.Length - 1; i >= 0; --i) - { - if (vc.moduleGimbals[i].gimbalActive && !vc.moduleGimbals[i].gimbalLock && vc.moduleGimbals[i].enablePitch != enable) - { - changed = true; - vc.moduleGimbals[i].enablePitch = enable; - } - } - - return (changed) ? 1.0 : 0.0; - } - - /// - /// Controls whether active, unlocked gimbals provide roll control. - /// - /// If true, enables gimbal roll control. If false, disables gimbal roll control. - /// 1 if any gimbal roll control changed, 0 otherwise. - public double SetGimbalRoll(bool enable) - { - bool changed = false; - for (int i = vc.moduleGimbals.Length - 1; i >= 0; --i) - { - if (vc.moduleGimbals[i].gimbalActive && !vc.moduleGimbals[i].gimbalLock && vc.moduleGimbals[i].enableRoll != enable) - { - changed = true; - vc.moduleGimbals[i].enableRoll = enable; - } - } - - return (changed) ? 1.0 : 0.0; - } - - /// - /// Controls whether active, unlocked gimbals provide yaw control. - /// - /// If true, enables gimbal yaw control. If false, disables gimbal yaw control. - /// 1 if any gimbal yaw control changed, 0 otherwise. - public double SetGimbalYaw(bool enable) - { - bool changed = false; - for (int i = vc.moduleGimbals.Length - 1; i >= 0; --i) - { - if (vc.moduleGimbals[i].gimbalActive && !vc.moduleGimbals[i].gimbalLock && vc.moduleGimbals[i].enableYaw != enable) - { - changed = true; - vc.moduleGimbals[i].enableYaw = enable; - } - } - - return (changed) ? 1.0 : 0.0; - } - - /// - /// Selects the primary or secondary mode for multi-mode engines. - /// - /// Selects the primary mode when true, the secondary mode when false. - /// 1 if any engines were toggled, 0 if no multi-mode engines are installed. - public double SetMultiModeEngineMode(bool runPrimary) - { - bool anyChanged = false; - for (int i = vc.multiModeEngines.Length - 1; i >= 0; --i) - { - if (vc.multiModeEngines[i].runningPrimary != runPrimary) - { - vc.multiModeEngines[i].ToggleMode(); - anyChanged = true; - } - } - - if (anyChanged) - { - vc.InvalidateModules(); - } - - return (anyChanged) ? 1.0 : 0.0; - } - - /// - /// Set the throttle. May be set to any value between 0 and 1. Values outside - /// that range are clamped to [0, 1]. - /// - /// Throttle setting, between 0 and 1. - /// The new throttle setting. - public double SetThrottle(double throttlePercentage) - { - float throttle = Mathf.Clamp01((float)throttlePercentage); - try - { - FlightInputHandler.state.mainThrottle = throttle; - } - catch (Exception e) - { - // RPM had a try-catch. Why? - Utility.LogError(this, "SetThrottle({0:0.00}) threw {1}", throttle, e); - } - return throttle; - } - - /// - /// Set the throttle limit. May be set to any value between 0 and 1. Values outside - /// that range are clamped to [0, 1]. - /// - /// - /// - public double SetThrottleLimit(double newLimit) - { - float limit = Mathf.Clamp01((float)newLimit) * 100.0f; - bool updated = vc.SetThrottleLimit(limit); - - return (updated) ? 1.0 : 0.0; - } - - /// - /// Returns the total delta-V remaining for the vessel based on its current - /// altitude. - /// - /// This version uses only the stock KSP delta-V computations. - /// - /// Remaining delta-V in m/s. - public double StockDeltaV() - { - VesselDeltaV vdV = vessel.VesselDeltaV; - if (vdV.IsReady) - { - return vdV.TotalDeltaVActual; - } - else - { - return 0.0; - } - } - - /// - /// Returns an estimate of the delta-V remaining for the current stage. This computation uses - /// the current ISP. - /// - /// This version uses only the stock KSP delta-V computations. - /// - /// Remaining delta-V for this stage in m/s. - public double StockDeltaVStage() - { - VesselDeltaV vdV = vessel.VesselDeltaV; - if (vdV.IsReady && vdV.currentStageActivated) - { - DeltaVStageInfo stageInfo = vdV.OperatingStageInfo[0]; - return stageInfo.deltaVActual; - } - else - { - return 0.0; - } - } - - /// - /// Turns on/off engines for the current stage - /// - /// 1 if engines are now enabled, 0 if they are disabled. - public double ToggleEnginesEnabled() - { - return (vc.SetEnginesEnabled(!vc.anyEnginesEnabled)) ? 1.0 : 0.0; - } - - /// - /// Toggles gimbal lock on/off for the current stage. - /// - /// 1 if active gimbals are now locked, 0 if they are unlocked. - public double ToggleGimbalLock() - { - bool newState = !vc.anyGimbalsLocked; - for (int i = vc.moduleGimbals.Length - 1; i >= 0; --i) - { - if (vc.moduleGimbals[i].gimbalActive) - { - vc.moduleGimbals[i].gimbalLock = newState; - } - } - - return (newState) ? 1.0 : 0.0; - } - - /// - /// Toggles pitch control for active, unlocked gimbals. - /// - /// 1 if any gimbal pitch control changed, 0 otherwise. - public double ToggleGimbalPitch() - { - bool changed = false; - bool enable = !vc.anyGimbalsPitch; - for (int i = vc.moduleGimbals.Length - 1; i >= 0; --i) - { - if (vc.moduleGimbals[i].gimbalActive && !vc.moduleGimbals[i].gimbalLock && vc.moduleGimbals[i].enablePitch != enable) - { - changed = true; - vc.moduleGimbals[i].enablePitch = enable; - } - } - - return (changed) ? 1.0 : 0.0; - } - - /// - /// Toggles roll control for active, unlocked gimbals. - /// - /// 1 if any gimbal roll control changed, 0 otherwise. - public double ToggleGimbalRoll() - { - bool changed = false; - bool enable = !vc.anyGimbalsRoll; - for (int i = vc.moduleGimbals.Length - 1; i >= 0; --i) - { - if (vc.moduleGimbals[i].gimbalActive && !vc.moduleGimbals[i].gimbalLock && vc.moduleGimbals[i].enableRoll != enable) - { - changed = true; - vc.moduleGimbals[i].enableRoll = enable; - } - } - - return (changed) ? 1.0 : 0.0; - } - - /// - /// Toggles yaw control for active, unlocked gimbals. - /// - /// 1 if any gimbal yaw control changed, 0 otherwise. - public double ToggleGimbalYaw() - { - bool changed = false; - bool enable = !vc.anyGimbalsYaw; - for (int i = vc.moduleGimbals.Length - 1; i >= 0; --i) - { - if (vc.moduleGimbals[i].gimbalActive && !vc.moduleGimbals[i].gimbalLock && vc.moduleGimbals[i].enableYaw != enable) - { - changed = true; - vc.moduleGimbals[i].enableYaw = enable; - } - } - - return (changed) ? 1.0 : 0.0; - } - - /// - /// Toggles the mode for any multi-mode engines. - /// - /// 1 if any engines were toggled, 0 if no multi-mode engines are installed. - public double ToggleMultiModeEngineMode() - { - for (int i = vc.multiModeEngines.Length - 1; i >= 0; --i) - { - vc.multiModeEngines[i].ToggleMode(); - } - - vc.InvalidateModules(); - - return (vc.multiModeEngines.Length > 0) ? 1.0 : 0.0; - } - #endregion - - /// - /// Flight status variables are in this category. - /// - #region Flight Status - /// - /// Returns 1 if the vessel is in a landed state (LANDED, SPLASHED, - /// or PRELAUNCH); 0 otherwise - /// - /// - public double VesselLanded() - { - return (vesselSituationConverted < 3) ? 1.0 : 0.0; - } - - /// - /// Returns 1 if the vessel is in a flying state (FLYING, SUB_ORBITAL, - /// ORBITING, ESCAPING, DOCKED). - /// - /// - public double VesselFlying() - { - if ((vessel.Landed || vessel.Splashed) != (vesselSituationConverted <= 2)) - { - Utility.LogMessage(this, "vessel.Landed {0} and vesselSituationConverted {1} disagree! - vessel.situation is {2}", vessel.Landed, vesselSituationConverted, vessel.situation); - } - return (vesselSituationConverted > 2) ? 1.0 : 0.0; - } - - /// - /// Returns the vessel's situation, based on the KSP variable: - /// - /// * 0 - LANDED - /// * 1 - SPLASHED - /// * 2 - PRELAUNCH - /// * 3 - FLYING - /// * 4 - SUB_ORBITAL - /// * 5 - ORBITING - /// * 6 - ESCAPING - /// * 7 - DOCKED - /// - /// A number between 0 and 7 (inclusive). - public double VesselSituation() - { - return vesselSituationConverted; - } - - /// - /// Returns the name of the vessel's situation. - /// - /// - public string VesselSituationName() - { - return vessel.SituationString; - } - #endregion - - /// - /// Variables and control methods for the Gear action group are in this - /// category. In addition, status and information methods for deployable - /// landing gear and wheels are in this category. For simplicity, landing gear - /// and wheels may be simply called "landing gear" in the descriptions. - /// - #region Gear - - /// - /// Returns the number of deployable landing gear on the craft. - /// this function only counts the parts using ModuleWheelDeployment. - /// - /// Number of deployable gear, or 0. - public double DeployableGearCount() - { - return vc.moduleWheelDeployment.Length; - } - - /// - /// Returns the number of landing gear or wheels that are broken. Returns 0 if none are, or if there - /// are no gear. - /// - /// The number of landing gear that are broken. - public double GearBrokenCount() - { - int brokenCount = 0; - - for (int i = vc.moduleWheelDamage.Length - 1; i >= 0; --i) - { - if (vc.moduleWheelDamage[i].isDamaged) - { - return ++brokenCount; - } - } - - return (double)brokenCount; - } - - /// - /// Returns the number of wheels / landing gear installed on the craft. This function counts all - /// landing gear and wheels, including those that do not deploy. - /// - /// Number of gear, or 0. - public double GearCount() - { - return vc.moduleWheelBase.Length; - } - - /// - /// Returns 1 if there are actions assigned to the landing gear AG. - /// - /// - public double GearHasActions() - { - return (vc.GroupHasActions(KSPActionGroup.Gear)) ? 1.0 : 0.0; - } - - /// - /// Returns -1 if any deployable landing gear or wheels are retracting, - /// +1 if they are extending. Otherwise returns 0. - /// - /// -1, 0, or +1. - public double GearMoving() - { - return vc.wheelDirection; - } - - /// - /// Returns a number representing the average position of undamaged deployable landing gear or wheels. - /// - /// * 0 - No deployable gear, no undamaged gear, or all undamaged gear are retracted. - /// * 1 - All deployable gear extended. - /// - /// If the gear are moving, a number between 0 and 1 is returned. - /// - /// An number between 0 and 1 as described in the summary. - public double GearPosition() - { - return vc.wheelPosition; - } - - /// - /// Returns the highest stress percentage of any non-broken landing gear in the - /// range [0, 1]. - /// - /// Highest stress percentage, or 0 if no gear/wheels. - public double GearStress() - { - float maxStress = 0.0f; - for (int i = vc.moduleWheelDamage.Length - 1; i >= 0; --i) - { - if (!vc.moduleWheelDamage[i].isDamaged) - { - maxStress = Mathf.Max(maxStress, vc.moduleWheelDamage[i].stressPercent); - } - } - - // stressPercent is a [0, 100] - convert it here for consistency - return maxStress * 0.01f; - } - - /// - /// Returns 1 if the landing gear action group is active. - /// - /// - public double GetGear() - { - return (vessel.ActionGroups[KSPActionGroup.Gear]) ? 1.0 : 0.0; - } - - /// - /// Set the landing gear action group to the specified state. - /// - /// - /// 1 if active is true, 0 otherwise. - public double SetGear(bool active) - { - vessel.ActionGroups.SetGroup(KSPActionGroup.Gear, active); - return (active) ? 1.0 : 0.0; - } - - /// - /// Toggle the landing gear action group - /// - /// 1 if the gear action group is active, 0 if not. - public double ToggleGear() - { - vessel.ActionGroups.ToggleGroup(KSPActionGroup.Gear); - return (vessel.ActionGroups[KSPActionGroup.Gear]) ? 1.0 : 0.0; - } - #endregion - - /// - /// The Grapple category controls grappling nodes ("The Claw"). - /// - /// Like the Dock category, many of these methods use the concept of "Primary Grapple". - /// The primary grapple is defined as the first or only grapple - /// found on the vessel. - /// - /// Grapples may be made reference transforms. The functions related to that are found - /// under the Dock category to be consistent with the existing reference transform - /// functionality. - /// - #region Grapple - - /// - /// Returns 1 if the primary grapple's pivot is locked, returns 0 if it is unlocked, or - /// there is no grapple. - /// - public double GetGrapplePivotLocked() - { - return (vc.clawNode != null && !vc.clawNode.IsLoose()) ? 1.0 : 0.0; - } - - /// - /// Returns 1 if the primary grapple is armed and ready for use. Returns 0 otherwise. - /// - /// 1 if the primary grapple is ready, 0 if it is not ready (in use, not armed), or there is no grapple. - public double GrappleArmed() - { - if (vc.clawNode != null) - { - return (vc.clawNodeState == MASVesselComputer.DockingNodeState.DISABLED) ? 0.0 : 1.0; - } - - return 0.0; - } - - /// - /// Returns 1 if the primary grapple is holding something. Returns 0 if it is not, or no grapple - /// is installed. - /// - /// - public double Grappled() - { - return (vc.clawNodeState == MASVesselComputer.DockingNodeState.DOCKED) ? 1.0 : 0.0; - } - - /// - /// Returns the name of the object grappled by the vessel. Returns an empty string if fc.Grappled() returns 0. - /// - /// The name of the grappled object. - public string GrappledObjectName() - { - if (vc.clawNodeState == MASVesselComputer.DockingNodeState.DOCKED) - { - if (vc.clawNode.vesselInfo != null) - { - string l10n = string.Empty; - if (KSP.Localization.Localizer.TryGetStringByTag(vc.clawNode.vesselInfo.name, out l10n)) - { - return l10n; - } - else - { - return vc.clawNode.vesselInfo.name; - } - } - } - return string.Empty; - } - - /// - /// Returns a number representing whether the primary grapple is arming, or disarming, or not changing. - /// - /// -1 if the grapple is disarming, +1 if the grapple is arming, 0 otherwise. - public double GrappleMoving() - { - if (vc.clawNode != null) - { - try - { - ModuleAnimateGeneric clawAnimation = (vc.clawNode.part.Modules[vc.clawNode.deployAnimationController] as ModuleAnimateGeneric); - if (clawAnimation != null) - { - if (clawAnimation.IsMoving()) - { - return (clawAnimation.animSpeed > 0.0f) ? 1.0 : -1.0; - } - } - } - catch - { - return 0.0; - } - } - - return 0.0; - } - - /// - /// Returns 1 if the primary grapple is armed and ready for use. Returns 0 otherwise. - /// - /// 1 if the primary grapple is ready, 0 if it is not ready, or there is no grapple. - public double GrappleReady() - { - if (vc.clawNode != null) - { - return (vc.clawNodeState == MASVesselComputer.DockingNodeState.READY) ? 1.0 : 0.0; - } - - return 0.0; - } - - /// - /// Release the primary grapple from whatever it's connected to. - /// - /// 1 if the grapple released, 0 if it did not, or there is no grapple. - public double GrappleRelease() - { - if (vc.clawNode != null) - { - if (vc.clawNodeState == MASVesselComputer.DockingNodeState.DOCKED) - { - vc.clawNode.Release(); - return 1.0; - } - else if (vc.clawNodeState == MASVesselComputer.DockingNodeState.PREATTACHED) - { - // Not sure this actually happens. - vc.clawNode.Decouple(); - return 1.0; - } - } - - return 0.0; - } - - /// - /// Indicates whether a primary grapple was found on the vessel. - /// - /// 1 if a grapple is available, 0 if none were detected. - public double HasGrapple() - { - return (vc.clawNode != null) ? 1.0 : 0.0; - } - - /// - /// Sets the arming state of the primary grapple. Has no effect if there is no grapple, or if it can not - /// be armed or disarmed, such as when it is grappling something. - /// - /// If true, and the grapple is disarmed, arm the grapple. If false, and the grapple can be disarmed, disarm the grapple. - /// 1 if the state was changed. 0 otherwise. - public double SetGrappleArmed(bool armGrapple) - { - if (vc.clawNode != null) - { - bool applyChange = false; - if (vc.clawNodeState == MASVesselComputer.DockingNodeState.DISABLED && armGrapple == true) - { - applyChange = true; - } - else if (vc.clawNodeState == MASVesselComputer.DockingNodeState.READY && armGrapple == false) - { - applyChange = true; - } - - if (applyChange) - { - try - { - ModuleAnimateGeneric clawAnimation = (vc.clawNode.part.Modules[vc.clawNode.deployAnimationController] as ModuleAnimateGeneric); - if (clawAnimation != null) - { - clawAnimation.Toggle(); - } - } - catch - { - return 0.0; - } - - return 1.0; - } - } - - return 0.0; - } - - /// - /// Change whether the primary grapple's pivot is locked on loose. - /// - /// If true, the joint is locked. If loose, it is unlocked. - /// 1 if the state is changed, 0 otherwise. - public double SetGrapplePivot(bool locked) - { - if (vc.clawNode != null) - { - if (locked && vc.clawNode.IsJointUnlocked()) - { - vc.clawNode.LockPivot(); - return 1.0; - } - else if (!locked && !vc.clawNode.IsJointUnlocked()) - { - vc.clawNode.SetLoose(); - return 1.0; - } - } - - return 0.0; - } - - /// - /// Arm or disarm the installed primary grapple. - /// - /// 1 if the grapple is arming or disarming, 0 if the grapple does not have an arming behavior, or if there is no grapple. - public double ToggleGrappleArmed() - { - if (vc.clawNode != null) - { - try - { - ModuleAnimateGeneric clawAnimation = (vc.clawNode.part.Modules[vc.clawNode.deployAnimationController] as ModuleAnimateGeneric); - if (clawAnimation != null) - { - clawAnimation.Toggle(); - } - } - catch - { - return 0.0; - } - - return 1.0; - } - - return 0.0; - } - - /// - /// Toggle the state of the primary grapple's pivot - /// - /// 1 if the state qas changed, 0 otherwise. - public double ToggleGrapplePivot() - { - if (vc.clawNode != null) - { - if (vc.clawNode.IsJointUnlocked()) - { - vc.clawNode.LockPivot(); - } - else - { - vc.clawNode.SetLoose(); - } - - return 1.0; - } - - return 0.0; - } - #endregion - - /// - /// The Lights action group can be controlled and queried through this category. - /// - #region Lights - /// - /// Returns 1 if the Lights action group has at least one action assigned to it. - /// - /// - public double LightsHasActions() - { - return (vc.GroupHasActions(KSPActionGroup.Light)) ? 1.0 : 0.0; - } - - /// - /// Returns 1 if the Lights action group is active. - /// - /// 1 if the lights action group is active, 0 otherwise. - public double GetLights() - { - return (vessel.ActionGroups[KSPActionGroup.Light]) ? 1.0 : 0.0; - } - - /// - /// Set the state of the lights action group. - /// - /// - /// 1 if the lights action group is active, 0 otherwise. - public double SetLights(bool active) - { - vessel.ActionGroups.SetGroup(KSPActionGroup.Light, active); - return (active) ? 1.0 : 0.0; - } - - /// - /// Toggle the lights action group. - /// - /// 1 if the lights action group is active, 0 otherwise. - public double ToggleLights() - { - vessel.ActionGroups.ToggleGroup(KSPActionGroup.Light); - return (vessel.ActionGroups[KSPActionGroup.Light]) ? 1.0 : 0.0; - } - #endregion - } - - /// - /// The MASProxyAttribute class is used to mark specific methods in the various - /// proxy classes as either Immutable or Uncacheable (both would be nonsensical). - /// - /// A method flagged as Immutable is evaluated once when it's created, and never - /// again (useful for values that never change in a game session). - /// - /// A method flagged as Dependent is a value that can change, but it does not need - /// to be queried each FixedUpdate. - /// - /// A method flagged as Persistent is a persistent value query. Provided the string - /// parameter is a constant, it will only be re-evaluated when the persistent value - /// is updated. - /// - /// A method flagged as Uncacheable is expected to change each time it's called, - /// such as random number generators. - /// - /// These attributes affect only variables that can be transformed to a - /// native evaluator - Lua scripts are always cacheable + mutable. - /// - [AttributeUsage(AttributeTargets.Method)] - public class MASProxyAttribute : System.Attribute - { - private bool immutable; - private bool dependent; - private bool persistent; - private bool uncacheable; - - public bool Immutable - { - get - { - return immutable; - } - set - { - immutable = value; - } - } - - public bool Dependent - { - get - { - return dependent; - } - set - { - dependent = value; - } - } - - public bool Persistent - { - get - { - return persistent; - } - set - { - persistent = value; - } - } - - public bool Uncacheable - { - get - { - return uncacheable; - } - set - { - uncacheable = value; - } - } - } -} diff --git a/ModifiedCode/MASFlightComputerProxy2.cs b/ModifiedCode/MASFlightComputerProxy2.cs deleted file mode 100644 index c121d970..00000000 --- a/ModifiedCode/MASFlightComputerProxy2.cs +++ /dev/null @@ -1,4187 +0,0 @@ -/***************************************************************************** - * The MIT License (MIT) - * - * Copyright (c) 2016-2020 MOARdV - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to - * deal in the Software without restriction, including without limitation the - * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or - * sell copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - * - ****************************************************************************/ -using KSP.UI; -using KSP.UI.Screens; -using MoonSharp.Interpreter; -using System; -using System.Collections; -using System.Collections.Generic; -using System.Text; -using UnityEngine; - -namespace AvionicsSystems -{ - // ΔV - put this somewhere where I can find it easily to copy/paste - - /// - /// The flight computer proxy provides the interface between the flight - /// computer module and the variable / Lua environment. - /// - /// While it is a wrapper for MASFlightComputer, not all - /// values are plumbed through to the flight computer (for instance, the - /// action group control and state are all handled in this class). - /// - /// fc - /// - /// The `fc` group contains the core interface between KSP, Avionics - /// Systems, and props in an IVA. It consists of many 'information' functions - /// that can be used to get information as well as numerous 'action' functions - /// that are used to do things. - /// - /// Due to the number of methods in the `fc` group, this document has been split - /// across three pages: - /// - /// * [[MASFlightComputerProxy]] (Abort - Lights), - /// * [[MASFlightComputerProxy2]] (Maneuver Node - Reaction Wheel), and - /// * [[MASFlightComputerProxy3]] (Resources - Vessel Info). - /// - /// **NOTE 1:** If a function listed below includes an entry for 'Supported Mod(s)', - /// then that function will automatically use one of the mods listed to - /// generate the data. In some cases, it is possible that the function does not work without - /// one of the required mods. Those instances are noted in the function's description. - /// - /// **NOTE 2:** Many descriptions make use of mathetmatical short-hand to describe - /// a range of values. This short-hand consists of using square brackets `[` and `]` - /// to denote "inclusive range", while parentheses `(` and `)` indicate exclusive range. - /// - /// For example, if a parameter says "an integer between [0, `fc.ExperimentCount()`)", it - /// means that the parameter must be an integer greater than or equal to 0, but less - /// than `fc.ExperimentCount()`. - /// - /// For another example, if a parameter says "a number in the range [0, 1]", it means that - /// the number must be at least zero, and it must not be larger than 1. - /// - internal partial class MASFlightComputerProxy - { - private const string siPrefixes = " kMGTPEZY"; - - [MoonSharpHidden] - private IEnumerator RecoverVesselLater() - { - yield return MASConfig.waitForFixedUpdate; - // Let the FixedUpdate finish before we recover - GameEvents.OnVesselRecoveryRequested.Fire(vessel); - } - - [MoonSharpHidden] - private string DoSIFormatLessThan1(double value, int length, int minDecimal, string delimiter, bool forceSign, bool showPrefix) - { - // '#.' - int nonDecimalChars = 2; - // Sign - if (forceSign || value < 0.0) - { - ++nonDecimalChars; - } - // add the blank prefix char - if (showPrefix) - { - ++nonDecimalChars; - --length; - } - - var result = StringBuilderCache.Acquire(); - result.AppendFormat("{{0,{0}:0", length); - int netDecimal = Math.Max(minDecimal, length - nonDecimalChars); - if (netDecimal > 0) - { - result.Append('.').Append('0', netDecimal); - } - result.Append("}"); - if (showPrefix) - { - result.Append(" "); - } - return string.Format(result.ToStringAndRelease(), value); - } - - [MoonSharpHidden] - private string DoSIFormat(double value, int length, int minDecimal, string delimiter, bool forceSign, bool showPrefix) - { - if (double.IsInfinity(value) || double.IsNaN(value)) - { - // Force illegal values to 0. - value = 0.0; - } - - //Utility.LogMessage(this, "DoSIFormat {0}, {1}, {2}, x, {3}, {4}", value, length, minDecimal, forceSign, showPrefix); - if (Math.Abs(value) < 1.0) - { - // special case: abs(value) < 1 - return DoSIFormatLessThan1(value, length, minDecimal, delimiter, forceSign, showPrefix); - } - int leadingDigitExponent; - leadingDigitExponent = (int)Math.Floor(Math.Log10(Math.Abs(value))); - if (leadingDigitExponent / 3 >= siPrefixes.Length) - { - // If the number is too big to handle, treat it as zero. Note that float infinity - // makes it past the tests above. - return DoSIFormatLessThan1(0.0, length, minDecimal, delimiter, forceSign, showPrefix); - } - - // How many characters need to be set aside? - int reservedCharacters = (minDecimal > 0) ? (1 + minDecimal) : 0; - if (showPrefix) - { - ++reservedCharacters; - } - if (value < 0.0 || forceSign) - { - ++reservedCharacters; - } - - // How much space is there to work with? - int freeCharacters = length - reservedCharacters; - - // Nearest SI exponent to the value. - int siExponent = Math.Min(leadingDigitExponent / 3, siPrefixes.Length - 1) * 3; - - int digits = leadingDigitExponent - siExponent; - double scaledInputValue = Math.Round(value / Math.Pow(10.0, siExponent), minDecimal); - int scaledLength = (Math.Abs(scaledInputValue) > 0.0) ? (int)Math.Floor(Math.Log10(Math.Abs(scaledInputValue))) + 1 : 1; - int groupLen = (string.IsNullOrEmpty(delimiter)) ? 3 : 4; - - int freeCh2 = freeCharacters - ((scaledLength == 4) ? groupLen + 1 : scaledLength); - //Utility.LogMessage(this, "freeCh2 => {0}, si = {1}", freeCh2, siExponent); - while (freeCh2 >= groupLen && siExponent >= 3) - { - freeCh2 -= groupLen; - siExponent -= 3; - //Utility.LogMessage(this, "freeCh2 => {0}, si = {1}", freeCh2, siExponent); - } - if (minDecimal == 0 && freeCh2 > 0) - { - // Reserve a space for the decimal - --freeCh2; - } - minDecimal += freeCh2; - scaledInputValue = Math.Round(value / Math.Pow(10.0, siExponent), minDecimal); - - // TODO: Make a cache of format strings based on length, minDecimal (as adjusted at this point), and showPrefix. - - //Utility.LogMessage(this, "leadExp = {1}, siExp = {2}, reserve = {0}, freeCh = {3}, scaledIn = {6}, scaledLen = {7}, freeCh2 = {5}", - // reservedCharacters, - // leadingDigitExponent, - // siExponent, - // freeCharacters, - // digits, - // freeCh2, - // scaledInputValue, - // scaledLength); - var result = StringBuilderCache.Acquire(); - result.AppendFormat("{{0,{0}:", length); - if (groupLen == 4) - { - result.Append("#,#"); - } - result.Append("0"); - if (minDecimal > 0) - { - result.Append('.').Append('0', minDecimal); - } - result.Append("}"); - if (showPrefix) - { - result.Append("{1}"); - } - - if (siExponent < 0 || siExponent >= siPrefixes.Length) - { - Utility.LogWarning(this, "Formatting the value {0}, got an exponent of {1} (out of bounds). Jamming the value to 0 to avoid an exception.", - value, siExponent); - scaledInputValue = 0.0; - siExponent = 0; - } - //Utility.LogMessage(this, "\"{0}\" -> \"{1}\"", result.ToString(), string.Format(result.ToString(), scaledInputValue, 'Q')); - return string.Format(result.ToStringAndRelease(), scaledInputValue, siPrefixes[siExponent / 3]); - } - - /// - /// Methods for querying and controlling maneuver nodes are in this category. - /// - #region Maneuver Node - - /// - /// Replace any scheduled maneuver nodes with this maneuver node. - /// - /// ΔV in the prograde direction at the time of the maneuver, in m/s. - /// ΔV in the normal direction at the time of the maneuver, in m/s. - /// ΔV in the radial direction at the time of the maneuver, in m/s. - /// UT to schedule the maneuver, in seconds. - /// 1 if the manuever node was created, 0 on any errors. - public double AddManeuverNode(double progradedV, double normaldV, double radialdV, double timeUT) - { - if (vessel.patchedConicSolver != null) - { - if (double.IsNaN(progradedV) || double.IsInfinity(progradedV) || - double.IsNaN(normaldV) || double.IsInfinity(normaldV) || - double.IsNaN(radialdV) || double.IsInfinity(radialdV) || - double.IsNaN(timeUT) || double.IsInfinity(timeUT)) - { - // bad parameters? - return 0.0; - } - - Vector3d dV = new Vector3d(radialdV, normaldV, progradedV); - - // No living in the past. - timeUT = Math.Max(timeUT, vc.universalTime); - - vessel.patchedConicSolver.maneuverNodes.Clear(); - ManeuverNode mn = vessel.patchedConicSolver.AddManeuverNode(timeUT); - mn.OnGizmoUpdated(dV, timeUT); - - return 1.0; - } - - return 0.0; - } - - /// - /// Clear all scheduled maneuver nodes. - /// - /// 1 if any nodes were cleared, 0 if no nodes were cleared. - public double ClearManeuverNode() - { - if (vessel.patchedConicSolver != null) - { - int nodeCount = vessel.patchedConicSolver.maneuverNodes.Count; - // TODO: what is vessel.patchedConicSolver.flightPlan? And do I care? - for (int i = nodeCount - 1; i >= 0; --i) - { - vessel.patchedConicSolver.RemoveManeuverNode(vessel.patchedConicSolver.maneuverNodes[i]); - } - - return (nodeCount > 0) ? 1.0 : 0.0; - } - - return 0.0; - } - - /// - /// Clear first scheduled maneuver node. - /// - /// 1 if any nodes were cleared, 0 if no nodes were cleared. - public double ClearOneManeuverNode() - { - if (vessel.patchedConicSolver != null) - { - int nodeCount = vessel.patchedConicSolver.maneuverNodes.Count; - // TODO: what is vessel.patchedConicSolver.flightPlan? And do I care? - vessel.patchedConicSolver.maneuverNodes[0].RemoveSelf(); - - return (nodeCount > 0) ? 1.0 : 0.0; - } - - return 0.0; - } - - /// - /// Returns the apoapsis of the orbit that results from the scheduled maneuver. - /// - /// New Ap in meters, or 0 if no node is scheduled. - public double ManeuverNodeAp() - { - if (vc.maneuverNodeValid) - { - return vc.nodeOrbit.ApA; - } - else - { - return 0.0; - } - } - - /// - /// Returns an estimate of the maneuver node burn time, in seconds. - /// - /// Approximate burn time in seconds, or 0 if no node is scheduled. - public double ManeuverNodeBurnTime() - { - return vc.NodeBurnTime(); - } - - /// - /// Delta-V remaining for the next scheduled node. - /// - /// ΔV in m/s, or 0 if no node is scheduled. - public double ManeuverNodeDV() - { - return vc.maneuverNodeDeltaV; - } - - /// - /// The normal component of the next scheduled maneuver. - /// - /// ΔV in m/s; negative values indicate anti-normal. - public double ManeuverNodeDVNormal() - { - if (vc.maneuverNodeValid && vc.nodeOrbit != null) - { - return vc.maneuverNodeComponent.y; - } - return 0.0; - } - - /// - /// The prograde component of the next scheduled maneuver. - /// - /// ΔV in m/s; negative values indicate retrograde. - public double ManeuverNodeDVPrograde() - { - if (vc.maneuverNodeValid && vc.nodeOrbit != null) - { - return vc.maneuverNodeComponent.x; - } - return 0.0; - } - - /// - /// The radial component of the next scheduled maneuver. - /// - /// ΔV in m/s; negative values indicate anti-radial. - public double ManeuverNodeDVRadial() - { - if (vc.maneuverNodeValid && vc.nodeOrbit != null) - { - return vc.maneuverNodeComponent.z; - } - return 0.0; - } - - /// - /// Returns the eccentricity of the orbit that results from the scheduled maneuver. - /// - /// New eccentricity, or 0 if no node is scheduled. - public double ManeuverNodeEcc() - { - if (vc.maneuverNodeValid) - { - return vc.nodeOrbit.eccentricity; - } - else - { - return 0.0; - } - } - - /// - /// Returns 1 if there is a valid maneuver node; 0 otherwise - /// - /// - public double ManeuverNodeExists() - { - return (vc.maneuverNodeValid) ? 1.0 : 0.0; - } - - /// - /// Returns the inclination of the orbit that results from the scheduled maneuver. - /// - /// New inclination in degrees, or 0 if no node is scheduled. - public double ManeuverNodeInc() - { - if (vc.maneuverNodeValid) - { - return vc.nodeOrbit.inclination; - } - else - { - return 0.0; - } - } - - /// - /// Returns the index to the body that the vessel will encounter after a change in SoI caused by - /// a scheduled maneuver node. If there is no maneuver node, or the vessel does not change SoI - /// because of the maneuver, returns -1 (current body). - /// - /// - public double ManeuverNodeNextBody() - { - if (vc.maneuverNodeValid && vesselSituationConverted > 2) - { - if (vc.nodeOrbit.patchEndTransition == Orbit.PatchTransitionType.ENCOUNTER || vc.nodeOrbit.patchEndTransition == Orbit.PatchTransitionType.ESCAPE) - { - Orbit o = vc.nodeOrbit.nextPatch; - if (o != null) - { - int bodiesCount = FlightGlobals.Bodies.Count; - for (int i = 0; i < bodiesCount; ++i) - { - if (FlightGlobals.Bodies[i].name == o.referenceBody.name) - { - return i; - } - } - } - } - } - - return -1.0; - } - - /// - /// Returns 1 if the SoI change after a scheduled maneuver is an 'encounter', -1 if it is an - /// 'escape', and 0 if the scheduled orbit does not change SoI. - /// - /// 0 if the orbit does not transition. 1 if the vessel will encounter a body, -1 if the vessel will escape the current body. - public double ManeuverNodeNextSoI() - { - if (vc.maneuverNodeValid && vesselSituationConverted > 2) - { - if (vc.nodeOrbit.patchEndTransition == Orbit.PatchTransitionType.ENCOUNTER) - { - return 1.0; - } - else if (vc.nodeOrbit.patchEndTransition == Orbit.PatchTransitionType.ESCAPE) - { - return -1.0; - } - } - - return 0.0; - } - - /// - /// Returns the periapsis of the orbit that results from the scheduled maneuver. - /// - /// New Pe in meters, or 0 if no node is scheduled. - public double ManeuverNodePe() - { - if (vc.maneuverNodeValid) - { - return vc.nodeOrbit.PeA; - } - else - { - return 0.0; - } - } - - /// - /// Returns the relative inclination of the target that will result from the - /// scheduled maneuver. - /// - /// New relative inclination in degrees, or 0 if there is no maneuver node, - /// no target, or the target orbits a different body. - public double ManeuverNodeRelativeInclination() - { - if (vc.maneuverNodeValid && vc.targetType > 0 && vc.targetOrbit.referenceBody == vc.nodeOrbit.referenceBody) - { - return Vector3.Angle(vc.nodeOrbit.GetOrbitNormal(), vc.targetOrbit.GetOrbitNormal()); - } - else - { - return 0.0; - } - } - - /// - /// Closest approach to the target after the next maneuver completes, in meters. If there - /// is no maneuver scheduled, or no target, returns 0. - /// - /// Closest approach to the target, or 0. - public double ManeuverNodeTargetClosestApproachDistance() - { - if (vc.maneuverNodeValid && vc.targetType > 0) - { - if (!nodeApproachSolver.resultsReady) - { - if (vc.targetType == MASVesselComputer.TargetType.CelestialBody) - { - nodeApproachSolver.SolveBodyIntercept(vc.nodeOrbit, vc.activeTarget as CelestialBody); - } - else - { - nodeApproachSolver.SolveOrbitIntercept(vc.nodeOrbit, vc.targetOrbit); - } - } - - if (vc.targetType == MASVesselComputer.TargetType.CelestialBody) - { - return Math.Max(0.0, nodeApproachSolver.targetClosestDistance - (vc.activeTarget as CelestialBody).Radius); - } - else - { - return nodeApproachSolver.targetClosestDistance; - } - } - else - { - return 0.0; - } - } - - /// - /// Relative speed of the target at closest approach after the scheduled maneuver, in m/s. If there - /// is no maneuver scheduled, or no target, returns 0. - /// - /// Relative speed of the target at closest approach after the maneuver, m/s. - public double ManeuverNodeTargetClosestApproachSpeed() - { - if (vc.maneuverNodeValid && vc.targetType > 0) - { - if (!nodeApproachSolver.resultsReady) - { - if (vc.targetType == MASVesselComputer.TargetType.CelestialBody) - { - nodeApproachSolver.SolveBodyIntercept(vc.nodeOrbit, vc.activeTarget as CelestialBody); - } - else - { - nodeApproachSolver.SolveOrbitIntercept(vc.nodeOrbit, vc.targetOrbit); - } - } - return nodeApproachSolver.targetClosestSpeed; - } - else - { - return 0.0; - } - } - - /// - /// Time when the closest approach with a target occurs after the scheduled maneuver, in seconds. If there - /// is no maneuver scheduled, or no target, returns 0. - /// - /// Time until closest approach after the maneuver. - public double ManeuverNodeTargetClosestApproachTime() - { - if (vc.maneuverNodeValid && vc.targetType > 0) - { - if (!nodeApproachSolver.resultsReady) - { - if (vc.targetType == MASVesselComputer.TargetType.CelestialBody) - { - nodeApproachSolver.SolveBodyIntercept(vc.nodeOrbit, vc.activeTarget as CelestialBody); - } - else - { - nodeApproachSolver.SolveOrbitIntercept(vc.nodeOrbit, vc.targetOrbit); - } - } - return Math.Max(nodeApproachSolver.targetClosestUT - Planetarium.GetUniversalTime(), 0.0); - } - else - { - return 0.0; - } - } - - /// - /// Returns time in seconds until the maneuver node; 0 if no node is - /// valid. - /// - /// - public double ManeuverNodeTime() - { - return vc.maneuverNodeTime; - } - - /// - /// Returns the time to the next SoI transition in the scheduled maneuver, in seconds. If the planned orbit does not change - /// SoI, or no node is scheduled, returns 0. - /// - /// Time until the next SoI after the scheduled maneuver, or 0 - public double ManeuverNodeTimeToNextSoI() - { - if (vc.maneuverNodeValid && - vc.nodeOrbit.patchEndTransition == Orbit.PatchTransitionType.ENCOUNTER || - vc.nodeOrbit.patchEndTransition == Orbit.PatchTransitionType.ESCAPE) - { - return vc.nodeOrbit.UTsoi - Planetarium.GetUniversalTime(); - } - return 0.0; - } - - /// - /// Total Delta-V required for the next scheduled node. This value does not account for any - /// maneuvering. - /// - /// ΔV in m/s, or 0 if no node is scheduled. - public double ManeuverNodeTotalDV() - { - return vc.maneuverNodeTotalDeltaV; - } - #endregion - - /// - /// Vessel mass may be queried with these methods. - /// - #region Mass - /// - /// Returns the mass of the vessel in tonnes. - /// - /// wet mass if true, dry mass otherwise - /// Vessel mass in tonnes. - public double Mass(bool wetMass) - { - if (wetMass) - { - return vessel.totalMass; - } - else - { - return vessel.totalMass - vc.totalResourceMass; - } - } - #endregion - - /// - /// Provides MAS-native methods for common math primitives. These methods generally - /// duplicate the functions in the Lua math table, but by placing them in MAS, MAS - /// can use native delegates instead of having to call into Lua (which is slower). - /// - /// This region also contains other useful mathematical methods. - /// - #region Math - - [MASProxy(Dependent = true)] - /// - /// Returns the absolute value of `value`. - /// - /// The absolute value of `value`. - public double Abs(double value) - { - return Math.Abs(value); - } - - [MASProxy(Dependent = true)] - /// - /// Returns 1 if `value` is at least equal to `lowerBound` and not greater - /// than `upperBound`. Returns 0 otherwise. - /// - /// In other words, - /// * If `value` >= `lowerBound` and `value` <= `upperBound`, return 1. - /// * Otherwise, reutrn 0. - /// - /// The value to test. - /// The lower bound (inclusive) of the range to test. - /// The upper bound (inclusive) of the range to test. - /// 1 if `value` is between `lowerBound` and `upperBound`, 0 otherwise. - public double Between(double value, double lowerBound, double upperBound) - { - return (value >= lowerBound && value <= upperBound) ? 1.0 : 0.0; - } - - [MASProxy(Dependent = true)] - /// - /// Converts `value1` and `value2` to 32 bit integers and applies a bitwise-AND - /// operation. - /// - /// An integer value. - /// An integer value. - /// value1 AND value2 - public double BitwiseAnd(double value1, double value2) - { - int v1 = (int)value1; - int v2 = (int)value2; - - return (double)(v1 & v2); - } - - [MASProxy(Dependent = true)] - /// - /// Converts `value1` and `value2` to 32 bit integers and applies a bitwise-OR - /// operation. - /// - /// An integer value. - /// An integer value. - /// value1 OR value2 - public double BitwiseOr(double value1, double value2) - { - int v1 = (int)value1; - int v2 = (int)value2; - - return (double)(v1 | v2); - } - - [MASProxy(Dependent = true)] - /// - /// Converts `value` to a 32 bit value and applies a bitwise-negation - /// operation. - /// - /// An integer value. - /// ~value - public double BitwiseNegate(double value) - { - int v1 = (int)value; - - return (double)(~v1); - } - - [MASProxy(Dependent = true)] - /// - /// Converts `value1` and `value2` to 32 bit integers and applies a bitwise-XOR - /// operation. - /// - /// An integer value. - /// An integer value. - /// value1 XOR value2 - public double BitwiseXor(double value1, double value2) - { - int v1 = (int)value1; - int v2 = (int)value2; - - return (double)(v1 ^ v2); - } - - [MASProxy(Dependent = true)] - /// - /// Rounds a number up to the next integer. - /// - /// The value to round - /// - public double Ceiling(double value) - { - return Math.Ceiling(value); - } - - [MASProxy(Dependent = true)] - /// - /// Clamps `value` to stay within the range `a` to `b`, inclusive. `a` does not - /// have to be less than `b`. - /// - /// The value to clamp. - /// The first bound. - /// The second bound. - /// The clamped value. - public double Clamp(double value, double a, double b) - { - double max = Math.Max(a, b); - double min = Math.Min(a, b); - return Math.Max(Math.Min(value, max), min); - } - - [MASProxy(Dependent = true)] - /// - /// Rounds a number down to the next integer. - /// - /// The value to round - /// - public double Floor(double value) - { - return Math.Floor(value); - } - - [MASProxy(Dependent = true)] - /// - /// Provides an "inverse lerp" of value, returning a value between 0 and 1 - /// depending on where `value` falls between `range1` and `range2`. - /// - /// If `range1` == `range2`, or if `value` < `range1`, it returns 0. - /// - /// If `value` > `range2`, it returns 1. - /// - /// The value to evaluate. - /// The first bound of the range. - /// The second bound of the range. - /// A value in the range [0, 1] as described in the summary. - public double InverseLerp(double value, double range1, double range2) - { - if (range1 == range2) - { - return 0.0; - } - else if (range1 > range2) - { - value = -value; - range1 = -range1; - range2 = -range2; - } - - if (value <= range1) - { - return 0.0; - } - else if (value >= range2) - { - return 1.0; - } - - return (value - range1) / (range2 - range1); - } - - [MASProxy(Dependent = true)] - /// - /// Return the larger value - /// - /// The first value to test. - /// The second value to test. - /// `a` if `a` is larger than `b`; `b` otherwise. - public double Max(double a, double b) - { - return Math.Max(a, b); - } - - [MASProxy(Dependent = true)] - /// - /// Return the smaller value - /// - /// The first value to test. - /// The second value to test. - /// `a` if `a` is smaller than `b`; `b` otherwise. - public double Min(double a, double b) - { - return Math.Min(a, b); - } - - [MASProxy(Dependent = true)] - /// - /// Normalizes an angle to the range [0, 360). - /// - /// The de-normalized angle to correct. - /// A value between 0 (inclusive) and 360 (exclusive). - public double NormalizeAngle(double angle) - { - return Utility.NormalizeAngle(angle); - } - - [MASProxy(Dependent = true)] - /// - /// Normalizes an angle to the range [-180, 180). - /// - /// The de-normalized angle to correct. - /// A value between -180 (inclusive) and +180 (exclusive). - public double NormalizeLongitude(double angle) - { - return Utility.NormalizeLongitude(angle); - } - - [MASProxy(Dependent = true)] - /// - /// Converts an angle to a pitch value in the range of [-90, +90]. - /// - /// The angle to convert - /// A value between -90 and +90, inclusive. - public double NormalizePitch(double angle) - { - double pitch = Utility.NormalizeLongitude(angle); - if (pitch > 90.0) - { - return 180.0 - pitch; - } - else if (pitch < -90.0) - { - return -180.0 - pitch; - } - else - { - return pitch; - } - } - - [MASProxy(Dependent = true)] - /// - /// Apply a log10-like curve to the value. - /// - /// The exact formula is: - /// - /// ``` - /// if (abs(sourceValue) < 1.0) - /// return sourceValue; - /// else - /// return (1 + Log10(abs(sourceValue))) * Sign(sourceValue); - /// end - /// ``` - /// - /// An input number - /// A Log10-like representation of the input value. - public double PseudoLog10(double sourceValue) - { - double absValue = Math.Abs(sourceValue); - if (absValue <= 1.0) - { - return sourceValue; - } - else - { - return (1.0f + Math.Log10(absValue)) * Math.Sign(sourceValue); - } - } - - [MASProxy(Dependent = true)] - /// - /// Round the given value towards zero (round down for positive values, - /// round up for negative values). - /// - /// - /// - public double RoundZero(double sourceValue) - { - if (sourceValue < 0.0) - { - return Math.Ceiling(sourceValue); - } - else - { - return Math.Floor(sourceValue); - } - } - - [MASProxy(Dependent = true)] - /// - /// Divides `numerator` by `denominator`. If the denominator is zero, this method - /// returns 0 instead of infinity or throwing a divide-by-zero exception. - /// - /// The numerator - /// The denominator - /// numerator / denominator, or 0 if the denominator is zero. - public double SafeDivide(double numerator, double denominator) - { - if (Math.Abs(denominator) > 0.0) - { - return numerator / denominator; - } - else - { - return 0.0; - } - } - - [MASProxy(Dependent = true)] - /// - /// Returns the remainder of `numerator` divided by `denominator`. If the denominator is zero, this method - /// returns 0 instead of infinity or throwing a divide-by-zero exception. - /// - /// The numerator - /// The denominator - /// A value between 0 and `denominator`, or 0 if the denominator is zero. - public double SafeModulo(double numerator, double denominator) - { - if (Math.Abs(denominator) > 0.0) - { - return numerator % denominator; - } - else - { - return 0.0; - } - } - - #endregion - - /// - /// Meta variables and functions are variables provide information about the - /// game, as opposed to the vessel. They also include the `fc.Conditioned()` - /// functions, which can provide some realism by disrupting lighting under - /// low power or high G situations. - /// - #region Meta - - [MASProxyAttribute(Dependent = true)] - /// - /// Checks for the existence of the named assembly (eg, `fc.AssemblyLoaded("MechJeb2")`). - /// This can be used to determine - /// if a particular mod has been installed when that mod is not directly supported by - /// Avionics Systems. - /// - /// 1 if the named assembly is loaded, 0 otherwise. - public double AssemblyLoaded(string assemblyName) - { - return MASLoader.knownAssemblies.Contains(assemblyName) ? 1.0 : 0.0; - } - - /// - /// Cancel time warp. - /// - /// If true, time warp is immediately set to x1. If false, time warp counts downward like in normal gameplay. - /// 1 if time warp was successfully adjusted, 0 if it could not be adjusted. - public double CancelTimeWarp(bool instantCancel) - { - if (TimeWarp.fetch != null) - { - TimeWarp.fetch.CancelAutoWarp(); - TimeWarp.SetRate(0, instantCancel); - - return 1.0; - } - else - { - return 0.0; - } - } - - [MASProxy(Dependent = true)] - /// - /// Returns the requested color channel for the `namedColor` specified. The parameter - /// `channel` may be 0 (for red), 1 (for green), 2 (for blue), or 3 (for alpha). - /// - /// Invalid channels or invalid named colors will result in a 255 being returned. - /// - /// The named color to look up. - /// The channel to return (0, 1, 2, or 3). - /// A value between [0, 255]. - public double ColorComponent(string namedColor, double channel) - { - int i_channel = (int)channel; - Color32 namedColorValue; - if (fc.TryGetNamedColor(namedColor, out namedColorValue) && i_channel >= 0 && i_channel < 4) - { - switch (i_channel) - { - case 0: - return namedColorValue.r; - case 1: - return namedColorValue.g; - case 2: - return namedColorValue.b; - case 3: - return namedColorValue.a; - } - } - - return 255.0; - } - - [MASProxy(Dependent = true)] - /// - /// Converts the supplied RGB value into a MAS text color tag - /// (eg, `fc.ColorTag(255, 255, 0)` returns "[#ffff00]"). - /// This value is slightly more efficient if you do not need to - /// change the alpha channel. - /// - /// All values are clamped to the range 0 to 255. - /// - /// Red channel value, [0, 255] - /// Green channel value, [0, 255] - /// Blue channel value, [0, 255] - /// The color tag that represents the requested color. - public string ColorTag(double red, double green, double blue) - { - int r = Mathf.Clamp((int)(red + 0.5), 0, 255); - int g = Mathf.Clamp((int)(green + 0.5), 0, 255); - int b = Mathf.Clamp((int)(blue + 0.5), 0, 255); - return string.Format("[#{0:X2}{1:X2}{2:X2}]", r, g, b); - } - - [MASProxy(Dependent = true)] - /// - /// Converts the supplied RGBA value into a MAS text color tag - /// (eg, `fc.ColorTag(255, 255, 0, 255)` returns "[#ffff00ff]"). - /// - /// All values are clamped to the range 0 to 255. - /// - /// Red channel value, [0, 255] - /// Green channel value, [0, 255] - /// Blue channel value, [0, 255] - /// Alpha channel value, [0, 255] - /// The color tag that represents the requested color. - public string ColorTag(double red, double green, double blue, double alpha) - { - int r = Mathf.Clamp((int)(red + 0.5), 0, 255); - int g = Mathf.Clamp((int)(green + 0.5), 0, 255); - int b = Mathf.Clamp((int)(blue + 0.5), 0, 255); - int a = Mathf.Clamp((int)(alpha + 0.5), 0, 255); - return string.Format("[#{0:X2}{1:X2}{2:X2}{3:X2}]", r, g, b, a); - } - - [MASProxy(Dependent = true)] - /// - /// Looks up the [Named Color](https://github.com/MOARdV/AvionicsSystems/wiki/Named-Colors) `namedColor`, and returns its value as a color tag. - /// For instance, `fc.ColorTag("COLOR_XKCD_KSPUNNAMEDCYAN")` will return "[#5fbdb9ff]". - /// - /// If an invalid color is selected, this function returns bright magenta "[#ff00ff]". - /// - /// The named color to look up. - /// The color tag that represents the named color. - public string ColorTag(string namedColor) - { - Color32 namedColorValue; - if (fc.TryGetNamedColor(namedColor, out namedColorValue)) - { - return string.Format("[#{0:X2}{1:X2}{2:X2}{3:X2}]", namedColorValue.r, namedColorValue.g, namedColorValue.b, namedColorValue.a); - } - else - { - return "[#ff0ff]"; - } - } - - /// - /// Applies some "realism" conditions to the variable to cause it to - /// return zero under the following conditions: - /// - /// 1) When there is no power available (the config-file-specified - /// power variable is below 0.0001), or - /// - /// 2) The craft is under high g-loading. G-loading limits are defined - /// in the per-pod config file. When these limits are exceeded, there - /// is a chance (also defined in the config file) of the variable being - /// interrupted. This chance increases as the g-forces exceed the - /// threshold using a square-root curve, or - /// - /// 3) The optional variable `powerOnVariable` in MASFlightComputer returns - /// 0 or less. - /// - /// The variable `fc.Conditioned(1)` behaves the same as the RasterPropMonitor - /// ASET Props custom variable `CUSTOM_ALCOR_POWEROFF`, with an inverted - /// value (`CUSTOM_ALCOR_POWEROFF` returns 1 to indicate "disrupt", but - /// `fc.Conditioned(1)` returns 0 instead). - /// - /// For boolean parameters, `true` is treated as 1, and `false` is treated - /// as 0. - /// - /// A numeric value or a boolean - /// `value` if the conditions above are not met. - public double Conditioned(double value) - { - if (fc.isPowered && UnityEngine.Random.value > fc.disruptionChance) - { - return value; - } - else - { - return 0.0; - } - } - public double Conditioned(bool value) - { - if (value && fc.isPowered && UnityEngine.Random.value > fc.disruptionChance) - { - return 1.0; - } - else - { - return 0.0; - } - } - - /// - /// Applies some "realism" conditions to the variable to cause it to - /// return 'defaultValue' under the following conditions: - /// - /// 1) When there is no power available (the config-file-specified - /// power variable is below 0.0001), or - /// - /// 2) The craft is under high g-loading. G-loading limits are defined - /// in the per-pod config file. When these limits are exceeded, there - /// is a chance (also defined in the config file) of the variable being - /// interrupted. This chance increases as the g-forces exceed the - /// threshold using a square-root curve, or - /// - /// 3) The optional variable `powerOnVariable` in MASFlightComputer returns - /// 0 or less. - /// - /// This variant of 'fc.Conditioned()' is intended to be used with ANIMATION or - /// other nodes where the 'off' position is not the same as the 0 position. If the - /// off position should be 0, use the single-parameter fc.Conditioned. - /// - /// A numeric value or a boolean - /// The value that is returned if the conditions described - /// in the summary are not met. - /// `value` if the conditions above are not met. - public double Conditioned(double value, double defaultValue) - { - if (fc.isPowered && UnityEngine.Random.value > fc.disruptionChance) - { - return value; - } - else - { - return defaultValue; - } - } - - /// - /// Returns the value of a debug 'register' in MAS. - /// - /// **NOTE:** The debug registers are used for development and debugging purposes. - /// Bugs filed against this method will be summarily dismissed. - /// - /// An integer between 0 and the length of the debug array. - /// Contents of the debug value, or an empty string. - public object DebugValue(double index) - { - int idx = (int)index; - if ((idx >= 0 && idx < vc.debugValue.Length) || vc.debugValue[idx] == null) - { - return vc.debugValue[idx]; - } - else - { - return string.Empty; - } - } - - /// - /// Returns the current Flight UI Mode. - /// - /// The Flight UI mode is the UI typically in the lower-left corner of the - /// screen during flight when not in IVA. It may be one of the following - /// values: - /// * 0 - Staging Mode - /// * 1 - Docking Mode - /// * 2 = Map Mode - /// * 3 = Maneuver Edit Mode - /// * 4 = Maneuver Info Mode - /// - /// A mode number as described above, or -1 if the mode cannot be queried. - public double FlightUIMode() - { - var ui = FlightUIModeController.Instance; - if (ui != null) - { - return (double)ui.Mode; - } - - return -1.0; - } - - [MASProxy(Dependent = true)] - /// - /// Applies `parameter` to `formatString`, returning the result. - /// - /// The `formatString` parameter uses the MAS custom delimeters <= and =>. - /// Only one variable may be substituted using `formatString`. - /// - /// *Ex 1:* `fc.FormatString("<=0:0.0=>", fc.GetAltitude())` - /// - /// *Ex 2:* `fc.FormatString(fc.Select(fc.GetPersistentAsNumber("Precision"), "<=0:0.0=>", "<=0:0.000=>"), fc.GetAltitude())` - /// - /// The C# format string, with at most one variable field (eg, <=0=>). - /// The argument to formatString - /// The formatted string, or an empty string if an invalid parameter was supplied. - public string FormatString(string format, object arg0) - { - string result = string.Empty; - try - { - result = string.Format(MdVTextMesh.formatter, format, arg0); - } - catch - { - result = string.Empty; - } - - return result; - } - - /// - /// Returns 1 if any of the throttle keys (full throttle, cut throttle, throttle up, throttle down) - /// were pressed on the keyboard since the last Fixed Update, or if any of those keys are still - /// being pressed. - /// - /// 1 if any throttle keys are being pressed, 0 otherwise. - public double GetThrottleKeyPressed() - { - return fc.anyThrottleKeysPressed ? 1.0 : 0.0; - } - - /// - /// Returns the number of hours per day on Kerbin. With a stock installation, this is 6 hours or - /// 24 hours, depending on whether the Kerbin calendar or Earth calendar is selected. Mods - /// may change this to a different value. - /// - /// 6 for Kerbin time, 24 for Earth time, or another value for modded installations. - public double HoursPerDay() - { - return KSPUtil.dateTimeFormatter.Day / 3600; - } - - /// - /// Returns 1 if the KSP UI is configured for the Kerbin calendar (6 hour days); - /// returns 0 for Earth days (24 hour). - /// - /// - public double KerbinTime() - { - return (GameSettings.KERBIN_TIME) ? 1.0 : 0.0; - } - - /// - /// Log messages to the KSP.log. Messages will be prefixed with - /// [MASFlightComputerProxy]. - /// - /// The string to write. Strings may be formatted using the Lua string library, or using the `..` concatenation operator. - public double LogMessage(string message) - { - Utility.LogMessage(this, message); - return 1.0; - } - - [MASProxy(Dependent = true)] - /// - /// Returns the U texture shift required to display the map icon listed below. - /// This function is intended to be used in conjunction with the '%MAP_ICON%' texture in - /// MASMonitor IMAGE nodes. - /// - /// * 0 - Invalid (not one of the below types) - /// * 1 - Ship target - /// * 2 - Plane target - /// * 3 - Probe target - /// * 4 - Lander target - /// * 5 - Station target - /// * 6 - Relay target - /// * 7 - Rover target - /// * 8 - Base target - /// * 9 - EVA target - /// * 10 - Flag target - /// * 11 - Debris target - /// * 12 - Space Object target - /// * 13 - Unknown target - /// * 14 - Celestial Body (planet) - /// * 15 - Ap Icon - /// * 16 - Pe Icon - /// * 17 - AN Icon - /// * 18 - DN Icon - /// * 19 - Maneuver Node Icon - /// * 20 - Ship location at intercept - /// * 21 - Target location at intercept - /// * 22 - Enter an SoI - /// * 23 - Exit an SoI - /// * 24 - Point of Impact (?) - /// - /// Note that entries 0 - 14 correspond to the results of `fc.TargetTypeId()`. - /// - /// The Icon Id - either `fc.TargetTypeId()` or one of the numbers listed in the description. - /// The U shift to select the icon from the '%MAP_ICON%' texture. - public double MapIconU(double iconId) - { - int index = (int)iconId; - switch (index) - { - case 0: - return 0.6; // WRONG - using Unknown Target - case 1: - return 0.0; - case 2: - return 0.8; - case 3: - return 0.2; - case 4: - return 0.6; - case 5: - return 0.6; - case 6: - return 0.8; - case 7: - return 0.0; - case 8: - return 0.4; - case 9: - return 0.4; - case 10: - return 0.8; - case 11: - return 0.2; - case 12: - return 0.8; - case 13: - return 0.6; - case 14: - return 0.4; - case 15: - return 0.2; - case 16: - return 0.0; - case 17: - return 0.4; - case 18: - return 0.6; - case 19: - return 0.4; - case 20: - return 0.0; - case 21: - return 0.2; - case 22: - return 0.0; - case 23: - return 0.2; - case 24: - return 0.8; - } - - return 0.0; - } - - [MASProxy(Dependent = true)] - /// - /// Returns the V texture shift required to display the map icon listed below. - /// This function is intended to be used in conjunction with the '%MAP_ICON%' texture in - /// MASMonitor IMAGE nodes. - /// - /// * 0 - Invalid (not one of the below types) - /// * 1 - Ship target - /// * 2 - Plane target - /// * 3 - Probe target - /// * 4 - Lander target - /// * 5 - Station target - /// * 6 - Relay target - /// * 7 - Rover target - /// * 8 - Base target - /// * 9 - EVA target - /// * 10 - Flag target - /// * 11 - Debris target - /// * 12 - Space Object target - /// * 13 - Unknown target - /// * 14 - Celestial Body (planet) - /// * 15 - Ap Icon - /// * 16 - Pe Icon - /// * 17 - AN Icon - /// * 18 - DN Icon - /// * 19 - Maneuver Node Icon - /// * 20 - Ship location at intercept - /// * 21 - Target location at intercept - /// * 22 - Enter an SoI - /// * 23 - Exit an SoI - /// * 24 - Point of Impact (?) - /// - /// Note that entries 0 - 14 correspond to the results of `fc.TargetTypeId()`. - /// - /// The Icon Id - either `fc.TargetTypeId()` or one of the numbers listed in the description. - /// The V shift to select the icon from the '%MAP_ICON%' texture. - public double MapIconV(double iconId) - { - int index = (int)iconId; - switch (index) - { - case 0: - return 0.6; // WRONG - Using Unknown Target - case 1: - return 0.6; - case 2: - return 0.8; - case 3: - return 0.0; - case 4: - return 0.0; - case 5: - return 0.2; - case 6: - return 0.6; - case 7: - return 0.0; - case 8: - return 0.0; - case 9: - return 0.4; - case 10: - return 0.0; - case 11: - return 0.6; - case 12: - return 0.2; - case 13: - return 0.6; - case 14: - return 0.6; - case 15: - return 0.8; - case 16: - return 0.8; - case 17: - return 0.8; - case 18: - return 0.8; - case 19: - return 0.2; - case 20: - return 0.2; - case 21: - return 0.2; - case 22: - return 0.4; - case 23: - return 0.4; - case 24: - return 0.4; - } - - return 0.0; - } - - [MASProxyAttribute(Immutable = true)] - /// - /// Returns the version number of the MAS plugin, as a string, - /// such as `1.0.1.12331`. - /// - /// MAS Version in string format. - public string MASVersion() - { - return MASLoader.masVersion; - } - - [MASProxy(Dependent = true)] - /// - /// Returns the B color value for the navball marker icon selected. - /// This function is intended to be used in conjunction with the '%NAVBALL_ICON%' texture in - /// MASMonitor IMAGE nodes. - /// - /// * 0 - Prograde - /// * 1 - Retrograde - /// * 2 - Radial Out - /// * 3 - Radial In - /// * 4 - Normal + - /// * 5 - Normal - (anti-normal) - /// * 6 - Maneuver Node - /// * 7 - Target + - /// * 8 - Target - (anti-target) - /// - /// If an invalid number is supplied, this function treats is as "Prograde". - /// - /// The icon as explained in the description. - /// The B value to use in `passiveColor` or `activeColor`. - public double NavballB(double iconId) - { - int index = (int)iconId; - switch (index) - { - case 1: - return 0.0; - case 2: - return 255.0; - case 3: - return 255.0; - case 4: - return 206.0; - case 5: - return 206.0; - case 6: - return 249.0; - case 7: - return 255.0; - case 8: - return 255.0; - } - return 0.0; - } - - [MASProxy(Dependent = true)] - /// - /// Returns the G color value for the navball marker icon selected. - /// This function is intended to be used in conjunction with the '%NAVBALL_ICON%' texture in - /// MASMonitor IMAGE nodes. - /// - /// * 0 - Prograde - /// * 1 - Retrograde - /// * 2 - Radial Out - /// * 3 - Radial In - /// * 4 - Normal + - /// * 5 - Normal - (anti-normal) - /// * 6 - Maneuver Node - /// * 7 - Target + - /// * 8 - Target - (anti-target) - /// - /// If an invalid number is supplied, this function treats is as "Prograde". - /// - /// The icon as explained in the description. - /// The G value to use in `passiveColor` or `activeColor`. - public double NavballG(double iconId) - { - int index = (int)iconId; - switch (index) - { - case 1: - return 203.0; - case 2: - return 155.0; - case 3: - return 155.0; - case 4: - return 0.0; - case 5: - return 0.0; - case 6: - return 102.0; - case 7: - return 0.0; - case 8: - return 0.0; - } - return 203.0; - } - - [MASProxy(Dependent = true)] - /// - /// Returns the R color value for the navball marker icon selected. - /// This function is intended to be used in conjunction with the '%NAVBALL_ICON%' texture in - /// MASMonitor IMAGE nodes. - /// - /// * 0 - Prograde - /// * 1 - Retrograde - /// * 2 - Radial Out - /// * 3 - Radial In - /// * 4 - Normal + - /// * 5 - Normal - (anti-normal) - /// * 6 - Maneuver Node - /// * 7 - Target + - /// * 8 - Target - (anti-target) - /// - /// If an invalid number is supplied, this function treats is as "Prograde". - /// - /// The icon as explained in the description. - /// The R value to use in `passiveColor` or `activeColor`. - public double NavballR(double iconId) - { - int index = (int)iconId; - switch (index) - { - case 1: - return 255.0; - case 2: - return 0.0; - case 3: - return 0.0; - case 4: - return 156.0; - case 5: - return 156.0; - case 6: - return 0.0; - case 7: - return 255.0; - case 8: - return 255.0; - } - return 255.0; - } - - [MASProxy(Dependent = true)] - /// - /// Returns the U texture shift to select the navball marker icon as listed below. - /// This function is intended to be used in conjunction with the '%NAVBALL_ICON%' texture in - /// MASMonitor IMAGE nodes. - /// - /// * 0 - Prograde - /// * 1 - Retrograde - /// * 2 - Radial Out - /// * 3 - Radial In - /// * 4 - Normal + - /// * 5 - Normal - (anti-normal) - /// * 6 - Maneuver Node - /// * 7 - Target + - /// * 8 - Target - (anti-target) - /// - /// If an invalid number is supplied, this function treats is as "Prograde". - /// - /// The icon as explained in the description. - /// The U value to use in `uvShift`. - public double NavballU(double iconId) - { - int index = (int)iconId; - switch (index) - { - case 1: - return (1.0 / 3.0); - case 2: - return (1.0 / 3.0); - case 3: - return 0.0; - case 4: - return 0.0; - case 5: - return (1.0 / 3.0); - case 6: - return (2.0 / 3.0); - case 7: - return (2.0 / 3.0); - case 8: - return (2.0 / 3.0); - } - return 0.0; - } - - [MASProxy(Dependent = true)] - /// - /// Returns the V texture shift to select the navball marker icon as listed below. - /// This function is intended to be used in conjunction with the '%NAVBALL_ICON%' texture in - /// MASMonitor IMAGE nodes. - /// - /// * 0 - Prograde - /// * 1 - Retrograde - /// * 2 - Radial Out - /// * 3 - Radial In - /// * 4 - Normal + - /// * 5 - Normal - (anti-normal) - /// * 6 - Maneuver Node - /// * 7 - Target + - /// * 8 - Target - (anti-target) - /// - /// If an invalid number is supplied, this function treats is as "Prograde". - /// - /// The icon as explained in the description. - /// The V value to use in `uvShift`. - public double NavballV(double iconId) - { - int index = (int)iconId; - switch (index) - { - case 1: - return (2.0 / 3.0); - case 2: - return (1.0 / 3.0); - case 3: - return (1.0 / 3.0); - case 4: - return 0.0; - case 5: - return 0.0; - case 6: - return 0.0; - case 7: - return (2.0 / 3.0); - case 8: - return (1.0 / 3.0); - } - return 2.0 / 3.0; - } - - /// - /// Play the audio file specified in `sound`, at the volume specified in `volume`. - /// If `stopCurrent` is true, any current sound clip is canceled first. If `stopCurrent` - /// is false, and an audio clip is currently playing, this call to PlayAudio does nothing. - /// - /// The name (URI) of the sound to play. - /// The volume to use for playback, between 0 and 1 (inclusive). - /// If 'true', stops any current audio clip being played. - /// Returns 1 if the audio was played, 0 if it was not found or otherwise not played. - public double PlayAudio(string sound, double volume, bool stopCurrent) - { - return fc.PlayAudio(sound, Mathf.Clamp01((float)volume), stopCurrent) ? 1.0 : 0.0; - } - - /// - /// Play the sequence of letters as a Morse Code sequence. Letters play automatically, - /// and a space ' ' inserts a pause in the sequence. All other characters are skipped. - /// - /// The sequence of letters to play as a Morse Code. - /// The volume to use for playback, between 0 and 1 (inclusive). - /// If 'true', stops any current audio clip being played. - /// - public double PlayMorseSequence(string sequence, double volume, bool stopCurrent) - { - return fc.PlayMorseSequence(sequence, Mathf.Clamp01((float)volume), stopCurrent); - } - - /// - /// Recover the vessel if it is recoverable. Has no effect if the craft can not be - /// recovered. - /// - /// 1 if the craft can be recovered (although it is also recovered immediately), 0 otherwise. - public double RecoverVessel() - { - if (vessel.IsRecoverable) - { - fc.StartCoroutine(RecoverVesselLater()); - return 1.0; - } - else - { - return 0.0; - } - } - - /// - /// Run the `startupScript` on every monitor and prop in the pod that has a defined `startupScript`. - /// - /// The number of scripts executed. - public double RunMonitorStartupScript() - { - double scriptCount = 0.0; - List props = fc.part.internalModel.props; - int numProps = props.Count; - for (int propIndex = 0; propIndex < numProps; ++propIndex) - { - List modules = props[propIndex].internalModules; - int numModules = modules.Count; - for (int moduleIndex = 0; moduleIndex < numModules; ++moduleIndex) - { - if (modules[moduleIndex] is MASMonitor) - { - if ((modules[moduleIndex] as MASMonitor).RunStartupScript(fc)) - { - scriptCount += 1.0; - } - } - else if (modules[moduleIndex] is MASComponent) - { - if ((modules[moduleIndex] as MASComponent).RunStartupScript(fc)) - { - scriptCount += 1.0; - } - } - } - } - - return scriptCount; - } - - /// - /// The ScrollingMarquee function takes a string, `input`, and it returns a substring - /// of maximum length `maxChars`. The substring that is returned changes every - /// `scrollRate` seconds if the string length is greater than `maxChars`, allowing - /// for a scrolling marquee effect. Using this method with the Repetition Scrolling - /// font can simulate an LED / LCD display. - /// - /// Note that characters advance one character width at a time - it is not a smooth - /// sliding movement. - /// - /// The string to use for the marquee. - /// The maximum number of characters in the string to display. - /// The frequency, in seconds, that the marquee advances. - /// A substring of no more than `maxChars` length. - public string ScrollingMarquee(string inputString, double maxChars, double scrollRate) - { - int maxCh = (int)maxChars; - int strlen = inputString.Length; - if (strlen <= maxCh) - { - return inputString; - } - else if (scrollRate <= 0.0) - { - return inputString.Substring(0, maxCh); - } - else - { - double adjustedTime = vc.universalTime / scrollRate; - double startD = adjustedTime % (double)(strlen + 1); - int start = (int)startD; - - if (start + maxCh <= strlen) - { - return inputString.Substring(start, maxCh); - } - else - { - int tail = maxCh - strlen + start - 1; - - StringBuilder sb = StringBuilderCache.Acquire(); - sb.Append(inputString.Substring(start)).Append(' ').Append(inputString.Substring(0, tail)); - - return sb.ToStringAndRelease(); - } - } - } - - /// - /// Attempt to set time warp rate to warpRate. Because KSP has specific - /// supported rates, the rate selected may not match the rate - /// requested. Warp rates are not changed when physics warp is enabled - /// (such as while flying in the atmosphere). - /// - /// The desired warp rate, such as '50'. - /// Should the rate be changed instantly? - /// The warp rate selected. This may not be the exact value requested. - public double SetWarp(double warpRate, bool instantChange) - { - if (TimeWarp.fetch != null) - { - if (warpRate <= 1.0) - { - TimeWarp.fetch.CancelAutoWarp(); - TimeWarp.SetRate(0, instantChange); - - return 1.0; - } - else if (warpRate != TimeWarp.CurrentRate && TimeWarp.WarpMode == TimeWarp.Modes.HIGH) - { - int rateIndex; - int maxRate = (vessel.situation == Vessel.Situations.LANDED || vessel.situation == Vessel.Situations.PRELAUNCH || vessel.situation == Vessel.Situations.SPLASHED) ? TimeWarp.fetch.warpRates.Length : TimeWarp.fetch.GetMaxRateForAltitude(vessel.altitude, vessel.mainBody); - - if (maxRate > 0) - { - - for (rateIndex = 0; rateIndex <= maxRate; ++rateIndex) - { - if (TimeWarp.fetch.warpRates[rateIndex] > warpRate) - { - break; - } - } - --rateIndex; - TimeWarp.SetRate(rateIndex, instantChange); - - return TimeWarp.fetch.warpRates[rateIndex]; - } - } - } - - return TimeWarp.CurrentRate; - } - - [MASProxy(Dependent = true)] - /// - /// Provides a custom SI formatter with more control than the basic SIP format. - /// - /// **NOT IMPLEMENTED:** Custom delimiter. - /// - /// The number to format. - /// The total length of the string. - /// The minimum decimal precision of the string. - /// A custom delimiter, or an empty string "" to indicate no delimiter - /// Require a space for the sign, even if the value is positive - /// Whether the SI prefix should be appended to the string. - /// The formatted string. - public string SIFormatValue(double value, double totalLength, double minDecimal, string delimiter, bool forceSign, bool showPrefix) - { - return DoSIFormat(value, (int)totalLength, (int)minDecimal, delimiter, forceSign, showPrefix); - } - - /// - /// Returns 1 if the vessel is recoverable, 0 otherwise. - /// - /// 1 if the craft can be recovered, 0 otherwise. - public double VesselRecoverable() - { - return (vessel.IsRecoverable) ? 1.0 : 0.0; - } - - /// - /// Warp to the specified universal time. If the time is in the past, or - /// the warp is otherwise not possible, nothing happens. - /// - /// The Universal Time to warp to. Must be in the future. - /// 1 if the warp to time is successfully set, 0 if it was not. - public double WarpTo(double UT) - { - if (TimeWarp.fetch != null && UT > Planetarium.GetUniversalTime()) - { - TimeWarp.fetch.CancelAutoWarp(); - TimeWarp.fetch.WarpTo(UT); - - return 1.0; - } - else - { - return 0.0; - } - } - #endregion - - /// - /// Information on the vessel's current orbit are available in this category. - /// - #region Orbit Parameters - /// - /// Returns the orbit's apoapsis (from datum) in meters. - /// - /// - public double Apoapsis() - { - return vc.apoapsis; - } - - /// - /// Returns the speed in m/s required to have a circular orbit at the provided altitude. - /// - /// Altitude in meters - /// The orbital speed in m/s needed for a circular orbit. - public double CircularOrbitSpeed(double altitude) - { - double GM = vc.mainBody.gravParameter; - double rA = altitude + vc.mainBody.Radius; - double Vi = Math.Sqrt(GM / rA); // Velocity of a circular orbit at radius A - - return Vi; - } - - /// - /// Return the eccentricity of the orbit. - /// - /// - public double Eccentricity() - { - return vc.orbit.eccentricity; - } - - /// - /// Return the vessel's orbital inclination. - /// - /// Inclination in degrees. - public double Inclination() - { - return vc.orbit.inclination; - } - - /// - /// Returns the name of the body that the vessel will be orbiting after the - /// next SoI change. If the craft is not changing SoI, returns an empty string. - /// - /// Name of the body, or an empty string if the orbit does not change SoI. - public string NextBodyName() - { - if (vesselSituationConverted > 2) - { - if (vc.orbit.patchEndTransition == Orbit.PatchTransitionType.ENCOUNTER) - { - return vessel.orbit.nextPatch.referenceBody.bodyName; - } - else if (vc.orbit.patchEndTransition == Orbit.PatchTransitionType.ESCAPE) - { - return vessel.mainBody.referenceBody.bodyName; - } - } - - return string.Empty; - } - - /// - /// Returns the time to the next SoI transition. If the current orbit does not change - /// SoI, returns 0. - /// - /// - public double TimeToNextSoI() - { - if (vessel.orbit.patchEndTransition == Orbit.PatchTransitionType.ENCOUNTER || - vessel.orbit.patchEndTransition == Orbit.PatchTransitionType.ESCAPE) - { - return vessel.orbit.UTsoi - Planetarium.GetUniversalTime(); - } - return 0.0; - } - - /// - /// Returns 1 if the next SoI change is an 'encounter', -1 if it is an - /// 'escape', and 0 if the orbit is not changing SoI. - /// - /// 0 if the orbit does not transition. 1 if the vessel will encounter a body, -1 if the vessel will escape the current body. - public double NextSoI() - { - if (vesselSituationConverted > 2) - { - if (vc.orbit.patchEndTransition == Orbit.PatchTransitionType.ENCOUNTER) - { - return 1.0; - } - else if (vc.orbit.patchEndTransition == Orbit.PatchTransitionType.ESCAPE) - { - return -1.0; - } - } - - return 0.0; - } - - /// - /// Returns the orbital period, in seconds. - /// - /// Orbital period, seconds. Zero if the craft is not in flight. - public double OrbitPeriod() - { - return (vesselSituationConverted > 2) ? vc.orbit.period : 0.0; - } - - /// - /// Returns the orbits periapsis (from datum) in meters. - /// - /// - public double Periapsis() - { - return vc.periapsis; - } - - /// - /// Returns the semi-major axis of the current orbit. When the SMA - /// matches a body's synchronous orbit SMA, the vessel is in a synchronous orbit. - /// - /// SMA in meters. - public double SemiMajorAxis() - { - return vc.orbit.semiMajorAxis; - } - #endregion - - /// - /// Variables related to the vessel's orientation in space, relative to a target, - /// or relative to the surface, are here. - /// - #region Orientation - - /// - /// Returns the angle of attack of the vessel. If FAR is installed, - /// FAR's results are used. - /// - /// The angle of attack, in degrees - public double AngleOfAttack() - { - if (MASIFAR.farFound) - { - return farProxy.AngleOfAttack(); - } - else - { - return vc.GetRelativePitch(vc.surfacePrograde) * (Math.Min(2.0, vessel.srfSpeed) * 0.5); - } - } - - /// - /// Returns the angle between the prograde vector of the orbit and the horizon when - /// the vessel crosses the specified altitude. If an invalid altitude is specified, - /// returns 0. - /// - /// This computation is based on unpowered flight, and it does not account for any effects - /// caused by atmospheric lift. It also does not account for surface-relative motion, so it - /// is not a good indicator for impact angle. - /// - /// The altitude above datum / sea level, in meters, at which the angle will be computed. - /// The flight path angle, in degrees, or 0. - public double FlightPathAngle(double altitude) - { - if (vc.orbit.ApA >= altitude && vc.orbit.PeA <= altitude && vc.orbit.eccentricity < 1.0) - { - double timeAtRadius = Utility.NextTimeToRadius(vc.orbit, altitude + vc.orbit.referenceBody.Radius); - - if (timeAtRadius > 0.0) - { - Vector3d position, vel; - double whatsThis = vc.orbit.GetOrbitalStateVectorsAtUT(timeAtRadius + vc.universalTime, out position, out vel); - - return 90.0 - Vector3d.Angle(position, vel); - } - } - - return 0.0; - } - - /// - /// Return heading relative to the surface in degrees [0, 360) - /// - /// - public double Heading() - { - return vc.heading; - } - - /// - /// Return the heading of the surface velocity vector relative to the surface in degrees [0, 360) - /// - /// - public double HeadingPrograde() - { - return vc.progradeHeading; - } - - /// - /// Return the instantaneous rate of change of the vessel heading in degrees per second. - /// - /// Note that extreme rates may be misreported. - /// - /// A value in the range of [-180, 180). - public double HeadingRate() - { - return vc.headingRate; - } - - /// - /// Return pitch relative to the surface [-90, 90] - /// - /// - public double Pitch() - { - return vc.pitch; - } - - /// - /// Pitch of the vessel relative to the current SAS prograde mode (orbit, surface, or target). - /// - /// - public double PitchActivePrograde() - { - var mode = FlightGlobals.speedDisplayMode; - - if (mode == FlightGlobals.SpeedDisplayModes.Orbit) - { - return vc.GetRelativePitch(vc.prograde); - } - else if (mode == FlightGlobals.SpeedDisplayModes.Target) - { - return vc.GetRelativePitch(vc.targetRelativeVelocity.normalized); - } - else - { - return vc.GetRelativePitch(vc.surfacePrograde); - } - } - - /// - /// Pitch of the vessel relative to the orbit anti-normal vector. - /// - /// - public double PitchAntiNormal() - { - return vc.GetRelativePitch(-vc.normal); - } - - /// - /// Pitch of the vessel relative to the vector pointing away from the target. - /// - /// - public double PitchAntiTarget() - { - if (vc.activeTarget == null) - { - return 0.0; - } - else - { - return vc.GetRelativePitch(-vc.targetDirection); - } - } - - /// - /// Returns the pitch component of the angle between a target docking - /// port and a reference (on Vessel) docking port; 0 if the target is - /// not a docking port or if the reference transform is not a docking - /// port. - /// - /// - public double PitchDockingAlignment() - { - if (vc.targetType == MASVesselComputer.TargetType.DockingPort && vc.targetDockingTransform != null) - { - Vector3 projectedVector = Vector3.ProjectOnPlane(-vc.targetDockingTransform.forward, vc.referenceTransform.right); - projectedVector.Normalize(); - - // Dot the projected vector with the 'top' direction so we can find - // the relative pitch. - float dotPitch = Vector3.Dot(projectedVector, vc.referenceTransform.forward); - float pitch = Mathf.Asin(dotPitch); - if (float.IsNaN(pitch)) - { - pitch = (dotPitch > 0.0f) ? 90.0f : -90.0f; - } - else - { - pitch *= Mathf.Rad2Deg; - } - - return pitch; - } - - return 0.0; - } - - /// - /// Pitch of the vessel relative to the next scheduled maneuver vector. - /// - /// - public double PitchManeuver() - { - if (vc.maneuverNodeValid) - { - return vc.GetRelativePitch(vc.maneuverNodeVector.normalized); - } - else - { - return 0.0; - } - } - - /// - /// Pitch of the vessel relative to the orbit normal vector. - /// - /// - public double PitchNormal() - { - return vc.GetRelativePitch(vc.normal); - } - - /// - /// Returns the pitch rate of the vessel in degrees/sec - /// - /// - public double PitchRate() - { - return -vessel.angularVelocity.x * Mathf.Rad2Deg; - } - - /// - /// Pitch of the vessel relative to the orbital prograde vector. - /// - /// - public double PitchPrograde() - { - return vc.GetRelativePitch(vc.prograde); - } - - /// - /// Pitch of the vessel relative to the orbital Radial In vector. - /// - /// - public double PitchRadialIn() - { - return vc.GetRelativePitch(-vc.radialOut); - } - - /// - /// Pitch of the vessel relative to the orbital Radial Out vector. - /// - /// - public double PitchRadialOut() - { - return vc.GetRelativePitch(vc.radialOut); - } - - /// - /// Pitch of the vessel relative to the orbital retrograde vector. - /// - /// - public double PitchRetrograde() - { - return vc.GetRelativePitch(-vc.prograde); - } - - /// - /// Pitch of the vessel relative to the current active SAS mode. If SAS - /// is not enabled, or the current mode is Stability Assist, returns 0. - /// - /// Pitch in the range [+90, -90] - public double PitchSAS() - { - double relativePitch = 0.0; - if (vessel.ActionGroups[KSPActionGroup.SAS] && vessel.Autopilot != null && vessel.Autopilot.SAS != null && autopilotMode != VesselAutopilot.AutopilotMode.StabilityAssist) - { - relativePitch = vc.GetRelativePitch(vessel.Autopilot.SAS.targetOrientation); - } - - return relativePitch; - } - - /// - /// Pitch of the vessel relative to the surface prograde vector. - /// - /// - public double PitchSurfacePrograde() - { - return vc.GetRelativePitch(vc.surfacePrograde); - } - - /// - /// Pitch of the vessel relative to the surface retrograde vector. - /// - /// - public double PitchSurfaceRetrograde() - { - return vc.GetRelativePitch(-vc.surfacePrograde); - } - - /// - /// Pitch of the vessel relative to the vector pointing at the target. - /// - /// - public double PitchTarget() - { - if (vc.activeTarget == null) - { - return 0.0; - } - else - { - return vc.GetRelativePitch(vc.targetDirection); - } - } - - /// - /// Pitch of the vessel relative to the target relative prograde vector. - /// - /// - public double PitchTargetPrograde() - { - if (vc.activeTarget == null) - { - return 0.0; - } - else - { - return vc.GetRelativePitch(vc.targetRelativeVelocity.normalized); - } - } - - /// - /// Pitch of the vessel relative to the target relative retrograde vector. - /// - /// - public double PitchTargetRetrograde() - { - if (vc.activeTarget == null) - { - return 0.0; - } - else - { - return vc.GetRelativePitch(-vc.targetRelativeVelocity.normalized); - } - } - - /// - /// Pitch of the vessel relative to the active waypoint. 0 if no active waypoint. - /// - /// - public double PitchWaypoint() - { - if (NavWaypoint.fetch.IsActive) - { - Vector3d srfPos = vessel.mainBody.GetWorldSurfacePosition(NavWaypoint.fetch.Latitude, NavWaypoint.fetch.Longitude, NavWaypoint.fetch.Altitude); - - return vc.GetRelativePitch(srfPos.normalized); - } - return 0.0; - } - - /// - /// Returns a number identifying what the current reference transform is: - /// * 1: The current IVA pod (if in IVA) - /// * 2: A command pod or probe control part. - /// * 3: A docking port - /// * 4: A Grapple Node (Claw) - /// * 0: Unknown. - /// - /// A number as specified in the summary. - public double ReferenceTransformType() - { - switch (vc.referenceTransformType) - { - case MASVesselComputer.ReferenceType.Unknown: - return 0.0; - case MASVesselComputer.ReferenceType.Self: - return 1.0; - case MASVesselComputer.ReferenceType.RemoteCommand: - return 2.0; - case MASVesselComputer.ReferenceType.DockingPort: - return 3.0; - case MASVesselComputer.ReferenceType.Claw: - return 4.0; - default: - return 0.0; - } - } - - /// - /// Returns roll relative to the surface, in degrees. [-180, 180] - /// - /// - public double Roll() - { - return vc.roll; - } - - /// - /// Returns the roll angle in degrees between the vessel's reference transform and a targeted docking port. - /// If the target is not a docking port, returns 0. Some docks have alignment requirements. To - /// determine whether these docks are suitably aligned for docking, use `fc.TargetDockError()`. - /// - /// The relative roll between the vessel and the currently-targeted docking port, or 0. - public double RollDockingAlignment() - { - if (vc.targetType == MASVesselComputer.TargetType.DockingPort && vc.targetDockingTransform != null) - { - Vector3 projectedVector = Vector3.ProjectOnPlane(-vc.targetDockingTransform.up, vc.referenceTransform.up); - projectedVector.Normalize(); - - float dotLateral = Vector3.Dot(projectedVector, vc.referenceTransform.right); - float dotLongitudinal = Vector3.Dot(projectedVector, vc.referenceTransform.forward); - - // Taking arc tangent of x/y lets us treat the front of the vessel - // as the 0 degree location. - float roll = Mathf.Atan2(dotLateral, dotLongitudinal); - roll *= Mathf.Rad2Deg; - - return roll; - } - - return 0.0; - } - - /// - /// Returns the roll rate of the vessel in degrees/sec - /// - /// - public double RollRate() - { - return -vessel.angularVelocity.y * Mathf.Rad2Deg; - } - - /// - /// Returns the vessel's current sideslip. If FAR is installed, - /// it will use FAR's computation of sideslip. - /// - /// Sideslip in degrees. - public double Sideslip() - { - if (MASIFAR.farFound) - { - return farProxy.Sideslip(); - } - else - { - return vc.GetRelativeYaw(vc.surfacePrograde) * (Math.Min(2.0, vessel.srfSpeed) * 0.5); - } - } - - /// - /// Returns the slope of the terrain directly below the vessel. If the vessel's altitude - /// is too high to read the slope, returns 0. - /// - /// Slope of the terrain below the vessel, or 0 if the slope cannot be read. - public double SlopeAngle() - { - return vc.GetSlopeAngle(); - } - - /// - /// Yaw of the vessel relative to the current SAS prograde mode (orbit, surface, or target). - /// - /// - public double YawActivePrograde() - { - var mode = FlightGlobals.speedDisplayMode; - - if (mode == FlightGlobals.SpeedDisplayModes.Orbit) - { - return vc.GetRelativeYaw(vc.prograde); - } - else if (mode == FlightGlobals.SpeedDisplayModes.Target) - { - return vc.GetRelativeYaw(vc.targetRelativeVelocity.normalized); - } - else - { - return vc.GetRelativeYaw(vc.surfacePrograde); - } - } - - /// - /// Yaw of the vessel relative to the orbit's anti-normal vector. - /// - /// - public double YawAntiNormal() - { - return vc.GetRelativeYaw(-vc.normal); - } - - /// - /// Yaw of the vessel relative to the vector pointing away from the target. - /// - /// - public double YawAntiTarget() - { - if (vc.activeTarget == null) - { - return 0.0; - } - else - { - return vc.GetRelativeYaw(vc.targetDirection); - } - } - - /// - /// Returns the yaw angle between the vessel's reference transform and a targeted docking port. - /// If the target is not a docking port, returns 0; - /// - /// - public double YawDockingAlignment() - { - if (vc.targetType == MASVesselComputer.TargetType.DockingPort && vc.targetDockingTransform != null) - { - Vector3 projectedVector = Vector3.ProjectOnPlane(-vc.targetDockingTransform.forward, vc.referenceTransform.forward); - projectedVector.Normalize(); - - // Determine the lateral displacement by dotting the vector with - // the 'right' vector... - float dotLateral = Vector3.Dot(projectedVector, vc.referenceTransform.right); - // And the forward/back displacement by dotting with the forward vector. - float dotLongitudinal = Vector3.Dot(projectedVector, vc.referenceTransform.up); - - // Taking arc tangent of x/y lets us treat the front of the vessel - // as the 0 degree location. - float yaw = Mathf.Atan2(dotLateral, dotLongitudinal); - yaw *= Mathf.Rad2Deg; - - return yaw; - } - - return 0.0; - } - - /// - /// Yaw of the vessel relative to the next scheduled maneuver vector. - /// - /// - public double YawManeuver() - { - if (vc.maneuverNodeValid) - { - return vc.GetRelativeYaw(vc.maneuverNodeVector.normalized); - } - else - { - return 0.0; - } - } - - /// - /// Yaw of the vessel relative to the orbit's normal vector. - /// - /// - public double YawNormal() - { - return vc.GetRelativeYaw(vc.normal); - } - - /// - /// Returns the yaw rate of the vessel in degrees/sec - /// - /// - public double YawRate() - { - return -vessel.angularVelocity.z * Mathf.Rad2Deg; - } - - /// - /// Yaw of the vessel relative to the orbital prograde vector. - /// - /// - public double YawPrograde() - { - return vc.GetRelativeYaw(vc.prograde); - } - - /// - /// Yaw of the vessel relative to the radial in vector. - /// - /// - public double YawRadialIn() - { - return vc.GetRelativeYaw(-vc.radialOut); - } - - /// - /// Yaw of the vessel relative to the radial out vector. - /// - /// - public double YawRadialOut() - { - return vc.GetRelativeYaw(vc.radialOut); - } - - /// - /// Yaw of the vessel relative to the orbital retrograde vector. - /// - /// - public double YawRetrograde() - { - return vc.GetRelativeYaw(-vc.prograde); - } - - /// - /// Yaw of the vessel relative to the current active SAS mode. If SAS - /// is not enabled, or the current mode is Stability Assist, returns 0. - /// - /// Yaw in the range [+180, -180] - public double YawSAS() - { - double relativeYaw = 0.0; - if (vessel.ActionGroups[KSPActionGroup.SAS] && vessel.Autopilot != null && vessel.Autopilot.SAS != null && autopilotMode != VesselAutopilot.AutopilotMode.StabilityAssist) - { - relativeYaw = vc.GetRelativeYaw(vessel.Autopilot.SAS.targetOrientation); - } - - return relativeYaw; - } - - /// - /// Yaw of the vessel relative to the surface prograde vector. - /// - /// - public double YawSurfacePrograde() - { - return vc.GetRelativeYaw(vc.surfacePrograde); - } - - /// - /// Yaw of the vessel relative to the surface retrograde vector. - /// - /// - public double YawSurfaceRetrograde() - { - return vc.GetRelativeYaw(-vc.surfacePrograde); - } - - /// - /// Yaw of the vessel relative to the vector pointing at the target. - /// - /// - public double YawTarget() - { - if (vc.activeTarget == null) - { - return 0.0; - } - else - { - return vc.GetRelativeYaw(vc.targetDirection); - } - } - - /// - /// Yaw of the vessel relative to the target relative prograde vector. - /// - /// - public double YawTargetPrograde() - { - if (vc.activeTarget == null) - { - return 0.0; - } - else - { - return vc.GetRelativeYaw(vc.targetRelativeVelocity.normalized); - } - } - - /// - /// Yaw of the vessel relative to the target relative retrograde vector. - /// - /// - public double YawTargetRetrograde() - { - if (vc.activeTarget == null) - { - return 0.0; - } - else - { - return vc.GetRelativeYaw(-vc.targetRelativeVelocity.normalized); - } - } - - /// - /// Yaw of the vessel relative to the active waypoint. 0 if no active waypoint. - /// - /// - public double YawWaypoint() - { - if (NavWaypoint.fetch.IsActive) - { - Vector3d srfPos = vessel.mainBody.GetWorldSurfacePosition(NavWaypoint.fetch.Latitude, NavWaypoint.fetch.Longitude, NavWaypoint.fetch.Altitude); - - return vc.GetRelativeYaw(srfPos.normalized); - } - return 0.0; - } - #endregion - - /// - /// Periodic variables change value over time, based on a requested - /// frequency. - /// - #region Periodic Variables - - /// - /// Returns a periodic variable that counts upwards from 0 to 'countTo'-1 before repeating, - /// with each change based on the 'period'. Note that the counter is not guaranteed to start - /// at zero, since it is based on universal time. - /// - /// The period required to increase the counter, in cycles/second (Hertz). - /// The exclusive upper limit of the count. - /// An integer between [0 and countTo). - public double PeriodCount(double period, double countTo) - { - if (period > 0.0 && countTo >= 2.0) - { - double invPeriod = Math.Floor(countTo) / period; - - double remainder = period * (vc.universalTime % invPeriod); - - return Math.Floor(remainder); - } - - return 0.0; - } - - /// - /// Returns a random number in the range [0, 1] that updates based on `period`. - /// - /// Note that all props using the same `period` will see the same value from this function. - /// - /// The period between updates, in cycles/second (Hertz). - /// A number between 0 and 1, inclusive. 0 if period is not a positive value. - public double PeriodRandom(double period) - { - if (period > 0.0) - { - return fc.PeriodRandom((float)(1.0 / period)); - } - - return 0.0; - } - - /// - /// Returns a periodic variable that follows a sine-wave curve. - /// - /// The period of the change, in cycles/second (Hertz). - /// A number between -1 and +1. - public double PeriodSine(double period) - { - if (period > 0.0) - { - double invPeriod = 1.0 / period; - - double remainder = vc.universalTime % invPeriod; - - return Math.Sin(remainder * period * Math.PI * 2.0); - } - - return 0.0; - } - - /// - /// Returns a stair-step periodic variable (changes from 0 to 1 to 0 with - /// no ramps between values). - /// - /// The period of the change, in cycles/second (Hertz). - /// 0 or 1 - public double PeriodStep(double period) - { - if (period > 0.0) - { - double invPeriod = 1.0 / period; - - double remainder = vc.universalTime % invPeriod; - - return (remainder > invPeriod * 0.5) ? 1.0 : 0.0; - } - - return 0.0; - } - #endregion - - /// - /// Persistent variables are the primary means of data storage in Avionics Systems. - /// As such, there are many ways to set, alter, or query these variables. - /// - /// Persistent variables may be numbers or strings. Several of the setter and - /// getter functions in this category will convert the variable automatically - /// from one to the other (whenever possible), but it is the responsibility - /// of the prop config maker to make sure that text and numbers are not - /// intermingled when a specific persistent variable will be used as a number. - /// - #region Persistent Vars - /// - /// This method adds an amount to the named persistent. If the variable - /// did not already exist, it is created and initialized to 0 before - /// adding `amount`. If the variable was a string, it is converted to - /// a number before adding `amount`. - /// - /// If the variable cannot converted to a number, the variable's name is - /// returned, instead. - /// - /// The name of the persistent variable to change. - /// The amount to add to the persistent variable. - /// The new value of the persistent variable, or the name of the variable if it could not be converted to a number. - public object AddPersistent(string persistentName, double amount) - { - return fc.AddPersistent(persistentName, amount); - } - - /// - /// This method adds an amount to the named persistent. The result - /// is clamped to the range [minValue, maxValue]. - /// - /// If the variable - /// did not already exist, it is created and initialized to 0 before - /// adding `amount`. If the variable was a string, it is converted to - /// a number before adding `amount`. - /// - /// If the variable cannot converted to a number, the variable's name is - /// returned, instead. - /// - /// The name of the persistent variable to change. - /// The amount to add to the persistent variable. - /// The minimum value of the variable. If adding `amount` to the variable - /// causes it to be less than this value, the variable is set to this value, instead. - /// The maximum value of the variable. If adding `amount` to the variable - /// causes it to be greater than this value, the variable is set to this value, instead. - /// The new value of the persistent variable, or the name of the variable if it could not be - /// converted to a number. - public object AddPersistentClamped(string persistentName, double amount, double minValue, double maxValue) - { - return fc.AddPersistentClamped(persistentName, amount, minValue, maxValue); - } - - /// - /// This method adds an amount to the named persistent. The result - /// wraps around the range [minValue, maxValue]. This feature is used, - /// for instance, for - /// adjusting a heading between 0 and 360 degrees without having to go - /// from 359 all the way back to 0. `maxValue` is treated as an alias - /// for `minValue`, so if adding to a persistent value makes it equal - /// exactly `maxValue`, it is set to `minValue` instead. With the heading - /// example above, for instance, you would use `fc.AddPersistentWrapped("SomeVariableName", 1, 0, 360)`. - /// - /// To make a counter that runs from 0 to 2 before wrapping back to 0 - /// again, `fc.AddPersistentWrapped("SomeVariableName", 1, 0, 3)`. - /// - /// If the variable - /// did not already exist, it is created and initialized to 0 before - /// adding `amount`. If the variable was a string, it is converted to - /// a number before adding `amount`. - /// - /// If the variable cannot converted to a number, the variable's name is - /// returned, instead. - /// - /// If minValue and maxValue are the same, `amount` is treated as zero (nothing is added). - /// - /// The name of the persistent variable to change. - /// The amount to add to the persistent variable. - /// The minimum value of the variable. If adding `amount` would make the - /// variable less than `minValue`, MAS sets the variable to `maxValue` minus the - /// difference. - /// The maximum value of the variable. If adding `amount` would make the - /// variable greather than `maxValue`, MAS sets the variable to `minValue` plus the overage. - /// The new value of the persistent variable, or the name of the variable if it could not be - /// converted to a number. - public object AddPersistentWrapped(string persistentName, double amount, double minValue, double maxValue) - { - return fc.AddPersistentWrapped(persistentName, amount, minValue, maxValue); - } - - /// - /// Append the string `addon` to the persistent variable `persistentName`, but - /// only up to the specified maximum length. If the persistent does not exist, - /// it is created and initialized to `addon`. If the persistent is a numeric value, - /// it is converted to a string, and then `addon` is added. - /// - /// The name of the persistent variable to change. - /// The amount to add to the persistent variable. - /// The maximum number of characters allowed in the - /// string. Characters in excess of this amount are not added to the persistent. - /// The new string. - public object AppendPersistent(string persistentName, string addon, double maxLength) - { - return fc.AppendPersistent(persistentName, addon, (int)maxLength); - } - - /// - /// Treat the persistent value `persistentName` as a number. The integer `digit` is - /// appended to the end of the number, but only if the resulting string length remains - /// less than or equal to `maxLength`. - /// - /// This behiavor mimics the effect of typing a number on a calculator or other numeric - /// input. `digit` must be an integer between 0 and 9 (inclusive), or this function - /// has no effect. - /// - /// This method does not support adding a decimal point. The conventional `fc.AppendPersistent()` - /// must be used for that capability. - /// - /// The name of the persistent variable to change. - /// An integer in the range [0, 9]. - /// The maximum number of digits that `persistentName` may contain. - /// The new value. If the number was not updated, the old value is returned. - public double AppendPersistentDigit(string persistentName, double digit, double maxLength) - { - int dig_i = (int)digit; - int maxL = (int)maxLength; - if (dig_i >= 0 && dig_i <= 9) - { - return fc.AppendPersistentNumeric(persistentName, dig_i, maxL); - } - else - { - return fc.GetPersistentAsNumber(persistentName); - } - } - - /// - /// Clears 0 or more bits in the number stored in `persistentName`. - /// - /// The value in `persistentName` is converted to a 32 bit integer, - /// as is `bits`. `bits` is converted to its bitwise negation, and - /// A bit-wise AND is applied, and the resulting value - /// is stored in `persistentName`. - /// - /// If `persistentName` does not exist yet, or it is a string that cannot - /// be converted to an integer, it is set to 0 before the AND. - /// - /// The name of the persistent variable to change. - /// An integer representing the bits to clear. - /// Result of the bit-wise AND. - public double ClearBits(string persistentName, double bits) - { - return fc.ClearBits(persistentName, (int)bits); - } - - [MASProxy(Persistent = true)] - /// - /// Return value of the persistent. Strings are returned as strings, - /// numbers are returned as numbers. If the persistent does not exist - /// yet, the name is returned. - /// - /// The name of the persistent variable to query. - /// The value of the persistent, or its name if it does not exist. - public object GetPersistent(string persistentName) - { - return fc.GetPersistent(persistentName); - } - - [MASProxy(Persistent = true)] - /// - /// Return the value of the persistent as a number. If the persistent - /// does not exist yet, or it is a string that can not be converted to - /// a number, return 0. - /// - /// The name of the persistent variable to query. - /// The numeric value of the persistent, or 0 if it either does not - /// exist, or it cannot be converted to a number. - public double GetPersistentAsNumber(string persistentName) - { - return fc.GetPersistentAsNumber(persistentName); - } - - /// - /// Returns 1 if the named persistent variable has been initialized. Returns 0 - /// if the variable does not exist yet. - /// - /// The persistent variable name to check. - /// 1 if the variable contains initialized data, 0 if it does not. - public double GetPersistentExists(string persistentName) - { - return fc.GetPersistentExists(persistentName) ? 1.0 : 0.0; - } - - /// - /// Set the persistent value in `persistentName` to `value`, but - /// only if the persistent value does not already exist. If the - /// persistent already exists, this function does nothing. - /// - /// This function can be used to replace a script construct like - /// - /// ```Lua - /// if GetPersistentExists("MyPersistent") == 0 then - /// SetPersistent("MyPersistent", 42) - /// endif - /// ``` - /// - /// with a single initialization call. - /// - /// The name of the persistent variable to initialize. - /// The new number or text string to use for this persistent. - /// 1 if the persistent value was created, 0 if it already exists. - public double InitializePersistent(string persistentName, object value) - { - return fc.InitializePersistent(persistentName, value); - } - - /// - /// Sets 0 or more bits in the number stored in `persistentName`. - /// - /// The value in `persistentName` is converted to a 32 bit integer, - /// as is `bits`. A bit-wise OR is applied, and the resulting value - /// is stored in `persistentName`. - /// - /// If `persistentName` does not exist yet, or it is a string that cannot - /// be converted to an integer, it is set to 0 before the OR. - /// - /// The name of the persistent variable to change. - /// An integer representing the bits to set. - /// Result of the bit-wise OR. - public double SetBits(string persistentName, double bits) - { - return fc.SetBits(persistentName, (int)bits); - } - - /// - /// Set a persistent to `value`. `value` may be either a string or - /// a number. The existing value of the persistent is replaced. - /// - /// The name of the persistent variable to change. - /// The new number or text string to use for this persistent. - /// `value` - public object SetPersistent(string persistentName, object value) - { - return fc.SetPersistent(persistentName, value); - } - - /// - /// Set a persistent to `value`, but allow the persistent to change by at most `maxChangePerSecond` - /// from its initial value. - /// - /// If the persistent did not already exist, or if it was a string that could not be converted to - /// a number, it is set to value immediately. - /// - /// For other cases, the persistent's value is updated by adding or subtracting the minimum of - /// (maxChangePerSecond * timestep) or Abs(old value - value). - /// - /// While this method is a "setter" method, it is best applied where its results are displayed, - /// such as a number on an MFD, or controlling the animation of a prop. This will ensure that - /// the number continually updates, whereas using this method in a collider action will cause it - /// to only update the value when the collider is hit. - /// - /// The name of the persistent variable to change. - /// The new value for this variable. - /// The maximum amount the existing variable may change per second. - /// The resulting value. - public double SetPersistentBlended(string persistentName, double value, double maxChangePerSecond) - { - return fc.SetPersistentBlended(persistentName, value, maxChangePerSecond); - } - - /// - /// Toggle a persistent between 0 and 1. - /// - /// If the persistent is a number, it becomes 0 if it was already a - /// positive number, and it becomes 1 if it was previously <= 0. - /// - /// If the persistent is a string, it is converted to a number, and - /// the same rule is applied. - /// - /// If the persistent did not previously exist, or it is a string that - /// cannot be converted to a number, it is treated as if it - /// were zero. - /// - /// The name of the persistent variable to change. - /// 0 or 1. - public double TogglePersistent(string persistentName) - { - return fc.TogglePersistent(persistentName); - } - #endregion - - /// - /// The Position category provides information about the vessel's position - /// relative to a body (latitude and longitude) as well as landing predictions - /// and the like. - /// - /// The landing predictions will use Kerbal Engineer Redux if that mod is installed. - /// If KER is not available, MAS will fall back to using the MechJeb Landing - /// Predictions module if it is enabled. - /// Otherwise, MAS uses a very rudimentary predictor that assumes the planet - /// has no atmosphere. Because of these - /// limitations, the results will change during atmospheric braking, and they - /// may be wildly inaccurate in mountainous terrain. - /// - #region Position - /// - /// Returns the predicted altitude of the landing position. - /// - /// See the [category description](#description) for limitations on this function. - /// - /// MechJeb, Kerbal Engineer Redux - /// Predicted altitude (meters ASL) of the point of landing. - public double LandingAltitude() - { - if (MASIKerbalEngineer.keFound) - { - return keProxy.LandingAltitude(); - } - else if (mjProxy.LandingComputerActive() > 0.0) - { - return mjProxy.LandingAltitude(); - } - else - { - return vc.landingAltitude; - } - } - - /// - /// Returns the predicted latitude of the landing position. - /// - /// See the [category description](#description) for limitations on this function. - /// - /// MechJeb, Kerbal Engineer Redux - /// Latitude of estimated landing point, or 0 if the orbit does not lithobrake. - public double LandingLatitude() - { - if (MASIKerbalEngineer.keFound) - { - return keProxy.LandingLatitude(); - } - else if (mjProxy.LandingComputerActive() > 0.0 && mjProxy.LandingLatitude() != 0.0) - { - return mjProxy.LandingLatitude(); - } - else - { - return vc.landingLatitude; - } - } - - /// - /// Returns the predicted longitude of the landing position. - /// - /// See the [category description](#description) for limitations on this function. - /// - /// MechJeb, Kerbal Engineer Redux - /// Longitude of estimated landing point, or 0 if the orbit does not lithobrake. - public double LandingLongitude() - { - if (MASIKerbalEngineer.keFound) - { - return keProxy.LandingLongitude(); - } - else if (mjProxy.LandingComputerActive() > 0.0 && mjProxy.LandingLongitude() != 0.0) - { - return mjProxy.LandingLongitude(); - } - else - { - return vc.landingLongitude; - } - } - - /// - /// Returns an estimate of the speed of the vessel at landing, based on current speed - /// and current thrust. - /// - /// This is purely an estimate, and it does not account for atmospheric drag or maximum - /// available thrust. - /// - /// If the vessel will not lithobrake, or if the current thrust is adequate, the speed - /// estimate is 0. - /// - /// Impact speed in m/s, or 0 if the vessel will not intersect the planet. - public double LandingSpeed() - { - double landingTime = LandingTime(); - if (landingTime > 0.0) - { - return Math.Max(0.0, SurfaceSpeed() - AccelSurfacePrograde() * landingTime); - } - - return 0.0; - } - - /// - /// Returns the predicted time until landing in seconds. - /// - /// See the [category description](#description) for limitations on this function. - /// - /// MechJeb, Kerbal Engineer Redux - /// Estimated time until landing, or 0 if the orbit does not lithobrake. - public double LandingTime() - { - if (MASIKerbalEngineer.keFound) - { - return keProxy.LandingTime(); - } - else if (mjProxy.LandingComputerActive() > 0.0) - { - return mjProxy.LandingTime(); - } - else - { - double landingTime = vc.timeToImpact; - return (landingTime > 0.0) ? (landingTime) : 0.0; - } - } - - /// - /// Returns 1 if landing predictions are valid. If Kerbal Engineer Redux is installed, - /// MAS will use KER's predictions. Otherwise, if MechJeb is installed, MAS will - /// use MechJeb's landing computer (if it is active). If neither of those options - /// are available, MAS uses its own internal predictor. - /// - /// MechJeb, Kerbal Engineer Redux - /// - public double LandingPredictorActive() - { - if (MASIKerbalEngineer.keFound) - { - return keProxy.LandingTime() > 0.0 ? 1.0 : 0.0; - } - // Landing predictor sometimes gets weird and insists it's on, but it spews - // 0N, 0E as the landing point. - else if (mjProxy.LandingComputerActive() > 0.0 && mjProxy.LandingLongitude() != 0.0) - { - return 1.0; - } - else - { - return (vc.timeToImpact > 0.0) ? 1.0 : 0.0; - } - } - - /// - /// Return the vessel's latitude. - /// - /// - public double Latitude() - { - return vessel.latitude; - } - - /// - /// Return the vessel's longitude. - /// - /// - public double Longitude() - { - // longitude seems to be unnormalized. - return Utility.NormalizeLongitude(vessel.longitude); - } - - /// - /// Returns the predicted altitude of the landing position. - /// - /// This version will not use any mods to compute the landing site. - /// - /// See the [category description](#description) for limitations on this function. - /// - /// Predicted altitude (meters ASL) of the point of landing. - public double MASLandingAltitude() - { - return vc.landingAltitude; - } - - /// - /// Returns the predicted latitude of the landing position. - /// - /// This version will not use any mods to compute the landing site. - /// - /// See the [category description](#description) for limitations on this function. - /// - /// Latitude of estimated landing point, or 0 if the orbit does not lithobrake. - public double MASLandingLatitude() - { - return vc.landingLatitude; - } - - /// - /// Returns the predicted longitude of the landing position. - /// - /// This version will not use any mods to compute the landing site. - /// - /// See the [category description](#description) for limitations on this function. - /// - /// Longitude of estimated landing point, or 0 if the orbit does not lithobrake. - public double MASLandingLongitude() - { - return vc.landingLongitude; - } - - /// - /// Returns the predicted time until landing in seconds. - /// - /// This version will not use any mods to compute the landing site. - /// - /// See the [category description](#description) for limitations on this function. - /// - /// Estimated time until landing, or 0 if the orbit does not lithobrake. - public double MASLandingTime() - { - double landingTime = vc.timeToImpact; - return (landingTime > 0.0) ? (landingTime) : 0.0; - } - #endregion - - /// - /// Queries and controls related to power production belong in this category. - /// - /// For all of these components, if the player has changed the `ElectricCharge` field - /// in the MAS config file, these components will track that resource instead. - /// - /// The Fuel Cell methods track any ModuleResourceConverter that outputs - /// ElectricCharge, unless a different resource converter tracker has been installed - /// with a higher priority. For general-purpose ModuleResourceConverter tracking, - /// refer to Resource Converter category. - /// - #region Power Production - /// - /// Returns the number of alternators on the vessel. - /// - /// Number of alternator modules. - public double AlternatorCount() - { - return vc.moduleAlternator.Length; - } - - /// - /// Returns the current net output of the alternators. - /// - /// Units of ElectricCharge/second - public double AlternatorOutput() - { - return vc.netAlternatorOutput; - } - - /// - /// Returns the number of fuel cells on the vessel. Fuel cells are defined - /// as ModuleResourceConverter units that output `ElectricCharge` (or whatever - /// the player-selected override is in the MAS config file). - /// - /// Number of fuel cells. - public double FuelCellCount() - { - return ResourceConverterCount(0.0); - } - - /// - /// Returns the current output of installed fuel cells. - /// - /// Units of ElectricCharge/second. - public double FuelCellOutput() - { - return ResourceConverterOutput(0.0); - } - - /// - /// Returns the number of generators on the vessel. Generators - /// are and ModuleGenerator that outputs `ElectricCharge`. - /// - /// Number of generator.s - public double GeneratorCount() - { - return vc.moduleGenerator.Length; - } - - /// - /// Returns the current output of installed generators. - /// - /// Output in ElectricCharge/sec. - public double GeneratorOutput() - { - return vc.netGeneratorOutput; - } - - /// - /// Returns 1 if at least one fuel cell is enabled; 0 otherwise. - /// - /// 1 if any fuel cell is switched on; 0 otherwise. - public double GetFuelCellActive() - { - return GetResourceConverterActive(0.0); - } - - /// - /// Sets the fuel cells on or off per the 'active' parameter. Fuel cells that can - /// not be manually controlled are not changed. - /// - /// 1 if fuel cells are now active, 0 if they're off or they could not be toggled. - public double SetFuelCellActive(bool active) - { - return SetResourceConverterActive(0.0, active); - } - - /// - /// Deploys / undeploys solar panels. - /// - /// 'true' to extend solar panels, 'false' to retract them (when possible). - /// 1 if at least one panel is moving; 0 otherwise. - public double SetSolarPanelDeploy(bool deploy) - { - bool anyMoving = false; - if (vc.solarPanelsDeployable && deploy) - { - for (int i = vc.moduleSolarPanel.Length - 1; i >= 0; --i) - { - if (vc.moduleSolarPanel[i].useAnimation && vc.moduleSolarPanel[i].deployState == ModuleDeployablePart.DeployState.RETRACTED) - { - vc.moduleSolarPanel[i].Extend(); - anyMoving = true; - } - } - } - else if (vc.solarPanelsRetractable && !deploy) - { - for (int i = vc.moduleSolarPanel.Length - 1; i >= 0; --i) - { - if (vc.moduleSolarPanel[i].useAnimation && vc.moduleSolarPanel[i].retractable && vc.moduleSolarPanel[i].deployState == ModuleDeployablePart.DeployState.EXTENDED) - { - vc.moduleSolarPanel[i].Retract(); - anyMoving = true; - } - } - } - - return (anyMoving) ? 1.0 : 0.0; - } - - /// - /// Returns the number of solar panels on the vessel. - /// - /// The number of solar panel modules on the vessel. - public double SolarPanelCount() - { - return vc.moduleSolarPanel.Length; - } - - /// - /// Returns 1 if any solar panels are damaged. - /// - /// 1 is all solar panels are damaged; 0 otherwise. - public double SolarPanelDamaged() - { - return (vc.solarPanelsDamaged) ? 1.0 : 0.0; - } - - /// - /// Returns 1 if at least one solar panel may be deployed. - /// - /// 1 if any solar panel is retracted and available to deploy; 0 otherwise. - public double SolarPanelDeployable() - { - return (vc.solarPanelsDeployable) ? 1.0 : 0.0; - } - - /// - /// Returns the net efficiency of solar panels. This value can - /// provide some information about blocked solar panels, or non-rotating solar panels that - /// are not optimally positioned. Because the amount of energy delivered depends on the - /// distance from the star, maximum efficiency can be larger than 1 (or less than 1). - /// - /// The efficiency value, 0 or larger. - public double SolarPanelEfficiency() - { - return vc.solarPanelsEfficiency; - } - - /// - /// Returns -1 if a solar panel is retracting, +1 if a solar panel is extending, or 0 - /// if no solar panels are moving. - /// - /// -1, 0, or +1. - public double SolarPanelMoving() - { - return vc.solarPanelsMoving; - } - - /// - /// Returns the current output of installed solar panels. - /// - /// Solar panel output in ElectricCharge/sec. - public double SolarPanelOutput() - { - return vc.netSolarOutput; - } - - /// - /// Returns a number representing the average position of undamaged deployable solar panels. - /// - /// * 0 - No solar panels, no undamaged solar panels, or all undamaged solar panels are retracted. - /// * 1 - All deployable solar panels extended. - /// - /// If the solar panels are moving, a number between 0 and 1 is returned. Note that unretractable - /// panels will always return 1 once they have deployed. - /// - /// Panel Position (a number between 0 and 1). - public double SolarPanelPosition() - { - float numPanels = 0.0f; - float lerpPosition = 0.0f; - for (int i = vc.moduleSolarPanel.Length - 1; i >= 0; --i) - { - if (vc.moduleSolarPanel[i].useAnimation && vc.moduleSolarPanel[i].deployState != ModuleDeployablePart.DeployState.BROKEN) - { - numPanels += 1.0f; - - lerpPosition += vc.moduleSolarPanel[i].GetScalar; - } - } - - if (numPanels > 1.0f) - { - return lerpPosition / numPanels; - } - else if (numPanels == 1.0f) - { - return lerpPosition; - } - - return 0.0; - } - - /// - /// Returns 1 if at least one solar panel is retractable. - /// - /// 1 if a solar panel is deployed, and it may be retracted; 0 otherwise. - public double SolarPanelRetractable() - { - return (vc.solarPanelsRetractable) ? 1.0 : 0.0; - } - - /// - /// Toggles fuel cells from off to on or vice versa. Fuel cells that can - /// not be manually controlled are not toggled. - /// - /// 1 if fuel cells are now active, 0 if they're off or they could not be toggled. - public double ToggleFuelCellActive() - { - return SetResourceConverterActive(0.0, GetFuelCellActive() < 1.0); - } - - /// - /// Deploys / undeploys solar panels. - /// - /// 1 if at least one panel is moving; 0 otherwise. - public double ToggleSolarPanel() - { - bool anyMoving = false; - if (vc.solarPanelsDeployable) - { - for (int i = vc.moduleSolarPanel.Length - 1; i >= 0; --i) - { - if (vc.moduleSolarPanel[i].useAnimation && vc.moduleSolarPanel[i].deployState == ModuleDeployablePart.DeployState.RETRACTED) - { - vc.moduleSolarPanel[i].Extend(); - anyMoving = true; - } - } - } - else if (vc.solarPanelsRetractable) - { - for (int i = vc.moduleSolarPanel.Length - 1; i >= 0; --i) - { - if (vc.moduleSolarPanel[i].useAnimation && vc.moduleSolarPanel[i].retractable && vc.moduleSolarPanel[i].deployState == ModuleDeployablePart.DeployState.EXTENDED) - { - vc.moduleSolarPanel[i].Retract(); - anyMoving = true; - } - } - } - - return (anyMoving) ? 1.0 : 0.0; - } - #endregion - - /// - /// This section contains the functions used to interact with the stock procedural - /// fairings system. - /// - #region Procedural Fairings - - /// - /// Deploys all stock procedural fairings that are currently available to - /// deploy. - /// - /// 1 if any fairings deployed, 0 otherwise. - public double DeployFairings() - { - bool deployed = false; - - for (int i = vc.moduleProceduralFairing.Length - 1; i >= 0; --i) - { - if (vc.moduleProceduralFairing[i].CanMove) - { - vc.moduleProceduralFairing[i].DeployFairing(); - deployed = true; - } - } - - return (deployed) ? 1.0 : 0.0; - } - - /// - /// Returns 1 if at least one installed stock procedural fairing is available to - /// deploy. - /// - /// 1 if any fairings can deploy, 0 otherwise. - public double FairingsCanDeploy() - { - return (vc.fairingsCanDeploy) ? 1.0 : 0.0; - } - - /// - /// Returns the number of stock procedural fairings installed on the vessel. - /// - /// The total number of stock p-fairings on the vessel. - public double FairingsCount() - { - return vc.moduleProceduralFairing.Length; - } - #endregion - - /// - /// The Radar category provides the interface for controlling MASRadar - /// modules installed on the craft. - /// - #region Radar - /// - /// Returns 1 if any radars are turned on; 0 otherwise. - /// - /// 1 if any radar is switched on; 0 otherwise. - public double RadarActive() - { - for (int i = vc.moduleRadar.Length - 1; i >= 0; --i) - { - if (vc.moduleRadar[i].radarEnabled) - { - return 1.0; - } - } - return 0.0; - } - - /// - /// Returns the number of radar modules available on the vessel. - /// - /// The count of the number of radar units installed on the vessel, 0 or higher. - public double RadarCount() - { - return vc.moduleRadar.Length; - } - - /// - /// Activate or deactivate docking radars. - /// - /// 'true' to enable docking radars, false otherwise. - /// 1 if radars are now active, 0 otherwise. - public double SetRadarActive(bool active) - { - for (int i = vc.moduleRadar.Length - 1; i >= 0; --i) - { - vc.moduleRadar[i].radarEnabled = active; - } - - return (active) ? 1.0 : 0.0; - } - - /// - /// Toggle any installed radar from active to inactive. - /// - /// 1 if radars are now active, 0 otherwise. - public double ToggleRadar() - { - bool state = (vc.moduleRadar.Length > 0) ? !vc.moduleRadar[0].radarEnabled : false; - for (int i = vc.moduleRadar.Length - 1; i >= 0; --i) - { - vc.moduleRadar[i].radarEnabled = state; - } - - return (state) ? 1.0 : 0.0; - } - #endregion - - /// - /// Random number generators are in this category. - /// - #region Random - [MASProxyAttribute(Uncacheable = true)] - /// - /// Return a random number in the range of [0, 1] - /// - /// A uniformly-distributed pseudo-random number in the range [0, 1]. - public double Random() - { - return UnityEngine.Random.Range(0.0f, 1.0f); - } - - [MASProxyAttribute(Uncacheable = true)] - /// - /// Return an approximation of a normal distribution with a mean and - /// standard deviation as specified. The actual result falls in the - /// range of (-7, +7) for a mean of 0 and a standard deviation of 1. - /// - /// fc.RandomNormal uses a Box-Muller approximation method modified - /// to prevent a 0 in the u component (to avoid trying to take the - /// log of 0). The number was tweaked so for all practical purposes - /// the range of numbers is about (-7, +7), as explained above. - /// - /// The desired mean of the normal distribution. - /// The desired standard deviation of the normal distribution. - /// A pseudo-random number that emulates a normal distribution. See the summary for more detail. - public double RandomNormal(double mean, double stdDev) - { - // Box-Muller method tweaked to prevent a 0 in u: for a stddev of 1 - // the range is (-7, 7). - float u = UnityEngine.Random.Range(0.0009765625f, 1.0f); - float v = UnityEngine.Random.Range(0.0f, 2.0f * Mathf.PI); - double x = Mathf.Sqrt(-2.0f * Mathf.Log(u)) * Mathf.Cos(v) * stdDev; - return x + mean; - } - #endregion - - /// - /// The RCS controls may be accessed in this category along with status - /// variables. - /// - #region RCS - /// - /// Returns 1 if any RCS ports are disabled on the vessel. - /// - /// 1 if any ports are disabled; 0 if all are enabled or there are no RCS ports. - public double AnyRCSDisabled() - { - return (vc.anyRcsDisabled) ? 1.0 : 0.0; - } - - /// - /// Returns the current thrust percentage of all enabled RCS thrusters. This number counts only active - /// RCS ports. Even so, it is possible for the result to be less than 1.0. For instance, if some thrusters - /// are firing at less than full power to maintain orientation while translating, the net thrust will be - /// less than 1.0. - /// - /// The result does not account for thrust reductions in the atmosphere due to lower ISP, so sea level thrust - /// will be a fraction of full thrust. - /// - /// A value between 0.0 and 1.0. - public double CurrentRCSThrust() - { - return vc.rcsActiveThrustPercent; - } - - /// - /// Enables any RCS ports that have been disabled. - /// - /// 1 if any disabled RCS ports were enabled, 0 if there were no disabled ports. - public double EnableAllRCS() - { - double anyEnabled = 0.0; - for (int i = vc.moduleRcs.Length - 1; i >= 0; --i) - { - if (!vc.moduleRcs[i].rcsEnabled) - { - vc.moduleRcs[i].rcsEnabled = true; - anyEnabled = 1.0; - } - } - - return anyEnabled; - } - - /// - /// Returns 1 if the RCS action group has any actions attached to it. Note that - /// RCS thrusters don't neccessarily appear here. - /// - /// 1 if any actions are assigned to the RCS group. - public double RCSHasActions() - { - return (vc.GroupHasActions(KSPActionGroup.RCS)) ? 1.0 : 0.0; - } - - /// - /// Returns 1 if RCS is on, 0 otherwise. - /// - /// 1 if the RCS group is enabled, 0 otherwise. - public double GetRCS() - { - return (vessel.ActionGroups[KSPActionGroup.RCS]) ? 1.0 : 0.0; - } - - /// - /// Returns 1 if any RCS thrusters are firing, 0 otherwise. - /// - /// - public double GetRCSActive() - { - return (vc.anyRcsFiring) ? 1.0 : 0.0; - } - - /// - /// Returns 1 if any RCS thrusters are configured to allow rotation, - /// 0 otherwise. - /// - /// - public double GetRCSRotate() - { - return (vc.anyRcsRotate) ? 1.0 : 0.0; - } - - /// - /// Returns the thrust-weighted average of the RCS thrust limit for - /// all enabled RCS thrusters. - /// - /// A weighted average between 0 (no thrust) and 1 (full rated thrust). - public double GetRCSThrustLimit() - { - return vc.rcsWeightedThrustLimit; - } - - /// - /// Returns 1 if any RCS thrusters are configured to allow translation, - /// 0 otherwise. - /// - /// - public double GetRCSTranslate() - { - return (vc.anyRcsTranslate) ? 1.0 : 0.0; - } - - /// - /// Returns 1 if there is at least once RCS module on the vessel. - /// - /// - public double HasRCS() - { - return (vc.moduleRcs.Length > 0) ? 1.0 : 0.0; - } - - /// - /// Set the state of RCS. - /// - /// `true` to enable RCS, `false` to disable RCS. - /// 1 if the RCS group is enabled, 0 otherwise. - public double SetRCS(bool active) - { - vessel.ActionGroups.SetGroup(KSPActionGroup.RCS, active); - return (active) ? 1.0 : 0.0; - } - - /// - /// Enable or disable RCS rotation control. - /// - /// Whether RCS should be used for rotation. - /// The number of RCS modules updated (0 if none, more than 0 if any RCS are installed). - public double SetRCSRotate(bool active) - { - for (int i = vc.moduleRcs.Length - 1; i >= 0; --i) - { - vc.moduleRcs[i].enableRoll = active; - vc.moduleRcs[i].enableYaw = active; - vc.moduleRcs[i].enablePitch = active; - } - - return vc.moduleRcs.Length; - } - - /// - /// Enable or disable RCS translation control. - /// - /// Whether RCS should be used for translation. - /// The number of RCS modules updated (0 if none, more than 0 if any RCS are installed). - public double SetRCSTranslate(bool active) - { - for (int i = vc.moduleRcs.Length - 1; i >= 0; --i) - { - vc.moduleRcs[i].enableX = active; - vc.moduleRcs[i].enableY = active; - vc.moduleRcs[i].enableZ = active; - } - - return vc.moduleRcs.Length; - } - /// - /// Set the maximum thrust limit of the RCS thrusters. - /// - /// A value between 0 (no thrust) and 1 (full thrust). - /// The RCS thrust limit, clamped to [0, 1]. - public double SetRCSThrustLimit(double limit) - { - float clampedLimit = Mathf.Clamp01((float)limit); - float flimit = clampedLimit * 100.0f; - - for (int i = vc.moduleRcs.Length - 1; i >= 0; --i) - { - vc.moduleRcs[i].thrustPercentage = flimit; - } - return clampedLimit; - } - - /// - /// Toggle RCS off-to-on or vice versa. - /// - /// 1 if the RCS group is enabled, 0 otherwise. - public double ToggleRCS() - { - vessel.ActionGroups.ToggleGroup(KSPActionGroup.RCS); - return (vessel.ActionGroups[KSPActionGroup.RCS]) ? 1.0 : 0.0; - } - - /// - /// Toggle RCS rotation control. - /// - /// 1 if rotation is now on, 0 otherwise. - public double ToggleRCSRotate() - { - if (vc.anyRcsRotate) - { - for (int i = vc.moduleRcs.Length - 1; i >= 0; --i) - { - vc.moduleRcs[i].enableRoll = false; - vc.moduleRcs[i].enableYaw = false; - vc.moduleRcs[i].enablePitch = false; - } - return 0.0; - } - else - { - for (int i = vc.moduleRcs.Length - 1; i >= 0; --i) - { - vc.moduleRcs[i].enableRoll = true; - vc.moduleRcs[i].enableYaw = true; - vc.moduleRcs[i].enablePitch = true; - } - return 1.0; - } - } - - /// - /// Toggle RCS translation control. - /// - /// 1 if translation is now on, 0 otherwise. - public double ToggleRCSTranslate() - { - if (vc.anyRcsTranslate) - { - for (int i = vc.moduleRcs.Length - 1; i >= 0; --i) - { - vc.moduleRcs[i].enableX = false; - vc.moduleRcs[i].enableY = false; - vc.moduleRcs[i].enableZ = false; - } - return 0.0; - } - else - { - for (int i = vc.moduleRcs.Length - 1; i >= 0; --i) - { - vc.moduleRcs[i].enableX = true; - vc.moduleRcs[i].enableY = true; - vc.moduleRcs[i].enableZ = true; - } - return 1.0; - } - } - #endregion RCS - - /// - /// Methods for controlling and reporting information from reaction wheels are in this - /// category. - /// - /// Unlike other categories, the reaction wheels methods can be used to inspect the - /// reaction wheels installed in the current pod (when `currentPod` is true), or the - /// methods can be used to inspect all reaction wheels *not* in the current pod (when - /// `currentPod` is false). To inspect values for all reaction wheels (current pod - /// and rest of vessel), sum the results together (with the exception of ReactionWheelState). - /// - #region Reaction Wheels - - /// - /// Returns 1 if at least one reaction wheel is on the vessel and active. Returns 0 otherwise. - /// - /// - public double GetReactionWheelActive() - { - return (vc.reactionWheelActive) ? 1.0 : 0.0; - } - - /// - /// Returns the current reaction wheel authority as a percentage of maximum. - /// - /// Reaction wheel authority in the range of [0, 1]. - public double GetReactionWheelAuthority() - { - return vc.reactionWheelAuthority; - } - - /// - /// Returns 1 if at least one reaction wheel is damaged. Returns 0 otherwise. - /// - /// - public double GetReactionWheelDamaged() - { - return (vc.reactionWheelDamaged) ? 1.0 : 0.0; - } - - /// - /// Returns the net pitch being applied by reaction wheels as a value - /// between -1 and +1. Note that if two wheels are applying pitch in - /// opposite directions, this value will cancel out and reprot 0. - /// - /// The net pitch, between -1 and +1. - public double ReactionWheelPitch() - { - return vc.reactionWheelPitch; - } - - /// - /// Returns the net roll being applied by reaction wheels as a value - /// between -1 and +1. Note that if two wheels are applying roll in - /// opposite directions, this value will cancel out and reprot 0. - /// - /// The net roll, between -1 and +1. - public double ReactionWheelRoll() - { - return vc.reactionWheelRoll; - } - - /// - /// Returns the total torque percentage currently being applied via reaction wheels. - /// - /// A number between 0 (no torque) and 1 (maximum torque). - public double ReactionWheelTorque() - { - return vc.reactionWheelNetTorque; - } - - /// - /// Returns the net yaw being applied by reaction wheels as a value - /// between -1 and +1. Note that if two wheels are applying yaw in - /// opposite directions, this value will cancel out and reprot 0. - /// - /// The net yaw, between -1 and +1. - public double ReactionWheelYaw() - { - return vc.reactionWheelYaw; - } - - /// - /// Enable or disable reaction wheels. - /// - /// If true, reaction wheels are activated. If false, they are deactivated. - /// 1 if any reaction wheels are installed, otherwise 0. - public double SetReactionWheelActive(bool active) - { - for (int i = vc.moduleReactionWheel.Length - 1; i >= 0; --i) - { - if (vc.moduleReactionWheel[i].wheelState == ModuleReactionWheel.WheelState.Active && active == false) - { - vc.moduleReactionWheel[i].OnToggle(); - } - else if (vc.moduleReactionWheel[i].wheelState == ModuleReactionWheel.WheelState.Disabled && active == true) - { - vc.moduleReactionWheel[i].OnToggle(); - } - } - - return (vc.moduleReactionWheel.Length > 0) ? 1.0 : 0.0; - } - - /// - /// Update all active reaction wheels' authority. - /// - /// The new authority percentage, between 0 and 1. Value is clamped if it is outside that range. - /// The new reaction wheel authority, or 0 if no wheels are available. - public double SetReactionWheelAuthority(double authority) - { - float newAuthority = Mathf.Clamp01((float)authority); - for (int i = vc.moduleReactionWheel.Length - 1; i >= 0; --i) - { - vc.moduleReactionWheel[i].authorityLimiter = newAuthority * 100.0f; - } - - return (vc.moduleReactionWheel.Length > 0) ? newAuthority : 0.0; - } - - /// - /// Toggle the reaction wheels. - /// - /// 1 if any reaction wheels are installed, otherwise 0. - public double ToggleReactionWheel() - { - for (int i = vc.moduleReactionWheel.Length - 1; i >= 0; --i) - { - vc.moduleReactionWheel[i].OnToggle(); - } - - return (vc.moduleReactionWheel.Length > 0) ? 1.0 : 0.0; - } - #endregion - } -} diff --git a/ModifiedCode/MASIKAC.cs b/ModifiedCode/MASIKAC.cs deleted file mode 100644 index 5b5bb4f4..00000000 --- a/ModifiedCode/MASIKAC.cs +++ /dev/null @@ -1,266 +0,0 @@ -/***************************************************************************** - * The MIT License (MIT) - * - * Copyright (c) 2016-2017 MOARdV - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to - * deal in the Software without restriction, including without limitation the - * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or - * sell copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - * - ****************************************************************************/ -using MoonSharp.Interpreter; -using System; -using System.Collections.Generic; -using System.Text; - -namespace AvionicsSystems -{ - /// - /// The MASIKAC class interfaces with the KACWrapper, which is imported - /// verbatim from KAC's source. - /// - /// kac - /// - /// The MASIKAC object encapsulates the interface with Kerbal Alarm Clock. - /// - internal class MASIKAC - { - internal Vessel vessel; - private double[] alarms; - private string[] alarmIDs = new string[0]; - - [MoonSharpHidden] - public MASIKAC(Vessel vessel) - { - this.vessel = vessel; - this.alarms = new double[0]; - } - - ~MASIKAC() - { - vessel = null; - } - - [MoonSharpHidden] - internal void Update() - { - if (KACWrapper.InstanceExists) - { - KACWrapper.KACAPI.KACAlarmList alarms = KACWrapper.KAC.Alarms; - - int alarmCount = alarms.Count; - - if (alarmCount > 0) - { - double UT = Planetarium.GetUniversalTime(); - int vesselAlarmCount = 0; - string id = vessel.id.ToString(); - for (int i = 0; i < alarmCount; ++i) - { - if (alarms[i].VesselID == id && alarms[i].AlarmTime > UT) - { - ++vesselAlarmCount; - } - } - - if (this.alarms.Length != vesselAlarmCount) - { - this.alarms = new double[vesselAlarmCount]; - alarmIDs = new string[vesselAlarmCount]; - } - - if (vesselAlarmCount > 0) - { - for (int i = 0; i < alarmCount; ++i) - { - if (alarms[i].VesselID == id && alarms[i].AlarmTime > UT) - { - --vesselAlarmCount; - alarmIDs[vesselAlarmCount] = alarms[i].ID; - this.alarms[vesselAlarmCount] = alarms[i].AlarmTime - UT; - } - } - - // Sort the array so the next alarm is in [0]. - Array.Sort(this.alarms); - } - } - else if (this.alarms.Length > 0) - { - this.alarms = new double[0]; - alarmIDs = new string[0]; - } - } - } - - /// - /// The functions for interacting with the Kerbal Alarm Clock are listed in this category. - /// - #region Kerbal Alarm - - /// - /// Returns the number of future alarms scheduled for this vessel. - /// Alarms that are in the past, or for other vessels, are not - /// counted. If Kerbal Alarm Clock is not installed, this value - /// is zero. - /// - /// Count of alarms for this vessel; 0 or more. - public double AlarmCount() - { - return alarms.Length; - } - - /// - /// Scans the list of alarms assigned to this vessel to see if the alarm identified by `alarmID` - /// exists. Returns 1 if it is found, 0 otherwise. - /// - /// The ID of the alarm, typically from the return value of `fc.CreateAlarm()`. - /// 1 if the alarm exists, 0 otherwise. - public double AlarmExists(string alarmID) - { - if (alarmIDs.Length > 0) - { - int idx = alarmIDs.IndexOf(alarmID); - - return (idx == -1) ? 0.0 : 1.0; - } - - return 0.0; - } - - [MASProxyAttribute(Immutable = true)] - /// - /// Returns 1 if Kerbal Alarm Clock is installed and available on this craft, 0 if it - /// is not available. - /// - /// - public double Available() - { - return (KACWrapper.InstanceExists) ? 1.0 : 0.0; - } - - /// - /// Create a Raw alarm at the time specified by `UT`, using the name `name`. This alarm is - /// assigned to the current vessel ID. - /// - /// The short name to apply to the alarm. - /// The UT when the alarm should fire. - /// The alarm ID (a string), or an empty string if the method failed. - public string CreateAlarm(string name, double UT) - { - if (KACWrapper.InstanceExists) - { - string alarmID = KACWrapper.KAC.CreateAlarm(KACWrapper.KACAPI.AlarmTypeEnum.Raw, name, UT); - - if (string.IsNullOrEmpty(alarmID)) - { - return string.Empty; - } - else - { - var newAlarm = KACWrapper.KAC.Alarms.Find(x => x.ID == alarmID); - if (newAlarm != null) - { - newAlarm.VesselID = vessel.id.ToString(); - } - - return alarmID; - } - - } - - return string.Empty; - } - - /// - /// Create an alarm of specified at the time specified by `UT`, using the name `name`. This alarm is - /// assigned to the current vessel ID. - /// - /// The type of alarm to create." - /// The short name to apply to the alarm. - /// The UT when the alarm should fire. - /// The alarm ID (a string), or an empty string if the method failed. - public string CreateTypeAlarm(string alarmTypeStr, string name, double UT) - { - if (KACWrapper.InstanceExists) - { - KACWrapper.KACAPI.AlarmTypeEnum alarmType = (KACWrapper.KACAPI.AlarmTypeEnum) Enum.Parse(typeof(KACWrapper.KACAPI.AlarmTypeEnum), alarmTypeStr); - - string alarmID = KACWrapper.KAC.CreateAlarm(alarmType, name, UT); - - if (string.IsNullOrEmpty(alarmID)) - { - return string.Empty; - } - else - { - var newAlarm = KACWrapper.KAC.Alarms.Find(x => x.ID == alarmID); - if (newAlarm != null) - { - newAlarm.VesselID = vessel.id.ToString(); - } - - return alarmID; - } - - } - - return string.Empty; - } - - /// - /// Attempts to remove the alarm identified by `alarmID`. Normally, `alarmID` is the return - /// value from `fc.CreateAlarm()`. - /// - /// The ID of the alarm to remove - /// 1 if the alarm was removed, 0 if it was not, or it did not exist, or the Kerbal Alarm Clock is not available. - public double DeleteAlarm(string alarmID) - { - if (alarms.Length > 0) - { - return (KACWrapper.KAC.DeleteAlarm(alarmID)) ? 1.0 : 0.0; - } - return 0.0; - } - - /// - /// Returns the time to the next alarm scheduled in Kerbal Alarm Clock - /// for this vessel. If no alarm is scheduled, or all alarms occurred in - /// the past, this value is 0. - /// - /// Time to the next alarm for the current vessel, in seconds; 0 if there are no alarms. - public double TimeToAlarm() - { - if (alarms.Length > 0) - { - return alarms[0]; - } - else - { - return 0.0; - } - } - #endregion - - #region Reflection Configuration - static MASIKAC() - { - KACWrapper.InitKACWrapper(); - } - #endregion - } -} diff --git a/ModifiedCode/MASITransfer.cs b/ModifiedCode/MASITransfer.cs deleted file mode 100644 index 0ac59e65..00000000 --- a/ModifiedCode/MASITransfer.cs +++ /dev/null @@ -1,1746 +0,0 @@ -//#define COMPARE_PHASE_ANGLE_PROTRACTOR -//#define DEBUG_CHANGE_ALTITUDE -/***************************************************************************** - * The MIT License (MIT) - * - * Copyright (c) 2016-2018 MOARdV - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to - * deal in the Software without restriction, including without limitation the - * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or - * sell copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - * - ****************************************************************************/ -using MoonSharp.Interpreter; -using System; -using System.Collections.Generic; -using System.Text; -using UnityEngine; - -namespace AvionicsSystems -{ - // ΔV - put this somewhere where I can find it easily to copy/paste - - /// - /// The MASITransfer class contains functionality equivalent to the KSP - /// mod Protractor. However, the code here was written from scratch, since - /// Protractor is GPL. - /// - /// transfer - /// The MASITransfer module does calculations to find phase angles - /// and ejection angles for Hohmann transfer orbits. It provides functionality - /// equivalent to the Protractor mod, but focused strictly on computations - /// involving the current vessel and a target (either another vessel or a - /// Celestial Body). - /// - /// Note that MASITransfer assumes the target has a small relative inclination. - /// It will generate erroneous results for high inclination and retrograde relative - /// orbits. - /// - /// In addition to providing orbital transfer information, the MASITransfer module - /// can create maneuver nodes for basic orbital operations (changing Ap or Pe, - /// circularizing at a specific altitude, plotting Hohmann transfers, etc). - /// - internal class MASITransfer - { - private bool invalid = true; - - internal Vessel vessel; - internal MASVesselComputer vc; - - private bool hohmannTransfer; - private double currentPhaseAngle; - private double transferPhaseAngle; - private double timeUntilTransfer; - - private double ejectionDeltaV; - private double currentEjectionAngle; - private double transferEjectionAngle; - private double timeUntilEjection; - - private double oberthAltitude; - private double oberthEjectionVelocity; - private double oberthCurrentEjectionAngle; - private double oberthTransferEjectionAngle; - private double oberthTimeUntilEjection; - - private double initialDeltaV; - private double finalDeltaV; - - private Orbit oUpper = new Orbit(); - private Orbit oLower = new Orbit(); - private Orbit oMid = new Orbit(); - - [MoonSharpHidden] - public MASITransfer(Vessel vessel) - { - this.vessel = vessel; - } - - /// - /// The Delta-V section provides information on the amount of velocity - /// change needed to change orbits. This information can be computed - /// based on the current target, or a target altitude, depending on the - /// specific method called. - /// - /// These values are estimates based on circular orbits, assuming - /// no plane change is required. Eccentric orbits, or non-coplanar - /// orbits, will not reflect the total ΔV required. - /// - #region Transfer Delta-V - - /// - /// Returns an estimate of the ΔV required to circularize a Hohmann transfer at - /// the target's orbit. - /// - /// Negative values indicate a retrograde burn. Positive values indicate a - /// prograde burn. - /// - /// The ΔV in m/s to finialize the transfer. - public double DeltaVFinal() - { - if (vc.activeTarget != null) - { - if (invalid) - { - UpdateTransferParameters(); - } - - return finalDeltaV; - } - else - { - return 0.0; - } - } - - /// - /// Returns and estimate of the ΔV required to circularize the vessel's orbit - /// at the altitude provided. - /// - /// Negative values indicate a retrograde burn. Positive values indicate a - /// prograde burn. - /// - /// Destination altitude, in meters. - /// ΔV in m/s to circularize at the requested altitude, or 0 if the vessel is not in flight. - public double DeltaVFinal(double destinationAltitude) - { - if (!(vessel.Landed || vessel.Splashed)) - { - double GM = vessel.mainBody.gravParameter; - double rA = vessel.orbit.semiMajorAxis; - double rB = destinationAltitude + vessel.mainBody.Radius; - - double atx = 0.5 * (rA + rB); - double Vf = Math.Sqrt(GM / rB); - - double Vtxf = Math.Sqrt(GM * (2.0 / rB - 1.0 / atx)); - - return Vf - Vtxf; - } - else - { - return 0.0; - } - } - - /// - /// Returns an estimate of the ΔV required to start a Hohmann transfer to - /// the target's orbit. - /// - /// Negative values indicate a retrograde burn. Positive values indicate a - /// prograde burn. - /// - /// The ΔV in m/s to start the transfer. - public double DeltaVInitial() - { - if (vc.activeTarget != null) - { - if (invalid) - { - UpdateTransferParameters(); - } - - return initialDeltaV; - } - else - { - return 0.0; - } - } - - /// - /// Returns and estimate of the ΔV required to change the vessel's orbit - /// to the altitude provided. - /// - /// Negative values indicate a retrograde burn. Positive values indicate a - /// prograde burn. - /// - /// Destination altitude, in meters. - /// ΔV in m/s to reach the requested altitude, or 0 if the vessel is not in flight. - public double DeltaVInitial(double destinationAltitude) - { - if (!(vessel.Landed || vessel.Splashed)) - { - return DeltaVInitial(vessel.orbit.semiMajorAxis, destinationAltitude + vessel.mainBody.Radius, vessel.mainBody.gravParameter); - } - else - { - return 0.0; - } - } - #endregion - - /// - /// The Ejection Angle region provides information on the ejection angle. The - /// ejection angle is used on interplanetary transfers to determine when - /// the vessel should start its burn to escape the world it currently orbits. - /// - /// When the vessel is orbiting a moon in preparation for an interplanetary - /// transfer, the target ejection angle will reflect the ejection angle - /// required to take advantage of the Oberth effect during the ejection. - /// - #region Transfer Ejection Angle - - /// - /// Reports the vessel's current ejection angle. When this value matches - /// the transfer ejection angle, it is time to start an interplanetary burn. - /// - /// This angle is a measurement of the vessel from the planet's prograde direction. - /// - /// Current ejection angle in degrees, or 0 if there is no ejection angle. - public double CurrentEjectionAngle() - { - if (vc.activeTarget != null) - { - if (invalid) - { - UpdateTransferParameters(); - } - - return currentEjectionAngle; - } - else - { - return 0.0; - } - } - - /// - /// The ΔV required to reach the correct exit velocity for an interplanetary transfer. - /// - /// ΔV in m/s, or 0 if there is no exit velocity required. - public double EjectionVelocity() - { - if (vc.activeTarget != null) - { - if (invalid) - { - UpdateTransferParameters(); - } - - return ejectionDeltaV; - } - else - { - return 0.0; - } - } - - /// - /// Reports the difference between the vessel's current ejection angle - /// and the transfer ejection angle. When this value is 0, it is time to - /// start an interplanetary burn. - /// - /// Relative ejection angle in degrees, or 0 if there is no ejection angle. - public double RelativeEjectionAngle() - { - if (vc.activeTarget != null) - { - if (invalid) - { - UpdateTransferParameters(); - } - - return Utility.NormalizeAngle(currentEjectionAngle - transferEjectionAngle); - } - else - { - return 0.0; - } - } - - /// - /// Provides the time until the vessel reaches the transfer ejection angle. - /// - /// Time until the relative ejection angle is 0, in seconds, or 0 if there is no ejection angle. - public double TimeUntilEjection() - { - if (vc.activeTarget != null) - { - if (invalid) - { - UpdateTransferParameters(); - } - - return timeUntilEjection; - } - else - { - return 0.0; - } - } - - /// - /// Reports the ejection angle when an interplanetary Hohmann transfer - /// orbit should begin. This is of use for transfers from one planet - /// to another - once the transfer phase angle has been reached, the - /// vessel should launch when the next transfer ejection angle is reached. - /// - /// Transfer ejection angle in degrees, or 0 if there is no ejection angle. - public double TransferEjectionAngle() - { - if (vc.activeTarget != null) - { - if (invalid) - { - UpdateTransferParameters(); - } - - return transferEjectionAngle; - } - else - { - return 0.0; - } - } - - #endregion - - /// - /// The Maneuver Planning region provides functions that can be used to generate - /// maneuver nodes to accomplish basic orbital tasks. This capability - /// does not include autopilot functionality - it is simply a set of helper functions to create - /// maneuver nodes. - /// - /// The MAS Maneuver Planner is not as full-featured as MechJeb - it does not - /// work with parabolic / hyperbolic orbits, for instance. - /// - #region Transfer Maneuver Planning - - /// - /// Raise or lower the altitude of the apoapsis. The maneuver node is placed at - /// periapsis to minimize fuel requirements. If an invalid apoapsis is supplied - /// (either by being below the periapsis, or above the SoI of the planet), this function does - /// nothing. - /// - /// The new altitude for the apoapsis, in meters. - /// 1 if a valid maneuver node was created, 0 if it was not. - public double ChangeApoapsis(double newAltitude) - { - Orbit current = vessel.orbit; - double newApR = newAltitude + current.referenceBody.Radius; - if (newApR >= current.PeR && newApR <= current.referenceBody.sphereOfInfluence && vessel.patchedConicSolver != null) - { - CelestialBody referenceBody = current.referenceBody; - double ut = current.timeToPe + Planetarium.GetUniversalTime(); - Vector3d posAtUt = current.getRelativePositionAtUT(ut); - Vector3d velAtUt = current.getOrbitalVelocityAtUT(ut); - Vector3d fwdAtUt = velAtUt.normalized; - - double dVUpper; - double dVLower; - - dVLower = DeltaVInitial(current.PeR, newApR, referenceBody.gravParameter, velAtUt.magnitude); - if (current.eccentricity >= 1.0 || newApR > current.ApR) - { - // Could possibly set dvUpper to 0? - dVUpper = DeltaVInitial(Math.Min(2.0 * newApR, current.referenceBody.sphereOfInfluence), newApR, referenceBody.gravParameter); - } - else - { - // Our current orbit brackets the desired altitude, so we'll initialize our search with the - // Ap and Pe. Since the maneuver starts at the Pe, we'll account for actual velocity with - // our lower bound value, which should allow us to converge to the solution quicker (or immediately). - dVUpper = DeltaVInitial(current.ApR, newApR, referenceBody.gravParameter); - } - oUpper.UpdateFromStateVectors(posAtUt, velAtUt + fwdAtUt * dVUpper, referenceBody, ut); - oLower.UpdateFromStateVectors(posAtUt, velAtUt + fwdAtUt * dVLower, referenceBody, ut); - - double dVMid = (dVUpper + dVLower) * 0.5; - double apUpper = (oUpper.ApR < 0.0) ? current.referenceBody.sphereOfInfluence : oUpper.ApR; - double apLower = (oLower.ApR < 0.0) ? current.referenceBody.sphereOfInfluence : oLower.ApR; - -#if DEBUG_CHANGE_ALTITUDE - Utility.LogMessage(this, "Change Ap {0:0.000}:", newAltitude * 0.001); -#endif - if (Math.Abs(apLower - newApR) < 1.0) - { -#if DEBUG_CHANGE_ALTITUDE - Utility.LogMessage(this, "Close enough - apLower"); -#endif - // Within 1m of the desired altitude - good enough. - // apLower is most likely to be right on, since we're using - // the actual orbital velocity @ Pe to determine dV. - dVMid = dVLower; - } - else if (Math.Abs(apUpper - newApR) < 1.0) - { -#if DEBUG_CHANGE_ALTITUDE - Utility.LogMessage(this, "Close enough - apUpper"); -#endif - // Within 1m of the desired altitude - good enough. - dVMid = dVUpper; - } - else - { - oMid.UpdateFromStateVectors(posAtUt, velAtUt + fwdAtUt * dVMid, referenceBody, ut); - double apMid = (oMid.ApR < 0.0) ? current.referenceBody.sphereOfInfluence : oMid.ApR; - - while (Math.Abs(dVUpper - dVLower) > 0.015625) - { -#if DEBUG_CHANGE_ALTITUDE - Utility.LogMessage(this, " - Upper = {0,6:0.0}m/s -> Pe {1,9:0.000}, Ap {2,9:0.000}", dVUpper, oUpper.PeA * 0.001, oUpper.ApA * 0.001); - Utility.LogMessage(this, " - Lower = {0,6:0.0}m/s -> Pe {1,9:0.000}, Ap {2,9:0.000}", dVLower, oLower.PeA * 0.001, oLower.ApA * 0.001); - Utility.LogMessage(this, " - Mid = {0,6:0.0}m/s -> Pe {1,9:0.000}, Ap {2,9:0.000}", dVMid, oMid.PeA * 0.001, oMid.ApA * 0.001); -#endif - if (Math.Abs(apLower - newApR) < 1.0) - { - // Within 1m of the desired altitude - good enough. - dVMid = dVLower; - // Only for debug printing: -#if DEBUG_CHANGE_ALTITUDE - oMid.UpdateFromStateVectors(posAtUt, velAtUt + fwdAtUt * dVMid, referenceBody, ut); -#endif - break; - } - else if (Math.Abs(apUpper - newApR) < 1.0) - { - // Within 1m of the desired altitude - good enough. - dVMid = dVUpper; - // Only for debug printing: -#if DEBUG_CHANGE_ALTITUDE - oMid.UpdateFromStateVectors(posAtUt, velAtUt + fwdAtUt * dVMid, referenceBody, ut); -#endif - break; - } - else if (Math.Abs(apUpper - newApR) < Math.Abs(apLower - newApR)) - { - apLower = apMid; - dVLower = dVMid; - - Orbit tmp = oLower; - oLower = oMid; - oMid = tmp; - } - else - { - apUpper = apMid; - dVUpper = dVMid; - - Orbit tmp = oUpper; - oUpper = oMid; - oMid = tmp; - } - dVMid = (dVUpper + dVLower) * 0.5; - oMid.UpdateFromStateVectors(posAtUt, velAtUt + fwdAtUt * dVMid, referenceBody, ut); - apMid = (oMid.ApR < 0.0) ? current.referenceBody.sphereOfInfluence : oMid.ApR; - } -#if DEBUG_CHANGE_ALTITUDE - Utility.LogMessage(this, " - Final = {0,6:0.0}m/s -> Pe {1,9:0.000}, Ap {2,9:0.000}", dVMid, oMid.PeA * 0.001, oMid.ApA * 0.001); -#endif - } - - Vector3d dV = Utility.ManeuverNode(dVMid, 0.0, 0.0); - - vessel.patchedConicSolver.maneuverNodes.Clear(); - ManeuverNode mn = vessel.patchedConicSolver.AddManeuverNode(ut); - mn.OnGizmoUpdated(dV, ut); - - return 1.0; - } - - return 0.0; - } - - /// - /// Change the inclination of the orbit. The change will be scheduled at either the AN or DN - /// of the orbit, whichever results in a less expensive maneuver. - /// - /// The new inclination, in degrees. - /// 1 if the maneuver was scheduled, 0 if it could not be scheduled. - public double ChangeInclination(double newInclination) - { - Orbit current = vessel.orbit; - - if (MASFlightComputerProxy.ConvertVesselSituation(vessel.situation) > 2 && vessel.patchedConicSolver != null && current.eccentricity < 1.0) - { - double deltaI = newInclination - current.inclination; - double deltaI2 = Utility.NormalizeLongitude(deltaI); - - if (Math.Abs(deltaI2) > 0.0) - { - Vector3d ANVector = current.GetANVector(); - ANVector.Normalize(); - - double taAN = current.GetTrueAnomalyOfZupVector(ANVector); - double timeAN = Utility.NormalizeOrbitTime(current.GetUTforTrueAnomaly(taAN, current.period) - vc.universalTime, current) + vc.universalTime; - Vector3d vAN = current.getOrbitalVelocityAtUT(timeAN); - - double taDN = taAN + Math.PI; - double timeDN = Utility.NormalizeOrbitTime(current.GetUTforTrueAnomaly(taDN, current.period) - vc.universalTime, current) + vc.universalTime; - Vector3d vDN = current.getOrbitalVelocityAtUT(timeDN); - - double deltaVScale = Math.Sin(0.5 * Utility.Deg2Rad * deltaI2); - - // Pick the slower of the two nodes - double mTime; - double speedAtUt; - if (vAN.sqrMagnitude < vDN.sqrMagnitude) - { - mTime = timeAN; - speedAtUt = vAN.magnitude; - } - else - { - mTime = timeDN; - speedAtUt = vDN.magnitude; - } - - double planeChangedV = 2.0 * speedAtUt * deltaVScale; - - // This is interesting ... Eq. 4.73 at http://www.braeunig.us/space/ describes the ΔV - // needed to change inclination. However, if I apply that strictly as a ΔV on the - // normal, the resulting orbit is not correct. Looking at the results MechJeb generates, - // I notice that there's a retrograde ΔV equal to sin(Θ/2) (which MJ doesn't directly compute - - // it's an artifact of its technique). So, first I determine the prograde component of - // the maneuver, and then I apply the balance of the ΔV to the normal. - double progradedV = -Math.Abs(deltaVScale * planeChangedV); - double normaldV = Math.Sqrt(planeChangedV * planeChangedV - progradedV * progradedV) * Math.Sign(deltaVScale); - Vector3d maneuver = Utility.ManeuverNode(progradedV, normaldV, 0.0); - - vessel.patchedConicSolver.maneuverNodes.Clear(); - ManeuverNode mn = vessel.patchedConicSolver.AddManeuverNode(mTime); - mn.OnGizmoUpdated(maneuver, mTime); - - return 1.0; - } - } - - return 0.0; - } - - /// - /// Raise or lower the altitude of the periapsis. The maneuver node is placed at - /// apoapsis to minimize fuel requirements. If an invalid periapsis is supplied - /// (either by being above the apoapsis, or lower than the center of the planet, or - /// if the current orbit is hyperbolic), this function does - /// nothing. - /// - /// The new altitude for the periapsis, in meters. - /// 1 if a valid maneuver node was created, 0 if it was not. - public double ChangePeriapsis(double newAltitude) - { - Orbit current = vessel.orbit; - double newPeR = newAltitude + current.referenceBody.Radius; - if (newPeR >= 0.0 && newPeR <= current.ApR && vessel.patchedConicSolver != null && current.eccentricity < 1.0) - { - CelestialBody referenceBody = current.referenceBody; - double ut = current.timeToAp + Planetarium.GetUniversalTime(); - Vector3d posAtUt = current.getRelativePositionAtUT(ut); - Vector3d velAtUt = current.getOrbitalVelocityAtUT(ut); - Vector3d fwdAtUt = velAtUt.normalized; - - double dVUpper; - double dVLower; - - dVUpper = DeltaVInitial(current.ApR, newPeR, referenceBody.gravParameter, velAtUt.magnitude); - if (newPeR < current.PeR) - { - // Our current Pe is higher than the target, so we treat it as the upper bound and half the - // target Pe as the lower bound. - dVLower = DeltaVInitial(newPeR * 0.5, newPeR, referenceBody.gravParameter); - } - else - { - // Our current orbit brackets the desired altitude, so we'll initialize our search with the - // Ap and Pe. - dVLower = DeltaVInitial(current.PeR, newPeR, referenceBody.gravParameter); - } - oUpper.UpdateFromStateVectors(posAtUt, velAtUt + fwdAtUt * dVUpper, referenceBody, ut); - oLower.UpdateFromStateVectors(posAtUt, velAtUt + fwdAtUt * dVLower, referenceBody, ut); - - double dVMid = (dVUpper + dVLower) * 0.5; - double peUpper = oUpper.PeR; - double peLower = oLower.PeR; - -#if DEBUG_CHANGE_ALTITUDE - Utility.LogMessage(this, "Change Pe {0:0.000}:", newAltitude * 0.001); -#endif - - if (Math.Abs(peUpper - newPeR) < 1.0) - { -#if DEBUG_CHANGE_ALTITUDE - Utility.LogMessage(this, "Close enough - peUpper"); -#endif - // Within 1m of the desired altitude - good enough. - // peUpper is most likely to be right on, since we're using - // the actual orbital velocity @ Ap to determine dV. - dVMid = dVUpper; - } - else if (Math.Abs(peLower - newPeR) < 1.0) - { -#if DEBUG_CHANGE_ALTITUDE - Utility.LogMessage(this, "Close enough - peLower"); -#endif - // Within 1m of the desired altitude - good enough. - dVMid = dVLower; - } - else - { - oMid.UpdateFromStateVectors(posAtUt, velAtUt + fwdAtUt * dVMid, referenceBody, ut); - double peMid = oMid.PeR; - - while (Math.Abs(dVUpper - dVLower) > 0.015625) - { -#if DEBUG_CHANGE_ALTITUDE - Utility.LogMessage(this, " - Upper = {0,6:0.0}m/s -> Pe {1,9:0.000}, Ap {2,9:0.000}", dVUpper, oUpper.PeA * 0.001, oUpper.ApA * 0.001); - Utility.LogMessage(this, " - Lower = {0,6:0.0}m/s -> Pe {1,9:0.000}, Ap {2,9:0.000}", dVLower, oLower.PeA * 0.001, oLower.ApA * 0.001); - Utility.LogMessage(this, " - Mid = {0,6:0.0}m/s -> Pe {1,9:0.000}, Ap {2,9:0.000}", dVMid, oMid.PeA * 0.001, oMid.ApA * 0.001); -#endif - if (Math.Abs(peUpper - newPeR) < 1.0) - { - // Within 1m of the desired altitude - good enough. - dVMid = dVLower; - // Only for debug printing: -#if DEBUG_CHANGE_ALTITUDE - oMid.UpdateFromStateVectors(posAtUt, velAtUt + fwdAtUt * dVMid, referenceBody, ut); -#endif - break; - } - else if (Math.Abs(peLower - newPeR) < 1.0) - { - // Within 1m of the desired altitude - good enough. - dVMid = dVUpper; - // Only for debug printing: -#if DEBUG_CHANGE_ALTITUDE - oMid.UpdateFromStateVectors(posAtUt, velAtUt + fwdAtUt * dVMid, referenceBody, ut); -#endif - break; - } - else if (Math.Abs(peUpper - newPeR) < Math.Abs(peLower - newPeR)) - { - peLower = peMid; - dVLower = dVMid; - - Orbit tmp = oLower; - oLower = oMid; - oMid = tmp; - } - else - { - peUpper = peMid; - dVUpper = dVMid; - - Orbit tmp = oUpper; - oUpper = oMid; - oMid = tmp; - } - dVMid = (dVUpper + dVLower) * 0.5; - oMid.UpdateFromStateVectors(posAtUt, velAtUt + fwdAtUt * dVMid, referenceBody, ut); - peMid = oMid.PeR; - } -#if DEBUG_CHANGE_ALTITUDE - Utility.LogMessage(this, " - Final = {0,6:0.0}m/s -> Pe {1,9:0.000}, Ap {2,9:0.000}", dVMid, oMid.PeA * 0.001, oMid.ApA * 0.001); -#endif - } - - Vector3d dV = Utility.ManeuverNode(dVMid, 0.0, 0.0); - - vessel.patchedConicSolver.maneuverNodes.Clear(); - ManeuverNode mn = vessel.patchedConicSolver.AddManeuverNode(ut); - mn.OnGizmoUpdated(dV, ut); - - return 1.0; - } - - return 0.0; - } - - /// - /// Circularize the vessel's orbit at the specified altitude, in meters. This new altitude - /// must be between the current periapsis and apoapsis, and the current orbit must not be hyperbolic. - /// - /// The altitude at which the orbit will be circularized, in meters. - /// 1 if a node was created, 0 otherwise. - public double CircularizeAltitude(double newAltitude) - { - Orbit current = vessel.orbit; - double newSMA = newAltitude + current.referenceBody.Radius; - if (newSMA >= current.PeR && newSMA <= current.ApR && vessel.patchedConicSolver != null && current.eccentricity < 1.0) - { - CelestialBody referenceBody = current.referenceBody; - double vNew = Math.Sqrt(referenceBody.gravParameter / newSMA); - double maneuverUt = Planetarium.GetUniversalTime() + Utility.NextTimeToRadius(current, newSMA); - - Vector3d velAtUt = current.getOrbitalVelocityAtUT(maneuverUt); - - Vector3d relativePosition = current.getRelativePositionAtUT(maneuverUt); - - Vector3d prograde; - Vector3d normal; - Vector3d radial; - Utility.GetOrbitalBasisVectors(velAtUt, relativePosition, out prograde, out radial, out normal); - - Vector3d upAtUt = relativePosition.xzy.normalized; - Vector3d fwdAtUt = Vector3d.Cross(upAtUt, normal); - Vector3d maneuverVel = fwdAtUt * vNew; - - Vector3d deltaV = maneuverVel - velAtUt.xzy; - //Utility.LogMessage(this, "dV = {0} because {1} - {2}", deltaV, maneuverVel, velAtUt); - //Utility.LogMessage(this, "prograde (dot) fwd = {0:0.000}", Vector3d.Dot(prograde, fwdAtUt)); - - Vector3d maneuverdV = Utility.ManeuverNode(Vector3d.Dot(deltaV, prograde), Vector3d.Dot(deltaV, normal), Vector3d.Dot(deltaV, radial)); - - vessel.patchedConicSolver.maneuverNodes.Clear(); - ManeuverNode mn = vessel.patchedConicSolver.AddManeuverNode(maneuverUt); - mn.OnGizmoUpdated(maneuverdV, maneuverUt); - - //Vector3d posAtUt = current.getRelativePositionAtUT(maneuverUt); - //oUpper.UpdateFromStateVectors(posAtUt, maneuverVel.xzy, referenceBody, maneuverUt); - //Utility.LogMessage(this, "Circularize at {0:0.000}km: {1:0.000} x {2:0.000} @ {3:0.0}", - // newAltitude * 0.001, - // oUpper.ApA * 0.001, oUpper.PeA * 0.001, - // oUpper.inclination); - - return 1.0; - } - - return 0.0; - } - - /// - /// Circularize the vessel's orbit at the specified altitude, in meters. This new altitude - /// must be between the current periapsis and the SoI limit. If the orbit is hyperbolic and the craft - /// is past the periapsis, the altitude must also be greater than the current altitude. - /// - /// The altitude at which the orbit will be circularized, in meters. - /// 1 if a node was created, 0 otherwise. - public double CircularizeAltitudeHyp(double newAltitude) - { - Orbit current = vessel.orbit; - double newSMA = newAltitude + current.referenceBody.Radius; - - Utility.LogMessage(this, "newSMA = {0} because {1} + {2}", newSMA, newAltitude, current.referenceBody.Radius); - - if ((newSMA >= current.PeR && vc.orbit.timeToPe >= 0 || newSMA >= vessel.altitude) && newSMA <= vessel.mainBody.sphereOfInfluence && vessel.patchedConicSolver != null) - { - CelestialBody referenceBody = current.referenceBody; - double vNew = Math.Sqrt(referenceBody.gravParameter / newSMA); - double tAAtNewSMA = current.TrueAnomalyAtRadius(newSMA); - //double maneuverUt = Planetarium.GetUniversalTime() + current.GetUTforTrueAnomaly(tAAtNewSMA, 0); - double maneuverUt = current.GetUTforTrueAnomaly(tAAtNewSMA, 0); - - Utility.LogMessage(this, "maneuverUt = {0} because {1}", maneuverUt, current.GetUTforTrueAnomaly(tAAtNewSMA, 0)); - - Vector3d velAtUt = current.getOrbitalVelocityAtUT(maneuverUt); - - Utility.LogMessage(this, "velAtUt = {0} ", velAtUt); - - Vector3d relativePosition = current.getRelativePositionAtUT(maneuverUt); - - Utility.LogMessage(this, "relativePosition = {0}", relativePosition); - - Vector3d prograde; - Vector3d normal; - Vector3d radial; - Utility.GetOrbitalBasisVectors(velAtUt, relativePosition, out prograde, out radial, out normal); - - Utility.LogMessage(this, "maneuverUt = {0} because {1} - {2}", maneuverUt, Planetarium.GetUniversalTime(), current.GetUTforTrueAnomaly(tAAtNewSMA, 0)); - - Vector3d upAtUt = relativePosition.xzy.normalized; - Vector3d fwdAtUt = Vector3d.Cross(upAtUt, normal); - Vector3d maneuverVel = fwdAtUt * vNew; - - Utility.LogMessage(this, "maneuverVel = {0} because {1} - {2}", maneuverVel, upAtUt, fwdAtUt); - - Vector3d deltaV = maneuverVel - velAtUt.xzy; - Utility.LogMessage(this, "dV = {0} because {1} - {2}", deltaV, maneuverVel, velAtUt); - Utility.LogMessage(this, "prograde (dot) fwd = {0:0.000}", Vector3d.Dot(prograde, fwdAtUt)); - - Vector3d maneuverdV = Utility.ManeuverNode(Vector3d.Dot(deltaV, prograde), Vector3d.Dot(deltaV, normal), Vector3d.Dot(deltaV, radial)); - - vessel.patchedConicSolver.maneuverNodes.Clear(); - ManeuverNode mn = vessel.patchedConicSolver.AddManeuverNode(maneuverUt); - mn.OnGizmoUpdated(maneuverdV, maneuverUt); - - //Vector3d posAtUt = current.getRelativePositionAtUT(maneuverUt); - //oUpper.UpdateFromStateVectors(posAtUt, maneuverVel.xzy, referenceBody, maneuverUt); - //Utility.LogMessage(this, "Circularize at {0:0.000}km: {1:0.000} x {2:0.000} @ {3:0.0}", - // newAltitude * 0.001, - // oUpper.ApA * 0.001, oUpper.PeA * 0.001, - // oUpper.inclination); - - return 1.0; - } - - return 0.0; - } - - /// - /// Circularize the vessel's orbit at the specified altitude, in meters. This new altitude - /// must be between the current periapsis and the SoI limit. If the orbit is hyperbolic and the craft - /// is past the periapsis, the altitude must also be greater than the current altitude. This method - /// uses calculations derived from first principles rather than the KSP orbit functions. - /// - /// The altitude at which the orbit will be circularized, in meters. - /// 1 if a node was created, 0 otherwise. - public double CircularizeAltitudeHypVis(double newAltitude) - { - Orbit current = vessel.orbit; - double newSMA = newAltitude + current.referenceBody.Radius; - - Utility.LogMessage(this, "newSMA = {0} because {1} + {2}", newSMA, newAltitude, current.referenceBody.Radius); - - if ((newSMA >= current.PeR && vc.orbit.timeToPe >= 0 || newSMA >= vessel.altitude) && newSMA <= vessel.mainBody.sphereOfInfluence && vessel.patchedConicSolver != null) - { - CelestialBody referenceBody = current.referenceBody; - double vNew = Math.Sqrt(referenceBody.gravParameter / newSMA); - double tAAtNewSMA = current.TrueAnomalyAtRadius(newSMA); - //double maneuverUt = Planetarium.GetUniversalTime() + current.GetUTforTrueAnomaly(tAAtNewSMA, 0); - double maneuverUt = current.GetUTforTrueAnomaly(tAAtNewSMA, 0); - - Utility.LogMessage(this, "maneuverUt = {0} because {1}", maneuverUt, current.GetUTforTrueAnomaly(tAAtNewSMA, 0)); - - double vPreBurn = Math.Sqrt(referenceBody.gravParameter * (2 / newSMA - 1 / current.semiMajorAxis)); - - Utility.LogMessage(this, "v before burn = {0} ", vPreBurn); - - /* double spAngMom = current.PeR * current.getOrbitalSpeedAt(current.GetTimeToPeriapsis() + Planetarium.GetUniversalTime()); - - Utility.LogMessage(this, "specific angular momentum = {0} PeR = {1} time to PE = {2} UT at PE = {3} speed at PE = {4}", - spAngMom, current.PeR, current.GetTimeToPeriapsis(), current.GetTimeToPeriapsis() + Planetarium.GetUniversalTime(), - current.getOrbitalSpeedAt(current.GetTimeToPeriapsis() + Planetarium.GetUniversalTime()));*/ - - double spAngMom = current.PeR * current.getOrbitalSpeedAtDistance(current.PeR); - - Utility.LogMessage(this, "specific angular momentum = {0} PeR = {1} PeA = {2} Speed at PeR = {3} speed at PeA = {4}", - spAngMom, current.PeR, current.PeA, current.getOrbitalSpeedAtDistance(current.PeR), current.getOrbitalSpeedAtDistance(current.PeA)); - - double vPreBurnCirc = spAngMom / newSMA; - - Utility.LogMessage(this, "preburn circumferential velocity = {0}", vPreBurnCirc); - - double elevationAngle = Math.Acos(vPreBurnCirc / vPreBurn); - - Utility.LogMessage(this, "Elevation angle = {0}", elevationAngle); - - double deltaVTotal = Math.Sqrt(Math.Pow(vNew, 2) + Math.Pow(vPreBurn, 2) - 2 * vNew * vPreBurn * Math.Cos(elevationAngle)); - - Utility.LogMessage(this, "deltaVTotal = {0}", deltaVTotal); - - /*double angleOffRetrograde = Math.Asin(vNew / vPreBurn); - - Utility.LogMessage(this, "angle off retrograde = {0}", angleOffRetrograde);*/ - - /*double deltaVPrograde = -deltaVTotal * Math.Cos(angleOffRetrograde); - double deltaVRadial = -deltaVTotal * Math.Sin(angleOffRetrograde);*/ - - double deltaVPrograde = - (vPreBurn - vNew * Math.Cos(elevationAngle)); - double deltaVRadial = -vNew * Math.Sin(elevationAngle); - - Utility.LogMessage(this, "prograde = {0}, radial = {1}", deltaVPrograde, deltaVRadial); - - Vector3d maneuverdV = Utility.ManeuverNode(deltaVPrograde, 0, deltaVRadial); - - Utility.LogMessage(this, "maneuverdV = {0}", maneuverdV); - - vessel.patchedConicSolver.maneuverNodes.Clear(); - ManeuverNode mn = vessel.patchedConicSolver.AddManeuverNode(maneuverUt); - mn.OnGizmoUpdated(maneuverdV, maneuverUt); - - //Vector3d posAtUt = current.getRelativePositionAtUT(maneuverUt); - //oUpper.UpdateFromStateVectors(posAtUt, maneuverVel.xzy, referenceBody, maneuverUt); - //Utility.LogMessage(this, "Circularize at {0:0.000}km: {1:0.000} x {2:0.000} @ {3:0.0}", - // newAltitude * 0.001, - // oUpper.ApA * 0.001, oUpper.PeA * 0.001, - // oUpper.inclination); - - return 1.0; - } - - return 0.0; - } - - /// - /// Generate a maneuver to conduct a Hohmann transfer to the current target. If there is no - /// target, or the transfer is not a simple transfer to a nearly co-planar target, nothing happens. - /// - /// Results are best when both the vessel and the target are in low-eccentricity orbits. - /// - /// Returns 1 if a transfer was successfully plotted, 0 otherwise. - public double HohmannTransfer() - { - if (hohmannTransfer) - { - Orbit current = vessel.orbit; - CelestialBody referenceBody = current.referenceBody; - double maneuverUt = Planetarium.GetUniversalTime() + timeUntilTransfer; - - Vector3d velAtUt = current.getOrbitalVelocityAtUT(maneuverUt); - - Vector3d relativePosition = current.getRelativePositionAtUT(maneuverUt); - - Vector3d prograde; - Vector3d normal; - Vector3d radial; - Utility.GetOrbitalBasisVectors(velAtUt, relativePosition, out prograde, out radial, out normal); - - Vector3d upAtUt = relativePosition.xzy.normalized; - Vector3d fwdAtUt = Vector3d.Cross(upAtUt, normal); - Vector3d maneuverVel = fwdAtUt * (initialDeltaV + Math.Sqrt(referenceBody.gravParameter / relativePosition.magnitude)); - //Utility.LogMessage(this, "insertion v = {0:0.0}, v @ insertion = {1:0.0}", initialDeltaV, velAtUt.magnitude); - - Vector3d deltaV = maneuverVel - velAtUt.xzy; -#if DEBUG_CHANGE_ALTITUDE - Utility.LogMessage(this, "deltaV = {0} because {1} - {2}", deltaV, maneuverVel, velAtUt.xzy); -#endif - //Utility.LogMessage(this, "prograde (dot) fwd = {0:0.000}", Vector3d.Dot(prograde, fwdAtUt)); - - Vector3d maneuverdV = Utility.ManeuverNode(Vector3d.Dot(deltaV, prograde), Vector3d.Dot(deltaV, normal), Vector3d.Dot(deltaV, radial)); -#if DEBUG_CHANGE_ALTITUDE - Utility.LogMessage(this, "maneuverdV = {0}", maneuverdV); -#endif - - vessel.patchedConicSolver.maneuverNodes.Clear(); - ManeuverNode mn = vessel.patchedConicSolver.AddManeuverNode(maneuverUt); - mn.OnGizmoUpdated(maneuverdV, maneuverUt); - - return 1.0; - } - - return 0.0; - } - - /// - /// Match the plane of the target's orbit. If there is no target, or the target is in a different sphere - /// of influence, this operation has no effect. - /// - /// 1 if a maneuver was plotted, 0 otherwise. - public double MatchPlane() - { - Orbit current = vessel.orbit; - Orbit target = vc.targetOrbit; - - if (MASFlightComputerProxy.ConvertVesselSituation(vessel.situation) > 2 && vessel.patchedConicSolver != null && current.eccentricity < 1.0 && target.referenceBody == current.referenceBody) - { - // In the test case I ran, this seemed to be the opposite of the ChangeInclination call. - // I may need to tweak the sign of deltaI2 based on whether we're changing inclination - // at the AN or DN. - double deltaI = current.inclination - target.inclination; - double deltaI2 = Utility.NormalizeLongitude(deltaI); - - if (Math.Abs(deltaI2) > 0.0) - { - Vector3d vesselNormal = vc.orbit.GetOrbitNormal(); - Vector3d targetNormal = vc.activeTarget.GetOrbit().GetOrbitNormal(); - Vector3d cross = Vector3d.Cross(vesselNormal, targetNormal); - double taAN = vc.orbit.GetTrueAnomalyOfZupVector(-cross); - double timeAN = Utility.NormalizeOrbitTime(vc.orbit.GetUTforTrueAnomaly(taAN, vc.orbit.period) - vc.universalTime, current) + vc.universalTime; - double taDN = vc.orbit.GetTrueAnomalyOfZupVector(cross); - double timeDN = Utility.NormalizeOrbitTime(vc.orbit.GetUTforTrueAnomaly(taDN, vc.orbit.period) - vc.universalTime, current) + vc.universalTime; - - Vector3d vAN = current.getOrbitalVelocityAtUT(timeAN); - - Vector3d vDN = current.getOrbitalVelocityAtUT(timeDN); - - double deltaVScale = Math.Sin(0.5 * Utility.Deg2Rad * deltaI2); - - // Pick the slower of the two nodes - double mTime; - double speedAtUt; - if (vAN.sqrMagnitude < vDN.sqrMagnitude) - { - mTime = timeAN; - speedAtUt = vAN.magnitude; - } - else - { - mTime = timeDN; - speedAtUt = vDN.magnitude; - } - - double planeChangedV = 2.0 * speedAtUt * deltaVScale; - - // This is interesting ... Eq. 4.73 at http://www.braeunig.us/space/ describes the ΔV - // needed to change inclination. However, if I apply that strictly as a ΔV on the - // normal, the resulting orbit is not correct. Looking at the results MechJeb generates, - // I notice that there's a retrograde ΔV equal to sin(Θ/2) (which MJ doesn't directly compute - - // it's an artifact of its technique). So, first I determine the prograde component of - // the maneuver, and then I apply the balance of the ΔV to the normal. - double progradedV = -Math.Abs(deltaVScale * planeChangedV); - double normaldV = Math.Sqrt(planeChangedV * planeChangedV - progradedV * progradedV) * Math.Sign(deltaVScale); - Vector3d maneuver = Utility.ManeuverNode(progradedV, normaldV, 0.0); - - vessel.patchedConicSolver.maneuverNodes.Clear(); - ManeuverNode mn = vessel.patchedConicSolver.AddManeuverNode(mTime); - mn.OnGizmoUpdated(maneuver, mTime); - - // TODO: When I'm more awake, I should do this analytically by deciding whether the change - // in inclination is normal or anti-normal at the time of the maneuver. - Vector3d newVesselNormal = vessel.patchedConicSolver.maneuverNodes[0].nextPatch.GetOrbitNormal(); - if (Vector3.Angle(newVesselNormal, targetNormal) > Mathf.Abs((float)deltaI)) - { - maneuver.y = -maneuver.y; - vessel.patchedConicSolver.maneuverNodes.Clear(); - mn = vessel.patchedConicSolver.AddManeuverNode(mTime); - mn.OnGizmoUpdated(maneuver, mTime); - } - - return 1.0; - } - } - - return 0.0; - } - - /// - /// Match velocities with the target at the moment of closest approach. If there is no target, - /// or the target is in a different sphere of influence, the operation has no effect. - /// - /// 1 if a maneuver was plotted, 0 otherwise. - public double MatchVelocities() - { - if (vc.activeTarget != null && vc.targetOrbit.referenceBody == vessel.mainBody && vessel.patchedConicSolver != null && vc.targetClosestUT > Planetarium.GetUniversalTime()) - { - double maneuverUt = vc.targetClosestUT; - Orbit current = vessel.orbit; - Vector3d velAtUt = current.getOrbitalVelocityAtUT(maneuverUt); - Vector3d tgtAtUt = vc.targetOrbit.getOrbitalVelocityAtUT(maneuverUt); - - Vector3d relativePosition = current.getRelativePositionAtUT(maneuverUt); - - Vector3d prograde; - Vector3d normal; - Vector3d radial; - Utility.GetOrbitalBasisVectors(velAtUt, relativePosition, out prograde, out radial, out normal); - - Vector3d deltaV = tgtAtUt - velAtUt; - - Vector3d maneuverdV = Utility.ManeuverNode(Vector3d.Dot(deltaV, prograde), Vector3d.Dot(deltaV, normal), Vector3d.Dot(deltaV, radial)); -#if DEBUG_CHANGE_ALTITUDE - Utility.LogMessage(this, "maneuverdV = {0}", maneuverdV); -#endif - - vessel.patchedConicSolver.maneuverNodes.Clear(); - ManeuverNode mn = vessel.patchedConicSolver.AddManeuverNode(maneuverUt); - mn.OnGizmoUpdated(maneuverdV, maneuverUt); - - return 1.0; - } - - return 0.0; - } - - /// - /// Return from a moon with a resulting altitude over the planet of 'newAltitude', in meters. - /// If the vessel is orbiting the Sun, this command has no effect. - /// - /// This maneuver may be used to eject from Kerbin (or any other planet) to a solar orbit with - /// a specified altitude over the sun. - /// - /// Altitude of the desired orbit around the parent world, in meters. - /// 1 if the maneuver was plotted, 0 otherwise. - public double ReturnFromMoon(double newAltitude) - { - // The last condition listed here is because the sun's referenceBody is - // itself. - if (vessel.mainBody.referenceBody != null && vessel.patchedConicSolver != null && vessel.mainBody.referenceBody != vessel.mainBody) - { - CelestialBody moon = vessel.mainBody; - CelestialBody planet = moon.referenceBody; - - double newRadius = newAltitude + planet.Radius; - double returnDeltaV = DeltaVInitial(moon.orbit.semiMajorAxis, newRadius, planet.gravParameter); - double currentEjectionAngle = ComputeEjectionAngle(vessel.orbit); - - double ejectiondV, returnEjectionAngle, timeUntilReturn; - UpdateEjectionParameters(vessel.orbit, returnDeltaV, currentEjectionAngle, out ejectiondV, out returnEjectionAngle, out timeUntilReturn); - - double maneuverUt = timeUntilReturn + Planetarium.GetUniversalTime(); - - Vector3d maneuverdV = Utility.ManeuverNode(ejectiondV, 0.0, 0.0); - - vessel.patchedConicSolver.maneuverNodes.Clear(); - ManeuverNode mn = vessel.patchedConicSolver.AddManeuverNode(maneuverUt); - mn.OnGizmoUpdated(maneuverdV, maneuverUt); - } - - return 0.0; - } - - #endregion - - /// - /// The Oberth Effect region provides information specific to taking advantage - /// of the Oberth Effect when transferring from a moon to another planet. These - /// fields assume the vessel will eject from the moon to the `OberthAltitude` - /// over the planet, - /// from which it will fire the interplanetary ejection burn. - /// - /// If the vessel is not in a situtation where the Oberth Effect would be applicable, - /// these fields all return 0. - /// - #region Transfer Oberth Effect - - /// - /// The current ejection angle for the moon in degrees. When this value matches - /// `TransferOberthEjectionAngle()`, it is time to do the moon ejection burn. - /// - /// Current ejection angle over the moon in degrees, or 0. - public double CurrentOberthEjectionAngle() - { - if (vc.activeTarget != null) - { - if (invalid) - { - UpdateTransferParameters(); - } - - return oberthCurrentEjectionAngle; - } - else - { - return 0.0; - } - } - - /// - /// The preferred altitude over the parent planet for the - /// interplanetary ejection burn, in meters. - /// If the vessel's target - /// is not another world, or the vessel does not currently orbit a moon, returns 0. - /// - /// Altitude in meters, or 0. - public double OberthAltitude() - { - if (vc.activeTarget != null) - { - if (invalid) - { - UpdateTransferParameters(); - } - - return oberthAltitude; - } - else - { - return 0.0; - } - } - - /// - /// Returns the ΔV in m/s required for the Oberth effect transfer ejection burn. - /// The vessel's altitude orbiting the moon's parent should match `OberthAltitude()` - /// after this burn. - /// If the vessel's target - /// is not another world, or the vessel does not currently orbit a moon, returns 0. - /// - /// ΔV in m/s or 0. - public double OberthEjectionDeltaV() - { - if (vc.activeTarget != null) - { - if (invalid) - { - UpdateTransferParameters(); - } - - return oberthEjectionVelocity; - } - else - { - return 0.0; - } - } - - /// - /// Returns the relative ejection angle for an Oberth effect tranfers. When this - /// value reaches 0, it is time to burn. - /// If the vessel's target - /// is not another world, or the vessel does not currently orbit a moon, returns 0. - /// - /// Relative angle in degrees, or 0. - public double RelativeOberthEjectionAngle() - { - if (vc.activeTarget != null) - { - if (invalid) - { - UpdateTransferParameters(); - } - - return Utility.NormalizeAngle(oberthCurrentEjectionAngle - oberthTransferEjectionAngle); - } - else - { - return 0.0; - } - } - - /// - /// Returns the time until the ejection burn must begin for an Oberth effect tranfer, in seconds. - /// If the vessel's target - /// is not another world, or the vessel does not currently orbit a moon, returns 0. - /// - /// Time in seconds until the burn, or 0. - public double TimeUntilOberthEjectionAngle() - { - if (vc.activeTarget != null) - { - if (invalid) - { - UpdateTransferParameters(); - } - - return oberthTimeUntilEjection; - } - else - { - return 0.0; - } - } - - /// - /// Returns the ejection angle required to initiate an ejection from the moon's orbit - /// to the moon's parent world for an interplanetary transfer. If the vessel's target - /// is not another world, or the vessel does not currently orbit a moon, returns 0. - /// - /// Required ejection angle in degrees, or 0. - public double TransferOberthEjectionAngle() - { - if (vc.activeTarget != null) - { - if (invalid) - { - UpdateTransferParameters(); - } - - return oberthTransferEjectionAngle; - } - else - { - return 0.0; - } - } - #endregion - - /// - /// The Phase Angle section provides measurements of the phase angle, the - /// measure of the angle created by drawing lines from the body being - /// orbited to the vessel and to the target. This angle shows relative - /// position of the two objects, and it is continuously changing as long as - /// the craft are not in the same orbit. - /// - /// To do a Hohmann transfer between orbits, the vessel should initiate a - /// burn when its current phase angle reaches the transfer phase angle. - /// Alternatively, when the relative phase angle reaches 0, initiate a burn. - /// - #region Transfer Phase Angle - /// - /// Returns the current phase angle between the vessel and its target. - /// - /// Current phase angle in degrees, from 0 to 360. - public double CurrentPhaseAngle() - { - if (vc.activeTarget != null) - { - if (invalid) - { - UpdateTransferParameters(); - } - - return currentPhaseAngle; - } - else - { - return 0.0; - } - } - - /// - /// Returns the difference (in degrees) between the current phase angle - /// and the transfer phase angle. When this value reaches 0, it is time - /// to start the transfer burn. If there is no valid target, this value - /// is 0. - /// - /// The difference between the transfer phase angle and the current - /// phase angle in degrees, ranging from 0 to 360. - public double RelativePhaseAngle() - { - if (vc.activeTarget != null) - { - if (invalid) - { - UpdateTransferParameters(); - } - - return Utility.NormalizeAngle(currentPhaseAngle - transferPhaseAngle); - } - else - { - return 0.0; - } - } - - /// - /// Returns the time in seconds until the vessel reaches the correct - /// phase angle for initiating a burn to transfer to the target. - /// - /// Time until transfer, in seconds, or 0 if there is no solution. - public double TimeUntilPhaseAngle() - { - if (vc.activeTarget != null) - { - if (invalid) - { - UpdateTransferParameters(); - } - - return timeUntilTransfer; - } - else - { - return 0.0; - } - } - - /// - /// Returns the phase angle required to initiate a Hohmann - /// transfer orbit. This is the absolute phase angle, so it - /// does not vary over time when comparing two stable orbits. - /// Use `fc.RelativePhaseAngle()` to count down to a transfer. - /// - /// Returns 0 if there is no active target. - /// - /// Required phase angle in degrees (always betweeen 0 - /// and 180). - public double TransferPhaseAngle() - { - if (vc.activeTarget != null) - { - if (invalid) - { - UpdateTransferParameters(); - } - - return transferPhaseAngle; - } - else - { - return 0.0; - } - } - - #endregion - -#if COMPARE_PHASE_ANGLE_PROTRACTOR - private static double ProjectAngle2D(Vector3d a, Vector3d b) - { - Vector3d ray1 = Vector3d.Project(new Vector3d(a.x, 0.0, a.z), a); - Vector3d ray2 = Vector3d.Project(new Vector3d(b.x, 0.0, b.z), b); - - double phase = Vector3d.Angle(ray1, ray2); - - Vector3d ap = Quaternion.AngleAxis(90.0f, Vector3d.forward) * a; - Vector3d ray1p = Vector3d.Project(new Vector3d(ap.x, 0.0, ap.z), ap); - if (Vector3d.Angle(ray1p, ray2) > 90.0) - { - phase = 360.0 - phase; - } - - return Utility.NormalizeAngle(phase); - } - private static double Angle2d(Vector3d vector1, Vector3d vector2) - { - Vector3d v1 = Vector3d.Project(new Vector3d(vector1.x, 0, vector1.z), vector1); - Vector3d v2 = Vector3d.Project(new Vector3d(vector2.x, 0, vector2.z), vector2); - return Vector3d.Angle(v1, v2); - } -#endif - - // For a given orbit, find the orbit of the object that orbits the sun. - // - // If the orbit provided orbits the sun, this orbit is returned. If the - // orbit is around a body that orbits the sun, the body's orbit is returned. - // If the orbit is around a moon, return the orbit of the moon's parent. - static private Orbit GetSolarOrbit(Orbit orbit, out int numHops) - { - // Does this object orbit the sun? - if (orbit.referenceBody == Planetarium.fetch.Sun) - { - numHops = 0; - return orbit; - } - // Does this object orbit something that orbits the sun? - else if (orbit.referenceBody.GetOrbit().referenceBody == Planetarium.fetch.Sun) - { - numHops = 1; - return orbit.referenceBody.GetOrbit(); - } - // Does this object orbit the moon of something that orbits the sun? - else if (orbit.referenceBody.GetOrbit().referenceBody.GetOrbit().referenceBody == Planetarium.fetch.Sun) - { - numHops = 2; - return orbit.referenceBody.GetOrbit().referenceBody.GetOrbit(); - } - else - { - // Nothing in stock KSP orbits more than two levels deep. - throw new ArgumentException("GetSolarOrbit(): Unable to find a valid solar orbit."); - } - } - - // Find the orbits we can use to determine phase angle. These orbits - // need to share a common reference body. We also report how many parent - // bodies we had to look at to find the returned vesselOrbit, so we can - // compute the correct ejection angle (either ejection angle, or moon ejection - // angle for Oberth transfers). - static private void GetCommonOrbits(ref Orbit vesselOrbit, ref Orbit destinationOrbit, out int vesselOrbitSteps) - { - if (vesselOrbit.referenceBody == destinationOrbit.referenceBody) - { - // Orbiting the same body. Easy case. We're done. - vesselOrbitSteps = 0; - } - else if (vesselOrbit.referenceBody == Planetarium.fetch.Sun) - { - // We orbit the sun. Find the orbit of whichever parent - // of the target orbits the sun: - int dontCare; - destinationOrbit = GetSolarOrbit(destinationOrbit, out dontCare); - vesselOrbitSteps = 0; - } - else if (destinationOrbit.referenceBody == Planetarium.fetch.Sun) - { - // The target orbits the sun, but we don't. - vesselOrbit = GetSolarOrbit(vesselOrbit, out vesselOrbitSteps); - } - else - { - // Complex case... - int dontCare; - Orbit newVesselOrbit = GetSolarOrbit(vesselOrbit, out vesselOrbitSteps); - Orbit newDestinationOrbit = GetSolarOrbit(destinationOrbit, out dontCare); - - if (newVesselOrbit == newDestinationOrbit) - { - // Even more complex case. Source and destination orbit are in the - // same planetary system, but one or both orbit moons. - if (vesselOrbitSteps == 2) - { - // Vessel orbits a moon. - vesselOrbit = vesselOrbit.referenceBody.GetOrbit(); - vesselOrbitSteps = 1; - } - if (dontCare == 2) - { - destinationOrbit = destinationOrbit.referenceBody.GetOrbit(); - } - } - else - { - vesselOrbit = newVesselOrbit; - destinationOrbit = newDestinationOrbit; - } - } - } - - // Compute the delta-V required for the injection and circularization burns for the - // given orbits. - // - // Equation from http://www.braeunig.us/space/ - private void UpdateTransferDeltaV(Orbit startOrbit, Orbit destinationOrbit) - { - double GM = startOrbit.referenceBody.gravParameter; - double rA = startOrbit.semiMajorAxis; - double rB = destinationOrbit.semiMajorAxis; - - double atx = 0.5 * (rA + rB); - double Vi = Math.Sqrt(GM / rA); // Velocity of a circular orbit at radius A - double Vf = Math.Sqrt(GM / rB); // Velocity of a circular orbit at radius B - - double Vtxi = Math.Sqrt(GM * (2.0 / rA - 1.0 / atx)); - double Vtxf = Math.Sqrt(GM * (2.0 / rB - 1.0 / atx)); - - initialDeltaV = Vtxi - Vi; - finalDeltaV = Vf - Vtxf; - } - - // Computes the delta-V for the initial burn of a Hohmann transfer. Assumes circular - // initial orbit. - private static double DeltaVInitial(double startRadius, double endRadius, double GM) - { - double atx = 0.5 * (startRadius + endRadius); - double Vi = Math.Sqrt(GM / startRadius); - - double Vtxi = Math.Sqrt(GM * (2.0 / startRadius - 1.0 / atx)); - - return Vtxi - Vi; - } - - // Allows actual velocity at startRadius to be applied, which may lead to quicker convergence - // on the desired dV. - private static double DeltaVInitial(double startRadius, double endRadius, double GM, double velocityAtStartRadius) - { - double atx = 0.5 * (startRadius + endRadius); - //double Vi = Math.Sqrt(GM / startRadius); - - double Vtxi = Math.Sqrt(GM * (2.0 / startRadius - 1.0 / atx)); - - return Vtxi - velocityAtStartRadius; - } - - // Determine the current ejection angle (angle from parent body's prograde) - private static double ComputeEjectionAngle(Orbit o) - { - Vector3d vesselPos = o.pos.xzy; - vesselPos.Normalize(); - Vector3d bodyProgradeVec = o.referenceBody.orbit.vel.xzy; - bodyProgradeVec.Normalize(); - Vector3d bodyPosVec = o.referenceBody.orbit.pos; - double currentEjectionAngle = Vector3d.Angle(vesselPos, bodyProgradeVec); - if (Vector3d.Dot(vesselPos, bodyPosVec) > 0.0) - { - currentEjectionAngle = Utility.NormalizeAngle(360.0 - currentEjectionAngle); - } - return currentEjectionAngle; - } - - // Update ejection parameters - private static void UpdateEjectionParameters(Orbit o, double departureDeltaV, double currentEjectionAngle, out double ejectionDeltaV, out double transferEjectionAngle, out double timeUntilEjection) - { - double r1 = o.semiMajorAxis; - double r2 = o.referenceBody.sphereOfInfluence; - double GM = o.referenceBody.gravParameter; - double v2 = departureDeltaV; - - bool raiseAltitude = (departureDeltaV > 0.0); - - // Absolute velocity required, not delta-V. - double ejectionVelocity = Math.Sqrt((r1 * (r2 * v2 * v2 - 2.0 * GM) + 2.0 * r2 * GM) / (r1 * r2)); - - double eps = ejectionVelocity * ejectionVelocity * 0.5 - GM / r1; - double h = r1 * ejectionVelocity; - double e = Math.Sqrt(1.0 + 2.0 * eps * h * h / (GM * GM)); - double theta = Math.Acos(1.0 / e) * Utility.Rad2Deg; - transferEjectionAngle = Utility.NormalizeAngle(((raiseAltitude) ? 180.0 : 360.0) - theta); - - // Figure out how long until we cross that angle - double orbitFraction = Utility.NormalizeAngle(currentEjectionAngle - transferEjectionAngle) / 360.0; - timeUntilEjection = orbitFraction * o.period; - - try - { - double oVel = o.getOrbitalSpeedAt(Planetarium.GetUniversalTime() + timeUntilEjection); - - // Convert ejectionVelocity into ejection delta-V. - ejectionDeltaV = ejectionVelocity - oVel; - } - catch - { - // Orbit.getOrbitalSpeedAt() can fail for high eccentricity. Trap those and - // jam ejectionDeltaV to 0 (we must already be ejecting?). - ejectionDeltaV = 0.0; - } - } - - // Updater method - called at most once per FixedUpdate when the - // transfer parameters are being queried. - private void UpdateTransferParameters() - { - // Initialize values - hohmannTransfer = false; - currentPhaseAngle = 0.0; - transferPhaseAngle = 0.0; - timeUntilTransfer = 0.0; - - ejectionDeltaV = 0.0; - currentEjectionAngle = 0.0; - transferEjectionAngle = 0.0; - timeUntilEjection = 0.0; - - oberthAltitude = 0.0; - oberthEjectionVelocity = 0.0; - oberthCurrentEjectionAngle = 0.0; - oberthTransferEjectionAngle = 0.0; - oberthTimeUntilEjection = 0.0; - - initialDeltaV = 0.0; - finalDeltaV = 0.0; - - if (vc.activeTarget != null) - { - Orbit vesselOrbit = vessel.orbit; - Orbit destinationOrbit = vc.activeTarget.GetOrbit(); - - if (vesselOrbit.eccentricity >= 1.0 || destinationOrbit.eccentricity >= 1.0) - { - // One or both orbits are escape orbits. We can't work with them. - return; - } - - // Orbit steps counts how many levels of orbital parents we have to step across - // to find a common orbit with our target. - // 0 means both orbit the same body (simple Hohmann transfer case). - // 1 means the vessel orbits a planet, and it must transfer to another planet, - // OR a vessel orbits a moon, and it must transfer to another moon. - // 2 means the vessel orbits a moon, and it must transfer to another planet. - int vesselOrbitSteps; - GetCommonOrbits(ref vesselOrbit, ref destinationOrbit, out vesselOrbitSteps); - - // Figure out what sort of transfer we're doing. - if (vesselOrbit.referenceBody != destinationOrbit.referenceBody) - { - // We can't find a common orbit? - Utility.LogError(this, "Bailing out compute transfer parameters: unable to reconcile orbits"); - return; - } - - // TODO: At what relative inclination should it bail out? - if (Vector3.Angle(vesselOrbit.GetOrbitNormal(), destinationOrbit.GetOrbitNormal()) > 30.0) - { - // Relative inclination is very out-of-spec. Bail out. - return; - } - - hohmannTransfer = (vessel.orbit.referenceBody == vc.activeTarget.GetOrbit().referenceBody); - UpdateTransferDeltaV(vesselOrbit, destinationOrbit); - - // transfer phase angle from https://en.wikipedia.org/wiki/Hohmann_transfer_orbit - // This does not do anything special for Oberth effect transfers - transferPhaseAngle = 180.0 * (1.0 - 0.35355339 * Math.Pow(vesselOrbit.semiMajorAxis / destinationOrbit.semiMajorAxis + 1.0, 1.5)); - -#if COMPARE_PHASE_ANGLE_PROTRACTOR - // current phase angle: the angle between the two positions as projected onto a 2D plane. - Vector3d pos1 = vesselOrbit.getRelativePositionAtUT(vc.universalTime); - Vector3d pos2 = destinationOrbit.getRelativePositionAtUT(vc.universalTime); - - double protractorPhaseAngle = ProjectAngle2D(pos1, pos2); -#endif - // Use orbital parameters. Note that the argumentOfPeriapsis and LAN - // are both in degrees, while true anomaly is in radians. - double tA1 = (vesselOrbit.trueAnomaly * Orbit.Rad2Deg + vesselOrbit.argumentOfPeriapsis + vesselOrbit.LAN); - double tA2 = (destinationOrbit.trueAnomaly * Orbit.Rad2Deg + destinationOrbit.argumentOfPeriapsis + destinationOrbit.LAN); - currentPhaseAngle = Utility.NormalizeAngle(tA2 - tA1); - -#if COMPARE_PHASE_ANGLE_PROTRACTOR - if (Math.Abs(currentPhaseAngle - protractorPhaseAngle) > 0.5) - { - Utility.LogMessage(this, "Protractor phase angle = {0,7:0.00}; trueAnomaly pa = {1,7:0.00}, diff = {2,7:0.00}", protractorPhaseAngle, currentPhaseAngle, currentPhaseAngle - protractorPhaseAngle); - } -#endif - - // The difference in mean motion tells us how quickly the phase angle is changing. - // Since Orbit.meanMotion is in rad/sec, we need to convert the difference to deg/sec. - double deltaRelativePhaseAngle = (vesselOrbit.meanMotion - destinationOrbit.meanMotion) * Orbit.Rad2Deg; - - if (deltaRelativePhaseAngle > 0.0) - { - timeUntilTransfer = Utility.NormalizeAngle(currentPhaseAngle - transferPhaseAngle) / deltaRelativePhaseAngle; - } - else if (deltaRelativePhaseAngle < 0.0) - { - timeUntilTransfer = Utility.NormalizeAngle(transferPhaseAngle - currentPhaseAngle) / deltaRelativePhaseAngle; - } - // else can't compute it - the orbits have the exact same period. Time already zero'd. - - // Compute current ejection angle -#if COMPARE_PHASE_ANGLE_PROTRACTOR - //--- PROTRACTOR - Vector3d vesselvec = vessel.orbit.getRelativePositionAtUT(Planetarium.GetUniversalTime()); - - // get planet's position relative to universe - Vector3d bodyvec = vessel.mainBody.orbit.getRelativePositionAtUT(Planetarium.GetUniversalTime()); - - Vector3d forwardVec = Quaternion.AngleAxis(90.0f, Vector3d.forward) * bodyvec; - forwardVec.Normalize(); - double protractorEject = Angle2d(vesselvec, Quaternion.AngleAxis(90.0f, Vector3d.forward) * bodyvec); - - if (Angle2d(vesselvec, Quaternion.AngleAxis(180.0f, Vector3d.forward) * bodyvec) > Angle2d(vesselvec, bodyvec)) - { - protractorEject = 360.0 - protractorEject;//use cross vector to determine up or down - } - //--- PROTRACTOR -#endif - - if (vesselOrbitSteps == 1) - { - //Vector3d vesselPos = vessel.orbit.pos; - //vesselPos.Normalize(); - //Vector3d bodyProgradeVec = vessel.mainBody.orbit.vel; - //bodyProgradeVec.Normalize(); - //Vector3d bodyPosVec = vessel.mainBody.orbit.pos; - //currentEjectionAngle = Vector3d.Angle(vesselPos, bodyProgradeVec); - //if (Vector3d.Dot(vesselPos, bodyPosVec) > 0.0) - //{ - // currentEjectionAngle = Utility.NormalizeAngle(360.0 - currentEjectionAngle); - //} - - currentEjectionAngle = ComputeEjectionAngle(vessel.orbit); -#if COMPARE_PHASE_ANGLE_PROTRACTOR - Utility.LogMessage(this, "Protractor ejection angle = {0,5:0.0} , computed = {1,5:0.0}", protractorEject, currentEjectionAngle); -#endif - - UpdateEjectionParameters(vessel.orbit, initialDeltaV, currentEjectionAngle, out ejectionDeltaV, out transferEjectionAngle, out timeUntilEjection); - } - else if (vesselOrbitSteps == 2) - { - CelestialBody moon = vessel.orbit.referenceBody; - CelestialBody planet = moon.referenceBody; - - currentEjectionAngle = ComputeEjectionAngle(moon.orbit); - - // Compute ejection parameters based on the orbital parameters of the moon we are orbiting. - UpdateEjectionParameters(moon.orbit, initialDeltaV, currentEjectionAngle, out ejectionDeltaV, out transferEjectionAngle, out timeUntilEjection); - - // TODO: Compute moon ejection angle to take advantage of the Oberth effect. - oberthAltitude = 0.05 * (planet.Radius + planet.atmosphereDepth); - - double oberthAltitudeDeltaV = DeltaVInitial(moon.orbit.semiMajorAxis, oberthAltitude + planet.Radius, planet.gravParameter); - - // Compute the moon-specific parameters - oberthCurrentEjectionAngle = ComputeEjectionAngle(vessel.orbit); - UpdateEjectionParameters(vessel.orbit, oberthAltitudeDeltaV, oberthCurrentEjectionAngle, out oberthEjectionVelocity, out oberthTransferEjectionAngle, out oberthTimeUntilEjection); - - // Delta-V required to target the ejection altitude. - //oberthEjectionVelocity = 0.0; - } - } - - invalid = false; - } - - /// - /// Called per-FixedUpdate to invalidate computations. - /// - [MoonSharpHidden] - internal void Update() - { - invalid = true; - } - } -} diff --git a/ModifiedCode/MASVesselComputer.cs b/ModifiedCode/MASVesselComputer.cs deleted file mode 100644 index 3fd63cef..00000000 --- a/ModifiedCode/MASVesselComputer.cs +++ /dev/null @@ -1,1499 +0,0 @@ -/***************************************************************************** - * The MIT License (MIT) - * - * Copyright (c) 2016-2020 MOARdV - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to - * deal in the Software without restriction, including without limitation the - * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or - * sell copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - * - ****************************************************************************/ -using System; -using System.Collections.Generic; -using System.Text; -using UnityEngine; - -namespace AvionicsSystems -{ - /// - /// The MASVesselComputer encompasses all of the per-vessel data tracking - /// used in Avionics Systems. As such, it's entirely concerned with keeping - /// tabs on data, but not much else. - /// - internal partial class MASVesselComputer : MonoBehaviour - { - internal enum ReferenceType - { - Unknown, - Self, - RemoteCommand, - DockingPort, - Claw - }; - - /// - /// Our current reference transform. - /// - private Transform _referenceTransform; - internal Transform referenceTransform - { - get - { - if (_referenceTransform == null) - { - UpdateReferenceTransform(vessel.GetReferenceTransformPart(), true); - } - return _referenceTransform; - } - } - - /// - /// Type of object that the reference transform is attached to. - /// - internal ReferenceType referenceTransformType; - - /// - /// Local copy of the current orbit. This is updated per fixed-update - /// so we're not querying an indeterminate-cost property of Vessel. - /// - internal Orbit orbit; - - /// - /// The vessel this module represents. - /// - private Vessel vessel; - - /// - /// A copy of the module's vessel ID, in case vessel is null'd before OnDestroy fires. - /// - private Guid vesselId; - - /// - /// Whether the vessel needs MASVC support (has at least one crew). - /// - internal bool vesselCrewed; - - /// - /// Whether the vessel is actually loaded and active. - /// - internal bool vesselActive; - - /// - /// Boolean used to detect double-clicks during IVA, which would cause - /// the vessel to lose target track. - /// - private bool doubleClickDetected = false; - - /// - /// What world / star are we orbiting? - /// - internal CelestialBody mainBody; - - /// - /// Current UT. - /// - internal double universalTime; - - /// - /// Debug 'registers'. Used so I can draw values out of code for viewing in real- - /// time without using message logging. - /// - internal object[] debugValue = new object[4]; - - /// - /// Wrapper method for all of the subcategories of data that are - /// refreshed per-FixedUpdate. - /// - private void RefreshData() - { - // First step: - PrepareResourceData(); - - UpdateModuleData(); - try - { - UpdateAttitude(); - } - catch(Exception e) - { - throw new ArgumentException("Error in UpdateAttitude:" + e.Source + e.TargetSite + e.Data + e.StackTrace, e); - } - UpdateAltitudes(); - UpdateManeuverNode(); - UpdateOwnDockingPorts(); - UpdateTarget(); - UpdateMisc(); - // Last step: - ProcessResourceData(); - } - - private void UpdateThermals() - { - double difference = float.MaxValue; - - double currentHottest = 0.0; - for (int partIdx = vessel.parts.Count - 1; partIdx >= 0; --partIdx) - { - Part part = vessel.parts[partIdx]; - double maxTemp = part.maxTemp; - double currentTemp = part.temperature; - if (maxTemp - currentTemp < difference) - { - _hottestPartMax = maxTemp; - currentHottest = currentTemp; - difference = maxTemp - currentTemp; - } - maxTemp = part.skinMaxTemp; - currentTemp = part.skinTemperature; - if (maxTemp - currentTemp < difference) - { - _hottestPartMax = maxTemp; - currentHottest = currentTemp; - difference = maxTemp - currentTemp; - } - } - _hottestPartSign = Math.Sign(_hottestPart - currentHottest); - _hottestPart = currentHottest; - } - private double _hottestPart; - private double _hottestPartMax; - private float _hottestPartSign; - internal double hottestPart - { - get - { - if (_hottestPartMax == -1.0) - { - UpdateThermals(); - } - return _hottestPart; - } - } - internal double hottestPartSign - { - get - { - if (_hottestPartMax == -1.0) - { - UpdateThermals(); - } - return _hottestPartSign; - } - } - internal double hottestPartMax - { - get - { - if (_hottestPartMax == -1.0) - { - UpdateThermals(); - } - return _hottestPartMax; - } - } - - #region Monobehaviour - /// - /// Update per-Vessel fields. - /// - private void FixedUpdate() - { - // Compute delta-t from Planetarium time so if the vessel goes - // inactive for a while, it won't resume with an inaccurate - // time ... or is that even worth the extra effort? - if (vesselCrewed && vesselActive) - { - // TODO: Can I make these two update by callback? - mainBody = vessel.mainBody; - orbit = vessel.orbit; - _hottestPartMax = -1.0; - - universalTime = Planetarium.GetUniversalTime(); - - // GetReferenceTransformPart() seems to be pointing at the - // previous part when the callback fires, so I use this hack - // to manually recompute it here. - UpdateReferenceTransform(vessel.GetReferenceTransformPart(), false); - - // If there was a mouse double-click event, and we think there's - // a target, and KSP says there isn't a target, the user likely - // double-clicked in the IVA and accidentally cleared the active - // target. Let's fix that for them. - // - // However, there seems to be a one-update delay in the change - // registering: - // 1) LateUpdate sees a double-click. - // 2) FixedUpdate shows a VesselTarget in FlightGlobals. - // 3) LateUpdate does not see a double-click. - // 4) FixedUpdate shows the target is cleared. - // - // I could do a countdown timer instead of a boolean, I suppose. - if (doubleClickDetected) - { - if (activeTarget == null) - { - //Utility.LogMessage(this, "doubleClick corrector: no target expected (active = {0}, FG = {1})", - // activeTarget != null, FlightGlobals.fetch.VesselTarget != null); - doubleClickDetected = false; - } - else if (activeTarget != null && FlightGlobals.fetch.VesselTarget == null) - { - FlightGlobals.fetch.SetVesselTarget(activeTarget); - //Utility.LogMessage(this, "doubleClick corrector: resetting"); - doubleClickDetected = false; - } - //else - //{ - // Utility.LogMessage(this, "doubleClick corrector: no-op (active = {0}, FG = {1})", - // activeTarget != null, FlightGlobals.fetch.VesselTarget != null); - //} - } - - RefreshData(); - - //Utility.LogMessage(this, "FixedUpdate for {0}", vessel.id); - } - } - - /// - /// We use the LateUpdate() (why? - RPM did it here, but I don't know if - /// it *needs* to be here) to look for double-click events, which happen - /// too easily when playing in IVA. The double-click clears targets, which - /// can be a problem. - /// - public void LateUpdate() - { - if (vesselActive) - { - doubleClickDetected |= Mouse.Left.GetDoubleClick(); - //Utility.LogMessage(this, "LateUpdate: doubleClick = {0} ({1})", doubleClickDetected, Mouse.Left.GetDoubleClick()); - } - else - { - doubleClickDetected = false; - } - } - - /// - /// Initialize some fields to safe values (or expected unchanging values). - /// The vessel fields of VesselComputer doesn't have good values yet, so this - /// step is only good for non-specific initial values. - /// - public void Awake() - { - if (HighLogic.LoadedSceneIsFlight == false) - { - Utility.LogWarning(this, "Someone is creating a vessel computer outside of flight!"); - } - - vessel = gameObject.GetComponent(); - if (vessel == null) - { - throw new ArgumentNullException("[MASVesselComputer] Awake(): Could not find the vessel!"); - } - //Utility.LogMessage(this, "Awake() for {0}", vessel.id); - - mainBody = vessel.mainBody; - vesselId = vessel.id; - orbit = vessel.orbit; - - universalTime = Planetarium.GetUniversalTime(); - - InitResourceData(); - - UpdateReferenceTransform(vessel.GetReferenceTransformPart(), true); - //vesselCrewed = (vessel.GetCrewCount() > 0); - vesselCrewed = true; - vesselActive = ActiveVessel(vessel); - if (vesselCrewed) - { - RefreshData(); - } - - GameEvents.OnCameraChange.Add(onCameraChange); - GameEvents.onStageActivate.Add(onStageActivate); - GameEvents.onVesselChange.Add(onVesselChange); - GameEvents.onVesselSOIChanged.Add(onVesselSOIChanged); - GameEvents.onVesselWasModified.Add(onVesselWasModified); - - // onDominantBodyChange - } - - /// - /// This vessel is being scrapped. Release modules. - /// - private void OnDestroy() - { - //Utility.LogMessage(this, "OnDestroy for {0}", vesselId); - - GameEvents.OnCameraChange.Remove(onCameraChange); - GameEvents.onStageActivate.Remove(onStageActivate); - GameEvents.onVesselChange.Remove(onVesselChange); - GameEvents.onVesselSOIChanged.Remove(onVesselSOIChanged); - GameEvents.onVesselWasModified.Remove(onVesselWasModified); - - TeardownResourceData(); - - vesselId = Guid.Empty; - orbit = null; - mainBody = null; - activeTarget = null; - } - #endregion - - #region Vessel Data - - /// - /// Helper method to determine if the vessel is the active IVA vessel, - /// since we don't want to burn cycles on vessels whose IVA doesn't exist. - /// - /// - /// - private static bool ActiveVessel(Vessel vessel) - { - // This does not account for the stock overlays. However, that - // required iterating over the cameras list to find - // "InternalSpaceOverlay Host". At least, in 1.1.3. - return vessel.isActiveVessel && (CameraManager.Instance.currentCameraMode == CameraManager.CameraMode.IVA || CameraManager.Instance.currentCameraMode == CameraManager.CameraMode.Internal); - } - - internal ModuleDockingNode[] ownDockingPorts = new ModuleDockingNode[0]; - - private void UpdateOwnDockingPorts() - { - //Vessel targetVessel = (targetType == TargetType.Vessel) ? (activeTarget as Vessel) : (activeTarget as ModuleDockingNode).vessel; - //if (!targetVessel.packed && targetVessel.loaded) - //{ - List potentialOwnDocks = vessel.FindPartModulesImplementing(); - List validOwnDocks = new List(); - - if (dockingNode != null) - { - for (int i = potentialOwnDocks.Count - 1; i >= 0; --i) - { - ModuleDockingNode checkDock = potentialOwnDocks[i]; - // Only lock on to an available dock of the same type that is either ungendered or the opposite gender. - //if (otherDock.state == "Ready" && (string.IsNullOrEmpty(dockingNode.nodeType) || dockingNode.nodeType == otherDock.nodeType) && (dockingNode.gendered == false || dockingNode.genderFemale != otherDock.genderFemale)) - if (checkDock.state == "Ready") - { - validOwnDocks.Add(checkDock); - } - } - } -/* else - { - for (int i = potentialOwnDocks.Count - 1; i >= 0; --i) - { - ModuleDockingNode otherDock = potentialOwnDocks[i]; - // Only lock on to an available dock of the same type that is either ungendered or the opposite gender. - if (otherDock.state == "Ready") - { - validOwnDocks.Add(otherDock); - } - } - }*/ - if (ownDockingPorts.Length != validOwnDocks.Count) - { - ownDockingPorts = validOwnDocks.ToArray(); - } - else - { - for (int i = ownDockingPorts.Length - 1; i >= 0; --i) - { - ownDockingPorts[i] = validOwnDocks[i]; - } - } - //} - - //else if ((targetVessel.packed || !targetVessel.loaded) && targetDockingPorts.Length > 0) - //{ - // targetDockingPorts = new ModuleDockingNode[0]; - //} - } - - // Time in seconds until impact. 0 if there is no impact. - private double timeToImpact_; - internal double timeToImpact - { - get - { - if (timeToImpact_ < 0.0) - { - RefreshLandingEstimate(); - } - return timeToImpact_; - } - } - private double landingAltitude_; - internal double landingAltitude - { - get - { - if (timeToImpact_ < 0.0) - { - RefreshLandingEstimate(); - } - return landingAltitude_; - } - } - private double landingLongitude_; - internal double landingLongitude - { - get - { - if (timeToImpact_ < 0.0) - { - RefreshLandingEstimate(); - } - return landingLongitude_; - } - } - private double landingLatitude_; - internal double landingLatitude - { - get - { - if (timeToImpact_ < 0.0) - { - RefreshLandingEstimate(); - } - return landingLatitude_; - } - } - - private void RefreshLandingEstimate() - { - if (orbit.PeA < 0.0 && orbit.eccentricity < 1.0 && !(vessel.Landed || vessel.Splashed)) - { - // Initial estimate: - landingAltitude_ = 0.0; - - timeToImpact_ = Utility.NextTimeToRadius(orbit, landingAltitude_ + orbit.referenceBody.Radius); - Vector3d pos = orbit.getPositionAtUT(timeToImpact_ + Planetarium.GetUniversalTime()); - - Vector2d latlon = orbit.referenceBody.GetLatitudeAndLongitude(pos); - landingAltitude_ = orbit.referenceBody.TerrainAltitude(latlon.x, latlon.y); - landingLatitude_ = latlon.x; - landingLongitude_ = latlon.y; - - landingAltitude_ = Math.Min(orbit.ApA, Math.Max(orbit.PeA, Math.Max(landingAltitude_, 0.0))); - - double lastImpact = timeToImpact_; - - //Utility.LogMessage(this, "RefreshLandingEstimate():"); - for (int i = 0; i < 6; ++i) - { - timeToImpact_ = Utility.NextTimeToRadius(orbit, landingAltitude_ + orbit.referenceBody.Radius); - - pos = orbit.getPositionAtUT(timeToImpact_ + Planetarium.GetUniversalTime()); - latlon = orbit.referenceBody.GetLatitudeAndLongitude(pos); - landingAltitude_ = orbit.referenceBody.TerrainAltitude(latlon.x, latlon.y); - landingLatitude_ = latlon.x; - landingLongitude_ = latlon.y; - - landingAltitude_ = Math.Min(orbit.ApA, Math.Max(orbit.PeA, Math.Max(landingAltitude_, 0.0))); - - //Utility.LogMessage(this, "[{2}]: {0:0}m in {1:0}s", landingAltitude_, timeToImpact_, i); - - if (Math.Abs(timeToImpact_ - lastImpact) < 2.0) - { - break; - } - else - { - lastImpact = timeToImpact_; - } - } - } - else - { - timeToImpact_ = 0.0; - landingAltitude_ = 0.0; - landingLongitude_ = 0.0; - landingLatitude_ = 0.0; - } - } - - internal double altitudeASL; - internal double altitudeTerrain; - internal double altitudeTerrainRate; - private double altitudeBottom_; - internal double altitudeBottom - { - get - { - // This is expensive to compute, so we - // don't until we're close to the ground, - // and never until it's requested. - if (altitudeBottom_ < 0.0) - { - altitudeBottom_ = Math.Min(altitudeASL, altitudeTerrain); - - // Precision isn't *that* important ... until we get close. - if (altitudeBottom_ < 500.0) - { - double lowestPoint = altitudeASL; - - for (int i = vessel.parts.Count - 1; i >= 0; --i) - { - if (vessel.parts[i].collider != null) - { - Vector3d bottomPoint = vessel.parts[i].collider.ClosestPointOnBounds(mainBody.position); - double partBottomAlt = mainBody.GetAltitude(bottomPoint); - lowestPoint = Math.Min(lowestPoint, partBottomAlt); - } - } - lowestPoint -= altitudeASL; - altitudeBottom_ += lowestPoint; - - altitudeBottom_ = Math.Max(0.0, altitudeBottom_); - } - } - - return altitudeBottom_; - } - } - internal double atmosphereDepth - { - get - { - return (mainBody.atmosphere) ? Mathf.Clamp01((float)(vessel.atmDensity / mainBody.atmDensityASL)) : 0.0; - } - } - internal double apoapsis; - internal double periapsis; - void UpdateAltitudes() - { - double previousAltitudeTerrain = Math.Min(altitudeTerrain, altitudeASL); - altitudeASL = vessel.altitude; - altitudeTerrain = vessel.altitude - vessel.terrainAltitude; - - // Apply exponential smoothing - terrain rate is very noisy. - const float alpha = 0.0625f; - altitudeTerrainRate = altitudeTerrainRate * (1.0 - alpha) + ((Math.Min(altitudeTerrain, altitudeASL) - previousAltitudeTerrain) / TimeWarp.fixedDeltaTime) * alpha; - - altitudeBottom_ = -1.0; - timeToImpact_ = -1.0; - apoapsis = orbit.ApA; - periapsis = orbit.PeA; - } - - #region Attitudes - /// - /// Surface Attitude is pitch, heading, roll - /// - private Vector3 surfaceAttitude; - internal float heading - { - get - { - return surfaceAttitude.y; - } - } - internal float pitch - { - get - { - return surfaceAttitude.x; - } - } - internal float roll - { - get - { - return surfaceAttitude.z; - } - } - private readonly Quaternion navballYRotate = Quaternion.Euler(0.0f, 180.0f, 0.0f); - private Quaternion navballRelativeGimbal; - internal Quaternion navBallRelativeGimbal - { - get - { - return navballRelativeGimbal; - } - } - private Quaternion navballAttitudeGimbal; - internal Quaternion navBallAttitudeGimbal - { - get - { - return navballAttitudeGimbal; - } - } - // Surface-relative vectors. - internal Vector3 up; // local world "up" - internal Vector3 surfaceRight; // vessel right projected onto the plane described by "up" - internal Vector3 surfaceForward; // vessel forward projected onto the plane described by "up"; special handling when 'forward' is near 'up'. - - internal Vector3 prograde; - internal Vector3 surfacePrograde; - internal Vector3 radialOut; - internal Vector3 normal; - - // Vessel-relative right, forward, and "top" unit vectors. - internal Vector3 right; - internal Vector3 forward; - internal Vector3 top; - - internal float progradeHeading; - - private float lastHeading; - internal double headingRate = 0.0; - - /// - /// Because the gimbal is reflected for presentation, we need to - /// mirror the value here so the gimbal is correct. - /// - /// - /// - static internal Quaternion MirrorXAxis(Quaternion input) - { - return new Quaternion(input.x, -input.y, -input.z, input.w); - } - - /// - /// Do a ray-cast to determine slope beneath the vessel. - /// - /// Slope, or 0 if it can not be computed. - internal double GetSlopeAngle() - { - RaycastHit sfc; - if (Physics.Raycast(vessel.CoM, -up, out sfc, (float)altitudeASL + 1000.0f, 1 << 15)) - { - return Vector3.Angle(up, sfc.normal); - } - else - { - return 0.0; - } - } - - // The vessel's transform is rotated about the x axis 90 degrees, which is why "forward" uses the "up" transform, for instance; - // we need to correct the reference transform's orientation using this transform. - private readonly Quaternion vesselOrientationCorrection = Quaternion.Euler(90.0f, 0.0f, 0.0f); - - void UpdateAttitude() - { - if (vessel.GetReferenceTransformPart() == null) - { - /*try - { - UpdateReferenceTransform(vessel.GetReferenceTransformPart(), true); - } - catch (Exception e) - { - throw new ArgumentException("Error in UpdateReferenceTransform in UpdateAttitude: \"" + e.Source + " " + e.TargetSite + " " + e.Data + " " + e.StackTrace + " Transform: " + vessel.GetReferenceTransformPart() + "\"", e); - }*/ - - UpdateReferenceTransform(vessel.GetReferenceTransformPart(), true); - } - /* try - { - navballAttitudeGimbal = vesselOrientationCorrection * Quaternion.Inverse(referenceTransform.rotation); - } - catch (Exception e) - { - throw new ArgumentException("Error in navballAttitudeGimbal: \"" + e.Source + " " + e.TargetSite + " " + e.Data + " " + e.StackTrace + " Transform: " + referenceTransform + "\"", e); - }*/ - - navballAttitudeGimbal = vesselOrientationCorrection * Quaternion.Inverse(referenceTransform.rotation); - - Vector3 relativePositionVector = (referenceTransform.position - mainBody.position).normalized; - //Utility.LogMessage(this, "Past gate 1"); - - Quaternion relativeGimbal = navballAttitudeGimbal * Quaternion.LookRotation( - Vector3.ProjectOnPlane(relativePositionVector + mainBody.transform.up, (relativePositionVector)), - relativePositionVector); - - //Utility.LogMessage(this, "Past gate 2"); - - // We have to do all sorts of voodoo to get the navball - // gimbal rotated so the rendered navball behaves the same - // as navballs. - navballRelativeGimbal = navballYRotate * MirrorXAxis(relativeGimbal); - surfaceAttitude = Quaternion.Inverse(relativeGimbal).eulerAngles; - if (surfaceAttitude.x > 180.0f) - { - surfaceAttitude.x = 360.0f - surfaceAttitude.x; - } - else - { - surfaceAttitude.x = -surfaceAttitude.x; - } - - if (surfaceAttitude.z > 180.0f) - { - surfaceAttitude.z = 360.0f - surfaceAttitude.z; - } - else - { - surfaceAttitude.z = -surfaceAttitude.z; - } - - //Utility.LogMessage(this, "Past gate 3"); - - double headingChange = Utility.NormalizeLongitude(surfaceAttitude.y - lastHeading); - lastHeading = surfaceAttitude.y; - headingRate = 0.875 * headingRate + 0.125 * headingChange / TimeWarp.fixedDeltaTime; - - //Utility.LogMessage(this, "Past gate 4"); - - up = vessel.upAxis; - prograde = vessel.obt_velocity.normalized; - surfacePrograde = vessel.srf_vel_direction; - radialOut = Vector3.ProjectOnPlane(up, prograde).normalized; - normal = -Vector3.Cross(radialOut, prograde).normalized; - // TODO: does Vector3.OrthoNormalize do anything for me here? - - //Utility.LogMessage(this, "Past gate 5"); - - right = vessel.GetTransform().right; - forward = vessel.GetTransform().up; - top = vessel.GetTransform().forward; - - //Utility.LogMessage(this, "Past gate 6"); - - // We base our surface vector off of UP and RIGHT, unless roll is extreme. - if (Mathf.Abs(Vector3.Dot(right, up)) > 0.995f) - { - surfaceRight = Vector3.Cross(forward, up); - surfaceForward = Vector3.Cross(up, surfaceRight); - } - else - { - surfaceForward = Vector3.Cross(up, right); - surfaceRight = Vector3.Cross(surfaceForward, up); - } - - //Utility.LogMessage(this, "Past gate 7"); - - Vector3 surfaceProgradeProjected = Vector3.ProjectOnPlane(surfacePrograde, up); - progradeHeading = Vector3.Angle(surfaceProgradeProjected, vessel.north); - if (Vector3.Dot(surfaceProgradeProjected, vessel.east) < 0.0) - { - progradeHeading = 360.0f - progradeHeading; - } - - //Utility.LogMessage(this, "Past gate 8"); - - // TODO: Am I computing normal wrong? - // TODO: orbit.GetOrbitNormal() appears to return a vector in the opposite - // direction when in the inertial frame of reference, but it's perpendicular - // in the rotating reference frame. Is there a way to always get the inertial - // frame? Or can I get away with working in whatever framework KSP is working - // in? - //Orbit.SolveClosestApproach(); - //orbit.GetTimeToPeriapsis(); - //orbit.timeToAp; - //orbit.timeToPe; - //orbit.getRelativePositionAtUT(); - // Trajectory object? - //Utility.LogMessage(this, "orb Ap {0:0} / Pe {1:0}, end type {2}", orbit.ApA, orbit.PeA, orbit.patchEndTransition.ToString()); - } - internal double GetRelativePitch(Vector3 direction) - { - // Project the direction vector onto the plane YZ plane - Vector3 projectedVector = Vector3.ProjectOnPlane(direction, right); - projectedVector.Normalize(); - - // Dot the projected vector with the 'top' direction so we can find - // the relative pitch. - float dotPitch = Vector3.Dot(projectedVector, top); - float pitch = Mathf.Asin(dotPitch); - if (float.IsNaN(pitch)) - { - pitch = (dotPitch > 0.0f) ? 90.0f : -90.0f; - } - else - { - pitch *= Mathf.Rad2Deg; - } - - return pitch; - } - internal double GetRelativeYaw(Vector3 direction) - { - // Project the direction vector onto the plane XZ plane - Vector3 projectedVector = Vector3.ProjectOnPlane(direction, top); - projectedVector.Normalize(); - - // Determine the lateral displacement by dotting the vector with - // the 'right' vector... - float dotLateral = Vector3.Dot(projectedVector, right); - // And the forward/back displacement by dotting with the forward vector. - float dotLongitudinal = Vector3.Dot(projectedVector, forward); - - // Taking arc tangent of x/y lets us treat the front of the vessel - // as the 0 degree location. - float yaw = Mathf.Atan2(dotLateral, dotLongitudinal); - yaw *= Mathf.Rad2Deg; - - return yaw; - } - #endregion - - #region Maneuver - private ManeuverNode node; - internal Orbit nodeOrbit; - private double nodeDV = -1.0; - private double nodeTotalDV = 0.0; - private void RefreshNodeValues() - { - // Per KSP API wiki http://docuwiki-kspapi.rhcloud.com/#/classes/ManeuverNode: - // The x-component of DeltaV represents the delta-V in the radial-plus direction. - // The y-component of DeltaV represents the delta-V in the normal-plus direction. - // The z-component of DeltaV represents the delta-V in the prograde direction. - // However... it is not returned in the basis of the orbit at the time of the - // maneuver. It needs transformed into the right basis. - maneuverVector = node.GetBurnVector(orbit); - nodeDV = maneuverVector.magnitude; - nodeTotalDV = node.DeltaV.magnitude; - - // Swizzle these into the right order. - Vector3d mnvrVel = orbit.getOrbitalVelocityAtUT(node.UT).xzy; - Vector3d mnvrPos = orbit.getRelativePositionAtUT(node.UT).xzy; - - Vector3d mnvrPrograde = mnvrVel.normalized; // Prograde vector at maneuver time - Vector3d mnvrNml = Vector3d.Cross(mnvrVel, mnvrPos).normalized; - Vector3d mnvrRadial = Vector3d.Cross(mnvrNml, mnvrPrograde); - - maneuverNodeComponentVector.x = Vector3d.Dot(maneuverVector, mnvrPrograde); - maneuverNodeComponentVector.y = Vector3d.Dot(maneuverVector, mnvrNml); - maneuverNodeComponentVector.z = Vector3d.Dot(maneuverVector, mnvrRadial); - } - - private Vector3d maneuverNodeComponentVector = Vector3d.zero; - internal Vector3d maneuverNodeComponent - { - get - { - if (nodeDV < 0.0) - { - if (node != null && orbit != null) - { - RefreshNodeValues(); - } - else - { - nodeDV = 0.0; - } - } - - return maneuverNodeComponentVector; - } - } - internal double maneuverNodeDeltaV - { - get - { - if (nodeDV < 0.0) - { - if (node != null && orbit != null) - { - RefreshNodeValues(); - } - else - { - nodeDV = 0.0; - } - } - - return nodeDV; - } - } - internal double maneuverNodeTotalDeltaV - { - get - { - if (nodeDV < 0.0) - { - if (node != null && orbit != null) - { - RefreshNodeValues(); - } - else - { - nodeDV = 0.0; - } - } - - return nodeTotalDV; - } - } - private Vector3d maneuverVector; - internal Vector3d maneuverNodeVector - { - get - { - if (nodeDV < 0.0) - { - if (node != null && orbit != null) - { - RefreshNodeValues(); - } - else - { - nodeDV = 0.0; - } - } - - return maneuverVector; - } - } - internal bool maneuverNodeValid - { - get - { - return node != null; - } - } - internal double maneuverNodeTime - { - get - { - if (node != null) - { - return (universalTime - node.UT); - } - else - { - return 0.0; - } - } - } - - internal double NodeBurnTime() - { - if (maneuverNodeValid && currentIsp > 0.0 && currentMaxThrust > 0.0) - { - return currentIsp * (1.0f - Math.Exp(-maneuverNodeDeltaV / currentIsp / PhysicsGlobals.GravitationalAcceleration)) / (currentMaxThrust / (vessel.totalMass * PhysicsGlobals.GravitationalAcceleration)); - } - else - { - return 0.0; - } - } - - void UpdateManeuverNode() - { - if (vessel.patchedConicSolver != null) - { - node = vessel.patchedConicSolver.maneuverNodes.Count > 0 ? vessel.patchedConicSolver.maneuverNodes[0] : null; - - if (node != null) - { - nodeOrbit = node.nextPatch; - } - else - { - nodeOrbit = null; - } - } - else - { - node = null; - nodeOrbit = null; - } - nodeDV = -1.0; - nodeTotalDV = 0.0; - maneuverVector = Vector3d.zero; - maneuverNodeComponentVector = Vector3d.zero; - } - #endregion - - #region Target - public enum TargetType - { - None, - Vessel, - DockingPort, - CelestialBody, - PositionTarget, - Asteroid, - }; - internal ITargetable activeTarget = null; - internal ApproachSolver approachSolver = new ApproachSolver(); - internal Vector3 targetDisplacement = Vector3.zero; - internal Vector3 targetDirection = Vector3.zero; - internal Vector3d targetRelativeVelocity = Vector3.zero; - internal TargetType targetType = TargetType.None; - internal string targetName = string.Empty; - internal Transform targetDockingTransform; // Docking node transform - valid only for docking port targets. - internal ModuleDockingNode[] targetDockingPorts = new ModuleDockingNode[0]; - internal Orbit targetOrbit; - internal double targetClosestUT - { - get - { - if (activeTarget != null && !approachSolver.resultsReady) - { - if (targetType == MASVesselComputer.TargetType.CelestialBody) - { - approachSolver.SolveBodyIntercept(orbit, activeTarget as CelestialBody); - } - else - { - approachSolver.SolveOrbitIntercept(orbit, targetOrbit); - } - } - return approachSolver.resultsReady ? approachSolver.targetClosestUT : 0.0; - } - } - internal double targetClosestSpeed - { - get - { - if (activeTarget != null && !approachSolver.resultsReady) - { - if (targetType == MASVesselComputer.TargetType.CelestialBody) - { - approachSolver.SolveBodyIntercept(orbit, activeTarget as CelestialBody); - } - else - { - approachSolver.SolveOrbitIntercept(orbit, targetOrbit); - } - } - return approachSolver.resultsReady ? approachSolver.targetClosestSpeed : 0.0; - } - } - internal double targetClosestDistance - { - get - { - if (activeTarget != null && !approachSolver.resultsReady) - { - if (targetType == MASVesselComputer.TargetType.CelestialBody) - { - approachSolver.SolveBodyIntercept(orbit, activeTarget as CelestialBody); - } - else - { - approachSolver.SolveOrbitIntercept(orbit, targetOrbit); - } - } - if (approachSolver.resultsReady) - { - if (targetType == TargetType.CelestialBody) - { - // If we are targeting a body, account for the radius of the planet when describing closest approach. - // That is, targetClosestDistance is effectively PeA. - return Math.Max(0.0, approachSolver.targetClosestDistance - (activeTarget as CelestialBody).Radius); - } - else - { - return approachSolver.targetClosestDistance; - } - } - else - { - return 0.0; - } - } - } - internal bool targetValid - { - get - { - return (activeTarget != null); - } - } - private double targetCmpSpeed = -1.0; - internal double targetSpeed - { - get - { - if (targetCmpSpeed < 0.0) - { - targetCmpSpeed = targetRelativeVelocity.magnitude; - } - return targetCmpSpeed; - } - } - private void UpdateTargetDockingPorts() - { - Vessel targetVessel = (targetType == TargetType.Vessel) ? (activeTarget as Vessel) : (activeTarget as ModuleDockingNode).vessel; - if (!targetVessel.packed && targetVessel.loaded) - { - List potentialDocks = targetVessel.FindPartModulesImplementing(); - List validDocks = new List(); - - if (dockingNode != null) - { - for (int i = potentialDocks.Count - 1; i >= 0; --i) - { - ModuleDockingNode otherDock = potentialDocks[i]; - // Only lock on to an available dock of the same type that is either ungendered or the opposite gender. - if (otherDock.state == "Ready" && (string.IsNullOrEmpty(dockingNode.nodeType) || dockingNode.nodeType == otherDock.nodeType) && (dockingNode.gendered == false || dockingNode.genderFemale != otherDock.genderFemale)) - { - validDocks.Add(otherDock); - } - } - } - else - { - for (int i = potentialDocks.Count - 1; i >= 0; --i) - { - ModuleDockingNode otherDock = potentialDocks[i]; - // Only lock on to an available dock of the same type that is either ungendered or the opposite gender. - if (otherDock.state == "Ready") - { - validDocks.Add(otherDock); - } - } - } - if (targetDockingPorts.Length != validDocks.Count) - { - targetDockingPorts = validDocks.ToArray(); - } - else - { - for (int i = targetDockingPorts.Length - 1; i >= 0; --i) - { - targetDockingPorts[i] = validDocks[i]; - } - } - } - - else if ((targetVessel.packed || !targetVessel.loaded) && targetDockingPorts.Length > 0) - { - targetDockingPorts = new ModuleDockingNode[0]; - } - } - private void UpdateTarget() - { - activeTarget = FlightGlobals.fetch.VesselTarget; - if (activeTarget != null) - { - targetDisplacement = activeTarget.GetTransform().position - vessel.GetTransform().position; - targetDirection = targetDisplacement.normalized; - - targetRelativeVelocity = vessel.obt_velocity - activeTarget.GetObtVelocity(); - targetCmpSpeed = -1.0; - targetDockingTransform = null; - - if (activeTarget is Vessel) - { - targetType = ((activeTarget as Vessel).vesselType == VesselType.SpaceObject) ? TargetType.Asteroid : TargetType.Vessel; - } - else if (activeTarget is CelestialBody) - { - targetType = TargetType.CelestialBody; - } - else if (activeTarget is ModuleDockingNode) - { - targetType = TargetType.DockingPort; - targetDockingTransform = (activeTarget as ModuleDockingNode).GetTransform(); - } - else if (activeTarget is PositionTarget) - { - targetType = TargetType.PositionTarget; - } - else - { - Utility.LogError(this, "UpdateTarget() - unable to classify target {0}", activeTarget.GetType().Name); - targetType = TargetType.None; - } - - if (targetType == TargetType.Vessel || targetType == TargetType.DockingPort) - { - UpdateTargetDockingPorts(); - } - - targetName = activeTarget.GetName(); - targetOrbit = activeTarget.GetOrbit(); - } - else - { - targetCmpSpeed = 0.0; - targetType = TargetType.None; - targetDisplacement = Vector3.zero; - targetRelativeVelocity = Vector3d.zero; - targetDirection = forward; - targetDockingTransform = null; - targetName = string.Empty; - targetOrbit = null; - if (targetDockingPorts.Length > 0) - { - targetDockingPorts = new ModuleDockingNode[0]; - } - } - approachSolver.ResetComputation(); - } - #endregion - - private bool aeroDataValid = false; - private double dragForce; - private double gravForce; - private double liftForce; - private double liftUpForce; - private double terminalVelocity; - - private void UpdateAeroForces() - { - if (aeroDataValid) - { - return; - } - - aeroDataValid = true; - - gravForce = 1000.0 * vessel.GetTotalMass() * FlightGlobals.getGeeForceAtPosition(vessel.CoM).magnitude; // force of gravity - - // Short-circuit these computations if there's no atmosphere. - if (vessel.atmDensity == 0.0) - { - liftForce = 0.0; - dragForce = 0.0; - terminalVelocity = 0.0; - liftUpForce = 0.0; - - return; - } - - // Code substantially from NathanKell's AeroGUI mod, - // https://github.com/NathanKell/AeroGUI/blob/ccfd5e2e40fdf13e6ce66517ceb1db418689a5f0/AeroGUI/AeroGUI.cs#L301 - - Vector3d vLift = Vector3d.zero; // the sum of lift from all parts - Vector3d vDrag = Vector3d.zero; // the sum of drag from all parts - double areaDrag = 0.0; - - for (int i = vessel.Parts.Count - 1; i >= 0; --i) - { - Part p = vessel.Parts[i]; - - // get part drag (but not wing/surface drag) - vDrag += -p.dragVectorDir * p.dragScalar; - if (!p.hasLiftModule) - { - Vector3 bodyLift = p.transform.rotation * (p.bodyLiftScalar * p.DragCubes.LiftForce); - bodyLift = Vector3.ProjectOnPlane(bodyLift, -p.dragVectorDir); - vLift += bodyLift; - } - - ModuleLiftingSurface wing = p.FindModuleImplementing(); - if (wing != null) - { - vLift += wing.liftForce; - vDrag += wing.dragForce; - } - - areaDrag += p.DragCubes.AreaDrag * PhysicsGlobals.DragCubeMultiplier * PhysicsGlobals.DragMultiplier; - } - - Vector3d force = vLift + vDrag; // sum of all forces on the craft - Vector3d nVel = vessel.srf_velocity.normalized; - Vector3d liftDir = -Vector3d.Cross(vessel.transform.right, nVel); // we need the "lift" direction, which - // is "up" from our current velocity vector and roll angle. - - // Now we can compute the dots. - liftForce = Vector3d.Dot(force, liftDir); // just the force in the 'lift' direction - - dragForce = Vector3d.Dot(force, -nVel); // drag force, = pDrag + lift-induced drag - - liftUpForce = Vector3d.Dot(force, up); - - terminalVelocity = Math.Sqrt(2.0 * gravForce / (areaDrag * vessel.atmDensity)); - } - - internal double DragForce() - { - if (!aeroDataValid) - { - UpdateAeroForces(); - } - - return dragForce; - } - internal double GravForce() - { - if (!aeroDataValid) - { - UpdateAeroForces(); - } - - return gravForce; - } - internal double LiftForce() - { - if (!aeroDataValid) - { - UpdateAeroForces(); - } - - return liftForce; - } - internal double LiftUpForce() - { - if (!aeroDataValid) - { - UpdateAeroForces(); - } - - return liftUpForce; - } - internal double TerminalVelocity() - { - if (!aeroDataValid) - { - UpdateAeroForces(); - } - - return terminalVelocity; - } - - internal double surfaceAccelerationFromGravity; - private void UpdateMisc() - { - // Convert to m/2^s - surfaceAccelerationFromGravity = orbit.referenceBody.GeeASL * PhysicsGlobals.GravitationalAcceleration; - aeroDataValid = false; - } - - private void UpdateReferenceTransform(Part referencePart, bool forceEvaluate) - { - if (referencePart == null) - { - // During staging, it's possible for referencePart to be null. If it is, let's skip - // this processing. Things will sort out later. - Utility.LogMessage(this, "Null referencePart for {0}; setting to root!", vessel.id); - vessel.SetReferenceTransform(vessel.rootPart); - referencePart = vessel.GetReferenceTransformPart(); - //return; - } - - Transform newRefXform = referencePart.GetReferenceTransform(); - if (_referenceTransform == newRefXform && !forceEvaluate) - { - return; - } - - _referenceTransform = newRefXform; - referenceTransformType = ReferenceType.Unknown; - - if (referencePart.Modules.GetModule() != null) - { - referenceTransformType = ReferenceType.RemoteCommand; - if (CameraManager.Instance.currentCameraMode == CameraManager.CameraMode.IVA) - { - Kerbal refKerbal = CameraManager.Instance.IVACameraActiveKerbal; - if (refKerbal != null && refKerbal.InPart == referencePart) - { - referenceTransformType = ReferenceType.Self; - } - } - } - else if (referencePart.Modules.GetModule() != null) - { - referenceTransformType = ReferenceType.DockingPort; - } - else if (referencePart.Modules.GetModule() != null) - { - referenceTransformType = ReferenceType.Claw; - } - - UpdateDockingNode(referencePart); - } - #endregion - - #region GameEvent Callbacks - /// - /// The player changed camera modes. If we're going from 'outside' to - /// 'inside', we can figure out which part we're in, and thus whether - /// there are docks available. To do that, we have to reprocess the - /// reference transform. - /// - /// - private void onCameraChange(CameraManager.CameraMode newMode) - { - vesselActive = ActiveVessel(vessel); - UpdateReferenceTransform(vessel.GetReferenceTransformPart(), vesselActive); - } - - /// - /// We staged - time to refresh our resource tracking. - /// - /// - private void onStageActivate(int stage) - { - InvalidateModules(); - } - - private void onVesselChange(Vessel who) - { - if (who.id == vesselId) - { - vesselCrewed = true && HighLogic.LoadedSceneIsFlight; - vesselActive = ActiveVessel(vessel); - InvalidateModules(); - Utility.LogMessage(this, "onVesselChange for {0}", who.id); - } - } - - private void onVesselSOIChanged(GameEvents.HostedFromToAction what) - { - if (what.host.id == vesselId) - { - Utility.LogMessage(this, "onVesselSOIChanged: to {0}", what.to.bodyName); - mainBody = what.to; - } - } - - private void onVesselWasModified(Vessel who) - { - if (who.id == vesselId) - { - vesselCrewed = true && HighLogic.LoadedSceneIsFlight; - vesselActive = ActiveVessel(vessel); - InvalidateModules(); - Utility.LogMessage(this, "onVesselWasModified for {0}", who.id); - } - } - - private void onVesselDestroy(Vessel who) - { - if (who.id == vesselId) - { - vesselCrewed = true && HighLogic.LoadedSceneIsFlight; - vesselActive = ActiveVessel(vessel); - Utility.LogMessage(this, "onVesselDestroy for {0}", who.id); - } - } - - private void onVesselCreate(Vessel who) - { - if (who.id == vesselId) - { - vesselCrewed = true && HighLogic.LoadedSceneIsFlight; - vesselActive = ActiveVessel(vessel); - Utility.LogMessage(this, "onVesselCreate for {0}", who.id); - } - } - - private void onVesselCrewWasModified(Vessel who) - { - if (who.id == vessel.id) - { - vesselCrewed = true && HighLogic.LoadedSceneIsFlight; - vesselActive = ActiveVessel(vessel); - Utility.LogMessage(this, "onVesselCrewWasModified for {0}", who.id); - } - } - #endregion - } -} diff --git a/ModifiedCode/MASVesselComputerModules.cs b/ModifiedCode/MASVesselComputerModules.cs deleted file mode 100644 index cec5b9eb..00000000 --- a/ModifiedCode/MASVesselComputerModules.cs +++ /dev/null @@ -1,1815 +0,0 @@ -//#define TIME_REBUILD -//#define TIME_UPDATES -/***************************************************************************** - * The MIT License (MIT) - * - * Copyright (c) 2016-2020 MOARdV - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to - * deal in the Software without restriction, including without limitation the - * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or - * sell copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - * - ****************************************************************************/ -using KSP.UI.Screens; -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Text; -using UnityEngine; - -namespace AvionicsSystems -{ - internal partial class MASVesselComputer : MonoBehaviour - { - // Tracks per-module data. - - internal bool modulesInvalidated = true; - // There appears to be a quirk when entering flight. The MASVesselComputer will - // initialize before all of the ModuleScienceExperiment modules are ready, and MAS - // will see some ScienceExperiment fields that aren't initialized yet. To manage - // that problem, this boolean is exposed such that the fc proxy can set it. - // When true, the vc will reprocess the MSE entries and rebuild its table of known - // science types. - internal bool scienceInvalidated = false; - - #region Action Groups - //---Action Groups - private bool[] hasActionGroup = new bool[17]; - - /// - /// Returns true if there is at least one action in this action group. - /// - /// - /// - internal bool GroupHasActions(KSPActionGroup ag) - { - int index = -1; - for (int ui = (int)ag; ui != 0; ui >>= 1) - { - ++index; - } - - if (index < 0 || index > hasActionGroup.Length) - { - // Should never happen! - throw new ArgumentOutOfRangeException("MASVesselComputer.GroupHasActions() called with invalid action group!"); - } - - return hasActionGroup[index]; - } - - /// - /// Sets the indicated action group to true (meaning there is an action) - /// - /// - private void SetActionGroup(KSPActionGroup ag) - { - int index = 0; - for (int ui = (int)ag; ui != 0; ui >>= 1) - { - if ((ui & 0x1) != 0) - { - hasActionGroup[index] = true; - } - ++index; - } - } - #endregion - - #region Air Brakes - private List airBrakeList = new List(); - internal ModuleAeroSurface[] moduleAirBrake = new ModuleAeroSurface[0]; - #endregion - - #region Aircraft Engines - private List thrustReverserList = new List(); - internal MASThrustReverser[] moduleThrustReverser = new MASThrustReverser[0]; - private List resourceIntakeList = new List(); - internal ModuleResourceIntake[] moduleResourceIntake = new ModuleResourceIntake[0]; - #endregion - - #region Brakes - private List brakesList = new List(); - internal ModuleWheels.ModuleWheelBrakes[] moduleBrakes = new ModuleWheels.ModuleWheelBrakes[0]; - #endregion - - #region Cameras - private List cameraList = new List(4); - internal MASCamera[] moduleCamera = new MASCamera[0]; - private int dockCamCount = 0; - private int camCount = 0; - private bool camerasReset = true; - private void UpdateCamera() - { - if (camerasReset) - { - camerasReset = false; - for (int i = moduleCamera.Length - 1; i >= 0; --i) - { - if (string.IsNullOrEmpty(moduleCamera[i].cameraName)) - { - if (moduleCamera[i].isDockingPortCamera) - { - ++dockCamCount; - moduleCamera[i].cameraName = string.Format("Dock {0}", dockCamCount); - } - else - { - ++camCount; - moduleCamera[i].cameraName = string.Format("Camera {0}", camCount); - } - } - else - { - MASCamera[] dupeNames = Array.FindAll(moduleCamera, x => x.cameraName == moduleCamera[i].cameraName); - - if (dupeNames.Length > 1) - { - for (int dupeIndex = 0; dupeIndex < dupeNames.Length; ++dupeIndex) - { - dupeNames[dupeIndex].cameraName = string.Format("{0} {1}", dupeNames[dupeIndex].cameraName, dupeIndex + 1); - } - } - } - } - } - } - - /// - /// Search the list of camera modules to find the named camera. - /// - /// The name to search for - /// The camera module, or null if it was not found. - internal MASCamera FindCameraModule(string cameraName) - { - for (int i = moduleCamera.Length - 1; i >= 0; --i) - { - if (moduleCamera[i].cameraName == cameraName) - { - return moduleCamera[i]; - } - } - - return null; - } - #endregion - - #region Cargo Bay - private List cargoBayList = new List(2); - internal ModuleCargoBay[] moduleCargoBay = new ModuleCargoBay[0]; - internal float cargoBayDirection = 0.0f; - internal float cargoBayPosition = 0.0f; - void UpdateCargoBay() - { - if (moduleCargoBay.Length > 0) - { - float newPosition = 0.0f; - float numBays = 0.0f; - for (int i = moduleCargoBay.Length - 1; i >= 0; --i) - { - ModuleCargoBay me = moduleCargoBay[i]; - PartModule deployer = me.part.Modules[me.DeployModuleIndex]; - /*if (deployer is ModuleServiceModule) - { - ModuleServiceModule msm = deployer as ModuleServiceModule; - if (msm.IsDeployed) - { - cargoBayDeployed = true; - } - else - { - cargoBayRetracted = true; - } - } - else*/ - if (deployer is ModuleAnimateGeneric) - { - ModuleAnimateGeneric mag = deployer as ModuleAnimateGeneric; - newPosition += Mathf.InverseLerp(me.closedPosition, Mathf.Abs(1.0f - me.closedPosition), mag.animTime); - } - } - - if (numBays > 1.0f) - { - newPosition /= numBays; - } - - cargoBayDirection = newPosition - cargoBayPosition; - if (cargoBayDirection < 0.0f) - { - cargoBayDirection = -1.0f; - } - else if (cargoBayDirection > 0.0f) - { - cargoBayDirection = 1.0f; - } - cargoBayPosition = newPosition; - } - } - #endregion - - #region Color Changer - private List colorChangerList = new List(2); - internal ModuleColorChanger[] moduleColorChanger = new ModuleColorChanger[0]; - #endregion - - #region Communications - private List antennaList = new List(2); - internal ModuleDeployableAntenna[] moduleAntenna = new ModuleDeployableAntenna[0]; - internal bool antennaDeployable; - internal bool antennaRetractable; - internal int antennaMoving; - internal bool antennaDamaged; - - private List transmitterList = new List(2); - internal ModuleDataTransmitter[] moduleTransmitter = new ModuleDataTransmitter[0]; - private void UpdateAntenna() - { - // TODO: What about detecting if dynamic pressure is low enough to deploy antennae? - antennaDeployable = false; - antennaRetractable = false; - antennaMoving = 0; - antennaDamaged = false; - - for (int i = moduleAntenna.Length - 1; i >= 0; --i) - { - if (moduleAntenna[i].useAnimation) - { - antennaRetractable |= (moduleAntenna[i].retractable && moduleAntenna[i].deployState == ModuleDeployablePart.DeployState.EXTENDED); - antennaDeployable |= (moduleAntenna[i].deployState == ModuleDeployablePart.DeployState.RETRACTED); - if (moduleAntenna[i].deployState == ModuleDeployablePart.DeployState.RETRACTING) - { - antennaMoving = -1; - } - else if (moduleAntenna[i].deployState == ModuleDeployablePart.DeployState.EXTENDING) - { - antennaMoving = 1; - } - antennaDamaged |= (moduleAntenna[i].deployState == ModuleDeployablePart.DeployState.BROKEN); - } - } - } - - #endregion - - #region Docking - // Unlike some of the other sections here, the Dock section focuses on - // a single ModuleDockingNode that the vessel computer designates as - // the "main" docking node. We do this because we do NOT want an - // undock event, for instance, to disassemble an entire space - // station, and we never want to affect more than one docking port with - // such transactions. Code substantially imported from what I wrote for - // RasterPropMonitor. - // This concept has also been extended to the Claw / grabber units. - internal MASCamera dockingCamera; - internal ModuleDockingNode dockingNode; - internal DockingNodeState dockingNodeState = DockingNodeState.UNKNOWN; - internal enum DockingNodeState - { - // Indeterminate state - UNKNOWN, - // Docked to an object - DOCKED, - // Pre-attached (docked in the VAB/SPH) - PREATTACHED, - // Ready to dock - READY, - // Disabled (such as a grapple that is not armed) - DISABLED - }; - internal ModuleGrappleNode clawNode; - internal DockingNodeState clawNodeState = DockingNodeState.UNKNOWN; - private void UpdateDockingNode(Part referencePart) - { - // Our candidate for docking node. Called from UpdateReferenceTransform() - ModuleDockingNode dockingNode = null; - ModuleGrappleNode clawNode = null; - - if (referencePart != null) - { - // See if the reference part is a docking port. - if (referenceTransformType == ReferenceType.DockingPort) - { - // If it's a docking port, this should be all we need to do. - dockingNode = referencePart.FindModuleImplementing(); - } - else if (referenceTransformType == ReferenceType.Claw) - { - clawNode = referencePart.FindModuleImplementing(); - } - else - { - uint shipFlightNumber = 0; - if (referenceTransformType == ReferenceType.Self || referenceTransformType == ReferenceType.RemoteCommand) - { - // If the reference transform is the current IVA, we need - // to look for another part that has a docking node and the - // same ID as our part. - shipFlightNumber = referencePart.launchID; - } - else - { - if (CameraManager.Instance.currentCameraMode == CameraManager.CameraMode.IVA) - { - Kerbal refKerbal = CameraManager.Instance.IVACameraActiveKerbal; - if (refKerbal != null) - { - shipFlightNumber = refKerbal.InPart.launchID; - } - } - } - - if (shipFlightNumber > 0) - { - List vesselParts = vessel.Parts; - for (int i = vesselParts.Count - 1; i >= 0; --i) - { - Part p = vesselParts[i]; - if (p.launchID == shipFlightNumber) - { - dockingNode = p.FindModuleImplementing(); - if (dockingNode != null) - { - break; - } - clawNode = p.FindModuleImplementing(); - if (clawNode != null) - { - break; - } - } - } - } - } - } - - this.dockingNode = dockingNode; - if (this.dockingNode != null) - { - this.dockingCamera = this.dockingNode.part.FindModuleImplementing(); - } - else - { - this.dockingCamera = null; - } - this.clawNode = clawNode; - } - - private void UpdateDockingNodeState() - { - if (dockingNode != null) - { - switch (dockingNode.state) - { - case "PreAttached": - dockingNodeState = DockingNodeState.PREATTACHED; - break; - case "Docked (docker)": - dockingNodeState = DockingNodeState.DOCKED; - break; - case "Docked (dockee)": - dockingNodeState = DockingNodeState.DOCKED; - break; - case "Ready": - dockingNodeState = DockingNodeState.READY; - break; - default: - dockingNodeState = DockingNodeState.UNKNOWN; - break; - } - } - else - { - dockingNodeState = DockingNodeState.UNKNOWN; - } - - if (clawNode != null) - { - switch (clawNode.state) - { - case "Disabled": - clawNodeState = DockingNodeState.DISABLED; - break; - case "Ready": - clawNodeState = DockingNodeState.READY; - break; - case "Grappled": - clawNodeState = DockingNodeState.DOCKED; - break; - default: - Utility.LogMessage(this, "Claw = {0}, unlocked {1}, loose {2}", clawNode.state, clawNode.IsJointUnlocked(), clawNode.IsLoose()); - clawNodeState = DockingNodeState.UNKNOWN; - break; - } - } - else - { - clawNodeState = DockingNodeState.UNKNOWN; - } - } - #endregion - - #region Engines - //---Engines - private List idEnginesList = new List(); - internal MASIdEngine[] moduleIdEngines = new MASIdEngine[0]; - private List enginesList = new List(8); - internal ModuleEngines[] moduleEngines = new ModuleEngines[0]; - private List multiModeEngineList = new List(); - internal MultiModeEngine[] multiModeEngines = new MultiModeEngine[0]; - private List engineGroupList = new List(); - internal MASIdEngineGroup[] engineGroup = new MASIdEngineGroup[0]; - internal PartModule moduleGraviticEngine = null; - private float[] invMaxISP = new float[0]; - internal float currentThrust; // current net thrust, kN - internal float currentLimitedMaxThrust; // Max thrust, accounting for throttle limits, kN - internal float currentMaxThrust; // Max possible thrust at current altitude, kN - internal float maxRatedThrust; // Max possible thrust, kN - internal float maxEngineFuelFlow; // max fuel flow, g/s - internal float currentEngineFuelFlow; // current fuel flow, g/s - internal float currentIsp; - internal float maxIsp; - internal float hottestEngineTemperature; - internal float hottestEngineMaxTemperature; - internal float hottestEngineSign; - internal int currentEngineCount; - internal int activeEngineCount; - internal float throttleLimit; - internal bool anyEnginesFlameout; - internal bool anyEnginesEnabled; - private List visitedParts = new List(); - private bool UpdateEngines() - { - this.currentThrust = 0.0f; - this.maxRatedThrust = 0.0f; - currentLimitedMaxThrust = 0.0f; - currentMaxThrust = 0.0f; - hottestEngineMaxTemperature = 0.0f; - hottestEngineSign = 0.0f; - maxEngineFuelFlow = 0.0f; - currentEngineFuelFlow = 0.0f; - throttleLimit = 0.0f; - float throttleCount = 0.0f; - anyEnginesFlameout = false; - anyEnginesEnabled = false; - activeEngineCount = 0; - - float hottestEngine = float.MaxValue; - float currentHottestEngine = 0.0f; - float maxIspContribution = 0.0f; - float averageIspContribution = 0.0f; - - visitedParts.Clear(); - - bool requestReset = false; - for (int i = moduleEngines.Length - 1; i >= 0; --i) - { - ModuleEngines me = moduleEngines[i]; - requestReset |= (!me.isEnabled); - - Part thatPart = me.part; - if (thatPart.inverseStage == StageManager.CurrentStage) - { - if (!visitedParts.Contains(thatPart)) - { - currentEngineCount++; - if (me.getIgnitionState) - { - activeEngineCount++; - } - visitedParts.Add(thatPart); - } - } - - anyEnginesEnabled |= me.allowShutdown && me.getIgnitionState; - anyEnginesFlameout |= (me.isActiveAndEnabled && me.flameout); - - if (me.EngineIgnited && me.isEnabled) - { - throttleLimit += me.thrustPercentage; - throttleCount += 1.0f; - - float currentThrust = me.finalThrust; - this.currentThrust += currentThrust; - this.maxRatedThrust += me.GetMaxThrust(); - float rawMaxThrust = me.GetMaxThrust() * me.realIsp * invMaxISP[i]; - currentMaxThrust += rawMaxThrust; - float maxThrust = rawMaxThrust * me.thrustPercentage * 0.01f; - currentLimitedMaxThrust += maxThrust; - float realIsp = me.realIsp; - - if (realIsp > 0.0f) - { - averageIspContribution += maxThrust / realIsp; - - // Compute specific fuel consumption and - // multiply by thrust to get grams/sec fuel flow - float specificFuelConsumption = 101972f / realIsp; - maxEngineFuelFlow += specificFuelConsumption * rawMaxThrust; - currentEngineFuelFlow += specificFuelConsumption * currentThrust; - } - if (invMaxISP[i] > 0.0f) - { - maxIspContribution += maxThrust * invMaxISP[i]; - } - - List propellants = me.GetConsumedResources(); - for (int res = propellants.Count - 1; res >= 0; --res) - { - MarkActiveEnginePropellant(propellants[res].id); - } - } - - if (thatPart.skinMaxTemp - thatPart.skinTemperature < hottestEngine) - { - currentHottestEngine = (float)thatPart.skinTemperature; - hottestEngineMaxTemperature = (float)thatPart.skinMaxTemp; - hottestEngine = hottestEngineMaxTemperature - currentHottestEngine; - } - if (thatPart.maxTemp - thatPart.temperature < hottestEngine) - { - currentHottestEngine = (float)thatPart.temperature; - hottestEngineMaxTemperature = (float)thatPart.maxTemp; - hottestEngine = hottestEngineMaxTemperature - currentHottestEngine; - } - } - - hottestEngineSign = Mathf.Sign(currentHottestEngine - hottestEngineTemperature); - hottestEngineTemperature = currentHottestEngine; - - if (throttleCount > 0.0f) - { - throttleLimit /= (throttleCount * 100.0f); - } - - if (averageIspContribution > 0.0f) - { - currentIsp = currentLimitedMaxThrust / averageIspContribution; - } - else - { - currentIsp = 0.0f; - } - - if (maxIspContribution > 0.0f) - { - maxIsp = currentLimitedMaxThrust / maxIspContribution; - } - else - { - maxIsp = 0.0f; - } - - return requestReset; - } - internal bool SetEnginesEnabled(bool newState) - { - for (int i = moduleEngines.Length - 1; i >= 0; --i) - { - Part thatPart = moduleEngines[i].part; - - if (thatPart.inverseStage == StageManager.CurrentStage || !newState) - { - if (moduleEngines[i].EngineIgnited != newState) - { - if (newState && moduleEngines[i].allowRestart) - { - moduleEngines[i].Activate(); - } - else if (moduleEngines[i].allowShutdown) - { - moduleEngines[i].Shutdown(); - } - } - } - } - - return newState; - } - - internal bool SetThrottleLimit(float newLimit) - { - bool anyUpdated = false; - for (int i = moduleEngines.Length - 1; i >= 0; --i) - { - Part thatPart = moduleEngines[i].part; - - if (thatPart.inverseStage == StageManager.CurrentStage) - { - if (moduleEngines[i].EngineIgnited) - { - moduleEngines[i].thrustPercentage = newLimit; - anyUpdated = true; - } - } - } - - return anyUpdated; - } - #endregion - - #region Launch Clamp - private List launchClampList = new List(); - internal LaunchClamp[] moduleLaunchClamp = new LaunchClamp[0]; - #endregion - - #region Gimbal - private List gimbalsList = new List(8); - internal ModuleGimbal[] moduleGimbals = new ModuleGimbal[0]; - internal bool anyGimbalsLocked = false; - internal bool anyGimbalsRoll = false; - internal bool anyGimbalsPitch = false; - internal bool anyGimbalsYaw = false; - internal bool anyGimbalsActive = false; - internal bool activeEnginesGimbal = false; - internal float gimbalDeflection = 0.0f; - internal Vector2 gimbalAxisDeflection = Vector2.zero; - internal float gimbalLimit = 0.0f; - void UpdateGimbals() - { - gimbalLimit = 0.0f; - gimbalDeflection = 0.0f; - anyGimbalsLocked = false; - anyGimbalsActive = false; - anyGimbalsRoll = false; - anyGimbalsPitch = false; - anyGimbalsYaw = false; - Vector2 localDeflection = Vector2.zero; - gimbalAxisDeflection = Vector2.zero; - float gimbalCount = 0.0f; - - for (int i = moduleGimbals.Length - 1; i >= 0; --i) - { - if (moduleGimbals[i].gimbalLock) - { - anyGimbalsLocked = true; - } - else if (moduleGimbals[i].gimbalActive) - { - anyGimbalsActive = true; - - anyGimbalsRoll |= moduleGimbals[i].enableRoll; - anyGimbalsPitch |= moduleGimbals[i].enablePitch; - anyGimbalsYaw |= moduleGimbals[i].enableYaw; - - gimbalCount += 1.0f; - - gimbalLimit += moduleGimbals[i].gimbalLimiter; - - localDeflection.x = moduleGimbals[i].actuationLocal.x; - // all gimbal range values are positive scalars. - if (localDeflection.x < 0.0f) - { - localDeflection.x /= moduleGimbals[i].gimbalRangeXN; - } - else - { - localDeflection.x /= moduleGimbals[i].gimbalRangeXP; - } - - localDeflection.y = moduleGimbals[i].actuationLocal.y; - if (localDeflection.y < 0.0f) - { - localDeflection.y /= moduleGimbals[i].gimbalRangeYN; - } - else - { - localDeflection.y /= moduleGimbals[i].gimbalRangeYP; - } - - gimbalAxisDeflection += localDeflection; - gimbalDeflection += localDeflection.magnitude; - } - } - - if (gimbalCount > 0.0f) - { - float invGimbalCount = 1.0f / gimbalCount; - - gimbalDeflection *= invGimbalCount; - // Must convert from [0, 100] to [0, 1], so multiply by 0.01. - gimbalLimit *= invGimbalCount * 0.01f; - gimbalAxisDeflection.x *= invGimbalCount; - gimbalAxisDeflection.y *= invGimbalCount; - } - } - #endregion - - #region Parachutes - private List realchuteList = new List(8); - internal PartModule[] moduleRealChute = new PartModule[0]; - private List parachuteList = new List(8); - internal ModuleParachute[] moduleParachute = new ModuleParachute[0]; - #endregion - - #region Power Production - private List alternatorList = new List(); - internal ModuleAlternator[] moduleAlternator = new ModuleAlternator[0]; - private List generatorList = new List(); - internal ModuleGenerator[] moduleGenerator = new ModuleGenerator[0]; - internal List generatorOutputList = new List(); - internal float[] generatorOutput = new float[0]; - private List solarPanelList = new List(); - internal ModuleDeployableSolarPanel[] moduleSolarPanel = new ModuleDeployableSolarPanel[0]; - internal bool generatorActive; - internal bool solarPanelsDeployable; - internal float solarPanelsEfficiency; - internal bool solarPanelsRetractable; - internal int solarPanelsMoving; - internal bool solarPanelsDamaged; - internal float netAlternatorOutput; - internal float netGeneratorOutput; - internal float netSolarOutput; - private void UpdatePower() - { - // TODO: What about detecting if dynamic pressure is low enough to deploy solar panels? - netAlternatorOutput = 0.0f; - netGeneratorOutput = 0.0f; - netSolarOutput = 0.0f; - solarPanelsEfficiency = 0.0f; - - generatorActive = false; - solarPanelsDeployable = false; - solarPanelsRetractable = false; - solarPanelsMoving = 0; - solarPanelsDamaged = false; - - for (int i = moduleGenerator.Length - 1; i >= 0; --i) - { - generatorActive |= (moduleGenerator[i].generatorIsActive && !moduleGenerator[i].isAlwaysActive); - - if (moduleGenerator[i].generatorIsActive) - { - float output = moduleGenerator[i].efficiency * generatorOutput[i]; - if (moduleGenerator[i].isThrottleControlled) - { - output *= moduleGenerator[i].throttle; - } - netGeneratorOutput += output; - } - } - - for (int i = moduleAlternator.Length - 1; i >= 0; --i) - { - // I assume there's only one ElectricCharge output in a given ModuleAlternator - netAlternatorOutput += moduleAlternator[i].outputRate; - } - - for (int i = moduleSolarPanel.Length - 1; i >= 0; --i) - { - netSolarOutput += moduleSolarPanel[i].flowRate; - solarPanelsEfficiency += moduleSolarPanel[i].flowRate / moduleSolarPanel[i].chargeRate; - - if (moduleSolarPanel[i].useAnimation) - { - solarPanelsRetractable |= (moduleSolarPanel[i].retractable && moduleSolarPanel[i].deployState == ModuleDeployablePart.DeployState.EXTENDED); - solarPanelsDeployable |= (moduleSolarPanel[i].deployState == ModuleDeployablePart.DeployState.RETRACTED); - if (moduleSolarPanel[i].deployState == ModuleDeployablePart.DeployState.RETRACTING) - { - solarPanelsMoving = -1; - } - else if (moduleSolarPanel[i].deployState == ModuleDeployablePart.DeployState.EXTENDING) - { - solarPanelsMoving = 1; - } - solarPanelsDamaged |= (moduleSolarPanel[i].deployState == ModuleDeployablePart.DeployState.BROKEN); - } - } - if (solarPanelsEfficiency > 0.0f) - { - solarPanelsEfficiency /= (float)moduleSolarPanel.Length; - } - } - #endregion - - #region Procedural Fairings - private List proceduralFairingList = new List(); - internal ModuleProceduralFairing[] moduleProceduralFairing = new ModuleProceduralFairing[0]; - internal bool fairingsCanDeploy = false; - private void UpdateProceduralFairing() - { - fairingsCanDeploy = false; - - for (int i = moduleProceduralFairing.Length - 1; i >= 0; --i) - { - if (moduleProceduralFairing[i].CanMove) - { - fairingsCanDeploy = true; - } - //Utility.LogMessage(this, "fairing {0}: CanMove {1}", - // i, moduleProceduralFairing[i].CanMove); - } - } - #endregion - - #region Radar - private List radarList = new List(); - internal MASRadar[] moduleRadar = new MASRadar[0]; - #endregion - - #region RCS - private List rcsList = new List(8); - internal ModuleRCS[] moduleRcs = new ModuleRCS[0]; - internal bool anyRcsDisabled = false; - internal bool anyRcsFiring = false; - internal bool anyRcsRotate = false; - internal bool anyRcsTranslate = false; - internal float rcsWeightedThrustLimit; - internal float rcsActiveThrustPercent; - private void UpdateRcs() - { - anyRcsDisabled = false; - anyRcsFiring = false; - anyRcsRotate = false; - anyRcsTranslate = false; - float netThrust = 0.0f; - rcsWeightedThrustLimit = 0.0f; - rcsActiveThrustPercent = 0.0f; - float numActiveThrusters = 0.0f; - - for (int i = moduleRcs.Length - 1; i >= 0; --i) - { - if (moduleRcs[i].rcsEnabled == false) - { - anyRcsDisabled = true; - } - else - { - if (moduleRcs[i].enableX || moduleRcs[i].enableY || moduleRcs[i].enableZ) - { - anyRcsTranslate = true; - } - if (moduleRcs[i].enableRoll || moduleRcs[i].enableYaw || moduleRcs[i].enablePitch) - { - anyRcsRotate = true; - } - if (moduleRcs[i].rcs_active) - { - for (int q = 0; q < moduleRcs[i].thrustForces.Length; ++q) - { - if (moduleRcs[i].thrustForces[q] > 0.0f) - { - rcsActiveThrustPercent += moduleRcs[i].thrustForces[q] / moduleRcs[i].thrusterPower; - numActiveThrusters += 1.0f; - anyRcsFiring = true; - } - } - } - netThrust += moduleRcs[i].thrusterPower; - rcsWeightedThrustLimit += moduleRcs[i].thrusterPower * moduleRcs[i].thrustPercentage; - - List propellants = moduleRcs[i].GetConsumedResources(); - for (int res = propellants.Count - 1; res >= 0; --res) - { - MarkActiveRcsPropellant(propellants[res].id); - } - } - } - - if (numActiveThrusters > 0.0f) - { - rcsActiveThrustPercent /= numActiveThrusters; - } - - if (netThrust > 0.0f) - { - rcsWeightedThrustLimit = rcsWeightedThrustLimit / (netThrust * 100.0f); - } - } - #endregion - - #region Reaction Wheels - private List reactionWheelList = new List(4); - internal ModuleReactionWheel[] moduleReactionWheel = new ModuleReactionWheel[0]; - internal float reactionWheelNetTorque = 0.0f; - internal float reactionWheelPitch = 0.0f; - internal float reactionWheelRoll = 0.0f; - internal float reactionWheelYaw = 0.0f; - internal float reactionWheelAuthority = 0.0f; - internal bool reactionWheelActive = false; - internal bool reactionWheelDamaged = false; - private void UpdateReactionWheels() - { - // wheelState == Disabled, unit is disabled. - // wheelState == Active and inputSum == 0, unit is idle - // wheelState == Active and inputSum > 0, unit is torquing. - // inputVector provides current torque demand as ( pitch, roll, yaw ) - reactionWheelNetTorque = 0.0f; - reactionWheelPitch = 0.0f; - reactionWheelRoll = 0.0f; - reactionWheelYaw = 0.0f; - reactionWheelAuthority = 0.0f; - reactionWheelActive = false; - reactionWheelDamaged = false; - - float activeWheels = 0.0f; - float activePitch = 0.0f; - float activeRoll = 0.0f; - float activeYaw = 0.0f; - - for (int i = moduleReactionWheel.Length - 1; i >= 0; --i) - { - if (moduleReactionWheel[i].wheelState == ModuleReactionWheel.WheelState.Active) - { - reactionWheelAuthority += moduleReactionWheel[i].authorityLimiter; - - if (moduleReactionWheel[i].inputVector.sqrMagnitude > 0.0f) - { - float partMaxTorque = 0.0f; - if (moduleReactionWheel[i].PitchTorque > 0.0f) - { - float torque = moduleReactionWheel[i].inputVector.x / moduleReactionWheel[i].PitchTorque; - if (Mathf.Abs(torque) > 0.0f) - { - reactionWheelPitch += torque; - activePitch += 1.0f; - partMaxTorque = Mathf.Max(partMaxTorque, Mathf.Abs(torque)); - } - } - if (moduleReactionWheel[i].RollTorque > 0.0f) - { - float torque = moduleReactionWheel[i].inputVector.y / moduleReactionWheel[i].RollTorque; - if (Mathf.Abs(torque) > 0.0f) - { - reactionWheelRoll += torque; - activeRoll += 1.0f; - partMaxTorque = Mathf.Max(partMaxTorque, Mathf.Abs(torque)); - } - } - if (moduleReactionWheel[i].YawTorque > 0.0f) - { - float torque = moduleReactionWheel[i].inputVector.z / moduleReactionWheel[i].YawTorque; - if (Mathf.Abs(torque) > 0.0f) - { - reactionWheelYaw += torque; - activeYaw += 1.0f; - partMaxTorque = Mathf.Max(partMaxTorque, Mathf.Abs(torque)); - } - } - - reactionWheelActive = true; - reactionWheelNetTorque += partMaxTorque; - activeWheels += 1.0f; - } - } - else if (moduleReactionWheel[i].wheelState == ModuleReactionWheel.WheelState.Broken) - { - reactionWheelDamaged = true; - } - } - - if (reactionWheelAuthority > 0.0f) - { - reactionWheelAuthority = reactionWheelAuthority / (100.0f * moduleReactionWheel.Length); - } - - if (activeWheels > 1.0f) - { - reactionWheelNetTorque /= activeWheels; - } - if (activePitch > 1.0f) - { - reactionWheelPitch /= activePitch; - } - if (activeRoll > 1.0f) - { - reactionWheelRoll /= activeRoll; - } - if (activeYaw > 1.0f) - { - reactionWheelYaw /= activeYaw; - } - } - #endregion - - #region Resource Converters - internal class GeneralPurposeResourceConverter - { - internal List converterList = new List(); - internal ModuleResourceConverter[] moduleConverter = new ModuleResourceConverter[0]; - internal List outputRatioList = new List(); - internal float[] outputRatio = new float[0]; - internal string outputResource = string.Empty; - internal int id; - internal float netOutput = 0.0f; - internal bool converterActive = false; - } - internal List resourceConverterList = new List(); - private void UpdateResourceConverter() - { - for (int rsrc = resourceConverterList.Count - 1; rsrc >= 0; --rsrc) - { - GeneralPurposeResourceConverter rc = resourceConverterList[rsrc]; - rc.converterActive = false; - rc.netOutput = 0.0f; - - for (int i = rc.moduleConverter.Length - 1; i >= 0; --i) - { - rc.converterActive |= (rc.moduleConverter[i].IsActivated && !rc.moduleConverter[i].AlwaysActive); - - if (rc.moduleConverter[i].IsActivated) - { - rc.netOutput += (float)rc.moduleConverter[i].lastTimeFactor * rc.outputRatio[i]; - } - } - } - } - #endregion - - #region Resource Harvestor - private List scannerList = new List(); - internal ModuleAnalysisResource[] moduleScanner = new ModuleAnalysisResource[0]; - - private List harvesterList = new List(); - internal ModuleResourceHarvester[] moduleHarvester = new ModuleResourceHarvester[0]; - - internal bool surfaceScannerInstalled; - internal double harvesterCoreTemp; - internal double activeHarvesterCoreTemp; - internal double thermalEfficiency; - internal int activeDrillCount; - - private void UpdateOreHarvesters() - { - harvesterCoreTemp = 0.0d; - activeHarvesterCoreTemp = 0.0f; - thermalEfficiency = 0.0f; - activeDrillCount = 0; - if (moduleHarvester.Length > 0) - { - for (int i = moduleHarvester.Length - 1; i >= 0; --i) - { - harvesterCoreTemp += moduleHarvester[i].GetCoreTemperature(); - if (moduleHarvester[i].IsActivated) - { - activeHarvesterCoreTemp += moduleHarvester[i].GetCoreTemperature(); - thermalEfficiency += moduleHarvester[i].ThermalEfficiency.Evaluate((float)moduleHarvester[i].GetCoreTemperature()); - activeDrillCount++; - } - //moduleHarvester[i].status //OreRate i.e. X% Load (only while running) - } - if (activeDrillCount > 0) - { - activeHarvesterCoreTemp /= activeDrillCount; - harvesterCoreTemp = activeHarvesterCoreTemp; - thermalEfficiency /= activeDrillCount; - } - else - { - harvesterCoreTemp /= moduleHarvester.Length; - } - - } - - surfaceScannerInstalled = false; - if (moduleScanner.Length > 0) - { - surfaceScannerInstalled = true; - } - } - - #endregion - - #region Science - // For a bootstrap to interpreting science values, see https://github.com/KerboKatz/AutomatedScienceSampler/blob/master/source/AutomatedScienceSampler/DefaultActivator.cs - private List scienceExperimentList = new List(); - internal ModuleScienceExperiment[] moduleScienceExperiment = new ModuleScienceExperiment[0]; - private List scienceContainerList = new List(); - internal ModuleScienceContainer[] scienceContainer = new ModuleScienceContainer[0]; - - private class ModuleScienceExperimentComparer : IComparer - { - public int Compare(ModuleScienceExperiment a, ModuleScienceExperiment b) - { - return string.Compare(a.experiment.id, b.experiment.id); - } - } - ModuleScienceExperimentComparer mseComparer = new ModuleScienceExperimentComparer(); - internal class ScienceType - { - internal ScienceExperiment type; - internal List experiments = new List(); - }; - private List scienceTypeList = new List(); - internal ScienceType[] scienceType = new ScienceType[0]; - private void RebuildScienceTypes() - { - // Sort the array. - Array.Sort(moduleScienceExperiment, mseComparer); - int numExperiments = moduleScienceExperiment.Length; - for (int i = 0; i < numExperiments; ++i) - { - int idx = scienceTypeList.FindIndex(x => x.type.id == moduleScienceExperiment[i].experiment.id); - if (idx == -1) - { - ScienceType st = new ScienceType(); - st.type = moduleScienceExperiment[i].experiment; - st.experiments.Add(moduleScienceExperiment[i]); - scienceTypeList.Add(st); - } - else - { - scienceTypeList[idx].experiments.Add(moduleScienceExperiment[i]); - } - } - - // TransferModules does not copy values if the array isn't resized. However, we - // may need to do that here. For some reason, not every experiment has a - // valid ScienceExperiment the first time this code runs. - //TransferModules(scienceTypeList, ref scienceType); - if (scienceTypeList.Count != scienceType.Length) - { - scienceType = new ScienceType[scienceTypeList.Count]; - } - for (int i = scienceTypeList.Count - 1; i >= 0; --i) - { - scienceType[i] = scienceTypeList[i]; - } - scienceTypeList.Clear(); - } - - internal class ExperimentData - { - internal CelestialBody body; - internal ExperimentSituations situation; - internal ScienceSubject subject; - internal string biomeDisplayName; - internal string experimentResults; - }; - private Dictionary processedExperimentData = new Dictionary(); - internal ExperimentData GetExperimentData(string subjectID, ScienceExperiment experiment) - { - ExperimentData ed; - if (!processedExperimentData.TryGetValue(subjectID, out ed)) - { - ed = new ExperimentData(); - - string bodyName; - string biome; - ScienceUtil.GetExperimentFieldsFromScienceID(subjectID, out bodyName, out ed.situation, out biome); - ed.body = FlightGlobals.GetBodyByName(bodyName); - ed.biomeDisplayName = ScienceUtil.GetBiomedisplayName(ed.body, biome); - ed.subject = ResearchAndDevelopment.GetExperimentSubject(experiment, ed.situation, ed.body, biome, ed.biomeDisplayName); - ed.experimentResults = ResearchAndDevelopment.GetResults(subjectID); - - processedExperimentData.Add(subjectID, ed); - } - - return ed; - } - internal ScienceData GenerateScienceData(ScienceExperiment experiment, float xmitDataScalar) - { - string biome = string.Empty; - ExperimentSituations situation = ScienceUtil.GetExperimentSituation(vessel); - if (experiment.BiomeIsRelevantWhile(situation)) - { - biome = ScienceUtil.GetExperimentBiome(vessel.mainBody, vessel.latitude, vessel.longitude); - } - - ScienceSubject subject = ResearchAndDevelopment.GetExperimentSubject(experiment, situation, vessel.mainBody, biome, null); - return new ScienceData(experiment.baseValue * subject.dataScale, xmitDataScalar, 0.0f, - subject.id, subject.title); - } - internal bool CanRunExperiment(ModuleScienceExperiment exp) - { - int requirements = exp.usageReqMaskInternal; - // ExperimentUsageReqs.Always = 0, ExperimentUsageReqs.VesselControl = 1 << 0, ExperimentUsageReqs.CrewInVessel = 1 << 1 - if (requirements == 0 || (requirements & 3) != 0) - { - return true; - } - // ExperimentUsageReqs.CrewInPart = 1 << 2 - if ((requirements & 4) != 0) - { - if (exp.part.protoModuleCrew != null && exp.part.protoModuleCrew.Count > 0) - { - return true; - } - } - // ExperimentUsageReqs.ScientistCrew = 1 << 3 - if ((requirements & 8) != 0) - { - int idx = vessel.GetVesselCrew().FindIndex(x => x.trait == KerbalRoster.scientistTrait); - if (idx >= 0) - { - return true; - } - } - - return false; - } - - #endregion - - #region Thermal Management - private List radiatorList = new List(); - internal ModuleActiveRadiator[] moduleRadiator = new ModuleActiveRadiator[0]; - private List ablatorList = new List(); - internal ModuleAblator[] moduleAblator = new ModuleAblator[0]; - private List deployableRadiatorList = new List(); - internal ModuleDeployableRadiator[] moduleDeployableRadiator = new ModuleDeployableRadiator[0]; - internal double currentEnergyTransfer; - internal double maxEnergyTransfer; - internal bool radiatorActive; - internal bool radiatorInactive; - internal bool radiatorDeployable; - internal bool radiatorRetractable; - internal int radiatorMoving; - internal bool radiatorDamaged; - internal float hottestAblator; - internal float hottestAblatorMax; - internal float hottestAblatorSign = 0.0f; - private void UpdateRadiators() - { - radiatorActive = false; - radiatorInactive = false; - radiatorDeployable = false; - radiatorRetractable = false; - radiatorMoving = 0; - radiatorDamaged = false; - maxEnergyTransfer = 0.0; - currentEnergyTransfer = 0.0; - - if (moduleAblator.Length == 0) - { - hottestAblator = 0.0f; - hottestAblatorMax = 0.0f; - hottestAblatorSign = 0.0f; - } - else - { - float currentHottestAblatorDiff = float.MaxValue; - float currentMaxAblator = 0.0f; - float currentAblator = 0.0f; - for (int i = moduleAblator.Length - 1; i >= 0; --i) - { - Part ablatorPart = moduleAblator[i].part; - if ((ablatorPart.skinMaxTemp - ablatorPart.skinTemperature) < currentHottestAblatorDiff) - { - currentHottestAblatorDiff = (float)(ablatorPart.skinMaxTemp - ablatorPart.skinTemperature); - currentMaxAblator = (float)ablatorPart.skinMaxTemp; - currentAblator = (float)ablatorPart.skinTemperature; - } - } - - if (currentHottestAblatorDiff < float.MaxValue) - { - hottestAblatorSign = Math.Sign(currentAblator - hottestAblator); - hottestAblator = currentAblator; - hottestAblatorMax = currentMaxAblator; - } - else - { - hottestAblator = 0.0f; - hottestAblatorMax = 0.0f; - hottestAblatorSign = 0.0f; - } - } - - string tempString; - for (int i = moduleRadiator.Length - 1; i >= 0; --i) - { - if (moduleRadiator[i].IsCooling) - { - radiatorActive = true; - maxEnergyTransfer += moduleRadiator[i].maxEnergyTransfer; - float xv; - // Hack: I can't coax this information out through another - // public field. - tempString = moduleRadiator[i].status.Substring(0, moduleRadiator[i].status.Length - 1); - if (float.TryParse(tempString, out xv)) - { - currentEnergyTransfer += xv * 0.01 * moduleRadiator[i].maxEnergyTransfer; - } - } - else - { - radiatorInactive = true; - } - } - - for (int i = moduleDeployableRadiator.Length - 1; i >= 0; --i) - { - if (moduleDeployableRadiator[i].useAnimation) - { - radiatorRetractable |= (moduleDeployableRadiator[i].retractable && moduleDeployableRadiator[i].deployState == ModuleDeployablePart.DeployState.EXTENDED); - radiatorDeployable |= (moduleDeployableRadiator[i].deployState == ModuleDeployablePart.DeployState.RETRACTED); - if (moduleDeployableRadiator[i].deployState == ModuleDeployablePart.DeployState.RETRACTING) - { - radiatorMoving = -1; - } - else if (moduleDeployableRadiator[i].deployState == ModuleDeployablePart.DeployState.EXTENDING) - { - radiatorMoving = 1; - } - radiatorDamaged |= (moduleDeployableRadiator[i].deployState == ModuleDeployablePart.DeployState.BROKEN); - } - } - } - #endregion - - #region Wheels - private List wheelDamageList = new List(); - internal ModuleWheels.ModuleWheelDamage[] moduleWheelDamage = new ModuleWheels.ModuleWheelDamage[0]; - private List wheelDeploymentList = new List(); - internal ModuleWheels.ModuleWheelDeployment[] moduleWheelDeployment = new ModuleWheels.ModuleWheelDeployment[0]; - private List wheelBaseList = new List(); - internal ModuleWheelBase[] moduleWheelBase = new ModuleWheelBase[0]; - internal float wheelPosition = 0.0f; - internal float wheelDirection = 0.0f; - private void UpdateGear() - { - if (moduleWheelDeployment.Length > 0) - { - float newPosition = 0.0f; - float numWheels = 0.0f; - for (int i = moduleWheelDeployment.Length - 1; i >= 0; --i) - { - if (!moduleWheelDamage[i].isDamaged) - { - numWheels += 1.0f; - - newPosition += Mathf.InverseLerp(moduleWheelDeployment[i].retractedPosition, moduleWheelDeployment[i].deployedPosition, moduleWheelDeployment[i].position); - } - } - - if (numWheels > 1.0f) - { - newPosition /= numWheels; - } - - wheelDirection = newPosition - wheelPosition; - if (wheelDirection < 0.0f) - { - wheelDirection = -1.0f; - } - else if (wheelDirection > 0.0f) - { - wheelDirection = 1.0f; - } - wheelPosition = newPosition; - } - } - #endregion - - #region Modules Management - /// - /// Mark modules as potentially invalid to force reiterating over the - /// part and module lists. - /// - internal void InvalidateModules() - { - modulesInvalidated = true; - camerasReset = true; - } - - /// - /// Helper method to transfer a list to an array without creating a new - /// array (if the existing array is the same size as the list needs). - /// - /// - /// - /// - static void TransferModules(List sourceList, ref T[] destArray) - { - if (sourceList.Count != destArray.Length) - { - destArray = new T[sourceList.Count]; - - // Assume the list can't change without changing size - - // I shouldn't be able to add a module and remove a module - // of the same type in a single transaction, right? - // For whatever reason, this copy is faster than List.CopyTo() - for (int i = sourceList.Count - 1; i >= 0; --i) - { - destArray[i] = sourceList[i]; - } - } - sourceList.Clear(); - } - -#if TIME_REBUILD - private Stopwatch rebuildStopwatch = new Stopwatch(); -#endif - - /// - /// Iterate over all of everything to update tracked modules. - /// - private void RebuildModules() - { - // Merkur: - // LOAD TIME: - // [LOG 09:41:38.560] [MASVesselComputer] Scan parts in 0.172 ms, transfer modules in 0.776 ms - // STAGING: - // [LOG 09:42:11.132] [MASVesselComputer] Scan parts in 0.044 ms, transfer modules in 0.044 ms - -#if TIME_REBUILD - rebuildStopwatch.Reset(); - rebuildStopwatch.Start(); -#endif - - activeResources.Clear(); - for (int agIndex = hasActionGroup.Length - 1; agIndex >= 0; --agIndex) - { - hasActionGroup[agIndex] = false; - } - - activeEnginesGimbal = false; - moduleGraviticEngine = null; - - // Update the lists of modules - for (int partIdx = vessel.parts.Count - 1; partIdx >= 0; --partIdx) - { - PartResourceList resources = vessel.parts[partIdx].Resources; - UpdateResourceList(resources); - - PartModuleList Modules = vessel.parts[partIdx].Modules; - for (int moduleIdx = Modules.Count - 1; moduleIdx >= 0; --moduleIdx) - { - PartModule module = Modules[moduleIdx]; - if (module.isEnabled) - { - if (module is ModuleEngines) - { - ModuleEngines engine = module as ModuleEngines; - enginesList.Add(engine); - - // This is something we only need to worry about when rebulding the list. - if (activeEnginesGimbal == false && engine.EngineIgnited && engine.isOperational && Modules.GetModule() != null) - { - activeEnginesGimbal = true; - } - // Crazy Mode controllers are a special-case engine. - if (MASIVTOL.wbiWBIGraviticEngine_t != null && MASIVTOL.wbiWBIGraviticEngine_t.IsAssignableFrom(module.GetType())) - { - if (MASIVTOL.wbiCrazyModeIsActive(module)) - { - moduleGraviticEngine = module; - } - } - } - else if (module is ModuleAblator) - { - ablatorList.Add(module as ModuleAblator); - } - else if (module is ModuleGimbal) - { - gimbalsList.Add(module as ModuleGimbal); - } - else if (module is ModuleParachute) - { - parachuteList.Add(module as ModuleParachute); - } - else if (module is ModuleAlternator) - { - ModuleAlternator alternator = module as ModuleAlternator; - for (int i = alternator.resHandler.outputResources.Count - 1; i >= 0; --i) - { - if (alternator.resHandler.outputResources[i].name == MASConfig.ElectricCharge) - { - alternatorList.Add(alternator); - break; - } - } - } - else if (module is ModuleDeployableSolarPanel) - { - solarPanelList.Add(module as ModuleDeployableSolarPanel); - } - else if (module is ModuleGenerator) - { - ModuleGenerator generator = module as ModuleGenerator; - for (int i = generator.resHandler.outputResources.Count - 1; i >= 0; --i) - { - if (generator.resHandler.outputResources[i].name == MASConfig.ElectricCharge) - { - generatorList.Add(generator); - generatorOutputList.Add((float)generator.resHandler.outputResources[i].rate); - break; - } - } - } - else if (module is ModuleResourceConverter) - { - ModuleResourceConverter gen = module as ModuleResourceConverter; - List outputs = gen.Recipe.Outputs; - int outputCount = outputs.Count; - for (int rsrc = resourceConverterList.Count - 1; rsrc >= 0; --rsrc) - { - int rrIdx = outputs.FindIndex(x => x.ResourceName == resourceConverterList[rsrc].outputResource); - if (rrIdx > -1) - { - resourceConverterList[rsrc].converterList.Add(gen); - resourceConverterList[rsrc].outputRatioList.Add((float)outputs[rrIdx].Ratio); - } - } - } - else if (module is ModuleResourceHarvester) - { - harvesterList.Add(module as ModuleResourceHarvester); - } - else if (module is ModuleAnalysisResource) - { - scannerList.Add(module as ModuleAnalysisResource); - } - else if (module is ModuleDeployableAntenna) - { - antennaList.Add(module as ModuleDeployableAntenna); - } - else if (module is MASRadar) - { - radarList.Add(module as MASRadar); - } - else if (module is ModuleDeployableRadiator) - { - deployableRadiatorList.Add(module as ModuleDeployableRadiator); - } - else if (module is ModuleActiveRadiator) - { - radiatorList.Add(module as ModuleActiveRadiator); - } - else if (module is ModuleRCS) - { - rcsList.Add(module as ModuleRCS); - } - else if (module is ModuleReactionWheel) - { - reactionWheelList.Add(module as ModuleReactionWheel); - } - else if (MASIParachute.realChuteFound && module.GetType() == MASIParachute.rcAPI_t) - { - realchuteList.Add(module); - } - else if (module is MASCamera) - { - // Do not add broken cameras to the control list. - if ((module as MASCamera).IsDamaged() == false) - { - cameraList.Add(module as MASCamera); - } - } - else if (module is ModuleProceduralFairing) - { - proceduralFairingList.Add(module as ModuleProceduralFairing); - } - else if (module is MASThrustReverser) - { - thrustReverserList.Add(module as MASThrustReverser); - } - else if (module is MASIdEngine) - { - MASIdEngine idE = module as MASIdEngine; - if (idE.partId > 0 && idEnginesList.FindIndex(x => x.partId == idE.partId) == -1) - { - idEnginesList.Add(idE); - } - } - else if (module is ModuleWheelBase) - { - wheelBaseList.Add(module as ModuleWheelBase); - } - else if (module is ModuleWheels.ModuleWheelDamage) - { - wheelDamageList.Add(module as ModuleWheels.ModuleWheelDamage); - } - else if (module is ModuleWheels.ModuleWheelDeployment) - { - wheelDeploymentList.Add(module as ModuleWheels.ModuleWheelDeployment); - } - else if (module is ModuleCargoBay) - { - ModuleCargoBay cb = module as ModuleCargoBay; - if (Modules[cb.DeployModuleIndex] is ModuleAnimateGeneric) - { - // ModuleCargoBay has been used with one of 3 deploy modules: - // 1) ModuleProceduralFairing - we don't count these, since they're already covered by fairing controls. - // 2) ModuleServiceModule - KSP 1.4.x, used so far only in the expansion. Doesn't appear to have a way - // to control the service module deployment outside of staging. - // 3) ModuleAnimateGeneric - we can control these. - cargoBayList.Add(cb); - } - } - else if (module is MultiModeEngine) - { - multiModeEngineList.Add(module as MultiModeEngine); - } - else if (module is MASIdEngineGroup) - { - var group = module as MASIdEngineGroup; - if (group.partId != 0 && group.engine != null) - { - engineGroupList.Add(group); - } - } - else if (module is ModuleWheels.ModuleWheelBrakes) - { - brakesList.Add(module as ModuleWheels.ModuleWheelBrakes); - } - else if (module is ModuleResourceIntake) - { - resourceIntakeList.Add(module as ModuleResourceIntake); - } - else if (module is LaunchClamp) - { - launchClampList.Add(module as LaunchClamp); - } - else if (module is ModuleScienceExperiment) - { - scienceExperimentList.Add(module as ModuleScienceExperiment); - } - else if (module is ModuleScienceContainer) - { - scienceContainerList.Add(module as ModuleScienceContainer); - } - else if (module is ModuleAeroSurface) - { - airBrakeList.Add(module as ModuleAeroSurface); - } - else if (module is ModuleColorChanger) - { - ModuleColorChanger cc = module as ModuleColorChanger; - if (cc.toggleInFlight && cc.toggleAction) - { - colorChangerList.Add(cc); - } - } - else if (module is ModuleDataTransmitter) - { - ModuleDataTransmitter mdt = module as ModuleDataTransmitter; - // We currently only care about transmitters that can send science. - if (mdt.CanTransmit()) - { - transmitterList.Add(mdt); - } - } - - foreach (BaseAction ba in module.Actions) - { - if (ba.actionGroup != KSPActionGroup.None) - { - SetActionGroup(ba.actionGroup); - } - } - } - } - - // While we're here, update active resources - if (vessel.parts[partIdx].inverseStage >= vessel.currentStage) - { - activeResources.UnionWith(vessel.parts[partIdx].crossfeedPartSet.GetParts()); - } - } -#if TIME_REBUILD - TimeSpan scanTime = rebuildStopwatch.Elapsed; -#endif - - // Rebuild the part set. - if (partSet == null) - { - partSet = new PartSet(activeResources); - } - else - { - partSet.RebuildParts(activeResources); - } - - idEnginesList.Sort((a, b) => { return a.partId - b.partId; }); - - // Transfer the modules to an array, since the array is cheaper to - // iterate over, and we're going to be iterating over it a lot. - TransferModules(enginesList, ref moduleEngines); - if (invMaxISP.Length != moduleEngines.Length) - { - invMaxISP = new float[moduleEngines.Length]; - } - for (int i = moduleEngines.Length - 1; i >= 0; --i) - { - // MOARdV TODO: This ignores the velocity ISP curve of jets. - float maxIsp, minIsp; - moduleEngines[i].atmosphereCurve.FindMinMaxValue(out minIsp, out maxIsp); - invMaxISP[i] = 1.0f / maxIsp; - } - - TransferModules(airBrakeList, ref moduleAirBrake); - TransferModules(alternatorList, ref moduleAlternator); - TransferModules(ablatorList, ref moduleAblator); - TransferModules(cargoBayList, ref moduleCargoBay); - TransferModules(colorChangerList, ref moduleColorChanger); - TransferModules(transmitterList, ref moduleTransmitter); - TransferModules(antennaList, ref moduleAntenna); - TransferModules(deployableRadiatorList, ref moduleDeployableRadiator); - TransferModules(engineGroupList, ref engineGroup); - TransferModules(idEnginesList, ref moduleIdEngines); - TransferModules(generatorList, ref moduleGenerator); - TransferModules(generatorOutputList, ref generatorOutput); - TransferModules(gimbalsList, ref moduleGimbals); - TransferModules(multiModeEngineList, ref multiModeEngines); - TransferModules(parachuteList, ref moduleParachute); - TransferModules(radarList, ref moduleRadar); - TransferModules(radiatorList, ref moduleRadiator); - TransferModules(rcsList, ref moduleRcs); - TransferModules(realchuteList, ref moduleRealChute); - TransferModules(reactionWheelList, ref moduleReactionWheel); - TransferModules(resourceIntakeList, ref moduleResourceIntake); - TransferModules(solarPanelList, ref moduleSolarPanel); - TransferModules(cameraList, ref moduleCamera); - TransferModules(proceduralFairingList, ref moduleProceduralFairing); - TransferModules(thrustReverserList, ref moduleThrustReverser); - TransferModules(wheelDamageList, ref moduleWheelDamage); - TransferModules(wheelDeploymentList, ref moduleWheelDeployment); - TransferModules(wheelBaseList, ref moduleWheelBase); - TransferModules(brakesList, ref moduleBrakes); - TransferModules(scienceExperimentList, ref moduleScienceExperiment); - TransferModules(scienceContainerList, ref scienceContainer); - TransferModules(launchClampList, ref moduleLaunchClamp); - - for (int i = resourceConverterList.Count - 1; i >= 0; --i) - { - TransferModules(resourceConverterList[i].converterList, ref resourceConverterList[i].moduleConverter); - TransferModules(resourceConverterList[i].outputRatioList, ref resourceConverterList[i].outputRatio); - } - TransferModules(harvesterList, ref moduleHarvester); - TransferModules(scannerList, ref moduleScanner); - RebuildScienceTypes(); - -#if TIME_REBUILD - TimeSpan transferTime = rebuildStopwatch.Elapsed - scanTime; - - Utility.LogMessage(this, "Scan parts in {0:0.000} ms, transfer modules in {1:0.000} ms", - ((double)scanTime.Ticks) / ((double)TimeSpan.TicksPerMillisecond), - ((double)transferTime.Ticks) / ((double)TimeSpan.TicksPerMillisecond)); -#endif - UpdateDockingNode(vessel.GetReferenceTransformPart()); - } - -#if TIME_UPDATES - private Stopwatch updateStopwatch = new Stopwatch(); -#endif - /// - /// Update per-module data after refreshing the module lists, if needed. - /// - private void UpdateModuleData() - { - if (modulesInvalidated) - { - InitRebuildPartResources(); - - RebuildModules(); - - modulesInvalidated = false; - scienceInvalidated = false; - } - if (scienceInvalidated) - { - RebuildScienceTypes(); - scienceInvalidated = false; - } - -#if TIME_UPDATES - updateStopwatch.Reset(); - updateStopwatch.Start(); -#endif - bool requestReset = false; - UpdateAntenna(); - UpdateCamera(); - UpdateCargoBay(); - UpdateDockingNodeState(); - requestReset |= UpdateEngines(); - UpdateGimbals(); - UpdatePower(); - UpdateProceduralFairing(); - UpdateRadiators(); - UpdateGear(); - UpdateRcs(); - UpdateReactionWheels(); - UpdateResourceConverter(); - UpdateOreHarvesters(); - - if (requestReset) - { - InvalidateModules(); - } -#if TIME_UPDATES - TimeSpan updateTime = updateStopwatch.Elapsed; - - Utility.LogMessage(this, "UpdateModuleData in {0:0.000} ms", - ((double)updateTime.Ticks) / ((double)TimeSpan.TicksPerMillisecond)); -#endif - } - #endregion - } -} From 8059d9986cb5fbf5bf3951d73d46365d7226e5dd Mon Sep 17 00:00:00 2001 From: Sovetskysoyuz Date: Thu, 11 Feb 2021 16:19:02 -0500 Subject: [PATCH 23/48] Remove extra file --- AvionicsSystems.csproj | 1 - Source/MASIAtmosphereAutopilot.cs | 214 ------------------------------ 2 files changed, 215 deletions(-) delete mode 100644 Source/MASIAtmosphereAutopilot.cs diff --git a/AvionicsSystems.csproj b/AvionicsSystems.csproj index 718c1a68..81193923 100644 --- a/AvionicsSystems.csproj +++ b/AvionicsSystems.csproj @@ -114,7 +114,6 @@ - diff --git a/Source/MASIAtmosphereAutopilot.cs b/Source/MASIAtmosphereAutopilot.cs deleted file mode 100644 index 97d9b1e6..00000000 --- a/Source/MASIAtmosphereAutopilot.cs +++ /dev/null @@ -1,214 +0,0 @@ -/***************************************************************************** - * The MIT License (MIT) - * - * Copyright (c) 2021 Sovetskysoyuz - * based on code by MoarDV - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to - * deal in the Software without restriction, including without limitation the - * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or - * sell copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - * - ****************************************************************************/ -using MoonSharp.Interpreter; -using System; -using System.Collections.Generic; -using System.Reflection; -using System.Text; - -namespace AvionicsSystems -{ - /// - /// MASIAtmosphereAutopilot is the interface with the Atmosphere Autopilot mod. - /// - /// atmosphereAutopilot - /// - /// The MASIAtmosphereAutopilot class provides an interface between the Atmosphere Autopilot mod - /// and Avionics Systems. It provides two informational variables and one - /// action. - /// - internal class MASIAtmosphereAutopilot - { - static private bool aaFound = false; - - // basic mod control functions - static private Type aaAPI_t; - // control over which controller is active - static private Type aaTopModuleAPI_t; - // static private MethodInfo txMethod_t; - // static private MethodInfo rxMethod_t; - // static private MethodInfo chatterMethod_t; - // replace these with MethodInfo for the requisite AA methods - static private MethodInfo activateAutopilot_t; - - // internal Func chattererTx; - // internal Func chattererRx; - // internal Action chattererStartTalking; - // Figure out what these need to be replaced with - internal Action aaActivateAutopilot; - - [MoonSharpHidden] - public MASIAtmosphereAutopilot() - { - InitAtmosphereAutopilotMethods(); - } - - ~MASIAtmosphereAutopilot() - { - } - - private void InitAtmosphereAutopilotMethods() - { - if (!aaFound) - { - return; - } - - object atmosphereAutopilot = UnityEngine.Object.FindObjectOfType(aaAPI_t); - object topModuleManager = UnityEngine.Object.FindObjectOfType(aaTopModuleAPI_t); - - // chattererTx = (Func)Delegate.CreateDelegate(typeof(Func), chatterer, txMethod_t); - // chattererRx = (Func)Delegate.CreateDelegate(typeof(Func), chatterer, rxMethod_t); - // chattererStartTalking = (Action)Delegate.CreateDelegate(typeof(Action), chatterer, chatterMethod_t); - // replace these with whatever functions wound up being defined above. - - aaActivateAutopilot = (Action)Delegate.CreateDelegate(typeof(Action), topModuleManager, activateAutopilot_t); - } - - /// - /// The AtmosphereAutopilot category provides the interface with the AtmosphereAutopilot mod (when installed). - /// - #region AtmosphereAutopilot - - [MASProxyAttribute(Immutable = true)] - /// - /// Reports whether the AtmosphereAutopilot mod is installed. - /// - /// 1 if AtmosphereAutopilot is installed, 0 otherwise - public double Available() - { - return (aaFound) ? 1.0 : 0.0; - } - - /// - /// Reports on whether or not Mission Control is communicating with the capsule. - /// - /// 1 if ground control is talking, 0 otherwise. - // public double Receiving() - // { - // if (chattererFound) - // { - // return (chattererRx()) ? 1.0 : 0.0; - // } - // else - // { - // return 0.0; - // } - // } - - /// - /// If the comm channel is idle, start a chatter sequence. If there is - /// already an exchange active, do nothing. - /// - /// 1 if Chatterer starts transmitting; 0 otherwise. - // public double StartTalking() - // { - // if (chattererFound) - // { - // if (!chattererTx() && !chattererRx()) - // { - // chattererStartTalking(); - // return 1.0; - // } - // } - - // return 0.0; - // } - - public double AAActivateAutopilot() - { - if(aaFound) - { - aaActivateAutopilot(); - return 1.0; - } - - return 0.0; - } - - /// - /// Reports whether or not the vessel is transmitting to Mission Control. - /// - /// 1 if the vessel is transmitting, 0 otherwise. - // public double Transmitting() - // { - // if (chattererFound) - // { - // return (chattererTx()) ? 1.0 : 0.0; - // } - // else - // { - // return 0.0; - // } - // } - #endregion - - [MoonSharpHidden] - internal void UpdateVessel() - { - InitAtmosphereAutopilotMethods(); - } - - #region Reflection Configuration - static MASIAtmosphereAutopilot() - { - aaFound = false; - aaAPI_t = Utility.GetExportedType("AtmosphereAutopilot", "AtmosphereAutopilot.AtmosphereAutopilot"); - aaTopModuleAPI_t = Utility.GetExportedType("AtmosphereAutopilot", "AtmosphereAutopilot.TopModuleManager"); - if(aaAPI_t != null) - { - // txMethod_t = chattererAPI_t.GetMethod("VesselIsTransmitting", BindingFlags.Instance | BindingFlags.Public); - // if (txMethod_t == null) - // { - // throw new NotImplementedException("txMethod_t"); - // } - - // rxMethod_t = chattererAPI_t.GetMethod("VesselIsReceiving", BindingFlags.Instance | BindingFlags.Public); - // if (rxMethod_t == null) - // { - // throw new NotImplementedException("rxMethod_t"); - // } - - // chatterMethod_t = chattererAPI_t.GetMethod("InitiateChatter", BindingFlags.Instance | BindingFlags.Public); - // if (chatterMethod_t == null) - // { - // throw new NotImplementedException("chatterMethod_t"); - // } - - aaFound = true; - } - if(aaTopModuleAPI_t != null) - { - activateAutopilot_t = aaTopModuleAPI_t.GetMethod("activateAutopilot", BindingFlags.Instance | BindingFlags.Public); - if (activateAutopilot_t == null) - { - throw new NotImplementedException("activateAutopilot_t"); - } - } - } - #endregion - } -} From 6d05db398630bd1d7c26bdd8d3a70a588092c5b5 Mon Sep 17 00:00:00 2001 From: Sovetskysoyuz Date: Thu, 11 Feb 2021 16:29:06 -0500 Subject: [PATCH 24/48] Add SetNextDockToReference --- Source/MASFlightComputerProxy.cs | 45 ++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/Source/MASFlightComputerProxy.cs b/Source/MASFlightComputerProxy.cs index 6504a293..2decd7f3 100644 --- a/Source/MASFlightComputerProxy.cs +++ b/Source/MASFlightComputerProxy.cs @@ -3917,6 +3917,51 @@ public double SetDockToReference() } } + public double SetNextDockToReference() + { + /*if (vc.dockingNode != null) + { + vessel.SetReferenceTransform(vc.dockingNode.part); + return 1.0; + } + else + { + return 0.0; + }*/ + + if (vc.ownDockingPorts.Length == 0) + { + return 0.0; + } + else if (fc.part == vessel.GetReferenceTransformPart()) + { + vessel.SetReferenceTransform(vc.ownDockingPorts[0].part); + return 1.0; + } + else if (vc.referenceTransformType == MASVesselComputer.ReferenceType.DockingPort) + { + if (vc.ownDockingPorts.Length == 1) + { + // We're already referencing the only docking port. + return 1.0; + } + + ModuleDockingNode activeOwnNode = vc.dockingNode as ModuleDockingNode; + int currentOwnIndex = Array.FindIndex(vc.ownDockingPorts, x => x == activeOwnNode); + if (currentOwnIndex == -1) + { + vessel.SetReferenceTransform(vc.ownDockingPorts[0].part); + } + else + { + vessel.SetReferenceTransform(vc.ownDockingPorts[(currentOwnIndex + 1) % vc.ownDockingPorts.Length].part); + } + return 1.0; + } + + return 0.0; + } + /// /// Set the primary grapple to be the reference transform. /// From 65712b6a8f22e02c10a83c3149952f33406d7cb0 Mon Sep 17 00:00:00 2001 From: Sovetskysoyuz Date: Thu, 11 Feb 2021 17:09:19 -0500 Subject: [PATCH 25/48] Added missing comment --- Source/MASFlightComputerProxy.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Source/MASFlightComputerProxy.cs b/Source/MASFlightComputerProxy.cs index 2decd7f3..d67f9f48 100644 --- a/Source/MASFlightComputerProxy.cs +++ b/Source/MASFlightComputerProxy.cs @@ -3917,6 +3917,10 @@ public double SetDockToReference() } } + /// + /// Set the next docking port to be the reference transform. + /// + /// 1 if the reference was changed, 0 otherwise. public double SetNextDockToReference() { /*if (vc.dockingNode != null) From b92c5fb5f5fae48bad0f8fd3a6cd812e61028c5f Mon Sep 17 00:00:00 2001 From: Sovetskysoyuz Date: Thu, 11 Feb 2021 17:41:24 -0500 Subject: [PATCH 26/48] Update build rules --- AvionicsSystems.csproj | 4174 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 4168 insertions(+), 6 deletions(-) diff --git a/AvionicsSystems.csproj b/AvionicsSystems.csproj index 81193923..e9a2bbe7 100644 --- a/AvionicsSystems.csproj +++ b/AvionicsSystems.csproj @@ -25,7 +25,7 @@ pdbonly true - bin\Release\ + D:\KSPMod\VSOutput\IVAs\ TRACE prompt 4 @@ -33,59 +33,77 @@ D:\KSPMod\IVAInstall\Minimal Mods\Kerbal Space Program\KSP_x64_Data\Managed\Assembly-CSharp.dll + False D:\KSPMod\IVAInstall\Minimal Mods\Kerbal Space Program\KSP_x64_Data\Managed\Assembly-CSharp-firstpass.dll + False ..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Kerbal Space Program\GameData\AtmosphereAutopilot\AtmosphereAutopilot.dll + False ..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Kerbal Space Program\GameData\AtmosphereAutopilot\AtmosphereAutopilot.UI.dll + False ..\..\..\Downloads\Games\KSP\Modding\moonsharp_release_2.0.0.0\repl\MoonSharp.Interpreter.dll + False D:\KSPMod\IVAInstall\Minimal Mods\Kerbal Space Program\KSP_x64_Data\Managed\UnityEngine.dll + False D:\KSPMod\IVAInstall\Minimal Mods\Kerbal Space Program\KSP_x64_Data\Managed\UnityEngine.AnimationModule.dll + False D:\KSPMod\IVAInstall\Minimal Mods\Kerbal Space Program\KSP_x64_Data\Managed\UnityEngine.AssetBundleModule.dll + False D:\KSPMod\IVAInstall\Minimal Mods\Kerbal Space Program\KSP_x64_Data\Managed\UnityEngine.AudioModule.dll + False D:\KSPMod\IVAInstall\Minimal Mods\Kerbal Space Program\KSP_x64_Data\Managed\UnityEngine.CoreModule.dll + False D:\KSPMod\IVAInstall\Minimal Mods\Kerbal Space Program\KSP_x64_Data\Managed\UnityEngine.IMGUIModule.dll + False D:\KSPMod\IVAInstall\Minimal Mods\Kerbal Space Program\KSP_x64_Data\Managed\UnityEngine.InputLegacyModule.dll + False D:\KSPMod\IVAInstall\Minimal Mods\Kerbal Space Program\KSP_x64_Data\Managed\UnityEngine.PhysicsModule.dll + False D:\KSPMod\IVAInstall\Minimal Mods\Kerbal Space Program\KSP_x64_Data\Managed\UnityEngine.TextRenderingModule.dll + False D:\KSPMod\IVAInstall\Minimal Mods\Kerbal Space Program\KSP_x64_Data\Managed\UnityEngine.UI.dll + False D:\KSPMod\IVAInstall\Minimal Mods\Kerbal Space Program\KSP_x64_Data\Managed\UnityEngine.UIModule.dll + False D:\KSPMod\IVAInstall\Minimal Mods\Kerbal Space Program\KSP_x64_Data\Managed\UnityEngine.UnityWebRequestAssetBundleModule.dll + False D:\KSPMod\IVAInstall\Minimal Mods\Kerbal Space Program\KSP_x64_Data\Managed\UnityEngine.UnityWebRequestModule.dll + False @@ -192,13 +210,4157 @@ - + + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + - copy $(TargetPath) "D:\Games\KSP-IVA\GameData\MOARdV\AvionicsSystems" -copy $(TargetPath) $(SolutionDir)GameData\MOARdV\AvionicsSystems\ -$(SolutionDir)Documentor\$(OutDir)Documentor.exe $(SolutionDir)Source\MASIFAR.cs $(SolutionDir)Source\MASIKAC.cs $(SolutionDir)Source\MASIChatterer.cs $(SolutionDir)Source\MASIEngine.cs $(SolutionDir)Source\MASIMechJeb.cs $(SolutionDir)Source\MASINavigation.cs $(SolutionDir)Source\MASIRealChute.cs $(SolutionDir)Source\MASITransfer.cs $(SolutionDir)Source\MASFlightComputerProxy.cs $(SolutionDir)Source\MASFlightComputerProxy2.cs $(SolutionDir)Source\MASFlightComputerProxy3.cs $(SolutionDir)Source\MASIVTOL.cs $(SolutionDir)Source\MASIKerbalEngineer.cs - + copy $(TargetPath) "D:\KSPMod\VSOutput\IVAs\GameData\MOARdV\AvionicsSystems"