diff --git a/GameData/JSI/RPMPodPatches/BasicMFD/MFD40x20.cfg b/GameData/JSI/RPMPodPatches/BasicMFD/MFD40x20.cfg index 51889de2..bab3987e 100644 --- a/GameData/JSI/RPMPodPatches/BasicMFD/MFD40x20.cfg +++ b/GameData/JSI/RPMPodPatches/BasicMFD/MFD40x20.cfg @@ -371,7 +371,7 @@ PROP text = MechJeb software not installed. PAGEHANDLER:NEEDS[MechJeb2] { - name = MechJebRPM + name = MechJebRPMMenu method = ShowMenu pageActiveMethod = PageActive buttonClickMethod = ClickProcessor diff --git a/GameData/JSI/RasterPropMonitor/Plugins/mscorlib.dll b/GameData/JSI/RasterPropMonitor/Plugins/mscorlib.dll new file mode 100644 index 00000000..0f66cf92 Binary files /dev/null and b/GameData/JSI/RasterPropMonitor/Plugins/mscorlib.dll differ diff --git a/GameData/JSI/RasterPropMonitor/RasterPropMonitor.version b/GameData/JSI/RasterPropMonitor/RasterPropMonitor.version index d19c0493..af36a8a9 100644 --- a/GameData/JSI/RasterPropMonitor/RasterPropMonitor.version +++ b/GameData/JSI/RasterPropMonitor/RasterPropMonitor.version @@ -5,5 +5,5 @@ "VERSION": "1.0.2.0", "KSP_VERSION": "1.12", "KSP_VERSION_MIN": "1.12.3", - "KSP_VERSION_MAX": "1.12" + "KSP_VERSION_MAX": "'1.12'" } diff --git a/MechJebRPM/MechJebRPM.csproj b/MechJebRPM/MechJebRPM.csproj.disabled similarity index 100% rename from MechJebRPM/MechJebRPM.csproj rename to MechJebRPM/MechJebRPM.csproj.disabled diff --git a/RasterPropMonitor/Auxiliary modules/JSINumericInput.cs b/RasterPropMonitor/Auxiliary modules/JSINumericInput.cs index dc65378a..e68ab56c 100644 --- a/RasterPropMonitor/Auxiliary modules/JSINumericInput.cs +++ b/RasterPropMonitor/Auxiliary modules/JSINumericInput.cs @@ -25,7 +25,7 @@ namespace JSI { - class JSINumericInput : InternalModule + public class JSINumericInput : InternalModule { [SerializeReference] ConfigNodeHolder moduleConfig; diff --git a/RasterPropMonitor/Core/OrbitExtensions.cs b/RasterPropMonitor/Core/OrbitExtensions.cs index 91e189ce..46daee8e 100644 --- a/RasterPropMonitor/Core/OrbitExtensions.cs +++ b/RasterPropMonitor/Core/OrbitExtensions.cs @@ -138,7 +138,6 @@ public static double RelativeInclination_DEG(this Orbit a, Orbit b) // note strangely, the stock RelativeInclination function is already in degrees return Orbit.RelativeInclination(a, b); } -#if false // These "Swapped" functions translate preexisting Orbit class functions into world // space. For some reason, Orbit class functions seem to use a coordinate system @@ -450,6 +449,5 @@ public static Vector3d DeltaVToManeuverNodeCoordinates(this Orbit o, double UT, Vector3d.Dot(-o.NormalPlus(UT), dV), Vector3d.Dot(o.Prograde(UT), dV)); } -#endif } } diff --git a/RasterPropMonitor/Handlers/JSIGraphingBackground.cs b/RasterPropMonitor/Handlers/JSIGraphingBackground.cs index 6d57a1b6..c9efe92a 100644 --- a/RasterPropMonitor/Handlers/JSIGraphingBackground.cs +++ b/RasterPropMonitor/Handlers/JSIGraphingBackground.cs @@ -26,7 +26,7 @@ namespace JSI { // JSIGraphingBackground provides an editable / configurable way to render // one or more data in a graphical manner. - class JSIGraphingBackground : InternalModule + public class JSIGraphingBackground : InternalModule { [KSPField] public string layout = null; diff --git a/RasterPropMonitor/Handlers/JSIHeadsUpDisplay.cs b/RasterPropMonitor/Handlers/JSIHeadsUpDisplay.cs index 02106da6..605ab479 100644 --- a/RasterPropMonitor/Handlers/JSIHeadsUpDisplay.cs +++ b/RasterPropMonitor/Handlers/JSIHeadsUpDisplay.cs @@ -31,7 +31,7 @@ namespace JSI * are displayed with a "ladder" (texture). Strips also provide heading * information. ************************************************************************/ - class JSIHeadsUpDisplay : InternalModule + public class JSIHeadsUpDisplay : InternalModule { [KSPField] public string cameraTransform = string.Empty; diff --git a/RasterPropMonitor/Handlers/JSIMechJeb.cs b/RasterPropMonitor/Handlers/JSIMechJeb.cs index 84004099..7d042e4e 100644 --- a/RasterPropMonitor/Handlers/JSIMechJeb.cs +++ b/RasterPropMonitor/Handlers/JSIMechJeb.cs @@ -1,4 +1,4 @@ -/***************************************************************************** +/***************************************************************************** * RasterPropMonitor * ================= * Plugin for Kerbal Space Program @@ -286,22 +286,23 @@ static JSIMechJeb() throw new NotImplementedException("mjGetComputerModule"); } getComputerModule = DynamicMethodDelegateFactory.Create(mjGetComputerModule); - mjCoreTarget = mjMechJebCore_t.GetField("target", BindingFlags.Instance | BindingFlags.Public); + // MechJeb 2.15+ uses PascalCase field names + mjCoreTarget = mjMechJebCore_t.GetField("Target", BindingFlags.Instance | BindingFlags.Public); if (mjCoreTarget == null) { throw new NotImplementedException("mjCoreTarget"); } - mjCoreNode = mjMechJebCore_t.GetField("node", BindingFlags.Instance | BindingFlags.Public); + mjCoreNode = mjMechJebCore_t.GetField("Node", BindingFlags.Instance | BindingFlags.Public); if (mjCoreNode == null) { throw new NotImplementedException("mjCoreNode"); } - mjCoreAttitude = mjMechJebCore_t.GetField("attitude", BindingFlags.Instance | BindingFlags.Public); + mjCoreAttitude = mjMechJebCore_t.GetField("Attitude", BindingFlags.Instance | BindingFlags.Public); if (mjCoreAttitude == null) { throw new NotImplementedException("mjCoreAttitude"); } - mjCoreVesselState = mjMechJebCore_t.GetField("vesselState", BindingFlags.Instance | BindingFlags.Public); + mjCoreVesselState = mjMechJebCore_t.GetField("VesselState", BindingFlags.Instance | BindingFlags.Public); if (mjCoreVesselState == null) { throw new NotImplementedException("mjCoreVesselState"); @@ -399,60 +400,62 @@ static JSIMechJeb() { throw new NotImplementedException("mjAbsoluteVector_t"); } - mjAbsoluteVectorLat = mjAbsoluteVector_t.GetField("latitude", BindingFlags.Instance | BindingFlags.Public); + // MechJeb 2.15+ uses PascalCase + mjAbsoluteVectorLat = mjAbsoluteVector_t.GetField("Latitude", BindingFlags.Instance | BindingFlags.Public); if (mjAbsoluteVectorLat == null) { throw new NotImplementedException("mjAbsoluteVectorLat"); } - mjAbsoluteVectorLon = mjAbsoluteVector_t.GetField("longitude", BindingFlags.Instance | BindingFlags.Public); + mjAbsoluteVectorLon = mjAbsoluteVector_t.GetField("Longitude", BindingFlags.Instance | BindingFlags.Public); if (mjAbsoluteVectorLon == null) { throw new NotImplementedException("mjAbsoluteVectorLon"); } - // MechJebModuleAscentAutopilot - Type mjMechJebModuleAscentAutopilot_t = loadedMechJebAssy.assembly.GetExportedTypes() - .SingleOrDefault(t => t.FullName == "MuMech.MechJebModuleAscentAutopilot"); - if (mjMechJebModuleAscentAutopilot_t == null) + // MechJebModuleAscentSettings - contains orbit altitude, inclination, and autopilot access + // Note: MJ 2.15+ consolidated AscentAutopilot and AscentGuidance into AscentSettings + Type mjMechJebModuleAscentSettings_t = loadedMechJebAssy.assembly.GetExportedTypes() + .SingleOrDefault(t => t.FullName == "MuMech.MechJebModuleAscentSettings"); + if (mjMechJebModuleAscentSettings_t == null) { - throw new NotImplementedException("mjMechJebModuleAscentAutopilot_t"); + throw new NotImplementedException("mjMechJebModuleAscentSettings_t"); } - launchOrbitAltitude = mjMechJebModuleAscentAutopilot_t.GetField("desiredOrbitAltitude"); + // DesiredOrbitAltitude field (PascalCase in MJ 2.15+) + launchOrbitAltitude = mjMechJebModuleAscentSettings_t.GetField("DesiredOrbitAltitude", BindingFlags.Instance | BindingFlags.Public); if (launchOrbitAltitude == null) { throw new NotImplementedException("launchOrbitAltitude"); } - // MOARdV TODO: when the next version of MJ is out, this will be the only way to engage - // the AP, so we will want to throw an exception if aapEngaged is null. - PropertyInfo aapEngaged = mjMechJebModuleAscentAutopilot_t.GetProperty("Engaged"); - if (aapEngaged != null) + // DesiredInclination field (also in AscentSettings, PascalCase) + launchOrbitInclination = mjMechJebModuleAscentSettings_t.GetField("DesiredInclination", BindingFlags.Instance | BindingFlags.Public); + if (launchOrbitInclination == null) { - MethodInfo getter = aapEngaged.GetGetMethod(); - getAscentAutopilotEngaged = DynamicMethodDelegateFactory.CreateFuncBool(getter); - if (getAscentAutopilotEngaged == null) - { - throw new NotImplementedException("getAscentAutopilotEngaged"); - } + throw new NotImplementedException("launchOrbitInclination"); + } - MethodInfo setter = aapEngaged.GetSetMethod(); - setAscentAutopilotEngaged = DynamicMethodDelegateFactory.Create(setter); - if (setAscentAutopilotEngaged == null) + // AscentAutopilot property returns the current autopilot module + // The autopilot's Enabled property (inherited from ComputerModule) controls engagement + PropertyInfo aapAutopilot = mjMechJebModuleAscentSettings_t.GetProperty("AscentAutopilot", BindingFlags.Instance | BindingFlags.Public); + if (aapAutopilot != null) + { + // Get the autopilot type to access its Enabled property + Type mjAscentAutopilot_t = aapAutopilot.PropertyType; + // The Enabled property is on ComputerModule base class + PropertyInfo aapEnabled = mjAscentAutopilot_t.GetProperty("Enabled", BindingFlags.Instance | BindingFlags.Public); + if (aapEnabled != null) { - throw new NotImplementedException("setAscentAutopilotEngaged"); + MethodInfo getter = aapEnabled.GetGetMethod(); + if (getter != null) + { + getAscentAutopilotEngaged = DynamicMethodDelegateFactory.CreateFuncBool(getter); + } + MethodInfo setter = aapEnabled.GetSetMethod(); + if (setter != null) + { + setAscentAutopilotEngaged = DynamicMethodDelegateFactory.Create(setter); + } } } - // MechJebModuleAscentAutopilot - Type mjMechJebModuleAscentGuidance_t = loadedMechJebAssy.assembly.GetExportedTypes() - .SingleOrDefault(t => t.FullName == "MuMech.MechJebModuleAscentGuidance"); - if (mjMechJebModuleAscentGuidance_t == null) - { - throw new NotImplementedException("mjMechJebModuleAscentGuidance_t"); - } - launchOrbitInclination = mjMechJebModuleAscentGuidance_t.GetField("desiredInclination"); - if (launchOrbitInclination == null) - { - throw new NotImplementedException("launchOrbitInclination"); - } Type mjEditableDoubleMult_t = loadedMechJebAssy.assembly.GetExportedTypes() .SingleOrDefault(t => t.FullName == "MuMech.EditableDoubleMult"); @@ -460,12 +463,8 @@ static JSIMechJeb() { throw new NotImplementedException("mjEditableDoubleMult_t"); } - getEditableDoubleMultMultiplier = mjEditableDoubleMult_t.GetField("multiplier"); - if (getEditableDoubleMultMultiplier == null) - { - throw new NotImplementedException("getEditableDoubleMultMultiplier"); - } - PropertyInfo edmVal = mjEditableDoubleMult_t.GetProperty("val"); + // MJ 2.15+ made multiplier private (_multiplier), so skip lookup - it's not used anyway + PropertyInfo edmVal = mjEditableDoubleMult_t.GetProperty("Val"); // Was "val", now PascalCase if (edmVal == null) { throw new NotImplementedException("edmVal"); @@ -557,20 +556,40 @@ static JSIMechJeb() } getTargetOrbit = DynamicMethodDelegateFactory.CreateFuncObject(mjGetTargetOrbit); - // MuMech.FuelFlowSimulation - Type mjFuelFlowSimulation_t = loadedMechJebAssy.assembly.GetExportedTypes() - .SingleOrDefault(t => t.FullName == "MuMech.FuelFlowSimulation"); - if (mjFuelFlowSimulation_t == null) + // FuelStats type - In MJ 2.15+, this is in MechJebLib.FuelFlowSimulation.FuelStats (separate assembly) + Type mjFuelStats_t = null; + + // First try to find MechJebLib assembly (new MJ2 structure) + AssemblyLoader.LoadedAssembly mechJebLibAssy = AssemblyLoader.loadedAssemblies + .SingleOrDefault(a => a.assembly.GetName().Name == "MechJebLib"); + if (mechJebLibAssy != null) { - throw new NotImplementedException("mjFuelFlowSimulation_t"); + mjFuelStats_t = mechJebLibAssy.assembly.GetExportedTypes() + .SingleOrDefault(t => t.FullName == "MechJebLib.FuelFlowSimulation.FuelStats"); } - // MuMech.FuelFlowSimulation.Stats OR MuMech.FuelFlowSimulation.FuelStats - Type mjFuelFlowSimulationStats_t = mjFuelFlowSimulation_t.GetNestedType("Stats") ?? mjFuelFlowSimulation_t.GetNestedType("FuelStats"); - if (mjFuelFlowSimulationStats_t == null) + + // If not found, try old structure (nested type in MuMech.FuelFlowSimulation) + if (mjFuelStats_t == null) { - throw new NotImplementedException("mjFuelFlowSimulationStats_t"); + Type mjFuelFlowSimulation_t = loadedMechJebAssy.assembly.GetExportedTypes() + .SingleOrDefault(t => t.FullName == "MuMech.FuelFlowSimulation"); + if (mjFuelFlowSimulation_t != null) + { + mjFuelStats_t = mjFuelFlowSimulation_t.GetNestedType("FuelStats") ?? mjFuelFlowSimulation_t.GetNestedType("Stats"); + } + } + + if (mjFuelStats_t == null) + { + throw new NotImplementedException("mjFuelStats_t"); + } + // DeltaV field - note capital V in MJ 2.15+ + mjStageDv = mjFuelStats_t.GetField("DeltaV", BindingFlags.Instance | BindingFlags.Public); + if (mjStageDv == null) + { + // Try lowercase for older versions + mjStageDv = mjFuelStats_t.GetField("deltaV", BindingFlags.Instance | BindingFlags.Public); } - mjStageDv = mjFuelFlowSimulationStats_t.GetField("deltaV", BindingFlags.Instance | BindingFlags.Public) ?? mjFuelFlowSimulationStats_t.GetField("DeltaV", BindingFlags.Instance | BindingFlags.Public); if (mjStageDv == null) { throw new NotImplementedException("mjStageDv"); @@ -588,17 +607,17 @@ static JSIMechJeb() { throw new NotImplementedException("mjReentryResult_t"); } - mjReentryOutcome = mjReentryResult_t.GetField("outcome", BindingFlags.Instance | BindingFlags.Public); + mjReentryOutcome = mjReentryResult_t.GetField("Outcome", BindingFlags.Instance | BindingFlags.Public); // MJ 2.15+ PascalCase if (mjReentryOutcome == null) { throw new NotImplementedException("mjReentryOutcome"); } - mjReentryEndPosition = mjReentryResult_t.GetField("endPosition", BindingFlags.Instance | BindingFlags.Public); + mjReentryEndPosition = mjReentryResult_t.GetField("EndPosition", BindingFlags.Instance | BindingFlags.Public); // MJ 2.15+ PascalCase if (mjReentryEndPosition == null) { throw new NotImplementedException("mjReentryEndPosition"); } - mjReentryTime = mjReentryResult_t.GetField("endUT", BindingFlags.Instance | BindingFlags.Public); + mjReentryTime = mjReentryResult_t.GetField("EndUT", BindingFlags.Instance | BindingFlags.Public); // MJ 2.15+ PascalCase if (mjReentryTime == null) { throw new NotImplementedException("mjReentryTime"); @@ -695,7 +714,7 @@ static JSIMechJeb() // EditableDouble Type mjEditableDouble_t = loadedMechJebAssy.assembly.GetExportedTypes() .SingleOrDefault(t => t.FullName == "MuMech.EditableDouble"); - PropertyInfo mjEditableDoubleVal = mjEditableDouble_t.GetProperty("val", BindingFlags.Instance | BindingFlags.Public); + PropertyInfo mjEditableDoubleVal = mjEditableDouble_t.GetProperty("Val", BindingFlags.Instance | BindingFlags.Public); // MJ 2.15+ PascalCase MethodInfo mjGetEditableDouble = null, mjSetEditableDouble = null; if (mjEditableDoubleVal != null) { @@ -765,7 +784,13 @@ static JSIMechJeb() throw new NotImplementedException("mjRequestUpdate"); } requestUpdate = DynamicMethodDelegateFactory.Create(mjRequestUpdate); - mjVacStageStats = mjModuleStageStats_t.GetField("vacStats", BindingFlags.Instance | BindingFlags.Public); + // VacStats/AtmoStats - In MJ 2.15+, these are List with PascalCase names + mjVacStageStats = mjModuleStageStats_t.GetField("VacStats", BindingFlags.Instance | BindingFlags.Public); + if (mjVacStageStats == null) + { + // Try old naming convention + mjVacStageStats = mjModuleStageStats_t.GetField("vacStats", BindingFlags.Instance | BindingFlags.Public); + } if (mjVacStageStats == null) { throw new NotImplementedException("mjVacStageStats"); @@ -775,13 +800,25 @@ static JSIMechJeb() // its internal FuelFlowSimulation. This sim uses an array of // structs, which entails a couple of extra hoops to jump through // when reading via reflection. - mjAtmStageStats = mjModuleStageStats_t.GetField("atmoStats", BindingFlags.Instance | BindingFlags.Public); + // MJ 2.15+ uses List instead of FuelStats[] + mjAtmStageStats = mjModuleStageStats_t.GetField("AtmoStats", BindingFlags.Instance | BindingFlags.Public); + if (mjAtmStageStats == null) + { + // Try old naming convention + mjAtmStageStats = mjModuleStageStats_t.GetField("atmoStats", BindingFlags.Instance | BindingFlags.Public); + } if (mjAtmStageStats == null) { throw new NotImplementedException("mjAtmStageStats"); } - PropertyInfo mjStageStatsLength = mjVacStageStats.FieldType.GetProperty("Length"); + // For List, use Count property; for array, use Length property + PropertyInfo mjStageStatsLength = mjVacStageStats.FieldType.GetProperty("Count"); + if (mjStageStatsLength == null) + { + // Try Length for array type (older MJ2) + mjStageStatsLength = mjVacStageStats.FieldType.GetProperty("Length"); + } if (mjStageStatsLength == null) { throw new NotImplementedException("mjStageStatsLength"); @@ -792,7 +829,13 @@ static JSIMechJeb() throw new NotImplementedException("mjStageStatsGetLength"); } stageStatsGetLength = DynamicMethodDelegateFactory.CreateFuncInt(mjStageStatsGetLength); - MethodInfo mjStageStatsGetIndex = mjVacStageStats.FieldType.GetMethod("Get"); + // For List, use get_Item; for array, use Get + MethodInfo mjStageStatsGetIndex = mjVacStageStats.FieldType.GetMethod("get_Item"); + if (mjStageStatsGetIndex == null) + { + // Try Get for array type (older MJ2) + mjStageStatsGetIndex = mjVacStageStats.FieldType.GetMethod("Get"); + } if (mjStageStatsGetIndex == null) { throw new NotImplementedException("mjStageStatsGetIndex"); @@ -818,7 +861,7 @@ static JSIMechJeb() // Computer Module Type mjComputerModule_t = loadedMechJebAssy.assembly.GetExportedTypes() .SingleOrDefault(t => t.FullName == "MuMech.ComputerModule"); - PropertyInfo mjModuleEnabledProperty = mjComputerModule_t.GetProperty("enabled", BindingFlags.Instance | BindingFlags.Public); + PropertyInfo mjModuleEnabledProperty = mjComputerModule_t.GetProperty("Enabled", BindingFlags.Instance | BindingFlags.Public); // MJ 2.15+ PascalCase MethodInfo mjModuleEnabled = null; if (mjModuleEnabledProperty != null) { @@ -829,7 +872,7 @@ static JSIMechJeb() throw new NotImplementedException("mjModuleEnabled"); } moduleEnabled = DynamicMethodDelegateFactory.CreateFuncBool(mjModuleEnabled); - mjModuleUsers = mjComputerModule_t.GetField("users", BindingFlags.Instance | BindingFlags.Public); + mjModuleUsers = mjComputerModule_t.GetField("Users", BindingFlags.Instance | BindingFlags.Public); // MJ 2.15+ PascalCase if (mjModuleUsers == null) { throw new NotImplementedException("mjModuleUsers"); @@ -1029,7 +1072,6 @@ private void UpdateDeltaVStats(object activeJeb) try { object stagestats = GetComputerModule(activeJeb, "MechJebModuleStageStats"); - requestUpdate(stagestats, new object[] { this, false }); int atmStatsLength = 0, vacStatsLength = 0; @@ -1265,7 +1307,7 @@ public double GetLaunchAltitude() { double alt = 0.0; object activeJeb = GetMasterMechJeb(vessel); - object ascent = GetComputerModule(activeJeb, "MechJebModuleAscentAutopilot"); + object ascent = GetComputerModule(activeJeb, "MechJebModuleAscentSettings"); if (ascent != null) { object desiredAlt = launchOrbitAltitude.GetValue(ascent); @@ -1287,7 +1329,7 @@ public double GetLaunchAltitude() public void SetLaunchAltitude(double altitude) { object activeJeb = GetMasterMechJeb(vessel); - object ascent = GetComputerModule(activeJeb, "MechJebModuleAscentAutopilot"); + object ascent = GetComputerModule(activeJeb, "MechJebModuleAscentSettings"); if (ascent != null) { object desiredAlt = launchOrbitAltitude.GetValue(ascent); @@ -1302,7 +1344,7 @@ public double GetLaunchInclination() { double angle = 0.0; object activeJeb = GetMasterMechJeb(vessel); - object ascent = GetComputerModule(activeJeb, "MechJebModuleAscentGuidance"); + object ascent = GetComputerModule(activeJeb, "MechJebModuleAscentSettings"); if (ascent != null) { object inclination = launchOrbitInclination.GetValue(ascent); @@ -1314,7 +1356,7 @@ public double GetLaunchInclination() public void SetLaunchInclination(double inclination) { object activeJeb = GetMasterMechJeb(vessel); - object ascent = GetComputerModule(activeJeb, "MechJebModuleAscentGuidance"); + object ascent = GetComputerModule(activeJeb, "MechJebModuleAscentSettings"); if (ascent != null) { object incline = launchOrbitInclination.GetValue(ascent); @@ -1656,42 +1698,61 @@ public bool ButtonNodeExecuteState() public void ButtonAscentGuidance(bool state) { object activeJeb = GetMasterMechJeb(vessel); - object ap = GetComputerModule(activeJeb, "MechJebModuleAscentAutopilot"); - if (ap != null) + // Try MechJebModuleAscentBaseAutopilot first (newer MJ) + object ap = GetComputerModule(activeJeb, "MechJebModuleAscentBaseAutopilot"); + if (ap == null) { - // MOARdV TODO: When MJ 2.5.4 (or higher) is out, remove the - // null check here and eliminate the else path, since getAAPEngaged - // will be the only valid path. - if (setAscentAutopilotEngaged != null) + // Fall back to getting autopilot from AscentSettings + object ascentSettings = GetComputerModule(activeJeb, "MechJebModuleAscentSettings"); + if (ascentSettings != null) { - setAscentAutopilotEngaged(ap, new object[] { state }); - } - else - { - object users = mjModuleUsers.GetValue(ap); - if (users == null) + PropertyInfo ascentAutopilotProp = ascentSettings.GetType().GetProperty("AscentAutopilot"); + if (ascentAutopilotProp != null) { - throw new NotImplementedException("mjModuleUsers(ap) was null"); + ap = ascentAutopilotProp.GetValue(ascentSettings, null); } + } + } - object agPilot = GetComputerModule(activeJeb, "MechJebModuleAscentGuidance"); - if (agPilot == null) - { - JUtil.LogErrorMessage(this, "Unable to fetch MechJebModuleAscentGuidance"); - return; - } + if (ap == null) + { + return; + } - if (ModuleEnabled(ap)) - { - removeUser(users, new object[] { agPilot }); - } - else - { - addUser(users, new object[] { agPilot }); - } + // Get the Users collection + object users = mjModuleUsers.GetValue(ap); + if (users == null) + { + // Try getting Users via property (newer MJ uses PascalCase) + PropertyInfo usersProp = ap.GetType().GetProperty("Users"); + if (usersProp != null) + { + users = usersProp.GetValue(ap, null); } } + if (users == null) + { + JUtil.LogErrorMessage(this, "mjModuleUsers(ap) was null"); + return; + } + + // Get MechJebModuleAscentMenu (newer MJ) instead of MechJebModuleAscentGuidance + object agPilot = GetComputerModule(activeJeb, "MechJebModuleAscentMenu"); + if (agPilot == null) + { + JUtil.LogErrorMessage(this, "Unable to fetch MechJebModuleAscentMenu"); + return; + } + + if (ModuleEnabled(ap)) + { + removeUser(users, new object[] { agPilot }); + } + else + { + addUser(users, new object[] { agPilot }); + } } /// @@ -1703,19 +1764,27 @@ public bool ButtonAscentGuidanceState() object activeJeb = GetMasterMechJeb(vessel); if (activeJeb != null) { - object ap = GetComputerModule(activeJeb, "MechJebModuleAscentAutopilot"); - - // MOARdV TODO: When MJ 2.5.4 (or higher) is out, remove the - // null check here and eliminate the else path, since getAAPEngaged - // will be the only valid path. - if (getAscentAutopilotEngaged != null) + // Try MechJebModuleAscentBaseAutopilot first (newer MJ) + object ap = GetComputerModule(activeJeb, "MechJebModuleAscentBaseAutopilot"); + if (ap == null) { - return getAscentAutopilotEngaged(ap); + // Fall back to getting autopilot from AscentSettings + object ascentSettings = GetComputerModule(activeJeb, "MechJebModuleAscentSettings"); + if (ascentSettings != null) + { + PropertyInfo ascentAutopilotProp = ascentSettings.GetType().GetProperty("AscentAutopilot"); + if (ascentAutopilotProp != null) + { + ap = ascentAutopilotProp.GetValue(ascentSettings, null); + } + } } - else + + if (getAscentAutopilotEngaged != null && ap != null) { - return ModuleEnabled(ap); + return getAscentAutopilotEngaged(ap); } + return ModuleEnabled(ap); } else { diff --git a/RasterPropMonitor/Handlers/JSISASMenu.cs b/RasterPropMonitor/Handlers/JSISASMenu.cs index f6422321..28673589 100644 --- a/RasterPropMonitor/Handlers/JSISASMenu.cs +++ b/RasterPropMonitor/Handlers/JSISASMenu.cs @@ -70,7 +70,7 @@ private static VesselAutopilot.AutopilotMode ConvertMode(VesselAutopilot.Autopil } } - class JSISASMenu : InternalModule + public class JSISASMenu : InternalModule { [KSPField] public string pageTitle = string.Empty; diff --git a/RasterPropMonitor/Handlers/JSIScienceDisplay.cs b/RasterPropMonitor/Handlers/JSIScienceDisplay.cs index 684e75c5..2099d57b 100644 --- a/RasterPropMonitor/Handlers/JSIScienceDisplay.cs +++ b/RasterPropMonitor/Handlers/JSIScienceDisplay.cs @@ -5,9 +5,9 @@ using System.Text; using UnityEngine; -namespace JSI.Handlers +namespace JSI { - class ExperimentDetailsMenu : TextMenu + public class ExperimentDetailsMenu : TextMenu { public ExperimentDetailsMenu(ModuleScienceExperiment experimentModule) { @@ -103,7 +103,7 @@ static void AddMessageChunk(string message, StringBuilder stringBuilder, ref int } } - class JSIScienceDisplay : InternalModule + public class JSIScienceDisplay : InternalModule { [KSPField] public string pageTitle; diff --git a/RasterPropMonitor/Handlers/MechJebProxy.cs b/RasterPropMonitor/Handlers/MechJebProxy.cs new file mode 100644 index 00000000..6e9649aa --- /dev/null +++ b/RasterPropMonitor/Handlers/MechJebProxy.cs @@ -0,0 +1,5623 @@ +/***************************************************************************** + * RasterPropMonitor + * ================= + * Plugin for Kerbal Space Program + * + * by Mihara (Eugene Medvedev), MOARdV, and other contributors + * + * RasterPropMonitor is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, revision + * date 29 June 2007, or (at your option) any later version. + * + * RasterPropMonitor is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * You should have received a copy of the GNU General Public License + * along with RasterPropMonitor. If not, see . + ****************************************************************************/ + +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Linq; + +namespace JSI +{ + /// + /// MechJebProxy provides a comprehensive reflection-based interface to MechJeb 2.15+ + /// This is a complete rewrite for full feature parity with MechJeb's IMGUI interface. + /// + public static class MechJebProxy + { + #region Initialization State + private static bool initialized = false; + private static bool mjAvailable = false; + private static string initError = null; + + public static bool IsAvailable { get { return mjAvailable; } } + public static string InitializationError { get { return initError; } } + #endregion + + #region Assembly References + private static Assembly mechJebAssembly; + private static Assembly mechJebLibAssembly; + #endregion + + #region Type Cache + // Core Types + private static Type t_MechJebCore; + private static Type t_ComputerModule; + private static Type t_DisplayModule; + private static Type t_VesselState; + private static Type t_VesselExtensions; + + // Editable Types + private static Type t_EditableDouble; + private static Type t_EditableDoubleMult; + private static Type t_EditableInt; + private static Type t_EditableAngle; + + // Module Types + private static Type t_SmartASS; + private static Type t_AttitudeController; + private static Type t_ThrustController; + private static Type t_StagingController; + private static Type t_NodeExecutor; + private static Type t_TargetController; + private static Type t_AscentSettings; + private static Type t_AscentBaseAutopilot; + private static Type t_AscentClassicAutopilot; + private static Type t_AscentPSGAutopilot; + private static Type t_AscentMenu; + private static Type t_LandingAutopilot; + private static Type t_LandingPredictions; + private static Type t_LandingGuidance; + private static Type t_ManeuverPlanner; + private static Type t_RendezvousAutopilot; + private static Type t_RendezvousGuidance; + private static Type t_DockingAutopilot; + private static Type t_DockingGuidance; + private static Type t_Translatron; + private static Type t_RoverController; + private static Type t_AirplaneAutopilot; + private static Type t_SpaceplaneAutopilot; + private static Type t_StageStats; + private static Type t_RCSBalancer; + private static Type t_WarpController; + private static Type t_Settings; + + // Maneuver/Operations Types + private static Type t_OrbitalManeuverCalculator; + private static Type t_Operation; + private static Type t_OperationAdvancedTransfer; + private static Type t_OperationCourseCorrection; + private static Type t_ManeuverParameters; + private static Type t_TransferCalculator; + + // Data Types + private static Type t_AbsoluteVector; + private static Type t_ReentryResult; + private static Type t_FuelStats; + private static Type t_UserPool; + + // Enum Types + private static Type t_SmartASSTarget; + private static Type t_SmartASSMode; + private static Type t_AttitudeReference; + private static Type t_TranslatronMode; + #endregion + + #region Method/Field/Property Cache + // VesselExtensions + private static MethodInfo m_GetMasterMechJeb; + private static MethodInfo m_PlaceManeuverNode; + + // MechJebCore + private static MethodInfo m_GetComputerModule; + private static FieldInfo f_Core_Target; + private static FieldInfo f_Core_Node; + private static FieldInfo f_Core_Attitude; + private static FieldInfo f_Core_Thrust; + private static FieldInfo f_Core_Staging; + private static FieldInfo f_Core_VesselState; + + // ComputerModule + private static PropertyInfo p_Module_Enabled; + private static FieldInfo f_Module_Users; + + // UserPool + private static MethodInfo m_UserPool_Add; + private static MethodInfo m_UserPool_Remove; + private static MethodInfo m_UserPool_Contains; + + // EditableDouble + private static PropertyInfo p_EditableDouble_Val; + + // EditableDoubleMult + private static PropertyInfo p_EditableDoubleMult_Val; + + // EditableInt + private static PropertyInfo p_EditableInt_Val; + + // EditableAngle - converts to double + private static MethodInfo m_EditableAngle_ToDouble; + + // AbsoluteVector + private static FieldInfo f_AbsoluteVector_Latitude; + private static FieldInfo f_AbsoluteVector_Longitude; + private static MethodInfo m_AbsoluteVector_ToDouble; + + // VesselState + private static MethodInfo m_VesselState_TerminalVelocity; + + // SmartASS + private static FieldInfo f_SmartASS_Target; + private static FieldInfo f_SmartASS_Mode; + private static FieldInfo f_SmartASS_ForceRol; + private static FieldInfo f_SmartASS_Rol; + private static FieldInfo f_SmartASS_SrfHdg; + private static FieldInfo f_SmartASS_SrfPit; + private static FieldInfo f_SmartASS_SrfRol; + private static FieldInfo f_SmartASS_SrfVelYaw; + private static FieldInfo f_SmartASS_SrfVelPit; + private static FieldInfo f_SmartASS_SrfVelRol; + private static FieldInfo f_SmartASS_AdvReference; + private static FieldInfo f_SmartASS_AdvDirection; + private static MethodInfo m_SmartASS_Engage; + private static FieldInfo f_SmartASS_ModeTexts; + private static FieldInfo f_SmartASS_TargetTexts; + + // NodeExecutor + private static MethodInfo m_NodeExecutor_ExecuteOneNode; + private static MethodInfo m_NodeExecutor_ExecuteAllNodes; + private static MethodInfo m_NodeExecutor_Abort; + private static FieldInfo f_NodeExecutor_Autowarp; + private static FieldInfo f_NodeExecutor_LeadTime; + + // TargetController + private static PropertyInfo p_Target_PositionTargetExists; + private static PropertyInfo p_Target_NormalTargetExists; + private static PropertyInfo p_Target_TargetOrbit; + private static FieldInfo f_Target_TargetLatitude; + private static FieldInfo f_Target_TargetLongitude; + private static MethodInfo m_Target_SetPositionTarget; + private static MethodInfo m_Target_PickPositionTargetOnMap; + + // AscentSettings + private static FieldInfo f_Ascent_DesiredOrbitAltitude; + private static FieldInfo f_Ascent_DesiredApoapsis; + private static FieldInfo f_Ascent_DesiredInclination; + private static FieldInfo f_Ascent_DesiredLAN; + private static FieldInfo f_Ascent_LaunchPhaseAngle; + private static FieldInfo f_Ascent_LaunchLANDifference; + private static PropertyInfo p_Ascent_AscentAutopilot; + private static FieldInfo f_Ascent_WarpCountDown; + private static FieldInfo f_Ascent_SkipCircularization; + private static FieldInfo f_Ascent_ForceRoll; + private static FieldInfo f_Ascent_VerticalRoll; + private static FieldInfo f_Ascent_TurnRoll; + private static FieldInfo f_Ascent_RollAltitude; + private static FieldInfo f_Ascent_LimitAoA; + private static FieldInfo f_Ascent_MaxAoA; + private static FieldInfo f_Ascent_AOALimitFadeoutPressure; + private static FieldInfo f_Ascent_CorrectiveSteering; + private static FieldInfo f_Ascent_CorrectiveSteeringGain; + private static FieldInfo f_Ascent_TurnStartAltitude; + private static FieldInfo f_Ascent_TurnStartVelocity; + private static FieldInfo f_Ascent_TurnEndAltitude; + private static FieldInfo f_Ascent_TurnEndAngle; + private static FieldInfo f_Ascent_TurnShapeExponent; + private static FieldInfo f_Ascent_AutoPath; + private static FieldInfo f_Ascent_AutoTurnPerc; + private static FieldInfo f_Ascent_AutoTurnSpdFactor; + private static PropertyInfo p_Ascent_Autostage; + private static FieldInfo f_Ascent_Autostage; + + // AscentBaseAutopilot + private static PropertyInfo p_AscentAP_Status; + private static MethodInfo m_AscentAP_StartCountdown; + + // LandingAutopilot + private static MethodInfo m_Landing_LandAtPositionTarget; + private static MethodInfo m_Landing_LandUntargeted; + private static MethodInfo m_Landing_StopLanding; + private static FieldInfo f_Landing_TouchdownSpeed; + private static FieldInfo f_Landing_DeployGears; + private static FieldInfo f_Landing_DeployChutes; + private static FieldInfo f_Landing_LimitGearsStage; + private static FieldInfo f_Landing_LimitChutesStage; + private static FieldInfo f_Landing_UseRCS; + private static PropertyInfo p_Landing_Status; + + // LandingPredictions + private static MethodInfo m_Predictions_GetResult; + private static PropertyInfo p_Predictions_ShowTrajectory; + + // ReentrySimulation.Result + private static FieldInfo f_Result_Outcome; + private static FieldInfo f_Result_EndPosition; + private static FieldInfo f_Result_EndUT; + private static FieldInfo f_Result_MaxDragGees; + + // ThrustController + private static FieldInfo f_Thrust_LimitToPreventOverheats; + private static FieldInfo f_Thrust_LimitToTerminalVelocity; + private static FieldInfo f_Thrust_LimitToMaxDynamicPressure; + private static FieldInfo f_Thrust_MaxDynamicPressure; + private static FieldInfo f_Thrust_LimitAcceleration; + private static FieldInfo f_Thrust_MaxAcceleration; + private static FieldInfo f_Thrust_LimitThrottle; + private static FieldInfo f_Thrust_MaxThrottle; + private static FieldInfo f_Thrust_LimiterMinThrottle; + private static FieldInfo f_Thrust_MinThrottle; + private static FieldInfo f_Thrust_LimitToPreventFlameout; + private static FieldInfo f_Thrust_FlameoutSafetyPct; + private static FieldInfo f_Thrust_SmoothThrottle; + private static FieldInfo f_Thrust_ManageIntakes; + private static FieldInfo f_Thrust_DifferentialThrottle; + private static FieldInfo f_Thrust_DifferentialThrottleSuccess; + + // StagingController + private static FieldInfo f_Staging_Autostage; + private static FieldInfo f_Staging_AutostageLimit; + private static FieldInfo f_Staging_AutostagePreDelay; + private static FieldInfo f_Staging_AutostagePostDelay; + private static FieldInfo f_Staging_ClampAutoStageThrustPct; + private static FieldInfo f_Staging_FairingMaxAerothermalFlux; + private static FieldInfo f_Staging_FairingMaxDynamicPressure; + private static FieldInfo f_Staging_FairingMinAltitude; + private static FieldInfo f_Staging_HotStagingLeadTime; + private static FieldInfo f_Staging_DropSolids; + private static FieldInfo f_Staging_DropSolidsLeadTime; + private static MethodInfo m_Staging_AutostageOnce; + + // StageStats + private static MethodInfo m_StageStats_RequestUpdate; + private static FieldInfo f_StageStats_VacStats; + private static FieldInfo f_StageStats_AtmoStats; + + // FuelStats + private static FieldInfo f_FuelStats_DeltaV; + private static FieldInfo f_FuelStats_DeltaTime; + private static FieldInfo f_FuelStats_StartMass; + private static FieldInfo f_FuelStats_EndMass; + private static FieldInfo f_FuelStats_StartThrust; + private static FieldInfo f_FuelStats_MaxAccel; + private static FieldInfo f_FuelStats_Isp; + + // ManeuverParameters + private static FieldInfo f_ManeuverParameters_dV; + private static FieldInfo f_ManeuverParameters_UT; + + // Translatron + private static PropertyInfo p_Translatron_TransSpd; + private static FieldInfo f_Translatron_TransSpdAct; + private static PropertyInfo p_Translatron_TransKillH; + private static MethodInfo m_Translatron_SetMode; + private static MethodInfo m_Translatron_PanicSwitch; + + // RendezvousAutopilot + private static FieldInfo f_Rendezvous_DesiredDistance; + private static FieldInfo f_Rendezvous_MaxPhasingOrbits; + private static FieldInfo f_Rendezvous_MaxClosingSpeed; + private static PropertyInfo p_Rendezvous_Status; + + // DockingAutopilot + private static FieldInfo f_Docking_SpeedLimit; + private static FieldInfo f_Docking_ForceRoll; + private static FieldInfo f_Docking_Roll; + private static FieldInfo f_Docking_OverrideSafeDistance; + private static FieldInfo f_Docking_OverridenSafeDistance; + private static FieldInfo f_Docking_OverrideTargetSize; + private static FieldInfo f_Docking_OverridenTargetSize; + private static FieldInfo f_Docking_DrawBoundingBox; + private static PropertyInfo p_Docking_Status; + private static PropertyInfo p_Docking_SASCalc_X; + private static PropertyInfo p_Docking_SASCalc_Y; + private static PropertyInfo p_Docking_SASCalc_Z; + + // RoverController + private static FieldInfo f_Rover_ControlHeading; + private static FieldInfo f_Rover_ControlSpeed; + private static FieldInfo f_Rover_Heading; + private static FieldInfo f_Rover_Speed; + private static FieldInfo f_Rover_HeadingError; + private static FieldInfo f_Rover_SpeedError; + private static FieldInfo f_Rover_StabilityControl; + private static FieldInfo f_Rover_BrakeOnEject; + private static FieldInfo f_Rover_BrakeOnEnergyDepletion; + private static FieldInfo f_Rover_WarpToDaylight; + private static MethodInfo m_Rover_DriveToTarget; + private static MethodInfo m_Rover_Stop; + private static MethodInfo m_Rover_AddWaypoint; + private static MethodInfo m_Rover_ClearWaypoints; + + // AirplaneAutopilot + private static FieldInfo f_Airplane_AltitudeHold; + private static FieldInfo f_Airplane_AltitudeTarget; + private static FieldInfo f_Airplane_VertSpeedHold; + private static FieldInfo f_Airplane_VertSpeedTarget; + private static FieldInfo f_Airplane_HeadingHold; + private static FieldInfo f_Airplane_HeadingTarget; + private static FieldInfo f_Airplane_RollHold; + private static FieldInfo f_Airplane_RollTarget; + private static FieldInfo f_Airplane_RollMax; + private static FieldInfo f_Airplane_SpeedHold; + private static FieldInfo f_Airplane_SpeedTarget; + private static FieldInfo f_Airplane_AccKp; + private static FieldInfo f_Airplane_AccKi; + private static FieldInfo f_Airplane_AccKd; + private static FieldInfo f_Airplane_PitKp; + private static FieldInfo f_Airplane_PitKi; + private static FieldInfo f_Airplane_PitKd; + private static FieldInfo f_Airplane_RolKp; + private static FieldInfo f_Airplane_RolKi; + private static FieldInfo f_Airplane_RolKd; + private static FieldInfo f_Airplane_YawKp; + private static FieldInfo f_Airplane_YawKi; + private static FieldInfo f_Airplane_YawKd; + + // SpaceplaneAutopilot + private static FieldInfo f_Spaceplane_Glideslope; + private static FieldInfo f_Spaceplane_ApproachSpeed; + private static FieldInfo f_Spaceplane_TouchdownSpeed; + private static FieldInfo f_Spaceplane_Mode; + private static MethodInfo m_Spaceplane_Autoland; + private static MethodInfo m_Spaceplane_HoldHeadingAndAltitude; + private static MethodInfo m_Spaceplane_AutopilotOff; + + // WarpController + private static MethodInfo m_Warp_WarpToUT; + + // OrbitalManeuverCalculator (static methods) + private static MethodInfo m_Calc_DeltaVToCircularize; + private static MethodInfo m_Calc_DeltaVToChangeApoapsis; + private static MethodInfo m_Calc_DeltaVToChangePeriapsis; + private static MethodInfo m_Calc_DeltaVToMatchVelocities; + private static MethodInfo m_Calc_DeltaVAndTimeForHohmannTransfer; + private static MethodInfo m_Calc_DeltaVForSemiMajorAxis; + private static MethodInfo m_Calc_DeltaVToChangeInclination; + private static MethodInfo m_Calc_DeltaVToMatchPlanesAscending; + private static MethodInfo m_Calc_DeltaVToMatchPlanesDescending; + private static MethodInfo m_Calc_DeltaVToShiftLAN; + private static MethodInfo m_Calc_DeltaVToEllipticize; + + // Operation (maneuver planner) + private static MethodInfo m_Operation_MakeNodes; + private static MethodInfo m_Operation_GetErrorMessage; + private static MethodInfo m_Operation_GetName; + + // ManeuverPlanner static fields (wrapping MechJeb's actual instances) + private static FieldInfo f_ManeuverPlanner_operation; // static Operation[] _operation + private static FieldInfo f_ManeuverPlanner_operationId; // [Persistent] int _operationId + + // Cached static Operation array (from MechJeb's ManeuverPlanner) + private static object[] cachedOperations; + private static string[] cachedOperationNames; + + // TimeSelector for operation timing options + private static Type t_TimeSelector; + private static MethodInfo m_TimeSelector_ComputeManeuverTime; + private static FieldInfo f_TimeSelector_CurrentTimeRef; + private static FieldInfo f_TimeSelector_CircularizeAltitude; + private static FieldInfo f_TimeSelector_LeadTime; + private static PropertyInfo p_TimeSelector_TimeReference; + private static Type t_TimeReference; + + // OperationAdvancedTransfer internals + private static FieldInfo f_AdvancedTransfer_SelectionMode; + private static FieldInfo f_AdvancedTransfer_Worker; + private static FieldInfo f_AdvancedTransfer_Plot; + private static FieldInfo f_AdvancedTransfer_IncludeCaptureBurn; + private static FieldInfo f_AdvancedTransfer_PeriapsisHeight; + private static FieldInfo f_AdvancedTransfer_LastTargetCelestial; + private static MethodInfo m_AdvancedTransfer_ComputeTimes; + private static MethodInfo m_AdvancedTransfer_ComputeStuff; + + // OperationGeneric (Hohmann/bi-impulsive transfer) internals + private static Type t_OperationGeneric; + private static FieldInfo f_Generic_Capture; + private static FieldInfo f_Generic_PlanCapture; + private static FieldInfo f_Generic_Rendezvous; + private static FieldInfo f_Generic_Coplanar; + private static FieldInfo f_Generic_LagTime; + + // OperationCourseCorrection internals + private static FieldInfo f_CourseCorrection_TargetPe; + private static PropertyInfo p_CourseCorrection_TargetPe; + + // PlotArea (porkchop plot) + private static Type t_PlotArea; + private static PropertyInfo p_PlotArea_SelectedPoint; + + // TransferCalculator internals + private static FieldInfo f_TransferCalculator_Computed; + private static FieldInfo f_TransferCalculator_BestDate; + private static FieldInfo f_TransferCalculator_BestDuration; + private static PropertyInfo p_TransferCalculator_Finished; + private static PropertyInfo p_TransferCalculator_Progress; + private static PropertyInfo p_TransferCalculator_ArrivalDate; + private static MethodInfo m_TransferCalculator_DateFromIndex; + private static MethodInfo m_TransferCalculator_DurationFromIndex; + #endregion + + #region Public Enum Copies + /// SmartASS target modes (copied from MechJeb for reflection-free access) + public enum Target + { + OFF, + KILLROT, + NODE, + SURFACE, + PROGRADE, + RETROGRADE, + NORMAL_PLUS, + NORMAL_MINUS, + RADIAL_PLUS, + RADIAL_MINUS, + RELATIVE_PLUS, + RELATIVE_MINUS, + TARGET_PLUS, + TARGET_MINUS, + PARALLEL_PLUS, + PARALLEL_MINUS, + ADVANCED, + AUTO, + SURFACE_PROGRADE, + SURFACE_RETROGRADE, + HORIZONTAL_PLUS, + HORIZONTAL_MINUS, + VERTICAL_PLUS, + } + + /// SmartASS mode categories + public enum Mode + { + ORBITAL = 0, + SURFACE = 1, + TARGET = 2, + ADVANCED = 3, + AUTO = 4, + } + + /// Translatron modes + public enum TranslatronMode + { + OFF, + KEEP_OBT, + KEEP_SURF, + KEEP_VERT, + KEEP_REL, + DIRECT, + } + + /// Mapping from Target to Mode + public static readonly Mode[] Target2Mode = new Mode[] + { + Mode.ORBITAL, // OFF + Mode.ORBITAL, // KILLROT + Mode.ORBITAL, // NODE + Mode.SURFACE, // SURFACE + Mode.ORBITAL, // PROGRADE + Mode.ORBITAL, // RETROGRADE + Mode.ORBITAL, // NORMAL_PLUS + Mode.ORBITAL, // NORMAL_MINUS + Mode.ORBITAL, // RADIAL_PLUS + Mode.ORBITAL, // RADIAL_MINUS + Mode.TARGET, // RELATIVE_PLUS + Mode.TARGET, // RELATIVE_MINUS + Mode.TARGET, // TARGET_PLUS + Mode.TARGET, // TARGET_MINUS + Mode.TARGET, // PARALLEL_PLUS + Mode.TARGET, // PARALLEL_MINUS + Mode.ADVANCED,// ADVANCED + Mode.AUTO, // AUTO + Mode.SURFACE, // SURFACE_PROGRADE + Mode.SURFACE, // SURFACE_RETROGRADE + Mode.SURFACE, // HORIZONTAL_PLUS + Mode.SURFACE, // HORIZONTAL_MINUS + Mode.SURFACE, // VERTICAL_PLUS + }; + + /// Display texts for modes + public static readonly string[] ModeTexts = new string[] + { + "OBT", + "SURF", + "TGT", + "ADV", + "AUTO", + }; + + /// Display texts for targets + public static readonly string[] TargetTexts = new string[] + { + "OFF", + "KILL\nROT", + "NODE", + "SURF", + "PRO\nGRAD", + "RETR\nGRAD", + "NML\n+", + "NML\n-", + "RAD\n+", + "RAD\n-", + "RVEL\n+", + "RVEL\n-", + "TGT\n+", + "TGT\n-", + "PAR\n+", + "PAR\n-", + "ADV", + "AUTO", + "SVEL\n+", + "SVEL\n-", + "HVEL\n+", + "HVEL\n-", + "UP", + }; + + /// Landing prediction outcomes + public enum LandingOutcome + { + LANDED, + AEROBRAKED, + TIMED_OUT, + NO_REENTRY, + ERROR + } + #endregion + + #region Initialization + /// + /// Initialize all reflection bindings. Called once at startup. + /// + public static void Initialize() + { + if (initialized) return; + initialized = true; + + try + { + // Find MechJeb assembly + foreach (AssemblyLoader.LoadedAssembly la in AssemblyLoader.loadedAssemblies) + { + if (la.assembly.GetName().Name == "MechJeb2") + { + mechJebAssembly = la.assembly; + } + else if (la.assembly.GetName().Name == "MechJebLib") + { + mechJebLibAssembly = la.assembly; + } + } + + if (mechJebAssembly == null) + { + initError = "MechJeb2 assembly not found"; + return; + } + + // Initialize all types and members + InitializeCoreTypes(); + InitializeEditableTypes(); + InitializeModuleTypes(); + InitializeDataTypes(); + InitializeEnumTypes(); + InitializeMethods(); + + mjAvailable = true; + JUtil.LogMessage(null, "MechJebProxy: Successfully initialized with full feature parity"); + } + catch (Exception ex) + { + initError = ex.Message; + JUtil.LogErrorMessage(null, "MechJebProxy initialization failed: {0}\n{1}", ex.Message, ex.StackTrace); + } + } + + private static void InitializeCoreTypes() + { + t_MechJebCore = mechJebAssembly.GetType("MuMech.MechJebCore"); + if (t_MechJebCore == null) throw new Exception("MechJebCore not found"); + + t_ComputerModule = mechJebAssembly.GetType("MuMech.ComputerModule"); + if (t_ComputerModule == null) throw new Exception("ComputerModule not found"); + + t_DisplayModule = mechJebAssembly.GetType("MuMech.DisplayModule"); + if (t_DisplayModule == null) throw new Exception("DisplayModule not found"); + + t_VesselState = mechJebAssembly.GetType("MuMech.VesselState"); + if (t_VesselState == null) throw new Exception("VesselState not found"); + + t_VesselExtensions = mechJebAssembly.GetType("MuMech.VesselExtensions"); + if (t_VesselExtensions == null) throw new Exception("VesselExtensions not found"); + } + + private static void InitializeEditableTypes() + { + t_EditableDouble = mechJebAssembly.GetType("MuMech.EditableDouble"); + t_EditableDoubleMult = mechJebAssembly.GetType("MuMech.EditableDoubleMult"); + t_EditableInt = mechJebAssembly.GetType("MuMech.EditableInt"); + t_EditableAngle = mechJebAssembly.GetType("MuMech.EditableAngle"); + + if (t_EditableDouble != null) + { + p_EditableDouble_Val = t_EditableDouble.GetProperty("Val", BindingFlags.Public | BindingFlags.Instance); + } + if (t_EditableDoubleMult != null) + { + p_EditableDoubleMult_Val = t_EditableDoubleMult.GetProperty("Val", BindingFlags.Public | BindingFlags.Instance); + } + if (t_EditableInt != null) + { + p_EditableInt_Val = t_EditableInt.GetProperty("Val", BindingFlags.Public | BindingFlags.Instance); + } + if (t_EditableAngle != null) + { + // EditableAngle has implicit conversion to double + m_EditableAngle_ToDouble = t_EditableAngle.GetMethods(BindingFlags.Public | BindingFlags.Static) + .FirstOrDefault(m => m.Name == "op_Implicit" && m.ReturnType == typeof(double)); + } + } + + private static void InitializeModuleTypes() + { + // Get all module types + t_SmartASS = mechJebAssembly.GetType("MuMech.MechJebModuleSmartASS"); + t_AttitudeController = mechJebAssembly.GetType("MuMech.MechJebModuleAttitudeController"); + t_ThrustController = mechJebAssembly.GetType("MuMech.MechJebModuleThrustController"); + t_StagingController = mechJebAssembly.GetType("MuMech.MechJebModuleStagingController"); + t_NodeExecutor = mechJebAssembly.GetType("MuMech.MechJebModuleNodeExecutor"); + t_TargetController = mechJebAssembly.GetType("MuMech.MechJebModuleTargetController"); + t_AscentSettings = mechJebAssembly.GetType("MuMech.MechJebModuleAscentSettings"); + t_AscentBaseAutopilot = mechJebAssembly.GetType("MuMech.MechJebModuleAscentBaseAutopilot"); + t_AscentClassicAutopilot = mechJebAssembly.GetType("MuMech.MechJebModuleAscentClassicAutopilot"); + t_AscentPSGAutopilot = mechJebAssembly.GetType("MuMech.MechJebModuleAscentPSGAutopilot"); + t_AscentMenu = mechJebAssembly.GetType("MuMech.MechJebModuleAscentMenu"); + t_LandingAutopilot = mechJebAssembly.GetType("MuMech.MechJebModuleLandingAutopilot"); + t_LandingPredictions = mechJebAssembly.GetType("MuMech.MechJebModuleLandingPredictions"); + t_LandingGuidance = mechJebAssembly.GetType("MuMech.MechJebModuleLandingGuidance"); + t_ManeuverPlanner = mechJebAssembly.GetType("MuMech.MechJebModuleManeuverPlanner"); + t_RendezvousAutopilot = mechJebAssembly.GetType("MuMech.MechJebModuleRendezvousAutopilot"); + t_RendezvousGuidance = mechJebAssembly.GetType("MuMech.MechJebModuleRendezvousGuidance"); + t_DockingAutopilot = mechJebAssembly.GetType("MuMech.MechJebModuleDockingAutopilot"); + t_DockingGuidance = mechJebAssembly.GetType("MuMech.MechJebModuleDockingGuidance"); + t_Translatron = mechJebAssembly.GetType("MuMech.MechJebModuleTranslatron"); + t_RoverController = mechJebAssembly.GetType("MuMech.MechJebModuleRoverController"); + t_AirplaneAutopilot = mechJebAssembly.GetType("MuMech.MechJebModuleAirplaneAutopilot"); + t_SpaceplaneAutopilot = mechJebAssembly.GetType("MuMech.MechJebModuleSpaceplaneAutopilot"); + t_StageStats = mechJebAssembly.GetType("MuMech.MechJebModuleStageStats"); + t_RCSBalancer = mechJebAssembly.GetType("MuMech.MechJebModuleRCSBalancer"); + t_WarpController = mechJebAssembly.GetType("MuMech.MechJebModuleWarpController"); + t_Settings = mechJebAssembly.GetType("MuMech.MechJebModuleSettings"); + + // Maneuver / Operations + t_OrbitalManeuverCalculator = mechJebAssembly.GetType("MuMech.OrbitalManeuverCalculator"); + t_Operation = mechJebAssembly.GetType("MuMech.Operation"); + t_OperationAdvancedTransfer = mechJebAssembly.GetType("MuMech.OperationAdvancedTransfer"); + t_OperationCourseCorrection = mechJebAssembly.GetType("MuMech.OperationCourseCorrection"); + t_ManeuverParameters = mechJebAssembly.GetType("MuMech.ManeuverParameters"); + t_TransferCalculator = mechJebAssembly.GetType("MuMech.TransferCalculator"); + } + + private static void InitializeDataTypes() + { + t_AbsoluteVector = mechJebAssembly.GetType("MuMech.AbsoluteVector"); + t_ReentryResult = mechJebAssembly.GetType("MuMech.ReentrySimulation+Result"); + t_UserPool = mechJebAssembly.GetType("MuMech.UserPool"); + + // FuelStats is in MechJebLib + if (mechJebLibAssembly != null) + { + t_FuelStats = mechJebLibAssembly.GetType("MechJebLib.FuelFlowSimulation.FuelStats"); + } + + // Initialize AbsoluteVector fields + if (t_AbsoluteVector != null) + { + f_AbsoluteVector_Latitude = t_AbsoluteVector.GetField("Latitude", BindingFlags.Public | BindingFlags.Instance); + f_AbsoluteVector_Longitude = t_AbsoluteVector.GetField("Longitude", BindingFlags.Public | BindingFlags.Instance); + m_AbsoluteVector_ToDouble = t_AbsoluteVector.GetMethods(BindingFlags.Public | BindingFlags.Static) + .FirstOrDefault(m => m.Name == "op_Implicit" && m.ReturnType == typeof(double)); + } + + // Initialize ReentryResult fields + if (t_ReentryResult != null) + { + f_Result_Outcome = t_ReentryResult.GetField("Outcome", BindingFlags.Public | BindingFlags.Instance); + f_Result_EndPosition = t_ReentryResult.GetField("EndPosition", BindingFlags.Public | BindingFlags.Instance); + f_Result_EndUT = t_ReentryResult.GetField("EndUT", BindingFlags.Public | BindingFlags.Instance); + f_Result_MaxDragGees = t_ReentryResult.GetField("MaxDragGees", BindingFlags.Public | BindingFlags.Instance); + } + + // Initialize FuelStats fields + if (t_FuelStats != null) + { + f_FuelStats_DeltaV = t_FuelStats.GetField("DeltaV", BindingFlags.Public | BindingFlags.Instance); + f_FuelStats_DeltaTime = t_FuelStats.GetField("DeltaTime", BindingFlags.Public | BindingFlags.Instance); + f_FuelStats_StartMass = t_FuelStats.GetField("StartMass", BindingFlags.Public | BindingFlags.Instance); + f_FuelStats_EndMass = t_FuelStats.GetField("EndMass", BindingFlags.Public | BindingFlags.Instance); + f_FuelStats_StartThrust = t_FuelStats.GetField("StartThrust", BindingFlags.Public | BindingFlags.Instance); + f_FuelStats_MaxAccel = t_FuelStats.GetField("MaxAccel", BindingFlags.Public | BindingFlags.Instance); + f_FuelStats_Isp = t_FuelStats.GetField("Isp", BindingFlags.Public | BindingFlags.Instance); + } + + // Initialize UserPool methods - need to specify parameter type due to 'new' keyword hiding + if (t_UserPool != null) + { + m_UserPool_Add = t_UserPool.GetMethod("Add", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly, null, new Type[] { typeof(object) }, null); + m_UserPool_Remove = t_UserPool.GetMethod("Remove", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly, null, new Type[] { typeof(object) }, null); + m_UserPool_Contains = t_UserPool.GetMethod("Contains", BindingFlags.Public | BindingFlags.Instance); + } + } + + private static void InitializeEnumTypes() + { + t_SmartASSTarget = mechJebAssembly.GetType("MuMech.MechJebModuleSmartASS+Target"); + t_SmartASSMode = mechJebAssembly.GetType("MuMech.MechJebModuleSmartASS+Mode"); + t_AttitudeReference = mechJebAssembly.GetType("MuMech.AttitudeReference"); + t_TranslatronMode = mechJebAssembly.GetType("MuMech.MechJebModuleThrustController+TMode"); + } + + private static void InitializeMethods() + { + // VesselExtensions methods + if (t_VesselExtensions != null) + { + m_GetMasterMechJeb = t_VesselExtensions.GetMethod("GetMasterMechJeb", + BindingFlags.Public | BindingFlags.Static, + null, new Type[] { typeof(Vessel) }, null); + m_PlaceManeuverNode = t_VesselExtensions.GetMethod("PlaceManeuverNode", + BindingFlags.Public | BindingFlags.Static); + } + + // MechJebCore methods and fields + if (t_MechJebCore != null) + { + m_GetComputerModule = t_MechJebCore.GetMethod("GetComputerModule", + new Type[] { typeof(string) }); + f_Core_Target = t_MechJebCore.GetField("Target", BindingFlags.Public | BindingFlags.Instance); + f_Core_Node = t_MechJebCore.GetField("Node", BindingFlags.Public | BindingFlags.Instance); + f_Core_Attitude = t_MechJebCore.GetField("Attitude", BindingFlags.Public | BindingFlags.Instance); + f_Core_Thrust = t_MechJebCore.GetField("Thrust", BindingFlags.Public | BindingFlags.Instance); + f_Core_Staging = t_MechJebCore.GetField("Staging", BindingFlags.Public | BindingFlags.Instance); + f_Core_VesselState = t_MechJebCore.GetField("VesselState", BindingFlags.Public | BindingFlags.Instance); + } + + // ComputerModule properties + if (t_ComputerModule != null) + { + p_Module_Enabled = t_ComputerModule.GetProperty("Enabled", BindingFlags.Public | BindingFlags.Instance); + f_Module_Users = t_ComputerModule.GetField("Users", BindingFlags.Public | BindingFlags.Instance); + } + + // Initialize all module-specific methods and fields + InitializeSmartASSMembers(); + InitializeNodeExecutorMembers(); + InitializeTargetControllerMembers(); + InitializeAscentMembers(); + InitializeLandingMembers(); + InitializeThrustMembers(); + InitializeStagingMembers(); + InitializeStageStatsMembers(); + InitializeTranslatronMembers(); + InitializeRendezvousMembers(); + InitializeDockingMembers(); + InitializeRoverMembers(); + InitializeAirplaneMembers(); + InitializeSpaceplaneMembers(); + InitializeManeuverCalculatorMembers(); + InitializeAdvancedTransferMembers(); + InitializeOperationMembers(); + InitializeWarpMembers(); + } + + private static void InitializeManeuverCalculatorMembers() + { + if (t_OrbitalManeuverCalculator == null) return; + + m_Calc_DeltaVToCircularize = t_OrbitalManeuverCalculator.GetMethod("DeltaVToCircularize", + BindingFlags.Public | BindingFlags.Static); + m_Calc_DeltaVToChangeApoapsis = t_OrbitalManeuverCalculator.GetMethod("DeltaVToChangeApoapsis", + BindingFlags.Public | BindingFlags.Static); + m_Calc_DeltaVToChangePeriapsis = t_OrbitalManeuverCalculator.GetMethod("DeltaVToChangePeriapsis", + BindingFlags.Public | BindingFlags.Static); + m_Calc_DeltaVToMatchVelocities = t_OrbitalManeuverCalculator.GetMethod("DeltaVToMatchVelocities", + BindingFlags.Public | BindingFlags.Static); + m_Calc_DeltaVAndTimeForHohmannTransfer = t_OrbitalManeuverCalculator.GetMethod("DeltaVAndTimeForHohmannTransfer", + BindingFlags.Public | BindingFlags.Static); + m_Calc_DeltaVForSemiMajorAxis = t_OrbitalManeuverCalculator.GetMethod("DeltaVForSemiMajorAxis", + BindingFlags.Public | BindingFlags.Static); + m_Calc_DeltaVToChangeInclination = t_OrbitalManeuverCalculator.GetMethod("DeltaVToChangeInclination", + BindingFlags.Public | BindingFlags.Static); + m_Calc_DeltaVToMatchPlanesAscending = t_OrbitalManeuverCalculator.GetMethod("DeltaVAndTimeToMatchPlanesAscending", + BindingFlags.Public | BindingFlags.Static); + m_Calc_DeltaVToMatchPlanesDescending = t_OrbitalManeuverCalculator.GetMethod("DeltaVAndTimeToMatchPlanesDescending", + BindingFlags.Public | BindingFlags.Static); + m_Calc_DeltaVToShiftLAN = t_OrbitalManeuverCalculator.GetMethod("DeltaVToShiftLAN", + BindingFlags.Public | BindingFlags.Static); + m_Calc_DeltaVToEllipticize = t_OrbitalManeuverCalculator.GetMethod("DeltaVToEllipticize", + BindingFlags.Public | BindingFlags.Static); + } + + private static void InitializeAdvancedTransferMembers() + { + // Operation base type bindings + if (t_Operation != null) + { + m_Operation_MakeNodes = t_Operation.GetMethod("MakeNodes", BindingFlags.Public | BindingFlags.Instance); + m_Operation_GetErrorMessage = t_Operation.GetMethod("GetErrorMessage", BindingFlags.Public | BindingFlags.Instance); + m_Operation_GetName = t_Operation.GetMethod("GetName", BindingFlags.Public | BindingFlags.Instance); + } + + // ManeuverPlanner static fields - these give us access to MechJeb's actual Operation instances + if (t_ManeuverPlanner != null) + { + f_ManeuverPlanner_operation = t_ManeuverPlanner.GetField("_operation", + BindingFlags.NonPublic | BindingFlags.Static); + f_ManeuverPlanner_operationId = t_ManeuverPlanner.GetField("_operationId", + BindingFlags.Public | BindingFlags.Instance); + } + + // TimeSelector and TimeReference for operation timing + t_TimeSelector = mechJebAssembly.GetType("MuMech.TimeSelector"); + if (t_TimeSelector != null) + { + m_TimeSelector_ComputeManeuverTime = t_TimeSelector.GetMethod("ComputeManeuverTime", + BindingFlags.Public | BindingFlags.Instance); + f_TimeSelector_CurrentTimeRef = t_TimeSelector.GetField("_currentTimeRef", + BindingFlags.Public | BindingFlags.Instance); + f_TimeSelector_CircularizeAltitude = t_TimeSelector.GetField("CircularizeAltitude", + BindingFlags.Public | BindingFlags.Instance); + f_TimeSelector_LeadTime = t_TimeSelector.GetField("LeadTime", + BindingFlags.Public | BindingFlags.Instance); + p_TimeSelector_TimeReference = t_TimeSelector.GetProperty("TimeReference", + BindingFlags.Public | BindingFlags.Instance); + } + + t_TimeReference = mechJebAssembly.GetType("MuMech.TimeReference"); + + if (t_OperationAdvancedTransfer != null) + { + f_AdvancedTransfer_SelectionMode = t_OperationAdvancedTransfer.GetField("selectionMode", BindingFlags.NonPublic | BindingFlags.Instance); + f_AdvancedTransfer_Worker = t_OperationAdvancedTransfer.GetField("worker", BindingFlags.NonPublic | BindingFlags.Instance); + f_AdvancedTransfer_Plot = t_OperationAdvancedTransfer.GetField("plot", BindingFlags.NonPublic | BindingFlags.Instance); + f_AdvancedTransfer_IncludeCaptureBurn = t_OperationAdvancedTransfer.GetField("includeCaptureBurn", BindingFlags.NonPublic | BindingFlags.Instance); + f_AdvancedTransfer_PeriapsisHeight = t_OperationAdvancedTransfer.GetField("periapsisHeight", BindingFlags.NonPublic | BindingFlags.Instance); + f_AdvancedTransfer_LastTargetCelestial = t_OperationAdvancedTransfer.GetField("lastTargetCelestial", BindingFlags.NonPublic | BindingFlags.Instance); + m_AdvancedTransfer_ComputeTimes = t_OperationAdvancedTransfer.GetMethod("ComputeTimes", BindingFlags.NonPublic | BindingFlags.Instance); + m_AdvancedTransfer_ComputeStuff = t_OperationAdvancedTransfer.GetMethod("ComputeStuff", BindingFlags.NonPublic | BindingFlags.Instance); + } + + // OperationGeneric (Hohmann/bi-impulsive transfer) + t_OperationGeneric = mechJebAssembly.GetType("MuMech.OperationGeneric"); + if (t_OperationGeneric != null) + { + f_Generic_Capture = t_OperationGeneric.GetField("Capture", BindingFlags.Public | BindingFlags.Instance); + f_Generic_PlanCapture = t_OperationGeneric.GetField("PlanCapture", BindingFlags.Public | BindingFlags.Instance); + f_Generic_Rendezvous = t_OperationGeneric.GetField("Rendezvous", BindingFlags.Public | BindingFlags.Instance); + f_Generic_Coplanar = t_OperationGeneric.GetField("Coplanar", BindingFlags.Public | BindingFlags.Instance); + f_Generic_LagTime = t_OperationGeneric.GetField("LagTime", BindingFlags.Public | BindingFlags.Instance); + } + + // PlotArea for porkchop selection + t_PlotArea = mechJebAssembly.GetType("MuMech.PlotArea"); + if (t_PlotArea != null) + { + p_PlotArea_SelectedPoint = t_PlotArea.GetProperty("SelectedPoint", BindingFlags.Public | BindingFlags.Instance); + } + + if (t_TransferCalculator != null) + { + f_TransferCalculator_Computed = t_TransferCalculator.GetField("Computed", BindingFlags.Public | BindingFlags.Instance); + f_TransferCalculator_BestDate = t_TransferCalculator.GetField("BestDate", BindingFlags.Public | BindingFlags.Instance); + f_TransferCalculator_BestDuration = t_TransferCalculator.GetField("BestDuration", BindingFlags.Public | BindingFlags.Instance); + p_TransferCalculator_Finished = t_TransferCalculator.GetProperty("Finished", BindingFlags.Public | BindingFlags.Instance); + p_TransferCalculator_Progress = t_TransferCalculator.GetProperty("Progress", BindingFlags.Public | BindingFlags.Instance); + p_TransferCalculator_ArrivalDate = t_TransferCalculator.GetProperty("ArrivalDate", BindingFlags.Public | BindingFlags.Instance); + m_TransferCalculator_DateFromIndex = t_TransferCalculator.GetMethod("DateFromIndex", BindingFlags.Public | BindingFlags.Instance); + m_TransferCalculator_DurationFromIndex = t_TransferCalculator.GetMethod("DurationFromIndex", BindingFlags.Public | BindingFlags.Instance); + } + + if (t_ManeuverParameters != null) + { + f_ManeuverParameters_dV = t_ManeuverParameters.GetField("dV", BindingFlags.Public | BindingFlags.Instance); + f_ManeuverParameters_UT = t_ManeuverParameters.GetField("UT", BindingFlags.Public | BindingFlags.Instance); + } + } + + private static void InitializeOperationMembers() + { + if (t_OperationCourseCorrection == null) return; + + string[] candidates = new string[] + { + "targetPe", "targetPeA", "desiredPe", "desiredPeA", "periapsis", "periapsisAltitude", "PeA" + }; + + foreach (string name in candidates) + { + FieldInfo f = t_OperationCourseCorrection.GetField(name, BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic); + if (f != null) + { + f_CourseCorrection_TargetPe = f; + return; + } + + PropertyInfo p = t_OperationCourseCorrection.GetProperty(name, BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic); + if (p != null) + { + p_CourseCorrection_TargetPe = p; + return; + } + } + } + + private static void InitializeWarpMembers() + { + if (t_WarpController == null) return; + m_Warp_WarpToUT = t_WarpController.GetMethod("WarpToUT", BindingFlags.Public | BindingFlags.Instance); + } + + private static void InitializeSmartASSMembers() + { + if (t_SmartASS == null) return; + + f_SmartASS_Target = t_SmartASS.GetField("target", BindingFlags.Public | BindingFlags.Instance); + f_SmartASS_Mode = t_SmartASS.GetField("mode", BindingFlags.Public | BindingFlags.Instance); + f_SmartASS_ForceRol = t_SmartASS.GetField("forceRol", BindingFlags.Public | BindingFlags.Instance); + f_SmartASS_Rol = t_SmartASS.GetField("rol", BindingFlags.Public | BindingFlags.Instance); + f_SmartASS_SrfHdg = t_SmartASS.GetField("srfHdg", BindingFlags.Public | BindingFlags.Instance); + f_SmartASS_SrfPit = t_SmartASS.GetField("srfPit", BindingFlags.Public | BindingFlags.Instance); + f_SmartASS_SrfRol = t_SmartASS.GetField("srfRol", BindingFlags.Public | BindingFlags.Instance); + f_SmartASS_SrfVelYaw = t_SmartASS.GetField("srfVelYaw", BindingFlags.Public | BindingFlags.Instance); + f_SmartASS_SrfVelPit = t_SmartASS.GetField("srfVelPit", BindingFlags.Public | BindingFlags.Instance); + f_SmartASS_SrfVelRol = t_SmartASS.GetField("srfVelRol", BindingFlags.Public | BindingFlags.Instance); + f_SmartASS_AdvReference = t_SmartASS.GetField("advReference", BindingFlags.Public | BindingFlags.Instance); + f_SmartASS_AdvDirection = t_SmartASS.GetField("advDirection", BindingFlags.Public | BindingFlags.Instance); + m_SmartASS_Engage = t_SmartASS.GetMethod("Engage", BindingFlags.Public | BindingFlags.Instance); + + // Static fields for text arrays + f_SmartASS_ModeTexts = t_SmartASS.GetField("ModeTexts", BindingFlags.Public | BindingFlags.Static); + f_SmartASS_TargetTexts = t_SmartASS.GetField("TargetTexts", BindingFlags.Public | BindingFlags.Static); + } + + private static void InitializeNodeExecutorMembers() + { + if (t_NodeExecutor == null) return; + + m_NodeExecutor_ExecuteOneNode = t_NodeExecutor.GetMethod("ExecuteOneNode", BindingFlags.Public | BindingFlags.Instance); + m_NodeExecutor_ExecuteAllNodes = t_NodeExecutor.GetMethod("ExecuteAllNodes", BindingFlags.Public | BindingFlags.Instance); + m_NodeExecutor_Abort = t_NodeExecutor.GetMethod("Abort", BindingFlags.Public | BindingFlags.Instance); + f_NodeExecutor_Autowarp = t_NodeExecutor.GetField("Autowarp", BindingFlags.Public | BindingFlags.Instance); + f_NodeExecutor_LeadTime = t_NodeExecutor.GetField("leadTime", BindingFlags.Public | BindingFlags.Instance); + } + + private static void InitializeTargetControllerMembers() + { + if (t_TargetController == null) return; + + p_Target_PositionTargetExists = t_TargetController.GetProperty("PositionTargetExists", BindingFlags.Public | BindingFlags.Instance); + p_Target_NormalTargetExists = t_TargetController.GetProperty("NormalTargetExists", BindingFlags.Public | BindingFlags.Instance); + p_Target_TargetOrbit = t_TargetController.GetProperty("TargetOrbit", BindingFlags.Public | BindingFlags.Instance); + f_Target_TargetLatitude = t_TargetController.GetField("targetLatitude", BindingFlags.Public | BindingFlags.Instance); + f_Target_TargetLongitude = t_TargetController.GetField("targetLongitude", BindingFlags.Public | BindingFlags.Instance); + m_Target_SetPositionTarget = t_TargetController.GetMethod("SetPositionTarget", BindingFlags.Public | BindingFlags.Instance); + m_Target_PickPositionTargetOnMap = t_TargetController.GetMethod("PickPositionTargetOnMap", BindingFlags.Public | BindingFlags.Instance); + } + + private static void InitializeAscentMembers() + { + if (t_AscentSettings == null) return; + + f_Ascent_DesiredOrbitAltitude = t_AscentSettings.GetField("DesiredOrbitAltitude", BindingFlags.Public | BindingFlags.Instance); + f_Ascent_DesiredApoapsis = t_AscentSettings.GetField("DesiredApoapsis", BindingFlags.Public | BindingFlags.Instance); + f_Ascent_DesiredInclination = t_AscentSettings.GetField("DesiredInclination", BindingFlags.Public | BindingFlags.Instance); + f_Ascent_DesiredLAN = t_AscentSettings.GetField("DesiredLan", BindingFlags.Public | BindingFlags.Instance); + f_Ascent_LaunchPhaseAngle = t_AscentSettings.GetField("LaunchPhaseAngle", BindingFlags.Public | BindingFlags.Instance); + f_Ascent_LaunchLANDifference = t_AscentSettings.GetField("LaunchLANDifference", BindingFlags.Public | BindingFlags.Instance); + p_Ascent_AscentAutopilot = t_AscentSettings.GetProperty("AscentAutopilot", BindingFlags.Public | BindingFlags.Instance); + f_Ascent_WarpCountDown = t_AscentSettings.GetField("WarpCountDown", BindingFlags.Public | BindingFlags.Instance); + f_Ascent_SkipCircularization = t_AscentSettings.GetField("SkipCircularization", BindingFlags.Public | BindingFlags.Instance); + f_Ascent_ForceRoll = t_AscentSettings.GetField("ForceRoll", BindingFlags.Public | BindingFlags.Instance); + f_Ascent_VerticalRoll = t_AscentSettings.GetField("VerticalRoll", BindingFlags.Public | BindingFlags.Instance); + f_Ascent_TurnRoll = t_AscentSettings.GetField("TurnRoll", BindingFlags.Public | BindingFlags.Instance); + f_Ascent_RollAltitude = t_AscentSettings.GetField("RollAltitude", BindingFlags.Public | BindingFlags.Instance); + f_Ascent_LimitAoA = t_AscentSettings.GetField("LimitAoA", BindingFlags.Public | BindingFlags.Instance); + f_Ascent_MaxAoA = t_AscentSettings.GetField("MaxAoA", BindingFlags.Public | BindingFlags.Instance); + f_Ascent_AOALimitFadeoutPressure = t_AscentSettings.GetField("AOALimitFadeoutPressure", BindingFlags.Public | BindingFlags.Instance); + f_Ascent_CorrectiveSteering = t_AscentSettings.GetField("CorrectiveSteering", BindingFlags.Public | BindingFlags.Instance); + f_Ascent_CorrectiveSteeringGain = t_AscentSettings.GetField("CorrectiveSteeringGain", BindingFlags.Public | BindingFlags.Instance); + + f_Ascent_TurnStartAltitude = t_AscentSettings.GetField("TurnStartAltitude", BindingFlags.Public | BindingFlags.Instance); + f_Ascent_TurnStartVelocity = t_AscentSettings.GetField("TurnStartVelocity", BindingFlags.Public | BindingFlags.Instance); + f_Ascent_TurnEndAltitude = t_AscentSettings.GetField("TurnEndAltitude", BindingFlags.Public | BindingFlags.Instance); + f_Ascent_TurnEndAngle = t_AscentSettings.GetField("TurnEndAngle", BindingFlags.Public | BindingFlags.Instance); + f_Ascent_TurnShapeExponent = t_AscentSettings.GetField("TurnShapeExponent", BindingFlags.Public | BindingFlags.Instance); + f_Ascent_AutoPath = t_AscentSettings.GetField("AutoPath", BindingFlags.Public | BindingFlags.Instance); + f_Ascent_AutoTurnPerc = t_AscentSettings.GetField("AutoTurnPerc", BindingFlags.Public | BindingFlags.Instance); + f_Ascent_AutoTurnSpdFactor = t_AscentSettings.GetField("AutoTurnSpdFactor", BindingFlags.Public | BindingFlags.Instance); + + p_Ascent_Autostage = t_AscentSettings.GetProperty("Autostage", BindingFlags.Public | BindingFlags.Instance); + // In MJ 2.15.1, Autostage is a public field, not a private backing field + f_Ascent_Autostage = t_AscentSettings.GetField("Autostage", BindingFlags.Public | BindingFlags.Instance); + if (f_Ascent_Autostage == null) + { + // Fallback for older versions that might use a private backing field + f_Ascent_Autostage = t_AscentSettings.GetField("_autostage", BindingFlags.NonPublic | BindingFlags.Instance); + } + + if (t_AscentBaseAutopilot != null) + { + p_AscentAP_Status = t_AscentBaseAutopilot.GetProperty("Status", BindingFlags.Public | BindingFlags.Instance); + m_AscentAP_StartCountdown = t_AscentBaseAutopilot.GetMethod("StartCountdown", BindingFlags.Public | BindingFlags.Instance); + } + } + + private static void InitializeLandingMembers() + { + if (t_LandingAutopilot == null) return; + + m_Landing_LandAtPositionTarget = t_LandingAutopilot.GetMethod("LandAtPositionTarget", BindingFlags.Public | BindingFlags.Instance); + m_Landing_LandUntargeted = t_LandingAutopilot.GetMethod("LandUntargeted", BindingFlags.Public | BindingFlags.Instance); + m_Landing_StopLanding = t_LandingAutopilot.GetMethod("StopLanding", BindingFlags.Public | BindingFlags.Instance); + f_Landing_TouchdownSpeed = t_LandingAutopilot.GetField("TouchdownSpeed", BindingFlags.Public | BindingFlags.Instance); + f_Landing_DeployGears = t_LandingAutopilot.GetField("DeployGears", BindingFlags.Public | BindingFlags.Instance); + f_Landing_DeployChutes = t_LandingAutopilot.GetField("DeployChutes", BindingFlags.Public | BindingFlags.Instance); + f_Landing_LimitGearsStage = t_LandingAutopilot.GetField("LimitGearsStage", BindingFlags.Public | BindingFlags.Instance); + f_Landing_LimitChutesStage = t_LandingAutopilot.GetField("LimitChutesStage", BindingFlags.Public | BindingFlags.Instance); + f_Landing_UseRCS = t_LandingAutopilot.GetField("RCSAdjustment", BindingFlags.Public | BindingFlags.Instance); + p_Landing_Status = t_LandingAutopilot.GetProperty("Status", BindingFlags.Public | BindingFlags.Instance); + + if (t_LandingPredictions != null) + { + m_Predictions_GetResult = t_LandingPredictions.GetMethod("GetResult", BindingFlags.Public | BindingFlags.Instance); + p_Predictions_ShowTrajectory = t_LandingPredictions.GetProperty("ShowTrajectory", BindingFlags.Public | BindingFlags.Instance); + } + } + + private static void InitializeThrustMembers() + { + if (t_ThrustController == null) return; + + f_Thrust_LimitToPreventOverheats = t_ThrustController.GetField("LimitToPreventOverheats", BindingFlags.Public | BindingFlags.Instance); + f_Thrust_LimitToTerminalVelocity = t_ThrustController.GetField("LimitToTerminalVelocity", BindingFlags.Public | BindingFlags.Instance); + f_Thrust_LimitToMaxDynamicPressure = t_ThrustController.GetField("LimitDynamicPressure", BindingFlags.Public | BindingFlags.Instance); + f_Thrust_MaxDynamicPressure = t_ThrustController.GetField("MaxDynamicPressure", BindingFlags.Public | BindingFlags.Instance); + f_Thrust_LimitAcceleration = t_ThrustController.GetField("LimitAcceleration", BindingFlags.Public | BindingFlags.Instance); + f_Thrust_MaxAcceleration = t_ThrustController.GetField("MaxAcceleration", BindingFlags.Public | BindingFlags.Instance); + f_Thrust_LimitThrottle = t_ThrustController.GetField("LimitThrottle", BindingFlags.Public | BindingFlags.Instance); + f_Thrust_MaxThrottle = t_ThrustController.GetField("MaxThrottle", BindingFlags.Public | BindingFlags.Instance); + f_Thrust_LimiterMinThrottle = t_ThrustController.GetField("LimiterMinThrottle", BindingFlags.Public | BindingFlags.Instance); + f_Thrust_MinThrottle = t_ThrustController.GetField("MinThrottle", BindingFlags.Public | BindingFlags.Instance); + f_Thrust_LimitToPreventFlameout = t_ThrustController.GetField("LimitToPreventFlameout", BindingFlags.Public | BindingFlags.Instance); + f_Thrust_FlameoutSafetyPct = t_ThrustController.GetField("FlameoutSafetyPct", BindingFlags.Public | BindingFlags.Instance); + f_Thrust_SmoothThrottle = t_ThrustController.GetField("SmoothThrottle", BindingFlags.Public | BindingFlags.Instance); + f_Thrust_ManageIntakes = t_ThrustController.GetField("ManageIntakes", BindingFlags.Public | BindingFlags.Instance); + f_Thrust_DifferentialThrottle = t_ThrustController.GetField("DifferentialThrottle", BindingFlags.Public | BindingFlags.Instance); + f_Thrust_DifferentialThrottleSuccess = t_ThrustController.GetField("DifferentialThrottleSuccess", BindingFlags.Public | BindingFlags.Instance); + } + + private static void InitializeStagingMembers() + { + if (t_StagingController == null) return; + + f_Staging_Autostage = t_StagingController.GetField("Autostage", BindingFlags.Public | BindingFlags.Instance); + f_Staging_AutostageLimit = t_StagingController.GetField("AutostageLimit", BindingFlags.Public | BindingFlags.Instance); + f_Staging_AutostagePreDelay = t_StagingController.GetField("AutostagePreDelay", BindingFlags.Public | BindingFlags.Instance); + f_Staging_AutostagePostDelay = t_StagingController.GetField("AutostagePostDelay", BindingFlags.Public | BindingFlags.Instance); + f_Staging_ClampAutoStageThrustPct = t_StagingController.GetField("ClampAutoStageThrustPct", BindingFlags.Public | BindingFlags.Instance); + f_Staging_FairingMaxAerothermalFlux = t_StagingController.GetField("FairingMaxAerothermalFlux", BindingFlags.Public | BindingFlags.Instance); + f_Staging_FairingMaxDynamicPressure = t_StagingController.GetField("FairingMaxDynamicPressure", BindingFlags.Public | BindingFlags.Instance); + f_Staging_FairingMinAltitude = t_StagingController.GetField("FairingMinAltitude", BindingFlags.Public | BindingFlags.Instance); + f_Staging_HotStagingLeadTime = t_StagingController.GetField("HotStagingLeadTime", BindingFlags.Public | BindingFlags.Instance); + f_Staging_DropSolids = t_StagingController.GetField("DropSolids", BindingFlags.Public | BindingFlags.Instance); + f_Staging_DropSolidsLeadTime = t_StagingController.GetField("DropSolidsLeadTime", BindingFlags.Public | BindingFlags.Instance); + m_Staging_AutostageOnce = t_StagingController.GetMethod("AutostageOnce", BindingFlags.Public | BindingFlags.Instance); + } + + private static void InitializeStageStatsMembers() + { + if (t_StageStats == null) return; + + m_StageStats_RequestUpdate = t_StageStats.GetMethod("RequestUpdate", BindingFlags.Public | BindingFlags.Instance); + f_StageStats_VacStats = t_StageStats.GetField("VacStats", BindingFlags.Public | BindingFlags.Instance); + f_StageStats_AtmoStats = t_StageStats.GetField("AtmoStats", BindingFlags.Public | BindingFlags.Instance); + } + + private static void InitializeTranslatronMembers() + { + if (t_Translatron == null) return; + + p_Translatron_TransSpd = t_Translatron.GetProperty("trans_spd", BindingFlags.Public | BindingFlags.Instance); + f_Translatron_TransSpdAct = t_Translatron.GetField("trans_spd_act", BindingFlags.Public | BindingFlags.Instance); + p_Translatron_TransKillH = t_Translatron.GetProperty("trans_kill_h", BindingFlags.Public | BindingFlags.Instance); + m_Translatron_SetMode = t_Translatron.GetMethod("SetMode", BindingFlags.Public | BindingFlags.Instance); + m_Translatron_PanicSwitch = t_Translatron.GetMethod("PanicSwitch", BindingFlags.Public | BindingFlags.Instance); + } + + private static void InitializeRendezvousMembers() + { + if (t_RendezvousAutopilot == null) return; + + f_Rendezvous_DesiredDistance = t_RendezvousAutopilot.GetField("desiredDistance", BindingFlags.Public | BindingFlags.Instance); + f_Rendezvous_MaxPhasingOrbits = t_RendezvousAutopilot.GetField("maxPhasingOrbits", BindingFlags.Public | BindingFlags.Instance); + f_Rendezvous_MaxClosingSpeed = t_RendezvousAutopilot.GetField("maxClosingSpeed", BindingFlags.Public | BindingFlags.Instance); + p_Rendezvous_Status = t_RendezvousAutopilot.GetProperty("Status", BindingFlags.Public | BindingFlags.Instance); + } + + private static void InitializeDockingMembers() + { + if (t_DockingAutopilot == null) return; + + f_Docking_SpeedLimit = t_DockingAutopilot.GetField("speedLimit", BindingFlags.Public | BindingFlags.Instance); + f_Docking_ForceRoll = t_DockingAutopilot.GetField("forceRol", BindingFlags.Public | BindingFlags.Instance); + f_Docking_Roll = t_DockingAutopilot.GetField("rol", BindingFlags.Public | BindingFlags.Instance); + f_Docking_OverrideSafeDistance = t_DockingAutopilot.GetField("overrideSafeDistance", BindingFlags.Public | BindingFlags.Instance); + f_Docking_OverridenSafeDistance = t_DockingAutopilot.GetField("overridenSafeDistance", BindingFlags.Public | BindingFlags.Instance); + f_Docking_OverrideTargetSize = t_DockingAutopilot.GetField("overrideTargetSize", BindingFlags.Public | BindingFlags.Instance); + f_Docking_OverridenTargetSize = t_DockingAutopilot.GetField("overridenTargetSize", BindingFlags.Public | BindingFlags.Instance); + f_Docking_DrawBoundingBox = t_DockingAutopilot.GetField("drawBoundingBox", BindingFlags.Public | BindingFlags.Instance); + p_Docking_Status = t_DockingAutopilot.GetProperty("Status", BindingFlags.Public | BindingFlags.Instance); + } + + private static void InitializeRoverMembers() + { + if (t_RoverController == null) return; + + f_Rover_ControlHeading = t_RoverController.GetField("ControlHeading", BindingFlags.Public | BindingFlags.Instance); + f_Rover_ControlSpeed = t_RoverController.GetField("ControlSpeed", BindingFlags.Public | BindingFlags.Instance); + f_Rover_Heading = t_RoverController.GetField("heading", BindingFlags.Public | BindingFlags.Instance); + f_Rover_Speed = t_RoverController.GetField("speed", BindingFlags.Public | BindingFlags.Instance); + f_Rover_HeadingError = t_RoverController.GetField("headingErr", BindingFlags.Public | BindingFlags.Instance); + f_Rover_SpeedError = t_RoverController.GetField("speedErr", BindingFlags.Public | BindingFlags.Instance); + f_Rover_StabilityControl = t_RoverController.GetField("StabilityControl", BindingFlags.Public | BindingFlags.Instance); + f_Rover_BrakeOnEject = t_RoverController.GetField("BrakeOnEject", BindingFlags.Public | BindingFlags.Instance); + f_Rover_BrakeOnEnergyDepletion = t_RoverController.GetField("BrakeOnEnergyDepletion", BindingFlags.Public | BindingFlags.Instance); + f_Rover_WarpToDaylight = t_RoverController.GetField("WarpToDaylight", BindingFlags.Public | BindingFlags.Instance); + m_Rover_DriveToTarget = t_RoverController.GetMethod("DriveToTarget", BindingFlags.Public | BindingFlags.Instance); + m_Rover_Stop = t_RoverController.GetMethod("Stop", BindingFlags.Public | BindingFlags.Instance); + + MethodInfo[] methods = t_RoverController.GetMethods(BindingFlags.Public | BindingFlags.Instance); + foreach (MethodInfo m in methods) + { + if (m_Rover_AddWaypoint == null && (m.Name == "AddWaypoint" || m.Name == "AddWayPoint" || m.Name == "AddNewWaypoint")) + { + m_Rover_AddWaypoint = m; + } + if (m_Rover_ClearWaypoints == null && (m.Name == "ClearWaypoints" || m.Name == "ClearAllWaypoints")) + { + m_Rover_ClearWaypoints = m; + } + } + } + + private static void InitializeAirplaneMembers() + { + if (t_AirplaneAutopilot == null) return; + + f_Airplane_AltitudeHold = t_AirplaneAutopilot.GetField("AltitudeHoldEnabled", BindingFlags.Public | BindingFlags.Instance); + f_Airplane_AltitudeTarget = t_AirplaneAutopilot.GetField("AltitudeTarget", BindingFlags.Public | BindingFlags.Instance); + f_Airplane_VertSpeedHold = t_AirplaneAutopilot.GetField("VertSpeedHoldEnabled", BindingFlags.Public | BindingFlags.Instance); + f_Airplane_VertSpeedTarget = t_AirplaneAutopilot.GetField("VertSpeedTarget", BindingFlags.Public | BindingFlags.Instance); + f_Airplane_HeadingHold = t_AirplaneAutopilot.GetField("HeadingHoldEnabled", BindingFlags.Public | BindingFlags.Instance); + f_Airplane_HeadingTarget = t_AirplaneAutopilot.GetField("HeadingTarget", BindingFlags.Public | BindingFlags.Instance); + f_Airplane_RollHold = t_AirplaneAutopilot.GetField("RollHoldEnabled", BindingFlags.Public | BindingFlags.Instance); + f_Airplane_RollTarget = t_AirplaneAutopilot.GetField("RollTarget", BindingFlags.Public | BindingFlags.Instance); + f_Airplane_RollMax = t_AirplaneAutopilot.GetField("BankAngle", BindingFlags.Public | BindingFlags.Instance); + f_Airplane_SpeedHold = t_AirplaneAutopilot.GetField("SpeedHoldEnabled", BindingFlags.Public | BindingFlags.Instance); + f_Airplane_SpeedTarget = t_AirplaneAutopilot.GetField("SpeedTarget", BindingFlags.Public | BindingFlags.Instance); + // PID parameters - note: MJ 2.15 may have changed these names + f_Airplane_AccKp = t_AirplaneAutopilot.GetField("AccKp", BindingFlags.Public | BindingFlags.Instance); + f_Airplane_AccKi = t_AirplaneAutopilot.GetField("AccKi", BindingFlags.Public | BindingFlags.Instance); + f_Airplane_AccKd = t_AirplaneAutopilot.GetField("AccKd", BindingFlags.Public | BindingFlags.Instance); + } + + private static void InitializeSpaceplaneMembers() + { + if (t_SpaceplaneAutopilot == null) return; + + f_Spaceplane_Glideslope = t_SpaceplaneAutopilot.GetField("glideslope", BindingFlags.Public | BindingFlags.Instance); + f_Spaceplane_ApproachSpeed = t_SpaceplaneAutopilot.GetField("approachSpeed", BindingFlags.Public | BindingFlags.Instance); + f_Spaceplane_TouchdownSpeed = t_SpaceplaneAutopilot.GetField("touchdownSpeed", BindingFlags.Public | BindingFlags.Instance); + f_Spaceplane_Mode = t_SpaceplaneAutopilot.GetField("mode", BindingFlags.Public | BindingFlags.Instance); + m_Spaceplane_Autoland = t_SpaceplaneAutopilot.GetMethod("Autoland", BindingFlags.Public | BindingFlags.Instance); + m_Spaceplane_HoldHeadingAndAltitude = t_SpaceplaneAutopilot.GetMethod("HoldHeadingAndAltitude", BindingFlags.Public | BindingFlags.Instance); + m_Spaceplane_AutopilotOff = t_SpaceplaneAutopilot.GetMethod("AutopilotOff", BindingFlags.Public | BindingFlags.Instance); + } + #endregion + + #region Core Access Methods + /// + /// Get the master MechJeb core for a vessel + /// + public static object GetMasterMechJeb(Vessel vessel) + { + if (!mjAvailable || vessel == null || m_GetMasterMechJeb == null) return null; + try + { + return m_GetMasterMechJeb.Invoke(null, new object[] { vessel }); + } + catch + { + return null; + } + } + + /// + /// Get a computer module by name from the core + /// + public static object GetComputerModule(object core, string moduleName) + { + if (core == null || m_GetComputerModule == null) return null; + try + { + return m_GetComputerModule.Invoke(core, new object[] { moduleName }); + } + catch + { + return null; + } + } + + /// + /// Check if a module exists on this core + /// + public static bool ModuleExists(object core, string moduleName) + { + return GetComputerModule(core, moduleName) != null; + } + + /// + /// Get whether a module is enabled + /// + public static bool GetModuleEnabled(object module) + { + if (module == null || p_Module_Enabled == null) return false; + try + { + return (bool)p_Module_Enabled.GetValue(module, null); + } + catch + { + return false; + } + } + + /// + /// Set module enabled state + /// + public static void SetModuleEnabled(object module, bool enabled) + { + if (module == null || p_Module_Enabled == null) return; + try + { + p_Module_Enabled.SetValue(module, enabled, null); + } + catch { } + } + + /// + /// Get the Users property of a module + /// + public static object GetModuleUsers(object module) + { + if (module == null || f_Module_Users == null) return null; + try + { + return f_Module_Users.GetValue(module); + } + catch + { + return null; + } + } + + /// + /// Add a user to a module's user pool + /// + public static void AddUser(object users, object user) + { + if (users == null || user == null || m_UserPool_Add == null) return; + try + { + m_UserPool_Add.Invoke(users, new object[] { user }); + } + catch { } + } + + /// + /// Remove a user from a module's user pool + /// + public static void RemoveUser(object users, object user) + { + if (users == null || user == null || m_UserPool_Remove == null) return; + try + { + m_UserPool_Remove.Invoke(users, new object[] { user }); + } + catch { } + } + #endregion + + #region Editable Value Helpers + /// + /// Get value from an EditableDouble + /// + public static double GetEditableDouble(object editable) + { + if (editable == null || p_EditableDouble_Val == null) return 0; + try + { + return (double)p_EditableDouble_Val.GetValue(editable, null); + } + catch + { + return 0; + } + } + + /// + /// Set value on an EditableDouble + /// + public static void SetEditableDouble(object editable, double value) + { + if (editable == null || p_EditableDouble_Val == null) return; + try + { + p_EditableDouble_Val.SetValue(editable, value, null); + } + catch { } + } + + /// + /// Get value from an EditableDoubleMult + /// + public static double GetEditableDoubleMult(object editable) + { + if (editable == null || p_EditableDoubleMult_Val == null) return 0; + try + { + return (double)p_EditableDoubleMult_Val.GetValue(editable, null); + } + catch + { + return 0; + } + } + + /// + /// Set value on an EditableDoubleMult + /// + public static void SetEditableDoubleMult(object editable, double value) + { + if (editable == null || p_EditableDoubleMult_Val == null) return; + try + { + p_EditableDoubleMult_Val.SetValue(editable, value, null); + } + catch { } + } + + /// + /// Get value from an EditableInt + /// + public static int GetEditableInt(object editable) + { + if (editable == null || p_EditableInt_Val == null) return 0; + try + { + return (int)p_EditableInt_Val.GetValue(editable, null); + } + catch + { + return 0; + } + } + + /// + /// Set value on an EditableInt + /// + public static void SetEditableInt(object editable, int value) + { + if (editable == null || p_EditableInt_Val == null) return; + try + { + p_EditableInt_Val.SetValue(editable, value, null); + } + catch { } + } + + /// + /// Convert an EditableAngle to double + /// + public static double GetEditableAngle(object editable) + { + if (editable == null || m_EditableAngle_ToDouble == null) return 0; + try + { + return (double)m_EditableAngle_ToDouble.Invoke(null, new object[] { editable }); + } + catch + { + return 0; + } + } + + /// + /// Get a field value of type EditableDouble and return its double value + /// + public static double GetEditableDoubleField(object obj, FieldInfo field) + { + if (obj == null || field == null) return 0; + try + { + object editable = field.GetValue(obj); + return GetEditableDouble(editable); + } + catch + { + return 0; + } + } + + /// + /// Set a field value of type EditableDouble + /// + public static void SetEditableDoubleField(object obj, FieldInfo field, double value) + { + if (obj == null || field == null) return; + try + { + object editable = field.GetValue(obj); + SetEditableDouble(editable, value); + } + catch { } + } + + /// + /// Get a field value of type EditableDoubleMult and return its double value + /// + public static double GetEditableDoubleMultField(object obj, FieldInfo field) + { + if (obj == null || field == null) return 0; + try + { + object editable = field.GetValue(obj); + return GetEditableDoubleMult(editable); + } + catch + { + return 0; + } + } + + /// + /// Set a field value of type EditableDoubleMult + /// + public static void SetEditableDoubleMultField(object obj, FieldInfo field, double value) + { + if (obj == null || field == null) return; + try + { + object editable = field.GetValue(obj); + SetEditableDoubleMult(editable, value); + } + catch { } + } + + private static void SetMemberDouble(object target, MemberInfo member, double value) + { + if (target == null || member == null) return; + try + { + object memberValue = null; + Type memberType = null; + + FieldInfo field = member as FieldInfo; + if (field != null) + { + memberValue = field.GetValue(target); + memberType = field.FieldType; + } + else + { + PropertyInfo prop = member as PropertyInfo; + if (prop != null) + { + memberValue = prop.GetValue(target, null); + memberType = prop.PropertyType; + } + } + + if (memberValue != null) + { + if (t_EditableDouble != null && t_EditableDouble.IsInstanceOfType(memberValue)) + { + SetEditableDouble(memberValue, value); + return; + } + if (t_EditableDoubleMult != null && t_EditableDoubleMult.IsInstanceOfType(memberValue)) + { + SetEditableDoubleMult(memberValue, value); + return; + } + } + + if (memberType == typeof(double)) + { + if (field != null) field.SetValue(target, value); + else if (member is PropertyInfo) ((PropertyInfo)member).SetValue(target, value, null); + return; + } + + if (memberType == typeof(float)) + { + if (field != null) field.SetValue(target, (float)value); + else if (member is PropertyInfo) ((PropertyInfo)member).SetValue(target, (float)value, null); + } + } + catch { } + } + + private static object CreateAbsoluteVector(CelestialBody body, double latitude, double longitude, double altitude) + { + if (t_AbsoluteVector == null) return null; + try + { + ConstructorInfo ctor = t_AbsoluteVector.GetConstructor(new Type[] + { + typeof(CelestialBody), typeof(double), typeof(double), typeof(double) + }); + if (ctor != null) + { + return ctor.Invoke(new object[] { body, latitude, longitude, altitude }); + } + + ctor = t_AbsoluteVector.GetConstructor(new Type[] + { + typeof(CelestialBody), typeof(double), typeof(double) + }); + if (ctor != null) + { + return ctor.Invoke(new object[] { body, latitude, longitude }); + } + } + catch { } + return null; + } + #endregion + + #region Core Module Accessors + /// + /// Get the Target controller from the core + /// + public static object GetTargetController(object core) + { + if (core == null || f_Core_Target == null) return null; + try + { + return f_Core_Target.GetValue(core); + } + catch + { + return null; + } + } + + /// + /// Get the Node executor from the core + /// + public static object GetNodeExecutor(object core) + { + if (core == null || f_Core_Node == null) return null; + try + { + return f_Core_Node.GetValue(core); + } + catch + { + return null; + } + } + + /// + /// Get the Attitude controller from the core + /// + public static object GetAttitudeController(object core) + { + if (core == null || f_Core_Attitude == null) return null; + try + { + return f_Core_Attitude.GetValue(core); + } + catch + { + return null; + } + } + + /// + /// Get the Thrust controller from the core + /// + public static object GetThrustController(object core) + { + if (core == null || f_Core_Thrust == null) return null; + try + { + return f_Core_Thrust.GetValue(core); + } + catch + { + return null; + } + } + + /// + /// Get the Staging controller from the core + /// + public static object GetStagingController(object core) + { + if (core == null || f_Core_Staging == null) return null; + try + { + return f_Core_Staging.GetValue(core); + } + catch + { + return null; + } + } + + /// + /// Get the VesselState from the core + /// + public static object GetVesselState(object core) + { + if (core == null || f_Core_VesselState == null) return null; + try + { + return f_Core_VesselState.GetValue(core); + } + catch + { + return null; + } + } + #endregion + + #region SmartASS Methods + /// + /// Get the SmartASS module + /// + public static object GetSmartASS(object core) + { + return GetComputerModule(core, "MechJebModuleSmartASS"); + } + + /// + /// Get the current SmartASS target + /// + public static int GetSmartASSTarget(object smartass) + { + if (smartass == null || f_SmartASS_Target == null) return 0; + try + { + return (int)f_SmartASS_Target.GetValue(smartass); + } + catch + { + return 0; + } + } + + /// + /// Set SmartASS target and engage + /// + public static void SetSmartASSTarget(object smartass, Target target) + { + if (smartass == null) return; + try + { + if (f_SmartASS_Target != null) + { + // Convert our Target enum to MechJeb's enum + object mjTarget = Enum.ToObject(t_SmartASSTarget, (int)target); + f_SmartASS_Target.SetValue(smartass, mjTarget); + } + if (m_SmartASS_Engage != null) + { + m_SmartASS_Engage.Invoke(smartass, new object[] { true }); + } + } + catch { } + } + + /// + /// Get force roll state + /// + public static bool GetSmartASSForceRoll(object smartass) + { + if (smartass == null || f_SmartASS_ForceRol == null) return false; + try + { + return (bool)f_SmartASS_ForceRol.GetValue(smartass); + } + catch + { + return false; + } + } + + /// + /// Set force roll state + /// + public static void SetSmartASSForceRoll(object smartass, bool force) + { + if (smartass == null || f_SmartASS_ForceRol == null) return; + try + { + f_SmartASS_ForceRol.SetValue(smartass, force); + } + catch { } + } + + /// + /// Get roll angle + /// + public static double GetSmartASSRoll(object smartass) + { + if (smartass == null || f_SmartASS_Rol == null) return 0; + return GetEditableDoubleField(smartass, f_SmartASS_Rol); + } + + /// + /// Set roll angle + /// + public static void SetSmartASSRoll(object smartass, double roll) + { + if (smartass == null || f_SmartASS_Rol == null) return; + SetEditableDoubleField(smartass, f_SmartASS_Rol, roll); + } + + /// + /// Get surface heading + /// + public static double GetSmartASSSurfaceHeading(object smartass) + { + if (smartass == null || f_SmartASS_SrfHdg == null) return 0; + return GetEditableDoubleField(smartass, f_SmartASS_SrfHdg); + } + + /// + /// Set surface heading + /// + public static void SetSmartASSSurfaceHeading(object smartass, double heading) + { + if (smartass == null || f_SmartASS_SrfHdg == null) return; + SetEditableDoubleField(smartass, f_SmartASS_SrfHdg, heading); + } + + /// + /// Get surface pitch + /// + public static double GetSmartASSSurfacePitch(object smartass) + { + if (smartass == null || f_SmartASS_SrfPit == null) return 0; + return GetEditableDoubleField(smartass, f_SmartASS_SrfPit); + } + + /// + /// Set surface pitch + /// + public static void SetSmartASSSurfacePitch(object smartass, double pitch) + { + if (smartass == null || f_SmartASS_SrfPit == null) return; + SetEditableDoubleField(smartass, f_SmartASS_SrfPit, pitch); + } + + /// + /// Get surface roll + /// + public static double GetSmartASSSurfaceRoll(object smartass) + { + if (smartass == null || f_SmartASS_SrfRol == null) return 0; + return GetEditableDoubleField(smartass, f_SmartASS_SrfRol); + } + + /// + /// Set surface roll + /// + public static void SetSmartASSSurfaceRoll(object smartass, double roll) + { + if (smartass == null || f_SmartASS_SrfRol == null) return; + SetEditableDoubleField(smartass, f_SmartASS_SrfRol, roll); + } + + /// + /// Engage SmartASS with current settings + /// + public static void EngageSmartASS(object smartass) + { + if (smartass == null || m_SmartASS_Engage == null) return; + try + { + m_SmartASS_Engage.Invoke(smartass, new object[] { true }); + } + catch { } + } + + public static string GetSmartASSAdvancedReferenceName(object smartass) + { + if (smartass == null || f_SmartASS_AdvReference == null) return "N/A"; + try + { + object val = f_SmartASS_AdvReference.GetValue(smartass); + return val != null ? val.ToString() : "N/A"; + } + catch { return "N/A"; } + } + + public static void CycleSmartASSAdvancedReference(object smartass, int direction) + { + if (smartass == null || f_SmartASS_AdvReference == null) return; + try + { + Type enumType = f_SmartASS_AdvReference.FieldType; + Array values = Enum.GetValues(enumType); + object current = f_SmartASS_AdvReference.GetValue(smartass); + int idx = Array.IndexOf(values, current); + if (idx < 0) idx = 0; + int next = (idx + direction + values.Length) % values.Length; + f_SmartASS_AdvReference.SetValue(smartass, values.GetValue(next)); + } + catch { } + } + + public static string GetSmartASSAdvancedDirectionName(object smartass) + { + if (smartass == null || f_SmartASS_AdvDirection == null) return "N/A"; + try + { + object val = f_SmartASS_AdvDirection.GetValue(smartass); + return val != null ? val.ToString() : "N/A"; + } + catch { return "N/A"; } + } + + public static void CycleSmartASSAdvancedDirection(object smartass, int direction) + { + if (smartass == null || f_SmartASS_AdvDirection == null) return; + try + { + Type enumType = f_SmartASS_AdvDirection.FieldType; + Array values = Enum.GetValues(enumType); + object current = f_SmartASS_AdvDirection.GetValue(smartass); + int idx = Array.IndexOf(values, current); + if (idx < 0) idx = 0; + int next = (idx + direction + values.Length) % values.Length; + f_SmartASS_AdvDirection.SetValue(smartass, values.GetValue(next)); + } + catch { } + } + #endregion + + #region Node Executor Methods + /// + /// Execute one maneuver node + /// + public static void ExecuteOneNode(object core, object controller) + { + object node = GetNodeExecutor(core); + if (node == null || m_NodeExecutor_ExecuteOneNode == null) return; + try + { + m_NodeExecutor_ExecuteOneNode.Invoke(node, new object[] { controller }); + } + catch { } + } + + /// + /// Execute all maneuver nodes + /// + public static void ExecuteAllNodes(object core, object controller) + { + object node = GetNodeExecutor(core); + if (node == null || m_NodeExecutor_ExecuteAllNodes == null) return; + try + { + m_NodeExecutor_ExecuteAllNodes.Invoke(node, new object[] { controller }); + } + catch { } + } + + /// + /// Abort node execution + /// + public static void AbortNode(object core) + { + object node = GetNodeExecutor(core); + if (node == null || m_NodeExecutor_Abort == null) return; + try + { + m_NodeExecutor_Abort.Invoke(node, null); + } + catch { } + } + + /// + /// Check if node executor is running + /// + public static bool IsNodeExecutorRunning(object core) + { + object node = GetNodeExecutor(core); + return GetModuleEnabled(node); + } + + /// + /// Get node executor autowarp setting + /// + public static bool GetNodeAutowarp(object core) + { + object node = GetNodeExecutor(core); + if (node == null || f_NodeExecutor_Autowarp == null) return false; + try + { + return (bool)f_NodeExecutor_Autowarp.GetValue(node); + } + catch + { + return false; + } + } + + /// + /// Set node executor autowarp setting + /// + public static void SetNodeAutowarp(object core, bool autowarp) + { + object node = GetNodeExecutor(core); + if (node == null || f_NodeExecutor_Autowarp == null) return; + try + { + f_NodeExecutor_Autowarp.SetValue(node, autowarp); + } + catch { } + } + + /// + /// Get node executor lead time + /// + public static double GetNodeLeadTime(object core) + { + object node = GetNodeExecutor(core); + if (node == null || f_NodeExecutor_LeadTime == null) return 3.0; + try + { + object editable = f_NodeExecutor_LeadTime.GetValue(node); + return GetEditableDouble(editable); + } + catch + { + return 3.0; + } + } + + /// + /// Set node executor lead time + /// + public static void SetNodeLeadTime(object core, double leadTime) + { + object node = GetNodeExecutor(core); + if (node == null || f_NodeExecutor_LeadTime == null) return; + try + { + object editable = f_NodeExecutor_LeadTime.GetValue(node); + SetEditableDouble(editable, leadTime); + } + catch { } + } + #endregion + + #region Target Controller Methods + /// + /// Check if a position target exists + /// + public static bool PositionTargetExists(object core) + { + object target = GetTargetController(core); + if (target == null || p_Target_PositionTargetExists == null) return false; + try + { + return (bool)p_Target_PositionTargetExists.GetValue(target, null); + } + catch + { + return false; + } + } + + /// + /// Check if a normal (vessel) target exists + /// + public static bool NormalTargetExists(object core) + { + object target = GetTargetController(core); + if (target == null || p_Target_NormalTargetExists == null) return false; + try + { + return (bool)p_Target_NormalTargetExists.GetValue(target, null); + } + catch + { + return false; + } + } + + /// + /// Get the target orbit + /// + public static Orbit GetTargetOrbit(object core) + { + object target = GetTargetController(core); + if (target == null || p_Target_TargetOrbit == null) return null; + try + { + return (Orbit)p_Target_TargetOrbit.GetValue(target, null); + } + catch + { + return null; + } + } + + /// + /// Get target latitude + /// + public static double GetTargetLatitude(object core) + { + object target = GetTargetController(core); + if (target == null || f_Target_TargetLatitude == null) return 0; + try + { + object latObj = f_Target_TargetLatitude.GetValue(target); + return GetEditableAngle(latObj); + } + catch + { + return 0; + } + } + + /// + /// Get target longitude + /// + public static double GetTargetLongitude(object core) + { + object target = GetTargetController(core); + if (target == null || f_Target_TargetLongitude == null) return 0; + try + { + object lonObj = f_Target_TargetLongitude.GetValue(target); + return GetEditableAngle(lonObj); + } + catch + { + return 0; + } + } + /// + /// Set target latitude using existing longitude + /// + public static void SetTargetLatitude(object core, CelestialBody body, double latitude) + { + if (body == null) return; + double lon = GetTargetLongitude(core); + SetPositionTarget(core, body, latitude, lon); + } + + /// + /// Set target longitude using existing latitude + /// + public static void SetTargetLongitude(object core, CelestialBody body, double longitude) + { + if (body == null) return; + double lat = GetTargetLatitude(core); + SetPositionTarget(core, body, lat, longitude); + } + + /// + /// Set position target + /// + public static void SetPositionTarget(object core, CelestialBody body, double latitude, double longitude) + { + object target = GetTargetController(core); + if (target == null || m_Target_SetPositionTarget == null) return; + try + { + m_Target_SetPositionTarget.Invoke(target, new object[] { body, latitude, longitude }); + } + catch { } + } + + /// + /// Open the map to pick a position target + /// + public static void PickPositionTargetOnMap(object core) + { + object target = GetTargetController(core); + if (target == null || m_Target_PickPositionTargetOnMap == null) return; + try + { + m_Target_PickPositionTargetOnMap.Invoke(target, null); + } + catch { } + } + #endregion + + #region Ascent Methods + /// + /// Get ascent settings module + /// + public static object GetAscentSettings(object core) + { + return GetComputerModule(core, "MechJebModuleAscentSettings"); + } + + /// + /// Get the ascent autopilot from settings + /// + public static object GetAscentAutopilot(object core) + { + object settings = GetAscentSettings(core); + if (settings == null || p_Ascent_AscentAutopilot == null) return null; + try + { + return p_Ascent_AscentAutopilot.GetValue(settings, null); + } + catch + { + return null; + } + } + + /// + /// Get desired orbit altitude (km) + /// + public static double GetAscentAltitude(object core) + { + object settings = GetAscentSettings(core); + if (settings == null || f_Ascent_DesiredOrbitAltitude == null) return 0; + return GetEditableDoubleMultField(settings, f_Ascent_DesiredOrbitAltitude); + } + + /// + /// Set desired orbit altitude (km) + /// + public static void SetAscentAltitude(object core, double altitude) + { + object settings = GetAscentSettings(core); + if (settings == null || f_Ascent_DesiredOrbitAltitude == null) return; + SetEditableDoubleMultField(settings, f_Ascent_DesiredOrbitAltitude, altitude); + } + + public static double GetAscentApoapsis(object core) + { + object settings = GetAscentSettings(core); + if (settings == null || f_Ascent_DesiredApoapsis == null) return 0; + return GetEditableDoubleMultField(settings, f_Ascent_DesiredApoapsis); + } + + public static void SetAscentApoapsis(object core, double value) + { + object settings = GetAscentSettings(core); + if (settings == null || f_Ascent_DesiredApoapsis == null) return; + SetEditableDoubleMultField(settings, f_Ascent_DesiredApoapsis, value); + } + + /// + /// Get desired inclination + /// + public static double GetAscentInclination(object core) + { + object settings = GetAscentSettings(core); + if (settings == null || f_Ascent_DesiredInclination == null) return 0; + return GetEditableDoubleField(settings, f_Ascent_DesiredInclination); + } + + /// + /// Set desired inclination + /// + public static void SetAscentInclination(object core, double inclination) + { + object settings = GetAscentSettings(core); + if (settings == null || f_Ascent_DesiredInclination == null) return; + SetEditableDoubleField(settings, f_Ascent_DesiredInclination, inclination); + } + + /// + /// Check if ascent autopilot is engaged + /// + public static bool IsAscentAutopilotEngaged(object core) + { + object autopilot = GetAscentAutopilot(core); + return GetModuleEnabled(autopilot); + } + + /// + /// Engage/disengage the ascent autopilot + /// + public static void SetAscentAutopilotEngaged(object core, bool engaged, object controller = null) + { + object autopilot = GetAscentAutopilot(core); + if (autopilot == null) return; + + object users = GetModuleUsers(autopilot); + if (users == null) return; + + if (controller == null) controller = core; + + if (engaged) + { + AddUser(users, controller); + } + else + { + RemoveUser(users, controller); + } + } + + /// + /// Get ascent autopilot status string + /// + public static string GetAscentStatus(object core) + { + object autopilot = GetAscentAutopilot(core); + if (autopilot == null || p_AscentAP_Status == null) return ""; + try + { + return (string)p_AscentAP_Status.GetValue(autopilot, null); + } + catch + { + return ""; + } + } + + /// + /// Get ascent autowarp setting + /// + public static bool GetAscentAutowarp(object core) + { + return GetNodeAutowarp(core); + } + + /// + /// Set ascent autowarp + /// + public static void SetAscentAutowarp(object core, bool autowarp) + { + SetNodeAutowarp(core, autowarp); + } + + public static bool GetAscentAutostage(object core) + { + object settings = GetAscentSettings(core); + if (settings == null) return false; + + try + { + if (p_Ascent_Autostage != null) + { + return (bool)p_Ascent_Autostage.GetValue(settings, null); + } + if (f_Ascent_Autostage != null) + { + return (bool)f_Ascent_Autostage.GetValue(settings); + } + } + catch { } + + return false; + } + + public static void SetAscentAutostage(object core, bool enabled) + { + object settings = GetAscentSettings(core); + if (settings == null) return; + + try + { + if (p_Ascent_Autostage != null) + { + p_Ascent_Autostage.SetValue(settings, enabled, null); + return; + } + if (f_Ascent_Autostage != null) + { + f_Ascent_Autostage.SetValue(settings, enabled); + } + } + catch { } + } + + public static bool GetAscentAutoPath(object core) + { + object settings = GetAscentSettings(core); + if (settings == null || f_Ascent_AutoPath == null) return false; + try + { + return (bool)f_Ascent_AutoPath.GetValue(settings); + } + catch { return false; } + } + + public static void SetAscentAutoPath(object core, bool enabled) + { + object settings = GetAscentSettings(core); + if (settings == null || f_Ascent_AutoPath == null) return; + try + { + f_Ascent_AutoPath.SetValue(settings, enabled); + } + catch { } + } + + public static double GetAscentTurnStartAltitude(object core) + { + object settings = GetAscentSettings(core); + if (settings == null || f_Ascent_TurnStartAltitude == null) return 0; + return GetEditableDoubleMultField(settings, f_Ascent_TurnStartAltitude); + } + + public static void SetAscentTurnStartAltitude(object core, double altitude) + { + object settings = GetAscentSettings(core); + if (settings == null || f_Ascent_TurnStartAltitude == null) return; + SetEditableDoubleMultField(settings, f_Ascent_TurnStartAltitude, altitude); + } + + public static double GetAscentTurnStartVelocity(object core) + { + object settings = GetAscentSettings(core); + if (settings == null || f_Ascent_TurnStartVelocity == null) return 0; + return GetEditableDoubleMultField(settings, f_Ascent_TurnStartVelocity); + } + + public static void SetAscentTurnStartVelocity(object core, double velocity) + { + object settings = GetAscentSettings(core); + if (settings == null || f_Ascent_TurnStartVelocity == null) return; + SetEditableDoubleMultField(settings, f_Ascent_TurnStartVelocity, velocity); + } + + public static double GetAscentTurnEndAltitude(object core) + { + object settings = GetAscentSettings(core); + if (settings == null || f_Ascent_TurnEndAltitude == null) return 0; + return GetEditableDoubleMultField(settings, f_Ascent_TurnEndAltitude); + } + + public static void SetAscentTurnEndAltitude(object core, double altitude) + { + object settings = GetAscentSettings(core); + if (settings == null || f_Ascent_TurnEndAltitude == null) return; + SetEditableDoubleMultField(settings, f_Ascent_TurnEndAltitude, altitude); + } + + public static double GetAscentTurnEndAngle(object core) + { + object settings = GetAscentSettings(core); + if (settings == null || f_Ascent_TurnEndAngle == null) return 0; + return GetEditableDoubleField(settings, f_Ascent_TurnEndAngle); + } + + public static void SetAscentTurnEndAngle(object core, double angle) + { + object settings = GetAscentSettings(core); + if (settings == null || f_Ascent_TurnEndAngle == null) return; + SetEditableDoubleField(settings, f_Ascent_TurnEndAngle, angle); + } + + public static double GetAscentTurnShapeExponent(object core) + { + object settings = GetAscentSettings(core); + if (settings == null || f_Ascent_TurnShapeExponent == null) return 0; + return GetEditableDoubleMultField(settings, f_Ascent_TurnShapeExponent); + } + + public static void SetAscentTurnShapeExponent(object core, double exponent) + { + object settings = GetAscentSettings(core); + if (settings == null || f_Ascent_TurnShapeExponent == null) return; + SetEditableDoubleMultField(settings, f_Ascent_TurnShapeExponent, exponent); + } + + public static double GetAscentAutoTurnPerc(object core) + { + object settings = GetAscentSettings(core); + if (settings == null || f_Ascent_AutoTurnPerc == null) return 0; + try + { + return (float)f_Ascent_AutoTurnPerc.GetValue(settings); + } + catch { return 0; } + } + + public static void SetAscentAutoTurnPerc(object core, double value) + { + object settings = GetAscentSettings(core); + if (settings == null || f_Ascent_AutoTurnPerc == null) return; + try + { + f_Ascent_AutoTurnPerc.SetValue(settings, (float)value); + } + catch { } + } + + public static double GetAscentAutoTurnSpdFactor(object core) + { + object settings = GetAscentSettings(core); + if (settings == null || f_Ascent_AutoTurnSpdFactor == null) return 0; + try + { + return (float)f_Ascent_AutoTurnSpdFactor.GetValue(settings); + } + catch { return 0; } + } + + public static void SetAscentAutoTurnSpdFactor(object core, double value) + { + object settings = GetAscentSettings(core); + if (settings == null || f_Ascent_AutoTurnSpdFactor == null) return; + try + { + f_Ascent_AutoTurnSpdFactor.SetValue(settings, (float)value); + } + catch { } + } + + public static double GetAscentLAN(object core) + { + object settings = GetAscentSettings(core); + if (settings == null || f_Ascent_DesiredLAN == null) return 0; + return GetEditableDoubleField(settings, f_Ascent_DesiredLAN); + } + + public static void SetAscentLAN(object core, double lan) + { + object settings = GetAscentSettings(core); + if (settings == null || f_Ascent_DesiredLAN == null) return; + SetEditableDoubleField(settings, f_Ascent_DesiredLAN, lan); + } + + public static double GetAscentLaunchPhaseAngle(object core) + { + object settings = GetAscentSettings(core); + if (settings == null || f_Ascent_LaunchPhaseAngle == null) return 0; + return GetEditableDoubleField(settings, f_Ascent_LaunchPhaseAngle); + } + + public static void SetAscentLaunchPhaseAngle(object core, double angle) + { + object settings = GetAscentSettings(core); + if (settings == null || f_Ascent_LaunchPhaseAngle == null) return; + SetEditableDoubleField(settings, f_Ascent_LaunchPhaseAngle, angle); + } + + public static double GetAscentLaunchLANDifference(object core) + { + object settings = GetAscentSettings(core); + if (settings == null || f_Ascent_LaunchLANDifference == null) return 0; + return GetEditableDoubleField(settings, f_Ascent_LaunchLANDifference); + } + + public static void SetAscentLaunchLANDifference(object core, double diff) + { + object settings = GetAscentSettings(core); + if (settings == null || f_Ascent_LaunchLANDifference == null) return; + SetEditableDoubleField(settings, f_Ascent_LaunchLANDifference, diff); + } + + public static int GetAscentWarpCountdown(object core) + { + object settings = GetAscentSettings(core); + if (settings == null || f_Ascent_WarpCountDown == null) return 0; + try + { + return GetEditableInt(f_Ascent_WarpCountDown.GetValue(settings)); + } + catch { return 0; } + } + + public static void SetAscentWarpCountdown(object core, int value) + { + object settings = GetAscentSettings(core); + if (settings == null || f_Ascent_WarpCountDown == null) return; + try + { + SetEditableInt(f_Ascent_WarpCountDown.GetValue(settings), value); + } + catch { } + } + + public static bool GetAscentSkipCircularization(object core) + { + object settings = GetAscentSettings(core); + if (settings == null || f_Ascent_SkipCircularization == null) return false; + try { return (bool)f_Ascent_SkipCircularization.GetValue(settings); } + catch { return false; } + } + + public static void SetAscentSkipCircularization(object core, bool value) + { + object settings = GetAscentSettings(core); + if (settings == null || f_Ascent_SkipCircularization == null) return; + try { f_Ascent_SkipCircularization.SetValue(settings, value); } + catch { } + } + + public static bool GetAscentForceRoll(object core) + { + object settings = GetAscentSettings(core); + if (settings == null || f_Ascent_ForceRoll == null) return false; + try { return (bool)f_Ascent_ForceRoll.GetValue(settings); } + catch { return false; } + } + + public static void SetAscentForceRoll(object core, bool value) + { + object settings = GetAscentSettings(core); + if (settings == null || f_Ascent_ForceRoll == null) return; + try { f_Ascent_ForceRoll.SetValue(settings, value); } + catch { } + } + + public static double GetAscentVerticalRoll(object core) + { + object settings = GetAscentSettings(core); + if (settings == null || f_Ascent_VerticalRoll == null) return 0; + return GetEditableDoubleField(settings, f_Ascent_VerticalRoll); + } + + public static void SetAscentVerticalRoll(object core, double value) + { + object settings = GetAscentSettings(core); + if (settings == null || f_Ascent_VerticalRoll == null) return; + SetEditableDoubleField(settings, f_Ascent_VerticalRoll, value); + } + + public static double GetAscentTurnRoll(object core) + { + object settings = GetAscentSettings(core); + if (settings == null || f_Ascent_TurnRoll == null) return 0; + return GetEditableDoubleField(settings, f_Ascent_TurnRoll); + } + + public static void SetAscentTurnRoll(object core, double value) + { + object settings = GetAscentSettings(core); + if (settings == null || f_Ascent_TurnRoll == null) return; + SetEditableDoubleField(settings, f_Ascent_TurnRoll, value); + } + + public static double GetAscentRollAltitude(object core) + { + object settings = GetAscentSettings(core); + if (settings == null || f_Ascent_RollAltitude == null) return 0; + return GetEditableDoubleField(settings, f_Ascent_RollAltitude); + } + + public static void SetAscentRollAltitude(object core, double value) + { + object settings = GetAscentSettings(core); + if (settings == null || f_Ascent_RollAltitude == null) return; + SetEditableDoubleField(settings, f_Ascent_RollAltitude, value); + } + + public static bool GetAscentLimitAoA(object core) + { + object settings = GetAscentSettings(core); + if (settings == null || f_Ascent_LimitAoA == null) return false; + try { return (bool)f_Ascent_LimitAoA.GetValue(settings); } + catch { return false; } + } + + public static void SetAscentLimitAoA(object core, bool value) + { + object settings = GetAscentSettings(core); + if (settings == null || f_Ascent_LimitAoA == null) return; + try { f_Ascent_LimitAoA.SetValue(settings, value); } + catch { } + } + + public static double GetAscentMaxAoA(object core) + { + object settings = GetAscentSettings(core); + if (settings == null || f_Ascent_MaxAoA == null) return 0; + return GetEditableDoubleField(settings, f_Ascent_MaxAoA); + } + + public static void SetAscentMaxAoA(object core, double value) + { + object settings = GetAscentSettings(core); + if (settings == null || f_Ascent_MaxAoA == null) return; + SetEditableDoubleField(settings, f_Ascent_MaxAoA, value); + } + + public static double GetAscentAoAFadeoutPressure(object core) + { + object settings = GetAscentSettings(core); + if (settings == null || f_Ascent_AOALimitFadeoutPressure == null) return 0; + return GetEditableDoubleField(settings, f_Ascent_AOALimitFadeoutPressure); + } + + public static void SetAscentAoAFadeoutPressure(object core, double value) + { + object settings = GetAscentSettings(core); + if (settings == null || f_Ascent_AOALimitFadeoutPressure == null) return; + SetEditableDoubleField(settings, f_Ascent_AOALimitFadeoutPressure, value); + } + + public static bool GetAscentCorrectiveSteering(object core) + { + object settings = GetAscentSettings(core); + if (settings == null || f_Ascent_CorrectiveSteering == null) return false; + try { return (bool)f_Ascent_CorrectiveSteering.GetValue(settings); } + catch { return false; } + } + + public static void SetAscentCorrectiveSteering(object core, bool value) + { + object settings = GetAscentSettings(core); + if (settings == null || f_Ascent_CorrectiveSteering == null) return; + try { f_Ascent_CorrectiveSteering.SetValue(settings, value); } + catch { } + } + + public static double GetAscentCorrectiveSteeringGain(object core) + { + object settings = GetAscentSettings(core); + if (settings == null || f_Ascent_CorrectiveSteeringGain == null) return 0; + return GetEditableDoubleField(settings, f_Ascent_CorrectiveSteeringGain); + } + + public static void SetAscentCorrectiveSteeringGain(object core, double value) + { + object settings = GetAscentSettings(core); + if (settings == null || f_Ascent_CorrectiveSteeringGain == null) return; + SetEditableDoubleField(settings, f_Ascent_CorrectiveSteeringGain, value); + } + #endregion + + #region Landing Methods + /// + /// Get landing autopilot module + /// + public static object GetLandingAutopilot(object core) + { + return GetComputerModule(core, "MechJebModuleLandingAutopilot"); + } + + /// + /// Get landing predictions module + /// + public static object GetLandingPredictions(object core) + { + return GetComputerModule(core, "MechJebModuleLandingPredictions"); + } + + /// + /// Start landing at position target + /// + public static void LandAtPositionTarget(object core, object controller = null) + { + object landing = GetLandingAutopilot(core); + if (landing == null || m_Landing_LandAtPositionTarget == null) return; + + if (controller == null) + { + controller = GetComputerModule(core, "MechJebModuleLandingGuidance"); + } + + try + { + m_Landing_LandAtPositionTarget.Invoke(landing, new object[] { controller }); + } + catch { } + } + + /// + /// Start landing untargeted + /// + public static void LandUntargeted(object core, object controller = null) + { + object landing = GetLandingAutopilot(core); + if (landing == null || m_Landing_LandUntargeted == null) return; + + if (controller == null) + { + controller = GetComputerModule(core, "MechJebModuleLandingGuidance"); + } + + try + { + m_Landing_LandUntargeted.Invoke(landing, new object[] { controller }); + } + catch { } + } + + /// + /// Stop landing + /// + public static void StopLanding(object core) + { + object landing = GetLandingAutopilot(core); + if (landing == null || m_Landing_StopLanding == null) return; + try + { + m_Landing_StopLanding.Invoke(landing, null); + } + catch { } + } + + /// + /// Check if landing autopilot is engaged + /// + public static bool IsLandingAutopilotEngaged(object core) + { + object landing = GetLandingAutopilot(core); + return GetModuleEnabled(landing); + } + + /// + /// Get landing prediction result + /// + public static object GetLandingPredictionResult(object core) + { + object predictions = GetLandingPredictions(core); + if (predictions == null || m_Predictions_GetResult == null) return null; + try + { + return m_Predictions_GetResult.Invoke(predictions, null); + } + catch + { + return null; + } + } + + /// + /// Get landing prediction outcome + /// + public static LandingOutcome GetLandingOutcome(object result) + { + if (result == null || f_Result_Outcome == null) return LandingOutcome.ERROR; + try + { + object outcome = f_Result_Outcome.GetValue(result); + string outcomeStr = outcome.ToString(); + LandingOutcome parsed; + if (Enum.TryParse(outcomeStr, out parsed)) + { + return parsed; + } + return LandingOutcome.ERROR; + } + catch + { + return LandingOutcome.ERROR; + } + } + + /// + /// Get landing prediction end position + /// + public static void GetLandingEndPosition(object result, out double latitude, out double longitude) + { + latitude = 0; + longitude = 0; + if (result == null || f_Result_EndPosition == null) return; + try + { + object endPos = f_Result_EndPosition.GetValue(result); + if (endPos != null && f_AbsoluteVector_Latitude != null && f_AbsoluteVector_Longitude != null) + { + object latObj = f_AbsoluteVector_Latitude.GetValue(endPos); + object lonObj = f_AbsoluteVector_Longitude.GetValue(endPos); + latitude = GetEditableAngle(latObj); + longitude = GetEditableAngle(lonObj); + } + } + catch { } + } + + /// + /// Get landing prediction end UT + /// + public static double GetLandingEndUT(object result) + { + if (result == null || f_Result_EndUT == null) return 0; + try + { + return (double)f_Result_EndUT.GetValue(result); + } + catch + { + return 0; + } + } + + /// + /// Get max drag gees from prediction + /// + public static double GetLandingMaxDragGees(object result) + { + if (result == null || f_Result_MaxDragGees == null) return 0; + try + { + return (double)f_Result_MaxDragGees.GetValue(result); + } + catch + { + return 0; + } + } + + public static bool GetLandingShowTrajectory(object core) + { + object predictions = GetLandingPredictions(core); + if (predictions == null || p_Predictions_ShowTrajectory == null) return false; + try + { + return (bool)p_Predictions_ShowTrajectory.GetValue(predictions, null); + } + catch + { + return false; + } + } + + public static void SetLandingShowTrajectory(object core, bool show) + { + object predictions = GetLandingPredictions(core); + if (predictions == null || p_Predictions_ShowTrajectory == null) return; + try + { + p_Predictions_ShowTrajectory.SetValue(predictions, show, null); + } + catch { } + } + + /// + /// Get touchdown speed setting + /// + public static double GetLandingTouchdownSpeed(object core) + { + object landing = GetLandingAutopilot(core); + if (landing == null || f_Landing_TouchdownSpeed == null) return 0; + return GetEditableDoubleField(landing, f_Landing_TouchdownSpeed); + } + + /// + /// Set touchdown speed + /// + public static void SetLandingTouchdownSpeed(object core, double speed) + { + object landing = GetLandingAutopilot(core); + if (landing == null || f_Landing_TouchdownSpeed == null) return; + SetEditableDoubleField(landing, f_Landing_TouchdownSpeed, speed); + } + + /// + /// Get deploy gears setting + /// + public static bool GetLandingDeployGears(object core) + { + object landing = GetLandingAutopilot(core); + if (landing == null || f_Landing_DeployGears == null) return false; + try + { + return (bool)f_Landing_DeployGears.GetValue(landing); + } + catch + { + return false; + } + } + + /// + /// Set deploy gears + /// + public static void SetLandingDeployGears(object core, bool deploy) + { + object landing = GetLandingAutopilot(core); + if (landing == null || f_Landing_DeployGears == null) return; + try + { + f_Landing_DeployGears.SetValue(landing, deploy); + } + catch { } + } + + /// + /// Get deploy chutes setting + /// + public static bool GetLandingDeployChutes(object core) + { + object landing = GetLandingAutopilot(core); + if (landing == null || f_Landing_DeployChutes == null) return false; + try + { + return (bool)f_Landing_DeployChutes.GetValue(landing); + } + catch + { + return false; + } + } + + /// + /// Set deploy chutes + /// + public static void SetLandingDeployChutes(object core, bool deploy) + { + object landing = GetLandingAutopilot(core); + if (landing == null || f_Landing_DeployChutes == null) return; + try + { + f_Landing_DeployChutes.SetValue(landing, deploy); + } + catch { } + } + + public static int GetLandingLimitGearsStage(object core) + { + object landing = GetLandingAutopilot(core); + if (landing == null || f_Landing_LimitGearsStage == null) return 0; + try { return GetEditableInt(f_Landing_LimitGearsStage.GetValue(landing)); } + catch { return 0; } + } + + public static void SetLandingLimitGearsStage(object core, int value) + { + object landing = GetLandingAutopilot(core); + if (landing == null || f_Landing_LimitGearsStage == null) return; + try { SetEditableInt(f_Landing_LimitGearsStage.GetValue(landing), value); } + catch { } + } + + public static int GetLandingLimitChutesStage(object core) + { + object landing = GetLandingAutopilot(core); + if (landing == null || f_Landing_LimitChutesStage == null) return 0; + try { return GetEditableInt(f_Landing_LimitChutesStage.GetValue(landing)); } + catch { return 0; } + } + + public static void SetLandingLimitChutesStage(object core, int value) + { + object landing = GetLandingAutopilot(core); + if (landing == null || f_Landing_LimitChutesStage == null) return; + try { SetEditableInt(f_Landing_LimitChutesStage.GetValue(landing), value); } + catch { } + } + + public static bool GetLandingUseRCS(object core) + { + object landing = GetLandingAutopilot(core); + if (landing == null || f_Landing_UseRCS == null) return false; + try { return (bool)f_Landing_UseRCS.GetValue(landing); } + catch { return false; } + } + + public static void SetLandingUseRCS(object core, bool value) + { + object landing = GetLandingAutopilot(core); + if (landing == null || f_Landing_UseRCS == null) return; + try { f_Landing_UseRCS.SetValue(landing, value); } + catch { } + } + #endregion + + #region Stage Stats Methods + /// + /// Get the stage stats module + /// + public static object GetStageStats(object core) + { + return GetComputerModule(core, "MechJebModuleStageStats"); + } + + /// + /// Request an update of stage stats + /// + public static void RequestStageStatsUpdate(object core, object controller = null) + { + object stats = GetStageStats(core); + if (stats == null || m_StageStats_RequestUpdate == null) return; + try + { + m_StageStats_RequestUpdate.Invoke(stats, new object[] { controller, false }); + } + catch { } + } + + /// + /// Get vacuum stage stats list + /// + public static System.Collections.IList GetVacuumStageStats(object core) + { + object stats = GetStageStats(core); + if (stats == null || f_StageStats_VacStats == null) return null; + try + { + return f_StageStats_VacStats.GetValue(stats) as System.Collections.IList; + } + catch + { + return null; + } + } + + /// + /// Get atmospheric stage stats list + /// + public static System.Collections.IList GetAtmoStageStats(object core) + { + object stats = GetStageStats(core); + if (stats == null || f_StageStats_AtmoStats == null) return null; + try + { + return f_StageStats_AtmoStats.GetValue(stats) as System.Collections.IList; + } + catch + { + return null; + } + } + + /// + /// Get delta-V for a specific stage + /// + public static double GetStageDeltaV(object fuelStats) + { + if (fuelStats == null || f_FuelStats_DeltaV == null) return 0; + try + { + return (double)f_FuelStats_DeltaV.GetValue(fuelStats); + } + catch + { + return 0; + } + } + + /// + /// Get total vacuum delta-V + /// + public static double GetTotalVacuumDeltaV(object core) + { + var stats = GetVacuumStageStats(core); + if (stats == null) return 0; + + double total = 0; + foreach (var stage in stats) + { + total += GetStageDeltaV(stage); + } + return total; + } + + /// + /// Get total atmospheric delta-V + /// + public static double GetTotalAtmoDeltaV(object core) + { + var stats = GetAtmoStageStats(core); + if (stats == null) return 0; + + double total = 0; + foreach (var stage in stats) + { + total += GetStageDeltaV(stage); + } + return total; + } + #endregion + + #region Thrust Controller Methods + /// + /// Get limit to prevent overheats + /// + public static bool GetLimitToPreventOverheats(object core) + { + object thrust = GetThrustController(core); + if (thrust == null || f_Thrust_LimitToPreventOverheats == null) return false; + try + { + return (bool)f_Thrust_LimitToPreventOverheats.GetValue(thrust); + } + catch + { + return false; + } + } + + /// + /// Set limit to prevent overheats + /// + public static void SetLimitToPreventOverheats(object core, bool limit) + { + object thrust = GetThrustController(core); + if (thrust == null || f_Thrust_LimitToPreventOverheats == null) return; + try + { + f_Thrust_LimitToPreventOverheats.SetValue(thrust, limit); + } + catch { } + } + + /// + /// Get limit to max dynamic pressure + /// + public static bool GetLimitToMaxDynamicPressure(object core) + { + object thrust = GetThrustController(core); + if (thrust == null || f_Thrust_LimitToMaxDynamicPressure == null) return false; + try + { + return (bool)f_Thrust_LimitToMaxDynamicPressure.GetValue(thrust); + } + catch + { + return false; + } + } + + /// + /// Set limit to max dynamic pressure + /// + public static void SetLimitToMaxDynamicPressure(object core, bool limit) + { + object thrust = GetThrustController(core); + if (thrust == null || f_Thrust_LimitToMaxDynamicPressure == null) return; + try + { + f_Thrust_LimitToMaxDynamicPressure.SetValue(thrust, limit); + } + catch { } + } + + /// + /// Get max dynamic pressure value + /// + public static double GetMaxDynamicPressure(object core) + { + object thrust = GetThrustController(core); + if (thrust == null || f_Thrust_MaxDynamicPressure == null) return 0; + return GetEditableDoubleField(thrust, f_Thrust_MaxDynamicPressure); + } + + /// + /// Set max dynamic pressure value + /// + public static void SetMaxDynamicPressure(object core, double pressure) + { + object thrust = GetThrustController(core); + if (thrust == null || f_Thrust_MaxDynamicPressure == null) return; + SetEditableDoubleField(thrust, f_Thrust_MaxDynamicPressure, pressure); + } + + /// + /// Get limit acceleration + /// + public static bool GetLimitAcceleration(object core) + { + object thrust = GetThrustController(core); + if (thrust == null || f_Thrust_LimitAcceleration == null) return false; + try + { + return (bool)f_Thrust_LimitAcceleration.GetValue(thrust); + } + catch + { + return false; + } + } + + /// + /// Set limit acceleration + /// + public static void SetLimitAcceleration(object core, bool limit) + { + object thrust = GetThrustController(core); + if (thrust == null || f_Thrust_LimitAcceleration == null) return; + try + { + f_Thrust_LimitAcceleration.SetValue(thrust, limit); + } + catch { } + } + + /// + /// Get max acceleration value + /// + public static double GetMaxAcceleration(object core) + { + object thrust = GetThrustController(core); + if (thrust == null || f_Thrust_MaxAcceleration == null) return 0; + return GetEditableDoubleField(thrust, f_Thrust_MaxAcceleration); + } + + /// + /// Set max acceleration value + /// + public static void SetMaxAcceleration(object core, double accel) + { + object thrust = GetThrustController(core); + if (thrust == null || f_Thrust_MaxAcceleration == null) return; + SetEditableDoubleField(thrust, f_Thrust_MaxAcceleration, accel); + } + + public static bool GetLimitToTerminalVelocity(object core) + { + object thrust = GetThrustController(core); + if (thrust == null || f_Thrust_LimitToTerminalVelocity == null) return false; + try { return (bool)f_Thrust_LimitToTerminalVelocity.GetValue(thrust); } + catch { return false; } + } + + public static void SetLimitToTerminalVelocity(object core, bool limit) + { + object thrust = GetThrustController(core); + if (thrust == null || f_Thrust_LimitToTerminalVelocity == null) return; + try { f_Thrust_LimitToTerminalVelocity.SetValue(thrust, limit); } + catch { } + } + + public static bool GetLimitToPreventFlameout(object core) + { + object thrust = GetThrustController(core); + if (thrust == null || f_Thrust_LimitToPreventFlameout == null) return false; + try { return (bool)f_Thrust_LimitToPreventFlameout.GetValue(thrust); } + catch { return false; } + } + + public static void SetLimitToPreventFlameout(object core, bool limit) + { + object thrust = GetThrustController(core); + if (thrust == null || f_Thrust_LimitToPreventFlameout == null) return; + try { f_Thrust_LimitToPreventFlameout.SetValue(thrust, limit); } + catch { } + } + + public static double GetFlameoutSafetyPct(object core) + { + object thrust = GetThrustController(core); + if (thrust == null || f_Thrust_FlameoutSafetyPct == null) return 0; + return GetEditableDoubleField(thrust, f_Thrust_FlameoutSafetyPct); + } + + public static void SetFlameoutSafetyPct(object core, double value) + { + object thrust = GetThrustController(core); + if (thrust == null || f_Thrust_FlameoutSafetyPct == null) return; + SetEditableDoubleField(thrust, f_Thrust_FlameoutSafetyPct, value); + } + + public static bool GetSmoothThrottle(object core) + { + object thrust = GetThrustController(core); + if (thrust == null || f_Thrust_SmoothThrottle == null) return false; + try { return (bool)f_Thrust_SmoothThrottle.GetValue(thrust); } + catch { return false; } + } + + public static void SetSmoothThrottle(object core, bool value) + { + object thrust = GetThrustController(core); + if (thrust == null || f_Thrust_SmoothThrottle == null) return; + try { f_Thrust_SmoothThrottle.SetValue(thrust, value); } + catch { } + } + + public static bool GetManageIntakes(object core) + { + object thrust = GetThrustController(core); + if (thrust == null || f_Thrust_ManageIntakes == null) return false; + try { return (bool)f_Thrust_ManageIntakes.GetValue(thrust); } + catch { return false; } + } + + public static void SetManageIntakes(object core, bool value) + { + object thrust = GetThrustController(core); + if (thrust == null || f_Thrust_ManageIntakes == null) return; + try { f_Thrust_ManageIntakes.SetValue(thrust, value); } + catch { } + } + + public static bool GetDifferentialThrottle(object core) + { + object thrust = GetThrustController(core); + if (thrust == null || f_Thrust_DifferentialThrottle == null) return false; + try { return (bool)f_Thrust_DifferentialThrottle.GetValue(thrust); } + catch { return false; } + } + + public static void SetDifferentialThrottle(object core, bool value) + { + object thrust = GetThrustController(core); + if (thrust == null || f_Thrust_DifferentialThrottle == null) return; + try { f_Thrust_DifferentialThrottle.SetValue(thrust, value); } + catch { } + } + + /// + /// Get limit throttle + /// + public static bool GetLimitThrottle(object core) + { + object thrust = GetThrustController(core); + if (thrust == null || f_Thrust_LimitThrottle == null) return false; + try + { + return (bool)f_Thrust_LimitThrottle.GetValue(thrust); + } + catch + { + return false; + } + } + + /// + /// Set limit throttle + /// + public static void SetLimitThrottle(object core, bool limit) + { + object thrust = GetThrustController(core); + if (thrust == null || f_Thrust_LimitThrottle == null) return; + try + { + f_Thrust_LimitThrottle.SetValue(thrust, limit); + } + catch { } + } + + /// + /// Get max throttle value + /// + public static double GetMaxThrottle(object core) + { + object thrust = GetThrustController(core); + if (thrust == null || f_Thrust_MaxThrottle == null) return 100; + return GetEditableDoubleField(thrust, f_Thrust_MaxThrottle); + } + + /// + /// Set max throttle value + /// + public static void SetMaxThrottle(object core, double throttle) + { + object thrust = GetThrustController(core); + if (thrust == null || f_Thrust_MaxThrottle == null) return; + SetEditableDoubleField(thrust, f_Thrust_MaxThrottle, throttle); + } + #endregion + + #region Staging Controller Methods + /// + /// Get autostage enabled + /// + public static bool GetAutostage(object core) + { + object staging = GetStagingController(core); + if (staging == null || f_Staging_Autostage == null) return false; + try + { + return (bool)f_Staging_Autostage.GetValue(staging); + } + catch + { + return false; + } + } + + /// + /// Set autostage enabled + /// + public static void SetAutostage(object core, bool autostage) + { + object staging = GetStagingController(core); + if (staging == null || f_Staging_Autostage == null) return; + try + { + f_Staging_Autostage.SetValue(staging, autostage); + } + catch { } + } + + /// + /// Get autostage limit + /// + public static int GetAutostageLimit(object core) + { + object staging = GetStagingController(core); + if (staging == null || f_Staging_AutostageLimit == null) return 0; + return GetEditableInt(f_Staging_AutostageLimit.GetValue(staging)); + } + + /// + /// Set autostage limit + /// + public static void SetAutostageLimit(object core, int limit) + { + object staging = GetStagingController(core); + if (staging == null || f_Staging_AutostageLimit == null) return; + SetEditableInt(f_Staging_AutostageLimit.GetValue(staging), limit); + } + + public static double GetAutostagePreDelay(object core) + { + object staging = GetStagingController(core); + if (staging == null || f_Staging_AutostagePreDelay == null) return 0; + return GetEditableDoubleField(staging, f_Staging_AutostagePreDelay); + } + + public static void SetAutostagePreDelay(object core, double value) + { + object staging = GetStagingController(core); + if (staging == null || f_Staging_AutostagePreDelay == null) return; + SetEditableDoubleField(staging, f_Staging_AutostagePreDelay, value); + } + + public static double GetAutostagePostDelay(object core) + { + object staging = GetStagingController(core); + if (staging == null || f_Staging_AutostagePostDelay == null) return 0; + return GetEditableDoubleField(staging, f_Staging_AutostagePostDelay); + } + + public static void SetAutostagePostDelay(object core, double value) + { + object staging = GetStagingController(core); + if (staging == null || f_Staging_AutostagePostDelay == null) return; + SetEditableDoubleField(staging, f_Staging_AutostagePostDelay, value); + } + + public static double GetClampAutoStageThrustPct(object core) + { + object staging = GetStagingController(core); + if (staging == null || f_Staging_ClampAutoStageThrustPct == null) return 0; + return GetEditableDoubleField(staging, f_Staging_ClampAutoStageThrustPct); + } + + public static void SetClampAutoStageThrustPct(object core, double value) + { + object staging = GetStagingController(core); + if (staging == null || f_Staging_ClampAutoStageThrustPct == null) return; + SetEditableDoubleField(staging, f_Staging_ClampAutoStageThrustPct, value); + } + + public static double GetFairingMaxAerothermalFlux(object core) + { + object staging = GetStagingController(core); + if (staging == null || f_Staging_FairingMaxAerothermalFlux == null) return 0; + return GetEditableDoubleField(staging, f_Staging_FairingMaxAerothermalFlux); + } + + public static void SetFairingMaxAerothermalFlux(object core, double value) + { + object staging = GetStagingController(core); + if (staging == null || f_Staging_FairingMaxAerothermalFlux == null) return; + SetEditableDoubleField(staging, f_Staging_FairingMaxAerothermalFlux, value); + } + + public static double GetFairingMaxDynamicPressure(object core) + { + object staging = GetStagingController(core); + if (staging == null || f_Staging_FairingMaxDynamicPressure == null) return 0; + return GetEditableDoubleField(staging, f_Staging_FairingMaxDynamicPressure); + } + + public static void SetFairingMaxDynamicPressure(object core, double value) + { + object staging = GetStagingController(core); + if (staging == null || f_Staging_FairingMaxDynamicPressure == null) return; + SetEditableDoubleField(staging, f_Staging_FairingMaxDynamicPressure, value); + } + + public static double GetFairingMinAltitude(object core) + { + object staging = GetStagingController(core); + if (staging == null || f_Staging_FairingMinAltitude == null) return 0; + return GetEditableDoubleField(staging, f_Staging_FairingMinAltitude); + } + + public static void SetFairingMinAltitude(object core, double value) + { + object staging = GetStagingController(core); + if (staging == null || f_Staging_FairingMinAltitude == null) return; + SetEditableDoubleField(staging, f_Staging_FairingMinAltitude, value); + } + + public static double GetHotStagingLeadTime(object core) + { + object staging = GetStagingController(core); + if (staging == null || f_Staging_HotStagingLeadTime == null) return 0; + return GetEditableDoubleField(staging, f_Staging_HotStagingLeadTime); + } + + public static void SetHotStagingLeadTime(object core, double value) + { + object staging = GetStagingController(core); + if (staging == null || f_Staging_HotStagingLeadTime == null) return; + SetEditableDoubleField(staging, f_Staging_HotStagingLeadTime, value); + } + + public static bool GetDropSolids(object core) + { + object staging = GetStagingController(core); + if (staging == null || f_Staging_DropSolids == null) return false; + try { return (bool)f_Staging_DropSolids.GetValue(staging); } + catch { return false; } + } + + public static void SetDropSolids(object core, bool value) + { + object staging = GetStagingController(core); + if (staging == null || f_Staging_DropSolids == null) return; + try { f_Staging_DropSolids.SetValue(staging, value); } + catch { } + } + + public static double GetDropSolidsLeadTime(object core) + { + object staging = GetStagingController(core); + if (staging == null || f_Staging_DropSolidsLeadTime == null) return 0; + return GetEditableDoubleField(staging, f_Staging_DropSolidsLeadTime); + } + + public static void SetDropSolidsLeadTime(object core, double value) + { + object staging = GetStagingController(core); + if (staging == null || f_Staging_DropSolidsLeadTime == null) return; + SetEditableDoubleField(staging, f_Staging_DropSolidsLeadTime, value); + } + + /// + /// Autostage once + /// + public static void AutostageOnce(object core, object controller = null) + { + object staging = GetStagingController(core); + if (staging == null || m_Staging_AutostageOnce == null) return; + try + { + m_Staging_AutostageOnce.Invoke(staging, new object[] { controller }); + } + catch { } + } + #endregion + + #region Docking Methods + /// + /// Get docking autopilot module + /// + public static object GetDockingAutopilot(object core) + { + return GetComputerModule(core, "MechJebModuleDockingAutopilot"); + } + + /// + /// Get docking guidance module + /// + public static object GetDockingGuidance(object core) + { + return GetComputerModule(core, "MechJebModuleDockingGuidance"); + } + + /// + /// Check if docking autopilot is engaged + /// + public static bool IsDockingAutopilotEngaged(object core) + { + object docking = GetDockingAutopilot(core); + return GetModuleEnabled(docking); + } + + /// + /// Set docking autopilot engaged + /// + public static void SetDockingAutopilotEngaged(object core, bool engaged) + { + object docking = GetDockingAutopilot(core); + if (docking == null) return; + + SetModuleEnabled(docking, engaged); + } + + /// + /// Get docking speed limit + /// + public static double GetDockingSpeedLimit(object core) + { + object docking = GetDockingAutopilot(core); + if (docking == null || f_Docking_SpeedLimit == null) return 0; + return GetEditableDoubleField(docking, f_Docking_SpeedLimit); + } + + /// + /// Set docking speed limit + /// + public static void SetDockingSpeedLimit(object core, double speed) + { + object docking = GetDockingAutopilot(core); + if (docking == null || f_Docking_SpeedLimit == null) return; + SetEditableDoubleField(docking, f_Docking_SpeedLimit, speed); + } + + /// + /// Get docking status + /// + public static string GetDockingStatus(object core) + { + object docking = GetDockingAutopilot(core); + if (docking == null || p_Docking_Status == null) return ""; + try + { + return (string)p_Docking_Status.GetValue(docking, null); + } + catch + { + return ""; + } + } + + public static bool GetDockingForceRoll(object core) + { + object docking = GetDockingAutopilot(core); + if (docking == null || f_Docking_ForceRoll == null) return false; + try { return (bool)f_Docking_ForceRoll.GetValue(docking); } + catch { return false; } + } + + public static void SetDockingForceRoll(object core, bool value) + { + object docking = GetDockingAutopilot(core); + if (docking == null || f_Docking_ForceRoll == null) return; + try { f_Docking_ForceRoll.SetValue(docking, value); } + catch { } + } + + public static double GetDockingRoll(object core) + { + object docking = GetDockingAutopilot(core); + if (docking == null || f_Docking_Roll == null) return 0; + return GetEditableDoubleField(docking, f_Docking_Roll); + } + + public static void SetDockingRoll(object core, double value) + { + object docking = GetDockingAutopilot(core); + if (docking == null || f_Docking_Roll == null) return; + SetEditableDoubleField(docking, f_Docking_Roll, value); + } + + public static bool GetDockingOverrideSafeDistance(object core) + { + object docking = GetDockingAutopilot(core); + if (docking == null || f_Docking_OverrideSafeDistance == null) return false; + try { return (bool)f_Docking_OverrideSafeDistance.GetValue(docking); } + catch { return false; } + } + + public static void SetDockingOverrideSafeDistance(object core, bool value) + { + object docking = GetDockingAutopilot(core); + if (docking == null || f_Docking_OverrideSafeDistance == null) return; + try { f_Docking_OverrideSafeDistance.SetValue(docking, value); } + catch { } + } + + public static double GetDockingOverridenSafeDistance(object core) + { + object docking = GetDockingAutopilot(core); + if (docking == null || f_Docking_OverridenSafeDistance == null) return 0; + return GetEditableDoubleField(docking, f_Docking_OverridenSafeDistance); + } + + public static void SetDockingOverridenSafeDistance(object core, double value) + { + object docking = GetDockingAutopilot(core); + if (docking == null || f_Docking_OverridenSafeDistance == null) return; + SetEditableDoubleField(docking, f_Docking_OverridenSafeDistance, value); + } + + public static bool GetDockingOverrideTargetSize(object core) + { + object docking = GetDockingAutopilot(core); + if (docking == null || f_Docking_OverrideTargetSize == null) return false; + try { return (bool)f_Docking_OverrideTargetSize.GetValue(docking); } + catch { return false; } + } + + public static void SetDockingOverrideTargetSize(object core, bool value) + { + object docking = GetDockingAutopilot(core); + if (docking == null || f_Docking_OverrideTargetSize == null) return; + try { f_Docking_OverrideTargetSize.SetValue(docking, value); } + catch { } + } + + public static double GetDockingOverridenTargetSize(object core) + { + object docking = GetDockingAutopilot(core); + if (docking == null || f_Docking_OverridenTargetSize == null) return 0; + return GetEditableDoubleField(docking, f_Docking_OverridenTargetSize); + } + + public static void SetDockingOverridenTargetSize(object core, double value) + { + object docking = GetDockingAutopilot(core); + if (docking == null || f_Docking_OverridenTargetSize == null) return; + SetEditableDoubleField(docking, f_Docking_OverridenTargetSize, value); + } + + public static bool GetDockingDrawBoundingBox(object core) + { + object docking = GetDockingAutopilot(core); + if (docking == null || f_Docking_DrawBoundingBox == null) return false; + try { return (bool)f_Docking_DrawBoundingBox.GetValue(docking); } + catch { return false; } + } + + public static void SetDockingDrawBoundingBox(object core, bool value) + { + object docking = GetDockingAutopilot(core); + if (docking == null || f_Docking_DrawBoundingBox == null) return; + try { f_Docking_DrawBoundingBox.SetValue(docking, value); } + catch { } + } + #endregion + + #region Rendezvous Methods + /// + /// Get rendezvous autopilot module + /// + public static object GetRendezvousAutopilot(object core) + { + return GetComputerModule(core, "MechJebModuleRendezvousAutopilot"); + } + + /// + /// Get rendezvous guidance module + /// + public static object GetRendezvousGuidance(object core) + { + return GetComputerModule(core, "MechJebModuleRendezvousGuidance"); + } + + /// + /// Check if rendezvous autopilot is engaged + /// + public static bool IsRendezvousAutopilotEngaged(object core) + { + object rendezvous = GetRendezvousAutopilot(core); + return GetModuleEnabled(rendezvous); + } + + /// + /// Set rendezvous autopilot engaged + /// + public static void SetRendezvousAutopilotEngaged(object core, bool engaged, object controller = null) + { + object rendezvous = GetRendezvousAutopilot(core); + if (rendezvous == null) return; + + object users = GetModuleUsers(rendezvous); + if (users == null) return; + + if (controller == null) + { + controller = GetComputerModule(core, "MechJebModuleRendezvousAutopilotWindow"); + } + + if (controller == null) return; + + if (engaged) + { + AddUser(users, controller); + } + else + { + RemoveUser(users, controller); + } + } + + /// + /// Get desired distance + /// + public static double GetRendezvousDesiredDistance(object core) + { + object rendezvous = GetRendezvousAutopilot(core); + if (rendezvous == null || f_Rendezvous_DesiredDistance == null) return 0; + return GetEditableDoubleField(rendezvous, f_Rendezvous_DesiredDistance); + } + + /// + /// Set desired distance + /// + public static void SetRendezvousDesiredDistance(object core, double distance) + { + object rendezvous = GetRendezvousAutopilot(core); + if (rendezvous == null || f_Rendezvous_DesiredDistance == null) return; + SetEditableDoubleField(rendezvous, f_Rendezvous_DesiredDistance, distance); + } + + /// + /// Get rendezvous status + /// + public static string GetRendezvousStatus(object core) + { + object rendezvous = GetRendezvousAutopilot(core); + if (rendezvous == null || p_Rendezvous_Status == null) return ""; + try + { + return (string)p_Rendezvous_Status.GetValue(rendezvous, null); + } + catch + { + return ""; + } + } + + public static int GetRendezvousMaxPhasingOrbits(object core) + { + object rendezvous = GetRendezvousAutopilot(core); + if (rendezvous == null || f_Rendezvous_MaxPhasingOrbits == null) return 0; + try { return GetEditableInt(f_Rendezvous_MaxPhasingOrbits.GetValue(rendezvous)); } + catch { return 0; } + } + + public static void SetRendezvousMaxPhasingOrbits(object core, int value) + { + object rendezvous = GetRendezvousAutopilot(core); + if (rendezvous == null || f_Rendezvous_MaxPhasingOrbits == null) return; + try { SetEditableInt(f_Rendezvous_MaxPhasingOrbits.GetValue(rendezvous), value); } + catch { } + } + + public static double GetRendezvousMaxClosingSpeed(object core) + { + object rendezvous = GetRendezvousAutopilot(core); + if (rendezvous == null || f_Rendezvous_MaxClosingSpeed == null) return 0; + return GetEditableDoubleField(rendezvous, f_Rendezvous_MaxClosingSpeed); + } + + public static void SetRendezvousMaxClosingSpeed(object core, double value) + { + object rendezvous = GetRendezvousAutopilot(core); + if (rendezvous == null || f_Rendezvous_MaxClosingSpeed == null) return; + SetEditableDoubleField(rendezvous, f_Rendezvous_MaxClosingSpeed, value); + } + #endregion + + #region Translatron Methods + /// + /// Get translatron module + /// + public static object GetTranslatron(object core) + { + return GetComputerModule(core, "MechJebModuleTranslatron"); + } + + /// + /// Set translatron mode + /// + public static void SetTranslatronMode(object core, TranslatronMode mode) + { + object translatron = GetTranslatron(core); + if (translatron == null || m_Translatron_SetMode == null) return; + try + { + object mjMode = Enum.ToObject(t_TranslatronMode, (int)mode); + m_Translatron_SetMode.Invoke(translatron, new object[] { mjMode }); + } + catch { } + } + + /// + /// Get translatron speed + /// + public static double GetTranslatronSpeed(object core) + { + object translatron = GetTranslatron(core); + if (translatron == null || p_Translatron_TransSpd == null) return 0; + try + { + object spd = p_Translatron_TransSpd.GetValue(translatron, null); + return GetEditableDouble(spd); + } + catch + { + return 0; + } + } + + /// + /// Set translatron speed + /// + public static void SetTranslatronSpeed(object core, double speed) + { + object translatron = GetTranslatron(core); + if (translatron == null || p_Translatron_TransSpd == null) return; + try + { + object spd = p_Translatron_TransSpd.GetValue(translatron, null); + SetEditableDouble(spd, speed); + } + catch { } + } + + /// + /// Get kill horizontal velocity setting + /// + public static bool GetTranslatronKillH(object core) + { + object translatron = GetTranslatron(core); + if (translatron == null || p_Translatron_TransKillH == null) return false; + try + { + return (bool)p_Translatron_TransKillH.GetValue(translatron, null); + } + catch + { + return false; + } + } + + /// + /// Set kill horizontal velocity + /// + public static void SetTranslatronKillH(object core, bool killH) + { + object translatron = GetTranslatron(core); + if (translatron == null || p_Translatron_TransKillH == null) return; + try + { + p_Translatron_TransKillH.SetValue(translatron, killH, null); + } + catch { } + } + + /// + /// Trigger PANIC button + /// + public static void PanicSwitch(object core) + { + object translatron = GetTranslatron(core); + if (translatron == null || m_Translatron_PanicSwitch == null) return; + try + { + m_Translatron_PanicSwitch.Invoke(translatron, null); + } + catch { } + } + #endregion + + #region Rover Methods + public static object GetRoverController(object core) + { + return GetComputerModule(core, "MechJebModuleRoverController"); + } + + public static bool GetRoverControlHeading(object core) + { + object rover = GetRoverController(core); + if (rover == null || f_Rover_ControlHeading == null) return false; + try { return (bool)f_Rover_ControlHeading.GetValue(rover); } + catch { return false; } + } + + public static void SetRoverControlHeading(object core, bool enabled) + { + object rover = GetRoverController(core); + if (rover == null || f_Rover_ControlHeading == null) return; + try { f_Rover_ControlHeading.SetValue(rover, enabled); } + catch { } + } + + public static bool GetRoverControlSpeed(object core) + { + object rover = GetRoverController(core); + if (rover == null || f_Rover_ControlSpeed == null) return false; + try { return (bool)f_Rover_ControlSpeed.GetValue(rover); } + catch { return false; } + } + + public static void SetRoverControlSpeed(object core, bool enabled) + { + object rover = GetRoverController(core); + if (rover == null || f_Rover_ControlSpeed == null) return; + try { f_Rover_ControlSpeed.SetValue(rover, enabled); } + catch { } + } + + public static double GetRoverHeading(object core) + { + object rover = GetRoverController(core); + if (rover == null || f_Rover_Heading == null) return 0; + return GetEditableDoubleField(rover, f_Rover_Heading); + } + + public static void SetRoverHeading(object core, double heading) + { + object rover = GetRoverController(core); + if (rover == null || f_Rover_Heading == null) return; + SetEditableDoubleField(rover, f_Rover_Heading, heading); + } + + public static double GetRoverSpeed(object core) + { + object rover = GetRoverController(core); + if (rover == null || f_Rover_Speed == null) return 0; + return GetEditableDoubleField(rover, f_Rover_Speed); + } + + public static void SetRoverSpeed(object core, double speed) + { + object rover = GetRoverController(core); + if (rover == null || f_Rover_Speed == null) return; + SetEditableDoubleField(rover, f_Rover_Speed, speed); + } + + public static bool GetRoverStabilityControl(object core) + { + object rover = GetRoverController(core); + if (rover == null || f_Rover_StabilityControl == null) return false; + try { return (bool)f_Rover_StabilityControl.GetValue(rover); } + catch { return false; } + } + + public static void SetRoverStabilityControl(object core, bool enabled) + { + object rover = GetRoverController(core); + if (rover == null || f_Rover_StabilityControl == null) return; + try { f_Rover_StabilityControl.SetValue(rover, enabled); } + catch { } + } + + public static bool GetRoverBrakeOnEject(object core) + { + object rover = GetRoverController(core); + if (rover == null || f_Rover_BrakeOnEject == null) return false; + try { return (bool)f_Rover_BrakeOnEject.GetValue(rover); } + catch { return false; } + } + + public static void SetRoverBrakeOnEject(object core, bool enabled) + { + object rover = GetRoverController(core); + if (rover == null || f_Rover_BrakeOnEject == null) return; + try { f_Rover_BrakeOnEject.SetValue(rover, enabled); } + catch { } + } + + public static bool GetRoverBrakeOnEnergyDepletion(object core) + { + object rover = GetRoverController(core); + if (rover == null || f_Rover_BrakeOnEnergyDepletion == null) return false; + try { return (bool)f_Rover_BrakeOnEnergyDepletion.GetValue(rover); } + catch { return false; } + } + + public static void SetRoverBrakeOnEnergyDepletion(object core, bool enabled) + { + object rover = GetRoverController(core); + if (rover == null || f_Rover_BrakeOnEnergyDepletion == null) return; + try { f_Rover_BrakeOnEnergyDepletion.SetValue(rover, enabled); } + catch { } + } + + public static bool GetRoverWarpToDaylight(object core) + { + object rover = GetRoverController(core); + if (rover == null || f_Rover_WarpToDaylight == null) return false; + try { return (bool)f_Rover_WarpToDaylight.GetValue(rover); } + catch { return false; } + } + + public static void SetRoverWarpToDaylight(object core, bool enabled) + { + object rover = GetRoverController(core); + if (rover == null || f_Rover_WarpToDaylight == null) return; + try { f_Rover_WarpToDaylight.SetValue(rover, enabled); } + catch { } + } + + public static void DriveToTarget(object core) + { + object rover = GetRoverController(core); + if (rover == null || m_Rover_DriveToTarget == null) return; + try { m_Rover_DriveToTarget.Invoke(rover, null); } + catch { } + } + + public static void StopRover(object core) + { + object rover = GetRoverController(core); + if (rover == null || m_Rover_Stop == null) return; + try { m_Rover_Stop.Invoke(rover, null); } + catch { } + } + + public static void AddRoverWaypointAtCurrentPosition(object core, Vessel vessel) + { + object rover = GetRoverController(core); + if (rover == null || vessel == null || m_Rover_AddWaypoint == null) return; + + try + { + ParameterInfo[] parms = m_Rover_AddWaypoint.GetParameters(); + if (parms.Length == 0) + { + m_Rover_AddWaypoint.Invoke(rover, null); + return; + } + + if (parms.Length == 2 && parms[0].ParameterType == typeof(double) && parms[1].ParameterType == typeof(double)) + { + m_Rover_AddWaypoint.Invoke(rover, new object[] { vessel.latitude, vessel.longitude }); + return; + } + + if (parms.Length == 1 && t_AbsoluteVector != null && parms[0].ParameterType == t_AbsoluteVector) + { + object absVec = CreateAbsoluteVector(vessel.mainBody, vessel.latitude, vessel.longitude, vessel.altitude); + if (absVec != null) + { + m_Rover_AddWaypoint.Invoke(rover, new object[] { absVec }); + } + } + } + catch { } + } + + public static void ClearRoverWaypoints(object core) + { + object rover = GetRoverController(core); + if (rover == null || m_Rover_ClearWaypoints == null) return; + try { m_Rover_ClearWaypoints.Invoke(rover, null); } + catch { } + } + #endregion + + #region Airplane Autopilot Methods + public static object GetAirplaneAutopilot(object core) + { + return GetComputerModule(core, "MechJebModuleAirplaneAutopilot"); + } + + public static bool GetAirplaneAltitudeHold(object core) + { + object ap = GetAirplaneAutopilot(core); + if (ap == null || f_Airplane_AltitudeHold == null) return false; + try { return (bool)f_Airplane_AltitudeHold.GetValue(ap); } + catch { return false; } + } + + public static void SetAirplaneAltitudeHold(object core, bool enabled) + { + object ap = GetAirplaneAutopilot(core); + if (ap == null || f_Airplane_AltitudeHold == null) return; + try { f_Airplane_AltitudeHold.SetValue(ap, enabled); } + catch { } + } + + public static double GetAirplaneAltitudeTarget(object core) + { + object ap = GetAirplaneAutopilot(core); + if (ap == null || f_Airplane_AltitudeTarget == null) return 0; + return GetEditableDoubleField(ap, f_Airplane_AltitudeTarget); + } + + public static void SetAirplaneAltitudeTarget(object core, double value) + { + object ap = GetAirplaneAutopilot(core); + if (ap == null || f_Airplane_AltitudeTarget == null) return; + SetEditableDoubleField(ap, f_Airplane_AltitudeTarget, value); + } + + public static bool GetAirplaneVertSpeedHold(object core) + { + object ap = GetAirplaneAutopilot(core); + if (ap == null || f_Airplane_VertSpeedHold == null) return false; + try { return (bool)f_Airplane_VertSpeedHold.GetValue(ap); } + catch { return false; } + } + + public static void SetAirplaneVertSpeedHold(object core, bool enabled) + { + object ap = GetAirplaneAutopilot(core); + if (ap == null || f_Airplane_VertSpeedHold == null) return; + try { f_Airplane_VertSpeedHold.SetValue(ap, enabled); } + catch { } + } + + public static double GetAirplaneVertSpeedTarget(object core) + { + object ap = GetAirplaneAutopilot(core); + if (ap == null || f_Airplane_VertSpeedTarget == null) return 0; + return GetEditableDoubleField(ap, f_Airplane_VertSpeedTarget); + } + + public static void SetAirplaneVertSpeedTarget(object core, double value) + { + object ap = GetAirplaneAutopilot(core); + if (ap == null || f_Airplane_VertSpeedTarget == null) return; + SetEditableDoubleField(ap, f_Airplane_VertSpeedTarget, value); + } + + public static bool GetAirplaneHeadingHold(object core) + { + object ap = GetAirplaneAutopilot(core); + if (ap == null || f_Airplane_HeadingHold == null) return false; + try { return (bool)f_Airplane_HeadingHold.GetValue(ap); } + catch { return false; } + } + + public static void SetAirplaneHeadingHold(object core, bool enabled) + { + object ap = GetAirplaneAutopilot(core); + if (ap == null || f_Airplane_HeadingHold == null) return; + try { f_Airplane_HeadingHold.SetValue(ap, enabled); } + catch { } + } + + public static double GetAirplaneHeadingTarget(object core) + { + object ap = GetAirplaneAutopilot(core); + if (ap == null || f_Airplane_HeadingTarget == null) return 0; + return GetEditableDoubleField(ap, f_Airplane_HeadingTarget); + } + + public static void SetAirplaneHeadingTarget(object core, double value) + { + object ap = GetAirplaneAutopilot(core); + if (ap == null || f_Airplane_HeadingTarget == null) return; + SetEditableDoubleField(ap, f_Airplane_HeadingTarget, value); + } + + public static bool GetAirplaneRollHold(object core) + { + object ap = GetAirplaneAutopilot(core); + if (ap == null || f_Airplane_RollHold == null) return false; + try { return (bool)f_Airplane_RollHold.GetValue(ap); } + catch { return false; } + } + + public static void SetAirplaneRollHold(object core, bool enabled) + { + object ap = GetAirplaneAutopilot(core); + if (ap == null || f_Airplane_RollHold == null) return; + try { f_Airplane_RollHold.SetValue(ap, enabled); } + catch { } + } + + public static double GetAirplaneRollTarget(object core) + { + object ap = GetAirplaneAutopilot(core); + if (ap == null || f_Airplane_RollTarget == null) return 0; + return GetEditableDoubleField(ap, f_Airplane_RollTarget); + } + + public static void SetAirplaneRollTarget(object core, double value) + { + object ap = GetAirplaneAutopilot(core); + if (ap == null || f_Airplane_RollTarget == null) return; + SetEditableDoubleField(ap, f_Airplane_RollTarget, value); + } + + public static bool GetAirplaneSpeedHold(object core) + { + object ap = GetAirplaneAutopilot(core); + if (ap == null || f_Airplane_SpeedHold == null) return false; + try { return (bool)f_Airplane_SpeedHold.GetValue(ap); } + catch { return false; } + } + + public static void SetAirplaneSpeedHold(object core, bool enabled) + { + object ap = GetAirplaneAutopilot(core); + if (ap == null || f_Airplane_SpeedHold == null) return; + try { f_Airplane_SpeedHold.SetValue(ap, enabled); } + catch { } + } + + public static double GetAirplaneSpeedTarget(object core) + { + object ap = GetAirplaneAutopilot(core); + if (ap == null || f_Airplane_SpeedTarget == null) return 0; + return GetEditableDoubleField(ap, f_Airplane_SpeedTarget); + } + + public static void SetAirplaneSpeedTarget(object core, double value) + { + object ap = GetAirplaneAutopilot(core); + if (ap == null || f_Airplane_SpeedTarget == null) return; + SetEditableDoubleField(ap, f_Airplane_SpeedTarget, value); + } + #endregion + + #region Spaceplane Autopilot Methods + public static object GetSpaceplaneAutopilot(object core) + { + return GetComputerModule(core, "MechJebModuleSpaceplaneAutopilot"); + } + + public static double GetSpaceplaneGlideslope(object core) + { + object sp = GetSpaceplaneAutopilot(core); + if (sp == null || f_Spaceplane_Glideslope == null) return 0; + return GetEditableDoubleField(sp, f_Spaceplane_Glideslope); + } + + public static void SetSpaceplaneGlideslope(object core, double value) + { + object sp = GetSpaceplaneAutopilot(core); + if (sp == null || f_Spaceplane_Glideslope == null) return; + SetEditableDoubleField(sp, f_Spaceplane_Glideslope, value); + } + + public static double GetSpaceplaneApproachSpeed(object core) + { + object sp = GetSpaceplaneAutopilot(core); + if (sp == null || f_Spaceplane_ApproachSpeed == null) return 0; + return GetEditableDoubleField(sp, f_Spaceplane_ApproachSpeed); + } + + public static void SetSpaceplaneApproachSpeed(object core, double value) + { + object sp = GetSpaceplaneAutopilot(core); + if (sp == null || f_Spaceplane_ApproachSpeed == null) return; + SetEditableDoubleField(sp, f_Spaceplane_ApproachSpeed, value); + } + + public static double GetSpaceplaneTouchdownSpeed(object core) + { + object sp = GetSpaceplaneAutopilot(core); + if (sp == null || f_Spaceplane_TouchdownSpeed == null) return 0; + return GetEditableDoubleField(sp, f_Spaceplane_TouchdownSpeed); + } + + public static void SetSpaceplaneTouchdownSpeed(object core, double value) + { + object sp = GetSpaceplaneAutopilot(core); + if (sp == null || f_Spaceplane_TouchdownSpeed == null) return; + SetEditableDoubleField(sp, f_Spaceplane_TouchdownSpeed, value); + } + + public static void SpaceplaneAutoland(object core) + { + object sp = GetSpaceplaneAutopilot(core); + if (sp == null || m_Spaceplane_Autoland == null) return; + try { m_Spaceplane_Autoland.Invoke(sp, null); } + catch { } + } + + public static void SpaceplaneHoldHeadingAndAltitude(object core) + { + object sp = GetSpaceplaneAutopilot(core); + if (sp == null || m_Spaceplane_HoldHeadingAndAltitude == null) return; + try { m_Spaceplane_HoldHeadingAndAltitude.Invoke(sp, null); } + catch { } + } + + public static void SpaceplaneAutopilotOff(object core) + { + object sp = GetSpaceplaneAutopilot(core); + if (sp == null || m_Spaceplane_AutopilotOff == null) return; + try { m_Spaceplane_AutopilotOff.Invoke(sp, null); } + catch { } + } + #endregion + + #region Warp Methods + public static object GetWarpController(object core) + { + return GetComputerModule(core, "MechJebModuleWarpController"); + } + + public static void WarpToUT(object core, double ut) + { + object warp = GetWarpController(core); + if (warp == null || m_Warp_WarpToUT == null) return; + try + { + m_Warp_WarpToUT.Invoke(warp, new object[] { ut }); + } + catch { } + } + #endregion + + #region Maneuver Calculator Methods + // These would call OrbitalManeuverCalculator static methods + // Implementing a few key ones + + /// + /// Place a maneuver node + /// + public static void PlaceManeuverNode(Vessel vessel, Orbit orbit, Vector3d dV, double UT) + { + if (m_PlaceManeuverNode == null) return; + try + { + m_PlaceManeuverNode.Invoke(null, new object[] { vessel, orbit, dV, UT }); + } + catch { } + } + + public static Vector3d CalcDeltaVToCircularize(Orbit orbit, double ut) + { + if (m_Calc_DeltaVToCircularize == null) return Vector3d.zero; + try + { + return (Vector3d)m_Calc_DeltaVToCircularize.Invoke(null, new object[] { orbit, ut }); + } + catch { return Vector3d.zero; } + } + + public static Vector3d CalcDeltaVToChangeApoapsis(Orbit orbit, double ut, double newApR) + { + if (m_Calc_DeltaVToChangeApoapsis == null) return Vector3d.zero; + try + { + return (Vector3d)m_Calc_DeltaVToChangeApoapsis.Invoke(null, new object[] { orbit, ut, newApR }); + } + catch { return Vector3d.zero; } + } + + public static Vector3d CalcDeltaVToChangePeriapsis(Orbit orbit, double ut, double newPeR) + { + if (m_Calc_DeltaVToChangePeriapsis == null) return Vector3d.zero; + try + { + return (Vector3d)m_Calc_DeltaVToChangePeriapsis.Invoke(null, new object[] { orbit, ut, newPeR }); + } + catch { return Vector3d.zero; } + } + + public static Vector3d CalcDeltaVForSemiMajorAxis(Orbit orbit, double ut, double newSma) + { + if (m_Calc_DeltaVForSemiMajorAxis == null) return Vector3d.zero; + try + { + return (Vector3d)m_Calc_DeltaVForSemiMajorAxis.Invoke(null, new object[] { orbit, ut, newSma }); + } + catch { return Vector3d.zero; } + } + + public static Vector3d CalcDeltaVToChangeInclination(Orbit orbit, double ut, double newInc) + { + if (m_Calc_DeltaVToChangeInclination == null) return Vector3d.zero; + try + { + return (Vector3d)m_Calc_DeltaVToChangeInclination.Invoke(null, new object[] { orbit, ut, newInc }); + } + catch { return Vector3d.zero; } + } + + public static bool TryCalcMatchPlanesAscending(Orbit orbit, Orbit target, double ut, out Vector3d dV, out double burnUT) + { + dV = Vector3d.zero; + burnUT = 0; + if (m_Calc_DeltaVToMatchPlanesAscending == null) return false; + try + { + object[] args = new object[] { orbit, target, ut, burnUT }; + dV = (Vector3d)m_Calc_DeltaVToMatchPlanesAscending.Invoke(null, args); + burnUT = (double)args[3]; + return true; + } + catch { return false; } + } + + public static bool TryCalcMatchPlanesDescending(Orbit orbit, Orbit target, double ut, out Vector3d dV, out double burnUT) + { + dV = Vector3d.zero; + burnUT = 0; + if (m_Calc_DeltaVToMatchPlanesDescending == null) return false; + try + { + object[] args = new object[] { orbit, target, ut, burnUT }; + dV = (Vector3d)m_Calc_DeltaVToMatchPlanesDescending.Invoke(null, args); + burnUT = (double)args[3]; + return true; + } + catch { return false; } + } + + public static Vector3d CalcDeltaVToMatchVelocities(Orbit orbit, double ut, Orbit target) + { + if (m_Calc_DeltaVToMatchVelocities == null) return Vector3d.zero; + try + { + return (Vector3d)m_Calc_DeltaVToMatchVelocities.Invoke(null, new object[] { orbit, ut, target }); + } + catch { return Vector3d.zero; } + } + + public static bool TryCalcHohmannTransfer(Orbit orbit, Orbit target, double ut, out Vector3d dv1, out double ut1, out Vector3d dv2, out double ut2) + { + dv1 = Vector3d.zero; + dv2 = Vector3d.zero; + ut1 = 0; + ut2 = 0; + if (m_Calc_DeltaVAndTimeForHohmannTransfer == null) return false; + try + { + object result = m_Calc_DeltaVAndTimeForHohmannTransfer.Invoke(null, new object[] { orbit, target, ut, double.NaN, false, true, true, true }); + dv1 = (Vector3d)result.GetType().GetField("dV1").GetValue(result); + ut1 = (double)result.GetType().GetField("UT1").GetValue(result); + dv2 = (Vector3d)result.GetType().GetField("dV2").GetValue(result); + ut2 = (double)result.GetType().GetField("UT2").GetValue(result); + return true; + } + catch { return false; } + } + + public static Vector3d CalcDeltaVToShiftLAN(Orbit orbit, double ut, double newLan) + { + if (m_Calc_DeltaVToShiftLAN == null) return Vector3d.zero; + try + { + return (Vector3d)m_Calc_DeltaVToShiftLAN.Invoke(null, new object[] { orbit, ut, newLan }); + } + catch { return Vector3d.zero; } + } + + public static Vector3d CalcDeltaVToEllipticize(Orbit orbit, double ut, double newPeR, double newApR) + { + if (m_Calc_DeltaVToEllipticize == null) return Vector3d.zero; + try + { + return (Vector3d)m_Calc_DeltaVToEllipticize.Invoke(null, new object[] { orbit, ut, newPeR, newApR }); + } + catch { return Vector3d.zero; } + } + + public static object CreateCourseCorrectionOperation() + { + if (t_OperationCourseCorrection == null) return null; + try + { + return Activator.CreateInstance(t_OperationCourseCorrection); + } + catch { return null; } + } + + public static bool IsCourseCorrectionAvailable + { + get { return t_OperationCourseCorrection != null; } + } + + public static void SetCourseCorrectionTargetPe(object operation, double peKm) + { + if (operation == null) return; + + if (f_CourseCorrection_TargetPe != null) + { + SetMemberDouble(operation, f_CourseCorrection_TargetPe, peKm); + return; + } + + if (p_CourseCorrection_TargetPe != null) + { + SetMemberDouble(operation, p_CourseCorrection_TargetPe, peKm); + } + } + + public static object CreateAdvancedTransferOperation() + { + if (t_OperationAdvancedTransfer == null) return null; + try + { + return Activator.CreateInstance(t_OperationAdvancedTransfer); + } + catch { return null; } + } + + public static void StartAdvancedTransferCompute(object operation, Orbit orbit, double ut, object targetController, bool includeCaptureBurn, double periapsisKm) + { + if (operation == null || targetController == null) return; + + try + { + if (f_AdvancedTransfer_IncludeCaptureBurn != null) + { + f_AdvancedTransfer_IncludeCaptureBurn.SetValue(operation, includeCaptureBurn); + } + + if (f_AdvancedTransfer_PeriapsisHeight != null) + { + object periapsisEditable = f_AdvancedTransfer_PeriapsisHeight.GetValue(operation); + SetEditableDouble(periapsisEditable, periapsisKm); + } + + if (f_AdvancedTransfer_SelectionMode != null) + { + Type enumType = f_AdvancedTransfer_SelectionMode.FieldType; + object limitedTime = Enum.Parse(enumType, "LIMITED_TIME", true); + f_AdvancedTransfer_SelectionMode.SetValue(operation, limitedTime); + } + + Orbit targetOrbit = GetTargetOrbitFromController(targetController); + if (targetOrbit == null) return; + + if (m_AdvancedTransfer_ComputeTimes != null) + { + m_AdvancedTransfer_ComputeTimes.Invoke(operation, new object[] { orbit, targetOrbit, ut }); + } + + if (m_AdvancedTransfer_ComputeStuff != null) + { + m_AdvancedTransfer_ComputeStuff.Invoke(operation, new object[] { orbit, ut, targetController }); + } + } + catch { } + } + + /// + /// Gets the includeCaptureBurn field from OperationAdvancedTransfer + /// + public static bool GetAdvancedTransferIncludeCapture(object operation) + { + if (operation == null || f_AdvancedTransfer_IncludeCaptureBurn == null) return false; + try + { + return (bool)f_AdvancedTransfer_IncludeCaptureBurn.GetValue(operation); + } + catch { return false; } + } + + /// + /// Sets the includeCaptureBurn field on OperationAdvancedTransfer + /// + public static void SetAdvancedTransferIncludeCapture(object operation, bool value) + { + if (operation == null || f_AdvancedTransfer_IncludeCaptureBurn == null) return; + try + { + f_AdvancedTransfer_IncludeCaptureBurn.SetValue(operation, value); + } + catch { } + } + + /// + /// Gets the periapsisHeight (in km) from OperationAdvancedTransfer + /// + public static double GetAdvancedTransferPeriapsisKm(object operation) + { + if (operation == null || f_AdvancedTransfer_PeriapsisHeight == null) return 100.0; + try + { + object periapsisEditable = f_AdvancedTransfer_PeriapsisHeight.GetValue(operation); + return GetEditableDouble(periapsisEditable); + } + catch { return 100.0; } + } + + /// + /// Sets the periapsisHeight (in km) on OperationAdvancedTransfer + /// + public static void SetAdvancedTransferPeriapsisKm(object operation, double valueKm) + { + if (operation == null || f_AdvancedTransfer_PeriapsisHeight == null) return; + try + { + object periapsisEditable = f_AdvancedTransfer_PeriapsisHeight.GetValue(operation); + SetEditableDouble(periapsisEditable, valueKm); + } + catch { } + } + + public static bool IsAdvancedTransferFinished(object operation, out int progress) + { + progress = 0; + if (operation == null || f_AdvancedTransfer_Worker == null) return false; + try + { + object worker = f_AdvancedTransfer_Worker.GetValue(operation); + if (worker == null || p_TransferCalculator_Finished == null) return false; + progress = p_TransferCalculator_Progress != null ? (int)p_TransferCalculator_Progress.GetValue(worker, null) : 0; + return (bool)p_TransferCalculator_Finished.GetValue(worker, null); + } + catch { return false; } + } + + public static void SelectAdvancedTransferLowestDV(object operation) + { + if (operation == null || f_AdvancedTransfer_Worker == null) return; + try + { + object worker = f_AdvancedTransfer_Worker.GetValue(operation); + if (worker == null) return; + // BestDate/BestDuration already represent lowest DV + } + catch { } + } + + public static void SelectAdvancedTransferASAP(object operation) + { + if (operation == null || f_AdvancedTransfer_Worker == null) return; + try + { + object worker = f_AdvancedTransfer_Worker.GetValue(operation); + if (worker == null || f_TransferCalculator_Computed == null || f_TransferCalculator_BestDate == null || f_TransferCalculator_BestDuration == null) return; + + double[,] computed = f_TransferCalculator_Computed.GetValue(worker) as double[,]; + if (computed == null) return; + + int bestDuration = 0; + int durationCount = computed.GetLength(1); + for (int i = 1; i < durationCount; i++) + { + if (computed[0, bestDuration] > computed[0, i]) + bestDuration = i; + } + + f_TransferCalculator_BestDate.SetValue(worker, 0); + f_TransferCalculator_BestDuration.SetValue(worker, bestDuration); + } + catch { } + } + + public static string GetOperationErrorMessage(object operation) + { + if (operation == null || m_Operation_GetErrorMessage == null) return ""; + try + { + return (string)m_Operation_GetErrorMessage.Invoke(operation, null); + } + catch { return ""; } + } + + /// + /// Sets the lastTargetCelestial field on OperationAdvancedTransfer before calling MakeNodes + /// + private static void SetAdvancedTransferTargetCelestial(object operation, CelestialBody target) + { + if (operation == null || target == null || f_AdvancedTransfer_LastTargetCelestial == null) return; + try + { + f_AdvancedTransfer_LastTargetCelestial.SetValue(operation, target); + } + catch { } + } + + /// + /// Gets the selected departure time and duration from the worker + /// + public static bool GetAdvancedTransferSelection(object operation, out double departureUT, out double duration, out double deltaV) + { + departureUT = 0; + duration = 0; + deltaV = 0; + + if (operation == null || f_AdvancedTransfer_Worker == null) return false; + + try + { + object worker = f_AdvancedTransfer_Worker.GetValue(operation); + if (worker == null) return false; + + if (f_TransferCalculator_BestDate == null || f_TransferCalculator_BestDuration == null) return false; + + int bestDateIdx = (int)f_TransferCalculator_BestDate.GetValue(worker); + int bestDurIdx = (int)f_TransferCalculator_BestDuration.GetValue(worker); + + if (m_TransferCalculator_DateFromIndex != null) + departureUT = (double)m_TransferCalculator_DateFromIndex.Invoke(worker, new object[] { bestDateIdx }); + + if (m_TransferCalculator_DurationFromIndex != null) + duration = (double)m_TransferCalculator_DurationFromIndex.Invoke(worker, new object[] { bestDurIdx }); + + // Get DV from computed array + if (f_TransferCalculator_Computed != null) + { + double[,] computed = f_TransferCalculator_Computed.GetValue(worker) as double[,]; + if (computed != null && bestDateIdx < computed.GetLength(0) && bestDurIdx < computed.GetLength(1)) + { + deltaV = computed[bestDateIdx, bestDurIdx]; + } + } + + return true; + } + catch { return false; } + } + + public static bool CreateNodesFromOperation(object operation, Orbit orbit, double ut, object targetController, Vessel vessel) + { + if (operation == null || m_Operation_MakeNodes == null || vessel == null) return false; + try + { + // For AdvancedTransfer, ensure lastTargetCelestial is set + if (t_OperationAdvancedTransfer != null && t_OperationAdvancedTransfer.IsInstanceOfType(operation)) + { + CelestialBody targetBody = FlightGlobals.fetch.VesselTarget as CelestialBody; + if (targetBody != null) + { + SetAdvancedTransferTargetCelestial(operation, targetBody); + } + } + + object nodeList = m_Operation_MakeNodes.Invoke(operation, new object[] { orbit, ut, targetController }); + if (nodeList == null) return false; + + System.Collections.IEnumerable nodes = nodeList as System.Collections.IEnumerable; + if (nodes == null) return false; + + foreach (object node in nodes) + { + if (node == null || f_ManeuverParameters_dV == null || f_ManeuverParameters_UT == null) continue; + Vector3d dV = (Vector3d)f_ManeuverParameters_dV.GetValue(node); + double nodeUT = (double)f_ManeuverParameters_UT.GetValue(node); + PlaceManeuverNode(vessel, orbit, dV, nodeUT); + } + + return true; + } + catch { return false; } + } + + #region ManeuverPlanner Operation Wrapper + /// + /// Gets MechJeb's static Operation array from ManeuverPlanner. + /// This is the SAME array that IMGUI uses, so changes sync automatically. + /// + public static object[] GetOperations() + { + if (cachedOperations != null) return cachedOperations; + if (f_ManeuverPlanner_operation == null) return null; + + try + { + object[] ops = f_ManeuverPlanner_operation.GetValue(null) as object[]; + if (ops != null) + { + cachedOperations = ops; + // Also cache names + cachedOperationNames = new string[ops.Length]; + for (int i = 0; i < ops.Length; i++) + { + cachedOperationNames[i] = GetOperationName(ops[i]) ?? "Unknown"; + } + } + return cachedOperations; + } + catch { return null; } + } + + /// + /// Gets the cached operation names array (same order as GetOperations()) + /// + public static string[] GetOperationNames() + { + if (cachedOperationNames == null) GetOperations(); // Populate cache + return cachedOperationNames; + } + + /// + /// Gets the name of an Operation instance + /// + public static string GetOperationName(object operation) + { + if (operation == null || m_Operation_GetName == null) return null; + try + { + return (string)m_Operation_GetName.Invoke(operation, null); + } + catch { return null; } + } + + /// + /// Gets an Operation by its name + /// + public static object GetOperationByName(string name) + { + object[] ops = GetOperations(); + if (ops == null) return null; + + foreach (object op in ops) + { + if (GetOperationName(op) == name) return op; + } + return null; + } + + /// + /// Gets an Operation by its index in the array + /// + public static object GetOperationByIndex(int index) + { + object[] ops = GetOperations(); + if (ops == null || index < 0 || index >= ops.Length) return null; + return ops[index]; + } + + /// + /// Gets the operation index by name + /// + public static int GetOperationIndexByName(string name) + { + string[] names = GetOperationNames(); + if (names == null) return -1; + for (int i = 0; i < names.Length; i++) + { + if (names[i] == name) return i; + } + return -1; + } + + /// + /// Gets the currently selected _operationId from ManeuverPlanner module + /// + public static int GetManeuverPlannerOperationId(object core) + { + if (f_ManeuverPlanner_operationId == null) return 0; + object planner = GetComputerModule(core, "MechJebModuleManeuverPlanner"); + if (planner == null) return 0; + try + { + return (int)f_ManeuverPlanner_operationId.GetValue(planner); + } + catch { return 0; } + } + + /// + /// Sets the _operationId on ManeuverPlanner module (syncs with IMGUI dropdown) + /// + public static void SetManeuverPlannerOperationId(object core, int operationId) + { + if (f_ManeuverPlanner_operationId == null) return; + object planner = GetComputerModule(core, "MechJebModuleManeuverPlanner"); + if (planner == null) return; + try + { + f_ManeuverPlanner_operationId.SetValue(planner, operationId); + } + catch { } + } + + /// + /// Gets the ManeuverPlanner module from core + /// + public static object GetManeuverPlanner(object core) + { + return GetComputerModule(core, "MechJebModuleManeuverPlanner"); + } + + /// + /// Gets an EditableDoubleMult field value from an operation + /// + public static double GetOperationEditableDouble(object operation, string fieldName) + { + if (operation == null) return 0; + try + { + Type opType = operation.GetType(); + FieldInfo field = opType.GetField(fieldName, + BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); + if (field == null) return 0; + + object editable = field.GetValue(operation); + if (editable == null) return 0; + + // Try EditableDoubleMult first + if (p_EditableDoubleMult_Val != null && t_EditableDoubleMult != null + && t_EditableDoubleMult.IsInstanceOfType(editable)) + { + return (double)p_EditableDoubleMult_Val.GetValue(editable, null); + } + + // Try EditableDouble + if (p_EditableDouble_Val != null && t_EditableDouble != null + && t_EditableDouble.IsInstanceOfType(editable)) + { + return (double)p_EditableDouble_Val.GetValue(editable, null); + } + + return 0; + } + catch { return 0; } + } + + /// + /// Sets an EditableDoubleMult field value on an operation + /// + public static void SetOperationEditableDouble(object operation, string fieldName, double value) + { + if (operation == null) return; + try + { + Type opType = operation.GetType(); + FieldInfo field = opType.GetField(fieldName, + BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); + if (field == null) return; + + object editable = field.GetValue(operation); + if (editable == null) return; + + // Try EditableDoubleMult first + if (p_EditableDoubleMult_Val != null && t_EditableDoubleMult != null + && t_EditableDoubleMult.IsInstanceOfType(editable)) + { + p_EditableDoubleMult_Val.SetValue(editable, value, null); + return; + } + + // Try EditableDouble + if (p_EditableDouble_Val != null && t_EditableDouble != null + && t_EditableDouble.IsInstanceOfType(editable)) + { + p_EditableDouble_Val.SetValue(editable, value, null); + return; + } + } + catch { } + } + + /// + /// Gets an EditableInt field value from an operation + /// + public static int GetOperationEditableInt(object operation, string fieldName) + { + if (operation == null) return 0; + try + { + Type opType = operation.GetType(); + FieldInfo field = opType.GetField(fieldName, + BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); + if (field == null) return 0; + + object editable = field.GetValue(operation); + if (editable == null) return 0; + + if (p_EditableInt_Val != null && t_EditableInt != null + && t_EditableInt.IsInstanceOfType(editable)) + { + return (int)p_EditableInt_Val.GetValue(editable, null); + } + + return 0; + } + catch { return 0; } + } + + /// + /// Sets an EditableInt field value on an operation + /// + public static void SetOperationEditableInt(object operation, string fieldName, int value) + { + if (operation == null) return; + try + { + Type opType = operation.GetType(); + FieldInfo field = opType.GetField(fieldName, + BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); + if (field == null) return; + + object editable = field.GetValue(operation); + if (editable == null) return; + + if (p_EditableInt_Val != null && t_EditableInt != null + && t_EditableInt.IsInstanceOfType(editable)) + { + p_EditableInt_Val.SetValue(editable, value, null); + } + } + catch { } + } + + /// + /// Gets a boolean field value from an operation + /// + public static bool GetOperationBool(object operation, string fieldName) + { + if (operation == null) return false; + try + { + Type opType = operation.GetType(); + FieldInfo field = opType.GetField(fieldName, + BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); + if (field == null) return false; + + return (bool)field.GetValue(operation); + } + catch { return false; } + } + + /// + /// Sets a boolean field value on an operation + /// + public static void SetOperationBool(object operation, string fieldName, bool value) + { + if (operation == null) return; + try + { + Type opType = operation.GetType(); + FieldInfo field = opType.GetField(fieldName, + BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); + if (field == null) return; + + field.SetValue(operation, value); + } + catch { } + } + + /// + /// Gets the TimeSelector from an operation (for timing options) + /// + public static object GetOperationTimeSelector(object operation) + { + if (operation == null || t_TimeSelector == null) return null; + try + { + Type opType = operation.GetType(); + // Look for static _timeSelector field + FieldInfo field = opType.GetField("_timeSelector", + BindingFlags.NonPublic | BindingFlags.Static); + if (field == null) return null; + + return field.GetValue(null); + } + catch { return null; } + } + + /// + /// Gets the current TimeReference index from a TimeSelector + /// + public static int GetTimeSelectorCurrentTimeRef(object timeSelector) + { + if (timeSelector == null || f_TimeSelector_CurrentTimeRef == null) return 0; + try + { + return (int)f_TimeSelector_CurrentTimeRef.GetValue(timeSelector); + } + catch { return 0; } + } + + /// + /// Sets the current TimeReference index on a TimeSelector + /// + public static void SetTimeSelectorCurrentTimeRef(object timeSelector, int timeRefIndex) + { + if (timeSelector == null || f_TimeSelector_CurrentTimeRef == null) return; + try + { + f_TimeSelector_CurrentTimeRef.SetValue(timeSelector, timeRefIndex); + } + catch { } + } + + /// + /// Gets the CircularizeAltitude from a TimeSelector (in meters) + /// + public static double GetTimeSelectorCircularizeAltitude(object timeSelector) + { + if (timeSelector == null || f_TimeSelector_CircularizeAltitude == null || p_EditableDoubleMult_Val == null) return 0; + try + { + object altField = f_TimeSelector_CircularizeAltitude.GetValue(timeSelector); + if (altField == null) return 0; + return (double)p_EditableDoubleMult_Val.GetValue(altField, null); + } + catch { return 0; } + } + + /// + /// Sets the CircularizeAltitude on a TimeSelector (in meters) + /// + public static void SetTimeSelectorCircularizeAltitude(object timeSelector, double altitudeMeters) + { + if (timeSelector == null || f_TimeSelector_CircularizeAltitude == null || p_EditableDoubleMult_Val == null) return; + try + { + object altField = f_TimeSelector_CircularizeAltitude.GetValue(timeSelector); + if (altField == null) return; + p_EditableDoubleMult_Val.SetValue(altField, altitudeMeters, null); + } + catch { } + } + + /// + /// Gets the LeadTime from a TimeSelector (in seconds) + /// + public static double GetTimeSelectorLeadTime(object timeSelector) + { + if (timeSelector == null || f_TimeSelector_LeadTime == null || p_EditableDouble_Val == null) return 0; + try + { + object leadField = f_TimeSelector_LeadTime.GetValue(timeSelector); + if (leadField == null) return 0; + return (double)p_EditableDouble_Val.GetValue(leadField, null); + } + catch { return 0; } + } + + /// + /// Sets the LeadTime on a TimeSelector (in seconds) + /// + public static void SetTimeSelectorLeadTime(object timeSelector, double seconds) + { + if (timeSelector == null || f_TimeSelector_LeadTime == null || p_EditableDouble_Val == null) return; + try + { + object leadField = f_TimeSelector_LeadTime.GetValue(timeSelector); + if (leadField == null) return; + p_EditableDouble_Val.SetValue(leadField, seconds, null); + } + catch { } + } + + /// + /// Computes the maneuver time using the operation's TimeSelector + /// + public static double ComputeOperationManeuverTime(object operation, object core, Orbit orbit, double ut) + { + if (operation == null || m_TimeSelector_ComputeManeuverTime == null) return ut; + try + { + object timeSelector = GetOperationTimeSelector(operation); + if (timeSelector == null) return ut; + + object targetController = GetTargetController(core); + return (double)m_TimeSelector_ComputeManeuverTime.Invoke( + timeSelector, new object[] { orbit, ut, targetController }); + } + catch { return ut; } + } + + /// + /// Calls MakeNodes on an operation and creates the maneuver nodes + /// + public static bool ExecuteOperation(object operation, object core, Vessel vessel) + { + if (operation == null || core == null || vessel == null) return false; + + Orbit orbit = vessel.orbit; + double ut = Planetarium.GetUniversalTime(); + object targetController = GetTargetController(core); + + return CreateNodesFromOperation(operation, orbit, ut, targetController, vessel); + } + #endregion + + #region OperationGeneric (Hohmann/Bi-Impulsive Transfer) + /// + /// Creates an OperationGeneric instance for Hohmann/bi-impulsive transfers + /// + public static object CreateGenericTransferOperation() + { + if (t_OperationGeneric == null) return null; + try + { + return Activator.CreateInstance(t_OperationGeneric); + } + catch { return null; } + } + + /// + /// Check if OperationGeneric is available + /// + public static bool IsGenericTransferAvailable + { + get { return t_OperationGeneric != null; } + } + + /// + /// Configure OperationGeneric for a specific transfer type + /// + /// The OperationGeneric instance + /// Include capture burn at target + /// Plan the insertion burn + /// Rendezvous mode (vs Transfer mode) + /// Coplanar maneuver + /// Rendezvous time offset in seconds + public static void ConfigureGenericTransfer(object operation, bool capture, bool planCapture, bool rendezvous, bool coplanar, double lagTime) + { + if (operation == null) return; + + try + { + if (f_Generic_Capture != null) + f_Generic_Capture.SetValue(operation, capture); + + if (f_Generic_PlanCapture != null) + f_Generic_PlanCapture.SetValue(operation, planCapture); + + if (f_Generic_Rendezvous != null) + f_Generic_Rendezvous.SetValue(operation, rendezvous); + + if (f_Generic_Coplanar != null) + f_Generic_Coplanar.SetValue(operation, coplanar); + + if (f_Generic_LagTime != null) + { + object lagEditable = f_Generic_LagTime.GetValue(operation); + SetEditableDouble(lagEditable, lagTime); + } + } + catch { } + } + + /// + /// Get current configuration of OperationGeneric + /// + public static void GetGenericTransferConfig(object operation, out bool capture, out bool planCapture, out bool rendezvous, out bool coplanar, out double lagTime) + { + capture = true; + planCapture = true; + rendezvous = true; + coplanar = false; + lagTime = 0; + + if (operation == null) return; + + try + { + if (f_Generic_Capture != null) + capture = (bool)f_Generic_Capture.GetValue(operation); + + if (f_Generic_PlanCapture != null) + planCapture = (bool)f_Generic_PlanCapture.GetValue(operation); + + if (f_Generic_Rendezvous != null) + rendezvous = (bool)f_Generic_Rendezvous.GetValue(operation); + + if (f_Generic_Coplanar != null) + coplanar = (bool)f_Generic_Coplanar.GetValue(operation); + + if (f_Generic_LagTime != null) + { + object lagEditable = f_Generic_LagTime.GetValue(operation); + lagTime = GetEditableDouble(lagEditable); + } + } + catch { } + } + #endregion + + private static Orbit GetTargetOrbitFromController(object targetController) + { + if (targetController == null || p_Target_TargetOrbit == null) return null; + try + { + return (Orbit)p_Target_TargetOrbit.GetValue(targetController, null); + } + catch { return null; } + } + #endregion + + #region Additional Operation Types + // Operation type cache for other maneuvers + private static Type t_OperationEccentricity; + private static Type t_OperationLongitude; + private static Type t_OperationCourseCorrection_FineTune; + private static Type t_OperationLambert; + private static Type t_OperationResonantOrbit; + private static Type t_OperationMoonReturn; + private static Type t_OperationInterplanetaryTransfer; + + /// + /// Calculate delta-V to change eccentricity at given time + /// + public static Vector3d CalcDeltaVToChangeEccentricity(Orbit orbit, double ut, double newEcc) + { + // Calculate new SMA to achieve target eccentricity while keeping periapsis constant + double r = orbit.Radius(ut); + double currentV = orbit.SwappedOrbitalVelocityAtUT(ut).magnitude; + double mu = orbit.referenceBody.gravParameter; + + // For an orbit with eccentricity e, at radius r: + // v^2 = mu * (2/r - 1/a) where a is semi-major axis + // With e and keeping Pe constant: a = Pe / (1 - e) + double pe = orbit.PeR; + double newSma = pe / (1.0 - newEcc); + double newV = Math.Sqrt(mu * (2.0 / r - 1.0 / newSma)); + + // Get velocity direction + Vector3d velocityDir = orbit.SwappedOrbitalVelocityAtUT(ut).normalized; + return velocityDir * (newV - currentV); + } + + /// + /// Calculate delta-V to change surface longitude of apsis + /// + public static Vector3d CalcDeltaVToChangeSurfaceLongitude(Orbit orbit, double ut, double targetLongitude) + { + // This requires changing the argument of periapsis + // Simplified implementation - may need refinement + double currentLon = orbit.LAN + orbit.argumentOfPeriapsis; + double deltaLon = targetLongitude - currentLon; + while (deltaLon > 180) deltaLon -= 360; + while (deltaLon < -180) deltaLon += 360; + + // Small radial burn to shift apsides (approximate) + double r = orbit.Radius(ut); + double v = orbit.SwappedOrbitalVelocityAtUT(ut).magnitude; + double deltaV = v * Math.Tan(deltaLon * Math.PI / 180.0) * 0.1; // Rough approximation + + Vector3d radialDir = Vector3d.Cross(orbit.SwappedOrbitalVelocityAtUT(ut), orbit.SwappedOrbitNormal()).normalized; + return radialDir * deltaV; + } + + /// + /// Create operation for fine tuning closest approach + /// + public static object CreateFineTuneClosestApproachOperation() + { + if (t_OperationCourseCorrection_FineTune == null) + { + t_OperationCourseCorrection_FineTune = mechJebAssembly.GetType("MuMech.OperationCourseCorrection"); + } + if (t_OperationCourseCorrection_FineTune == null) return null; + try + { + return Activator.CreateInstance(t_OperationCourseCorrection_FineTune); + } + catch { return null; } + } + + public static void SetFineTuneDistance(object operation, double distanceKm) + { + SetCourseCorrectionTargetPe(operation, distanceKm); + } + + /// + /// Create operation for intercepting target at a specific time + /// + public static object CreateInterceptAtTimeOperation() + { + if (t_OperationLambert == null) + { + t_OperationLambert = mechJebAssembly.GetType("MuMech.OperationLambert"); + } + if (t_OperationLambert == null) return null; + try + { + return Activator.CreateInstance(t_OperationLambert); + } + catch { return null; } + } + + public static void SetInterceptInterval(object operation, double seconds) + { + if (operation == null) return; + try + { + FieldInfo f = operation.GetType().GetField("interceptInterval", BindingFlags.Public | BindingFlags.Instance); + if (f != null) + { + object editable = f.GetValue(operation); + SetEditableDouble(editable, seconds); + } + } + catch { } + } + + /// + /// Create operation for resonant orbit + /// + public static object CreateResonantOrbitOperation() + { + if (t_OperationResonantOrbit == null) + { + t_OperationResonantOrbit = mechJebAssembly.GetType("MuMech.OperationResonantOrbit"); + } + if (t_OperationResonantOrbit == null) return null; + try + { + return Activator.CreateInstance(t_OperationResonantOrbit); + } + catch { return null; } + } + + public static void SetResonance(object operation, double numerator, double denominator) + { + if (operation == null) return; + try + { + FieldInfo fNum = operation.GetType().GetField("resonanceNumerator", BindingFlags.Public | BindingFlags.Instance); + FieldInfo fDen = operation.GetType().GetField("resonanceDenominator", BindingFlags.Public | BindingFlags.Instance); + if (fNum != null) + { + object editable = fNum.GetValue(operation); + SetEditableDouble(editable, numerator); + } + if (fDen != null) + { + object editable = fDen.GetValue(operation); + SetEditableDouble(editable, denominator); + } + } + catch { } + } + + /// + /// Create operation for moon return + /// + public static object CreateMoonReturnOperation() + { + if (t_OperationMoonReturn == null) + { + t_OperationMoonReturn = mechJebAssembly.GetType("MuMech.OperationMoonReturn"); + } + if (t_OperationMoonReturn == null) return null; + try + { + return Activator.CreateInstance(t_OperationMoonReturn); + } + catch { return null; } + } + + public static void SetMoonReturnAltitude(object operation, double altitudeKm) + { + if (operation == null) return; + try + { + FieldInfo f = operation.GetType().GetField("moonReturnAltitude", BindingFlags.Public | BindingFlags.Instance); + if (f != null) + { + object editable = f.GetValue(operation); + SetEditableDouble(editable, altitudeKm * 1000.0); // Convert to meters + } + } + catch { } + } + + /// + /// Create operation for interplanetary transfer + /// + public static object CreateInterplanetaryTransferOperation() + { + if (t_OperationInterplanetaryTransfer == null) + { + t_OperationInterplanetaryTransfer = mechJebAssembly.GetType("MuMech.OperationInterplanetaryTransfer"); + } + if (t_OperationInterplanetaryTransfer == null) return null; + try + { + return Activator.CreateInstance(t_OperationInterplanetaryTransfer); + } + catch { return null; } + } + #endregion + + #region VesselState Methods + /// + /// Get terminal velocity + /// + public static double GetTerminalVelocity(object core) + { + object vesselState = GetVesselState(core); + if (vesselState == null || m_VesselState_TerminalVelocity == null) return 0; + try + { + return (double)m_VesselState_TerminalVelocity.Invoke(vesselState, null); + } + catch + { + return 0; + } + } + #endregion + } +} diff --git a/RasterPropMonitor/Handlers/MechJebRPM.cs b/RasterPropMonitor/Handlers/MechJebRPM.cs index c0390303..6d42767e 100644 --- a/RasterPropMonitor/Handlers/MechJebRPM.cs +++ b/RasterPropMonitor/Handlers/MechJebRPM.cs @@ -5,7 +5,7 @@ namespace JSI { - class MechJebRPM : InternalModule + public class MechJebRPM : InternalModule { [KSPField] public string pageTitle = string.Empty; @@ -197,7 +197,7 @@ public void ClickProcessor(int buttonID) } else if (buttonID == buttonHome) { - if (currentMenu == MJMenu.RootMenu && activeMenu.currentSelection == 5 && smartassAvailable) + if (currentMenu == MJMenu.RootMenu && activeMenu.currentSelection == 6 && smartassAvailable) { // If Force Roll is highlighted, the Home key will increment the // roll value. @@ -426,7 +426,8 @@ private void UpdateRootMenu() item = activeMenu.Find(x => x.id == (int)MJMenu.AscentGuidanceMenu); if (item != null) { - if (!GetModuleExists("MechJebModuleAscentAutopilot")) + // MJ 2.15.1: AscentAutopilot is now accessed via AscentSettings + if (!GetModuleExists("MechJebModuleAscentSettings")) { item.isSelected = false; item.isDisabled = true; diff --git a/RasterPropMonitor/Handlers/MechJebRPMMenu.cs b/RasterPropMonitor/Handlers/MechJebRPMMenu.cs new file mode 100644 index 00000000..0ab3c774 --- /dev/null +++ b/RasterPropMonitor/Handlers/MechJebRPMMenu.cs @@ -0,0 +1,3542 @@ +/***************************************************************************** + * RasterPropMonitor + * ================= + * Plugin for Kerbal Space Program + * + * by Mihara (Eugene Medvedev), MOARdV, and other contributors + * + * RasterPropMonitor is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, revision + * date 29 June 2007, or (at your option) any later version. + * + * RasterPropMonitor is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * You should have received a copy of the GNU General Public License + * along with RasterPropMonitor. If not, see . + ****************************************************************************/ + +using System; +using System.Collections.Generic; +using UnityEngine; + +namespace JSI +{ + /// + /// MechJebRPMMenu provides a comprehensive text menu interface to MechJeb 2.15+ + /// matching full feature parity with MechJeb's IMGUI interface. + /// + public class MechJebRPMMenu : InternalModule + { + #region Configuration Fields + [KSPField] + public string pageTitle = "%.%.%.%.%.%.%.%.%.%.%.%.%.%.%.%.% %.%.%.%.%.%.%.%.%.%.%.%.%.%.%.%.%%.%.%.%.%.%.%.%.%.%.MechJeb%.%.%.%.%.%.%.%.%.%.%.%.%.%.%.%.% %.%.%.%.%.%.%.%.%.%.%.%.%.%.%.%.%"; + + [KSPField] + public int buttonUp = 0; + + [KSPField] + public int buttonDown = 1; + + [KSPField] + public int buttonEnter = 2; + + [KSPField] + public int buttonEsc = 3; + + [KSPField] + public int buttonHome = 4; + + [KSPField] + public int buttonRight = 5; + + [KSPField] + public int buttonLeft = 6; + #endregion + + #region Instance State + private TextMenu topMenu; + private TextMenu currentMenu; + private bool pageActiveState = false; + private object mjCore = null; + private Vessel activeVessel = null; + + private TextMenu smartassOrbitalMenu; + private TextMenu smartassSurfaceMenu; + private TextMenu smartassTargetMenu; + + // Maneuver planner state + private double circularizeAltitudeKm = 100.0; + private double changeApoapsisKm = 100.0; + private double changePeriapsisKm = 70.0; + private double changeSmaKm = 700.0; + private double changeInclinationDeg = 0.0; + private double changeLanDeg = 0.0; + private double changeEccentricity = 0.5; + private double resonanceNumerator = 2.0; + private double resonanceDenominator = 1.0; + private double interceptIntervalSeconds = 3600.0; + private double moonReturnAltitudeKm = 100.0; + private double fineTuneDistanceKm = 100.0; + private double surfaceLongitudeDeg = 0.0; + private double courseCorrectionPeKm = 50.0; + + // LEGACY: Hohmann state - no longer used, wrapper uses MechJeb's OperationGeneric directly + // Keeping for reference only - these fields are not used after wrapper conversion + // private object genericTransferOperation; + // private bool genericCapture = true; + // private bool genericPlanCapture = true; + // private bool genericRendezvous = true; + // private bool genericCoplanar = false; + // private double genericLagTime = 0.0; + + // LEGACY: advancedTransferOperation not used - wrapper uses MechJeb's static array + // Display cache variables still needed for UI refresh + private bool advancedTransferSelectLowestDV = true; // UI state for radio button display + private double advancedTransferDeltaV = 0.0; // Cached for display + private double advancedTransferDepartureUT = 0.0; // Cached for display + private double advancedTransferDuration = 0.0; // Cached for display + + // Stage stats update timing + private double lastStageStatsUpdateUT = 0.0; + + // Menu stacks for navigation + private Stack menuStack = new Stack(); + + // Tracked menu items that need dynamic state updates + private List trackedItems = new List(); + + // Currently focused editable field + private TextMenu.Item editingItem = null; + private string editBuffer = ""; + private bool isEditing = false; + #endregion + + #region Tracked Item Classes + private class TrackedMenuItem + { + public TextMenu.Item item; + public string id; + public Func isEnabled; + public Func isSelected; + public Func getLabel; + public Func getValue; + public Action action; + public bool isValueItem; + public Func getNumber; + public Action setNumber; + public double step; + public bool hasMin; + public double min; + public bool hasMax; + public double max; + } + #endregion + + #region Initialization + public void Start() + { + MechJebProxy.Initialize(); + + if (!MechJebProxy.IsAvailable) + { + JUtil.LogMessage(this, "MechJeb not available: {0}", MechJebProxy.InitializationError ?? "Unknown"); + return; + } + + BuildMenus(); + } + + private void BuildMenus() + { + topMenu = new TextMenu(); + topMenu.labelColor = JUtil.ColorToColorTag(Color.white); + topMenu.selectedColor = JUtil.ColorToColorTag(Color.green); + topMenu.disabledColor = JUtil.ColorToColorTag(Color.gray); + + // Add all main menu entries + AddMenuItem(topMenu, "Attitude Control (SmartASS)", () => PushMenu(BuildSmartASSMenu())); + AddMenuItem(topMenu, "Ascent Guidance", () => PushMenu(BuildAscentMenu()), + core => vessel != null && IsAscentAvailable(vessel)); + AddMenuItem(topMenu, "Landing Guidance", () => PushMenu(BuildLandingMenu()), + core => vessel != null && !vessel.LandedOrSplashed); + AddMenuItem(topMenu, "Maneuver Planner", () => PushMenu(BuildManeuverPlannerMenu())); + AddMenuItem(topMenu, "Node Editor", () => PushMenu(BuildNodeEditorMenu()), + core => vessel != null && vessel.patchedConicSolver != null && + vessel.patchedConicSolver.maneuverNodes.Count > 0); + AddMenuItem(topMenu, "Execute Node", () => ExecuteNode(), + core => vessel != null && vessel.patchedConicSolver != null && + vessel.patchedConicSolver.maneuverNodes.Count > 0); + AddMenuItem(topMenu, "Rendezvous", () => PushMenu(BuildRendezvousMenu()), + core => FlightGlobals.fetch.VesselTarget != null); + AddMenuItem(topMenu, "Docking Guidance", () => PushMenu(BuildDockingMenu()), + core => FlightGlobals.fetch.VesselTarget != null); + AddMenuItem(topMenu, "Translatron", () => PushMenu(BuildTranslatronMenu())); + AddMenuItem(topMenu, "Rover Autopilot", () => PushMenu(BuildRoverMenu()), + core => vessel != null && vessel.Landed); + AddMenuItem(topMenu, "Aircraft Autopilot", () => PushMenu(BuildAircraftMenu()), + core => vessel != null && vessel.atmDensity > 0); + AddMenuItem(topMenu, "Spaceplane Guidance", () => PushMenu(BuildSpaceplaneMenu()), + core => vessel != null && vessel.atmDensity > 0); + AddMenuItem(topMenu, "Utilities", () => PushMenu(BuildUtilitiesMenu())); + AddMenuItem(topMenu, "Info Display", () => PushMenu(BuildInfoMenu())); + AddMenuItem(topMenu, "Settings", () => PushMenu(BuildSettingsMenu())); + + currentMenu = topMenu; + } + + private void AddMenuItem(TextMenu menu, string label, Action action, + Func enabledCheck = null) + { + Action menuAction = null; + if (action != null) + { + menuAction = (idx, menuItem) => action(); + } + + var newItem = new TextMenu.Item(label, menuAction); + menu.Add(newItem); + + if (enabledCheck != null) + { + trackedItems.Add(new TrackedMenuItem + { + item = newItem, + id = label, + isEnabled = enabledCheck + }); + } + } + + // Overload for dynamic labels that update on refresh + private void AddMenuItem(TextMenu menu, Func labelFunc, Action action, + Func enabledCheck = null) + { + string initialLabel = labelFunc(); + Action menuAction = null; + if (action != null) + { + menuAction = (idx, menuItem) => action(); + } + + var newItem = new TextMenu.Item(initialLabel, menuAction); + menu.Add(newItem); + + trackedItems.Add(new TrackedMenuItem + { + item = newItem, + id = "DynamicLabel_" + initialLabel, + isEnabled = enabledCheck ?? (core => true), + getLabel = core => labelFunc() + }); + } + + private void AddToggleItem(TextMenu menu, string label, + Func getValue, Action setValue, + Func enabledCheck = null) + { + Action toggleAction = (idx, menuItem) => + { + if (mjCore == null) return; + bool current = getValue(mjCore); + setValue(mjCore, !current); + UpdateTrackedItems(); + }; + + // Use color highlighting for toggles - green when enabled, normal when disabled + // No checkbox prefix needed since RPM interprets [text] as color tags + var newItem = new TextMenu.Item(label, toggleAction); + menu.Add(newItem); + + trackedItems.Add(new TrackedMenuItem + { + item = newItem, + id = label, + isEnabled = enabledCheck ?? (core => true), + isSelected = getValue // This makes the item green when checked + }); + } + + private void AddValueItem(TextMenu menu, string label, + Func getValue, Action setValue, + Func enabledCheck = null) + { + Action editAction = (idx, menuItem) => + { + // Start editing mode + // For now, cycle through preset values or implement number input + }; + + var newItem = new TextMenu.Item(label, editAction); + menu.Add(newItem); + + trackedItems.Add(new TrackedMenuItem + { + item = newItem, + id = label, + isEnabled = enabledCheck ?? (core => true), + getValue = getValue, + getLabel = core => label + ": " + getValue(core) + }); + } + + private void AddNumericItem(TextMenu menu, string label, + Func getValue, Action setValue, + double step, Func format, + Func enabledCheck = null, + bool hasMin = false, double min = 0, + bool hasMax = false, double max = 0) + { + Action editAction = (idx, menuItem) => + { + // Enter key toggles edit mode; actual changes use left/right + }; + + var newItem = new TextMenu.Item(label, editAction); + menu.Add(newItem); + + trackedItems.Add(new TrackedMenuItem + { + item = newItem, + id = label, + isEnabled = enabledCheck ?? (core => true), + isValueItem = true, + getNumber = getValue, + setNumber = setValue, + step = step, + hasMin = hasMin, + min = min, + hasMax = hasMax, + max = max, + getLabel = core => label + ": " + format(getValue(core)) + }); + } + #endregion + + #region SmartASS Menu + private TextMenu BuildSmartASSMenu() + { + var menu = new TextMenu(); + menu.labelColor = JUtil.ColorToColorTag(Color.white); + menu.selectedColor = JUtil.ColorToColorTag(Color.green); + menu.disabledColor = JUtil.ColorToColorTag(Color.gray); + + AddMenuItem(menu, "[MODE: ORBITAL]", () => PushMenu(BuildSmartASSOrbitalMenu())); + AddMenuItem(menu, "[MODE: SURFACE]", () => PushMenu(BuildSmartASSSurfaceMenu())); + AddMenuItem(menu, "[MODE: TARGET]", () => PushMenu(BuildSmartASSTargetMenu()), + core => FlightGlobals.fetch.VesselTarget != null); + AddMenuItem(menu, "[MODE: ADVANCED]", () => PushMenu(BuildSmartASSAdvancedMenu())); + AddMenuItem(menu, "[MODE: AUTO]", () => SetSmartASSAuto()); + AddMenuItem(menu, "------", null); + AddMenuItem(menu, "OFF", () => SetSmartASSTarget(MechJebProxy.Target.OFF)); + AddMenuItem(menu, "KILL ROTATION", () => SetSmartASSTarget(MechJebProxy.Target.KILLROT)); + AddMenuItem(menu, "[BACK]", () => PopMenu()); + + return menu; + } + + private TextMenu BuildSmartASSOrbitalMenu() + { + var menu = new TextMenu(); + menu.labelColor = JUtil.ColorToColorTag(Color.white); + menu.selectedColor = JUtil.ColorToColorTag(Color.green); + menu.disabledColor = JUtil.ColorToColorTag(Color.gray); + + smartassOrbitalMenu = menu; + + menu.Add(new TextMenu.Item("PROGRADE", (idx, item) => SetSmartASSTarget(MechJebProxy.Target.PROGRADE), (int)MechJebProxy.Target.PROGRADE)); + menu.Add(new TextMenu.Item("RETROGRADE", (idx, item) => SetSmartASSTarget(MechJebProxy.Target.RETROGRADE), (int)MechJebProxy.Target.RETROGRADE)); + menu.Add(new TextMenu.Item("NORMAL+", (idx, item) => SetSmartASSTarget(MechJebProxy.Target.NORMAL_PLUS), (int)MechJebProxy.Target.NORMAL_PLUS)); + menu.Add(new TextMenu.Item("NORMAL-", (idx, item) => SetSmartASSTarget(MechJebProxy.Target.NORMAL_MINUS), (int)MechJebProxy.Target.NORMAL_MINUS)); + menu.Add(new TextMenu.Item("RADIAL+", (idx, item) => SetSmartASSTarget(MechJebProxy.Target.RADIAL_PLUS), (int)MechJebProxy.Target.RADIAL_PLUS)); + menu.Add(new TextMenu.Item("RADIAL-", (idx, item) => SetSmartASSTarget(MechJebProxy.Target.RADIAL_MINUS), (int)MechJebProxy.Target.RADIAL_MINUS)); + AddMenuItem(menu, "NODE", () => SetSmartASSTarget(MechJebProxy.Target.NODE), + core => vessel != null && vessel.patchedConicSolver != null && + vessel.patchedConicSolver.maneuverNodes.Count > 0); + AddMenuItem(menu, "------", null); + AddToggleItem(menu, "Force Roll", + core => MechJebProxy.GetSmartASSForceRoll(MechJebProxy.GetSmartASS(core)), + (core, val) => MechJebProxy.SetSmartASSForceRoll(MechJebProxy.GetSmartASS(core), val)); + AddMenuItem(menu, "[BACK]", () => PopMenu()); + + return menu; + } + + private TextMenu BuildSmartASSSurfaceMenu() + { + var menu = new TextMenu(); + menu.labelColor = JUtil.ColorToColorTag(Color.white); + menu.selectedColor = JUtil.ColorToColorTag(Color.green); + menu.disabledColor = JUtil.ColorToColorTag(Color.gray); + + smartassSurfaceMenu = menu; + + menu.Add(new TextMenu.Item("SURFACE PROGRADE", (idx, item) => SetSmartASSTarget(MechJebProxy.Target.SURFACE_PROGRADE), (int)MechJebProxy.Target.SURFACE_PROGRADE)); + menu.Add(new TextMenu.Item("SURFACE RETROGRADE", (idx, item) => SetSmartASSTarget(MechJebProxy.Target.SURFACE_RETROGRADE), (int)MechJebProxy.Target.SURFACE_RETROGRADE)); + menu.Add(new TextMenu.Item("HORIZONTAL+", (idx, item) => SetSmartASSTarget(MechJebProxy.Target.HORIZONTAL_PLUS), (int)MechJebProxy.Target.HORIZONTAL_PLUS)); + menu.Add(new TextMenu.Item("HORIZONTAL-", (idx, item) => SetSmartASSTarget(MechJebProxy.Target.HORIZONTAL_MINUS), (int)MechJebProxy.Target.HORIZONTAL_MINUS)); + menu.Add(new TextMenu.Item("VERTICAL+", (idx, item) => SetSmartASSTarget(MechJebProxy.Target.VERTICAL_PLUS), (int)MechJebProxy.Target.VERTICAL_PLUS)); + menu.Add(new TextMenu.Item("SURFACE", (idx, item) => SetSmartASSTarget(MechJebProxy.Target.SURFACE), (int)MechJebProxy.Target.SURFACE)); + AddMenuItem(menu, "------", null); + AddNumericItem(menu, "Heading", + core => MechJebProxy.GetSmartASSSurfaceHeading(MechJebProxy.GetSmartASS(core)), + (core, val) => MechJebProxy.SetSmartASSSurfaceHeading(MechJebProxy.GetSmartASS(core), val), + 1.0, v => v.ToString("F1") + "°", null, true, 0, true, 360); + AddNumericItem(menu, "Pitch", + core => MechJebProxy.GetSmartASSSurfacePitch(MechJebProxy.GetSmartASS(core)), + (core, val) => MechJebProxy.SetSmartASSSurfacePitch(MechJebProxy.GetSmartASS(core), val), + 1.0, v => v.ToString("F1") + "°", null, true, -90, true, 90); + AddNumericItem(menu, "Roll", + core => MechJebProxy.GetSmartASSSurfaceRoll(MechJebProxy.GetSmartASS(core)), + (core, val) => MechJebProxy.SetSmartASSSurfaceRoll(MechJebProxy.GetSmartASS(core), val), + 1.0, v => v.ToString("F1") + "°", null, true, 0, true, 360); + AddMenuItem(menu, "[BACK]", () => PopMenu()); + + return menu; + } + + private TextMenu BuildSmartASSTargetMenu() + { + var menu = new TextMenu(); + menu.labelColor = JUtil.ColorToColorTag(Color.white); + menu.selectedColor = JUtil.ColorToColorTag(Color.green); + menu.disabledColor = JUtil.ColorToColorTag(Color.gray); + + smartassTargetMenu = menu; + + menu.Add(new TextMenu.Item("TARGET+", (idx, item) => SetSmartASSTarget(MechJebProxy.Target.TARGET_PLUS), (int)MechJebProxy.Target.TARGET_PLUS)); + menu.Add(new TextMenu.Item("TARGET-", (idx, item) => SetSmartASSTarget(MechJebProxy.Target.TARGET_MINUS), (int)MechJebProxy.Target.TARGET_MINUS)); + menu.Add(new TextMenu.Item("RELATIVE VEL+", (idx, item) => SetSmartASSTarget(MechJebProxy.Target.RELATIVE_PLUS), (int)MechJebProxy.Target.RELATIVE_PLUS)); + menu.Add(new TextMenu.Item("RELATIVE VEL-", (idx, item) => SetSmartASSTarget(MechJebProxy.Target.RELATIVE_MINUS), (int)MechJebProxy.Target.RELATIVE_MINUS)); + menu.Add(new TextMenu.Item("PARALLEL+", (idx, item) => SetSmartASSTarget(MechJebProxy.Target.PARALLEL_PLUS), (int)MechJebProxy.Target.PARALLEL_PLUS)); + menu.Add(new TextMenu.Item("PARALLEL-", (idx, item) => SetSmartASSTarget(MechJebProxy.Target.PARALLEL_MINUS), (int)MechJebProxy.Target.PARALLEL_MINUS)); + AddMenuItem(menu, "[BACK]", () => PopMenu()); + + return menu; + } + + private TextMenu BuildSmartASSAdvancedMenu() + { + var menu = new TextMenu(); + menu.labelColor = JUtil.ColorToColorTag(Color.white); + menu.selectedColor = JUtil.ColorToColorTag(Color.green); + menu.disabledColor = JUtil.ColorToColorTag(Color.gray); + + AddMenuItem(menu, "Set ADVANCED Mode", () => SetSmartASSTarget(MechJebProxy.Target.ADVANCED)); + var refItem = new TextMenu.Item("Reference: [ORBIT]", (idx, item) => CycleSmartASSAdvancedReference(1)); + menu.Add(refItem); + trackedItems.Add(new TrackedMenuItem + { + item = refItem, + id = "SmartASSAdvReference", + isEnabled = core => true, + getLabel = core => "Reference: [" + MechJebProxy.GetSmartASSAdvancedReferenceName(MechJebProxy.GetSmartASS(core)) + "]" + }); + + var dirItem = new TextMenu.Item("Direction: [FORWARD]", (idx, item) => CycleSmartASSAdvancedDirection(1)); + menu.Add(dirItem); + trackedItems.Add(new TrackedMenuItem + { + item = dirItem, + id = "SmartASSAdvDirection", + isEnabled = core => true, + getLabel = core => "Direction: [" + MechJebProxy.GetSmartASSAdvancedDirectionName(MechJebProxy.GetSmartASS(core)) + "]" + }); + AddToggleItem(menu, "Force Roll", + core => MechJebProxy.GetSmartASSForceRoll(MechJebProxy.GetSmartASS(core)), + (core, val) => MechJebProxy.SetSmartASSForceRoll(MechJebProxy.GetSmartASS(core), val)); + AddNumericItem(menu, "Roll Angle", + core => MechJebProxy.GetSmartASSRoll(MechJebProxy.GetSmartASS(core)), + (core, val) => MechJebProxy.SetSmartASSRoll(MechJebProxy.GetSmartASS(core), val), + 1.0, v => v.ToString("F1") + "°", null, true, 0, true, 360); + AddMenuItem(menu, "[BACK]", () => PopMenu()); + + return menu; + } + + private void SetSmartASSAuto() + { + SetSmartASSTarget(MechJebProxy.Target.AUTO); + } + + private void SetSmartASSTarget(MechJebProxy.Target target) + { + if (mjCore == null) return; + var smartass = MechJebProxy.GetSmartASS(mjCore); + MechJebProxy.SetSmartASSTarget(smartass, target); + } + + private void CycleSmartASSAdvancedReference(int direction) + { + if (mjCore == null) return; + var smartass = MechJebProxy.GetSmartASS(mjCore); + MechJebProxy.CycleSmartASSAdvancedReference(smartass, direction); + } + + private void CycleSmartASSAdvancedDirection(int direction) + { + if (mjCore == null) return; + var smartass = MechJebProxy.GetSmartASS(mjCore); + MechJebProxy.CycleSmartASSAdvancedDirection(smartass, direction); + } + #endregion + + #region Ascent Menu + private TextMenu BuildAscentMenu() + { + var menu = new TextMenu(); + menu.labelColor = JUtil.ColorToColorTag(Color.white); + menu.selectedColor = JUtil.ColorToColorTag(Color.green); + menu.disabledColor = JUtil.ColorToColorTag(Color.gray); + + AddToggleItem(menu, "ENGAGE Ascent Autopilot", + core => MechJebProxy.IsAscentAutopilotEngaged(core), + (core, val) => MechJebProxy.SetAscentAutopilotEngaged(core, val, this)); + + AddMenuItem(menu, "------", null); + + // Orbit parameters + AddNumericItem(menu, "Target Altitude", + core => MechJebProxy.GetAscentAltitude(core) / 1000.0, + (core, val) => MechJebProxy.SetAscentAltitude(core, val * 1000.0), + 1.0, v => v.ToString("F1") + " km", null, true, 1.0, false, 0); + AddNumericItem(menu, "Target Inclination", + core => MechJebProxy.GetAscentInclination(core), + (core, val) => MechJebProxy.SetAscentInclination(core, val), + 0.5, v => v.ToString("F2") + "°", null, true, 0, true, 180); + AddMenuItem(menu, "Set to Current Inclination", () => + { + if (mjCore == null || vessel == null) return; + MechJebProxy.SetAscentInclination(mjCore, vessel.orbit.inclination); + }); + + AddMenuItem(menu, "------", null); + + // Sub-menus + AddMenuItem(menu, "Path Editor", () => PushMenu(BuildAscentPathMenu())); + AddMenuItem(menu, "Staging & Thrust", () => PushMenu(BuildAscentStagingMenu())); + AddMenuItem(menu, "Launch Parameters", () => PushMenu(BuildAscentLaunchMenu())); + AddMenuItem(menu, "Guidance & Safety", () => PushMenu(BuildAscentGuidanceMenu())); + + AddMenuItem(menu, "------", null); + + AddToggleItem(menu, "Auto-Warp", + core => MechJebProxy.GetAscentAutowarp(core), + (core, val) => MechJebProxy.SetAscentAutowarp(core, val)); + + AddMenuItem(menu, "[BACK]", () => PopMenu()); + + return menu; + } + + private TextMenu BuildAscentPathMenu() + { + var menu = new TextMenu(); + menu.labelColor = JUtil.ColorToColorTag(Color.white); + menu.selectedColor = JUtil.ColorToColorTag(Color.green); + menu.disabledColor = JUtil.ColorToColorTag(Color.gray); + + AddToggleItem(menu, "Automatic Altitude Turn", + core => MechJebProxy.GetAscentAutoPath(core), + (core, val) => MechJebProxy.SetAscentAutoPath(core, val)); + + AddNumericItem(menu, "Turn Start Alt", + core => MechJebProxy.GetAscentTurnStartAltitude(core) / 1000.0, + (core, val) => MechJebProxy.SetAscentTurnStartAltitude(core, val * 1000.0), + 1.0, v => v.ToString("F1") + " km", + core => !MechJebProxy.GetAscentAutoPath(core)); + + AddNumericItem(menu, "Turn Start Vel", + core => MechJebProxy.GetAscentTurnStartVelocity(core), + (core, val) => MechJebProxy.SetAscentTurnStartVelocity(core, val), + 10.0, v => v.ToString("F0") + " m/s", + core => !MechJebProxy.GetAscentAutoPath(core)); + + AddNumericItem(menu, "Turn End Alt", + core => MechJebProxy.GetAscentTurnEndAltitude(core) / 1000.0, + (core, val) => MechJebProxy.SetAscentTurnEndAltitude(core, val * 1000.0), + 1.0, v => v.ToString("F1") + " km", + core => !MechJebProxy.GetAscentAutoPath(core)); + + AddNumericItem(menu, "Final Flight Path Angle", + core => MechJebProxy.GetAscentTurnEndAngle(core), + (core, val) => MechJebProxy.SetAscentTurnEndAngle(core, val), + 0.5, v => v.ToString("F1") + "°"); + + AddNumericItem(menu, "Turn Shape", + core => MechJebProxy.GetAscentTurnShapeExponent(core), + (core, val) => MechJebProxy.SetAscentTurnShapeExponent(core, val), + 0.01, v => (v * 100.0).ToString("F0") + "%"); + + AddNumericItem(menu, "Auto Turn %", + core => MechJebProxy.GetAscentAutoTurnPerc(core) * 100.0, + (core, val) => MechJebProxy.SetAscentAutoTurnPerc(core, val / 100.0), + 0.5, v => v.ToString("F1") + "%", + core => MechJebProxy.GetAscentAutoPath(core), true, 0.5, true, 105.0); + + AddNumericItem(menu, "Auto Turn Spd", + core => MechJebProxy.GetAscentAutoTurnSpdFactor(core), + (core, val) => MechJebProxy.SetAscentAutoTurnSpdFactor(core, val), + 0.5, v => v.ToString("F1"), + core => MechJebProxy.GetAscentAutoPath(core), true, 4.0, true, 80.0); + + AddMenuItem(menu, "[BACK]", () => PopMenu()); + + return menu; + } + + private TextMenu BuildAscentStagingMenu() + { + var menu = new TextMenu(); + menu.labelColor = JUtil.ColorToColorTag(Color.white); + menu.selectedColor = JUtil.ColorToColorTag(Color.green); + menu.disabledColor = JUtil.ColorToColorTag(Color.gray); + + AddToggleItem(menu, "Autostage", + core => MechJebProxy.GetAscentAutostage(core), + (core, val) => MechJebProxy.SetAscentAutostage(core, val)); + AddNumericItem(menu, "Stop at Stage", + core => MechJebProxy.GetAutostageLimit(core), + (core, val) => MechJebProxy.SetAutostageLimit(core, (int)val), + 1.0, v => v.ToString("F0"), null, true, 0, false, 0); + + AddMenuItem(menu, "------", null); + + AddToggleItem(menu, "Limit to Prevent Overheats", + core => MechJebProxy.GetLimitToPreventOverheats(core), + (core, val) => MechJebProxy.SetLimitToPreventOverheats(core, val)); + AddToggleItem(menu, "Limit by Max Q", + core => MechJebProxy.GetLimitToMaxDynamicPressure(core), + (core, val) => MechJebProxy.SetLimitToMaxDynamicPressure(core, val)); + AddNumericItem(menu, "Max Q", + core => MechJebProxy.GetMaxDynamicPressure(core), + (core, val) => MechJebProxy.SetMaxDynamicPressure(core, val), + 1000.0, v => v.ToString("F0") + " Pa", null, true, 0, false, 0); + AddToggleItem(menu, "Limit Acceleration", + core => MechJebProxy.GetLimitAcceleration(core), + (core, val) => MechJebProxy.SetLimitAcceleration(core, val)); + AddNumericItem(menu, "Max Acceleration", + core => MechJebProxy.GetMaxAcceleration(core), + (core, val) => MechJebProxy.SetMaxAcceleration(core, val), + 0.1, v => v.ToString("F1") + " m/s²", null, true, 0, false, 0); + AddToggleItem(menu, "Limit Throttle", + core => MechJebProxy.GetLimitThrottle(core), + (core, val) => MechJebProxy.SetLimitThrottle(core, val)); + AddNumericItem(menu, "Max Throttle", + core => MechJebProxy.GetMaxThrottle(core), + (core, val) => MechJebProxy.SetMaxThrottle(core, val), + 1.0, v => v.ToString("F0") + "%", null, true, 0, true, 100); + + AddMenuItem(menu, "[BACK]", () => PopMenu()); + + return menu; + } + + private TextMenu BuildAscentLaunchMenu() + { + var menu = new TextMenu(); + menu.labelColor = JUtil.ColorToColorTag(Color.white); + menu.selectedColor = JUtil.ColorToColorTag(Color.green); + menu.disabledColor = JUtil.ColorToColorTag(Color.gray); + + AddNumericItem(menu, "Desired LAN", + core => MechJebProxy.GetAscentLAN(core), + (core, val) => MechJebProxy.SetAscentLAN(core, val), + 0.5, v => v.ToString("F2") + "°", null, true, 0, true, 360); + AddNumericItem(menu, "Launch Phase Angle", + core => MechJebProxy.GetAscentLaunchPhaseAngle(core), + (core, val) => MechJebProxy.SetAscentLaunchPhaseAngle(core, val), + 0.5, v => v.ToString("F2") + "°", null, true, -360, true, 360); + AddNumericItem(menu, "Launch LAN Difference", + core => MechJebProxy.GetAscentLaunchLANDifference(core), + (core, val) => MechJebProxy.SetAscentLaunchLANDifference(core, val), + 0.5, v => v.ToString("F2") + "°", null, true, -360, true, 360); + + AddMenuItem(menu, "------", null); + + AddNumericItem(menu, "Warp Countdown", + core => MechJebProxy.GetAscentWarpCountdown(core), + (core, val) => MechJebProxy.SetAscentWarpCountdown(core, (int)val), + 1.0, v => v.ToString("F0") + " s", null, true, 0, false, 0); + AddToggleItem(menu, "Skip Circularization", + core => MechJebProxy.GetAscentSkipCircularization(core), + (core, val) => MechJebProxy.SetAscentSkipCircularization(core, val)); + + AddMenuItem(menu, "[BACK]", () => PopMenu()); + return menu; + } + + private TextMenu BuildAscentGuidanceMenu() + { + var menu = new TextMenu(); + menu.labelColor = JUtil.ColorToColorTag(Color.white); + menu.selectedColor = JUtil.ColorToColorTag(Color.green); + menu.disabledColor = JUtil.ColorToColorTag(Color.gray); + + AddToggleItem(menu, "Force Roll", + core => MechJebProxy.GetAscentForceRoll(core), + (core, val) => MechJebProxy.SetAscentForceRoll(core, val)); + AddNumericItem(menu, "Vertical Roll", + core => MechJebProxy.GetAscentVerticalRoll(core), + (core, val) => MechJebProxy.SetAscentVerticalRoll(core, val), + 1.0, v => v.ToString("F1") + "°", null, true, -180, true, 180); + AddNumericItem(menu, "Turn Roll", + core => MechJebProxy.GetAscentTurnRoll(core), + (core, val) => MechJebProxy.SetAscentTurnRoll(core, val), + 1.0, v => v.ToString("F1") + "°", null, true, -180, true, 180); + AddNumericItem(menu, "Roll Altitude", + core => MechJebProxy.GetAscentRollAltitude(core) / 1000.0, + (core, val) => MechJebProxy.SetAscentRollAltitude(core, val * 1000.0), + 1.0, v => v.ToString("F1") + " km", null, true, 0, false, 0); + + AddMenuItem(menu, "------", null); + + AddToggleItem(menu, "Limit AoA", + core => MechJebProxy.GetAscentLimitAoA(core), + (core, val) => MechJebProxy.SetAscentLimitAoA(core, val)); + AddNumericItem(menu, "Max AoA", + core => MechJebProxy.GetAscentMaxAoA(core), + (core, val) => MechJebProxy.SetAscentMaxAoA(core, val), + 0.5, v => v.ToString("F1") + "°", null, true, 0, true, 45); + AddNumericItem(menu, "AoA Fadeout Pressure", + core => MechJebProxy.GetAscentAoAFadeoutPressure(core), + (core, val) => MechJebProxy.SetAscentAoAFadeoutPressure(core, val), + 100.0, v => v.ToString("F0") + " Pa", null, true, 0, false, 0); + + AddMenuItem(menu, "------", null); + + AddToggleItem(menu, "Corrective Steering", + core => MechJebProxy.GetAscentCorrectiveSteering(core), + (core, val) => MechJebProxy.SetAscentCorrectiveSteering(core, val)); + AddNumericItem(menu, "Corrective Gain", + core => MechJebProxy.GetAscentCorrectiveSteeringGain(core), + (core, val) => MechJebProxy.SetAscentCorrectiveSteeringGain(core, val), + 0.1, v => v.ToString("F2")); + + AddMenuItem(menu, "[BACK]", () => PopMenu()); + return menu; + } + #endregion + + #region Landing Menu + private TextMenu BuildLandingMenu() + { + var menu = new TextMenu(); + menu.labelColor = JUtil.ColorToColorTag(Color.white); + menu.selectedColor = JUtil.ColorToColorTag(Color.green); + menu.disabledColor = JUtil.ColorToColorTag(Color.gray); + + // Actions + AddMenuItem(menu, "Land at Target", () => LandAtTarget(), + core => MechJebProxy.PositionTargetExists(core)); + AddMenuItem(menu, "Land Somewhere", () => LandSomewhere()); + AddMenuItem(menu, "STOP", () => StopLanding(), + core => MechJebProxy.IsLandingAutopilotEngaged(core)); + + AddMenuItem(menu, "------", null); + + // Targeting + AddMenuItem(menu, "Pick Target on Map", () => PickTargetOnMap()); + AddNumericItem(menu, "Target Latitude", + core => MechJebProxy.GetTargetLatitude(core), + (core, val) => MechJebProxy.SetTargetLatitude(core, vessel != null ? vessel.mainBody : null, val), + 0.1, v => v.ToString("F3") + "°", null, true, -90, true, 90); + AddNumericItem(menu, "Target Longitude", + core => MechJebProxy.GetTargetLongitude(core), + (core, val) => MechJebProxy.SetTargetLongitude(core, vessel != null ? vessel.mainBody : null, val), + 0.1, v => v.ToString("F3") + "°", null, true, -180, true, 180); + + AddMenuItem(menu, "------", null); + + // Settings + AddNumericItem(menu, "Touchdown Speed", + core => MechJebProxy.GetLandingTouchdownSpeed(core), + (core, val) => MechJebProxy.SetLandingTouchdownSpeed(core, val), + 0.5, v => v.ToString("F1") + " m/s", null, true, 0, false, 0); + AddToggleItem(menu, "Deploy Gear", + core => MechJebProxy.GetLandingDeployGears(core), + (core, val) => MechJebProxy.SetLandingDeployGears(core, val)); + AddToggleItem(menu, "Deploy Chutes", + core => MechJebProxy.GetLandingDeployChutes(core), + (core, val) => MechJebProxy.SetLandingDeployChutes(core, val)); + AddNumericItem(menu, "Limit Gear Stage", + core => MechJebProxy.GetLandingLimitGearsStage(core), + (core, val) => MechJebProxy.SetLandingLimitGearsStage(core, (int)val), + 1.0, v => v.ToString("F0"), null, true, 0, false, 0); + AddNumericItem(menu, "Limit Chute Stage", + core => MechJebProxy.GetLandingLimitChutesStage(core), + (core, val) => MechJebProxy.SetLandingLimitChutesStage(core, (int)val), + 1.0, v => v.ToString("F0"), null, true, 0, false, 0); + AddToggleItem(menu, "Use RCS", + core => MechJebProxy.GetLandingUseRCS(core), + (core, val) => MechJebProxy.SetLandingUseRCS(core, val)); + + AddMenuItem(menu, "------", null); + + // Predictions sub-menu + AddMenuItem(menu, "Predictions Info", () => PushMenu(BuildLandingPredictionsMenu())); + + AddMenuItem(menu, "[BACK]", () => PopMenu()); + + return menu; + } + + private TextMenu BuildLandingPredictionsMenu() + { + var menu = new TextMenu(); + menu.labelColor = JUtil.ColorToColorTag(Color.white); + menu.selectedColor = JUtil.ColorToColorTag(Color.green); + menu.disabledColor = JUtil.ColorToColorTag(Color.gray); + + AddToggleItem(menu, "Show Trajectory", + core => MechJebProxy.GetLandingShowTrajectory(core), + (core, val) => MechJebProxy.SetLandingShowTrajectory(core, val)); + + AddMenuItem(menu, "------", null); + + // These are info items, will be updated dynamically + AddMenuItem(menu, "Predicted Landing:", null); + var latItem = new TextMenu.Item(" Lat: ---", null); + var lonItem = new TextMenu.Item(" Lon: ---", null); + var timeItem = new TextMenu.Item(" Time: ---", null); + var geesItem = new TextMenu.Item(" Max Gees: ---", null); + menu.Add(latItem); + menu.Add(lonItem); + menu.Add(timeItem); + menu.Add(geesItem); + + trackedItems.Add(new TrackedMenuItem + { + item = latItem, + id = "LandingPredLat", + isEnabled = core => true, + getLabel = core => " Lat: " + GetLandingPredLatitude(core) + }); + trackedItems.Add(new TrackedMenuItem + { + item = lonItem, + id = "LandingPredLon", + isEnabled = core => true, + getLabel = core => " Lon: " + GetLandingPredLongitude(core) + }); + trackedItems.Add(new TrackedMenuItem + { + item = timeItem, + id = "LandingPredTime", + isEnabled = core => true, + getLabel = core => " Time: " + GetLandingPredTime(core) + }); + trackedItems.Add(new TrackedMenuItem + { + item = geesItem, + id = "LandingPredGees", + isEnabled = core => true, + getLabel = core => " Max Gees: " + GetLandingPredGees(core) + }); + + AddMenuItem(menu, "[BACK]", () => PopMenu()); + + return menu; + } + + private void LandAtTarget() + { + if (mjCore == null) return; + MechJebProxy.LandAtPositionTarget(mjCore); + } + + private void LandSomewhere() + { + if (mjCore == null) return; + MechJebProxy.LandUntargeted(mjCore); + } + + private void StopLanding() + { + if (mjCore == null) return; + MechJebProxy.StopLanding(mjCore); + } + + private void PickTargetOnMap() + { + if (mjCore == null) return; + MechJebProxy.PickPositionTargetOnMap(mjCore); + } + #endregion + + #region Maneuver Planner Menu + // Menu matching IMGUI Maneuver Planner exactly + private TextMenu BuildManeuverPlannerMenu() + { + var menu = new TextMenu(); + menu.labelColor = JUtil.ColorToColorTag(Color.white); + menu.selectedColor = JUtil.ColorToColorTag(Color.green); + menu.disabledColor = JUtil.ColorToColorTag(Color.gray); + + // Match exact IMGUI dropdown order (alphabetical) + AddMenuItem(menu, "advanced transfer to another planet", () => PushMenu(BuildAdvancedTransferMenu()), + core => FlightGlobals.fetch.VesselTarget is CelestialBody); + AddMenuItem(menu, "change apoapsis", () => PushMenu(BuildChangeApoapsisMenu())); + AddMenuItem(menu, "change both Pe and Ap", () => PushMenu(BuildChangeBothPeApMenu())); + AddMenuItem(menu, "change eccentricity", () => PushMenu(BuildChangeEccentricityMenu())); + AddMenuItem(menu, "change inclination", () => PushMenu(BuildChangeInclinationMenu())); + AddMenuItem(menu, "change longitude of ascending node", () => PushMenu(BuildChangeLANMenu())); + AddMenuItem(menu, "change periapsis", () => PushMenu(BuildChangePeriapsisMenu())); + AddMenuItem(menu, "change semi-major axis", () => PushMenu(BuildChangeSMAMenu())); + AddMenuItem(menu, "change surface longitude of apsis", () => PushMenu(BuildChangeSurfaceLongitudeMenu())); + AddMenuItem(menu, "circularize", () => PushMenu(BuildCircularizeMenu())); + AddMenuItem(menu, "fine tune closest approach to target", () => PushMenu(BuildFineTuneClosestApproachMenu()), + core => FlightGlobals.fetch.VesselTarget != null); + AddMenuItem(menu, "intercept target at chosen time", () => PushMenu(BuildInterceptAtTimeMenu()), + core => FlightGlobals.fetch.VesselTarget != null); + AddMenuItem(menu, "match planes with target", () => PushMenu(BuildMatchPlanesMenu()), + core => FlightGlobals.fetch.VesselTarget != null); + AddMenuItem(menu, "match velocities with target", () => PushMenu(BuildMatchVelocitiesMenu()), + core => FlightGlobals.fetch.VesselTarget != null); + AddMenuItem(menu, "resonant orbit", () => PushMenu(BuildResonantOrbitMenu())); + AddMenuItem(menu, "return from a moon", () => PushMenu(BuildMoonReturnMenu()), + core => vessel != null && vessel.mainBody != null && vessel.mainBody.referenceBody != null && + vessel.mainBody.referenceBody != Planetarium.fetch.Sun); + AddMenuItem(menu, "transfer to another planet", () => PushMenu(BuildInterplanetaryTransferMenu()), + core => FlightGlobals.fetch.VesselTarget is CelestialBody); + AddMenuItem(menu, "two impulse (Hohmann) transfer to target", () => PushMenu(BuildHohmannMenu()), + core => FlightGlobals.fetch.VesselTarget != null); + + AddMenuItem(menu, "------", null); + AddMenuItem(menu, "Remove ALL nodes", () => RemoveAllNodes()); + AddMenuItem(menu, "------", null); + + // Node execution controls (matching IMGUI bottom controls) + AddToggleItem(menu, "Auto-warp", + core => MechJebProxy.GetNodeAutowarp(core), + (core, val) => MechJebProxy.SetNodeAutowarp(core, val)); + AddNumericItem(menu, "Lead time", + core => MechJebProxy.GetNodeLeadTime(core), + (core, val) => MechJebProxy.SetNodeLeadTime(core, val), + 1.0, v => v.ToString("F0") + " s", null, true, 0, false, 0); + + AddMenuItem(menu, "[BACK]", () => PopMenu()); + + return menu; + } + + private TextMenu BuildCircularizeMenu() + { + var menu = new TextMenu(); + menu.labelColor = JUtil.ColorToColorTag(Color.white); + menu.selectedColor = JUtil.ColorToColorTag(Color.green); + menu.disabledColor = JUtil.ColorToColorTag(Color.gray); + + // Get MechJeb's circularize operation and its TimeSelector + object circOp = MechJebProxy.GetOperationByName("circularize"); + object timeSelector = MechJebProxy.GetOperationTimeSelector(circOp); + + AddMenuItem(menu, "Circularize at:", null); + + // TimeReference indices for circularize: 0=APOAPSIS, 1=PERIAPSIS, 2=X_FROM_NOW, 3=ALTITUDE + AddMenuItem(menu, " At Next Apoapsis", () => ExecuteCircularize(0)); + AddMenuItem(menu, " At Next Periapsis", () => ExecuteCircularize(1)); + + // Altitude option with editable value - reads/writes directly to MechJeb's TimeSelector + AddNumericItem(menu, " At Altitude", + core => MechJebProxy.GetTimeSelectorCircularizeAltitude( + MechJebProxy.GetOperationTimeSelector(MechJebProxy.GetOperationByName("circularize"))) / 1000.0, + (core, val) => MechJebProxy.SetTimeSelectorCircularizeAltitude( + MechJebProxy.GetOperationTimeSelector(MechJebProxy.GetOperationByName("circularize")), val * 1000.0), + 1.0, v => v.ToString("F1") + " km", null, true, 1.0, false, 0); + AddMenuItem(menu, " [Execute at Altitude]", () => ExecuteCircularize(3)); + + AddMenuItem(menu, "[BACK]", () => PopMenu()); + + return menu; + } + + /// + /// Executes the circularize operation using MechJeb's actual Operation instance. + /// This ensures perfect sync with IMGUI. + /// + /// TimeSelector index: 0=APOAPSIS, 1=PERIAPSIS, 2=X_FROM_NOW, 3=ALTITUDE + private void ExecuteCircularize(int timeRefIndex) + { + if (vessel == null || mjCore == null) return; + + // Get MechJeb's circularize operation + object circOp = MechJebProxy.GetOperationByName("circularize"); + if (circOp == null) return; + + // Get its TimeSelector and set the time reference + object timeSelector = MechJebProxy.GetOperationTimeSelector(circOp); + if (timeSelector != null) + { + MechJebProxy.SetTimeSelectorCurrentTimeRef(timeSelector, timeRefIndex); + } + + // Execute the operation using MechJeb's MakeNodes + MechJebProxy.ExecuteOperation(circOp, mjCore, vessel); + } + + private TextMenu BuildChangeApoapsisMenu() + { + var menu = new TextMenu(); + menu.labelColor = JUtil.ColorToColorTag(Color.white); + menu.selectedColor = JUtil.ColorToColorTag(Color.green); + menu.disabledColor = JUtil.ColorToColorTag(Color.gray); + + // OperationApoapsis: NewApA parameter, TimeRefs: PERIAPSIS(0), APOAPSIS(1), X_FROM_NOW(2), ALTITUDE(3), EQ_DESCENDING(4), EQ_ASCENDING(5) + AddNumericItem(menu, "New Apoapsis", + core => MechJebProxy.GetOperationEditableDouble( + MechJebProxy.GetOperationByName("change apoapsis"), "NewApA") / 1000.0, + (core, val) => MechJebProxy.SetOperationEditableDouble( + MechJebProxy.GetOperationByName("change apoapsis"), "NewApA", val * 1000.0), + 1.0, v => v.ToString("F1") + " km", null, true, 1.0, false, 0); + + AddMenuItem(menu, "Schedule the burn:", null); + AddMenuItem(menu, " at next periapsis", () => ExecuteChangeApoapsis(0)); + AddMenuItem(menu, " at next apoapsis", () => ExecuteChangeApoapsis(1)); + AddMenuItem(menu, " at an altitude", () => PushMenu(BuildTimeSelectorAltitudeMenu("change apoapsis"))); + AddMenuItem(menu, " after a fixed time", () => PushMenu(BuildTimeSelectorLeadTimeMenu("change apoapsis"))); + AddMenuItem(menu, " at the equatorial DN", () => ExecuteChangeApoapsis(4)); + AddMenuItem(menu, " at the equatorial AN", () => ExecuteChangeApoapsis(5)); + + AddMenuItem(menu, "[BACK]", () => PopMenu()); + + return menu; + } + + /// + /// Executes change apoapsis operation + /// TimeRef indices: PERIAPSIS(0), APOAPSIS(1), X_FROM_NOW(2), ALTITUDE(3), EQ_DESCENDING(4), EQ_ASCENDING(5) + /// + private void ExecuteChangeApoapsis(int timeRefIndex) + { + if (vessel == null || mjCore == null) return; + + object op = MechJebProxy.GetOperationByName("change apoapsis"); + if (op == null) return; + + object timeSelector = MechJebProxy.GetOperationTimeSelector(op); + if (timeSelector != null) + { + MechJebProxy.SetTimeSelectorCurrentTimeRef(timeSelector, timeRefIndex); + } + + MechJebProxy.ExecuteOperation(op, mjCore, vessel); + } + + /// + /// Builds a submenu for setting altitude timing and executing an operation + /// + private TextMenu BuildTimeSelectorAltitudeMenu(string operationName) + { + var menu = new TextMenu(); + menu.labelColor = JUtil.ColorToColorTag(Color.white); + menu.selectedColor = JUtil.ColorToColorTag(Color.green); + menu.disabledColor = JUtil.ColorToColorTag(Color.gray); + + AddNumericItem(menu, "At Altitude", + core => MechJebProxy.GetTimeSelectorCircularizeAltitude( + MechJebProxy.GetOperationTimeSelector(MechJebProxy.GetOperationByName(operationName))) / 1000.0, + (core, val) => MechJebProxy.SetTimeSelectorCircularizeAltitude( + MechJebProxy.GetOperationTimeSelector(MechJebProxy.GetOperationByName(operationName)), val * 1000.0), + 1.0, v => v.ToString("F1") + " km", null, true, 1.0, false, 0); + + // Find ALTITUDE index for this operation's TimeSelector + AddMenuItem(menu, "[Execute]", () => { + object op = MechJebProxy.GetOperationByName(operationName); + object ts = MechJebProxy.GetOperationTimeSelector(op); + // Set to ALTITUDE time reference (index 3 for most operations that support it) + MechJebProxy.SetTimeSelectorCurrentTimeRef(ts, 3); + MechJebProxy.ExecuteOperation(op, mjCore, vessel); + PopMenu(); + }); + + AddMenuItem(menu, "[BACK]", () => PopMenu()); + return menu; + } + + /// + /// Builds a submenu for setting lead time and executing an operation + /// + private TextMenu BuildTimeSelectorLeadTimeMenu(string operationName) + { + var menu = new TextMenu(); + menu.labelColor = JUtil.ColorToColorTag(Color.white); + menu.selectedColor = JUtil.ColorToColorTag(Color.green); + menu.disabledColor = JUtil.ColorToColorTag(Color.gray); + + AddNumericItem(menu, "Seconds from now", + core => MechJebProxy.GetTimeSelectorLeadTime( + MechJebProxy.GetOperationTimeSelector(MechJebProxy.GetOperationByName(operationName))), + (core, val) => MechJebProxy.SetTimeSelectorLeadTime( + MechJebProxy.GetOperationTimeSelector(MechJebProxy.GetOperationByName(operationName)), val), + 10.0, v => v.ToString("F0") + " s", null, true, 0, false, 0); + + // Find X_FROM_NOW index for this operation's TimeSelector + AddMenuItem(menu, "[Execute]", () => { + object op = MechJebProxy.GetOperationByName(operationName); + object ts = MechJebProxy.GetOperationTimeSelector(op); + // Set to X_FROM_NOW time reference (index 2 for most operations that support it) + MechJebProxy.SetTimeSelectorCurrentTimeRef(ts, 2); + MechJebProxy.ExecuteOperation(op, mjCore, vessel); + PopMenu(); + }); + + AddMenuItem(menu, "[BACK]", () => PopMenu()); + return menu; + } + + private TextMenu BuildChangePeriapsisMenu() + { + var menu = new TextMenu(); + menu.labelColor = JUtil.ColorToColorTag(Color.white); + menu.selectedColor = JUtil.ColorToColorTag(Color.green); + menu.disabledColor = JUtil.ColorToColorTag(Color.gray); + + // OperationPeriapsis: NewPeA parameter, TimeRefs: PERIAPSIS(0), APOAPSIS(1), X_FROM_NOW(2), ALTITUDE(3) + AddNumericItem(menu, "New Periapsis", + core => MechJebProxy.GetOperationEditableDouble( + MechJebProxy.GetOperationByName("change periapsis"), "NewPeA") / 1000.0, + (core, val) => MechJebProxy.SetOperationEditableDouble( + MechJebProxy.GetOperationByName("change periapsis"), "NewPeA", val * 1000.0), + 1.0, v => v.ToString("F1") + " km", null, true, 1.0, false, 0); + + AddMenuItem(menu, "Schedule the burn:", null); + AddMenuItem(menu, " at next periapsis", () => ExecuteChangePeriapsis(0)); + AddMenuItem(menu, " at next apoapsis", () => ExecuteChangePeriapsis(1)); + AddMenuItem(menu, " at an altitude", () => PushMenu(BuildTimeSelectorAltitudeMenu("change periapsis"))); + AddMenuItem(menu, " after a fixed time", () => PushMenu(BuildTimeSelectorLeadTimeMenu("change periapsis"))); + + AddMenuItem(menu, "[BACK]", () => PopMenu()); + + return menu; + } + + private void ExecuteChangePeriapsis(int timeRefIndex) + { + if (vessel == null || mjCore == null) return; + + object op = MechJebProxy.GetOperationByName("change periapsis"); + if (op == null) return; + + object timeSelector = MechJebProxy.GetOperationTimeSelector(op); + if (timeSelector != null) + { + MechJebProxy.SetTimeSelectorCurrentTimeRef(timeSelector, timeRefIndex); + } + + MechJebProxy.ExecuteOperation(op, mjCore, vessel); + } + + private TextMenu BuildChangeSMAMenu() + { + var menu = new TextMenu(); + menu.labelColor = JUtil.ColorToColorTag(Color.white); + menu.selectedColor = JUtil.ColorToColorTag(Color.green); + + // OperationSemiMajor: NewSma parameter, TimeRefs: PERIAPSIS(0), APOAPSIS(1), X_FROM_NOW(2), ALTITUDE(3) + AddNumericItem(menu, "New Semi-Major Axis", + core => MechJebProxy.GetOperationEditableDouble( + MechJebProxy.GetOperationByName("change semi-major axis"), "NewSma") / 1000.0, + (core, val) => MechJebProxy.SetOperationEditableDouble( + MechJebProxy.GetOperationByName("change semi-major axis"), "NewSma", val * 1000.0), + 1.0, v => v.ToString("F1") + " km", null, true, 1.0, false, 0); + + AddMenuItem(menu, "Schedule the burn:", null); + AddMenuItem(menu, " at next periapsis", () => ExecuteChangeSMA(0)); + AddMenuItem(menu, " at next apoapsis", () => ExecuteChangeSMA(1)); + AddMenuItem(menu, " at an altitude", () => PushMenu(BuildTimeSelectorAltitudeMenu("change semi-major axis"))); + AddMenuItem(menu, " after a fixed time", () => PushMenu(BuildTimeSelectorLeadTimeMenu("change semi-major axis"))); + + AddMenuItem(menu, "[BACK]", () => PopMenu()); + + return menu; + } + + private void ExecuteChangeSMA(int timeRefIndex) + { + if (vessel == null || mjCore == null) return; + + object op = MechJebProxy.GetOperationByName("change semi-major axis"); + if (op == null) return; + + object timeSelector = MechJebProxy.GetOperationTimeSelector(op); + if (timeSelector != null) + { + MechJebProxy.SetTimeSelectorCurrentTimeRef(timeSelector, timeRefIndex); + } + + MechJebProxy.ExecuteOperation(op, mjCore, vessel); + } + + private TextMenu BuildChangeInclinationMenu() + { + var menu = new TextMenu(); + menu.labelColor = JUtil.ColorToColorTag(Color.white); + menu.selectedColor = JUtil.ColorToColorTag(Color.green); + + // OperationInclination: NewInc parameter, TimeRefs: EQ_NEAREST_AD(0), EQ_HIGHEST_AD(1), X_FROM_NOW(2), APOAPSIS(3), PERIAPSIS(4) + AddNumericItem(menu, "New Inclination", + core => MechJebProxy.GetOperationEditableDouble( + MechJebProxy.GetOperationByName("change inclination"), "NewInc"), + (core, val) => MechJebProxy.SetOperationEditableDouble( + MechJebProxy.GetOperationByName("change inclination"), "NewInc", val), + 0.5, v => v.ToString("F1") + "°", null, true, -180, true, 180); + + AddMenuItem(menu, "Schedule the burn:", null); + AddMenuItem(menu, " at the nearest AN/DN", () => ExecuteChangeInclination(0)); + AddMenuItem(menu, " at the highest AN/DN", () => ExecuteChangeInclination(1)); + AddMenuItem(menu, " after a fixed time", () => PushMenu(BuildTimeSelectorLeadTimeMenu("change inclination"))); + AddMenuItem(menu, " at next apoapsis", () => ExecuteChangeInclination(3)); + AddMenuItem(menu, " at next periapsis", () => ExecuteChangeInclination(4)); + + AddMenuItem(menu, "[BACK]", () => PopMenu()); + + return menu; + } + + private void ExecuteChangeInclination(int timeRefIndex) + { + if (vessel == null || mjCore == null) return; + + object op = MechJebProxy.GetOperationByName("change inclination"); + if (op == null) return; + + object timeSelector = MechJebProxy.GetOperationTimeSelector(op); + if (timeSelector != null) + { + MechJebProxy.SetTimeSelectorCurrentTimeRef(timeSelector, timeRefIndex); + } + + MechJebProxy.ExecuteOperation(op, mjCore, vessel); + } + + private TextMenu BuildChangeLANMenu() + { + var menu = new TextMenu(); + menu.labelColor = JUtil.ColorToColorTag(Color.white); + menu.selectedColor = JUtil.ColorToColorTag(Color.green); + + // OperationLan uses EditableAngle for targetLongitude - we need to handle this differently + // TimeRefs: X_FROM_NOW(0), APOAPSIS(1), PERIAPSIS(2) + AddNumericItem(menu, "New LAN", + core => MechJebProxy.GetOperationEditableDouble( + MechJebProxy.GetOperationByName("change longitude of ascending node"), "targetLongitude"), + (core, val) => MechJebProxy.SetOperationEditableDouble( + MechJebProxy.GetOperationByName("change longitude of ascending node"), "targetLongitude", val), + 0.5, v => v.ToString("F1") + "°", null, true, 0, true, 360); + + AddMenuItem(menu, "Schedule the burn:", null); + AddMenuItem(menu, " after a fixed time", () => PushMenu(BuildTimeSelectorLeadTimeMenu("change longitude of ascending node"))); + AddMenuItem(menu, " at next apoapsis", () => ExecuteChangeLAN(1)); + AddMenuItem(menu, " at next periapsis", () => ExecuteChangeLAN(2)); + + AddMenuItem(menu, "[BACK]", () => PopMenu()); + + return menu; + } + + private void ExecuteChangeLAN(int timeRefIndex) + { + if (vessel == null || mjCore == null) return; + + object op = MechJebProxy.GetOperationByName("change longitude of ascending node"); + if (op == null) return; + + object timeSelector = MechJebProxy.GetOperationTimeSelector(op); + if (timeSelector != null) + { + MechJebProxy.SetTimeSelectorCurrentTimeRef(timeSelector, timeRefIndex); + } + + MechJebProxy.ExecuteOperation(op, mjCore, vessel); + } + + private TextMenu BuildHohmannMenu() + { + // Wrapper for IMGUI "two impulse (Hohmann) transfer to target" - wraps MechJeb's OperationGeneric + var menu = new TextMenu(); + menu.labelColor = JUtil.ColorToColorTag(Color.white); + menu.selectedColor = JUtil.ColorToColorTag(Color.green); + + const string opName = "two impulse (Hohmann) transfer to target"; + + // "no insertion burn (impact/flyby)" checkbox - inverted Capture bool + AddToggleItem(menu, "no insertion burn (impact/flyby)", + core => !MechJebProxy.GetOperationBool(MechJebProxy.GetOperationByName(opName), "Capture"), + (core, val) => MechJebProxy.SetOperationBool(MechJebProxy.GetOperationByName(opName), "Capture", !val)); + + // "Plan insertion burn" checkbox + AddToggleItem(menu, "Plan insertion burn", + core => MechJebProxy.GetOperationBool(MechJebProxy.GetOperationByName(opName), "PlanCapture"), + (core, val) => MechJebProxy.SetOperationBool(MechJebProxy.GetOperationByName(opName), "PlanCapture", val)); + + // "coplanar maneuver" checkbox + AddToggleItem(menu, "coplanar maneuver", + core => MechJebProxy.GetOperationBool(MechJebProxy.GetOperationByName(opName), "Coplanar"), + (core, val) => MechJebProxy.SetOperationBool(MechJebProxy.GetOperationByName(opName), "Coplanar", val)); + + // Rendezvous vs Transfer radio buttons - use isSelected for green highlighting + var rendezvousItem = new TextMenu.Item("Rendezvous", (idx, item) => MechJebProxy.SetOperationBool(MechJebProxy.GetOperationByName(opName), "Rendezvous", true)); + menu.Add(rendezvousItem); + trackedItems.Add(new TrackedMenuItem { item = rendezvousItem, id = "HohmannRendezvous", isSelected = core => MechJebProxy.GetOperationBool(MechJebProxy.GetOperationByName(opName), "Rendezvous") }); + + var transferItem = new TextMenu.Item("Transfer", (idx, item) => MechJebProxy.SetOperationBool(MechJebProxy.GetOperationByName(opName), "Rendezvous", false)); + menu.Add(transferItem); + trackedItems.Add(new TrackedMenuItem { item = transferItem, id = "HohmannTransfer", isSelected = core => !MechJebProxy.GetOperationBool(MechJebProxy.GetOperationByName(opName), "Rendezvous") }); + + // Rendezvous time offset (LagTime in seconds) + AddNumericItem(menu, "rendezvous time offset", + core => MechJebProxy.GetOperationEditableDouble(MechJebProxy.GetOperationByName(opName), "LagTime"), + (core, val) => MechJebProxy.SetOperationEditableDouble(MechJebProxy.GetOperationByName(opName), "LagTime", val), + 1.0, v => v.ToString("F0") + " sec", null, false, 0, false, 0); + + // Schedule the burn - TimeSelector options + // OperationGeneric TimeRefs: COMPUTED(0), PERIAPSIS(1), APOAPSIS(2), X_FROM_NOW(3), ALTITUDE(4), + // EQ_DESCENDING(5), EQ_ASCENDING(6), REL_NEAREST_AD(7), REL_ASCENDING(8), REL_DESCENDING(9), CLOSEST_APPROACH(10) + AddMenuItem(menu, "Schedule the burn:", null); + AddMenuItem(menu, " at optimum time", () => ExecuteHohmann(0)); // COMPUTED + AddMenuItem(menu, " at next periapsis", () => ExecuteHohmann(1)); // PERIAPSIS + AddMenuItem(menu, " at next apoapsis", () => ExecuteHohmann(2)); // APOAPSIS + AddMenuItem(menu, " at rel. AN with target", () => ExecuteHohmann(8)); // REL_ASCENDING + AddMenuItem(menu, " at rel. DN with target", () => ExecuteHohmann(9)); // REL_DESCENDING + AddMenuItem(menu, " at nearest rel. AN/DN", () => ExecuteHohmann(7)); // REL_NEAREST_AD + AddMenuItem(menu, " at closest approach", () => ExecuteHohmann(10)); // CLOSEST_APPROACH + AddMenuItem(menu, " after a fixed time", () => PushMenu(BuildTimeSelectorLeadTimeMenu(opName))); + + AddMenuItem(menu, "[BACK]", () => PopMenu()); + + return menu; + } + + private void ExecuteHohmann(int timeRefIndex) + { + if (vessel == null || mjCore == null) return; + + object op = MechJebProxy.GetOperationByName("two impulse (Hohmann) transfer to target"); + if (op == null) return; + + object timeSelector = MechJebProxy.GetOperationTimeSelector(op); + if (timeSelector != null) + { + MechJebProxy.SetTimeSelectorCurrentTimeRef(timeSelector, timeRefIndex); + } + + MechJebProxy.ExecuteOperation(op, mjCore, vessel); + } + + // LEGACY: SyncGenericTransferOperation - no longer needed with wrapper pattern + private void SyncGenericTransferOperation() + { + // Legacy - kept for compatibility but no longer used + } + + // Wrapper implementations for remaining operations + private TextMenu BuildChangeBothPeApMenu() + { + var menu = new TextMenu(); + menu.labelColor = JUtil.ColorToColorTag(Color.white); + menu.selectedColor = JUtil.ColorToColorTag(Color.green); + + // OperationEllipticize: NewPeA, NewApA parameters, TimeRefs: PERIAPSIS(0), APOAPSIS(1), X_FROM_NOW(2) + AddNumericItem(menu, "New periapsis", + core => MechJebProxy.GetOperationEditableDouble( + MechJebProxy.GetOperationByName("change both Pe and Ap"), "NewPeA") / 1000.0, + (core, val) => MechJebProxy.SetOperationEditableDouble( + MechJebProxy.GetOperationByName("change both Pe and Ap"), "NewPeA", val * 1000.0), + 1.0, v => v.ToString("F1") + " km", null, true, 1.0, false, 0); + AddNumericItem(menu, "New apoapsis", + core => MechJebProxy.GetOperationEditableDouble( + MechJebProxy.GetOperationByName("change both Pe and Ap"), "NewApA") / 1000.0, + (core, val) => MechJebProxy.SetOperationEditableDouble( + MechJebProxy.GetOperationByName("change both Pe and Ap"), "NewApA", val * 1000.0), + 1.0, v => v.ToString("F1") + " km", null, true, 1.0, false, 0); + + AddMenuItem(menu, "Schedule the burn:", null); + AddMenuItem(menu, " at next periapsis", () => ExecuteChangeBothPeAp(0)); + AddMenuItem(menu, " at next apoapsis", () => ExecuteChangeBothPeAp(1)); + AddMenuItem(menu, " after a fixed time", () => PushMenu(BuildTimeSelectorLeadTimeMenu("change both Pe and Ap"))); + + AddMenuItem(menu, "[BACK]", () => PopMenu()); + + return menu; + } + + private void ExecuteChangeBothPeAp(int timeRefIndex) + { + if (vessel == null || mjCore == null) return; + + object op = MechJebProxy.GetOperationByName("change both Pe and Ap"); + if (op == null) return; + + object timeSelector = MechJebProxy.GetOperationTimeSelector(op); + if (timeSelector != null) + { + MechJebProxy.SetTimeSelectorCurrentTimeRef(timeSelector, timeRefIndex); + } + + MechJebProxy.ExecuteOperation(op, mjCore, vessel); + } + + private TextMenu BuildChangeEccentricityMenu() + { + var menu = new TextMenu(); + menu.labelColor = JUtil.ColorToColorTag(Color.white); + menu.selectedColor = JUtil.ColorToColorTag(Color.green); + + // OperationEccentricity: NewEcc parameter, TimeRefs: PERIAPSIS(0), APOAPSIS(1), X_FROM_NOW(2), ALTITUDE(3) + AddNumericItem(menu, "New eccentricity", + core => MechJebProxy.GetOperationEditableDouble( + MechJebProxy.GetOperationByName("change eccentricity"), "NewEcc"), + (core, val) => MechJebProxy.SetOperationEditableDouble( + MechJebProxy.GetOperationByName("change eccentricity"), "NewEcc", val), + 0.01, v => v.ToString("F3"), null, true, 0, true, 0.99); + + AddMenuItem(menu, "Schedule the burn:", null); + AddMenuItem(menu, " at next periapsis", () => ExecuteChangeEccentricity(0)); + AddMenuItem(menu, " at next apoapsis", () => ExecuteChangeEccentricity(1)); + AddMenuItem(menu, " at an altitude", () => PushMenu(BuildTimeSelectorAltitudeMenu("change eccentricity"))); + AddMenuItem(menu, " after a fixed time", () => PushMenu(BuildTimeSelectorLeadTimeMenu("change eccentricity"))); + + AddMenuItem(menu, "[BACK]", () => PopMenu()); + + return menu; + } + + private void ExecuteChangeEccentricity(int timeRefIndex) + { + if (vessel == null || mjCore == null) return; + + object op = MechJebProxy.GetOperationByName("change eccentricity"); + if (op == null) return; + + object timeSelector = MechJebProxy.GetOperationTimeSelector(op); + if (timeSelector != null) + { + MechJebProxy.SetTimeSelectorCurrentTimeRef(timeSelector, timeRefIndex); + } + + MechJebProxy.ExecuteOperation(op, mjCore, vessel); + } + + private TextMenu BuildChangeSurfaceLongitudeMenu() + { + var menu = new TextMenu(); + menu.labelColor = JUtil.ColorToColorTag(Color.white); + menu.selectedColor = JUtil.ColorToColorTag(Color.green); + + // OperationLongitude: targetLongitude (EditableAngle), TimeRefs: PERIAPSIS(0), APOAPSIS(1) + AddNumericItem(menu, "Target longitude", + core => MechJebProxy.GetOperationEditableDouble( + MechJebProxy.GetOperationByName("change surface longitude of apsis"), "targetLongitude"), + (core, val) => MechJebProxy.SetOperationEditableDouble( + MechJebProxy.GetOperationByName("change surface longitude of apsis"), "targetLongitude", val), + 1.0, v => v.ToString("F1") + "°", null, true, -180, true, 180); + + AddMenuItem(menu, "Schedule the burn:", null); + AddMenuItem(menu, " at next periapsis", () => ExecuteChangeSurfaceLongitude(0)); + AddMenuItem(menu, " at next apoapsis", () => ExecuteChangeSurfaceLongitude(1)); + + AddMenuItem(menu, "[BACK]", () => PopMenu()); + + return menu; + } + + private void ExecuteChangeSurfaceLongitude(int timeRefIndex) + { + if (vessel == null || mjCore == null) return; + + object op = MechJebProxy.GetOperationByName("change surface longitude of apsis"); + if (op == null) return; + + object timeSelector = MechJebProxy.GetOperationTimeSelector(op); + if (timeSelector != null) + { + MechJebProxy.SetTimeSelectorCurrentTimeRef(timeSelector, timeRefIndex); + } + + MechJebProxy.ExecuteOperation(op, mjCore, vessel); + } + + private TextMenu BuildFineTuneClosestApproachMenu() + { + var menu = new TextMenu(); + menu.labelColor = JUtil.ColorToColorTag(Color.white); + menu.selectedColor = JUtil.ColorToColorTag(Color.green); + + // OperationCourseCorrection: CourseCorrectFinalPeA or InterceptDistance parameters (no time selector) + // Check if target is celestial body or vessel + ITargetable target = FlightGlobals.fetch.VesselTarget; + bool isCelestialTarget = target is CelestialBody; + + if (isCelestialTarget) + { + AddNumericItem(menu, "Target periapsis", + core => MechJebProxy.GetOperationEditableDouble( + MechJebProxy.GetOperationByName("fine tune closest approach to target"), "CourseCorrectFinalPeA") / 1000.0, + (core, val) => MechJebProxy.SetOperationEditableDouble( + MechJebProxy.GetOperationByName("fine tune closest approach to target"), "CourseCorrectFinalPeA", val * 1000.0), + 1.0, v => v.ToString("F1") + " km", null, true, 0, false, 0); + } + else + { + AddNumericItem(menu, "Distance at closest approach", + core => MechJebProxy.GetOperationEditableDouble( + MechJebProxy.GetOperationByName("fine tune closest approach to target"), "InterceptDistance"), + (core, val) => MechJebProxy.SetOperationEditableDouble( + MechJebProxy.GetOperationByName("fine tune closest approach to target"), "InterceptDistance", val), + 10.0, v => v.ToString("F0") + " m", null, true, 0, false, 0); + } + + AddMenuItem(menu, "[Create Node]", () => ExecuteCourseCorrection()); + AddMenuItem(menu, "[BACK]", () => PopMenu()); + + return menu; + } + + private void ExecuteCourseCorrection() + { + if (vessel == null || mjCore == null) return; + + object op = MechJebProxy.GetOperationByName("fine tune closest approach to target"); + if (op == null) return; + + // CourseCorrection has no time selector - it computes automatically + MechJebProxy.ExecuteOperation(op, mjCore, vessel); + } + + private TextMenu BuildInterceptAtTimeMenu() + { + var menu = new TextMenu(); + menu.labelColor = JUtil.ColorToColorTag(Color.white); + menu.selectedColor = JUtil.ColorToColorTag(Color.green); + + // OperationLambert: InterceptInterval parameter, TimeRef: X_FROM_NOW only + AddNumericItem(menu, "Intercept after", + core => MechJebProxy.GetOperationEditableDouble( + MechJebProxy.GetOperationByName("intercept target at chosen time"), "InterceptInterval"), + (core, val) => MechJebProxy.SetOperationEditableDouble( + MechJebProxy.GetOperationByName("intercept target at chosen time"), "InterceptInterval", val), + 60.0, v => FormatTime(v), null, true, 60, false, 0); + + AddMenuItem(menu, "[Create Node]", () => ExecuteInterceptAtTime()); + AddMenuItem(menu, "[BACK]", () => PopMenu()); + + return menu; + } + + private void ExecuteInterceptAtTime() + { + if (vessel == null || mjCore == null) return; + + object op = MechJebProxy.GetOperationByName("intercept target at chosen time"); + if (op == null) return; + + // Lambert has X_FROM_NOW as its only time reference (index 0) + object timeSelector = MechJebProxy.GetOperationTimeSelector(op); + if (timeSelector != null) + { + MechJebProxy.SetTimeSelectorCurrentTimeRef(timeSelector, 0); + } + + MechJebProxy.ExecuteOperation(op, mjCore, vessel); + } + + private TextMenu BuildMatchPlanesMenu() + { + var menu = new TextMenu(); + menu.labelColor = JUtil.ColorToColorTag(Color.white); + menu.selectedColor = JUtil.ColorToColorTag(Color.green); + + // OperationPlane: no parameters, TimeRefs: REL_ASCENDING(0), REL_DESCENDING(1), REL_NEAREST_AD(2), REL_HIGHEST_AD(3) + AddMenuItem(menu, "[Match at nearest AN/DN]", () => ExecuteMatchPlanes(2)); + AddMenuItem(menu, "[Match at highest AN/DN]", () => ExecuteMatchPlanes(3)); + AddMenuItem(menu, "[Match at Ascending Node]", () => ExecuteMatchPlanes(0)); + AddMenuItem(menu, "[Match at Descending Node]", () => ExecuteMatchPlanes(1)); + AddMenuItem(menu, "[BACK]", () => PopMenu()); + + return menu; + } + + private void ExecuteMatchPlanes(int timeRefIndex) + { + if (vessel == null || mjCore == null) return; + + object op = MechJebProxy.GetOperationByName("match planes with target"); + if (op == null) return; + + object timeSelector = MechJebProxy.GetOperationTimeSelector(op); + if (timeSelector != null) + { + MechJebProxy.SetTimeSelectorCurrentTimeRef(timeSelector, timeRefIndex); + } + + MechJebProxy.ExecuteOperation(op, mjCore, vessel); + } + + private TextMenu BuildResonantOrbitMenu() + { + var menu = new TextMenu(); + menu.labelColor = JUtil.ColorToColorTag(Color.white); + menu.selectedColor = JUtil.ColorToColorTag(Color.green); + + // OperationResonantOrbit: ResonanceNumerator, ResonanceDenominator, TimeRefs: PERIAPSIS(0), APOAPSIS(1), X_FROM_NOW(2), ALTITUDE(3) + AddNumericItem(menu, "Resonance numerator", + core => MechJebProxy.GetOperationEditableInt( + MechJebProxy.GetOperationByName("resonant orbit"), "ResonanceNumerator"), + (core, val) => MechJebProxy.SetOperationEditableInt( + MechJebProxy.GetOperationByName("resonant orbit"), "ResonanceNumerator", (int)val), + 1.0, v => ((int)v).ToString(), null, true, 1, false, 0); + AddNumericItem(menu, "Resonance denominator", + core => MechJebProxy.GetOperationEditableInt( + MechJebProxy.GetOperationByName("resonant orbit"), "ResonanceDenominator"), + (core, val) => MechJebProxy.SetOperationEditableInt( + MechJebProxy.GetOperationByName("resonant orbit"), "ResonanceDenominator", (int)val), + 1.0, v => ((int)v).ToString(), null, true, 1, false, 0); + + AddMenuItem(menu, "Schedule the burn:", null); + AddMenuItem(menu, " at next periapsis", () => ExecuteResonantOrbit(0)); + AddMenuItem(menu, " at next apoapsis", () => ExecuteResonantOrbit(1)); + AddMenuItem(menu, " at an altitude", () => PushMenu(BuildTimeSelectorAltitudeMenu("resonant orbit"))); + AddMenuItem(menu, " after a fixed time", () => PushMenu(BuildTimeSelectorLeadTimeMenu("resonant orbit"))); + + AddMenuItem(menu, "[BACK]", () => PopMenu()); + + return menu; + } + + private void ExecuteResonantOrbit(int timeRefIndex) + { + if (vessel == null || mjCore == null) return; + + object op = MechJebProxy.GetOperationByName("resonant orbit"); + if (op == null) return; + + object timeSelector = MechJebProxy.GetOperationTimeSelector(op); + if (timeSelector != null) + { + MechJebProxy.SetTimeSelectorCurrentTimeRef(timeSelector, timeRefIndex); + } + + MechJebProxy.ExecuteOperation(op, mjCore, vessel); + } + + private TextMenu BuildMoonReturnMenu() + { + var menu = new TextMenu(); + menu.labelColor = JUtil.ColorToColorTag(Color.white); + menu.selectedColor = JUtil.ColorToColorTag(Color.green); + + // OperationMoonReturn: MoonReturnAltitude parameter (no time selector - auto computed) + AddNumericItem(menu, "Return altitude", + core => MechJebProxy.GetOperationEditableDouble( + MechJebProxy.GetOperationByName("return from a moon"), "MoonReturnAltitude") / 1000.0, + (core, val) => MechJebProxy.SetOperationEditableDouble( + MechJebProxy.GetOperationByName("return from a moon"), "MoonReturnAltitude", val * 1000.0), + 10.0, v => v.ToString("F0") + " km", null, true, 10, false, 0); + AddMenuItem(menu, "[Create Node]", () => ExecuteMoonReturn()); + AddMenuItem(menu, "[BACK]", () => PopMenu()); + + return menu; + } + + private void ExecuteMoonReturn() + { + if (vessel == null || mjCore == null) return; + + object op = MechJebProxy.GetOperationByName("return from a moon"); + if (op == null) return; + + // MoonReturn has no time selector - it computes the optimal ejection time automatically + MechJebProxy.ExecuteOperation(op, mjCore, vessel); + } + + private TextMenu BuildInterplanetaryTransferMenu() + { + var menu = new TextMenu(); + menu.labelColor = JUtil.ColorToColorTag(Color.white); + menu.selectedColor = JUtil.ColorToColorTag(Color.green); + + // OperationInterplanetaryTransfer: WaitForPhaseAngle (bool) parameter + AddToggleItem(menu, "Wait for optimal phase angle", + core => MechJebProxy.GetOperationBool( + MechJebProxy.GetOperationByName("transfer to another planet"), "WaitForPhaseAngle"), + (core, val) => MechJebProxy.SetOperationBool( + MechJebProxy.GetOperationByName("transfer to another planet"), "WaitForPhaseAngle", val)); + + AddMenuItem(menu, "[Create Node]", () => ExecuteInterplanetaryTransfer()); + AddMenuItem(menu, "[BACK]", () => PopMenu()); + + return menu; + } + + private void ExecuteInterplanetaryTransfer() + { + if (vessel == null || mjCore == null) return; + + object op = MechJebProxy.GetOperationByName("transfer to another planet"); + if (op == null) return; + + // InterplanetaryTransfer has no time selector - computes optimal time automatically + MechJebProxy.ExecuteOperation(op, mjCore, vessel); + } + + private TextMenu BuildAdvancedTransferMenu() + { + // Wrapper for IMGUI "advanced transfer to another planet" - wraps MechJeb's OperationAdvancedTransfer + var menu = new TextMenu(); + menu.labelColor = JUtil.ColorToColorTag(Color.white); + menu.selectedColor = JUtil.ColorToColorTag(Color.green); + + const string opName = "advanced transfer to another planet"; + + // Mode selection header + AddMenuItem(menu, "--- Porkchop selection ---", null); + + // Status display - shows computation progress/ready status + var statusItem = new TextMenu.Item("Status: ---", null); + menu.Add(statusItem); + trackedItems.Add(new TrackedMenuItem + { + item = statusItem, + id = "AdvancedTransferStatus", + isEnabled = core => true, + getLabel = core => GetAdvancedTransferStatusText() + }); + + // ΔV display + var dvItem = new TextMenu.Item("ΔV: ---", null); + menu.Add(dvItem); + trackedItems.Add(new TrackedMenuItem + { + item = dvItem, + id = "AdvancedTransferDV", + isEnabled = core => true, + getLabel = core => { + object op = MechJebProxy.GetOperationByName(opName); + if (op == null) return "ΔV: ---"; + double dv, dep, dur; + if (MechJebProxy.GetAdvancedTransferSelection(op, out dep, out dur, out dv) && dv > 0) + return "ΔV: " + dv.ToString("F1") + " m/s"; + return "ΔV: ---"; + } + }); + + // Include capture burn checkbox - wraps operation field + AddToggleItem(menu, "Include capture burn", + core => MechJebProxy.GetAdvancedTransferIncludeCapture(MechJebProxy.GetOperationByName(opName)), + (core, val) => MechJebProxy.SetAdvancedTransferIncludeCapture(MechJebProxy.GetOperationByName(opName), val)); + + // Periapsis input - wraps periapsisHeight field (in km) + AddNumericItem(menu, "Periapsis", + core => MechJebProxy.GetAdvancedTransferPeriapsisKm(MechJebProxy.GetOperationByName(opName)), + (core, val) => MechJebProxy.SetAdvancedTransferPeriapsisKm(MechJebProxy.GetOperationByName(opName), val), + 10.0, v => v.ToString("F0") + " km", null, true, 10.0, false, 0); + + // Selection mode - Lowest ΔV vs ASAP - use isSelected for green highlighting + var lowestDVItem = new TextMenu.Item("Lowest ΔV", (idx, item) => { advancedTransferSelectLowestDV = true; SelectAdvancedTransferLowestDV(); }); + menu.Add(lowestDVItem); + trackedItems.Add(new TrackedMenuItem { item = lowestDVItem, id = "AdvTransferLowestDV", isSelected = core => advancedTransferSelectLowestDV }); + + var asapItem = new TextMenu.Item("ASAP", (idx, item) => { advancedTransferSelectLowestDV = false; SelectAdvancedTransferASAP(); }); + menu.Add(asapItem); + trackedItems.Add(new TrackedMenuItem { item = asapItem, id = "AdvTransferASAP", isSelected = core => !advancedTransferSelectLowestDV }); + + // Departure info + var departureItem = new TextMenu.Item("Departure: ---", null); + menu.Add(departureItem); + trackedItems.Add(new TrackedMenuItem + { + item = departureItem, + id = "AdvancedTransferDeparture", + isEnabled = core => true, + getLabel = core => "Departure in " + GetAdvancedTransferDepartureText() + }); + + // Transit duration + var transitItem = new TextMenu.Item("Transit: ---", null); + menu.Add(transitItem); + trackedItems.Add(new TrackedMenuItem + { + item = transitItem, + id = "AdvancedTransferTransit", + isEnabled = core => true, + getLabel = core => "Transit duration " + GetAdvancedTransferTransitText() + }); + + AddMenuItem(menu, "------", null); + AddMenuItem(menu, "[Start/Refresh Compute]", () => StartAdvancedTransferCompute()); + AddMenuItem(menu, "[Create node]", () => CreateAdvancedTransferNode()); + AddMenuItem(menu, "[Create and execute]", () => CreateAndExecuteAdvancedTransfer()); + AddMenuItem(menu, "[BACK]", () => PopMenu()); + + return menu; + } + + private string GetAdvancedTransferStatusText() + { + object op = MechJebProxy.GetOperationByName("advanced transfer to another planet"); + if (op == null) return "Not available"; + int progress; + bool finished = MechJebProxy.IsAdvancedTransferFinished(op, out progress); + if (finished) + { + // Update cached selection info for display + MechJebProxy.GetAdvancedTransferSelection(op, + out advancedTransferDepartureUT, out advancedTransferDuration, out advancedTransferDeltaV); + return "Ready"; + } + return "Computing: " + progress + "%"; + } + + private string GetAdvancedTransferDepartureText() + { + if (advancedTransferDepartureUT <= 0) return "---"; + double dt = advancedTransferDepartureUT - Planetarium.GetUniversalTime(); + if (dt < 0) return "any time now"; + return FormatTime(dt); + } + + private string GetAdvancedTransferTransitText() + { + if (advancedTransferDuration <= 0) return "---"; + return FormatTime(advancedTransferDuration); + } + + private void CreateAndExecuteAdvancedTransfer() + { + CreateAdvancedTransferNode(); + if (mjCore != null) + { + MechJebProxy.ExecuteOneNode(mjCore, null); + } + } + + private TextMenu BuildCourseCorrectionMenu() + { + var menu = new TextMenu(); + menu.labelColor = JUtil.ColorToColorTag(Color.white); + menu.selectedColor = JUtil.ColorToColorTag(Color.green); + + AddNumericItem(menu, "Target PE", + core => courseCorrectionPeKm, + (core, val) => courseCorrectionPeKm = val, + 1.0, v => v.ToString("F1") + " km", null, true, 1.0, false, 0); + AddMenuItem(menu, "[Create Correction]", () => CreateCourseCorrection()); + AddMenuItem(menu, "[BACK]", () => PopMenu()); + + return menu; + } + + private TextMenu BuildMatchVelocitiesMenu() + { + var menu = new TextMenu(); + menu.labelColor = JUtil.ColorToColorTag(Color.white); + menu.selectedColor = JUtil.ColorToColorTag(Color.green); + + // OperationKillRelVel: no parameters, TimeRefs: CLOSEST_APPROACH(0), X_FROM_NOW(1) + AddMenuItem(menu, "[Match at Closest Approach]", () => ExecuteMatchVelocities(0)); + AddMenuItem(menu, "[Match after fixed time]", () => PushMenu(BuildTimeSelectorLeadTimeMenu("match velocities with target"))); + AddMenuItem(menu, "[BACK]", () => PopMenu()); + + return menu; + } + + private void ExecuteMatchVelocities(int timeRefIndex) + { + if (vessel == null || mjCore == null) return; + + object op = MechJebProxy.GetOperationByName("match velocities with target"); + if (op == null) return; + + object timeSelector = MechJebProxy.GetOperationTimeSelector(op); + if (timeSelector != null) + { + MechJebProxy.SetTimeSelectorCurrentTimeRef(timeSelector, timeRefIndex); + } + + MechJebProxy.ExecuteOperation(op, mjCore, vessel); + } + + private TextMenu BuildMatchPlanesANMenu() + { + var menu = new TextMenu(); + menu.labelColor = JUtil.ColorToColorTag(Color.white); + menu.selectedColor = JUtil.ColorToColorTag(Color.green); + + AddMenuItem(menu, "[Match at Ascending Node]", () => ExecuteMatchPlanes(0)); + AddMenuItem(menu, "[BACK]", () => PopMenu()); + + return menu; + } + + private TextMenu BuildMatchPlanesDNMenu() + { + var menu = new TextMenu(); + menu.labelColor = JUtil.ColorToColorTag(Color.white); + menu.selectedColor = JUtil.ColorToColorTag(Color.green); + + AddMenuItem(menu, "[Match at Descending Node]", () => ExecuteMatchPlanes(1)); + AddMenuItem(menu, "[BACK]", () => PopMenu()); + + return menu; + } + + // ============================================================================ + // LEGACY IMPLEMENTATIONS BELOW - kept for reference but no longer used + // These have been replaced by the wrapper implementations above + // ============================================================================ + + // Maneuver planner implementations (LEGACY - not using wrapper) + private void CircularizeAtApoapsis() + { + if (vessel == null) return; + double ut = vessel.orbit.NextApoapsisTime(Planetarium.GetUniversalTime()); + Vector3d dV = MechJebProxy.CalcDeltaVToCircularize(vessel.orbit, ut); + MechJebProxy.PlaceManeuverNode(vessel, vessel.orbit, dV, ut); + } + + private void CircularizeAtPeriapsis() + { + if (vessel == null) return; + double ut = vessel.orbit.NextPeriapsisTime(Planetarium.GetUniversalTime()); + Vector3d dV = MechJebProxy.CalcDeltaVToCircularize(vessel.orbit, ut); + MechJebProxy.PlaceManeuverNode(vessel, vessel.orbit, dV, ut); + } + + private void CircularizeAtAltitude() + { + if (vessel == null) return; + double ut = Planetarium.GetUniversalTime(); + try + { + double radius = vessel.mainBody.Radius + (circularizeAltitudeKm * 1000.0); + ut = vessel.orbit.NextTimeOfRadius(ut, radius); + } + catch { } + + Vector3d dV = MechJebProxy.CalcDeltaVToCircularize(vessel.orbit, ut); + MechJebProxy.PlaceManeuverNode(vessel, vessel.orbit, dV, ut); + } + + private void ChangeApoapsisAtPeriapsis() + { + if (vessel == null) return; + double ut = vessel.orbit.NextPeriapsisTime(Planetarium.GetUniversalTime()); + double newApR = vessel.mainBody.Radius + (changeApoapsisKm * 1000.0); + Vector3d dV = MechJebProxy.CalcDeltaVToChangeApoapsis(vessel.orbit, ut, newApR); + MechJebProxy.PlaceManeuverNode(vessel, vessel.orbit, dV, ut); + } + + private void ChangeApoapsisNow() + { + if (vessel == null) return; + double ut = Planetarium.GetUniversalTime(); + double newApR = vessel.mainBody.Radius + (changeApoapsisKm * 1000.0); + Vector3d dV = MechJebProxy.CalcDeltaVToChangeApoapsis(vessel.orbit, ut, newApR); + MechJebProxy.PlaceManeuverNode(vessel, vessel.orbit, dV, ut); + } + + private void ChangePeriapsisAtApoapsis() + { + if (vessel == null) return; + double ut = vessel.orbit.NextApoapsisTime(Planetarium.GetUniversalTime()); + double newPeR = vessel.mainBody.Radius + (changePeriapsisKm * 1000.0); + Vector3d dV = MechJebProxy.CalcDeltaVToChangePeriapsis(vessel.orbit, ut, newPeR); + MechJebProxy.PlaceManeuverNode(vessel, vessel.orbit, dV, ut); + } + + private void ChangePeriapsisNow() + { + if (vessel == null) return; + double ut = Planetarium.GetUniversalTime(); + double newPeR = vessel.mainBody.Radius + (changePeriapsisKm * 1000.0); + Vector3d dV = MechJebProxy.CalcDeltaVToChangePeriapsis(vessel.orbit, ut, newPeR); + MechJebProxy.PlaceManeuverNode(vessel, vessel.orbit, dV, ut); + } + + private void ChangeSmaNow() + { + if (vessel == null) return; + double ut = Planetarium.GetUniversalTime(); + double newSma = vessel.mainBody.Radius + (changeSmaKm * 1000.0); + Vector3d dV = MechJebProxy.CalcDeltaVForSemiMajorAxis(vessel.orbit, ut, newSma); + MechJebProxy.PlaceManeuverNode(vessel, vessel.orbit, dV, ut); + } + + private void ChangeInclinationAtAN() + { + if (vessel == null) return; + double ut = vessel.orbit.TimeOfAscendingNodeEquatorial(Planetarium.GetUniversalTime()); + Vector3d dV = MechJebProxy.CalcDeltaVToChangeInclination(vessel.orbit, ut, changeInclinationDeg); + MechJebProxy.PlaceManeuverNode(vessel, vessel.orbit, dV, ut); + } + + private void ChangeInclinationAtDN() + { + if (vessel == null) return; + double ut = vessel.orbit.TimeOfDescendingNodeEquatorial(Planetarium.GetUniversalTime()); + Vector3d dV = MechJebProxy.CalcDeltaVToChangeInclination(vessel.orbit, ut, changeInclinationDeg); + MechJebProxy.PlaceManeuverNode(vessel, vessel.orbit, dV, ut); + } + + private void ChangeLANAtApoapsis() + { + if (vessel == null) return; + double ut = vessel.orbit.NextApoapsisTime(Planetarium.GetUniversalTime()); + Vector3d dV = MechJebProxy.CalcDeltaVToShiftLAN(vessel.orbit, ut, changeLanDeg); + MechJebProxy.PlaceManeuverNode(vessel, vessel.orbit, dV, ut); + } + + private void ChangeLANAtPeriapsis() + { + if (vessel == null) return; + double ut = vessel.orbit.NextPeriapsisTime(Planetarium.GetUniversalTime()); + Vector3d dV = MechJebProxy.CalcDeltaVToShiftLAN(vessel.orbit, ut, changeLanDeg); + MechJebProxy.PlaceManeuverNode(vessel, vessel.orbit, dV, ut); + } + + private void ChangeLANNow() + { + if (vessel == null) return; + double ut = Planetarium.GetUniversalTime(); + Vector3d dV = MechJebProxy.CalcDeltaVToShiftLAN(vessel.orbit, ut, changeLanDeg); + MechJebProxy.PlaceManeuverNode(vessel, vessel.orbit, dV, ut); + } + + private void CreateHohmannTransfer() + { + if (vessel == null) return; + Orbit target = FlightGlobals.fetch.VesselTarget != null ? FlightGlobals.fetch.VesselTarget.GetOrbit() : null; + if (target == null) return; + + Vector3d dv1, dv2; + double ut1, ut2; + if (MechJebProxy.TryCalcHohmannTransfer(vessel.orbit, target, Planetarium.GetUniversalTime(), out dv1, out ut1, out dv2, out ut2)) + { + MechJebProxy.PlaceManeuverNode(vessel, vessel.orbit, dv1, ut1); + if (dv2.sqrMagnitude > 0.0) + { + MechJebProxy.PlaceManeuverNode(vessel, vessel.orbit, dv2, ut2); + } + } + } + + private void MatchVelocitiesAtClosestApproach() + { + if (vessel == null || FlightGlobals.fetch.VesselTarget == null) return; + Orbit target = FlightGlobals.fetch.VesselTarget.GetOrbit(); + if (target == null) return; + + double ut = vessel.orbit.NextClosestApproachTime(target, Planetarium.GetUniversalTime()); + Vector3d dV = MechJebProxy.CalcDeltaVToMatchVelocities(vessel.orbit, ut, target); + MechJebProxy.PlaceManeuverNode(vessel, vessel.orbit, dV, ut); + } + + private void MatchPlanesAtAscendingNode() + { + if (vessel == null || FlightGlobals.fetch.VesselTarget == null) return; + Orbit target = FlightGlobals.fetch.VesselTarget.GetOrbit(); + if (target == null) return; + + Vector3d dV; + double ut; + if (MechJebProxy.TryCalcMatchPlanesAscending(vessel.orbit, target, Planetarium.GetUniversalTime(), out dV, out ut)) + { + MechJebProxy.PlaceManeuverNode(vessel, vessel.orbit, dV, ut); + } + } + + private void MatchPlanesAtDescendingNode() + { + if (vessel == null || FlightGlobals.fetch.VesselTarget == null) return; + Orbit target = FlightGlobals.fetch.VesselTarget.GetOrbit(); + if (target == null) return; + + Vector3d dV; + double ut; + if (MechJebProxy.TryCalcMatchPlanesDescending(vessel.orbit, target, Planetarium.GetUniversalTime(), out dV, out ut)) + { + MechJebProxy.PlaceManeuverNode(vessel, vessel.orbit, dV, ut); + } + } + + private void StartAdvancedTransferCompute() + { + if (mjCore == null || vessel == null) return; + object targetController = MechJebProxy.GetTargetController(mjCore); + if (targetController == null || FlightGlobals.fetch.VesselTarget == null) return; + if (!(FlightGlobals.fetch.VesselTarget is CelestialBody)) return; + + object op = MechJebProxy.GetOperationByName("advanced transfer to another planet"); + if (op == null) return; + + // Get current settings from the operation itself for consistency + bool includeCapture = MechJebProxy.GetAdvancedTransferIncludeCapture(op); + double periapsisKm = MechJebProxy.GetAdvancedTransferPeriapsisKm(op); + + MechJebProxy.StartAdvancedTransferCompute( + op, + vessel.orbit, + Planetarium.GetUniversalTime(), + targetController, + includeCapture, + periapsisKm); + } + + private void SelectAdvancedTransferLowestDV() + { + object op = MechJebProxy.GetOperationByName("advanced transfer to another planet"); + if (op == null) return; + MechJebProxy.SelectAdvancedTransferLowestDV(op); + } + + private void SelectAdvancedTransferASAP() + { + object op = MechJebProxy.GetOperationByName("advanced transfer to another planet"); + if (op == null) return; + MechJebProxy.SelectAdvancedTransferASAP(op); + } + + private void CreateAdvancedTransferNode() + { + if (vessel == null || mjCore == null) return; + object targetController = MechJebProxy.GetTargetController(mjCore); + if (targetController == null) return; + + object op = MechJebProxy.GetOperationByName("advanced transfer to another planet"); + if (op == null) return; + + // Check if computation is finished + int progress; + if (!MechJebProxy.IsAdvancedTransferFinished(op, out progress)) + { + // Not ready yet - need to compute first + return; + } + + MechJebProxy.CreateNodesFromOperation(op, vessel.orbit, Planetarium.GetUniversalTime(), targetController, vessel); + } + + // Wrapper for Hohmann transfer using MechJeb's OperationGeneric + private void CreateHohmannTransferNode() + { + if (vessel == null || mjCore == null) return; + object targetController = MechJebProxy.GetTargetController(mjCore); + if (targetController == null || FlightGlobals.fetch.VesselTarget == null) return; + + object op = MechJebProxy.GetOperationByName("two impulse (Hohmann) transfer to target"); + if (op == null) return; + + // Use COMPUTED time reference for optimal timing + object timeSelector = MechJebProxy.GetOperationTimeSelector(op); + if (timeSelector != null) + { + MechJebProxy.SetTimeSelectorCurrentTimeRef(timeSelector, 0); // COMPUTED + } + + MechJebProxy.CreateNodesFromOperation(op, vessel.orbit, Planetarium.GetUniversalTime(), targetController, vessel); + } + + private void CreateAndExecuteHohmannTransfer() + { + CreateHohmannTransferNode(); + if (mjCore != null) + { + MechJebProxy.ExecuteOneNode(mjCore, null); + } + } + + // New operations implementations + private void ChangeBothAtPeriapsis() + { + if (vessel == null) return; + double ut = vessel.orbit.NextPeriapsisTime(Planetarium.GetUniversalTime()); + double newPeR = vessel.mainBody.Radius + (changePeriapsisKm * 1000.0); + double newApR = vessel.mainBody.Radius + (changeApoapsisKm * 1000.0); + Vector3d dV = MechJebProxy.CalcDeltaVToEllipticize(vessel.orbit, ut, newPeR, newApR); + MechJebProxy.PlaceManeuverNode(vessel, vessel.orbit, dV, ut); + } + + private void ChangeBothAtApoapsis() + { + if (vessel == null) return; + double ut = vessel.orbit.NextApoapsisTime(Planetarium.GetUniversalTime()); + double newPeR = vessel.mainBody.Radius + (changePeriapsisKm * 1000.0); + double newApR = vessel.mainBody.Radius + (changeApoapsisKm * 1000.0); + Vector3d dV = MechJebProxy.CalcDeltaVToEllipticize(vessel.orbit, ut, newPeR, newApR); + MechJebProxy.PlaceManeuverNode(vessel, vessel.orbit, dV, ut); + } + + private void ChangeBothNow() + { + if (vessel == null) return; + double ut = Planetarium.GetUniversalTime(); + double newPeR = vessel.mainBody.Radius + (changePeriapsisKm * 1000.0); + double newApR = vessel.mainBody.Radius + (changeApoapsisKm * 1000.0); + Vector3d dV = MechJebProxy.CalcDeltaVToEllipticize(vessel.orbit, ut, newPeR, newApR); + MechJebProxy.PlaceManeuverNode(vessel, vessel.orbit, dV, ut); + } + + private void ChangeEccentricityAtPeriapsis() + { + if (vessel == null) return; + double ut = vessel.orbit.NextPeriapsisTime(Planetarium.GetUniversalTime()); + Vector3d dV = MechJebProxy.CalcDeltaVToChangeEccentricity(vessel.orbit, ut, changeEccentricity); + MechJebProxy.PlaceManeuverNode(vessel, vessel.orbit, dV, ut); + } + + private void ChangeEccentricityAtApoapsis() + { + if (vessel == null) return; + double ut = vessel.orbit.NextApoapsisTime(Planetarium.GetUniversalTime()); + Vector3d dV = MechJebProxy.CalcDeltaVToChangeEccentricity(vessel.orbit, ut, changeEccentricity); + MechJebProxy.PlaceManeuverNode(vessel, vessel.orbit, dV, ut); + } + + private void ChangeSurfaceLongitude() + { + if (vessel == null) return; + double ut = Planetarium.GetUniversalTime(); + Vector3d dV = MechJebProxy.CalcDeltaVToChangeSurfaceLongitude(vessel.orbit, ut, surfaceLongitudeDeg); + MechJebProxy.PlaceManeuverNode(vessel, vessel.orbit, dV, ut); + } + + private void FineTuneClosestApproach() + { + if (vessel == null || mjCore == null || FlightGlobals.fetch.VesselTarget == null) return; + object targetController = MechJebProxy.GetTargetController(mjCore); + if (targetController == null) return; + + object op = MechJebProxy.CreateFineTuneClosestApproachOperation(); + if (op == null) return; + + MechJebProxy.SetFineTuneDistance(op, fineTuneDistanceKm); + MechJebProxy.CreateNodesFromOperation(op, vessel.orbit, Planetarium.GetUniversalTime(), targetController, vessel); + } + + private void InterceptAtTime() + { + if (vessel == null || mjCore == null || FlightGlobals.fetch.VesselTarget == null) return; + object targetController = MechJebProxy.GetTargetController(mjCore); + if (targetController == null) return; + + object op = MechJebProxy.CreateInterceptAtTimeOperation(); + if (op == null) return; + + MechJebProxy.SetInterceptInterval(op, interceptIntervalSeconds); + MechJebProxy.CreateNodesFromOperation(op, vessel.orbit, Planetarium.GetUniversalTime(), targetController, vessel); + } + + private void CreateResonantOrbit() + { + if (vessel == null || mjCore == null) return; + object targetController = MechJebProxy.GetTargetController(mjCore); + + object op = MechJebProxy.CreateResonantOrbitOperation(); + if (op == null) return; + + MechJebProxy.SetResonance(op, resonanceNumerator, resonanceDenominator); + MechJebProxy.CreateNodesFromOperation(op, vessel.orbit, Planetarium.GetUniversalTime(), targetController, vessel); + } + + private void CreateMoonReturn() + { + if (vessel == null || mjCore == null) return; + object targetController = MechJebProxy.GetTargetController(mjCore); + + object op = MechJebProxy.CreateMoonReturnOperation(); + if (op == null) return; + + MechJebProxy.SetMoonReturnAltitude(op, moonReturnAltitudeKm); + MechJebProxy.CreateNodesFromOperation(op, vessel.orbit, Planetarium.GetUniversalTime(), targetController, vessel); + } + + private void CreateInterplanetaryTransfer() + { + if (vessel == null || mjCore == null) return; + object targetController = MechJebProxy.GetTargetController(mjCore); + if (targetController == null || !(FlightGlobals.fetch.VesselTarget is CelestialBody)) return; + + object op = MechJebProxy.CreateInterplanetaryTransferOperation(); + if (op == null) return; + + MechJebProxy.CreateNodesFromOperation(op, vessel.orbit, Planetarium.GetUniversalTime(), targetController, vessel); + } + + private void RemoveAllNodes() + { + if (vessel == null || vessel.patchedConicSolver == null) return; + while (vessel.patchedConicSolver.maneuverNodes.Count > 0) + { + vessel.patchedConicSolver.maneuverNodes[0].RemoveSelf(); + } + } + #endregion + + #region Node Editor Menu + private TextMenu BuildNodeEditorMenu() + { + var menu = new TextMenu(); + menu.labelColor = JUtil.ColorToColorTag(Color.white); + menu.selectedColor = JUtil.ColorToColorTag(Color.green); + menu.disabledColor = JUtil.ColorToColorTag(Color.gray); + + AddMenuItem(menu, "-- ADJUST NODE --", null); + AddMenuItem(menu, "Prograde +1 m/s", () => AdjustNode(Vector3d.forward, 1)); + AddMenuItem(menu, "Prograde -1 m/s", () => AdjustNode(Vector3d.forward, -1)); + AddMenuItem(menu, "Prograde +10 m/s", () => AdjustNode(Vector3d.forward, 10)); + AddMenuItem(menu, "Prograde -10 m/s", () => AdjustNode(Vector3d.forward, -10)); + AddMenuItem(menu, "------", null); + AddMenuItem(menu, "Normal +1 m/s", () => AdjustNode(Vector3d.up, 1)); + AddMenuItem(menu, "Normal -1 m/s", () => AdjustNode(Vector3d.up, -1)); + AddMenuItem(menu, "Normal +10 m/s", () => AdjustNode(Vector3d.up, 10)); + AddMenuItem(menu, "Normal -10 m/s", () => AdjustNode(Vector3d.up, -10)); + AddMenuItem(menu, "------", null); + AddMenuItem(menu, "Radial +1 m/s", () => AdjustNode(Vector3d.right, 1)); + AddMenuItem(menu, "Radial -1 m/s", () => AdjustNode(Vector3d.right, -1)); + AddMenuItem(menu, "Radial +10 m/s", () => AdjustNode(Vector3d.right, 10)); + AddMenuItem(menu, "Radial -10 m/s", () => AdjustNode(Vector3d.right, -10)); + AddMenuItem(menu, "------", null); + AddMenuItem(menu, "Delete Node", () => DeleteCurrentNode()); + AddMenuItem(menu, "[BACK]", () => PopMenu()); + + return menu; + } + + private void AdjustNode(Vector3d direction, double amount) + { + if (vessel == null || vessel.patchedConicSolver == null) return; + if (vessel.patchedConicSolver.maneuverNodes.Count == 0) return; + + ManeuverNode node = vessel.patchedConicSolver.maneuverNodes[0]; + Vector3d dv = node.DeltaV; + dv += direction * amount; + node.DeltaV = dv; + node.solver.UpdateFlightPlan(); + } + + private void DeleteCurrentNode() + { + if (vessel == null || vessel.patchedConicSolver == null) return; + if (vessel.patchedConicSolver.maneuverNodes.Count == 0) return; + + vessel.patchedConicSolver.maneuverNodes[0].RemoveSelf(); + } + #endregion + + #region Execute Node + private void ExecuteNode() + { + if (mjCore == null) return; + if (MechJebProxy.IsNodeExecutorRunning(mjCore)) + { + MechJebProxy.AbortNode(mjCore); + } + else + { + MechJebProxy.ExecuteOneNode(mjCore, null); + } + } + #endregion + + #region Rendezvous Menu + private TextMenu BuildRendezvousMenu() + { + var menu = new TextMenu(); + menu.labelColor = JUtil.ColorToColorTag(Color.white); + menu.selectedColor = JUtil.ColorToColorTag(Color.green); + menu.disabledColor = JUtil.ColorToColorTag(Color.gray); + + AddToggleItem(menu, "ENGAGE Rendezvous Autopilot", + core => MechJebProxy.IsRendezvousAutopilotEngaged(core), + (core, val) => MechJebProxy.SetRendezvousAutopilotEngaged(core, val)); + + AddMenuItem(menu, "------", null); + + AddNumericItem(menu, "Desired Distance", + core => MechJebProxy.GetRendezvousDesiredDistance(core), + (core, val) => MechJebProxy.SetRendezvousDesiredDistance(core, val), + 10.0, v => v.ToString("F0") + " m", null, true, 0, false, 0); + + AddNumericItem(menu, "Max Phasing Orbits", + core => MechJebProxy.GetRendezvousMaxPhasingOrbits(core), + (core, val) => MechJebProxy.SetRendezvousMaxPhasingOrbits(core, (int)val), + 1.0, v => v.ToString("F0"), null, true, 0, false, 0); + AddNumericItem(menu, "Max Closing Speed", + core => MechJebProxy.GetRendezvousMaxClosingSpeed(core), + (core, val) => MechJebProxy.SetRendezvousMaxClosingSpeed(core, val), + 1.0, v => v.ToString("F1") + " m/s", null, true, 0, false, 0); + + AddMenuItem(menu, "------", null); + + // Info display + AddMenuItem(menu, "-- RENDEZVOUS INFO --", null); + var rendezvousStatus = new TextMenu.Item("Status: ---", null); + menu.Add(rendezvousStatus); + trackedItems.Add(new TrackedMenuItem + { + item = rendezvousStatus, + id = "RendezvousStatus", + isEnabled = core => true, + getLabel = core => "Status: " + MechJebProxy.GetRendezvousStatus(core) + }); + + AddMenuItem(menu, "[BACK]", () => PopMenu()); + + return menu; + } + #endregion + + #region Docking Menu + private TextMenu BuildDockingMenu() + { + var menu = new TextMenu(); + menu.labelColor = JUtil.ColorToColorTag(Color.white); + menu.selectedColor = JUtil.ColorToColorTag(Color.green); + menu.disabledColor = JUtil.ColorToColorTag(Color.gray); + + AddToggleItem(menu, "ENGAGE Docking Autopilot", + core => MechJebProxy.IsDockingAutopilotEngaged(core), + (core, val) => MechJebProxy.SetDockingAutopilotEngaged(core, val)); + + AddMenuItem(menu, "------", null); + + AddNumericItem(menu, "Speed Limit", + core => MechJebProxy.GetDockingSpeedLimit(core), + (core, val) => MechJebProxy.SetDockingSpeedLimit(core, val), + 0.1, v => v.ToString("F1") + " m/s", null, true, 0, false, 0); + + AddToggleItem(menu, "Force Roll", + core => MechJebProxy.GetDockingForceRoll(core), + (core, val) => MechJebProxy.SetDockingForceRoll(core, val)); + AddNumericItem(menu, "Roll", + core => MechJebProxy.GetDockingRoll(core), + (core, val) => MechJebProxy.SetDockingRoll(core, val), + 1.0, v => v.ToString("F1") + "°", null, true, -180, true, 180); + + AddToggleItem(menu, "Override Safe Distance", + core => MechJebProxy.GetDockingOverrideSafeDistance(core), + (core, val) => MechJebProxy.SetDockingOverrideSafeDistance(core, val)); + AddNumericItem(menu, "Safe Distance", + core => MechJebProxy.GetDockingOverridenSafeDistance(core), + (core, val) => MechJebProxy.SetDockingOverridenSafeDistance(core, val), + 0.1, v => v.ToString("F1") + " m", core => MechJebProxy.GetDockingOverrideSafeDistance(core), true, 0, false, 0); + + AddToggleItem(menu, "Override Target Size", + core => MechJebProxy.GetDockingOverrideTargetSize(core), + (core, val) => MechJebProxy.SetDockingOverrideTargetSize(core, val)); + AddNumericItem(menu, "Target Size", + core => MechJebProxy.GetDockingOverridenTargetSize(core), + (core, val) => MechJebProxy.SetDockingOverridenTargetSize(core, val), + 0.1, v => v.ToString("F1") + " m", core => MechJebProxy.GetDockingOverrideTargetSize(core), true, 0, false, 0); + + AddToggleItem(menu, "Draw Bounding Box", + core => MechJebProxy.GetDockingDrawBoundingBox(core), + (core, val) => MechJebProxy.SetDockingDrawBoundingBox(core, val)); + + AddMenuItem(menu, "------", null); + + // Status + AddMenuItem(menu, "Status:", null); + var dockingStatus = new TextMenu.Item(" ---", null); + menu.Add(dockingStatus); + trackedItems.Add(new TrackedMenuItem + { + item = dockingStatus, + id = "DockingStatus", + isEnabled = core => true, + getLabel = core => " " + MechJebProxy.GetDockingStatus(core) + }); + + AddMenuItem(menu, "[BACK]", () => PopMenu()); + + return menu; + } + #endregion + + #region Translatron Menu + private TextMenu BuildTranslatronMenu() + { + var menu = new TextMenu(); + menu.labelColor = JUtil.ColorToColorTag(Color.white); + menu.selectedColor = JUtil.ColorToColorTag(Color.green); + menu.disabledColor = JUtil.ColorToColorTag(Color.gray); + + AddMenuItem(menu, "-- MODE --", null); + AddMenuItem(menu, "OFF", () => MechJebProxy.SetTranslatronMode(mjCore, MechJebProxy.TranslatronMode.OFF)); + AddMenuItem(menu, "Keep Orbital Vel", () => MechJebProxy.SetTranslatronMode(mjCore, MechJebProxy.TranslatronMode.KEEP_OBT)); + AddMenuItem(menu, "Keep Surface Vel", () => MechJebProxy.SetTranslatronMode(mjCore, MechJebProxy.TranslatronMode.KEEP_SURF)); + AddMenuItem(menu, "Keep Vertical Vel", () => MechJebProxy.SetTranslatronMode(mjCore, MechJebProxy.TranslatronMode.KEEP_VERT)); + AddMenuItem(menu, "Keep Relative Vel", () => MechJebProxy.SetTranslatronMode(mjCore, MechJebProxy.TranslatronMode.KEEP_REL), + core => FlightGlobals.fetch.VesselTarget != null); + AddMenuItem(menu, "Direct", () => MechJebProxy.SetTranslatronMode(mjCore, MechJebProxy.TranslatronMode.DIRECT)); + + AddMenuItem(menu, "------", null); + + AddNumericItem(menu, "Target Speed", + core => MechJebProxy.GetTranslatronSpeed(core), + (core, val) => MechJebProxy.SetTranslatronSpeed(core, val), + 0.1, v => v.ToString("F1") + " m/s", null, true, 0, false, 0); + + AddToggleItem(menu, "Kill Horizontal", + core => MechJebProxy.GetTranslatronKillH(core), + (core, val) => MechJebProxy.SetTranslatronKillH(core, val)); + + AddMenuItem(menu, "------", null); + + AddMenuItem(menu, "!! PANIC !!", () => MechJebProxy.PanicSwitch(mjCore)); + + AddMenuItem(menu, "[BACK]", () => PopMenu()); + + return menu; + } + #endregion + + #region Rover Menu + private TextMenu BuildRoverMenu() + { + var menu = new TextMenu(); + menu.labelColor = JUtil.ColorToColorTag(Color.white); + menu.selectedColor = JUtil.ColorToColorTag(Color.green); + menu.disabledColor = JUtil.ColorToColorTag(Color.gray); + + AddMenuItem(menu, "Drive to Target", () => DriveToTarget(), + core => MechJebProxy.PositionTargetExists(core)); + AddMenuItem(menu, "STOP", () => StopRover()); + + AddMenuItem(menu, "------", null); + + AddToggleItem(menu, "Control Heading", + core => MechJebProxy.GetRoverControlHeading(core), + (core, val) => MechJebProxy.SetRoverControlHeading(core, val)); + AddNumericItem(menu, "Heading", + core => MechJebProxy.GetRoverHeading(core), + (core, val) => MechJebProxy.SetRoverHeading(core, val), + 1.0, v => v.ToString("F1") + "°", null, true, 0, true, 360); + + AddToggleItem(menu, "Control Speed", + core => MechJebProxy.GetRoverControlSpeed(core), + (core, val) => MechJebProxy.SetRoverControlSpeed(core, val)); + AddNumericItem(menu, "Speed", + core => MechJebProxy.GetRoverSpeed(core), + (core, val) => MechJebProxy.SetRoverSpeed(core, val), + 0.5, v => v.ToString("F1") + " m/s", null, true, 0, false, 0); + + AddMenuItem(menu, "------", null); + + AddToggleItem(menu, "Stability Control", + core => MechJebProxy.GetRoverStabilityControl(core), + (core, val) => MechJebProxy.SetRoverStabilityControl(core, val)); + AddToggleItem(menu, "Brake on Eject", + core => MechJebProxy.GetRoverBrakeOnEject(core), + (core, val) => MechJebProxy.SetRoverBrakeOnEject(core, val)); + AddToggleItem(menu, "Brake on Energy Depletion", + core => MechJebProxy.GetRoverBrakeOnEnergyDepletion(core), + (core, val) => MechJebProxy.SetRoverBrakeOnEnergyDepletion(core, val)); + AddToggleItem(menu, "Warp to Daylight", + core => MechJebProxy.GetRoverWarpToDaylight(core), + (core, val) => MechJebProxy.SetRoverWarpToDaylight(core, val)); + + AddMenuItem(menu, "------", null); + + AddMenuItem(menu, "Waypoints", () => PushMenu(BuildRoverWaypointsMenu())); + + AddMenuItem(menu, "[BACK]", () => PopMenu()); + + return menu; + } + + private TextMenu BuildRoverWaypointsMenu() + { + var menu = new TextMenu(); + menu.labelColor = JUtil.ColorToColorTag(Color.white); + menu.selectedColor = JUtil.ColorToColorTag(Color.green); + + AddMenuItem(menu, "Add Waypoint", () => AddRoverWaypoint()); + AddMenuItem(menu, "Clear All Waypoints", () => ClearRoverWaypoints()); + // Waypoint list would go here + + AddMenuItem(menu, "[BACK]", () => PopMenu()); + + return menu; + } + + private void DriveToTarget() + { + if (mjCore == null) return; + MechJebProxy.DriveToTarget(mjCore); + } + + private void StopRover() + { + if (mjCore == null) return; + MechJebProxy.StopRover(mjCore); + } + + private void AddRoverWaypoint() + { + if (mjCore == null || vessel == null) return; + MechJebProxy.AddRoverWaypointAtCurrentPosition(mjCore, vessel); + } + + private void ClearRoverWaypoints() + { + if (mjCore == null) return; + MechJebProxy.ClearRoverWaypoints(mjCore); + } + #endregion + + #region Aircraft Menu + private TextMenu BuildAircraftMenu() + { + var menu = new TextMenu(); + menu.labelColor = JUtil.ColorToColorTag(Color.white); + menu.selectedColor = JUtil.ColorToColorTag(Color.green); + menu.disabledColor = JUtil.ColorToColorTag(Color.gray); + + AddMenuItem(menu, "-- ALTITUDE --", null); + AddToggleItem(menu, "Altitude Hold", + core => MechJebProxy.GetAirplaneAltitudeHold(core), + (core, val) => MechJebProxy.SetAirplaneAltitudeHold(core, val)); + AddNumericItem(menu, "Target Altitude", + core => MechJebProxy.GetAirplaneAltitudeTarget(core), + (core, val) => MechJebProxy.SetAirplaneAltitudeTarget(core, val), + 50.0, v => v.ToString("F0") + " m", null, true, 0, false, 0); + AddToggleItem(menu, "Vertical Speed Hold", + core => MechJebProxy.GetAirplaneVertSpeedHold(core), + (core, val) => MechJebProxy.SetAirplaneVertSpeedHold(core, val)); + AddNumericItem(menu, "Target Vert Speed", + core => MechJebProxy.GetAirplaneVertSpeedTarget(core), + (core, val) => MechJebProxy.SetAirplaneVertSpeedTarget(core, val), + 1.0, v => v.ToString("F1") + " m/s", null, false, 0, false, 0); + + AddMenuItem(menu, "------", null); + + AddMenuItem(menu, "-- HEADING --", null); + AddToggleItem(menu, "Heading Hold", + core => MechJebProxy.GetAirplaneHeadingHold(core), + (core, val) => MechJebProxy.SetAirplaneHeadingHold(core, val)); + AddNumericItem(menu, "Target Heading", + core => MechJebProxy.GetAirplaneHeadingTarget(core), + (core, val) => MechJebProxy.SetAirplaneHeadingTarget(core, val), + 1.0, v => v.ToString("F1") + "°", null, true, 0, true, 360); + AddToggleItem(menu, "Roll Hold", + core => MechJebProxy.GetAirplaneRollHold(core), + (core, val) => MechJebProxy.SetAirplaneRollHold(core, val)); + AddNumericItem(menu, "Target Roll", + core => MechJebProxy.GetAirplaneRollTarget(core), + (core, val) => MechJebProxy.SetAirplaneRollTarget(core, val), + 1.0, v => v.ToString("F1") + "°", null, true, -180, true, 180); + + AddMenuItem(menu, "------", null); + + AddMenuItem(menu, "-- SPEED --", null); + AddToggleItem(menu, "Speed Hold", + core => MechJebProxy.GetAirplaneSpeedHold(core), + (core, val) => MechJebProxy.SetAirplaneSpeedHold(core, val)); + AddNumericItem(menu, "Target Speed", + core => MechJebProxy.GetAirplaneSpeedTarget(core), + (core, val) => MechJebProxy.SetAirplaneSpeedTarget(core, val), + 1.0, v => v.ToString("F1") + " m/s", null, true, 0, false, 0); + + AddMenuItem(menu, "[BACK]", () => PopMenu()); + + return menu; + } + #endregion + + #region Spaceplane Menu + private TextMenu BuildSpaceplaneMenu() + { + var menu = new TextMenu(); + menu.labelColor = JUtil.ColorToColorTag(Color.white); + menu.selectedColor = JUtil.ColorToColorTag(Color.green); + menu.disabledColor = JUtil.ColorToColorTag(Color.gray); + + AddMenuItem(menu, "Autoland", () => SpaceplaneAutoland()); + AddMenuItem(menu, "Hold Heading & Altitude", () => SpaceplaneHoldHeadingAlt()); + AddMenuItem(menu, "Autopilot OFF", () => SpaceplaneAutopilotOff()); + + AddMenuItem(menu, "------", null); + + AddNumericItem(menu, "Glideslope", + core => MechJebProxy.GetSpaceplaneGlideslope(core), + (core, val) => MechJebProxy.SetSpaceplaneGlideslope(core, val), + 0.1, v => v.ToString("F1") + "°", null, true, 0, true, 30); + AddNumericItem(menu, "Approach Speed", + core => MechJebProxy.GetSpaceplaneApproachSpeed(core), + (core, val) => MechJebProxy.SetSpaceplaneApproachSpeed(core, val), + 1.0, v => v.ToString("F1") + " m/s", null, true, 0, false, 0); + AddNumericItem(menu, "Touchdown Speed", + core => MechJebProxy.GetSpaceplaneTouchdownSpeed(core), + (core, val) => MechJebProxy.SetSpaceplaneTouchdownSpeed(core, val), + 1.0, v => v.ToString("F1") + " m/s", null, true, 0, false, 0); + + AddMenuItem(menu, "[BACK]", () => PopMenu()); + + return menu; + } + + private void SpaceplaneAutoland() + { + if (mjCore == null) return; + MechJebProxy.SpaceplaneAutoland(mjCore); + } + + private void SpaceplaneHoldHeadingAlt() + { + if (mjCore == null) return; + MechJebProxy.SpaceplaneHoldHeadingAndAltitude(mjCore); + } + + private void SpaceplaneAutopilotOff() + { + if (mjCore == null) return; + MechJebProxy.SpaceplaneAutopilotOff(mjCore); + } + #endregion + + #region Utilities Menu + private TextMenu BuildUtilitiesMenu() + { + var menu = new TextMenu(); + menu.labelColor = JUtil.ColorToColorTag(Color.white); + menu.selectedColor = JUtil.ColorToColorTag(Color.green); + menu.disabledColor = JUtil.ColorToColorTag(Color.gray); + + AddToggleItem(menu, "Autostage", + core => MechJebProxy.GetAutostage(core), + (core, val) => MechJebProxy.SetAutostage(core, val)); + AddNumericItem(menu, "Stop at Stage", + core => MechJebProxy.GetAutostageLimit(core), + (core, val) => MechJebProxy.SetAutostageLimit(core, (int)val), + 1.0, v => v.ToString("F0"), null, true, 0, false, 0); + AddMenuItem(menu, "Stage Once", () => StageOnce()); + AddMenuItem(menu, "Autostage Options", () => PushMenu(BuildAutostageOptionsMenu())); + + AddMenuItem(menu, "------", null); + + // Delta-V info + AddMenuItem(menu, "-- DELTA-V INFO --", null); + var stageVacItem = new TextMenu.Item("Stage dV (Vac):", null); + var totalVacItem = new TextMenu.Item("Total dV (Vac):", null); + var stageAtmItem = new TextMenu.Item("Stage dV (Atm):", null); + var totalAtmItem = new TextMenu.Item("Total dV (Atm):", null); + menu.Add(stageVacItem); + menu.Add(totalVacItem); + menu.Add(stageAtmItem); + menu.Add(totalAtmItem); + + trackedItems.Add(new TrackedMenuItem + { + item = stageVacItem, + id = "StageDVVac", + isEnabled = core => true, + getLabel = core => "Stage dV (Vac): " + GetStageDeltaVText(core, true) + }); + trackedItems.Add(new TrackedMenuItem + { + item = totalVacItem, + id = "TotalDVVac", + isEnabled = core => true, + getLabel = core => "Total dV (Vac): " + FormatDeltaV(MechJebProxy.GetTotalVacuumDeltaV(core)) + }); + trackedItems.Add(new TrackedMenuItem + { + item = stageAtmItem, + id = "StageDVAtm", + isEnabled = core => true, + getLabel = core => "Stage dV (Atm): " + GetStageDeltaVText(core, false) + }); + trackedItems.Add(new TrackedMenuItem + { + item = totalAtmItem, + id = "TotalDVAtm", + isEnabled = core => true, + getLabel = core => "Total dV (Atm): " + FormatDeltaV(MechJebProxy.GetTotalAtmoDeltaV(core)) + }); + + AddMenuItem(menu, "------", null); + + AddMenuItem(menu, "Warp Helper", () => PushMenu(BuildWarpHelperMenu())); + + AddMenuItem(menu, "[BACK]", () => PopMenu()); + + return menu; + } + + private TextMenu BuildAutostageOptionsMenu() + { + var menu = new TextMenu(); + menu.labelColor = JUtil.ColorToColorTag(Color.white); + menu.selectedColor = JUtil.ColorToColorTag(Color.green); + + AddNumericItem(menu, "Pre-Delay", + core => MechJebProxy.GetAutostagePreDelay(core), + (core, val) => MechJebProxy.SetAutostagePreDelay(core, val), + 0.1, v => v.ToString("F1") + " s", null, true, 0, false, 0); + AddNumericItem(menu, "Post-Delay", + core => MechJebProxy.GetAutostagePostDelay(core), + (core, val) => MechJebProxy.SetAutostagePostDelay(core, val), + 0.1, v => v.ToString("F1") + " s", null, true, 0, false, 0); + AddNumericItem(menu, "Clamp Thrust %", + core => MechJebProxy.GetClampAutoStageThrustPct(core), + (core, val) => MechJebProxy.SetClampAutoStageThrustPct(core, val), + 1.0, v => v.ToString("F0") + "%", null, true, 0, true, 100); + + AddMenuItem(menu, "------", null); + + AddNumericItem(menu, "Fairing Max Flux", + core => MechJebProxy.GetFairingMaxAerothermalFlux(core), + (core, val) => MechJebProxy.SetFairingMaxAerothermalFlux(core, val), + 1000.0, v => v.ToString("F0"), null, true, 0, false, 0); + AddNumericItem(menu, "Fairing Max Q", + core => MechJebProxy.GetFairingMaxDynamicPressure(core), + (core, val) => MechJebProxy.SetFairingMaxDynamicPressure(core, val), + 1000.0, v => v.ToString("F0") + " Pa", null, true, 0, false, 0); + AddNumericItem(menu, "Fairing Min Alt", + core => MechJebProxy.GetFairingMinAltitude(core) / 1000.0, + (core, val) => MechJebProxy.SetFairingMinAltitude(core, val * 1000.0), + 1.0, v => v.ToString("F1") + " km", null, true, 0, false, 0); + + AddMenuItem(menu, "------", null); + + AddNumericItem(menu, "Hot Staging Lead", + core => MechJebProxy.GetHotStagingLeadTime(core), + (core, val) => MechJebProxy.SetHotStagingLeadTime(core, val), + 0.1, v => v.ToString("F1") + " s", null, true, 0, false, 0); + AddToggleItem(menu, "Drop Solids", + core => MechJebProxy.GetDropSolids(core), + (core, val) => MechJebProxy.SetDropSolids(core, val)); + AddNumericItem(menu, "Drop Solids Lead", + core => MechJebProxy.GetDropSolidsLeadTime(core), + (core, val) => MechJebProxy.SetDropSolidsLeadTime(core, val), + 0.1, v => v.ToString("F1") + " s", core => MechJebProxy.GetDropSolids(core), true, 0, false, 0); + + AddMenuItem(menu, "[BACK]", () => PopMenu()); + return menu; + } + + private TextMenu BuildWarpHelperMenu() + { + var menu = new TextMenu(); + menu.labelColor = JUtil.ColorToColorTag(Color.white); + menu.selectedColor = JUtil.ColorToColorTag(Color.green); + + AddMenuItem(menu, "Warp to Apoapsis", () => WarpToApoapsis()); + AddMenuItem(menu, "Warp to Periapsis", () => WarpToPeriapsis()); + AddMenuItem(menu, "Warp to Node", () => WarpToNode(), + core => vessel != null && vessel.patchedConicSolver != null && + vessel.patchedConicSolver.maneuverNodes.Count > 0); + AddMenuItem(menu, "[BACK]", () => PopMenu()); + + return menu; + } + + private void StageOnce() + { + if (mjCore == null) return; + MechJebProxy.AutostageOnce(mjCore); + } + #endregion + + #region Info Display Menu + private TextMenu BuildInfoMenu() + { + var menu = new TextMenu(); + menu.labelColor = JUtil.ColorToColorTag(Color.white); + menu.selectedColor = JUtil.ColorToColorTag(Color.green); + + AddMenuItem(menu, "Orbit Info", () => PushMenu(BuildOrbitInfoMenu())); + AddMenuItem(menu, "Surface Info", () => PushMenu(BuildSurfaceInfoMenu())); + AddMenuItem(menu, "Target Info", () => PushMenu(BuildTargetInfoMenu()), + core => FlightGlobals.fetch.VesselTarget != null); + AddMenuItem(menu, "Vessel Info", () => PushMenu(BuildVesselInfoMenu())); + AddMenuItem(menu, "[BACK]", () => PopMenu()); + + return menu; + } + + private TextMenu BuildOrbitInfoMenu() + { + var menu = new TextMenu(); + menu.labelColor = JUtil.ColorToColorTag(Color.white); + menu.selectedColor = JUtil.ColorToColorTag(Color.green); + + // These items will have their labels updated dynamically + var apItem = new TextMenu.Item("Apoapsis:", null); + var peItem = new TextMenu.Item("Periapsis:", null); + var eccItem = new TextMenu.Item("Eccentricity:", null); + var incItem = new TextMenu.Item("Inclination:", null); + var lanItem = new TextMenu.Item("LAN:", null); + var argPeItem = new TextMenu.Item("Arg. of PE:", null); + var periodItem = new TextMenu.Item("Period:", null); + var tApItem = new TextMenu.Item("Time to AP:", null); + var tPeItem = new TextMenu.Item("Time to PE:", null); + menu.Add(apItem); + menu.Add(peItem); + menu.Add(eccItem); + menu.Add(incItem); + menu.Add(lanItem); + menu.Add(argPeItem); + menu.Add(periodItem); + menu.Add(tApItem); + menu.Add(tPeItem); + + trackedItems.Add(new TrackedMenuItem { item = apItem, id = "OrbitAp", isEnabled = core => true, getLabel = core => "Apoapsis: " + FormatDistance(vessel != null ? vessel.orbit.ApA : 0) }); + trackedItems.Add(new TrackedMenuItem { item = peItem, id = "OrbitPe", isEnabled = core => true, getLabel = core => "Periapsis: " + FormatDistance(vessel != null ? vessel.orbit.PeA : 0) }); + trackedItems.Add(new TrackedMenuItem { item = eccItem, id = "OrbitEcc", isEnabled = core => true, getLabel = core => "Eccentricity: " + (vessel != null ? vessel.orbit.eccentricity.ToString("F4") : "---") }); + trackedItems.Add(new TrackedMenuItem { item = incItem, id = "OrbitInc", isEnabled = core => true, getLabel = core => "Inclination: " + FormatAngle(vessel != null ? vessel.orbit.inclination : 0) }); + trackedItems.Add(new TrackedMenuItem { item = lanItem, id = "OrbitLAN", isEnabled = core => true, getLabel = core => "LAN: " + FormatAngle(vessel != null ? vessel.orbit.LAN : 0) }); + trackedItems.Add(new TrackedMenuItem { item = argPeItem, id = "OrbitArgPe", isEnabled = core => true, getLabel = core => "Arg. of PE: " + FormatAngle(vessel != null ? vessel.orbit.argumentOfPeriapsis : 0) }); + trackedItems.Add(new TrackedMenuItem { item = periodItem, id = "OrbitPeriod", isEnabled = core => true, getLabel = core => "Period: " + FormatTime(vessel != null ? vessel.orbit.period : 0) }); + trackedItems.Add(new TrackedMenuItem { item = tApItem, id = "OrbitTAP", isEnabled = core => true, getLabel = core => "Time to AP: " + FormatTime(GetTimeToApoapsis()) }); + trackedItems.Add(new TrackedMenuItem { item = tPeItem, id = "OrbitTPE", isEnabled = core => true, getLabel = core => "Time to PE: " + FormatTime(GetTimeToPeriapsis()) }); + AddMenuItem(menu, "[BACK]", () => PopMenu()); + + return menu; + } + + private TextMenu BuildSurfaceInfoMenu() + { + var menu = new TextMenu(); + menu.labelColor = JUtil.ColorToColorTag(Color.white); + menu.selectedColor = JUtil.ColorToColorTag(Color.green); + + var altAslItem = new TextMenu.Item("Altitude (ASL):", null); + var altAglItem = new TextMenu.Item("Altitude (AGL):", null); + var latItem = new TextMenu.Item("Latitude:", null); + var lonItem = new TextMenu.Item("Longitude:", null); + var srfSpdItem = new TextMenu.Item("Surface Speed:", null); + var vertSpdItem = new TextMenu.Item("Vertical Speed:", null); + var horizSpdItem = new TextMenu.Item("Horizontal Speed:", null); + var headingItem = new TextMenu.Item("Heading:", null); + menu.Add(altAslItem); + menu.Add(altAglItem); + menu.Add(latItem); + menu.Add(lonItem); + menu.Add(srfSpdItem); + menu.Add(vertSpdItem); + menu.Add(horizSpdItem); + menu.Add(headingItem); + + trackedItems.Add(new TrackedMenuItem { item = altAslItem, id = "SurfAltASL", isEnabled = core => true, getLabel = core => "Altitude (ASL): " + FormatDistance(vessel != null ? vessel.altitude : 0) }); + trackedItems.Add(new TrackedMenuItem { item = altAglItem, id = "SurfAltAGL", isEnabled = core => true, getLabel = core => "Altitude (AGL): " + FormatDistance(vessel != null ? vessel.radarAltitude : 0) }); + trackedItems.Add(new TrackedMenuItem { item = latItem, id = "SurfLat", isEnabled = core => true, getLabel = core => "Latitude: " + FormatAngle(vessel != null ? vessel.latitude : 0) }); + trackedItems.Add(new TrackedMenuItem { item = lonItem, id = "SurfLon", isEnabled = core => true, getLabel = core => "Longitude: " + FormatAngle(vessel != null ? vessel.longitude : 0) }); + trackedItems.Add(new TrackedMenuItem { item = srfSpdItem, id = "SurfSpd", isEnabled = core => true, getLabel = core => "Surface Speed: " + FormatSpeed(vessel != null ? vessel.srfSpeed : 0) }); + trackedItems.Add(new TrackedMenuItem { item = vertSpdItem, id = "VertSpd", isEnabled = core => true, getLabel = core => "Vertical Speed: " + FormatSpeed(vessel != null ? vessel.verticalSpeed : 0) }); + trackedItems.Add(new TrackedMenuItem { item = horizSpdItem, id = "HorizSpd", isEnabled = core => true, getLabel = core => "Horizontal Speed: " + FormatSpeed(vessel != null ? vessel.horizontalSrfSpeed : 0) }); + trackedItems.Add(new TrackedMenuItem { item = headingItem, id = "Heading", isEnabled = core => true, getLabel = core => "Heading: " + FormatAngle(GetSurfaceHeading()) }); + AddMenuItem(menu, "[BACK]", () => PopMenu()); + + return menu; + } + + private TextMenu BuildTargetInfoMenu() + { + var menu = new TextMenu(); + menu.labelColor = JUtil.ColorToColorTag(Color.white); + menu.selectedColor = JUtil.ColorToColorTag(Color.green); + + var distItem = new TextMenu.Item("Distance:", null); + var relVelItem = new TextMenu.Item("Relative Velocity:", null); + var caItem = new TextMenu.Item("Closest Approach:", null); + var tcaItem = new TextMenu.Item("Time to Closest:", null); + var relIncItem = new TextMenu.Item("Rel Inclination:", null); + menu.Add(distItem); + menu.Add(relVelItem); + menu.Add(caItem); + menu.Add(tcaItem); + menu.Add(relIncItem); + + trackedItems.Add(new TrackedMenuItem { item = distItem, id = "TgtDist", isEnabled = core => true, getLabel = core => "Distance: " + GetTargetDistanceText() }); + trackedItems.Add(new TrackedMenuItem { item = relVelItem, id = "TgtRelVel", isEnabled = core => true, getLabel = core => "Relative Velocity: " + GetTargetRelVelText() }); + trackedItems.Add(new TrackedMenuItem { item = caItem, id = "TgtCA", isEnabled = core => true, getLabel = core => "Closest Approach: " + GetTargetClosestApproachText() }); + trackedItems.Add(new TrackedMenuItem { item = tcaItem, id = "TgtTCA", isEnabled = core => true, getLabel = core => "Time to Closest: " + GetTargetTimeToClosestText() }); + trackedItems.Add(new TrackedMenuItem { item = relIncItem, id = "TgtRelInc", isEnabled = core => true, getLabel = core => "Rel Inclination: " + GetTargetRelInclinationText() }); + AddMenuItem(menu, "[BACK]", () => PopMenu()); + + return menu; + } + + private TextMenu BuildVesselInfoMenu() + { + var menu = new TextMenu(); + menu.labelColor = JUtil.ColorToColorTag(Color.white); + menu.selectedColor = JUtil.ColorToColorTag(Color.green); + + var massItem = new TextMenu.Item("Mass:", null); + var twrItem = new TextMenu.Item("TWR:", null); + var maxThrustItem = new TextMenu.Item("Max Thrust:", null); + var curThrustItem = new TextMenu.Item("Current Thrust:", null); + var dvVacItem = new TextMenu.Item("Total dV (Vac):", null); + var dvAtmItem = new TextMenu.Item("Total dV (Atm):", null); + menu.Add(massItem); + menu.Add(twrItem); + menu.Add(maxThrustItem); + menu.Add(curThrustItem); + menu.Add(dvVacItem); + menu.Add(dvAtmItem); + + trackedItems.Add(new TrackedMenuItem { item = massItem, id = "VesselMass", isEnabled = core => true, getLabel = core => "Mass: " + GetVesselMassText() }); + trackedItems.Add(new TrackedMenuItem { item = twrItem, id = "VesselTWR", isEnabled = core => true, getLabel = core => "TWR: " + GetVesselTwrText() }); + trackedItems.Add(new TrackedMenuItem { item = maxThrustItem, id = "VesselMaxThrust", isEnabled = core => true, getLabel = core => "Max Thrust: " + GetVesselMaxThrustText() }); + trackedItems.Add(new TrackedMenuItem { item = curThrustItem, id = "VesselCurThrust", isEnabled = core => true, getLabel = core => "Current Thrust: " + GetVesselCurrentThrustText() }); + trackedItems.Add(new TrackedMenuItem { item = dvVacItem, id = "VesselDVVac", isEnabled = core => true, getLabel = core => "Total dV (Vac): " + FormatDeltaV(MechJebProxy.GetTotalVacuumDeltaV(core)) }); + trackedItems.Add(new TrackedMenuItem { item = dvAtmItem, id = "VesselDVAtm", isEnabled = core => true, getLabel = core => "Total dV (Atm): " + FormatDeltaV(MechJebProxy.GetTotalAtmoDeltaV(core)) }); + AddMenuItem(menu, "[BACK]", () => PopMenu()); + + return menu; + } + #endregion + + #region Settings Menu + private TextMenu BuildSettingsMenu() + { + var menu = new TextMenu(); + menu.labelColor = JUtil.ColorToColorTag(Color.white); + menu.selectedColor = JUtil.ColorToColorTag(Color.green); + + AddMenuItem(menu, "-- THRUST LIMITS --", null); + AddToggleItem(menu, "Prevent Overheats", + core => MechJebProxy.GetLimitToPreventOverheats(core), + (core, val) => MechJebProxy.SetLimitToPreventOverheats(core, val)); + AddToggleItem(menu, "Limit by Max Q", + core => MechJebProxy.GetLimitToMaxDynamicPressure(core), + (core, val) => MechJebProxy.SetLimitToMaxDynamicPressure(core, val)); + AddToggleItem(menu, "Limit to Terminal Velocity", + core => MechJebProxy.GetLimitToTerminalVelocity(core), + (core, val) => MechJebProxy.SetLimitToTerminalVelocity(core, val)); + AddToggleItem(menu, "Limit Acceleration", + core => MechJebProxy.GetLimitAcceleration(core), + (core, val) => MechJebProxy.SetLimitAcceleration(core, val)); + AddToggleItem(menu, "Limit Throttle", + core => MechJebProxy.GetLimitThrottle(core), + (core, val) => MechJebProxy.SetLimitThrottle(core, val)); + + AddMenuItem(menu, "------", null); + + AddToggleItem(menu, "Prevent Flameout", + core => MechJebProxy.GetLimitToPreventFlameout(core), + (core, val) => MechJebProxy.SetLimitToPreventFlameout(core, val)); + AddNumericItem(menu, "Flameout Safety", + core => MechJebProxy.GetFlameoutSafetyPct(core), + (core, val) => MechJebProxy.SetFlameoutSafetyPct(core, val), + 1.0, v => v.ToString("F0") + "%", null, true, 0, true, 100); + AddToggleItem(menu, "Smooth Throttle", + core => MechJebProxy.GetSmoothThrottle(core), + (core, val) => MechJebProxy.SetSmoothThrottle(core, val)); + AddToggleItem(menu, "Manage Intakes", + core => MechJebProxy.GetManageIntakes(core), + (core, val) => MechJebProxy.SetManageIntakes(core, val)); + AddToggleItem(menu, "Differential Throttle", + core => MechJebProxy.GetDifferentialThrottle(core), + (core, val) => MechJebProxy.SetDifferentialThrottle(core, val)); + + AddMenuItem(menu, "------", null); + + AddMenuItem(menu, "-- NODE EXECUTION --", null); + AddToggleItem(menu, "Auto-Warp", + core => MechJebProxy.GetNodeAutowarp(core), + (core, val) => MechJebProxy.SetNodeAutowarp(core, val)); + + AddMenuItem(menu, "[BACK]", () => PopMenu()); + + return menu; + } + #endregion + + #region Menu Navigation + private void PushMenu(TextMenu newMenu) + { + if (newMenu != null) + { + menuStack.Push(currentMenu); + currentMenu = newMenu; + } + } + + private void PopMenu() + { + if (menuStack.Count > 0) + { + currentMenu = menuStack.Pop(); + } + } + + private void GoHome() + { + menuStack.Clear(); + currentMenu = topMenu; + } + + private bool IsAscentAvailable(Vessel v) + { + if (v == null) return false; + if (v.LandedOrSplashed) return true; + + if (v.situation == Vessel.Situations.ORBITING) + { + double atmosphere = v.mainBody != null ? v.mainBody.atmosphereDepth : 0; + if (atmosphere <= 0) atmosphere = 0; + return !(v.orbit.PeA > atmosphere && v.orbit.ApA > atmosphere); + } + + return true; + } + #endregion + + #region Update Loop + public void Update() + { + if (vessel == null || !MechJebProxy.IsAvailable) return; + + // Update MechJeb core reference if vessel changed + if (vessel != activeVessel || mjCore == null) + { + activeVessel = vessel; + mjCore = MechJebProxy.GetMasterMechJeb(vessel); + } + + // Update tracked items + UpdateTrackedItems(); + UpdateAdvancedTransferStatus(); + + double ut = Planetarium.GetUniversalTime(); + if (ut - lastStageStatsUpdateUT > 1.0) + { + MechJebProxy.RequestStageStatsUpdate(mjCore, this); + lastStageStatsUpdateUT = ut; + } + } + + // LEGACY: UpdateAdvancedTransferStatus - no longer needed + // Status is now computed dynamically in GetAdvancedTransferStatusText() using wrapper + private void UpdateAdvancedTransferStatus() + { + // Status is now computed on-demand in GetAdvancedTransferStatusText() + // using MechJeb's actual operation instance from GetOperationByName + } + + private void UpdateTrackedItems() + { + if (mjCore == null) return; + + foreach (var tracked in trackedItems) + { + try + { + // Update enabled state + if (tracked.isEnabled != null) + { + tracked.item.isDisabled = !tracked.isEnabled(mjCore); + } + + // Update label + if (tracked.getLabel != null) + { + string newLabel = tracked.getLabel(mjCore); + if (!string.IsNullOrEmpty(newLabel)) + { + tracked.item.labelText = newLabel; + } + } + + // Update selected state (for toggles) + if (tracked.isSelected != null) + { + tracked.item.isSelected = tracked.isSelected(mjCore); + } + } + catch (Exception) + { + // Silently ignore - keep existing label + } + } + + UpdateSmartASSSelections(); + } + + private void UpdateSmartASSSelections() + { + object smartass = MechJebProxy.GetSmartASS(mjCore); + if (smartass == null) return; + + int currentTarget = MechJebProxy.GetSmartASSTarget(smartass); + + UpdateMenuSelectionById(smartassOrbitalMenu, currentTarget); + UpdateMenuSelectionById(smartassSurfaceMenu, currentTarget); + UpdateMenuSelectionById(smartassTargetMenu, currentTarget); + } + + private void UpdateMenuSelectionById(TextMenu menu, int targetId) + { + if (menu == null) return; + + for (int i = 0; i < menu.Count; i++) + { + bool match = (menu[i].id == targetId); + menu[i].isSelected = match; + } + } + #endregion + + #region Maneuver Planner Extras + private void CreateCourseCorrection() + { + if (vessel == null || mjCore == null) return; + object targetController = MechJebProxy.GetTargetController(mjCore); + if (targetController == null) return; + + object op = MechJebProxy.CreateCourseCorrectionOperation(); + if (op == null) return; + + MechJebProxy.SetCourseCorrectionTargetPe(op, courseCorrectionPeKm); + MechJebProxy.CreateNodesFromOperation(op, vessel.orbit, Planetarium.GetUniversalTime(), targetController, vessel); + } + + private void MatchVelocitiesNow() + { + if (vessel == null || FlightGlobals.fetch.VesselTarget == null) return; + Orbit target = FlightGlobals.fetch.VesselTarget.GetOrbit(); + if (target == null) return; + + double ut = Planetarium.GetUniversalTime(); + Vector3d dV = MechJebProxy.CalcDeltaVToMatchVelocities(vessel.orbit, ut, target); + MechJebProxy.PlaceManeuverNode(vessel, vessel.orbit, dV, ut); + } + #endregion + + #region Warp Helpers + private void WarpToApoapsis() + { + if (vessel == null) return; + double ut = vessel.orbit.NextApoapsisTime(Planetarium.GetUniversalTime()); + MechJebProxy.WarpToUT(mjCore, ut); + } + + private void WarpToPeriapsis() + { + if (vessel == null) return; + double ut = vessel.orbit.NextPeriapsisTime(Planetarium.GetUniversalTime()); + MechJebProxy.WarpToUT(mjCore, ut); + } + + private void WarpToNode() + { + if (vessel == null || vessel.patchedConicSolver == null) return; + if (vessel.patchedConicSolver.maneuverNodes.Count == 0) return; + double ut = vessel.patchedConicSolver.maneuverNodes[0].UT; + MechJebProxy.WarpToUT(mjCore, ut); + } + + private void WarpToSOI() + { + if (vessel == null) return; + if (vessel.orbit.patchEndTransition == Orbit.PatchTransitionType.FINAL) return; + double ut = vessel.orbit.EndUT; + MechJebProxy.WarpToUT(mjCore, ut); + } + #endregion + + #region Landing Prediction Helpers + private string GetLandingPredLatitude(object core) + { + object result = MechJebProxy.GetLandingPredictionResult(core); + if (result == null) return "---"; + double lat, lon; + MechJebProxy.GetLandingEndPosition(result, out lat, out lon); + return lat.ToString("F3") + "°"; + } + + private string GetLandingPredLongitude(object core) + { + object result = MechJebProxy.GetLandingPredictionResult(core); + if (result == null) return "---"; + double lat, lon; + MechJebProxy.GetLandingEndPosition(result, out lat, out lon); + return lon.ToString("F3") + "°"; + } + + private string GetLandingPredTime(object core) + { + object result = MechJebProxy.GetLandingPredictionResult(core); + if (result == null) return "---"; + double ut = MechJebProxy.GetLandingEndUT(result); + double dt = ut - Planetarium.GetUniversalTime(); + return FormatTime(dt); + } + + private string GetLandingPredGees(object core) + { + object result = MechJebProxy.GetLandingPredictionResult(core); + if (result == null) return "---"; + double gees = MechJebProxy.GetLandingMaxDragGees(result); + return gees.ToString("F2"); + } + #endregion + + #region Formatting Helpers + private static string FormatDistance(double meters) + { + if (double.IsNaN(meters)) return "---"; + if (Math.Abs(meters) >= 1000.0) return (meters / 1000.0).ToString("F1") + " km"; + return meters.ToString("F1") + " m"; + } + + private static string FormatSpeed(double mps) + { + if (double.IsNaN(mps)) return "---"; + return mps.ToString("F1") + " m/s"; + } + + private static string FormatDeltaV(double mps) + { + if (double.IsNaN(mps)) return "---"; + return mps.ToString("F0") + " m/s"; + } + + private static string FormatAngle(double deg) + { + if (double.IsNaN(deg)) return "---"; + return deg.ToString("F2") + "°"; + } + + private static string FormatTime(double seconds) + { + if (double.IsNaN(seconds)) return "---"; + if (seconds < 0) seconds = 0; + return KSPUtil.PrintTimeCompact(seconds, false); + } + + private double GetTimeToApoapsis() + { + if (vessel == null) return 0; + double ut = vessel.orbit.NextApoapsisTime(Planetarium.GetUniversalTime()); + return ut - Planetarium.GetUniversalTime(); + } + + private double GetTimeToPeriapsis() + { + if (vessel == null) return 0; + double ut = vessel.orbit.NextPeriapsisTime(Planetarium.GetUniversalTime()); + return ut - Planetarium.GetUniversalTime(); + } + + private string GetStageDeltaVText(object core, bool vacuum) + { + var stats = vacuum ? MechJebProxy.GetVacuumStageStats(core) : MechJebProxy.GetAtmoStageStats(core); + if (stats == null || stats.Count == 0) return "---"; + double dv = MechJebProxy.GetStageDeltaV(stats[0]); + return FormatDeltaV(dv); + } + + private string GetTargetDistanceText() + { + if (vessel == null || FlightGlobals.fetch.VesselTarget == null) return "---"; + ITargetable target = FlightGlobals.fetch.VesselTarget; + Vector3d tgtPos = target.GetTransform().position; + return FormatDistance(Vector3d.Distance(vessel.GetWorldPos3D(), tgtPos)); + } + + private string GetTargetRelVelText() + { + if (vessel == null || FlightGlobals.fetch.VesselTarget == null) return "---"; + Orbit targetOrbit = FlightGlobals.fetch.VesselTarget.GetOrbit(); + if (targetOrbit == null) return "---"; + double ut = Planetarium.GetUniversalTime(); + Vector3d v1 = vessel.orbit.SwappedOrbitalVelocityAtUT(ut); + Vector3d v2 = targetOrbit.SwappedOrbitalVelocityAtUT(ut); + return FormatSpeed((v1 - v2).magnitude); + } + + private string GetTargetClosestApproachText() + { + if (vessel == null || FlightGlobals.fetch.VesselTarget == null) return "---"; + Orbit targetOrbit = FlightGlobals.fetch.VesselTarget.GetOrbit(); + if (targetOrbit == null) return "---"; + double dist = vessel.orbit.NextClosestApproachDistance(targetOrbit, Planetarium.GetUniversalTime()); + return FormatDistance(dist); + } + + private string GetTargetTimeToClosestText() + { + if (vessel == null || FlightGlobals.fetch.VesselTarget == null) return "---"; + Orbit targetOrbit = FlightGlobals.fetch.VesselTarget.GetOrbit(); + if (targetOrbit == null) return "---"; + double ut = vessel.orbit.NextClosestApproachTime(targetOrbit, Planetarium.GetUniversalTime()); + return FormatTime(ut - Planetarium.GetUniversalTime()); + } + + private string GetTargetRelInclinationText() + { + if (vessel == null || FlightGlobals.fetch.VesselTarget == null) return "---"; + Orbit targetOrbit = FlightGlobals.fetch.VesselTarget.GetOrbit(); + if (targetOrbit == null) return "---"; + double rel = Vector3d.Angle(vessel.orbit.GetOrbitNormal(), targetOrbit.GetOrbitNormal()); + return rel.ToString("F2") + "°"; + } + + private string GetVesselMassText() + { + if (vessel == null) return "---"; + return vessel.GetTotalMass().ToString("F2") + " t"; + } + + private string GetVesselTwrText() + { + if (vessel == null) return "---"; + double g = vessel.mainBody != null ? vessel.mainBody.GeeASL * 9.80665 : 9.80665; + double thrust = GetMaxThrust(); + double mass = vessel.GetTotalMass(); + if (mass <= 0) return "---"; + return (thrust / (mass * g)).ToString("F2"); + } + + private string GetVesselMaxThrustText() + { + if (vessel == null) return "---"; + return GetMaxThrust().ToString("F0") + " kN"; + } + + private string GetVesselCurrentThrustText() + { + if (vessel == null) return "---"; + return GetCurrentThrust().ToString("F0") + " kN"; + } + + private double GetSurfaceHeading() + { + if (vessel == null || vessel.ReferenceTransform == null) return 0; + return vessel.ReferenceTransform.rotation.eulerAngles.y; + } + + private double GetMaxThrust() + { + if (vessel == null) return 0; + double max = 0; + var engines = vessel.FindPartModulesImplementing(); + for (int i = 0; i < engines.Count; i++) + { + ModuleEngines engine = engines[i]; + if (engine == null) continue; + double limiter = engine.thrustPercentage / 100.0; + max += engine.maxThrust * limiter; + } + return max; + } + + private double GetCurrentThrust() + { + if (vessel == null) return 0; + double current = 0; + var engines = vessel.FindPartModulesImplementing(); + for (int i = 0; i < engines.Count; i++) + { + ModuleEngines engine = engines[i]; + if (engine == null) continue; + current += engine.finalThrust; + } + return current; + } + #endregion + + #region Button Handlers + public void PageActive(bool active, int pageNumber) + { + pageActiveState = active; + } + + // Alias for compatibility with configs that use ClickProcessor + public void ClickProcessor(int buttonID) + { + ButtonProcessor(buttonID); + } + + public void ButtonProcessor(int buttonID) + { + if (!pageActiveState || currentMenu == null) return; + + if (buttonID == buttonUp) + { + currentMenu.PreviousItem(); + } + else if (buttonID == buttonDown) + { + currentMenu.NextItem(); + } + else if (buttonID == buttonEnter) + { + currentMenu.SelectItem(); + UpdateTrackedItems(); + } + else if (buttonID == buttonEsc) + { + PopMenu(); + } + else if (buttonID == buttonHome) + { + GoHome(); + } + else if (buttonID == buttonRight) + { + // For value items, increase + IncrementCurrentValue(1); + } + else if (buttonID == buttonLeft) + { + // For value items, decrease + IncrementCurrentValue(-1); + } + } + + private void IncrementCurrentValue(int direction) + { + if (mjCore == null || currentMenu == null) return; + + TextMenu.Item currentItem = currentMenu.GetCurrentItem(); + if (currentItem == null) return; + + for (int i = 0; i < trackedItems.Count; i++) + { + TrackedMenuItem tracked = trackedItems[i]; + if (tracked.item == currentItem && tracked.isValueItem && tracked.getNumber != null && tracked.setNumber != null) + { + double current = tracked.getNumber(mjCore); + double next = current + (tracked.step * direction); + + if (tracked.hasMin && next < tracked.min) next = tracked.min; + if (tracked.hasMax && next > tracked.max) next = tracked.max; + + tracked.setNumber(mjCore, next); + UpdateTrackedItems(); + break; + } + } + } + #endregion + + #region Render + public string ShowMenu(int screenWidth, int screenHeight) + { + if (!MechJebProxy.IsAvailable) + { + return "MechJeb not available\n\n" + (MechJebProxy.InitializationError ?? "Unknown error"); + } + + if (mjCore == null) + { + return "No MechJeb core found on this vessel"; + } + + UpdateTrackedItems(); + + return pageTitle + Environment.NewLine + currentMenu.ShowMenu(screenWidth, screenHeight - 1); + } + #endregion + } +}