Skip to content

Instantly share code, notes, and snippets.

@lamont-granquist
Created February 6, 2022 04:19
Show Gist options
  • Save lamont-granquist/78bf88d0038375fd2771739a02ede2b9 to your computer and use it in GitHub Desktop.
Save lamont-granquist/78bf88d0038375fd2771739a02ede2b9 to your computer and use it in GitHub Desktop.
using Kode.Interfaces;
using Kode.Interfaces.PartModules;
using static Kode.Logging;
namespace Kode.AutopilotModules
{
// TODO: hotstaging [requires deltaV analysis]
// TODO: early solid jettison [requires deltaV analysis]
// TODO: prevent prelaunch activation? [requires events from KSP]
// FIXME: this needs a rename to not collide with the KSP class
public class StagingManager : BaseAutopilot
{
private double _lastStagingTime;
private double _startingStagingTime;
private State _state = State.Running;
public StagingManager(IVessel vessel) : base(vessel)
{
}
public double ClampThrustPct { get; set; } = 0.99;
public bool AggressiveStaging { get; set; } = true;
public double FairingMaxDynamicPressure { get; set; } = 5000; // Pa
public double FairingMinAltitude { get; set; } = 50000; // m
public double FairingMaxAerothermalFlux { get; set; } = 1135.0; // W/m^2
public double PreStagingDelay { get; set; } = 0.5; // wait before staging
public double PostStagingDelay { get; set; } = 1.0; // wait after last staging
protected override void OnDrive()
{
switch (_state)
{
case State.Running:
StateRunning();
break;
case State.Staging:
StateStaging();
break;
}
}
private void StateRunning()
{
// TODO: need KSP event for staging so that we wait until the user manually stages
//if (Vessel.Situation == IVesselSituations.PRELAUNCH)
// return;
if (Vessel.CurrentStage < 0)
return;
if (WouldDecoupleActiveEngine()) return;
if (WouldDecoupleDrainingTank()) return;
if (WouldDropUnfiredParachute()) return;
if (ShouldDecoupleFairing())
{
Log("ShouldDecoupleFairing");
TransitionDoStage();
return;
}
if (ShouldDecoupleDeactivatedEngine())
{
Log("ShouldDecoupleDeactivatedEngine");
TransitionRequestStage();
return;
}
if (ShouldDecoupleDrainedTank())
{
Log("ShouldDecoupleDrainedTank");
TransitionRequestStage();
return;
}
if (ShouldReleaseLaunchClamps())
{
Log("ShouldReleaseLaunchClamps");
TransitionDoStage();
return;
}
if (NoActiveEngines() && AggressiveStaging)
{
Log("NoActiveEngines");
TransitionRequestStage();
}
}
private void TransitionRequestStage()
{
_state = State.Staging;
_startingStagingTime = Vessel.Time;
}
private void StateStaging()
{
if (Vessel.Time > _startingStagingTime + PreStagingDelay &&
Vessel.Time > _lastStagingTime + PostStagingDelay)
TransitionDoStage();
}
private void TransitionDoStage()
{
Stage();
_state = State.Running;
_lastStagingTime = Vessel.Time;
}
private void Stage()
{
Vessel.Control.Stage();
}
private bool WouldDecoupleActiveEngine()
{
foreach (IEngine e in Vessel.Parts.GetModulesDroppedInStage<IEngine>(Vessel.CurrentStage - 1))
{
if (e.IsSepratron || !e.IsEnabled)
continue;
if ((e.State == IPartStates.ACTIVE || e.State == IPartStates.IDLE) && !e.FlameoutState) return true;
}
return false;
}
private bool NoActiveEngines()
{
foreach (IEngine e in Vessel.Parts.GetModules<IEngine>())
{
if (e.IsSepratron || !e.IsEnabled)
continue;
if (e.IgnitionState && !e.FlameoutState) return false;
}
return true;
}
private bool WouldDecoupleDrainingTank()
{
foreach (IPart p in Vessel.Parts.GetPartsDroppedInStage(Vessel.CurrentStage - 1))
{
if (IsSepratron(p))
continue;
for (int j = 0; j < p.Resources.Count; j++)
{
IResource r = p.Resources[j];
if (r.Electricity)
continue;
if (p.InActiveEngineCrossfeedSet && r.Amount > p.ResourceRequestRemainingThreshold) return true;
}
}
return false;
}
// FIXME: move to IPart base class
private bool IsSepratron(IPart p)
{
foreach (IEngine e in p.Modules.GetModules<IEngine>())
if (e.IsSepratron)
return true;
return false;
}
// FIXME: this is awfully similar to WouldDecompleDrainingTank
private bool ShouldDecoupleDrainedTank()
{
bool hadResources = false;
foreach (IPart p in Vessel.Parts.GetPartsDroppedInStage(Vessel.CurrentStage - 1))
{
if (IsSepratron(p))
continue;
for (int j = 0; j < p.Resources.Count; j++)
{
IResource r = p.Resources[j];
if (r.Electricity)
continue;
if (r.MaxAmount > p.ResourceRequestRemainingThreshold) hadResources = true;
if (r.Amount > p.ResourceRequestRemainingThreshold) return false;
}
}
return hadResources;
}
private bool ShouldDecoupleFairing()
{
if (Vessel.DynamicPressure > FairingMaxDynamicPressure) return false;
if (Vessel.Altitude < FairingMinAltitude) return false;
if (Vessel.AerothermalFlux > FairingMaxAerothermalFlux) return false;
// stock ModuleProceduralFairing
foreach (IProceduralFairing f in Vessel.Parts.GetModulesInStage<IProceduralFairing>(Vessel.CurrentStage - 1)) return true;
// ProceduralFairingDecoupler from Proc Fairings
foreach (IProceduralFairingDecoupler f in Vessel.Parts.GetModulesInStage<IProceduralFairingDecoupler>(Vessel.CurrentStage - 1)
) return true;
// FIXME: RSB fairings (all they seem to have is a ModuleDecouple so I guess would need to see if the part name matched?
return false;
}
private bool ShouldDecoupleDeactivatedEngine()
{
foreach (IEngine e in Vessel.Parts.GetModulesDroppedInStage<IEngine>(Vessel.CurrentStage - 1))
{
if (e.State == IPartStates.DEACTIVATED && !e.IsSepratron)
return true;
if (e.FlameoutState)
return true;
}
return false;
}
private bool WouldDropUnfiredParachute()
{
foreach (IParachute p in Vessel.Parts.GetModulesDroppedInStage<IParachute>(Vessel.CurrentStage - 1))
if (p.InverseStage != p.DecoupledInStage)
return true;
return false;
}
private bool ShouldReleaseLaunchClamps()
{
if (Vessel.ThrustCurrent.magnitude == 0) return false;
if (Vessel.ThrustCurrent.magnitude / Vessel.ThrustMax.magnitude < ClampThrustPct) return false;
foreach (ILaunchClamp c in Vessel.Parts.GetModulesDroppedInStage<ILaunchClamp>(Vessel.CurrentStage - 1)) return true;
return false;
}
private enum State
{
Running,
Staging
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment