diff --git a/MechJeb2/MechJebModuleDeployableAntennaController.cs b/MechJeb2/MechJebModuleDeployableAntennaController.cs index 98c7acd77..f9825c120 100644 --- a/MechJeb2/MechJebModuleDeployableAntennaController.cs +++ b/MechJeb2/MechJebModuleDeployableAntennaController.cs @@ -13,33 +13,29 @@ public MechJebModuleDeployableAntennaController(MechJebCore core) : base(core) [GeneralInfoItem("#MechJeb_ToggleAntennas", InfoItem.Category.Misc, showInEditor = false)] //Toggle antennas public void AntennaDeployButton() { - autoDeploy = GUILayout.Toggle(autoDeploy, Localizer.Format("#MechJeb_Autodeployantennas")); //"Auto-deploy antennas" + AutoDeploy = GUILayout.Toggle(AutoDeploy, Localizer.Format("#MechJeb_Autodeployantennas")); //"Auto-deploy antennas" - if (GUILayout.Button(buttonText)) - { - if (ExtendingOrRetracting()) - return; + if (!GUILayout.Button(ButtonText)) return; + + if (ExtendingOrRetracting()) + return; - if (!extended) - ExtendAll(); - else - RetractAll(); - } + if (!Extended) + ExtendAll(); + else + RetractAll(); } - protected override bool isModules(ModuleDeployablePart p) => p is ModuleDeployableAntenna; + protected override bool IsModules(ModuleDeployablePart p) => p is ModuleDeployableAntenna; - protected override string getButtonText(DeployablePartState deployablePartState) + protected override string GetButtonText(DeployablePartState deployablePartState) { - switch (deployablePartState) + return deployablePartState switch { - case DeployablePartState.EXTENDED: - return Localizer.Format("#MechJeb_AntennasEXTENDED"); //"Toggle antennas (currently extended)" - case DeployablePartState.RETRACTED: - return Localizer.Format("#MechJeb_AntennasRETRACTED"); //"Toggle antennas (currently retracted)" - default: - return Localizer.Format("#MechJeb_AntennasToggle"); //"Toggle antennas" - } + DeployablePartState.EXTENDED => Localizer.Format("#MechJeb_AntennasEXTENDED"), //"Toggle antennas (currently extended)" + DeployablePartState.RETRACTED => Localizer.Format("#MechJeb_AntennasRETRACTED"), //"Toggle antennas (currently retracted)" + _ => Localizer.Format("#MechJeb_AntennasToggle") + }; } } } diff --git a/MechJeb2/MechJebModuleDeployableController.cs b/MechJeb2/MechJebModuleDeployableController.cs index bc8235d2a..9c81b7184 100644 --- a/MechJeb2/MechJebModuleDeployableController.cs +++ b/MechJeb2/MechJebModuleDeployableController.cs @@ -7,62 +7,64 @@ namespace MuMech { public abstract class MechJebModuleDeployableController : ComputerModule { - public MechJebModuleDeployableController(MechJebCore core) : base(core) + protected MechJebModuleDeployableController(MechJebCore core) : base(core) { Priority = 200; - Enabled = true; + Enabled = true; } - protected string buttonText; - protected bool extended; + protected string ButtonText; + protected bool Extended; + [UsedImplicitly] [Persistent(pass = (int)Pass.GLOBAL)] - public bool autoDeploy = false; + public bool AutoDeploy; [UsedImplicitly] [Persistent(pass = (int)Pass.LOCAL)] - public bool prev_shouldDeploy; - - public bool prev_autoDeploy = true; + public bool PrevShouldDeploy; - protected string type = ""; + public bool PrevAutoDeploy = true; - protected readonly List cachedPartModules = new List(16); + [UsedImplicitly] + protected readonly List CachedPartModules = new List(16); + [UsedImplicitly] protected void DiscoverDeployablePartModules() { - cachedPartModules.Clear(); + CachedPartModules.Clear(); foreach (Part p in Vessel.Parts) foreach (PartModule pm in p.Modules) - if (pm != null && pm is ModuleDeployablePart mdp && isModules(mdp)) - cachedPartModules.Add(mdp); + if (pm != null && pm is ModuleDeployablePart mdp && IsModules(mdp)) + CachedPartModules.Add(mdp); } - protected bool isDeployable(ModuleDeployablePart sa) => sa.Events["Extend"].active || sa.Events["Retract"].active; + [UsedImplicitly] + protected bool IsDeployable(ModuleDeployablePart sa) => sa.Events["Extend"].active || sa.Events["Retract"].active; public void ExtendAll() { - foreach (ModuleDeployablePart mdp in cachedPartModules) - if (mdp != null && isDeployable(mdp) && !mdp.part.ShieldedFromAirstream) + foreach (ModuleDeployablePart mdp in CachedPartModules) + if (!(mdp is null) && IsDeployable(mdp) && !mdp.part.ShieldedFromAirstream) mdp.Extend(); } public void RetractAll() { - foreach (ModuleDeployablePart mdp in cachedPartModules) - if (mdp != null && isDeployable(mdp) && !mdp.part.ShieldedFromAirstream) + foreach (ModuleDeployablePart mdp in CachedPartModules) + if (!(mdp is null) && IsDeployable(mdp) && !mdp.part.ShieldedFromAirstream) mdp.Retract(); } public bool AllRetracted() { - foreach (ModuleDeployablePart mdp in cachedPartModules) - if (mdp != null && isDeployable(mdp) && mdp.deployState != ModuleDeployablePart.DeployState.RETRACTED) + foreach (ModuleDeployablePart mdp in CachedPartModules) + if (!(mdp is null) && IsDeployable(mdp) && mdp.deployState != ModuleDeployablePart.DeployState.RETRACTED) return false; return true; } - public bool ShouldDeploy() + private bool ShouldDeploy() { if (!MainBody.atmosphere) return true; @@ -73,61 +75,63 @@ public bool ShouldDeploy() if (Vessel.LandedOrSplashed) return false; // True adds too many complex case - double dt = 10; - double min_alt; // minimum altitude between now and now+dt seconds + const double DT = 10; + double minAlt; // minimum altitude between now and now+dt seconds double t = Planetarium.GetUniversalTime(); - double PeT = Orbit.NextPeriapsisTime(t) - t; - if (PeT > 0 && PeT < dt) - min_alt = Orbit.PeA; + double peT = Orbit.NextPeriapsisTime(t) - t; + if (peT > 0 && peT < DT) + minAlt = Orbit.PeA; else - min_alt = Math.Sqrt(Math.Min(Orbit.getRelativePositionAtUT(t).sqrMagnitude, Orbit.getRelativePositionAtUT(t + dt).sqrMagnitude)) - - MainBody.Radius; + minAlt = Math.Sqrt(Math.Min(Orbit.getRelativePositionAtUT(t).sqrMagnitude, Orbit.getRelativePositionAtUT(t + DT).sqrMagnitude)) - + MainBody.Radius; - if (min_alt > MainBody.RealMaxAtmosphereAltitude()) - return true; - - return false; + return minAlt > MainBody.RealMaxAtmosphereAltitude(); } public override void OnFixedUpdate() { // Let the ascent guidance handle the solar panels to retract them before launch - if (autoDeploy && !Core.Ascent.Enabled) + if (AutoDeploy && !Core.Ascent.Enabled) { bool tmp = ShouldDeploy(); - if (tmp && (!prev_shouldDeploy || autoDeploy != prev_autoDeploy)) - ExtendAll(); - else if (!tmp && (prev_shouldDeploy || autoDeploy != prev_autoDeploy)) - RetractAll(); - - prev_shouldDeploy = tmp; - prev_autoDeploy = true; + switch (tmp) + { + case true when !PrevShouldDeploy || AutoDeploy != PrevAutoDeploy: + ExtendAll(); + break; + case false when PrevShouldDeploy || AutoDeploy != PrevAutoDeploy: + RetractAll(); + break; + } + + PrevShouldDeploy = tmp; + PrevAutoDeploy = true; } else { - prev_autoDeploy = false; + PrevAutoDeploy = false; } bool extendedThisPass = !AllRetracted(); - if (extended != extendedThisPass) - buttonText = getButtonText(extendedThisPass ? DeployablePartState.EXTENDED : DeployablePartState.RETRACTED); + if (Extended != extendedThisPass) + ButtonText = GetButtonText(extendedThisPass ? DeployablePartState.EXTENDED : DeployablePartState.RETRACTED); - extended = extendedThisPass; + Extended = extendedThisPass; } protected bool ExtendingOrRetracting() { - foreach (ModuleDeployablePart mdp in cachedPartModules) - if (mdp != null && isDeployable(mdp) - && (mdp.deployState == ModuleDeployablePart.DeployState.EXTENDING || - mdp.deployState == ModuleDeployablePart.DeployState.RETRACTING)) + foreach (ModuleDeployablePart mdp in CachedPartModules) + if (mdp != null && IsDeployable(mdp) + && (mdp.deployState == ModuleDeployablePart.DeployState.EXTENDING || + mdp.deployState == ModuleDeployablePart.DeployState.RETRACTING)) return true; return false; } - protected abstract bool isModules(ModuleDeployablePart p); + protected abstract bool IsModules(ModuleDeployablePart p); protected enum DeployablePartState { @@ -135,7 +139,7 @@ protected enum DeployablePartState EXTENDED } - protected abstract string getButtonText(DeployablePartState deployablePartState); + protected abstract string GetButtonText(DeployablePartState deployablePartState); public override void OnStart(PartModule.StartState state) { diff --git a/MechJeb2/MechJebModuleSolarPanelController.cs b/MechJeb2/MechJebModuleSolarPanelController.cs index 9e44e7a69..989db2fe4 100644 --- a/MechJeb2/MechJebModuleSolarPanelController.cs +++ b/MechJeb2/MechJebModuleSolarPanelController.cs @@ -15,33 +15,29 @@ public MechJebModuleSolarPanelController(MechJebCore core) [GeneralInfoItem("#MechJeb_ToggleSolarPanels", InfoItem.Category.Misc, showInEditor = false)] //Toggle solar panels public void SolarPanelDeployButton() { - autoDeploy = GUILayout.Toggle(autoDeploy, Localizer.Format("#MechJeb_SolarPanelDeployButton")); //"Auto-deploy solar panels" + AutoDeploy = GUILayout.Toggle(AutoDeploy, Localizer.Format("#MechJeb_SolarPanelDeployButton")); //"Auto-deploy solar panels" - if (GUILayout.Button(buttonText)) - { - if (ExtendingOrRetracting()) - return; + if (!GUILayout.Button(ButtonText)) return; + + if (ExtendingOrRetracting()) + return; - if (!extended) - ExtendAll(); - else - RetractAll(); - } + if (!Extended) + ExtendAll(); + else + RetractAll(); } - protected override bool isModules(ModuleDeployablePart p) => p is ModuleDeployableSolarPanel; + protected override bool IsModules(ModuleDeployablePart p) => p is ModuleDeployableSolarPanel; - protected override string getButtonText(DeployablePartState deployablePartState) + protected override string GetButtonText(DeployablePartState deployablePartState) { - switch (deployablePartState) + return deployablePartState switch { - case DeployablePartState.EXTENDED: - return Localizer.Format("#MechJeb_SolarPanelDeploy"); //"Toggle solar panels (currently extended)" - case DeployablePartState.RETRACTED: - return Localizer.Format("#MechJeb_SolarPanelRetracted"); //"Toggle solar panels (currently retracted)" - default: - return Localizer.Format("#MechJeb_SolarPanelToggle"); //"Toggle solar panels" - } + DeployablePartState.EXTENDED => Localizer.Format("#MechJeb_SolarPanelDeploy"), //"Toggle solar panels (currently extended)" + DeployablePartState.RETRACTED => Localizer.Format("#MechJeb_SolarPanelRetracted"), //"Toggle solar panels (currently retracted)" + _ => Localizer.Format("#MechJeb_SolarPanelToggle") + }; } } } diff --git a/MechJeb2/MechJebModuleThrustController.cs b/MechJeb2/MechJebModuleThrustController.cs index 22884e965..0466a995b 100644 --- a/MechJeb2/MechJebModuleThrustController.cs +++ b/MechJeb2/MechJebModuleThrustController.cs @@ -253,6 +253,10 @@ public override void OnStart(PartModule.StartState state) new ScreenMessage(Localizer.Format("#MechJeb_Ascent_srcmsg1"), 2f, ScreenMessageStyle.UPPER_CENTER); //"[MechJeb]: Killing throttle to prevent unstable ignition" _pid = new PIDController(0.05, 0.000001, 0.05); + // Permanent self-pin: keep this module always Enabled so its throttle limiters and + // auto-RCS-ullaging run every tick as an always-on safety service, even when no autopilot + // is driving the throttle (e.g. the player is flying manually). Active throttle control is + // gated separately on there being a *second* user (see Users.Count > 1 in Drive()). Users.Add(this); base.OnStart(state); @@ -435,8 +439,9 @@ public override void Drive(FlightCtrlState s) } } - // Only set throttle if a module need it. Otherwise, let the user or other mods set it - // There is always at least 1 user : the module itself (why ?) + // Only command the throttle if an actual consumer is engaged; otherwise leave it to the + // player or other mods. The module self-pins one permanent user in OnStart (to stay always + // Enabled for the limiter/ullage service), so "a consumer is driving" means Count > 1, not > 0. if (Users.Count > 1) s.mainThrottle = TargetThrottle; diff --git a/MechJebKos/Addon.cs b/MechJebKos/Addon.cs index 9f74b3111..563a0d520 100644 --- a/MechJebKos/Addon.cs +++ b/MechJebKos/Addon.cs @@ -35,6 +35,8 @@ private void InitializeSuffixes() var nodeExecutor = new NodeExecutorBinding(() => _core); var stagingController = new StagingControllerBinding(() => _core); var thrustController = new ThrustControllerBinding(() => _core); + var antennaController = new AntennaControllerBinding(() => _core); + var solarPanelController = new SolarPanelControllerBinding(() => _core); AddSuffix("RUNNING", new NoArgsSuffix(() => _core?.running ?? false, "True if MechJeb is present and running on this vessel.")); @@ -44,6 +46,10 @@ private void InitializeSuffixes() "The autostaging controller.")); AddSuffix("THRUSTCONTROLLER", new NoArgsSuffix(() => thrustController, "The thrust controller (throttle limiters).")); + AddSuffix("ANTENNACONTROLLER", new NoArgsSuffix(() => antennaController, + "The deployable antenna controller.")); + AddSuffix("SOLARPANELCONTROLLER", new NoArgsSuffix(() => solarPanelController, + "The solar panel controller.")); } } } diff --git a/MechJebKos/AntennaControllerBinding.cs b/MechJebKos/AntennaControllerBinding.cs new file mode 100644 index 000000000..b5f8024d3 --- /dev/null +++ b/MechJebKos/AntennaControllerBinding.cs @@ -0,0 +1,17 @@ +/* + * Copyright Lamont Granquist, Sebastien Gaggini and the MechJeb contributors + * SPDX-License-Identifier: LicenseRef-PD-hp OR Unlicense OR CC0-1.0 OR 0BSD OR MIT-0 OR MIT OR LGPL-2.1+ + */ + +using System; +using kOS.Safe.Utilities; + +namespace MuMech.MechJebKos +{ + // ADDONS:MECHJEB:ANTENNACONTROLLER - drives MechJebModuleDeployableAntennaController. + [KOSNomenclature("MechJebAntennaController")] + public class AntennaControllerBinding : DeployableControllerBinding + { + public AntennaControllerBinding(Func core) : base(core) { } + } +} diff --git a/MechJebKos/DeployableControllerBinding.cs b/MechJebKos/DeployableControllerBinding.cs new file mode 100644 index 000000000..e69479caa --- /dev/null +++ b/MechJebKos/DeployableControllerBinding.cs @@ -0,0 +1,32 @@ +/* + * Copyright Lamont Granquist, Sebastien Gaggini and the MechJeb contributors + * SPDX-License-Identifier: LicenseRef-PD-hp OR Unlicense OR CC0-1.0 OR 0BSD OR MIT-0 OR MIT OR LGPL-2.1+ + */ + +using System; +using kOS.Safe.Encapsulation; +using kOS.Safe.Encapsulation.Suffixes; +using kOS.Safe.Utilities; + +namespace MuMech.MechJebKos +{ + // Shared binding for MechJebModuleDeployableController subclasses (antennas, solar panels). + // These are always-on services gated by AUTODEPLOY, so there is deliberately no ENABLED suffix. + [KOSNomenclature("MechJebDeployableController")] + public abstract class DeployableControllerBinding : ComputerModuleBinding where T : MechJebModuleDeployableController + { + protected DeployableControllerBinding(Func core) : base(core) { } + + protected override void InitializeSuffixes() + { + AddSuffix("AUTODEPLOY", new SetSuffix(() => Module.AutoDeploy, value => Module.AutoDeploy = value, + "Automatically extend/retract the controlled parts based on flight conditions.")); + AddSuffix("EXTENDED", new Suffix(() => !Module.AllRetracted(), + "True if any controlled part is extended or extending.")); + AddSuffix("EXTEND", new NoArgsVoidSuffix(() => Module.ExtendAll(), + "Extend all controlled parts.")); + AddSuffix("RETRACT", new NoArgsVoidSuffix(() => Module.RetractAll(), + "Retract all controlled parts.")); + } + } +} diff --git a/MechJebKos/SolarPanelControllerBinding.cs b/MechJebKos/SolarPanelControllerBinding.cs new file mode 100644 index 000000000..1ecf0156c --- /dev/null +++ b/MechJebKos/SolarPanelControllerBinding.cs @@ -0,0 +1,17 @@ +/* + * Copyright Lamont Granquist, Sebastien Gaggini and the MechJeb contributors + * SPDX-License-Identifier: LicenseRef-PD-hp OR Unlicense OR CC0-1.0 OR 0BSD OR MIT-0 OR MIT OR LGPL-2.1+ + */ + +using System; +using kOS.Safe.Utilities; + +namespace MuMech.MechJebKos +{ + // ADDONS:MECHJEB:SOLARPANELCONTROLLER - drives MechJebModuleSolarPanelController. + [KOSNomenclature("MechJebSolarPanelController")] + public class SolarPanelControllerBinding : DeployableControllerBinding + { + public SolarPanelControllerBinding(Func core) : base(core) { } + } +}