Skip to content

Commit

Permalink
Fix Fairing Separation logic
Browse files Browse the repository at this point in the history
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 <lamont@scriptkiddie.org>
  • Loading branch information
lamont-granquist committed Jun 8, 2023
1 parent ad8f394 commit 4392aaa
Show file tree
Hide file tree
Showing 3 changed files with 69 additions and 9 deletions.
2 changes: 1 addition & 1 deletion MechJeb2/MechJebLib/Core/Maths.cs
Expand Up @@ -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)
Expand Down
48 changes: 40 additions & 8 deletions MechJeb2/MechJebModuleStagingController.cs
Expand Up @@ -3,6 +3,7 @@
using JetBrains.Annotations;
using KSP.UI.Screens;
using Smooth.Slinq;
using Smooth.Slinq.Context;
using UnityEngine;

namespace MuMech
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -506,7 +508,7 @@ private bool HasActiveOrIdleEngineOrTankDescendant(Part p, List<int> 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)
Expand Down Expand Up @@ -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<ModuleProceduralFairing>() || (VesselState.isLoadedProceduralFairing &&
p.Modules.Contains("ProceduralFairingDecoupler")))) != null;
result = HasFairingUncached(inverseStage);
hasFairingCache.Add(inverseStage, result);
return result;
}

private readonly List<Part> _partsInStage = new List<Part>();

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);
}
}
}
28 changes: 28 additions & 0 deletions MechJeb2/PartExtensions.cs
Expand Up @@ -231,6 +231,34 @@ public static bool IsUnfiredDecoupler(this PartModule m, out Part decoupledPart)
return false;
}

/// <summary>
/// Determines if a given part is a ProceduralFairingDecoupler
/// </summary>
/// <param name="p">the part to check</param>
/// <returns>if the part is a procfairing payload decoupler</returns>
public static bool IsProceduralFairing(this Part p)
{
if (!VesselState.isLoadedProceduralFairing) return false;
return p.Modules.Contains("ProceduralFairingDecoupler");
}

/// <summary>
/// Determines if a given part is a ProceduralFairingDecoupler which is attached to a payload ProceduralFairingBase
/// </summary>
/// <param name="p">the part to check</param>
/// <returns>if the part is a procfairing payload decoupler</returns>
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<string>(fairingbase) == "Payload";
}

public static bool IsUnfiredDecoupler(this Part p, out Part decoupledPart)
{
foreach (PartModule m in p.Modules)
Expand Down

0 comments on commit 4392aaa

Please sign in to comment.