From 4392aaa87df3ec6592d34c308bb1e5df3900d4df Mon Sep 17 00:00:00 2001 From: Lamont Granquist Date: Thu, 8 Jun 2023 12:22:42 -0700 Subject: [PATCH] Fix Fairing Separation logic Fairing stages now must be: - if a PF fairing is in the stage, all the parts in the stage must be PF payload fairings. - if a stage has only decopulers, without children, that aren't launch clamps then it is considered a fairing stage. this fixes the bug where any PF fairing in a stage would turn it into a fairing stage. this also fixes things like RSB fairing decouplers, which are just normal decouplers and it is hard to identify them uniquely. this may identify non-PF interstage fairings that are in a stage by themselves as payload fairings. the workaround to that is to add the stack decoupler to that stage and stage them together (or an engine, or any other part which will prevent them being identified as a payload fairing). Signed-off-by: Lamont Granquist --- MechJeb2/MechJebLib/Core/Maths.cs | 2 +- MechJeb2/MechJebModuleStagingController.cs | 48 ++++++++++++++++++---- MechJeb2/PartExtensions.cs | 28 +++++++++++++ 3 files changed, 69 insertions(+), 9 deletions(-) diff --git a/MechJeb2/MechJebLib/Core/Maths.cs b/MechJeb2/MechJebLib/Core/Maths.cs index 5d51c7b0..54bba4cc 100644 --- a/MechJeb2/MechJebLib/Core/Maths.cs +++ b/MechJeb2/MechJebLib/Core/Maths.cs @@ -297,7 +297,7 @@ private static double AngleForInclination(double inc, double lat) public static double LatitudeFromBCI(V3 r) { - return Math.Asin(Clamp(r.z / r.magnitude, -1, 1)); + return SafeAsin(r.z / r.magnitude); } public static double LongitudeFromBCI(V3 r) diff --git a/MechJeb2/MechJebModuleStagingController.cs b/MechJeb2/MechJebModuleStagingController.cs index 764f3950..cb79a796 100644 --- a/MechJeb2/MechJebModuleStagingController.cs +++ b/MechJeb2/MechJebModuleStagingController.cs @@ -3,6 +3,7 @@ using JetBrains.Annotations; using KSP.UI.Screens; using Smooth.Slinq; +using Smooth.Slinq.Context; using UnityEngine; namespace MuMech @@ -32,7 +33,8 @@ public MechJebModuleStagingController(MechJebCore core) [Persistent(pass = (int)(Pass.Type | Pass.Global))] public EditableDoubleMult fairingMinAltitude = new EditableDoubleMult(50000, 1000); - [Persistent(pass = (int)Pass.Type)] public EditableDouble clampAutoStageThrustPct = 0.99; + [Persistent(pass = (int)Pass.Type)] + public EditableDouble clampAutoStageThrustPct = 0.99; [Persistent(pass = (int)(Pass.Type | Pass.Global))] public EditableDoubleMult fairingMaxAerothermalFlux = new EditableDoubleMult(1135); @@ -201,7 +203,6 @@ public void AutostageSettingsInfoItem() GuiUtils.SimpleTextBox(sFairingMinAltitude, fairingMinAltitude, "km", 50); //altitude GuiUtils.SimpleTextBox(sFairingMaxAerothermalFlux, fairingMaxAerothermalFlux, "W/m²", 50); //aerothermal flux - GUILayout.BeginHorizontal(); hotStaging = GUILayout.Toggle(hotStaging, CachedLocalizer.Instance.MechJeb_Ascent_hotStaging); //"Support hotstaging" GUILayout.FlexibleSpace(); @@ -260,7 +261,8 @@ public override void OnUpdate() // if autostage enabled, and if we've already staged at least once, and if there are stages left, // and if we are allowed to continue staging, and if we didn't just fire the previous stage - if (waitingForFirstStaging || vessel.currentStage <= 0 || vessel.currentStage <= autostageLimit || vesselState.time - lastStageTime < autostagePostDelay) + if (waitingForFirstStaging || vessel.currentStage <= 0 || vessel.currentStage <= autostageLimit || + vesselState.time - lastStageTime < autostagePostDelay) { return; } @@ -506,7 +508,7 @@ private bool HasActiveOrIdleEngineOrTankDescendant(Part p, List tankResourc continue; if (r.info.id == PartResourceLibrary.ElectricityHashcode) continue; - if(!tankResources.Contains(r.info.id)) + if (!tankResources.Contains(r.info.id)) continue; if (propellant.GetFlowMode() == ResourceFlowMode.NO_FLOW) @@ -612,15 +614,45 @@ public bool HasStayingChutes(int inverseStage) } // determine if there is a fairing to be deployed - public bool HasFairing(int inverseStage) + private bool HasFairing(int inverseStage) { if (hasFairingCache.TryGetValue(inverseStage, out bool result)) return result; - result = vessel.Parts.FirstOrDefault(p => p.inverseStage == inverseStage - && (p.HasModule() || (VesselState.isLoadedProceduralFairing && - p.Modules.Contains("ProceduralFairingDecoupler")))) != null; + result = HasFairingUncached(inverseStage); hasFairingCache.Add(inverseStage, result); return result; } + + private readonly List _partsInStage = new List(); + + private bool HasFairingUncached(int inverseStage) + { + _partsInStage.Clear(); + vessel.parts.Slinq().Where((p, s) => p.inverseStage == s, inverseStage).AddTo(_partsInStage); + + // proc parts are reasonably easy, but all the parts in the stage must be payload fairings for them to + // be treated as payload fairings here. a payload fairing and a stack decoupler will bypass the fairing + // checks which will then cause it to be detatched normally when the stack decouples, fixing the issue where + // fairings block stack separation. + + // ReSharper disable once SuggestVarOrType_Elsewhere + if (_partsInStage.Slinq().Any(p => p.IsProceduralFairing())) + // if we have any PF in the stage we must have ALL payload fairings, and we do not do the + // decoupler check below + return _partsInStage.Slinq().All(p => p.IsProceduralFairingPayloadFairing()); + + // this is simple, but subtle: + // 1. we do not identify fairings as separate from decouplers here because of part mods like RSB + // which only put a stock decoupler in the staging. + // 2. if we see ONLY decouplers with no child parts and no other parts we assume payload fairing + // 3. an interstage fairing mixed with a stack decoupler will not be identified as a fairing. + // 4. a stack decoupler alone in the stage (like RO hotstaging) will not be identified as a fairing + // (stack decouplers have children). + // 5. a payload fairing placed in a stage with a stack decoupler will also not be identified as a + // payload fairing now (fixing payload fairings causing stacks to not decouple). + // if a user requires an interstage fairing that is alone in a stage with no stack decoupler, engine, or + // anything else in the stage (for cinematics?) then the user MUST use proc fairings. + return _partsInStage.Slinq().All(p => p.IsDecoupler() && !p.IsLaunchClamp() && p.children.Count == 0); + } } } diff --git a/MechJeb2/PartExtensions.cs b/MechJeb2/PartExtensions.cs index 72ab9812..ce32353f 100644 --- a/MechJeb2/PartExtensions.cs +++ b/MechJeb2/PartExtensions.cs @@ -231,6 +231,34 @@ public static bool IsUnfiredDecoupler(this PartModule m, out Part decoupledPart) return false; } + /// + /// Determines if a given part is a ProceduralFairingDecoupler + /// + /// the part to check + /// if the part is a procfairing payload decoupler + public static bool IsProceduralFairing(this Part p) + { + if (!VesselState.isLoadedProceduralFairing) return false; + return p.Modules.Contains("ProceduralFairingDecoupler"); + } + + /// + /// Determines if a given part is a ProceduralFairingDecoupler which is attached to a payload ProceduralFairingBase + /// + /// the part to check + /// if the part is a procfairing payload decoupler + public static bool IsProceduralFairingPayloadFairing(this Part p) + { + if (!p.IsProceduralFairing()) return false; + Part basepart = p.parent; + if (basepart is null) + throw new Exception("ProceduralFairingDecoupler parent is null--fix your root staging?"); + PartModule fairingbase = basepart.Modules.GetModule("ProceduralFairingBase"); + if (fairingbase is null) + throw new Exception("ProceduralFairingBase not found in parent part, weird."); + return fairingbase.Fields["mode"].GetValue(fairingbase) == "Payload"; + } + public static bool IsUnfiredDecoupler(this Part p, out Part decoupledPart) { foreach (PartModule m in p.Modules)