Updated simulation logic.
Updated simulation logic.

// //
// Kerbal Engineer Redux // Kerbal Engineer Redux
// //
// Copyright (C) 2014 CYBUTEK // Copyright (C) 2014 CYBUTEK
// //
// This program is free software: you can redistribute it and/or modify // This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by // it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or // the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version. // (at your option) any later version.
// //
// This program is distributed in the hope that it will be useful, // This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details. // GNU General Public License for more details.
// //
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>. // along with this program. If not, see <http://www.gnu.org/licenses/>.
// //
#region Using Directives #region Using Directives
using System; using System;
using System.Linq; using System.Linq;
using KerbalEngineer.Extensions; using KerbalEngineer.Extensions;
using KerbalEngineer.Settings; using KerbalEngineer.Settings;
using KerbalEngineer.Simulation; using KerbalEngineer.VesselSimulator;
using UnityEngine; using UnityEngine;
#endregion #endregion
namespace KerbalEngineer.Editor namespace KerbalEngineer.Editor
{ {
[KSPAddon(KSPAddon.Startup.EditorAny, false)] [KSPAddon(KSPAddon.Startup.EditorAny, false)]
public class BuildAdvanced : MonoBehaviour public class BuildAdvanced : MonoBehaviour
{ {
#region Instance #region Instance
/// <summary> /// <summary>
/// Gets the current instance if started or returns null. /// Gets the current instance if started or returns null.
/// </summary> /// </summary>
public static BuildAdvanced Instance { get; private set; } public static BuildAdvanced Instance { get; private set; }
#endregion #endregion
#region Fields #region Fields
private readonly int windowId = new Guid().GetHashCode(); private readonly int windowId = new Guid().GetHashCode();
private bool hasChanged; private bool hasChanged;
private bool isEditorLocked; private bool isEditorLocked;
private int numberOfStages; private int numberOfStages;
private Rect windowPosition = new Rect(265.0f, 45.0f, 0, 0); private Rect windowPosition = new Rect(265.0f, 45.0f, 0, 0);
#region Styles #region Styles
private GUIStyle areaBodiesStyle; private GUIStyle areaBodiesStyle;
private GUIStyle areaStyle; private GUIStyle areaStyle;
private GUIStyle buttonStyle; private GUIStyle buttonStyle;
private GUIStyle infoStyle; private GUIStyle infoStyle;
private GUIStyle titleStyle; private GUIStyle titleStyle;
private GUIStyle windowStyle; private GUIStyle windowStyle;
#endregion #endregion
#endregion #endregion
#region Properties #region Properties
private bool compactMode; private bool compactMode;
private bool showAllStages; private bool showAllStages;
private bool showReferenceBodies; private bool showReferenceBodies;
private bool useAtmosphericDetails; private bool useAtmosphericDetails;
private bool visible = true; private bool visible = true;
/// <summary> /// <summary>
/// Gets and sets whether the display is enabled. /// Gets and sets whether the display is enabled.
/// </summary> /// </summary>
public bool Visible public bool Visible
{ {
get { return this.visible; } get { return this.visible; }
set { this.visible = value; } set { this.visible = value; }
} }
/// <summary> /// <summary>
/// Gets and sets whether to show in compact mode. /// Gets and sets whether to show in compact mode.
/// </summary> /// </summary>
public bool CompactMode public bool CompactMode
{ {
get { return this.compactMode; } get { return this.compactMode; }
set { this.compactMode = value; } set { this.compactMode = value; }
} }
/// <summary> /// <summary>
/// Gets and sets whether to show all stages. /// Gets and sets whether to show all stages.
/// </summary> /// </summary>
public bool ShowAllStages public bool ShowAllStages
{ {
get { return this.showAllStages; } get { return this.showAllStages; }
set { this.showAllStages = value; } set { this.showAllStages = value; }
} }
/// <summary> /// <summary>
/// Gets and sets whether to use atmospheric details. /// Gets and sets whether to use atmospheric details.
/// </summary> /// </summary>
public bool UseAtmosphericDetails public bool UseAtmosphericDetails
{ {
get { return this.useAtmosphericDetails; } get { return this.useAtmosphericDetails; }
set { this.useAtmosphericDetails = value; } set { this.useAtmosphericDetails = value; }
} }
/// <summary> /// <summary>
/// Gets and sets whether to show the reference body selection. /// Gets and sets whether to show the reference body selection.
/// </summary> /// </summary>
public bool ShowReferenceBodies public bool ShowReferenceBodies
{ {
get { return this.showReferenceBodies; } get { return this.showReferenceBodies; }
set { this.showReferenceBodies = value; } set { this.showReferenceBodies = value; }
} }
#endregion #endregion
#region Initialisation #region Initialisation
private void Awake() private void Awake()
{ {
Instance = this; Instance = this;
this.Load(); this.Load();
} }
private void Start() private void Start()
{ {
this.InitialiseStyles(); this.InitialiseStyles();
RenderingManager.AddToPostDrawQueue(0, this.OnDraw); RenderingManager.AddToPostDrawQueue(0, this.OnDraw);
} }
/// <summary> /// <summary>
/// Initialises all the styles that are required. /// Initialises all the styles that are required.
/// </summary> /// </summary>
private void InitialiseStyles() private void InitialiseStyles()
{ {
this.areaBodiesStyle = new GUIStyle(HighLogic.Skin.box); this.areaBodiesStyle = new GUIStyle(HighLogic.Skin.box);
this.windowStyle = new GUIStyle(HighLogic.Skin.window) this.windowStyle = new GUIStyle(HighLogic.Skin.window)
{ {
alignment = TextAnchor.UpperLeft alignment = TextAnchor.UpperLeft
}; };
this.areaStyle = new GUIStyle(HighLogic.Skin.box) this.areaStyle = new GUIStyle(HighLogic.Skin.box)
{ {
padding = new RectOffset(0, 0, 9, 0) padding = new RectOffset(0, 0, 9, 0)
}; };
this.buttonStyle = new GUIStyle(HighLogic.Skin.button) this.buttonStyle = new GUIStyle(HighLogic.Skin.button)
{ {
normal = normal =
{ {
textColor = Color.white textColor = Color.white
}, },
fontSize = 11, fontSize = 11,
fontStyle = FontStyle.Bold, fontStyle = FontStyle.Bold,
alignment = TextAnchor.MiddleCenter alignment = TextAnchor.MiddleCenter
}; };
this.titleStyle = new GUIStyle(HighLogic.Skin.label) this.titleStyle = new GUIStyle(HighLogic.Skin.label)
{ {
normal = normal =
{ {
textColor = Color.white textColor = Color.white
}, },
fontSize = 11, fontSize = 11,
fontStyle = FontStyle.Bold, fontStyle = FontStyle.Bold,
alignment = TextAnchor.MiddleCenter, alignment = TextAnchor.MiddleCenter,
stretchWidth = true stretchWidth = true
}; };
this.infoStyle = new GUIStyle(HighLogic.Skin.label) this.infoStyle = new GUIStyle(HighLogic.Skin.label)
{ {
fontSize = 11, fontSize = 11,
fontStyle = FontStyle.Bold, fontStyle = FontStyle.Bold,
alignment = TextAnchor.MiddleCenter, alignment = TextAnchor.MiddleCenter,
stretchWidth = true stretchWidth = true
}; };
} }
#endregion #endregion
#region Update and Drawing #region Update and Drawing
private void Update() private void Update()
{ {
try try
{ {
if (!this.visible || EditorLogic.fetch == null || EditorLogic.fetch.ship.parts.Count == 0) if (!this.visible || EditorLogic.fetch == null || EditorLogic.fetch.ship.parts.Count == 0)
{ {
return; return;
} }
// Configure the simulation parameters based on the selected reference body. // Configure the simulation parameters based on the selected reference body.
SimManager.Gravity = CelestialBodies.Instance.SelectedBodyInfo.Gravity; SimManager.Gravity = CelestialBodies.Instance.SelectedBodyInfo.Gravity;
if (this.useAtmosphericDetails) if (this.useAtmosphericDetails)
{ {
SimManager.Atmosphere = CelestialBodies.Instance.SelectedBodyInfo.Atmosphere * 0.01d; SimManager.Atmosphere = CelestialBodies.Instance.SelectedBodyInfo.Atmosphere * 0.01d;
} }
else else
{ {
SimManager.Atmosphere = 0; SimManager.Atmosphere = 0;
} }
SimManager.TryStartSimulation(); SimManager.TryStartSimulation();
} }
catch (Exception ex) catch (Exception ex)
{ {
Logger.Log("BuildAdvanced->Update"); Logger.Log("BuildAdvanced->Update");
Logger.Exception(ex); Logger.Exception(ex);
} }
} }
private void OnDraw() private void OnDraw()
{ {
try try
{ {
if (!this.visible || EditorLogic.fetch == null || EditorLogic.fetch.ship.parts.Count == 0) if (!this.visible || EditorLogic.fetch == null || EditorLogic.fetch.ship.parts.Count == 0)
{ {
return; return;
} }
SimManager.RequestSimulation(); SimManager.RequestSimulation();
// Change the window title based on whether in compact mode or not. // Change the window title based on whether in compact mode or not.
var title = !this.compactMode ? "KERBAL ENGINEER REDUX " + EngineerGlobals.AssemblyVersion : "K.E.R. " + EngineerGlobals.AssemblyVersion; var title = !this.compactMode ? "KERBAL ENGINEER REDUX " + EngineerGlobals.AssemblyVersion : "K.E.R. " + EngineerGlobals.AssemblyVersion;
// Reset the window size when the staging or something else has changed. // Reset the window size when the staging or something else has changed.
var stageCount = SimManager.Stages != null ? SimManager.Stages.Count(stage => this.showAllStages || stage.deltaV > 0) : 0; var stageCount = SimManager.Stages != null ? SimManager.Stages.Count(stage => this.showAllStages || stage.deltaV > 0) : 0;
if (this.hasChanged || stageCount != this.numberOfStages) if (this.hasChanged || stageCount != this.numberOfStages)
{ {
this.hasChanged = false; this.hasChanged = false;
this.numberOfStages = stageCount; this.numberOfStages = stageCount;
this.windowPosition.width = 0; this.windowPosition.width = 0;
this.windowPosition.height = 0; this.windowPosition.height = 0;
} }
this.windowPosition = GUILayout.Window(this.windowId, this.windowPosition, this.Window, title, this.windowStyle).ClampToScreen(); this.windowPosition = GUILayout.Window(this.windowId, this.windowPosition, this.Window, title, this.windowStyle).ClampToScreen();
// Check editor lock to manage click-through. // Check editor lock to manage click-through.
this.CheckEditorLock(); this.CheckEditorLock();
} }
catch (Exception ex) catch (Exception ex)
{ {
Logger.Log("BuildAdvanced->OnDraw"); Logger.Log("BuildAdvanced->OnDraw");
Logger.Exception(ex); Logger.Exception(ex);
} }
} }
/// <summary> /// <summary>
/// Checks whether the editor should be locked to stop click-through. /// Checks whether the editor should be locked to stop click-through.
/// </summary> /// </summary>
private void CheckEditorLock() private void CheckEditorLock()
{ {
if (this.windowPosition.MouseIsOver()) if (this.windowPosition.MouseIsOver())
{ {
EditorLogic.fetch.State = EditorLogic.EditorState.GUI_SELECTED; EditorLogic.fetch.State = EditorLogic.EditorState.GUI_SELECTED;
this.isEditorLocked = true; this.isEditorLocked = true;
} }
else if (!this.windowPosition.MouseIsOver() && this.isEditorLocked) else if (!this.windowPosition.MouseIsOver() && this.isEditorLocked)
{ {
EditorLogic.fetch.State = EditorLogic.EditorState.PAD_UNSELECTED; EditorLogic.fetch.State = EditorLogic.EditorState.PAD_UNSELECTED;
this.isEditorLocked = false; this.isEditorLocked = false;
} }
} }
/// <summary> /// <summary>
/// Draws the OnGUI window. /// Draws the OnGUI window.
/// </summary> /// </summary>
private void Window(int windowId) private void Window(int windowId)
{ {
// Draw the compact mode toggle. // Draw the compact mode toggle.
if (GUI.Toggle(new Rect(this.windowPosition.width - 70.0f, 5.0f, 65.0f, 20.0f), this.compactMode, "COMPACT", this.buttonStyle) != this.compactMode) if (GUI.Toggle(new Rect(this.windowPosition.width - 70.0f, 5.0f, 65.0f, 20.0f), this.compactMode, "COMPACT", this.buttonStyle) != this.compactMode)
{ {
this.hasChanged = true; this.hasChanged = true;
this.compactMode = !this.compactMode; this.compactMode = !this.compactMode;
} }
// When not in compact mode draw the 'All Stages' and 'Atmospheric' toggles. // When not in compact mode draw the 'All Stages' and 'Atmospheric' toggles.
if (!this.compactMode) if (!this.compactMode)
{ {
if (GUI.Toggle(new Rect(this.windowPosition.width - 153.0f, 5.0f, 80.0f, 20.0f), this.showAllStages, "ALL STAGES", this.buttonStyle) != this.showAllStages) if (GUI.Toggle(new Rect(this.windowPosition.width - 153.0f, 5.0f, 80.0f, 20.0f), this.showAllStages, "ALL STAGES", this.buttonStyle) != this.showAllStages)
{ {
this.hasChanged = true; this.hasChanged = true;
this.showAllStages = !this.showAllStages; this.showAllStages = !this.showAllStages;
} }
this.useAtmosphericDetails = GUI.Toggle(new Rect(this.windowPosition.width - 251.0f, 5.0f, 95.0f, 20.0f), this.useAtmosphericDetails, "ATMOSPHERIC", this.buttonStyle); this.useAtmosphericDetails = GUI.Toggle(new Rect(this.windowPosition.width - 251.0f, 5.0f, 95.0f, 20.0f), this.useAtmosphericDetails, "ATMOSPHERIC", this.buttonStyle);
if (GUI.Toggle(new Rect(this.windowPosition.width - 379.0f, 5.0f, 125.0f, 20.0f), this.showReferenceBodies, "REFERENCE BODIES", this.buttonStyle) != this.showReferenceBodies) if (GUI.Toggle(new Rect(this.windowPosition.width - 379.0f, 5.0f, 125.0f, 20.0f), this.showReferenceBodies, "REFERENCE BODIES", this.buttonStyle) != this.showReferenceBodies)
{ {
this.hasChanged = true; this.hasChanged = true;
this.showReferenceBodies = !this.showReferenceBodies; this.showReferenceBodies = !this.showReferenceBodies;
} }
} }
// Draw the main informational display box. // Draw the main informational display box.
if (!this.compactMode) if (!this.compactMode)
{ {
GUILayout.BeginHorizontal(this.areaStyle); GUILayout.BeginHorizontal(this.areaStyle);
this.DrawStageNumbers(); this.DrawStageNumbers();
//this.DrawPartCount(); //this.DrawPartCount();
this.DrawCost(); this.DrawCost();
this.DrawMass(); this.DrawMass();
this.DrawIsp(); this.DrawIsp();
this.DrawThrust(); this.DrawThrust();
this.DrawTwr(); this.DrawTwr();
this.DrawDeltaV(); this.DrawDeltaV();
this.DrawBurnTime(); this.DrawBurnTime();
GUILayout.EndHorizontal(); GUILayout.EndHorizontal();
if (this.showReferenceBodies) if (this.showReferenceBodies)
{ {
GUILayout.BeginVertical(this.areaBodiesStyle); GUILayout.BeginVertical(this.areaBodiesStyle);
this.DrawReferenceBodies(); this.DrawReferenceBodies();
GUILayout.EndVertical(); GUILayout.EndVertical();
} }
} }
else // Draw only a few details when in compact mode. else // Draw only a few details when in compact mode.
{ {
GUILayout.BeginHorizontal(this.areaStyle); GUILayout.BeginHorizontal(this.areaStyle);
this.DrawStageNumbers(); this.DrawStageNumbers();
this.DrawTwr(); this.DrawTwr();
this.DrawDeltaV(); this.DrawDeltaV();
GUILayout.EndHorizontal(); GUILayout.EndHorizontal();
} }
GUI.DragWindow(); GUI.DragWindow();
} }
/// <summary> /// <summary>
/// Draws all the reference bodies. /// Draws all the reference bodies.
/// </summary> /// </summary>
private void DrawReferenceBodies() private void DrawReferenceBodies()
{ {
var index = 0; var index = 0;
foreach (var bodyName in CelestialBodies.Instance.BodyList.Keys) foreach (var bodyName in CelestialBodies.Instance.BodyList.Keys)
{ {
if (index % 8 == 0) if (index % 8 == 0)
{ {
if (index > 0) if (index > 0)
{ {
GUILayout.EndHorizontal(); GUILayout.EndHorizontal();
} }
GUILayout.BeginHorizontal(); GUILayout.BeginHorizontal();
} }
if (GUILayout.Toggle(CelestialBodies.Instance.SelectedBodyName == bodyName, bodyName, this.buttonStyle)) if (GUILayout.Toggle(CelestialBodies.Instance.SelectedBodyName == bodyName, bodyName, this.buttonStyle))
{ {
CelestialBodies.Instance.SelectedBodyName = bodyName; CelestialBodies.Instance.SelectedBodyName = bodyName;
} }
index++; index++;
} }
GUILayout.EndHorizontal(); GUILayout.EndHorizontal();
} }
/// <summary> /// <summary>
/// Draws the stage number column. /// Draws the stage number column.
/// </summary> /// </summary>
private void DrawStageNumbers() private void DrawStageNumbers()
{ {
GUILayout.BeginVertical(GUILayout.Width(30.0f)); GUILayout.BeginVertical(GUILayout.Width(30.0f));
GUILayout.Label(string.Empty, this.titleStyle); GUILayout.Label(string.Empty, this.titleStyle);
foreach (var stage in SimManager.Stages) foreach (var stage in SimManager.Stages)
{ {
if (this.showAllStages || stage.deltaV > 0) if (this.showAllStages || stage.deltaV > 0)
{ {
GUILayout.Label("S" + stage.number, this.titleStyle); GUILayout.Label("S" + stage.number, this.titleStyle);
} }
} }
GUILayout.EndVertical(); GUILayout.EndVertical();
} }
/// <summary> /// <summary>
/// Draws the part count column. /// Draws the part count column.
/// </summary> /// </summary>
private void DrawPartCount() private void DrawPartCount()
{ {
GUILayout.BeginVertical(GUILayout.Width(50.0f)); GUILayout.BeginVertical(GUILayout.Width(50.0f));
GUILayout.Label("PARTS", this.titleStyle); GUILayout.Label("PARTS", this.titleStyle);
foreach (var stage in SimManager.Stages) foreach (var stage in SimManager.Stages)
{ {
if (this.showAllStages || stage.deltaV > 0) if (this.showAllStages || stage.deltaV > 0)
{ {
//GUILayout.Label(stage.PartCount.ToString("N0"), this.infoStyle); //GUILayout.Label(stage.PartCount.ToString("N0"), this.infoStyle);
} }
} }
GUILayout.EndVertical(); GUILayout.EndVertical();
} }
/// <summary> /// <summary>
/// Draws the cost column. /// Draws the cost column.
/// </summary> /// </summary>
private void DrawCost() private void DrawCost()
{ {
GUILayout.BeginVertical(GUILayout.Width(100.0f)); GUILayout.BeginVertical(GUILayout.Width(100.0f));
GUILayout.Label("COST", this.titleStyle); GUILayout.Label("COST", this.titleStyle);
foreach (var stage in SimManager.Stages) foreach (var stage in SimManager.Stages)
{ {
if (this.showAllStages || stage.deltaV > 0) if (this.showAllStages || stage.deltaV > 0)
{ {
GUILayout.Label(stage.cost.ToString("N0") + " / " + stage.totalCost.ToString("N0"), this.infoStyle); GUILayout.Label(stage.cost.ToString("N0") + " / " + stage.totalCost.ToString("N0"), this.infoStyle);
} }
} }
GUILayout.EndVertical(); GUILayout.EndVertical();
} }
/// <summary> /// <summary>
/// Draws the mass column. /// Draws the mass column.
/// </summary> /// </summary>
private void DrawMass() private void DrawMass()
{ {
GUILayout.BeginVertical(GUILayout.Width(100.0f)); GUILayout.BeginVertical(GUILayout.Width(100.0f));
GUILayout.Label("MASS", this.titleStyle); GUILayout.Label("MASS", this.titleStyle);
foreach (var stage in SimManager.Stages) foreach (var stage in SimManager.Stages)
{ {
if (this.showAllStages || stage.deltaV > 0) if (this.showAllStages || stage.deltaV > 0)
{ {
GUILayout.Label(stage.mass.ToMass(false) + " / " + stage.totalMass.ToMass(), this.infoStyle); GUILayout.Label(stage.mass.ToMass(false) + " / " + stage.totalMass.ToMass(), this.infoStyle);
} }
} }
GUILayout.EndVertical(); GUILayout.EndVertical();
} }
/// <summary> /// <summary>
/// Draws the specific impluse column. /// Draws the specific impluse column.
/// </summary> /// </summary>
private void DrawIsp() private void DrawIsp()
{ {
GUILayout.BeginVertical(GUILayout.Width(50.0f)); GUILayout.BeginVertical(GUILayout.Width(50.0f));
GUILayout.Label("ISP", this.titleStyle); GUILayout.Label("ISP", this.titleStyle);
foreach (var stage in SimManager.Stages) foreach (var stage in SimManager.Stages)
{ {
if (this.showAllStages || stage.deltaV > 0) if (this.showAllStages || stage.deltaV > 0)
{ {
GUILayout.Label(stage.isp.ToString("F1") + "s", this.infoStyle); GUILayout.Label(stage.isp.ToString("F1") + "s", this.infoStyle);
} }
} }
GUILayout.EndVertical(); GUILayout.EndVertical();
} }
/// <summary> /// <summary>
/// Draws the thrust column. /// Draws the thrust column.
/// </summary> /// </summary>
private void DrawThrust() private void DrawThrust()
{ {
GUILayout.BeginVertical(GUILayout.Width(75.0f)); GUILayout.BeginVertical(GUILayout.Width(75.0f));
GUILayout.Label("THRUST", this.titleStyle); GUILayout.Label("THRUST", this.titleStyle);
foreach (var stage in SimManager.Stages) foreach (var stage in SimManager.Stages)
{ {
if (this.showAllStages || stage.deltaV > 0) if (this.showAllStages || stage.deltaV > 0)
{ {
GUILayout.Label(stage.thrust.ToForce(), this.infoStyle); GUILayout.Label(stage.thrust.ToForce(), this.infoStyle);
} }
} }
GUILayout.EndVertical(); GUILayout.EndVertical();
} }
/// <summary> /// <summary>
/// Drwas the thrust to weight ratio column. /// Drwas the thrust to weight ratio column.
/// </summary> /// </summary>
private void DrawTwr() private void DrawTwr()
{ {
GUILayout.BeginVertical(GUILayout.Width(75.0f)); GUILayout.BeginVertical(GUILayout.Width(75.0f));
GUILayout.Label("TWR (MAX)", this.titleStyle); GUILayout.Label("TWR (MAX)", this.titleStyle);
foreach (var stage in SimManager.Stages) foreach (var stage in SimManager.Stages)
{ {
if (this.showAllStages || stage.deltaV > 0) if (this.showAllStages || stage.deltaV > 0)
{ {
GUILayout.Label(stage.thrustToWeight.ToString("F2") + " (" + stage.maxThrustToWeight.ToString("F2") + ")", this.infoStyle); GUILayout.Label(stage.thrustToWeight.ToString("F2") + " (" + stage.maxThrustToWeight.ToString("F2") + ")", this.infoStyle);
} }
} }
GUILayout.EndVertical(); GUILayout.EndVertical();
} }
/// <summary> /// <summary>
/// Draws the deltaV column. /// Draws the deltaV column.
/// </summary> /// </summary>
private void DrawDeltaV() private void DrawDeltaV()
{ {
GUILayout.BeginVertical(GUILayout.Width(100.0f)); GUILayout.BeginVertical(GUILayout.Width(100.0f));
GUILayout.Label("DELTA-V", this.titleStyle); GUILayout.Label("DELTA-V", this.titleStyle);
foreach (var stage in SimManager.Stages) foreach (var stage in SimManager.Stages)
{ {
if (this.showAllStages || stage.deltaV > 0) if (this.showAllStages || stage.deltaV > 0)
{ {
GUILayout.Label(stage.deltaV.ToString("N0") + " / " + stage.inverseTotalDeltaV.ToString("N0") + "m/s", this.infoStyle); GUILayout.Label(stage.deltaV.ToString("N0") + " / " + stage.inverseTotalDeltaV.ToString("N0") + "m/s", this.infoStyle);
} }
} }
GUILayout.EndVertical(); GUILayout.EndVertical();
} }
/// <summary> /// <summary>
/// Draws the burn time column. /// Draws the burn time column.
/// </summary> /// </summary>
private void DrawBurnTime() private void DrawBurnTime()
{ {
GUILayout.BeginVertical(GUILayout.Width(75.0f)); GUILayout.BeginVertical(GUILayout.Width(75.0f));
GUILayout.Label("BURN", this.titleStyle); GUILayout.Label("BURN", this.titleStyle);
foreach (var stage in SimManager.Stages) foreach (var stage in SimManager.Stages)
{ {
if (this.showAllStages || stage.deltaV > 0) if (this.showAllStages || stage.deltaV > 0)
{ {
GUILayout.Label(stage.time.ToTime(), this.infoStyle); GUILayout.Label(stage.time.ToTime(), this.infoStyle);
} }
} }
GUILayout.EndVertical(); GUILayout.EndVertical();
} }
#endregion #endregion
#region Save and Load #region Save and Load
/// <summary> /// <summary>
/// Saves the settings when this object is destroyed. /// Saves the settings when this object is destroyed.
/// </summary> /// </summary>
private void OnDestroy() private void OnDestroy()
{ {
try try
{ {
var handler = new SettingHandler(); var handler = new SettingHandler();
handler.Set("visible", this.visible); handler.Set("visible", this.visible);
handler.Set("windowPositionX", this.windowPosition.x); handler.Set("windowPositionX", this.windowPosition.x);
handler.Set("windowPositionY", this.windowPosition.y); handler.Set("windowPositionY", this.windowPosition.y);
handler.Set("compactMode", this.compactMode); handler.Set("compactMode", this.compactMode);
handler.Set("showAllStages", this.showAllStages); handler.Set("showAllStages", this.showAllStages);
handler.Set("useAtmosphericDetails", this.useAtmosphericDetails); handler.Set("useAtmosphericDetails", this.useAtmosphericDetails);
handler.Set("showReferenceBodies", this.showReferenceBodies); handler.Set("showReferenceBodies", this.showReferenceBodies);
handler.Set("selectedBodyName", CelestialBodies.Instance.SelectedBodyName); handler.Set("selectedBodyName", CelestialBodies.Instance.SelectedBodyName);
handler.Save("BuildAdvanced.xml"); handler.Save("BuildAdvanced.xml");
} }
catch (Exception ex) catch (Exception ex)
{ {
Logger.Log("BuildAdvanced->OnDestroy"); Logger.Log("BuildAdvanced->OnDestroy");
Logger.Exception(ex); Logger.Exception(ex);
} }
} }
/// <summary> /// <summary>
/// Loads the settings when this object is created. /// Loads the settings when this object is created.
/// </summary> /// </summary>
private void Load() private void Load()
{ {
try try
{ {
var handler = SettingHandler.Load("BuildAdvanced.xml"); var handler = SettingHandler.Load("BuildAdvanced.xml");
handler.Get("visible", ref this.visible); handler.Get("visible", ref this.visible);
this.windowPosition.x = handler.Get("windowPositionX", this.windowPosition.x); this.windowPosition.x = handler.Get("windowPositionX", this.windowPosition.x);
this.windowPosition.y = handler.Get("windowPositionY", this.windowPosition.y); this.windowPosition.y = handler.Get("windowPositionY", this.windowPosition.y);
handler.Get("compactMode", ref this.compactMode); handler.Get("compactMode", ref this.compactMode);
handler.Get("showAllStages", ref this.showAllStages); handler.Get("showAllStages", ref this.showAllStages);
handler.Get("useAtmosphericDetails", ref this.useAtmosphericDetails); handler.Get("useAtmosphericDetails", ref this.useAtmosphericDetails);
CelestialBodies.Instance.SelectedBodyName = handler.Get("selectedBodyName", CelestialBodies.Instance.SelectedBodyName); CelestialBodies.Instance.SelectedBodyName = handler.Get("selectedBodyName", CelestialBodies.Instance.SelectedBodyName);
} }
catch (Exception ex) catch (Exception ex)
{ {
Logger.Log("BuildAdvanced->Load"); Logger.Log("BuildAdvanced->Load");
Logger.Exception(ex); Logger.Exception(ex);
} }
} }
#endregion #endregion
} }
} }
// //
// Kerbal Engineer Redux // Kerbal Engineer Redux
// //
// Copyright (C) 2014 CYBUTEK // Copyright (C) 2014 CYBUTEK
// //
// This program is free software: you can redistribute it and/or modify // This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by // it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or // the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version. // (at your option) any later version.
// //
// This program is distributed in the hope that it will be useful, // This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details. // GNU General Public License for more details.
// //
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>. // along with this program. If not, see <http://www.gnu.org/licenses/>.
// //
#region Using Directives #region Using Directives
using System; using System;
using System.Diagnostics; using System.Diagnostics;
using KerbalEngineer.Extensions; using KerbalEngineer.Extensions;
using KerbalEngineer.Settings; using KerbalEngineer.Settings;
using KerbalEngineer.Simulation; using KerbalEngineer.VesselSimulator;
using UnityEngine; using UnityEngine;
#endregion #endregion
namespace KerbalEngineer.Editor namespace KerbalEngineer.Editor
{ {
[KSPAddon(KSPAddon.Startup.EditorAny, false)] [KSPAddon(KSPAddon.Startup.EditorAny, false)]
public class BuildOverlay : MonoBehaviour public class BuildOverlay : MonoBehaviour
{ {
#region Instance #region Instance
/// <summary> /// <summary>
/// Gets the current instance if started or returns null. /// Gets the current instance if started or returns null.
/// </summary> /// </summary>
public static BuildOverlay Instance { get; private set; } public static BuildOverlay Instance { get; private set; }
#endregion #endregion
#region Fields #region Fields
private readonly Stopwatch tooltipInfoTimer = new Stopwatch(); private readonly Stopwatch tooltipInfoTimer = new Stopwatch();
private readonly int windowId = new Guid().GetHashCode(); private readonly int windowId = new Guid().GetHashCode();
private Part selectedPart; private Part selectedPart;
private Rect windowPosition = new Rect(300.0f, 0, 0, 0); private Rect windowPosition = new Rect(300.0f, 0, 0, 0);
#endregion #endregion
#region Constructors #region Constructors
private void Awake() private void Awake()
{ {
Instance = this; Instance = this;
} }
private void Start() private void Start()
{ {
this.InitialiseStyles(); this.InitialiseStyles();
this.Load(); this.Load();
RenderingManager.AddToPostDrawQueue(0, this.OnDraw); RenderingManager.AddToPostDrawQueue(0, this.OnDraw);
} }
#endregion #endregion
#region Properties #region Properties
private float tooltipInfoDelay = 0.5f; private float tooltipInfoDelay = 0.5f;
private bool visible; private bool visible;
public float TooltipInfoDelay public float TooltipInfoDelay
{ {
get { return this.tooltipInfoDelay; } get { return this.tooltipInfoDelay; }
set { this.tooltipInfoDelay = value; } set { this.tooltipInfoDelay = value; }
} }
/// <summary> /// <summary>
/// Gets and sets whether the display is enabled. /// Gets and sets whether the display is enabled.
/// </summary> /// </summary>
public bool Visible public bool Visible
{ {
get { return this.visible; } get { return this.visible; }
set { this.visible = value; } set { this.visible = value; }
} }
#endregion #endregion
#region GUIStyles #region GUIStyles
private GUIStyle infoStyle; private GUIStyle infoStyle;
private GUIStyle titleStyle; private GUIStyle titleStyle;
private GUIStyle tooltipInfoStyle; private GUIStyle tooltipInfoStyle;
private GUIStyle tooltipTitleStyle; private GUIStyle tooltipTitleStyle;
private GUIStyle windowStyle; private GUIStyle windowStyle;
private void InitialiseStyles() private void InitialiseStyles()
{ {
this.windowStyle = new GUIStyle(GUIStyle.none) this.windowStyle = new GUIStyle(GUIStyle.none)
{ {
margin = new RectOffset(), margin = new RectOffset(),
padding = new RectOffset() padding = new RectOffset()
}; };
this.titleStyle = new GUIStyle(HighLogic.Skin.label) this.titleStyle = new GUIStyle(HighLogic.Skin.label)
{ {
normal = normal =
{ {
textColor = Color.white textColor = Color.white
}, },
margin = new RectOffset(), margin = new RectOffset(),
padding = new RectOffset(), padding = new RectOffset(),
fontSize = 11, fontSize = 11,
fontStyle = FontStyle.Bold, fontStyle = FontStyle.Bold,
stretchWidth = true stretchWidth = true
}; };
this.infoStyle = new GUIStyle(HighLogic.Skin.label) this.infoStyle = new GUIStyle(HighLogic.Skin.label)
{ {
margin = new RectOffset(), margin = new RectOffset(),
padding = new RectOffset(), padding = new RectOffset(),
fontSize = 11, fontSize = 11,
fontStyle = FontStyle.Bold, fontStyle = FontStyle.Bold,
stretchWidth = true stretchWidth = true
}; };
this.tooltipTitleStyle = new GUIStyle(HighLogic.Skin.label) this.tooltipTitleStyle = new GUIStyle(HighLogic.Skin.label)
{ {
normal = normal =
{ {
textColor = Color.white textColor = Color.white
}, },
fontSize = 11, fontSize = 11,
fontStyle = FontStyle.Bold, fontStyle = FontStyle.Bold,
stretchWidth = true stretchWidth = true
}; };
this.tooltipInfoStyle = new GUIStyle(HighLogic.Skin.label) this.tooltipInfoStyle = new GUIStyle(HighLogic.Skin.label)
{ {
fontSize = 11, fontSize = 11,
fontStyle = FontStyle.Bold, fontStyle = FontStyle.Bold,
stretchWidth = true stretchWidth = true
}; };
} }
#endregion #endregion
#region Update and Drawing #region Update and Drawing
private void Update() private void Update()
{ {
try try
{ {
if (EditorLogic.fetch == null || EditorLogic.fetch.ship.parts.Count == 0) if (EditorLogic.fetch == null || EditorLogic.fetch.ship.parts.Count == 0)
{ {
return; return;
} }
// Configure the simulation parameters based on the selected reference body. // Configure the simulation parameters based on the selected reference body.
SimManager.Gravity = CelestialBodies.Instance.SelectedBodyInfo.Gravity; SimManager.Gravity = CelestialBodies.Instance.SelectedBodyInfo.Gravity;
if (BuildAdvanced.Instance.UseAtmosphericDetails) if (BuildAdvanced.Instance.UseAtmosphericDetails)
{ {
SimManager.Atmosphere = CelestialBodies.Instance.SelectedBodyInfo.Atmosphere * 0.01d; SimManager.Atmosphere = CelestialBodies.Instance.SelectedBodyInfo.Atmosphere * 0.01d;
} }
else else
{ {
SimManager.Atmosphere = 0; SimManager.Atmosphere = 0;
} }
SimManager.TryStartSimulation(); SimManager.TryStartSimulation();
} }
catch (Exception ex) catch (Exception ex)
{ {
Logger.Log("BuildOverlay->Update"); Logger.Log("BuildOverlay->Update");
Logger.Exception(ex); Logger.Exception(ex);
} }
} }
private void OnDraw() private void OnDraw()
{ {
try try
{ {
if (!this.visible || EditorLogic.fetch == null || EditorLogic.fetch.ship.parts.Count == 0 || EditorLogic.fetch.editorScreen != EditorLogic.EditorScreen.Parts) if (!this.visible || EditorLogic.fetch == null || EditorLogic.fetch.ship.parts.Count == 0 || EditorLogic.fetch.editorScreen != EditorLogic.EditorScreen.Parts)
{ {
return; return;
} }
SimManager.RequestSimulation(); SimManager.RequestSimulation();
this.windowPosition = GUILayout.Window(this.windowId, this.windowPosition, this.Window, string.Empty, this.windowStyle); this.windowPosition = GUILayout.Window(this.windowId, this.windowPosition, this.Window, string.Empty, this.windowStyle);
// Check and set that the window is at the bottom of the screen. // Check and set that the window is at the bottom of the screen.
if (this.windowPosition.y + this.windowPosition.height != Screen.height - 5.0f) if (this.windowPosition.y + this.windowPosition.height != Screen.height - 5.0f)
{ {
this.windowPosition.y = Screen.height - this.windowPosition.height - 5.0f; this.windowPosition.y = Screen.height - this.windowPosition.height - 5.0f;
} }
// Find if a part is selected or being hovered over. // Find if a part is selected or being hovered over.
if (EditorLogic.SelectedPart != null) if (EditorLogic.SelectedPart != null)
{ {
// Do not allow the extended information to be shown. // Do not allow the extended information to be shown.
if (this.selectedPart != null) if (this.selectedPart != null)
{ {
this.selectedPart = null; this.selectedPart = null;
this.tooltipInfoTimer.Reset(); this.tooltipInfoTimer.Reset();
} }
this.DrawTooltip(EditorLogic.SelectedPart); this.DrawTooltip(EditorLogic.SelectedPart);
} }
else else
{ {
var isPartSelected = false; var isPartSelected = false;
foreach (var part in EditorLogic.SortedShipList) foreach (var part in EditorLogic.SortedShipList)
{ {
if (part.stackIcon.highlightIcon) if (part.stackIcon.highlightIcon)
{ {
// Start the extended information timer. // Start the extended information timer.
if (part != this.selectedPart) if (part != this.selectedPart)
{ {
this.selectedPart = part; this.selectedPart = part;
this.tooltipInfoTimer.Reset(); this.tooltipInfoTimer.Reset();
this.tooltipInfoTimer.Start(); this.tooltipInfoTimer.Start();
} }
isPartSelected = true; isPartSelected = true;
this.DrawTooltip(part); this.DrawTooltip(part);
break; break;
} }
} }
// If no part is being hovered over we must reset the extended information timer. // If no part is being hovered over we must reset the extended information timer.
if (!isPartSelected && this.selectedPart != null) if (!isPartSelected && this.selectedPart != null)
{ {
this.selectedPart = null; this.selectedPart = null;
this.tooltipInfoTimer.Reset(); this.tooltipInfoTimer.Reset();
} }
} }
} }
catch (Exception ex) catch (Exception ex)
{ {
Logger.Log("BuildOverlay->OnDraw"); Logger.Log("BuildOverlay->OnDraw");
Logger.Exception(ex); Logger.Exception(ex);
} }
} }
private void Window(int windowId) private void Window(int windowId)
{ {
GUILayout.BeginHorizontal(); GUILayout.BeginHorizontal();
// Titles // Titles
GUILayout.BeginVertical(GUILayout.Width(75.0f)); GUILayout.BeginVertical(GUILayout.Width(75.0f));
//GUILayout.Label("Parts:", this.titleStyle); //GUILayout.Label("Parts:", this.titleStyle);
GUILayout.Label("Delta-V:", this.titleStyle); GUILayout.Label("Delta-V:", this.titleStyle);
GUILayout.Label("TWR:", this.titleStyle); GUILayout.Label("TWR:", this.titleStyle);
GUILayout.EndVertical(); GUILayout.EndVertical();
// Details // Details
GUILayout.BeginVertical(GUILayout.Width(100.0f)); GUILayout.BeginVertical(GUILayout.Width(100.0f));
//GUILayout.Label(SimulationManager.Instance.LastStage.partCount.ToString("N0"), this.infoStyle); //GUILayout.Label(SimulationManager.Instance.LastStage.partCount.ToString("N0"), this.infoStyle);
GUILayout.Label(SimManager.LastStage.totalDeltaV.ToString("N0") + " m/s", this.infoStyle); GUILayout.Label(SimManager.LastStage.totalDeltaV.ToString("N0") + " m/s", this.infoStyle);
GUILayout.Label(SimManager.LastStage.thrustToWeight.ToString("F2"), this.infoStyle); GUILayout.Label(SimManager.LastStage.thrustToWeight.ToString("F2"), this.infoStyle);
GUILayout.EndVertical(); GUILayout.EndVertical();
GUILayout.EndHorizontal(); GUILayout.EndHorizontal();
} }
/// <summary> /// <summary>
/// Draws the tooltip details of the selected/highlighted part. /// Draws the tooltip details of the selected/highlighted part.
/// </summary> /// </summary>
private void DrawTooltip(Part part) private void DrawTooltip(Part part)
{ {
// Tooltip title (name of part). // Tooltip title (name of part).
var content = new GUIContent(part.partInfo.title); var content = new GUIContent(part.partInfo.title);
var size = this.tooltipTitleStyle.CalcSize(content); var size = this.tooltipTitleStyle.CalcSize(content);
var position = new Rect(Event.current.mousePosition.x + 16.0f, Event.current.mousePosition.y, size.x, size.y).ClampInsideScreen(); var position = new Rect(Event.current.mousePosition.x + 16.0f, Event.current.mousePosition.y, size.x, size.y).ClampInsideScreen();
if (position.x < Event.current.mousePosition.x + 16.0f) if (position.x < Event.current.mousePosition.x + 16.0f)
{ {
position.y += 16.0f; position.y += 16.0f;
} }
GUI.Label(position, content, this.tooltipTitleStyle); GUI.Label(position, content, this.tooltipTitleStyle);
// After hovering for a period of time, show extended information. // After hovering for a period of time, show extended information.
if (this.tooltipInfoTimer.Elapsed.TotalSeconds >= this.tooltipInfoDelay) if (this.tooltipInfoTimer.Elapsed.TotalSeconds >= this.tooltipInfoDelay)
{ {
// Stop the timer as it is no longer needed. // Stop the timer as it is no longer needed.
if (this.tooltipInfoTimer.IsRunning) if (this.tooltipInfoTimer.IsRunning)
{ {
this.tooltipInfoTimer.Stop(); this.tooltipInfoTimer.Stop();
} }
// Show the dry mass of the part if applicable. // Show the dry mass of the part if applicable.
if (part.physicalSignificance == Part.PhysicalSignificance.FULL) if (part.physicalSignificance == Part.PhysicalSignificance.FULL)
{ {
this.DrawTooltipInfo(ref position, "Dry Mass: " + part.GetDryMass().ToMass()); this.DrawTooltipInfo(ref position, "Dry Mass: " + part.GetDryMass().ToMass());
} }
// Show resources contained within the part. // Show resources contained within the part.
if (part.ContainsResources()) if (part.ContainsResources())
{ {
// Show the wet mass of the part if applicable. // Show the wet mass of the part if applicable.
if (part.GetResourceMass() > 0) if (part.GetResourceMass() > 0)
{ {
this.DrawTooltipInfo(ref position, "Wet Mass: " + part.GetWetMass().ToMass()); this.DrawTooltipInfo(ref position, "Wet Mass: " + part.GetWetMass().ToMass());
} }
// List all the resources contained within the part. // List all the resources contained within the part.
foreach (PartResource resource in part.Resources) foreach (PartResource resource in part.Resources)
{ {
if (resource.GetDensity() > 0) if (resource.GetDensity() > 0)
{ {
this.DrawTooltipInfo(ref position, resource.info.name + ": " + resource.GetMass().ToMass() + " (" + resource.amount + ")"); this.DrawTooltipInfo(ref position, resource.info.name + ": " + resource.GetMass().ToMass() + " (" + resource.amount + ")");
} }
else else
{ {
this.DrawTooltipInfo(ref position, resource.info.name + ": " + resource.amount); this.DrawTooltipInfo(ref position, resource.info.name + ": " + resource.amount);
} }
} }
} }
// Show details for engines. // Show details for engines.
if (part.IsEngine()) if (part.IsEngine())
{ {
this.DrawTooltipInfo(ref position, "Maximum Thrust: " + part.GetMaxThrust().ToForce()); this.DrawTooltipInfo(ref position, "Maximum Thrust: " + part.GetMaxThrust().ToForce());
this.DrawTooltipInfo(ref position, "Specific Impulse: " + part.GetSpecificImpulse(1f) + " / " + part.GetSpecificImpulse(0f) + "s"); this.DrawTooltipInfo(ref position, "Specific Impulse: " + part.GetSpecificImpulse(1f) + " / " + part.GetSpecificImpulse(0f) + "s");
// Thrust vectoring. // Thrust vectoring.
if (part.HasModule("ModuleGimbal")) if (part.HasModule("ModuleGimbal"))
{ {
this.DrawTooltipInfo(ref position, "Thrust Vectoring Enabled"); this.DrawTooltipInfo(ref position, "Thrust Vectoring Enabled");
} }
// Contains alternator. // Contains alternator.
if (part.HasModule("ModuleAlternator")) if (part.HasModule("ModuleAlternator"))
{ {
this.DrawTooltipInfo(ref position, "Contains Alternator"); this.DrawTooltipInfo(ref position, "Contains Alternator");
} }
} }
// Show details for RCS. // Show details for RCS.
if (part.IsRcsModule()) if (part.IsRcsModule())
{ {
var moduleRcs = part.GetModuleRcs(); var moduleRcs = part.GetModuleRcs();
this.DrawTooltipInfo(ref position, "Thrust Power: " + moduleRcs.thrusterPower.ToDouble().ToForce()); this.DrawTooltipInfo(ref position, "Thrust Power: " + moduleRcs.thrusterPower.ToDouble().ToForce());
this.DrawTooltipInfo(ref position, "Specific Impulse: " + moduleRcs.atmosphereCurve.Evaluate(1f) + " / " + moduleRcs.atmosphereCurve.Evaluate(0f) + "s"); this.DrawTooltipInfo(ref position, "Specific Impulse: " + moduleRcs.atmosphereCurve.Evaluate(1f) + " / " + moduleRcs.atmosphereCurve.Evaluate(0f) + "s");
} }
// Show details for solar panels. // Show details for solar panels.
if (part.IsSolarPanel()) if (part.IsSolarPanel())
{ {
this.DrawTooltipInfo(ref position, "Charge Rate: " + part.GetModuleDeployableSolarPanel().chargeRate.ToDouble().ToRate()); this.DrawTooltipInfo(ref position, "Charge Rate: " + part.GetModuleDeployableSolarPanel().chargeRate.ToDouble().ToRate());
} }
// Show details for generators. // Show details for generators.
if (part.IsGenerator()) if (part.IsGenerator())
{ {
foreach (var resource in part.GetModuleGenerator().inputList) foreach (var resource in part.GetModuleGenerator().inputList)
{ {
this.DrawTooltipInfo(ref position, "Input: " + resource.name + " (" + resource.rate.ToDouble().ToRate() + ")"); this.DrawTooltipInfo(ref position, "Input: " + resource.name + " (" + resource.rate.ToDouble().ToRate() + ")");
} }
foreach (var resource in part.GetModuleGenerator().outputList) foreach (var resource in part.GetModuleGenerator().outputList)
{ {
this.DrawTooltipInfo(ref position, "Output: " + resource.name + " (" + resource.rate.ToDouble().ToRate() + ")"); this.DrawTooltipInfo(ref position, "Output: " + resource.name + " (" + resource.rate.ToDouble().ToRate() + ")");
} }
} }
// Show details for parachutes. // Show details for parachutes.
if (part.IsParachute()) if (part.IsParachute())
{ {
var module = part.GetModuleParachute(); var module = part.GetModuleParachute();
this.DrawTooltipInfo(ref position, "Semi Deployed Drag: " + module.semiDeployedDrag); this.DrawTooltipInfo(ref position, "Semi Deployed Drag: " + module.semiDeployedDrag);
this.DrawTooltipInfo(ref position, "Fully Deployed Drag: " + module.fullyDeployedDrag); this.DrawTooltipInfo(ref position, "Fully Deployed Drag: " + module.fullyDeployedDrag);
this.DrawTooltipInfo(ref position, "Deployment Altitude: " + module.deployAltitude.ToDouble().ToDistance()); this.DrawTooltipInfo(ref position, "Deployment Altitude: " + module.deployAltitude.ToDouble().ToDistance());
} }
// Contains stability augmentation system. // Contains stability augmentation system.
if (part.HasModule("ModuleSAS")) if (part.HasModule("ModuleSAS"))
{ {
this.DrawTooltipInfo(ref position, "Contains SAS"); this.DrawTooltipInfo(ref position, "Contains SAS");
} }
// Contains reaction wheels. // Contains reaction wheels.
if (part.HasModule("ModuleReactionWheel")) if (part.HasModule("ModuleReactionWheel"))
{ {
this.DrawTooltipInfo(ref position, "Contains Reaction Wheels"); this.DrawTooltipInfo(ref position, "Contains Reaction Wheels");
} }
// Show if the part has an animation that can only be used once. // Show if the part has an animation that can only be used once.
if (part.HasOneShotAnimation()) if (part.HasOneShotAnimation())
{ {
this.DrawTooltipInfo(ref position, "Single Activation Only"); this.DrawTooltipInfo(ref position, "Single Activation Only");
} }
} }
} }
/// <summary> /// <summary>
/// Draws a line of extended information below the previous. /// Draws a line of extended information below the previous.
/// </summary> /// </summary>
private void DrawTooltipInfo(ref Rect position, string value) private void DrawTooltipInfo(ref Rect position, string value)
{ {
var content = new GUIContent(value); var content = new GUIContent(value);
var size = this.tooltipInfoStyle.CalcSize(content); var size = this.tooltipInfoStyle.CalcSize(content);
position.y += 16.0f; position.y += 16.0f;
position.width = size.x; position.width = size.x;
position.height = size.y; position.height = size.y;
GUI.Label(position, content, this.tooltipInfoStyle); GUI.Label(position, content, this.tooltipInfoStyle);
} }
#endregion #endregion
#region Save and Load #region Save and Load
/// <summary> /// <summary>
/// Saves the settings when this object is destroyed. /// Saves the settings when this object is destroyed.
/// </summary> /// </summary>
private void OnDestroy() private void OnDestroy()
{ {
try try
{ {
var handler = new SettingHandler(); var handler = new SettingHandler();
handler.Set("visible", this.visible); handler.Set("visible", this.visible);
handler.Save("BuildOverlay.xml"); handler.Save("BuildOverlay.xml");
} }
catch (Exception ex) catch (Exception ex)
{ {
Logger.Log("BuildOverlay->OnDestroy"); Logger.Log("BuildOverlay->OnDestroy");
Logger.Exception(ex); Logger.Exception(ex);
} }
} }
/// <summary> /// <summary>
/// Loads the settings when this object is created. /// Loads the settings when this object is created.
/// </summary> /// </summary>
private void Load() private void Load()
{ {
try try
{ {
var handler = SettingHandler.Load("BuildOverlay.xml"); var handler = SettingHandler.Load("BuildOverlay.xml");
handler.Get("visible", ref this.visible); handler.Get("visible", ref this.visible);
} }
catch (Exception ex) catch (Exception ex)
{ {
Logger.Log("BuildOverlay->Load"); Logger.Log("BuildOverlay->Load");
Logger.Exception(ex); Logger.Exception(ex);
} }
} }
#endregion #endregion
} }
} }
// //
// Kerbal Engineer Redux // Kerbal Engineer Redux
// //
// Copyright (C) 2014 CYBUTEK // Copyright (C) 2014 CYBUTEK
// //
// This program is free software: you can redistribute it and/or modify // This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by // it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or // the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version. // (at your option) any later version.
// //
// This program is distributed in the hope that it will be useful, // This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details. // GNU General Public License for more details.
// //
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>. // along with this program. If not, see <http://www.gnu.org/licenses/>.
// //
#region Using Directives #region Using Directives
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using KerbalEngineer.Flight.Sections; using KerbalEngineer.Flight.Sections;
using KerbalEngineer.Settings; using KerbalEngineer.Settings;
using KerbalEngineer.Simulation; using KerbalEngineer.VesselSimulator;
using UnityEngine; using UnityEngine;
#endregion #endregion
namespace KerbalEngineer.Flight namespace KerbalEngineer.Flight
{ {
/// <summary> /// <summary>
/// Graphical controller for section interaction in the form of a menu system. /// Graphical controller for section interaction in the form of a menu system.
/// </summary> /// </summary>
public class ActionMenu : MonoBehaviour public class ActionMenu : MonoBehaviour
{ {
#region Constants #region Constants
private const float ScrollSpeed = 2.0f; private const float ScrollSpeed = 2.0f;
#endregion #endregion
#region Fields #region Fields
private readonly int windowId = new Guid().GetHashCode(); private readonly int windowId = new Guid().GetHashCode();
private bool isOpen = true; private bool isOpen = true;
private int numberOfSections; private int numberOfSections;
private float scrollPercent; private float scrollPercent;
private Rect windowPosition = new Rect((Screen.width * 0.25f) - 100.0f, 0, 200.0f, 0); private Rect windowPosition = new Rect((Screen.width * 0.25f) - 100.0f, 0, 200.0f, 0);
#endregion #endregion
#region Constructors #region Constructors
/// <summary> /// <summary>
/// Initialises object's state on creation. /// Initialises object's state on creation.
/// </summary> /// </summary>
private void Start() private void Start()
{ {
this.InitialiseStyles(); this.InitialiseStyles();
this.Load(); this.Load();
RenderingManager.AddToPostDrawQueue(0, this.Draw); RenderingManager.AddToPostDrawQueue(0, this.Draw);
} }
#endregion #endregion
#region GUIStyles #region GUIStyles
private GUIStyle boxStyle; private GUIStyle boxStyle;
private GUIStyle buttonStyle; private GUIStyle buttonStyle;
private GUIStyle windowStyle; private GUIStyle windowStyle;
/// <summary> /// <summary>
/// Initialises all the styles required for this object. /// Initialises all the styles required for this object.
/// </summary> /// </summary>
private void InitialiseStyles() private void InitialiseStyles()
{ {
this.windowStyle = new GUIStyle(); this.windowStyle = new GUIStyle();
this.boxStyle = new GUIStyle(HighLogic.Skin.window) this.boxStyle = new GUIStyle(HighLogic.Skin.window)
{ {
margin = new RectOffset(), margin = new RectOffset(),
padding = new RectOffset(3, 3, 3, 3) padding = new RectOffset(3, 3, 3, 3)
}; };
this.buttonStyle = new GUIStyle(HighLogic.Skin.button) this.buttonStyle = new GUIStyle(HighLogic.Skin.button)
{ {
normal = normal =
{ {
textColor = Color.white textColor = Color.white
}, },
margin = new RectOffset(), margin = new RectOffset(),
padding = new RectOffset(), padding = new RectOffset(),
alignment = TextAnchor.MiddleCenter, alignment = TextAnchor.MiddleCenter,
fontSize = 11, fontSize = 11,
fontStyle = FontStyle.Bold, fontStyle = FontStyle.Bold,
fixedHeight = 20.0f, fixedHeight = 20.0f,
}; };
} }
#endregion #endregion
#region Drawing #region Drawing
/// <summary> /// <summary>
/// Called to draw the menu when the UI is enabled. /// Called to draw the menu when the UI is enabled.
/// </summary> /// </summary>
private void Draw() private void Draw()
{ {
if (this.numberOfSections != SectionLibrary.Instance.NumberOfSections) if (this.numberOfSections != SectionLibrary.Instance.NumberOfSections)
{ {
this.numberOfSections = SectionLibrary.Instance.NumberOfSections; this.numberOfSections = SectionLibrary.Instance.NumberOfSections;
this.windowPosition.height = 0; this.windowPosition.height = 0;
} }
this.windowPosition = GUILayout.Window(this.windowId, this.windowPosition, this.Window, string.Empty, this.windowStyle); this.windowPosition = GUILayout.Window(this.windowId, this.windowPosition, this.Window, string.Empty, this.windowStyle);
this.ScrollMechanism(); this.ScrollMechanism();
} }
/// <summary> /// <summary>
/// Draws the menu window. /// Draws the menu window.
/// </summary> /// </summary>
private void Window(int windowId) private void Window(int windowId)
{ {
GUILayout.BeginVertical(this.boxStyle); GUILayout.BeginVertical(this.boxStyle);
this.DrawControlBarButton(); this.DrawControlBarButton();
this.DrawSections(SectionLibrary.Instance.StockSections); this.DrawSections(SectionLibrary.Instance.StockSections);
this.DrawSections(SectionLibrary.Instance.CustomSections); this.DrawSections(SectionLibrary.Instance.CustomSections);
this.DrawNewButton(); this.DrawNewButton();
GUILayout.EndVertical(); GUILayout.EndVertical();
if (GUILayout.Button("FLIGHT ENGINEER", this.buttonStyle)) if (GUILayout.Button("FLIGHT ENGINEER", this.buttonStyle))
{ {
this.isOpen = !this.isOpen; this.isOpen = !this.isOpen;
} }
} }
/// <summary> /// <summary>
/// Draws and performs the control bar button action. /// Draws and performs the control bar button action.
/// </summary> /// </summary>
private void DrawControlBarButton() private void DrawControlBarButton()
{ {
if (GUILayout.Toggle(DisplayStack.Instance.ShowControlBar, "CONTROL BAR", this.buttonStyle) != DisplayStack.Instance.ShowControlBar) if (GUILayout.Toggle(DisplayStack.Instance.ShowControlBar, "CONTROL BAR", this.buttonStyle) != DisplayStack.Instance.ShowControlBar)
{ {
DisplayStack.Instance.ShowControlBar = !DisplayStack.Instance.ShowControlBar; DisplayStack.Instance.ShowControlBar = !DisplayStack.Instance.ShowControlBar;
DisplayStack.Instance.RequestResize(); DisplayStack.Instance.RequestResize();
} }
} }
/// <summary> /// <summary>
/// Draws an action list for the supplied sections. /// Draws an action list for the supplied sections.
/// </summary> /// </summary>
private void DrawSections(IEnumerable<SectionModule> sections) private void DrawSections(IEnumerable<SectionModule> sections)
{ {
foreach (var section in sections) foreach (var section in sections)
{ {
GUILayout.BeginHorizontal(); GUILayout.BeginHorizontal();
section.IsVisible = GUILayout.Toggle(section.IsVisible, section.Name.ToUpper(), this.buttonStyle); section.IsVisible = GUILayout.Toggle(section.IsVisible, section.Name.ToUpper(), this.buttonStyle);
section.IsEditorVisible = GUILayout.Toggle(section.IsEditorVisible, "EDIT", this.buttonStyle, GUILayout.Width(50.0f)); section.IsEditorVisible = GUILayout.Toggle(section.IsEditorVisible, "EDIT", this.buttonStyle, GUILayout.Width(50.0f));
GUILayout.EndHorizontal(); GUILayout.EndHorizontal();
} }
} }
/// <summary> /// <summary>
/// Draws and performs the new section button action. /// Draws and performs the new section button action.
/// </summary> /// </summary>
private void DrawNewButton() private void DrawNewButton()
{ {
GUILayout.BeginHorizontal(); GUILayout.BeginHorizontal();
GUI.skin = HighLogic.Skin; GUI.skin = HighLogic.Skin;
SimManager.minSimTime = (long)GUILayout.HorizontalSlider(SimManager.minSimTime, 0, 1000.0f); SimManager.minSimTime = (long)GUILayout.HorizontalSlider(SimManager.minSimTime, 0, 1000.0f);
GUI.skin = null; GUI.skin = null;
if (GUILayout.Button("NEW", this.buttonStyle, GUILayout.Width(50.0f))) if (GUILayout.Button("NEW", this.buttonStyle, GUILayout.Width(50.0f)))
{ {
SectionLibrary.Instance.CustomSections.Add(new SectionModule SectionLibrary.Instance.CustomSections.Add(new SectionModule
{ {
Name = "Custom " + (SectionLibrary.Instance.CustomSections.Count + 1), Name = "Custom " + (SectionLibrary.Instance.CustomSections.Count + 1),
Abbreviation = "CUST " + (SectionLibrary.Instance.CustomSections.Count + 1), Abbreviation = "CUST " + (SectionLibrary.Instance.CustomSections.Count + 1),
IsVisible = true, IsVisible = true,
IsCustom = true, IsCustom = true,
IsEditorVisible = true IsEditorVisible = true
}); });
} }
GUILayout.EndHorizontal(); GUILayout.EndHorizontal();
} }
/// <summary> /// <summary>
/// Controls the dynamics of the scrolling mechanism. /// Controls the dynamics of the scrolling mechanism.
/// </summary> /// </summary>
private void ScrollMechanism() private void ScrollMechanism()
{ {
if (this.isOpen && this.windowPosition.y != 0) if (this.isOpen && this.windowPosition.y != 0)
{ {
this.scrollPercent += Time.deltaTime * ScrollSpeed; this.scrollPercent += Time.deltaTime * ScrollSpeed;
this.windowPosition.y = Mathf.Lerp(this.windowPosition.y, 0, this.scrollPercent); this.windowPosition.y = Mathf.Lerp(this.windowPosition.y, 0, this.scrollPercent);
} }
else if (!this.isOpen && this.windowPosition.y != 20.0f - this.windowPosition.height) else if (!this.isOpen && this.windowPosition.y != 20.0f - this.windowPosition.height)
{ {
this.scrollPercent += Time.deltaTime * ScrollSpeed; this.scrollPercent += Time.deltaTime * ScrollSpeed;
this.windowPosition.y = Mathf.Lerp(this.windowPosition.y, 20.0f - this.windowPosition.height, this.scrollPercent); this.windowPosition.y = Mathf.Lerp(this.windowPosition.y, 20.0f - this.windowPosition.height, this.scrollPercent);
} }
else else
{ {
this.scrollPercent = 0; this.scrollPercent = 0;
} }
} }
#endregion #endregion
#region Destruction #region Destruction
/// <summary> /// <summary>
/// Runs when the object is destroyed. /// Runs when the object is destroyed.
/// </summary> /// </summary>
private void OnDestroy() private void OnDestroy()
{ {
this.Save(); this.Save();
RenderingManager.RemoveFromPostDrawQueue(0, this.Draw); RenderingManager.RemoveFromPostDrawQueue(0, this.Draw);
} }
#endregion #endregion
#region Saving and Loading #region Saving and Loading
/// <summary> /// <summary>
/// Saves the menu's state. /// Saves the menu's state.
/// </summary> /// </summary>
private void Save() private void Save()
{ {
var handler = new SettingHandler(); var handler = new SettingHandler();
handler.Set("isOpen", this.isOpen); handler.Set("isOpen", this.isOpen);
handler.Set("windowPositionY", this.windowPosition.y); handler.Set("windowPositionY", this.windowPosition.y);
handler.Set("windowPositionHeight", this.windowPosition.height); handler.Set("windowPositionHeight", this.windowPosition.height);
handler.Save("ActionMenu.xml"); handler.Save("ActionMenu.xml");
} }
/// <summary> /// <summary>
/// Loads the menu's state. /// Loads the menu's state.
/// </summary> /// </summary>
private void Load() private void Load()
{ {
var handler = SettingHandler.Load("ActionMenu.xml"); var handler = SettingHandler.Load("ActionMenu.xml");
handler.Get("isOpen", ref this.isOpen); handler.Get("isOpen", ref this.isOpen);
this.windowPosition.y = handler.Get("windowPositionY", this.windowPosition.y); this.windowPosition.y = handler.Get("windowPositionY", this.windowPosition.y);
this.windowPosition.height = handler.Get("windowPositionHeight", this.windowPosition.height); this.windowPosition.height = handler.Get("windowPositionHeight", this.windowPosition.height);
} }
#endregion #endregion
} }
} }
// //
// Kerbal Engineer Redux // Kerbal Engineer Redux
// //
// Copyright (C) 2014 CYBUTEK // Copyright (C) 2014 CYBUTEK
// //
// This program is free software: you can redistribute it and/or modify // This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by // it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or // the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version. // (at your option) any later version.
// //
// This program is distributed in the hope that it will be useful, // This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details. // GNU General Public License for more details.
// //
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>. // along with this program. If not, see <http://www.gnu.org/licenses/>.
// //
#region Using Directives #region Using Directives
using KerbalEngineer.Simulation; using KerbalEngineer.VesselSimulator;
#endregion #endregion
namespace KerbalEngineer.Flight.Readouts.Vessel namespace KerbalEngineer.Flight.Readouts.Vessel
{ {
public class DeltaVStaged : ReadoutModule public class DeltaVStaged : ReadoutModule
{ {
private int numberOfStages; private int numberOfStages;
public DeltaVStaged() public DeltaVStaged()
{ {
this.Name = "DeltaV Staged"; this.Name = "DeltaV Staged";
this.Category = ReadoutCategory.Vessel; this.Category = ReadoutCategory.Vessel;
this.HelpString = "Shows the vessel's delta velocity for each stage."; this.HelpString = "Shows the vessel's delta velocity for each stage.";
this.IsDefault = true; this.IsDefault = true;
} }
public override void Update() public override void Update()
{ {
SimManager.RequestUpdate(); SimManager.RequestUpdate();
} }
public override void Draw() public override void Draw()
{ {
var newNumberOfStages = 0; var newNumberOfStages = 0;
foreach (var stage in SimManager.Stages) foreach (var stage in SimManager.Stages)
{ {
if (stage.deltaV > 0 || stage.number == Staging.CurrentStage) if (stage.deltaV > 0 || stage.number == Staging.CurrentStage)
{ {
this.DrawLine("DeltaV (S" + stage.number + ")", stage.deltaV.ToString("N0") + "m/s"); this.DrawLine("DeltaV (S" + stage.number + ")", stage.deltaV.ToString("N0") + "m/s");
newNumberOfStages++; newNumberOfStages++;
} }
} }
if (newNumberOfStages != this.numberOfStages) if (newNumberOfStages != this.numberOfStages)
{ {
this.numberOfStages = newNumberOfStages; this.numberOfStages = newNumberOfStages;
this.ResizeRequested = true; this.ResizeRequested = true;
} }
} }
public override void Reset() public override void Reset()
{ {
FlightEngineerCore.Instance.AddUpdatable(SimManager.Instance); FlightEngineerCore.Instance.AddUpdatable(SimManager.Instance);
} }
} }
} }
// //
// Kerbal Engineer Redux // Kerbal Engineer Redux
// //
// Copyright (C) 2014 CYBUTEK // Copyright (C) 2014 CYBUTEK
// //
// This program is free software: you can redistribute it and/or modify // This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by // it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or // the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version. // (at your option) any later version.
// //
// This program is distributed in the hope that it will be useful, // This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details. // GNU General Public License for more details.
// //
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>. // along with this program. If not, see <http://www.gnu.org/licenses/>.
// //
#region Using Directives #region
using KerbalEngineer.Simulation; using KerbalEngineer.VesselSimulator;
#endregion #endregion
namespace KerbalEngineer.Flight.Readouts.Vessel namespace KerbalEngineer.Flight.Readouts.Vessel
{ {
public class DeltaVTotal : ReadoutModule public class DeltaVTotal : ReadoutModule
{ {
public DeltaVTotal() public DeltaVTotal()
{ {
this.Name = "DeltaV Total"; this.Name = "DeltaV Total";
this.Category = ReadoutCategory.Vessel; this.Category = ReadoutCategory.Vessel;
this.HelpString = "Shows the vessel's total delta velocity."; this.HelpString = "Shows the vessel's total delta velocity.";
this.IsDefault = true; this.IsDefault = true;
} }
public override void Update() public override void Update()
{ {
SimManager.RequestUpdate(); SimManager.RequestUpdate();
} }
public override void Draw() public override void Draw()
{ {
this.DrawLine(SimManager.LastStage.totalDeltaV.ToString("N0") + "m/s"); this.DrawLine(SimManager.LastStage.totalDeltaV.ToString("N0") + "m/s");
} }
public override void Reset() public override void Reset()
{ {
FlightEngineerCore.Instance.AddUpdatable(SimManager.Instance); FlightEngineerCore.Instance.AddUpdatable(SimManager.Instance);
} }
} }
} }
// //
// Kerbal Engineer Redux // Kerbal Engineer Redux
// //
// Copyright (C) 2014 CYBUTEK // Copyright (C) 2014 CYBUTEK
// //
// This program is free software: you can redistribute it and/or modify // This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by // it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or // the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version. // (at your option) any later version.
// //
// This program is distributed in the hope that it will be useful, // This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details. // GNU General Public License for more details.
// //
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>. // along with this program. If not, see <http://www.gnu.org/licenses/>.
// //
#region Using Directives #region Using Directives
using KerbalEngineer.Extensions; using KerbalEngineer.Extensions;
using KerbalEngineer.Simulation; using KerbalEngineer.VesselSimulator;
#endregion #endregion
namespace KerbalEngineer.Flight.Readouts.Vessel namespace KerbalEngineer.Flight.Readouts.Vessel
{ {
public class Mass : ReadoutModule public class Mass : ReadoutModule
{ {
public Mass() public Mass()
{ {
this.Name = "Mass"; this.Name = "Mass";
this.Category = ReadoutCategory.Vessel; this.Category = ReadoutCategory.Vessel;
this.HelpString = string.Empty; this.HelpString = string.Empty;
this.IsDefault = true; this.IsDefault = true;
} }
public override void Update() public override void Update()
{ {
SimManager.RequestUpdate(); SimManager.RequestUpdate();
} }
public override void Draw() public override void Draw()
{ {
this.DrawLine(SimManager.LastStage.mass.ToMass(false) + " / " + SimManager.LastStage.totalMass.ToMass()); this.DrawLine(SimManager.LastStage.mass.ToMass(false) + " / " + SimManager.LastStage.totalMass.ToMass());
} }
public override void Reset() public override void Reset()
{ {
FlightEngineerCore.Instance.AddUpdatable(SimManager.Instance); FlightEngineerCore.Instance.AddUpdatable(SimManager.Instance);
} }
} }
} }
// //
// Kerbal Engineer Redux // Kerbal Engineer Redux
// //
// Copyright (C) 2014 CYBUTEK // Copyright (C) 2014 CYBUTEK
// //
// This program is free software: you can redistribute it and/or modify // This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by // it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or // the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version. // (at your option) any later version.
// //
// This program is distributed in the hope that it will be useful, // This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details. // GNU General Public License for more details.
// //
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>. // along with this program. If not, see <http://www.gnu.org/licenses/>.
// //
#region Using Directives #region
using KerbalEngineer.Simulation; using KerbalEngineer.VesselSimulator;
#endregion #endregion
namespace KerbalEngineer.Flight.Readouts.Vessel namespace KerbalEngineer.Flight.Readouts.Vessel
{ {
public class SpecificImpulse : ReadoutModule public class SpecificImpulse : ReadoutModule
{ {
public SpecificImpulse() public SpecificImpulse()
{ {
this.Name = "Specific Impulse"; this.Name = "Specific Impulse";
this.Category = ReadoutCategory.Vessel; this.Category = ReadoutCategory.Vessel;
this.HelpString = string.Empty; this.HelpString = string.Empty;
this.IsDefault = true; this.IsDefault = true;
} }
public override void Update() public override void Update()
{ {
SimManager.RequestUpdate(); SimManager.RequestUpdate();
} }
public override void Draw() public override void Draw()
{ {
this.DrawLine(SimManager.LastStage.isp.ToString("F1") + "s"); this.DrawLine(SimManager.LastStage.isp.ToString("F1") + "s");
} }
public override void Reset() public override void Reset()
{ {
FlightEngineerCore.Instance.AddUpdatable(SimManager.Instance); FlightEngineerCore.Instance.AddUpdatable(SimManager.Instance);
} }
} }
} }
// //
// Kerbal Engineer Redux // Kerbal Engineer Redux
// //
// Copyright (C) 2014 CYBUTEK // Copyright (C) 2014 CYBUTEK
// //
// This program is free software: you can redistribute it and/or modify // This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by // it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or // the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version. // (at your option) any later version.
// //
// This program is distributed in the hope that it will be useful, // This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details. // GNU General Public License for more details.
// //
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>. // along with this program. If not, see <http://www.gnu.org/licenses/>.
// //
#region Using Directives #region Using Directives
using KerbalEngineer.Extensions; using KerbalEngineer.Extensions;
using KerbalEngineer.Simulation; using KerbalEngineer.VesselSimulator;
#endregion #endregion
namespace KerbalEngineer.Flight.Readouts.Vessel namespace KerbalEngineer.Flight.Readouts.Vessel
{ {
public class Thrust : ReadoutModule public class Thrust : ReadoutModule
{ {
public Thrust() public Thrust()
{ {
this.Name = "Thrust"; this.Name = "Thrust";
this.Category = ReadoutCategory.Vessel; this.Category = ReadoutCategory.Vessel;
this.HelpString = string.Empty; this.HelpString = string.Empty;
this.IsDefault = true; this.IsDefault = true;
} }
public override void Update() public override void Update()
{ {
SimManager.RequestUpdate(); SimManager.RequestUpdate();
} }
public override void Draw() public override void Draw()
{ {
this.DrawLine(SimManager.LastStage.actualThrust.ToForce(false) + " / " + SimManager.LastStage.thrust.ToForce()); this.DrawLine(SimManager.LastStage.actualThrust.ToForce(false) + " / " + SimManager.LastStage.thrust.ToForce());
} }
public override void Reset() public override void Reset()
{ {
FlightEngineerCore.Instance.AddUpdatable(SimManager.Instance); FlightEngineerCore.Instance.AddUpdatable(SimManager.Instance);
} }
} }
} }
// //
// Kerbal Engineer Redux // Kerbal Engineer Redux
// //
// Copyright (C) 2014 CYBUTEK // Copyright (C) 2014 CYBUTEK
// //
// This program is free software: you can redistribute it and/or modify // This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by // it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or // the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version. // (at your option) any later version.
// //
// This program is distributed in the hope that it will be useful, // This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details. // GNU General Public License for more details.
// //
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>. // along with this program. If not, see <http://www.gnu.org/licenses/>.
// //
#region Using Directives #region Using Directives
using KerbalEngineer.Simulation; using KerbalEngineer.VesselSimulator;
#endregion #endregion
namespace KerbalEngineer.Flight.Readouts.Vessel namespace KerbalEngineer.Flight.Readouts.Vessel
{ {
public class ThrustToWeight : ReadoutModule public class ThrustToWeight : ReadoutModule
{ {
private string actual = string.Empty; private string actual = string.Empty;
private string total = string.Empty; private string total = string.Empty;
public ThrustToWeight() public ThrustToWeight()
{ {
this.Name = "Thrust to Weight Ratio"; this.Name = "Thrust to Weight Ratio";
this.Category = ReadoutCategory.Vessel; this.Category = ReadoutCategory.Vessel;
this.HelpString = "Shows the vessel's actual and total thrust to weight ratio."; this.HelpString = "Shows the vessel's actual and total thrust to weight ratio.";
this.IsDefault = true; this.IsDefault = true;
} }
public override void Update() public override void Update()
{ {
SimManager.RequestUpdate(); SimManager.RequestUpdate();
} }
public override void Draw() public override void Draw()
{ {
this.actual = (SimManager.LastStage.actualThrust / (SimManager.LastStage.totalMass * FlightGlobals.getGeeForceAtPosition(FlightGlobals.ship_position).magnitude)).ToString("F2"); this.actual = (SimManager.LastStage.actualThrust / (SimManager.LastStage.totalMass * FlightGlobals.getGeeForceAtPosition(FlightGlobals.ship_position).magnitude)).ToString("F2");
this.total = (SimManager.LastStage.thrust / (SimManager.LastStage.totalMass * FlightGlobals.getGeeForceAtPosition(FlightGlobals.ship_position).magnitude)).ToString("F2"); this.total = (SimManager.LastStage.thrust / (SimManager.LastStage.totalMass * FlightGlobals.getGeeForceAtPosition(FlightGlobals.ship_position).magnitude)).ToString("F2");
this.DrawLine("TWR", this.actual + " / " + this.total); this.DrawLine("TWR", this.actual + " / " + this.total);
} }
public override void Reset() public override void Reset()
{ {
FlightEngineerCore.Instance.AddUpdatable(SimManager.Instance); FlightEngineerCore.Instance.AddUpdatable(SimManager.Instance);
} }
} }
} }
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" /> <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup> <PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{39806613-E0B7-46E0-89A6-A569EC538CBB}</ProjectGuid> <ProjectGuid>{39806613-E0B7-46E0-89A6-A569EC538CBB}</ProjectGuid>
<OutputType>Library</OutputType> <OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder> <AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>KerbalEngineer</RootNamespace> <RootNamespace>KerbalEngineer</RootNamespace>
<AssemblyName>KerbalEngineer</AssemblyName> <AssemblyName>KerbalEngineer</AssemblyName>
<TargetFrameworkVersion>v3.5</TargetFrameworkVersion> <TargetFrameworkVersion>v3.5</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment> <FileAlignment>512</FileAlignment>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>false</DebugSymbols> <DebugSymbols>false</DebugSymbols>
<DebugType>none</DebugType> <DebugType>none</DebugType>
<Optimize>false</Optimize> <Optimize>false</Optimize>
<OutputPath>..\Output\KerbalEngineer\</OutputPath> <OutputPath>..\Output\KerbalEngineer\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants> <DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport> <ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel> <WarningLevel>4</WarningLevel>
<UseVSHostingProcess>false</UseVSHostingProcess> <UseVSHostingProcess>false</UseVSHostingProcess>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>none</DebugType> <DebugType>none</DebugType>
<Optimize>true</Optimize> <Optimize>true</Optimize>
<OutputPath>..\Output\KerbalEngineer\</OutputPath> <OutputPath>..\Output\KerbalEngineer\</OutputPath>
<DefineConstants> <DefineConstants>
</DefineConstants> </DefineConstants>
<ErrorReport>prompt</ErrorReport> <ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel> <WarningLevel>4</WarningLevel>
<UseVSHostingProcess>false</UseVSHostingProcess> <UseVSHostingProcess>false</UseVSHostingProcess>
<AllowUnsafeBlocks>false</AllowUnsafeBlocks> <AllowUnsafeBlocks>false</AllowUnsafeBlocks>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Reference Include="Assembly-CSharp"> <Reference Include="Assembly-CSharp">
<HintPath>..\Game\KSP_x64_Data\Managed\Assembly-CSharp.dll</HintPath> <HintPath>..\Game\KSP_x64_Data\Managed\Assembly-CSharp.dll</HintPath>
<Private>False</Private> <Private>False</Private>
</Reference> </Reference>
<Reference Include="Assembly-CSharp-firstpass"> <Reference Include="Assembly-CSharp-firstpass">
<HintPath>..\Game\KSP_x64_Data\Managed\Assembly-CSharp-firstpass.dll</HintPath> <HintPath>..\Game\KSP_x64_Data\Managed\Assembly-CSharp-firstpass.dll</HintPath>
<Private>False</Private> <Private>False</Private>
</Reference> </Reference>
<Reference Include="System"> <Reference Include="System">
<HintPath>..\Game\KSP_Data\Managed\System.dll</HintPath> <HintPath>..\Game\KSP_Data\Managed\System.dll</HintPath>
<Private>False</Private> <Private>False</Private>
</Reference> </Reference>
<Reference Include="System.Xml"> <Reference Include="System.Xml">
<HintPath>..\Game\KSP_Data\Managed\System.Xml.dll</HintPath> <HintPath>..\Game\KSP_Data\Managed\System.Xml.dll</HintPath>
<Private>False</Private> <Private>False</Private>
</Reference> </Reference>
<Reference Include="UnityEngine"> <Reference Include="UnityEngine">
<HintPath>..\Game\KSP_x64_Data\Managed\UnityEngine.dll</HintPath> <HintPath>..\Game\KSP_x64_Data\Managed\UnityEngine.dll</HintPath>
<Private>False</Private> <Private>False</Private>
</Reference> </Reference>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="Editor\BuildAdvanced.cs" /> <Compile Include="Editor\BuildAdvanced.cs" />
<Compile Include="Editor\BuildOverlay.cs" /> <Compile Include="Editor\BuildOverlay.cs" />
<Compile Include="CelestialBodies.cs" /> <Compile Include="CelestialBodies.cs" />
<Compile Include="Editor\BuildToolbar.cs" /> <Compile Include="Editor\BuildToolbar.cs" />
<Compile Include="Logger.cs" /> <Compile Include="Logger.cs" />
<Compile Include="EngineerGlobals.cs" /> <Compile Include="EngineerGlobals.cs" />
<Compile Include="Extensions\DoubleExtensions.cs" /> <Compile Include="Extensions\DoubleExtensions.cs" />
<Compile Include="Extensions\FloatExtensions.cs" /> <Compile Include="Extensions\FloatExtensions.cs" />
<Compile Include="Extensions\PartExtensions.cs" /> <Compile Include="Extensions\PartExtensions.cs" />
<Compile Include="Extensions\PartResourceExtensions.cs" /> <Compile Include="Extensions\PartResourceExtensions.cs" />
<Compile Include="Extensions\RectExtensions.cs" /> <Compile Include="Extensions\RectExtensions.cs" />
<Compile Include="Flight\ActionMenu.cs" /> <Compile Include="Flight\ActionMenu.cs" />
<Compile Include="Flight\DisplayStack.cs" /> <Compile Include="Flight\DisplayStack.cs" />
<Compile Include="Flight\FlightEngineerCore.cs" /> <Compile Include="Flight\FlightEngineerCore.cs" />
<Compile Include="Flight\FlightEngineerModule.cs" /> <Compile Include="Flight\FlightEngineerModule.cs" />
<Compile Include="Flight\IUpdatable.cs" /> <Compile Include="Flight\IUpdatable.cs" />
<Compile Include="Flight\IUpdateRequest.cs" /> <Compile Include="Flight\IUpdateRequest.cs" />
<Compile Include="Flight\Readouts\Orbital\ApoapsisHeight.cs" /> <Compile Include="Flight\Readouts\Orbital\ApoapsisHeight.cs" />
<Compile Include="Flight\Readouts\Orbital\Eccentricity.cs" /> <Compile Include="Flight\Readouts\Orbital\Eccentricity.cs" />
<Compile Include="Flight\Readouts\Orbital\Inclination.cs" /> <Compile Include="Flight\Readouts\Orbital\Inclination.cs" />
<Compile Include="Flight\Readouts\Orbital\LongitudeOfAscendingNode.cs" /> <Compile Include="Flight\Readouts\Orbital\LongitudeOfAscendingNode.cs" />
<Compile Include="Flight\Readouts\Orbital\LongitudeOfPeriapsis.cs" /> <Compile Include="Flight\Readouts\Orbital\LongitudeOfPeriapsis.cs" />
<Compile Include="Flight\Readouts\Orbital\OrbitalPeriod.cs" /> <Compile Include="Flight\Readouts\Orbital\OrbitalPeriod.cs" />
<Compile Include="Flight\Readouts\Orbital\OrbitalSpeed.cs" /> <Compile Include="Flight\Readouts\Orbital\OrbitalSpeed.cs" />
<Compile Include="Flight\Readouts\Orbital\PeriapsisHeight.cs" /> <Compile Include="Flight\Readouts\Orbital\PeriapsisHeight.cs" />
<Compile Include="Flight\Readouts\Orbital\SemiMajorAxis.cs" /> <Compile Include="Flight\Readouts\Orbital\SemiMajorAxis.cs" />
<Compile Include="Flight\Readouts\Orbital\SemiMinorAxis.cs" /> <Compile Include="Flight\Readouts\Orbital\SemiMinorAxis.cs" />
<Compile Include="Flight\Readouts\Orbital\TimeToApoapsis.cs" /> <Compile Include="Flight\Readouts\Orbital\TimeToApoapsis.cs" />
<Compile Include="Flight\Readouts\Orbital\TimeToPeriapsis.cs" /> <Compile Include="Flight\Readouts\Orbital\TimeToPeriapsis.cs" />
<Compile Include="Flight\Readouts\ReadoutCategory.cs" /> <Compile Include="Flight\Readouts\ReadoutCategory.cs" />
<Compile Include="Flight\Readouts\ReadoutLibrary.cs" /> <Compile Include="Flight\Readouts\ReadoutLibrary.cs" />
<Compile Include="Flight\Readouts\ReadoutModule.cs" /> <Compile Include="Flight\Readouts\ReadoutModule.cs" />
<Compile Include="Flight\Readouts\Rendezvous\TimeToPeriapsis.cs" /> <Compile Include="Flight\Readouts\Rendezvous\TimeToPeriapsis.cs" />
<Compile Include="Flight\Readouts\Rendezvous\TimeToApoapsis.cs" /> <Compile Include="Flight\Readouts\Rendezvous\TimeToApoapsis.cs" />
<Compile Include="Flight\Readouts\Rendezvous\PeriapsisHeight.cs" /> <Compile Include="Flight\Readouts\Rendezvous\PeriapsisHeight.cs" />
<Compile Include="Flight\Readouts\Rendezvous\ApoapsisHeight.cs" /> <Compile Include="Flight\Readouts\Rendezvous\ApoapsisHeight.cs" />
<Compile Include="Flight\Readouts\Rendezvous\InterceptAngle.cs" /> <Compile Include="Flight\Readouts\Rendezvous\InterceptAngle.cs" />
<Compile Include="Flight\Readouts\Rendezvous\OrbitalPeriod.cs" /> <Compile Include="Flight\Readouts\Rendezvous\OrbitalPeriod.cs" />
<Compile Include="Flight\Readouts\Rendezvous\Distance.cs" /> <Compile Include="Flight\Readouts\Rendezvous\Distance.cs" />
<Compile Include="Flight\Readouts\Rendezvous\AltitudeSeaLevel.cs" /> <Compile Include="Flight\Readouts\Rendezvous\AltitudeSeaLevel.cs" />
<Compile Include="Flight\Readouts\Rendezvous\AngleToDescendingNode.cs" /> <Compile Include="Flight\Readouts\Rendezvous\AngleToDescendingNode.cs" />
<Compile Include="Flight\Readouts\Rendezvous\AngleToAscendingNode.cs" /> <Compile Include="Flight\Readouts\Rendezvous\AngleToAscendingNode.cs" />
<Compile Include="Flight\Readouts\Rendezvous\PhaseAngle.cs" /> <Compile Include="Flight\Readouts\Rendezvous\PhaseAngle.cs" />
<Compile Include="Flight\Readouts\Rendezvous\RelativeInclination.cs" /> <Compile Include="Flight\Readouts\Rendezvous\RelativeInclination.cs" />
<Compile Include="Flight\Readouts\Rendezvous\RendezvousProcessor.cs" /> <Compile Include="Flight\Readouts\Rendezvous\RendezvousProcessor.cs" />
<Compile Include="Flight\Readouts\Rendezvous\TargetSelector.cs" /> <Compile Include="Flight\Readouts\Rendezvous\TargetSelector.cs" />
<Compile Include="Flight\Readouts\Surface\AltitudeSeaLevel.cs" /> <Compile Include="Flight\Readouts\Surface\AltitudeSeaLevel.cs" />
<Compile Include="Flight\Readouts\Surface\AltitudeTerrain.cs" /> <Compile Include="Flight\Readouts\Surface\AltitudeTerrain.cs" />
<Compile Include="Flight\Readouts\Surface\ImpactLatitude.cs" /> <Compile Include="Flight\Readouts\Surface\ImpactLatitude.cs" />
<Compile Include="Flight\Readouts\Surface\ImpactAltitude.cs" /> <Compile Include="Flight\Readouts\Surface\ImpactAltitude.cs" />
<Compile Include="Flight\Readouts\Surface\ImpactLongitude.cs" /> <Compile Include="Flight\Readouts\Surface\ImpactLongitude.cs" />
<Compile Include="Flight\Readouts\Surface\ImpactTime.cs" /> <Compile Include="Flight\Readouts\Surface\ImpactTime.cs" />
<Compile Include="Flight\Readouts\Surface\AtmosphericProcessor.cs" /> <Compile Include="Flight\Readouts\Surface\AtmosphericProcessor.cs" />
<Compile Include="Flight\Readouts\Surface\AtmosphericEfficiency.cs" /> <Compile Include="Flight\Readouts\Surface\AtmosphericEfficiency.cs" />
<Compile Include="Flight\Readouts\Surface\GeeForce.cs" /> <Compile Include="Flight\Readouts\Surface\GeeForce.cs" />
<Compile Include="Flight\Readouts\Surface\HorizontalSpeed.cs" /> <Compile Include="Flight\Readouts\Surface\HorizontalSpeed.cs" />
<Compile Include="Flight\Readouts\Surface\ImpactProcessor.cs" /> <Compile Include="Flight\Readouts\Surface\ImpactProcessor.cs" />
<Compile Include="Flight\Readouts\Surface\Latitude.cs" /> <Compile Include="Flight\Readouts\Surface\Latitude.cs" />
<Compile Include="Flight\Readouts\Surface\Longitude.cs" /> <Compile Include="Flight\Readouts\Surface\Longitude.cs" />
<Compile Include="Flight\Readouts\Surface\TerminalVelocity.cs" /> <Compile Include="Flight\Readouts\Surface\TerminalVelocity.cs" />
<Compile Include="Flight\Readouts\Surface\VerticalSpeed.cs" /> <Compile Include="Flight\Readouts\Surface\VerticalSpeed.cs" />
<Compile Include="Flight\Readouts\Vessel\DeltaVStaged.cs" /> <Compile Include="Flight\Readouts\Vessel\DeltaVStaged.cs" />
<Compile Include="Flight\Readouts\Vessel\DeltaVTotal.cs" /> <Compile Include="Flight\Readouts\Vessel\DeltaVTotal.cs" />
<Compile Include="Flight\Readouts\Vessel\Mass.cs" /> <Compile Include="Flight\Readouts\Vessel\Mass.cs" />
<Compile Include="Flight\Readouts\Vessel\Thrust.cs" /> <Compile Include="Flight\Readouts\Vessel\Thrust.cs" />
<Compile Include="Flight\Readouts\Vessel\SpecificImpulse.cs" /> <Compile Include="Flight\Readouts\Vessel\SpecificImpulse.cs" />
<Compile Include="Flight\Readouts\Vessel\ThrustToWeight.cs" /> <Compile Include="Flight\Readouts\Vessel\ThrustToWeight.cs" />
<Compile Include="Flight\Sections\SectionEditor.cs" /> <Compile Include="Flight\Sections\SectionEditor.cs" />
<Compile Include="Flight\Sections\SectionLibrary.cs" /> <Compile Include="Flight\Sections\SectionLibrary.cs" />
<Compile Include="Flight\Sections\SectionModule.cs" /> <Compile Include="Flight\Sections\SectionModule.cs" />
<Compile Include="Flight\Sections\SectionWindow.cs" /> <Compile Include="Flight\Sections\SectionWindow.cs" />
<Compile Include="LogMsg.cs" /> <Compile Include="LogMsg.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Settings\SettingHandler.cs" /> <Compile Include="Settings\SettingHandler.cs" />
<Compile Include="Settings\SettingItem.cs" /> <Compile Include="Settings\SettingItem.cs" />
<Compile Include="Simulation\AttachNodeSim.cs" />  
<Compile Include="Simulation\EngineSim.cs" />  
<Compile Include="Simulation\PartSim.cs" />  
<Compile Include="Simulation\ResourceContainer.cs" />  
<Compile Include="Simulation\SimManager.cs" />  
<Compile Include="Simulation\Simulation.cs" />  
<Compile Include="Simulation\Stage.cs" />  
<Compile Include="TapeDriveAnimator.cs" /> <Compile Include="TapeDriveAnimator.cs" />
  <Compile Include="VesselSimulator\AttachNodeSim.cs" />
  <Compile Include="VesselSimulator\EngineSim.cs" />
  <Compile Include="VesselSimulator\PartSim.cs" />
  <Compile Include="VesselSimulator\ResourceContainer.cs" />
  <Compile Include="VesselSimulator\SimManager.cs" />
  <Compile Include="VesselSimulator\Simulation.cs" />
  <Compile Include="VesselSimulator\Stage.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup /> <ItemGroup />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<PropertyGroup> <PropertyGroup>
<PostBuildEvent>xcopy "$(SolutionDir)Output\*" "$(SolutionDir)Game\GameData\*" /E /Y</PostBuildEvent> <PostBuildEvent>xcopy "$(SolutionDir)Output\*" "$(SolutionDir)Game\GameData\*" /E /Y</PostBuildEvent>
</PropertyGroup> </PropertyGroup>
<!-- To modify your build process, add your task inside one of the targets below and uncomment it. <!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets. Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild"> <Target Name="BeforeBuild">
</Target> </Target>
<Target Name="AfterBuild"> <Target Name="AfterBuild">
</Target> </Target>
--> -->
</Project> </Project>
using System;  
using System.Text;  
 
namespace KerbalEngineer.Simulation  
{  
class AttachNodeSim  
{  
public PartSim attachedPartSim;  
public AttachNode.NodeType nodeType;  
public String id;  
 
public AttachNodeSim(PartSim partSim, String newId, AttachNode.NodeType newNodeType)  
{  
this.attachedPartSim = partSim;  
this.nodeType = newNodeType;  
this.id = newId;  
}  
 
#if LOG || true  
public void DumpToBuffer(StringBuilder buffer)  
{  
if (this.attachedPartSim == null)  
{  
buffer.Append("<staged>:<n>");  
}  
else  
{  
buffer.Append(this.attachedPartSim.name);  
buffer.Append(":");  
buffer.Append(this.attachedPartSim.partId);  
}  
buffer.Append("#");  
buffer.Append(this.nodeType);  
buffer.Append(":");  
buffer.Append(this.id);  
}  
#endif  
}  
}  
 
// Kerbal Engineer Redux  
// Author: CYBUTEK  
// License: Attribution-NonCommercial-ShareAlike 3.0 Unported  
 
using System;  
using System.Collections.Generic;  
using System.Linq;  
 
using UnityEngine;  
 
namespace KerbalEngineer.Simulation  
{  
public class EngineSim  
{  
ResourceContainer resourceConsumptions = new ResourceContainer();  
 
public PartSim partSim;  
 
public double thrust = 0;  
public double actualThrust = 0;  
public double isp = 0;  
 
// Add thrust vector to account for directional losses  
//public Vector3d thrustVec;  
 
public EngineSim(PartSim theEngine, double atmosphere,  
float maxThrust,  
float thrustPercentage,  
float requestedThrust,  
float realIsp,  
FloatCurve atmosphereCurve,  
bool throttleLocked,  
List<Propellant> propellants,  
bool correctThrust)  
{  
//MonoBehaviour.print("Create EngineSim for " + theEngine.name);  
//MonoBehaviour.print("maxThrust = " + maxThrust);  
//MonoBehaviour.print("thrustPercentage = " + thrustPercentage);  
//MonoBehaviour.print("requestedThrust = " + requestedThrust);  
 
this.partSim = theEngine;  
 
this.thrust = maxThrust * (thrustPercentage / 100f);  
//MonoBehaviour.print("thrust = " + thrust);  
 
double flowRate = 0d;  
if (this.partSim.hasVessel)  
{  
//MonoBehaviour.print("hasVessel is true");  
this.actualThrust = requestedThrust;  
this.isp = atmosphereCurve.Evaluate((float)this.partSim.part.staticPressureAtm);  
 
if (correctThrust && realIsp == 0)  
{  
this.thrust = this.thrust * this.isp / atmosphereCurve.Evaluate(0);  
//MonoBehaviour.print("corrected thrust = " + thrust);  
}  
 
if (throttleLocked)  
{  
//MonoBehaviour.print("throttleLocked is true");  
flowRate = this.thrust / (this.isp * 9.81d);  
}  
else  
{  
if (this.partSim.isLanded)  
{  
//MonoBehaviour.print("partSim.isLanded is true, mainThrottle = " + FlightInputHandler.state.mainThrottle);  
flowRate = Math.Max(0.000001d, this.thrust * FlightInputHandler.state.mainThrottle) / (this.isp * 9.81d);  
}  
else  
{  
if (requestedThrust > 0)  
{  
//MonoBehaviour.print("requestedThrust > 0");  
flowRate = requestedThrust / (this.isp * 9.81d);  
}  
else  
{  
//MonoBehaviour.print("requestedThrust <= 0");  
flowRate = this.thrust / (this.isp * 9.81d);  
}  
}  
}  
}  
else  
{  
//MonoBehaviour.print("hasVessel is false");  
this.isp = atmosphereCurve.Evaluate((float)atmosphere);  
if (correctThrust)  
{  
this.thrust = this.thrust * this.isp / atmosphereCurve.Evaluate(0);  
//MonoBehaviour.print("corrected thrust = " + thrust);  
}  
flowRate = this.thrust / (this.isp * 9.81d);  
}  
#if LOG  
StringBuilder buffer = new StringBuilder(1024);  
buffer.AppendFormat("flowRate = {0:g6}\n", flowRate);  
#endif  
float flowMass = 0f;  
 
foreach (Propellant propellant in propellants)  
flowMass += propellant.ratio * ResourceContainer.GetResourceDensity(propellant.id);  
#if LOG  
buffer.AppendFormat("flowMass = {0:g6}\n", flowMass);  
#endif  
foreach (Propellant propellant in propellants)  
{  
if (propellant.name == "ElectricCharge" || propellant.name == "IntakeAir")  
continue;  
 
double consumptionRate = propellant.ratio * flowRate / flowMass;  
#if LOG  
buffer.AppendFormat("Add consumption({0}, {1}:{2:d}) = {3:g6}\n", ResourceContainer.GetResourceName(propellant.id), theEngine.name, theEngine.partId, consumptionRate);  
#endif  
this.resourceConsumptions.Add(propellant.id, consumptionRate);  
}  
#if LOG  
MonoBehaviour.print(buffer);  
#endif  
}  
 
 
public bool SetResourceDrains(List<PartSim> allParts, List<PartSim> allFuelLines, HashSet<PartSim> drainingParts)  
{  
// A dictionary to hold a set of parts for each resource  
Dictionary<int, HashSet<PartSim>> sourcePartSets = new Dictionary<int, HashSet<PartSim>>();  
 
foreach (int type in this.resourceConsumptions.Types)  
{  
HashSet<PartSim> sourcePartSet = null;  
switch (ResourceContainer.GetResourceFlowMode(type))  
{  
case ResourceFlowMode.NO_FLOW:  
if (this.partSim.resources[type] > SimManager.RESOURCE_MIN)  
{  
sourcePartSet = new HashSet<PartSim>();  
//MonoBehaviour.print("SetResourceDrains(" + name + ":" + partId + ") setting sources to just this");  
sourcePartSet.Add(this.partSim);  
}  
break;  
 
case ResourceFlowMode.ALL_VESSEL:  
foreach (PartSim aPartSim in allParts)  
{  
if (aPartSim.resources[type] > SimManager.RESOURCE_MIN)  
{  
if (sourcePartSet == null)  
sourcePartSet = new HashSet<PartSim>();  
 
sourcePartSet.Add(aPartSim);  
}  
}  
break;  
 
case ResourceFlowMode.STAGE_PRIORITY_FLOW:  
{  
Dictionary<int, HashSet<PartSim>> stagePartSets = new Dictionary<int, HashSet<PartSim>>();  
int maxStage = -1;  
foreach (PartSim aPartSim in allParts)  
{  
if (aPartSim.resources[type] > SimManager.RESOURCE_MIN)  
{  
//int stage = aPartSim.decoupledInStage; // Use the number of the stage the tank is decoupled in  
int stage = aPartSim.DecouplerCount(); // Use the count of decouplers between tank and root  
if (stage > maxStage)  
maxStage = stage;  
if (stagePartSets.ContainsKey(stage))  
{  
sourcePartSet = stagePartSets[stage];  
}  
else  
{  
sourcePartSet = new HashSet<PartSim>();  
stagePartSets.Add(stage, sourcePartSet);  
}  
 
sourcePartSet.Add(aPartSim);  
}  
}  
 
while (maxStage >= 0)  
{  
if (stagePartSets.ContainsKey(maxStage))  
{  
if (stagePartSets[maxStage].Count() > 0)  
{  
sourcePartSet = stagePartSets[maxStage];  
break;  
}  
}  
maxStage--;  
}  
}  
break;  
 
case ResourceFlowMode.STACK_PRIORITY_SEARCH:  
HashSet<PartSim> visited = new HashSet<PartSim>();  
#if LOG  
LogMsg log = new LogMsg();  
log.buf.AppendLine("Find " + ResourceContainer.GetResourceName(type) + " sources for " + partSim.name + ":" + partSim.partId);  
#else  
LogMsg log = null;  
#endif  
sourcePartSet = this.partSim.GetSourceSet(type, allParts, allFuelLines, visited, log, "");  
#if LOG  
MonoBehaviour.print(log.buf);  
#endif  
break;  
 
default:  
MonoBehaviour.print("SetResourceDrains(" + this.partSim.name + ":" + this.partSim.partId + ") Unexpected flow type for " + ResourceContainer.GetResourceName(type) + ")");  
break;  
}  
 
if (sourcePartSet != null && sourcePartSet.Count > 0)  
{  
sourcePartSets[type] = sourcePartSet;  
#if LOG  
LogMsg log = new LogMsg();  
log.buf.AppendLine("Source parts for " + ResourceContainer.GetResourceName(type) + ":");  
foreach (PartSim partSim in sourcePartSet)  
{  
log.buf.AppendLine(partSim.name + ":" + partSim.partId);  
}  
MonoBehaviour.print(log.buf);  
#endif  
}  
}  
 
// If we don't have sources for all the needed resources then return false without setting up any drains  
foreach (int type in this.resourceConsumptions.Types)  
{  
if (!sourcePartSets.ContainsKey(type))  
{  
#if LOG  
MonoBehaviour.print("No source of " + ResourceContainer.GetResourceName(type));  
#endif  
return false;  
}  
}  
 
// Now we set the drains on the members of the sets and update the draining parts set  
foreach (int type in this.resourceConsumptions.Types)  
{  
HashSet<PartSim> sourcePartSet = sourcePartSets[type];  
// Loop through the members of the set  
double amount = this.resourceConsumptions[type] / sourcePartSet.Count;  
foreach (PartSim partSim in sourcePartSet)  
{  
#if LOG  
MonoBehaviour.print("Adding drain of " + amount + " " + ResourceContainer.GetResourceName(type) + " to " + partSim.name + ":" + partSim.partId);  
#endif  
partSim.resourceDrains.Add(type, amount);  
drainingParts.Add(partSim);  
}  
}  
 
return true;  
}  
 
 
public ResourceContainer ResourceConsumptions  
{  
get  
{  
return this.resourceConsumptions;  
}  
}  
 
#if LOG  
public void DumpEngineToBuffer(StringBuilder buffer, String prefix)  
{  
buffer.Append(prefix);  
buffer.AppendFormat("[thrust = {0:g6}, actual = {1:g6}, isp = {2:g6}\n", thrust, actualThrust, isp);  
}  
#endif  
}  
}  
 
// Kerbal Engineer Redux  
// Author: CYBUTEK  
// License: Attribution-NonCommercial-ShareAlike 3.0 Unported  
//  
// This class has taken a lot of inspiration from r4m0n's MuMech FuelFlowSimulator. Although extremely  
// similar to the code used within MechJeb, it is a clean re-write. The similarities are a testiment  
// to how well the MuMech code works and the robustness of the simulation algorithem used.  
 
using System;  
using System.Collections.Generic;  
using System.Linq;  
using System.Text;  
 
using KerbalEngineer.Extensions;  
 
using UnityEngine;  
 
namespace KerbalEngineer.Simulation  
{  
public class PartSim  
{  
public ResourceContainer resources = new ResourceContainer();  
public ResourceContainer resourceDrains = new ResourceContainer();  
ResourceContainer resourceFlowStates = new ResourceContainer();  
//ResourceContainer resourceConsumptions = new ResourceContainer();  
 
//Dictionary<int, bool> resourceCanSupply = new Dictionary<int, bool>();  
 
List<AttachNodeSim> attachNodes = new List<AttachNodeSim>();  
 
public Part part; // This is only set while the data structures are being initialised  
public int partId = 0;  
public String name;  
public PartSim parent;  
public PartSim fuelLineTarget;  
public bool hasVessel;  
public bool isLanded;  
public bool isDecoupler;  
public int decoupledInStage;  
public int inverseStage;  
public int cost;  
double baseMass = 0d;  
double startMass = 0d;  
public String noCrossFeedNodeKey;  
public bool fuelCrossFeed;  
public bool isEngine;  
public bool isFuelLine;  
public bool isFuelTank;  
public bool isSepratron;  
public bool hasMultiModeEngine;  
public bool hasModuleEnginesFX;  
public bool hasModuleEngines;  
public bool isNoPhysics;  
public bool localCorrectThrust;  
 
public PartSim(Part thePart, int id, double atmosphere)  
{  
this.part = thePart;  
this.partId = id;  
this.name = this.part.partInfo.name;  
#if LOG  
MonoBehaviour.print("Create PartSim for " + name);  
#endif  
this.parent = null;  
this.fuelCrossFeed = this.part.fuelCrossFeed;  
this.noCrossFeedNodeKey = this.part.NoCrossFeedNodeKey;  
this.decoupledInStage = this.DecoupledInStage(this.part);  
this.isFuelLine = this.part is FuelLine;  
this.isFuelTank = this.part is FuelTank;  
this.isSepratron = this.IsSepratron();  
this.inverseStage = this.part.inverseStage;  
//MonoBehaviour.print("inverseStage = " + inverseStage);  
 
//this.cost = this.part.partInfo.cost;  
 
// Work out if the part should have no physical significance  
this.isNoPhysics = this.part.HasModule<ModuleLandingGear>() ||  
this.part.HasModule<LaunchClamp>() ||  
this.part.physicalSignificance == Part.PhysicalSignificance.NONE ||  
this.part.PhysicsSignificance == 1;  
 
if (!this.isNoPhysics)  
this.baseMass = this.part.mass;  
#if LOG  
MonoBehaviour.print((isNoPhysics ? "Ignoring" : "Using") + " part.mass of " + part.mass);  
#endif  
foreach (PartResource resource in this.part.Resources)  
{  
// Make sure it isn't NaN as this messes up the part mass and hence most of the values  
// This can happen if a resource capacity is 0 and tweakable  
if (!Double.IsNaN(resource.amount))  
{  
#if LOG  
MonoBehaviour.print(resource.resourceName + " = " + resource.amount);  
#endif  
this.resources.Add(resource.info.id, resource.amount);  
this.resourceFlowStates.Add(resource.info.id, resource.flowState ? 1 : 0);  
}  
else  
{  
MonoBehaviour.print(resource.resourceName + " is NaN. Skipping.");  
}  
}  
 
this.startMass = this.GetMass();  
 
this.hasVessel = (this.part.vessel != null);  
this.isLanded = this.hasVessel && this.part.vessel.Landed;  
 
this.hasMultiModeEngine = this.part.HasModule<MultiModeEngine>();  
this.hasModuleEnginesFX = this.part.HasModule<ModuleEnginesFX>();  
this.hasModuleEngines = this.part.HasModule<ModuleEngines>();  
 
this.isEngine = this.hasMultiModeEngine || this.hasModuleEnginesFX || this.hasModuleEngines;  
#if LOG  
MonoBehaviour.print("Created " + name + ". Decoupled in stage " + decoupledInStage);  
#endif  
}  
 
public void CreateEngineSims(List<EngineSim> allEngines, double atmosphere)  
{  
bool correctThrust = SimManager.DoesEngineUseCorrectedThrust(this.part);  
//MonoBehaviour.print("Engine " + name + " correctThrust = " + correctThrust);  
#if LOG  
LogMsg log = new LogMsg();  
log.buf.AppendLine("CreateEngineSims for " + name);  
 
foreach (PartModule partMod in part.Modules)  
{  
log.buf.AppendLine("Module: " + partMod.moduleName);  
}  
 
log.buf.AppendLine("correctThrust = " + correctThrust);  
#endif  
 
if (this.hasMultiModeEngine)  
{  
// A multi-mode engine has multiple ModuleEnginesFX but only one is active at any point  
// The mode of the engine is the engineID of the ModuleEnginesFX that is active  
string mode = this.part.GetModule<MultiModeEngine>().mode;  
 
foreach (ModuleEnginesFX engine in this.part.GetModules<ModuleEnginesFX>())  
{  
if (engine.engineID == mode)  
{  
EngineSim engineSim = new EngineSim(this, atmosphere,  
engine.maxThrust,  
engine.thrustPercentage,  
engine.requestedThrust,  
engine.realIsp,  
engine.atmosphereCurve,  
engine.throttleLocked,  
engine.propellants,  
correctThrust);  
allEngines.Add(engineSim);  
}  
}  
}  
else  
{  
if (this.hasModuleEnginesFX)  
{  
foreach (ModuleEnginesFX engine in this.part.GetModules<ModuleEnginesFX>())  
{  
EngineSim engineSim = new EngineSim(this, atmosphere,  
engine.maxThrust,  
engine.thrustPercentage,  
engine.requestedThrust,  
engine.realIsp,  
engine.atmosphereCurve,  
engine.throttleLocked,  
engine.propellants,  
correctThrust);  
allEngines.Add(engineSim);  
}  
}  
 
if (this.hasModuleEngines)  
{  
foreach (ModuleEngines engine in this.part.GetModules<ModuleEngines>())  
{  
EngineSim engineSim = new EngineSim(this, atmosphere,  
engine.maxThrust,  
engine.thrustPercentage,  
engine.requestedThrust,  
engine.realIsp,  
engine.atmosphereCurve,  
engine.throttleLocked,  
engine.propellants,  
correctThrust);  
allEngines.Add(engineSim);  
}  
}  
}  
#if LOG  
log.Flush();  
#endif  
}  
 
 
public void SetupAttachNodes(Dictionary<Part, PartSim> partSimLookup)  
{  
#if LOG  
LogMsg log = new LogMsg();  
log.buf.AppendLine("SetupAttachNodes for " + name + ":" + partId + "");  
#endif  
this.attachNodes.Clear();  
foreach (AttachNode attachNode in this.part.attachNodes)  
{  
#if LOG  
log.buf.AppendLine("AttachNode " + attachNode.id + " = " + (attachNode.attachedPart != null ? attachNode.attachedPart.partInfo.name : "null"));  
#endif  
if (attachNode.attachedPart != null && attachNode.id != "Strut")  
{  
PartSim attachedSim;  
if (partSimLookup.TryGetValue(attachNode.attachedPart, out attachedSim))  
{  
#if LOG  
log.buf.AppendLine("Adding attached node " + attachedSim.name + ":" + attachedSim.partId + "");  
#endif  
this.attachNodes.Add(new AttachNodeSim(attachedSim, attachNode.id, attachNode.nodeType));  
}  
else  
{  
#if LOG  
log.buf.AppendLine("No PartSim for attached part (" + attachNode.attachedPart.partInfo.name + ")");  
#endif  
}  
}  
}  
 
if (this.isFuelLine)  
{  
if ((this.part as FuelLine).target != null)  
{  
PartSim targetSim;  
if (partSimLookup.TryGetValue((this.part as FuelLine).target, out targetSim))  
{  
#if LOG  
log.buf.AppendLine("Fuel line target is " + targetSim.name + ":" + targetSim.partId);  
#endif  
this.fuelLineTarget = targetSim;  
}  
else  
{  
#if LOG  
log.buf.AppendLine("No PartSim for fuel line target (" + part.partInfo.name + ")");  
#endif  
this.fuelLineTarget = null;  
}  
 
}  
else  
{  
#if LOG  
log.buf.AppendLine("Fuel line target is null");  
#endif  
this.fuelLineTarget = null;  
}  
}  
 
if (this.part.parent != null)  
{  
this.parent = null;  
if (partSimLookup.TryGetValue(this.part.parent, out this.parent))  
{  
#if LOG  
log.buf.AppendLine("Parent part is " + parent.name + ":" + parent.partId);  
#endif  
}  
else  
{  
#if LOG  
log.buf.AppendLine("No PartSim for parent part (" + part.parent.partInfo.name + ")");  
#endif  
}  
}  
#if LOG  
log.Flush();  
#endif  
}  
 
private int DecoupledInStage(Part thePart, int stage = -1)  
{  
if (this.IsDecoupler(thePart))  
{  
if (thePart.inverseStage > stage)  
{  
stage = thePart.inverseStage;  
}  
}  
 
if (thePart.parent != null)  
{  
stage = this.DecoupledInStage(thePart.parent, stage);  
}  
 
return stage;  
}  
 
private bool IsDecoupler(Part thePart)  
{  
return thePart.HasModule<ModuleDecouple>() ||  
thePart.HasModule<ModuleAnchoredDecoupler>();  
}  
 
private bool IsActiveDecoupler(Part thePart)  
{  
return thePart.FindModulesImplementing<ModuleDecouple>().Any(mod => !mod.isDecoupled) ||  
thePart.FindModulesImplementing<ModuleAnchoredDecoupler>().Any(mod => !mod.isDecoupled);  
}  
 
private bool IsSepratron()  
{  
if (!this.part.ActivatesEvenIfDisconnected)  
return false;  
 
if (this.part is SolidRocket)  
return true;  
 
var modList = this.part.Modules.OfType<ModuleEngines>();  
if (modList.Count() == 0)  
return false;  
 
if (modList.First().throttleLocked == true)  
return true;  
 
return false;  
}  
 
public void ReleasePart()  
{  
this.part = null;  
}  
 
 
// All functions below this point must not rely on the part member (it may be null)  
//  
 
public HashSet<PartSim> GetSourceSet(int type, List<PartSim> allParts, List<PartSim> allFuelLines, HashSet<PartSim> visited, LogMsg log, String indent)  
{  
#if LOG  
log.buf.AppendLine(indent + "GetSourceSet(" + ResourceContainer.GetResourceName(type) + ") for " + name + ":" + partId);  
indent += " ";  
#endif  
HashSet<PartSim> allSources = new HashSet<PartSim>();  
HashSet<PartSim> partSources = null;  
 
// Rule 1: Each part can be only visited once, If it is visited for second time in particular search it returns empty list.  
if (visited.Contains(this))  
{  
#if LOG  
log.buf.AppendLine(indent + "Returning empty set, already visited (" + name + ":" + partId + ")");  
#endif  
return allSources;  
}  
 
#if LOG  
log.buf.AppendLine("Adding this to visited");  
#endif  
visited.Add(this);  
 
// Rule 2: Part performs scan on start of every fuel pipe ending in it. This scan is done in order in which pipes were installed. Then it makes an union of fuel tank sets each pipe scan returned. If the resulting list is not empty, it is returned as result.  
//MonoBehaviour.print("foreach fuel line");  
 
foreach (PartSim partSim in allFuelLines)  
{  
if (partSim.fuelLineTarget == this)  
{  
#if LOG  
log.buf.AppendLine(indent + "Adding fuel line as source (" + partSim.name + ":" + partSim.partId + ")");  
#endif  
partSources = partSim.GetSourceSet(type, allParts, allFuelLines, visited, log, indent);  
if (partSources.Count > 0)  
{  
allSources.UnionWith(partSources);  
partSources.Clear();  
}  
}  
}  
 
if (allSources.Count > 0)  
{  
#if LOG  
log.buf.AppendLine(indent + "Returning " + allSources.Count + " fuel line sources (" + name + ":" + partId + ")");  
#endif  
return allSources;  
}  
 
// Rule 3: If the part is not crossfeed capable, it returns empty list.  
//MonoBehaviour.print("Test crossfeed");  
if (!this.fuelCrossFeed)  
{  
#if LOG  
log.buf.AppendLine(indent + "Returning empty set, no cross feed (" + name + ":" + partId + ")");  
#endif  
return allSources;  
}  
 
// Rule 4: Part performs scan on each of its axially mounted neighbors.  
// Couplers (bicoupler, tricoupler, ...) are an exception, they only scan one attach point on the single attachment side, skip the points on the side where multiple points are. [Experiment]  
// Again, the part creates union of scan lists from each of its neighbor and if it is not empty, returns this list.  
// The order in which mount points of a part are scanned appears to be fixed and defined by the part specification file. [Experiment]  
//MonoBehaviour.print("foreach attach node");  
foreach (AttachNodeSim attachSim in this.attachNodes)  
{  
if (attachSim.attachedPartSim != null)  
{  
if (attachSim.nodeType == AttachNode.NodeType.Stack &&  
(attachSim.attachedPartSim.fuelCrossFeed || attachSim.attachedPartSim.isFuelTank) &&  
!(this.noCrossFeedNodeKey != null && this.noCrossFeedNodeKey.Length > 0 && attachSim.id.Contains(this.noCrossFeedNodeKey)))  
{  
#if LOG  
log.buf.AppendLine(indent + "Adding attached part as source (" + attachSim.attachedPartSim.name + ":" + attachSim.attachedPartSim.partId + ")");  
#endif  
partSources = attachSim.attachedPartSim.GetSourceSet(type, allParts, allFuelLines, visited, log, indent);  
if (partSources.Count > 0)  
{  
allSources.UnionWith(partSources);  
partSources.Clear();  
}  
}  
}  
}  
 
if (allSources.Count > 0)  
{  
#if LOG  
log.buf.AppendLine(indent + "Returning " + allSources.Count + " attached sources (" + name + ":" + partId + ")");  
#endif  
return allSources;  
}  
 
// Rule 5: If the part is fuel container for searched type of fuel (i.e. it has capability to contain that type of fuel and the fuel type was not disabled [Experiment]) and it contains fuel, it returns itself.  
// Rule 6: If the part is fuel container for searched type of fuel (i.e. it has capability to contain that type of fuel and the fuel type was not disabled) but it does not contain the requested fuel, it returns empty list. [Experiment]  
if (this.resources.HasType(type) && this.resourceFlowStates[type] != 0)  
{  
if (this.resources[type] > SimManager.RESOURCE_MIN)  
{  
allSources.Add(this);  
#if LOG  
log.buf.AppendLine(indent + "Returning enabled tank as only source (" + name + ":" + partId + ")");  
#endif  
}  
else  
{  
#if LOG  
log.buf.AppendLine(indent + "Returning empty set, enabled tank is empty (" + name + ":" + partId + ")");  
#endif  
}  
 
return allSources;  
}  
 
// Rule 7: If the part is radially attached to another part and it is child of that part in the ship's tree structure, it scans its parent and returns whatever the parent scan returned. [Experiment] [Experiment]  
if (this.parent != null)  
{  
allSources = this.parent.GetSourceSet(type, allParts, allFuelLines, visited, log, indent);  
if (allSources.Count > 0)  
{  
#if LOG  
log.buf.AppendLine(indent + "Returning " + allSources.Count + " parent sources (" + name + ":" + partId + ")");  
#endif  
return allSources;  
}  
}  
 
// Rule 8: If all preceding rules failed, part returns empty list.  
#if LOG  
log.buf.AppendLine(indent + "Returning empty set, no sources found (" + name + ":" + partId + ")");  
#endif  
return allSources;  
}  
 
 
public void RemoveAttachedParts(HashSet<PartSim> partSims)  
{  
// Loop through the attached parts  
foreach (AttachNodeSim attachSim in this.attachNodes)  
{  
// If the part is in the set then "remove" it by clearing the PartSim reference  
if (partSims.Contains(attachSim.attachedPartSim))  
attachSim.attachedPartSim = null;  
}  
}  
 
 
public void DrainResources(double time)  
{  
//MonoBehaviour.print("DrainResources(" + name + ":" + partId + ", " + time + ")");  
foreach (int type in this.resourceDrains.Types)  
{  
//MonoBehaviour.print("draining " + (time * resourceDrains[type]) + " " + ResourceContainer.GetResourceName(type));  
this.resources.Add(type, -time * this.resourceDrains[type]);  
//MonoBehaviour.print(ResourceContainer.GetResourceName(type) + " left = " + resources[type]);  
}  
}  
 
public double TimeToDrainResource()  
{  
//MonoBehaviour.print("TimeToDrainResource(" + name + ":" + partId + ")");  
double time = double.MaxValue;  
 
foreach (int type in this.resourceDrains.Types)  
{  
if (this.resourceDrains[type] > 0)  
{  
time = Math.Min(time, this.resources[type] / this.resourceDrains[type]);  
//MonoBehaviour.print("type = " + ResourceContainer.GetResourceName(type) + " amount = " + resources[type] + " rate = " + resourceDrains[type] + " time = " + time);  
}  
}  
 
//if (time < double.MaxValue)  
// MonoBehaviour.print("TimeToDrainResource(" + name + ":" + partId + ") = " + time);  
return time;  
}  
 
public int DecouplerCount()  
{  
int count = 0;  
PartSim partSim = this;  
while (partSim != null)  
{  
if (partSim.isDecoupler)  
count++;  
 
partSim = partSim.parent;  
}  
return count;  
}  
 
public double GetStartMass()  
{  
return this.startMass;  
}  
 
public double GetMass()  
{  
double mass = this.baseMass;  
 
foreach (int type in this.resources.Types)  
mass += this.resources.GetResourceMass(type);  
 
return mass;  
}  
 
public ResourceContainer Resources  
{  
get  
{  
return this.resources;  
}  
}  
#if false  
public ResourceContainer ResourceConsumptions  
{  
get  
{  
return resourceConsumptions;  
}  
}  
#endif  
public ResourceContainer ResourceDrains  
{  
get  
{  
return this.resourceDrains;  
}  
}  
 
#if LOG || true  
public String DumpPartAndParentsToBuffer(StringBuilder buffer, String prefix)  
{  
if (this.parent != null)  
{  
prefix = this.parent.DumpPartAndParentsToBuffer(buffer, prefix) + " ";  
}  
 
this.DumpPartToBuffer(buffer, prefix);  
 
return prefix;  
}  
 
public void DumpPartToBuffer(StringBuilder buffer, String prefix, List<PartSim> allParts = null)  
{  
buffer.Append(prefix);  
buffer.Append(this.name);  
buffer.AppendFormat(":[id = {0:d}, decouple = {1:d}, invstage = {2:d}", this.partId, this.decoupledInStage, this.inverseStage);  
 
buffer.AppendFormat(", fuelCF = {0}", this.fuelCrossFeed);  
buffer.AppendFormat(", noCFNKey = '{0}'", this.noCrossFeedNodeKey);  
 
if (this.isFuelLine)  
buffer.AppendFormat(", fuelLineTarget = {0:d}", this.fuelLineTarget == null ? -1 : this.fuelLineTarget.partId);  
 
buffer.AppendFormat(", isSep = {0}", this.isSepratron);  
 
foreach (int type in this.resources.Types)  
buffer.AppendFormat(", {0} = {1:g6}", ResourceContainer.GetResourceName(type), this.resources[type]);  
 
if (this.attachNodes.Count > 0)  
{  
buffer.Append(", attached = <");  
this.attachNodes[0].DumpToBuffer(buffer);  
for (int i = 1; i < this.attachNodes.Count; i++)  
{  
buffer.Append(", ");  
this.attachNodes[i].DumpToBuffer(buffer);  
}  
buffer.Append(">");  
}  
 
// Add more info here  
 
buffer.Append("]\n");  
 
if (allParts != null)  
{  
String newPrefix = prefix + " ";  
foreach (PartSim partSim in allParts)  
{  
if (partSim.parent == this)  
partSim.DumpPartToBuffer(buffer, newPrefix, allParts);  
}  
}  
}  
#endif  
}  
}  
 
// Kerbal Engineer Redux  
// Author: CYBUTEK  
// License: Attribution-NonCommercial-ShareAlike 3.0 Unported  
 
using System.Collections;  
using System.Collections.Generic;  
 
namespace KerbalEngineer.Simulation  
{  
public class ResourceContainer  
{  
Hashtable resources = new Hashtable();  
 
public double this[int type]  
{  
get  
{  
if (this.resources.ContainsKey(type))  
return (double)this.resources[type];  
 
return 0d;  
}  
set  
{  
if (this.resources.ContainsKey(type))  
this.resources[type] = value;  
else  
this.resources.Add(type, value);  
}  
}  
 
public bool HasType(int type)  
{  
return this.resources.ContainsKey(type);  
}  
 
public List<int> Types  
{  
get  
{  
List<int> types = new List<int>();  
 
foreach (int key in this.resources.Keys)  
types.Add(key);  
 
return types;  
}  
}  
 
public double Mass  
{  
get  
{  
double mass = 0d;  
 
foreach (double resource in this.resources.Values)  
mass += resource;  
 
return mass;  
}  
}  
 
public bool Empty  
{  
get  
{  
foreach (int type in this.resources.Keys)  
{  
if ((double)this.resources[type] > SimManager.RESOURCE_MIN)  
return false;  
}  
 
return true;  
}  
}  
 
public bool EmptyOf(HashSet<int> types)  
{  
foreach (int type in types)  
{  
if (this.HasType(type) && (double)this.resources[type] > SimManager.RESOURCE_MIN)  
return false;  
}  
 
return true;  
}  
 
public void Add(int type, double amount)  
{  
if (this.resources.ContainsKey(type))  
this.resources[type] = (double)this.resources[type] + amount;  
else  
this.resources.Add(type, amount);  
}  
 
public void Reset()  
{  
this.resources = new Hashtable();  
}  
 
public void Debug()  
{  
foreach (int key in this.resources.Keys)  
{  
UnityEngine.MonoBehaviour.print(" -> " + GetResourceName(key) + " = " + this.resources[key]);  
}  
}  
 
public double GetResourceMass(int type)  
{  
double density = GetResourceDensity(type);  
return density == 0d ? 0d : (double)this.resources[type] * density;  
}  
 
public static ResourceFlowMode GetResourceFlowMode(int type)  
{  
return PartResourceLibrary.Instance.GetDefinition(type).resourceFlowMode;  
}  
 
public static ResourceTransferMode GetResourceTransferMode(int type)  
{  
return PartResourceLibrary.Instance.GetDefinition(type).resourceTransferMode;  
}  
 
public static float GetResourceDensity(int type)  
{  
return PartResourceLibrary.Instance.GetDefinition(type).density;  
}  
 
public static string GetResourceName(int type)  
{  
return PartResourceLibrary.Instance.GetDefinition(type).name;  
}  
}  
}  
 
#region  
 
using System;  
using System.Collections.Generic;  
using System.ComponentModel.Design.Serialization;  
using System.Diagnostics;  
using System.Linq;  
using System.Reflection;  
using System.Threading;  
 
using KerbalEngineer.Flight;  
 
using UnityEngine;  
 
#endregion  
 
namespace KerbalEngineer.Simulation  
{  
public class SimManager : IUpdatable, IUpdateRequest  
{  
#region Instance  
 
private static readonly SimManager instance = new SimManager();  
 
public static SimManager Instance  
{  
get { return instance; }  
}  
 
#endregion  
 
public const double RESOURCE_MIN = 0.0001;  
 
private static bool bRequested;  
private static bool bRunning;  
private static readonly Stopwatch timer = new Stopwatch();  
private static long delayBetweenSims;  
 
public static long minSimTime = 150;  
 
// Support for RealFuels using reflection to check localCorrectThrust without dependency  
private static bool hasCheckedForRealFuels;  
private static bool hasInstalledRealFuels;  
 
private static Type RF_ModuleEngineConfigs_Type;  
private static Type RF_ModuleHybridEngine_Type;  
 
private static FieldInfo RF_ModuleEngineConfigs_locaCorrectThrust;  
private static FieldInfo RF_ModuleHybridEngine_locaCorrectThrust;  
public static Stage[] Stages { get; private set; }  
public static Stage LastStage { get; private set; }  
public static String failMessage { get; private set; }  
public static double Gravity { get; set; }  
public static double Atmosphere { get; set; }  
 
#region IUpdatable Members  
 
public void Update()  
{  
TryStartSimulation();  
}  
 
#endregion  
 
#region IUpdateRequest Members  
 
public bool UpdateRequested { get; set; }  
 
#endregion  
 
public static void RequestUpdate()  
{  
instance.UpdateRequested = true;  
RequestSimulation();  
}  
 
private static void GetRealFuelsTypes()  
{  
hasCheckedForRealFuels = true;  
 
foreach (AssemblyLoader.LoadedAssembly assembly in AssemblyLoader.loadedAssemblies)  
{  
MonoBehaviour.print("Assembly:" + assembly.assembly);  
 
if (assembly.assembly.ToString().Split(',')[0] == "RealFuels")  
{  
MonoBehaviour.print("Found RealFuels mod");  
 
RF_ModuleEngineConfigs_Type = assembly.assembly.GetType("RealFuels.ModuleEngineConfigs");  
if (RF_ModuleEngineConfigs_Type == null)  
{  
MonoBehaviour.print("Failed to find ModuleEngineConfigs type");  
break;  
}  
 
RF_ModuleEngineConfigs_locaCorrectThrust = RF_ModuleEngineConfigs_Type.GetField("localCorrectThrust");  
if (RF_ModuleEngineConfigs_locaCorrectThrust == null)  
{  
MonoBehaviour.print("Failed to find ModuleEngineConfigs.localCorrectThrust field");  
break;  
}  
 
RF_ModuleHybridEngine_Type = assembly.assembly.GetType("RealFuels.ModuleHybridEngine");  
if (RF_ModuleHybridEngine_Type == null)  
{  
MonoBehaviour.print("Failed to find ModuleHybridEngine type");  
break;  
}  
 
RF_ModuleHybridEngine_locaCorrectThrust = RF_ModuleHybridEngine_Type.GetField("localCorrectThrust");  
if (RF_ModuleHybridEngine_locaCorrectThrust == null)  
{  
MonoBehaviour.print("Failed to find ModuleHybridEngine.localCorrectThrust field");  
break;  
}  
 
hasInstalledRealFuels = true;  
break;  
}  
}  
}  
 
public static bool DoesEngineUseCorrectedThrust(Part theEngine)  
{  
if (!hasInstalledRealFuels /*|| HighLogic.LoadedSceneIsFlight*/)  
{  
return false;  
}  
 
// Look for either of the Real Fuels engine modules and call the relevant method to find out  
PartModule modEngineConfigs = theEngine.Modules["ModuleEngineConfigs"];  
if (modEngineConfigs != null)  
{  
// Check the localCorrectThrust  
if ((bool)RF_ModuleEngineConfigs_locaCorrectThrust.GetValue(modEngineConfigs))  
{  
return true;  
}  
}  
 
PartModule modHybridEngine = theEngine.Modules["ModuleHybridEngine"];  
if (modHybridEngine != null)  
{  
// Check the localCorrectThrust  
if ((bool)RF_ModuleHybridEngine_locaCorrectThrust.GetValue(modHybridEngine))  
{  
return true;  
}  
}  
 
return false;  
}  
 
public static void RequestSimulation()  
{  
if (!hasCheckedForRealFuels)  
{  
GetRealFuelsTypes();  
}  
 
bRequested = true;  
if (!timer.IsRunning)  
{  
timer.Start();  
}  
}  
 
public static void TryStartSimulation()  
{  
if (bRequested && !bRunning && (HighLogic.LoadedSceneIsEditor || FlightGlobals.ActiveVessel != null) && timer.ElapsedMilliseconds > delayBetweenSims)  
{  
bRequested = false;  
timer.Reset();  
StartSimulation();  
}  
}  
 
public static bool ResultsReady()  
{  
return !bRunning;  
}  
 
private static void ClearResults()  
{  
failMessage = "";  
Stages = null;  
LastStage = null;  
}  
 
private static void StartSimulation()  
{  
try  
{  
bRunning = true;  
ClearResults();  
timer.Start();  
 
List<Part> parts = HighLogic.LoadedSceneIsEditor ? EditorLogic.SortedShipList : FlightGlobals.ActiveVessel.Parts;  
 
// Create the Simulation object in this thread  
Simulation sim = new Simulation();  
 
// This call doesn't ever fail at the moment but we'll check and return a sensible error for display  
if (sim.PrepareSimulation(parts, Gravity, Atmosphere))  
{  
ThreadPool.QueueUserWorkItem(RunSimulation, sim);  
}  
else  
{  
failMessage = "PrepareSimulation failed";  
bRunning = false;  
}  
}  
catch (Exception e)  
{  
MonoBehaviour.print("Exception in StartSimulation: " + e);  
failMessage = e.ToString();  
bRunning = false;  
}  
}  
 
private static void RunSimulation(object simObject)  
{  
try  
{  
Stages = (simObject as Simulation).RunSimulation();  
if (Stages != null)  
{  
#if LOG  
foreach (Stage stage in Stages)  
stage.Dump();  
#endif  
LastStage = Stages.Last();  
}  
}  
catch (Exception e)  
{  
MonoBehaviour.print("Exception in RunSimulation: " + e);  
Stages = null;  
LastStage = null;  
failMessage = e.ToString();  
}  
 
timer.Stop();  
MonoBehaviour.print("Total simulation time: " + timer.ElapsedMilliseconds + "ms");  
delayBetweenSims = minSimTime - timer.ElapsedMilliseconds;  
if (delayBetweenSims < 0)  
{  
delayBetweenSims = 0;  
}  
 
timer.Reset();  
timer.Start();  
 
bRunning = false;  
}  
}  
}  
// Kerbal Engineer Redux  
// Author: CYBUTEK  
// License: Attribution-NonCommercial-ShareAlike 3.0 Unported  
//  
// This class has taken a lot of inspiration from r4m0n's MuMech FuelFlowSimulator. Although extremely  
// similar to the code used within MechJeb, it is a clean re-write. The similarities are a testiment  
// to how well the MuMech code works and the robustness of the simulation algorithem used.  
 
using System;  
using System.Collections.Generic;  
 
using UnityEngine;  
 
namespace KerbalEngineer.Simulation  
{  
public class Simulation  
{  
private List<Part> partList;  
 
private List<PartSim> allParts;  
private List<PartSim> allFuelLines;  
private HashSet<PartSim> drainingParts;  
private List<EngineSim> allEngines;  
private List<EngineSim> activeEngines;  
private HashSet<int> drainingResources;  
 
private int lastStage = 0;  
private int currentStage = 0;  
 
private double gravity = 0;  
private double atmosphere = 0;  
#if LOG || TIMERS  
private Stopwatch _timer = new Stopwatch();  
#endif  
private const double STD_GRAVITY = 9.81d;  
private const double SECONDS_PER_DAY = 86400;  
 
public Simulation()  
{  
#if LOG  
MonoBehaviour.print("Simulation created");  
#endif  
}  
 
// This function prepares the simulation by creating all the necessary data structures it will  
// need during the simulation. All required data is copied from the core game data structures  
// so that the simulation itself can be run in a background thread without having issues with  
// the core game changing the data while the simulation is running.  
public bool PrepareSimulation(List<Part> parts, double theGravity, double theAtmosphere = 0)  
{  
#if LOG  
MonoBehaviour.print("PrepareSimulation started");  
#endif  
#if LOG || TIMERS  
_timer.Start();  
#endif  
// Store the parameters in members for ease of access in other functions  
this.partList = parts;  
this.gravity = theGravity;  
this.atmosphere = theAtmosphere;  
this.lastStage = Staging.lastStage;  
//MonoBehaviour.print("lastStage = " + lastStage);  
 
// Create the lists for our simulation parts  
this.allParts = new List<PartSim>();  
this.allFuelLines = new List<PartSim>();  
this.drainingParts = new HashSet<PartSim>();  
this.allEngines = new List<EngineSim>();  
this.activeEngines = new List<EngineSim>();  
this.drainingResources = new HashSet<int>();  
 
// A dictionary for fast lookup of Part->PartSim during the preparation phase  
Dictionary<Part, PartSim> partSimLookup = new Dictionary<Part, PartSim>();  
 
// First we create a PartSim for each Part (giving each a unique id)  
int partId = 1;  
foreach (Part part in this.partList)  
{  
// If the part is already in the lookup dictionary then log it and skip to the next part  
if (partSimLookup.ContainsKey(part))  
{  
MonoBehaviour.print("Part " + part.name + " appears in vessel list more than once");  
continue;  
}  
 
// Create the PartSim  
PartSim partSim = new PartSim(part, partId, this.atmosphere);  
 
// Add it to the Part lookup dictionary and the necessary lists  
partSimLookup.Add(part, partSim);  
this.allParts.Add(partSim);  
if (partSim.isFuelLine)  
this.allFuelLines.Add(partSim);  
if (partSim.isEngine)  
partSim.CreateEngineSims(this.allEngines, this.atmosphere);  
 
partId++;  
}  
 
// Now that all the PartSims have been created we can do any set up that needs access to other parts  
//MonoBehaviour.print("SetupAttachNodes and count stages");  
foreach (PartSim partSim in this.allParts)  
{  
partSim.SetupAttachNodes(partSimLookup);  
if (partSim.decoupledInStage >= this.lastStage)  
this.lastStage = partSim.decoupledInStage + 1;  
}  
 
// And finally release the Part references from all the PartSims  
//MonoBehaviour.print("ReleaseParts");  
foreach (PartSim partSim in this.allParts)  
partSim.ReleasePart();  
 
// And dereference the core's part list  
this.partList = null;  
 
#if LOG || TIMERS  
_timer.Stop();  
MonoBehaviour.print("PrepareSimulation: " + _timer.ElapsedMilliseconds + "ms");  
#endif  
#if LOG  
Dump();  
#endif  
return true;  
}  
 
 
// This function runs the simulation and returns a newly created array of Stage objects  
public Stage[] RunSimulation()  
{  
#if LOG  
MonoBehaviour.print("RunSimulation started");  
#endif  
#if LOG || TIMERS  
_timer.Start();  
#endif  
// Start with the last stage to simulate  
// (this is in a member variable so it can be accessed by AllowedToStage and ActiveStage)  
this.currentStage = this.lastStage;  
 
// Create the array of stages that will be returned  
Stage[] stages = new Stage[this.currentStage + 1];  
 
// Loop through the stages  
while (this.currentStage >= 0)  
{  
#if LOG  
MonoBehaviour.print("Simulating stage " + currentStage);  
MonoBehaviour.print("ShipMass = " + ShipMass);  
_timer.Reset();  
_timer.Start();  
#endif  
// Update active engines and resource drains  
this.UpdateResourceDrains();  
 
// Create the Stage object for this stage  
Stage stage = new Stage();  
 
double stageTime = 0d;  
double stageDeltaV = 0d;  
double totalStageThrust = 0d;  
double totalStageActualThrust = 0d;  
 
double totalStageFlowRate = 0d;  
double totalStageIspFlowRate = 0d;  
double currentisp = 0;  
double stageStartMass = this.ShipMass;  
double stepStartMass = stageStartMass;  
double stepEndMass = 0;  
 
// Loop through all the active engines totalling the thrust, actual thrust and mass flow rates  
foreach (EngineSim engine in this.activeEngines)  
{  
totalStageActualThrust += engine.actualThrust;  
totalStageThrust += engine.thrust;  
 
totalStageFlowRate += engine.ResourceConsumptions.Mass;  
totalStageIspFlowRate += engine.ResourceConsumptions.Mass * engine.isp;  
}  
 
// Calculate the effective isp at this point  
if (totalStageFlowRate > 0d && totalStageIspFlowRate > 0d)  
currentisp = totalStageIspFlowRate / totalStageFlowRate;  
else  
currentisp = 0;  
 
// Store various things in the Stage object  
stage.thrust = totalStageThrust;  
//MonoBehaviour.print("stage.thrust = " + stage.thrust);  
stage.thrustToWeight = totalStageThrust / (stageStartMass * this.gravity);  
stage.maxThrustToWeight = stage.thrustToWeight;  
//MonoBehaviour.print("StageMass = " + stageStartMass);  
//MonoBehaviour.print("Initial maxTWR = " + stage.maxThrustToWeight);  
stage.actualThrust = totalStageActualThrust;  
stage.actualThrustToWeight = totalStageActualThrust / (stageStartMass * this.gravity);  
 
// Calculate the cost and mass of this stage  
foreach (PartSim partSim in this.allParts)  
{  
if (partSim.decoupledInStage == this.currentStage - 1)  
{  
stage.cost += partSim.cost;  
stage.mass += partSim.GetStartMass();  
}  
}  
#if LOG  
MonoBehaviour.print("Stage setup took " + _timer.ElapsedMilliseconds + "ms");  
#endif  
// Now we will loop until we are allowed to stage  
int loopCounter = 0;  
while (!this.AllowedToStage())  
{  
loopCounter++;  
//MonoBehaviour.print("loop = " + loopCounter);  
 
// Calculate how long each draining tank will take to drain and run for the minimum time  
double resourceDrainTime = double.MaxValue;  
PartSim partMinDrain = null;  
foreach (PartSim partSim in this.drainingParts)  
{  
double time = partSim.TimeToDrainResource();  
if (time < resourceDrainTime)  
{  
resourceDrainTime = time;  
partMinDrain = partSim;  
}  
}  
#if LOG  
MonoBehaviour.print("Drain time = " + resourceDrainTime + " (" + partMinDrain.name + ":" + partMinDrain.partId + ")");  
#endif  
foreach (PartSim partSim in this.drainingParts)  
partSim.DrainResources(resourceDrainTime);  
 
// Get the mass after draining  
stepEndMass = this.ShipMass;  
stageTime += resourceDrainTime;  
 
double stepEndTWR = totalStageThrust / (stepEndMass * this.gravity);  
//MonoBehaviour.print("After drain mass = " + stepEndMass);  
//MonoBehaviour.print("currentThrust = " + totalStageThrust);  
//MonoBehaviour.print("currentTWR = " + stepEndTWR);  
if (stepEndTWR > stage.maxThrustToWeight)  
stage.maxThrustToWeight = stepEndTWR;  
 
//MonoBehaviour.print("newMaxTWR = " + stage.maxThrustToWeight);  
 
// If we have drained anything and the masses make sense then add this step's deltaV to the stage total  
if (resourceDrainTime > 0d && stepStartMass > stepEndMass && stepStartMass > 0d && stepEndMass > 0d)  
stageDeltaV += (currentisp * STD_GRAVITY) * Math.Log(stepStartMass / stepEndMass);  
 
// Update the active engines and resource drains for the next step  
this.UpdateResourceDrains();  
 
// Recalculate the current thrust and isp for the next step  
totalStageThrust = 0d;  
totalStageActualThrust = 0d;  
totalStageFlowRate = 0d;  
totalStageIspFlowRate = 0d;  
foreach (EngineSim engine in this.activeEngines)  
{  
totalStageActualThrust += engine.actualThrust;  
totalStageThrust += engine.thrust;  
 
totalStageFlowRate += engine.ResourceConsumptions.Mass;  
totalStageIspFlowRate += engine.ResourceConsumptions.Mass * engine.isp;  
}  
 
//MonoBehaviour.print("next step thrust = " + totalStageThrust);  
 
if (totalStageFlowRate > 0d && totalStageIspFlowRate > 0d)  
currentisp = totalStageIspFlowRate / totalStageFlowRate;  
else  
currentisp = 0;  
 
// Check if we actually changed anything  
if (stepStartMass == stepEndMass)  
{  
MonoBehaviour.print("No change in mass");  
break;  
}  
 
// Check to stop rampant looping  
if (loopCounter == 1000)  
{  
MonoBehaviour.print("exceeded loop count");  
MonoBehaviour.print("stageStartMass = " + stageStartMass);  
MonoBehaviour.print("stepStartMass = " + stepStartMass);  
MonoBehaviour.print("StepEndMass = " + stepEndMass);  
break;  
}  
 
// The next step starts at the mass this one ended at  
stepStartMass = stepEndMass;  
}  
 
// Store more values in the Stage object and stick it in the array  
// Recalculate effective stage isp from the stageDeltaV (flip the standard deltaV calculation around)  
// Note: If the mass doesn't change then this is a divide by zero  
if (stageStartMass != stepStartMass)  
stage.isp = stageDeltaV / (STD_GRAVITY * Math.Log(stageStartMass / stepStartMass));  
else  
stage.isp = 0;  
stage.deltaV = stageDeltaV;  
// Zero stage time if more than a day (this should be moved into the window code)  
stage.time = (stageTime < SECONDS_PER_DAY) ? stageTime : 0d;  
stage.number = this.currentStage;  
stages[this.currentStage] = stage;  
 
// Now activate the next stage  
this.currentStage--;  
#if LOG  
// Log how long the stage took  
_timer.Stop();  
MonoBehaviour.print("Simulating stage took " + _timer.ElapsedMilliseconds + "ms");  
stage.Dump();  
_timer.Reset();  
_timer.Start();  
#endif  
// Activate the next stage  
this.ActivateStage();  
#if LOG  
// Log home long it took to activate  
_timer.Stop();  
MonoBehaviour.print("ActivateStage took " + _timer.ElapsedMilliseconds + "ms");  
#endif  
}  
 
// Now we add up the various total fields in the stages  
for (int i = 0; i < stages.Length; i++)  
{  
// For each stage we total up the cost, mass, deltaV and time for this stage and all the stages above  
for (int j = i; j >= 0; j--)  
{  
stages[i].totalCost += stages[j].cost;  
stages[i].totalMass += stages[j].mass;  
stages[i].totalDeltaV += stages[j].deltaV;  
stages[i].totalTime += stages[j].time;  
}  
// We also total up the deltaV for stage and all stages below  
for (int j = i; j < stages.Length; j++)  
{  
stages[i].inverseTotalDeltaV += stages[j].deltaV;  
}  
 
// Zero the total time if the value will be huge (24 hours?) to avoid the display going weird  
// (this should be moved into the window code)  
if (stages[i].totalTime > SECONDS_PER_DAY)  
stages[i].totalTime = 0d;  
}  
#if LOG || TIMERS  
_timer.Stop();  
MonoBehaviour.print("RunSimulation: " + _timer.ElapsedMilliseconds + "ms");  
#endif  
return stages;  
}  
 
 
// This function does all the hard work of working out which engines are burning, which tanks are being drained  
// and setting the drain rates  
private void UpdateResourceDrains()  
{  
// Empty the active engines list and the draining resources set  
this.activeEngines.Clear();  
this.drainingResources.Clear();  
 
// Reset the resource drains of all draining parts  
foreach (PartSim partSim in this.drainingParts)  
partSim.ResourceDrains.Reset();  
 
// Empty the draining parts set  
this.drainingParts.Clear();  
 
// Loop through all the engine modules in the ship  
foreach (EngineSim engine in this.allEngines)  
{  
// If the engine is active in the current stage  
if (engine.partSim.inverseStage >= this.currentStage)  
{  
// Set the resource drains for this engine and add it to the active list if it is active  
if (engine.SetResourceDrains(this.allParts, this.allFuelLines, this.drainingParts))  
{  
this.activeEngines.Add(engine);  
foreach (int type in engine.ResourceConsumptions.Types)  
this.drainingResources.Add(type);  
}  
}  
}  
#if LOG  
StringBuilder buffer = new StringBuilder(1024);  
buffer.AppendFormat("Active engines = {0:d}\n", activeEngines.Count);  
int i = 0;  
foreach (EngineSim engine in activeEngines)  
engine.DumpEngineToBuffer(buffer, "Engine " + (i++) + ":");  
MonoBehaviour.print(buffer);  
#endif  
}  
 
// This function works out if it is time to stage  
private bool AllowedToStage()  
{  
#if LOG  
StringBuilder buffer = new StringBuilder(1024);  
buffer.AppendLine("AllowedToStage");  
buffer.AppendFormat("currentStage = {0:d}\n", currentStage);  
#endif  
if (this.activeEngines.Count == 0)  
{  
#if LOG  
buffer.AppendLine("No active engines => true");  
MonoBehaviour.print(buffer);  
#endif  
return true;  
}  
 
foreach (PartSim partSim in this.allParts)  
{  
//partSim.DumpPartToBuffer(buffer, "Testing: ", allParts);  
//buffer.AppendFormat("isSepratron = {0}\n", partSim.isSepratron ? "true" : "false");  
if (partSim.decoupledInStage == (this.currentStage - 1) && (!partSim.isSepratron || partSim.decoupledInStage < partSim.inverseStage))  
{  
if (!partSim.Resources.EmptyOf(this.drainingResources))  
{  
#if LOG  
partSim.DumpPartToBuffer(buffer, "Decoupled part not empty => false: ");  
MonoBehaviour.print(buffer);  
#endif  
return false;  
}  
foreach (EngineSim engine in this.activeEngines)  
{  
if (engine.partSim == partSim)  
{  
#if LOG  
partSim.DumpPartToBuffer(buffer, "Decoupled part is active engine => false: ");  
MonoBehaviour.print(buffer);  
#endif  
return false;  
}  
}  
}  
}  
 
if (this.currentStage > 0)  
{  
#if LOG  
buffer.AppendLine("Current stage > 0 => true");  
MonoBehaviour.print(buffer);  
#endif  
return true;  
}  
 
#if LOG  
buffer.AppendLine("Returning false");  
MonoBehaviour.print(buffer);  
#endif  
return false;  
}  
 
// This function activates the next stage  
// currentStage must be updated before calling this function  
private void ActivateStage()  
{  
// Build a set of all the parts that will be decoupled  
HashSet<PartSim> decoupledParts = new HashSet<PartSim>();  
foreach (PartSim partSim in this.allParts)  
{  
if (partSim.decoupledInStage >= this.currentStage)  
decoupledParts.Add(partSim);  
}  
 
foreach (PartSim partSim in decoupledParts)  
{  
// Remove it from the all parts list  
this.allParts.Remove(partSim);  
if (partSim.isEngine)  
{  
// If it is an engine then loop through all the engine modules and remove all the ones from this engine part  
for (int i = this.allEngines.Count - 1; i >= 0; i--)  
{  
if (this.allEngines[i].partSim == partSim)  
this.allEngines.RemoveAt(i);  
}  
}  
// If it is a fuel line then remove it from the list of all fuel lines  
if (partSim.isFuelLine)  
this.allFuelLines.Remove(partSim);  
}  
 
// Loop through all the (remaining) parts  
foreach (PartSim partSim in this.allParts)  
{  
// Ask the part to remove all the parts that are decoupled  
partSim.RemoveAttachedParts(decoupledParts);  
}  
}  
 
private double ShipStartMass  
{  
get  
{  
double mass = 0d;  
 
foreach (PartSim partSim in this.allParts)  
{  
mass += partSim.GetStartMass();  
}  
 
return mass;  
}  
}  
 
private double ShipMass  
{  
get  
{  
double mass = 0d;  
 
foreach (PartSim partSim in this.allParts)  
{  
mass += partSim.GetMass();  
}  
 
return mass;  
}  
}  
#if LOG  
public void Dump()  
{  
StringBuilder buffer = new StringBuilder(1024);  
buffer.AppendFormat("Part count = {0:d}\n", allParts.Count);  
 
// Output a nice tree view of the rocket  
if (allParts.Count > 0)  
{  
PartSim root = allParts[0];  
while (root.parent != null)  
root = root.parent;  
 
root.DumpPartToBuffer(buffer, "", allParts);  
}  
 
MonoBehaviour.print(buffer);  
}  
#endif  
}  
}  
 
// Kerbal Engineer Redux  
// Author: CYBUTEK  
// License: Attribution-NonCommercial-ShareAlike 3.0 Unported  
 
namespace KerbalEngineer.Simulation  
{  
public class Stage  
{  
public int number = 0;  
public int cost = 0;  
public int totalCost = 0;  
public double time = 0f;  
public double totalTime = 0f;  
public double mass = 0f;  
public double totalMass = 0f;  
public double isp = 0f;  
public double thrust = 0f;  
public double actualThrust = 0f;  
public double thrustToWeight = 0f;  
public double maxThrustToWeight = 0f;  
public double actualThrustToWeight = 0f;  
public double deltaV = 0f;  
public double totalDeltaV = 0f;  
public double inverseTotalDeltaV = 0f;  
#if LOG  
public void Dump()  
{  
StringBuilder str = new StringBuilder("", 512);  
str.AppendFormat("number : {0:d}\n", number);  
str.AppendFormat("cost : {0:d}\n", cost);  
str.AppendFormat("totalCost : {0:d}\n", totalCost);  
str.AppendFormat("time : {0:g6}\n", time);  
str.AppendFormat("totalTime : {0:g6}\n", totalTime);  
str.AppendFormat("mass : {0:g6}\n", mass);  
str.AppendFormat("totalMass : {0:g6}\n", totalMass);  
str.AppendFormat("isp : {0:g6}\n", isp);  
str.AppendFormat("thrust : {0:g6}\n", thrust);  
str.AppendFormat("actualThrust : {0:g6}\n", actualThrust);  
str.AppendFormat("thrustToWeight: {0:g6}\n", thrustToWeight);  
str.AppendFormat("maxTWR : {0:g6}\n", maxThrustToWeight);  
str.AppendFormat("actualTWR : {0:g6}\n", actualThrustToWeight);  
str.AppendFormat("deltaV : {0:g6}\n", deltaV);  
str.AppendFormat("totalDeltaV : {0:g6}\n", totalDeltaV);  
str.AppendFormat("invTotDeltaV : {0:g6}\n", inverseTotalDeltaV);  
 
MonoBehaviour.print(str);  
}  
#endif  
}  
}  
 
  using System;
  using System.Text;
 
  namespace KerbalEngineer.VesselSimulator
  {
  class AttachNodeSim
  {
  public PartSim attachedPartSim;
  public AttachNode.NodeType nodeType;
  public String id;
 
  public AttachNodeSim(PartSim partSim, String newId, AttachNode.NodeType newNodeType)
  {
  this.attachedPartSim = partSim;
  this.nodeType = newNodeType;
  this.id = newId;
  }
 
  public void DumpToBuffer(StringBuilder buffer)
  {
  if (this.attachedPartSim == null)
  {
  buffer.Append("<staged>:<n>");
  }
  else
  {
  buffer.Append(this.attachedPartSim.name);
  buffer.Append(":");
  buffer.Append(this.attachedPartSim.partId);
  }
  buffer.Append("#");
  buffer.Append(this.nodeType);
  buffer.Append(":");
  buffer.Append(this.id);
  }
  }
  }
 
  // Kerbal Engineer Redux
  // Author: CYBUTEK
  // License: Attribution-NonCommercial-ShareAlike 3.0 Unported
 
  #region
 
  using System;
  using System.Collections.Generic;
  using System.Linq;
  using System.Text;
 
  using UnityEngine;
 
  #endregion
 
  namespace KerbalEngineer.VesselSimulator
  {
  public class EngineSim
  {
  private readonly ResourceContainer resourceConsumptions = new ResourceContainer();
 
  public double actualThrust = 0;
  public bool isActive = false;
  public double isp = 0;
  public PartSim partSim;
 
  public double thrust = 0;
 
  // Add thrust vector to account for directional losses
  public Vector3 thrustVec;
 
  public EngineSim(PartSim theEngine,
  double atmosphere,
  double velocity,
  float maxThrust,
  float thrustPercentage,
  float requestedThrust,
  Vector3 vecThrust,
  float realIsp,
  FloatCurve atmosphereCurve,
  FloatCurve velocityCurve,
  bool throttleLocked,
  List<Propellant> propellants,
  bool active,
  bool correctThrust)
  {
  StringBuilder buffer = null;
  //MonoBehaviour.print("Create EngineSim for " + theEngine.name);
  //MonoBehaviour.print("maxThrust = " + maxThrust);
  //MonoBehaviour.print("thrustPercentage = " + thrustPercentage);
  //MonoBehaviour.print("requestedThrust = " + requestedThrust);
  //MonoBehaviour.print("velocity = " + velocity);
 
  this.partSim = theEngine;
 
  this.isActive = active;
  this.thrust = maxThrust * (thrustPercentage / 100f);
  //MonoBehaviour.print("thrust = " + thrust);
 
  this.thrustVec = vecThrust;
 
  double flowRate = 0d;
  if (this.partSim.hasVessel)
  {
  //MonoBehaviour.print("hasVessel is true");
  this.actualThrust = requestedThrust;
  if (velocityCurve != null)
  {
  this.actualThrust *= velocityCurve.Evaluate((float)velocity);
  //MonoBehaviour.print("actualThrust at velocity = " + actualThrust);
  }
 
  this.isp = atmosphereCurve.Evaluate((float)this.partSim.part.staticPressureAtm);
  if (this.isp == 0d)
  {
  MonoBehaviour.print("Isp at " + this.partSim.part.staticPressureAtm + " is zero. Flow rate will be NaN");
  }
 
  if (correctThrust && realIsp == 0)
  {
  float ispsl = atmosphereCurve.Evaluate(0);
  if (ispsl != 0)
  {
  this.thrust = this.thrust * this.isp / ispsl;
  }
  else
  {
  MonoBehaviour.print("Isp at sea level is zero. Unable to correct thrust.");
  }
  //MonoBehaviour.print("corrected thrust = " + thrust);
  }
 
  if (velocityCurve != null)
  {
  this.thrust *= velocityCurve.Evaluate((float)velocity);
  //MonoBehaviour.print("thrust at velocity = " + thrust);
  }
 
  if (throttleLocked)
  {
  //MonoBehaviour.print("throttleLocked is true");
  flowRate = this.thrust / (this.isp * 9.81d);
  }
  else
  {
  if (this.partSim.isLanded)
  {
  //MonoBehaviour.print("partSim.isLanded is true, mainThrottle = " + FlightInputHandler.state.mainThrottle);
  flowRate = Math.Max(0.000001d, this.thrust * FlightInputHandler.state.mainThrottle) / (this.isp * 9.81d);
  }
  else
  {
  if (requestedThrust > 0)
  {
  if (velocityCurve != null)
  {
  requestedThrust *= velocityCurve.Evaluate((float)velocity);
  //MonoBehaviour.print("requestedThrust at velocity = " + requestedThrust);
  }
 
  //MonoBehaviour.print("requestedThrust > 0");
  flowRate = requestedThrust / (this.isp * 9.81d);
  }
  else
  {
  //MonoBehaviour.print("requestedThrust <= 0");
  flowRate = this.thrust / (this.isp * 9.81d);
  }
  }
  }
  }
  else
  {
  //MonoBehaviour.print("hasVessel is false");
  this.isp = atmosphereCurve.Evaluate((float)atmosphere);
  if (this.isp == 0d)
  {
  MonoBehaviour.print("Isp at " + atmosphere + " is zero. Flow rate will be NaN");
  }
  if (correctThrust)
  {
  float ispsl = atmosphereCurve.Evaluate(0);
  if (ispsl != 0)
  {
  this.thrust = this.thrust * this.isp / ispsl;
  }
  else
  {
  MonoBehaviour.print("Isp at sea level is zero. Unable to correct thrust.");
  }
  //MonoBehaviour.print("corrected thrust = " + thrust);
  }
 
  if (velocityCurve != null)
  {
  this.thrust *= velocityCurve.Evaluate((float)velocity);
  //MonoBehaviour.print("thrust at velocity = " + thrust);
  }
 
  flowRate = this.thrust / (this.isp * 9.81d);
  }
 
  if (SimManager.logOutput)
  {
  buffer = new StringBuilder(1024);
  buffer.AppendFormat("flowRate = {0:g6}\n", flowRate);
  }
 
  float flowMass = 0f;
  foreach (Propellant propellant in propellants)
  {
  flowMass += propellant.ratio * ResourceContainer.GetResourceDensity(propellant.id);
  }
 
  if (SimManager.logOutput)
  {
  buffer.AppendFormat("flowMass = {0:g6}\n", flowMass);
  }
 
  foreach (Propellant propellant in propellants)
  {
  if (propellant.name == "ElectricCharge" || propellant.name == "IntakeAir")
  {
  continue;
  }
 
  double consumptionRate = propellant.ratio * flowRate / flowMass;
  if (SimManager.logOutput)
  {
  buffer.AppendFormat("Add consumption({0}, {1}:{2:d}) = {3:g6}\n", ResourceContainer.GetResourceName(propellant.id), theEngine.name, theEngine.partId, consumptionRate);
  }
  this.resourceConsumptions.Add(propellant.id, consumptionRate);
  }
 
  if (SimManager.logOutput)
  {
  MonoBehaviour.print(buffer);
  }
  }
 
  public ResourceContainer ResourceConsumptions
  {
  get { return this.resourceConsumptions; }
  }
 
  public bool SetResourceDrains(List<PartSim> allParts, List<PartSim> allFuelLines, HashSet<PartSim> drainingParts)
  {
  LogMsg log = null;
 
  // A dictionary to hold a set of parts for each resource
  Dictionary<int, HashSet<PartSim>> sourcePartSets = new Dictionary<int, HashSet<PartSim>>();
 
  foreach (int type in this.resourceConsumptions.Types)
  {
  HashSet<PartSim> sourcePartSet = null;
  switch (ResourceContainer.GetResourceFlowMode(type))
  {
  case ResourceFlowMode.NO_FLOW:
  if (this.partSim.resources[type] > SimManager.RESOURCE_MIN)
  {
  sourcePartSet = new HashSet<PartSim>();
  //MonoBehaviour.print("SetResourceDrains(" + name + ":" + partId + ") setting sources to just this");
  sourcePartSet.Add(this.partSim);
  }
  break;
 
  case ResourceFlowMode.ALL_VESSEL:
  foreach (PartSim aPartSim in allParts)
  {
  if (aPartSim.resources[type] > SimManager.RESOURCE_MIN)
  {
  if (sourcePartSet == null)
  {
  sourcePartSet = new HashSet<PartSim>();
  }
 
  sourcePartSet.Add(aPartSim);
  }
  }
  break;
 
  case ResourceFlowMode.STAGE_PRIORITY_FLOW:
  {
  Dictionary<int, HashSet<PartSim>> stagePartSets = new Dictionary<int, HashSet<PartSim>>();
  int maxStage = -1;
  foreach (PartSim aPartSim in allParts)
  {
  if (aPartSim.resources[type] > SimManager.RESOURCE_MIN)
  {
  //int stage = aPartSim.decoupledInStage; // Use the number of the stage the tank is decoupled in
  int stage = aPartSim.DecouplerCount(); // Use the count of decouplers between tank and root
  if (stage > maxStage)
  {
  maxStage = stage;
  }
  if (stagePartSets.ContainsKey(stage))
  {
  sourcePartSet = stagePartSets[stage];
  }
  else
  {
  sourcePartSet = new HashSet<PartSim>();
  stagePartSets.Add(stage, sourcePartSet);
  }
 
  sourcePartSet.Add(aPartSim);
  }
  }
 
  while (maxStage >= 0)
  {
  if (stagePartSets.ContainsKey(maxStage))
  {
  if (stagePartSets[maxStage].Count() > 0)
  {
  sourcePartSet = stagePartSets[maxStage];
  break;
  }
  }
  maxStage--;
  }
  }
  break;
 
  case ResourceFlowMode.STACK_PRIORITY_SEARCH:
  HashSet<PartSim> visited = new HashSet<PartSim>();
 
  if (SimManager.logOutput)
  {
  log = new LogMsg();
  log.buf.AppendLine("Find " + ResourceContainer.GetResourceName(type) + " sources for " + this.partSim.name + ":" + this.partSim.partId);
  }
  sourcePartSet = this.partSim.GetSourceSet(type, allParts, visited, log, "");
  if (SimManager.logOutput)
  {
  MonoBehaviour.print(log.buf);
  }
  break;
 
  default:
  MonoBehaviour.print("SetResourceDrains(" + this.partSim.name + ":" + this.partSim.partId + ") Unexpected flow type for " + ResourceContainer.GetResourceName(type) + ")");
  break;
  }
 
  if (sourcePartSet != null && sourcePartSet.Count > 0)
  {
  sourcePartSets[type] = sourcePartSet;
  if (SimManager.logOutput)
  {
  log = new LogMsg();
  log.buf.AppendLine("Source parts for " + ResourceContainer.GetResourceName(type) + ":");
  foreach (PartSim partSim in sourcePartSet)
  {
  log.buf.AppendLine(partSim.name + ":" + partSim.partId);
  }
  MonoBehaviour.print(log.buf);
  }
  }
  }
 
  // If we don't have sources for all the needed resources then return false without setting up any drains
  foreach (int type in this.resourceConsumptions.Types)
  {
  if (!sourcePartSets.ContainsKey(type))
  {
  if (SimManager.logOutput)
  {
  MonoBehaviour.print("No source of " + ResourceContainer.GetResourceName(type));
  }
 
  this.isActive = false;
  return false;
  }
  }
 
  // Now we set the drains on the members of the sets and update the draining parts set
  foreach (int type in this.resourceConsumptions.Types)
  {
  HashSet<PartSim> sourcePartSet = sourcePartSets[type];
  // Loop through the members of the set
  double amount = this.resourceConsumptions[type] / sourcePartSet.Count;
  foreach (PartSim partSim in sourcePartSet)
  {
  if (SimManager.logOutput)
  {
  MonoBehaviour.print("Adding drain of " + amount + " " + ResourceContainer.GetResourceName(type) + " to " + partSim.name + ":" + partSim.partId);
  }
 
  partSim.resourceDrains.Add(type, amount);
  drainingParts.Add(partSim);
  }
  }
 
  return true;
  }
 
  public void DumpEngineToBuffer(StringBuilder buffer, String prefix)
  {
  buffer.Append(prefix);
  buffer.AppendFormat("[thrust = {0:g6}, actual = {1:g6}, isp = {2:g6}\n", this.thrust, this.actualThrust, this.isp);
  }
  }
  }
  // Kerbal Engineer Redux
  // Author: CYBUTEK
  // License: Attribution-NonCommercial-ShareAlike 3.0 Unported
  //
  // This class has taken a lot of inspiration from r4m0n's MuMech FuelFlowSimulator. Although extremely
  // similar to the code used within MechJeb, it is a clean re-write. The similarities are a testiment
  // to how well the MuMech code works and the robustness of the simulation algorithem used.
 
  using System;
  using System.Collections.Generic;
  using System.Linq;
  using System.Text;
 
  using KerbalEngineer.Extensions;
 
  using UnityEngine;
 
  namespace KerbalEngineer.VesselSimulator
  {
  public class PartSim
  {
  public ResourceContainer resources = new ResourceContainer();
  public ResourceContainer resourceDrains = new ResourceContainer();
  public ResourceContainer resourceFlowStates = new ResourceContainer();
 
  List<AttachNodeSim> attachNodes = new List<AttachNodeSim>();
  public List<PartSim> fuelTargets = new List<PartSim>();
 
  public Part part; // This is only set while the data structures are being initialised
  public int partId = 0;
  public String name;
  public PartSim parent;
  public bool hasVessel;
  public String vesselName;
  public VesselType vesselType;
  public String initialVesselName;
  public bool isLanded;
  public bool isDecoupler;
  public int decoupledInStage;
  public int inverseStage;
  public float cost;
  public double baseMass = 0d;
  public double startMass = 0d;
  public String noCrossFeedNodeKey;
  public bool fuelCrossFeed;
  public bool isEngine;
  public bool isFuelLine;
  public bool isFuelTank;
  public bool isSepratron;
  public bool hasMultiModeEngine;
  public bool hasModuleEnginesFX;
  public bool hasModuleEngines;
  public bool isNoPhysics;
  public bool localCorrectThrust;
 
  public PartSim(Part thePart, int id, double atmosphere, LogMsg log)
  {
  this.part = thePart;
  this.partId = id;
  this.name = this.part.partInfo.name;
 
  if (log != null)
  log.buf.AppendLine("Create PartSim for " + this.name);
 
  this.parent = null;
  this.fuelCrossFeed = this.part.fuelCrossFeed;
  this.noCrossFeedNodeKey = this.part.NoCrossFeedNodeKey;
  this.decoupledInStage = this.DecoupledInStage(this.part);
  this.isFuelLine = this.part is FuelLine;
  this.isFuelTank = this.part is FuelTank;
  this.isSepratron = this.IsSepratron();
  this.inverseStage = this.part.inverseStage;
  //MonoBehaviour.print("inverseStage = " + inverseStage);
 
  this.cost = this.part.partInfo.cost;
  foreach (PartResource resource in this.part.Resources)
  {
  this.cost -= (float)((resource.maxAmount - resource.amount) * resource.info.unitCost);
  }
 
  // Work out if the part should have no physical significance
  this.isNoPhysics = this.part.HasModule<ModuleLandingGear>() ||
  this.part.HasModule<LaunchClamp>() ||
  this.part.physicalSignificance == Part.PhysicalSignificance.NONE ||
  this.part.PhysicsSignificance == 1;
 
  if (!this.isNoPhysics)
  this.baseMass = this.part.mass;
 
  if (SimManager.logOutput)
  MonoBehaviour.print((this.isNoPhysics ? "Ignoring" : "Using") + " part.mass of " + this.part.mass);
 
  foreach (PartResource resource in this.part.Resources)
  {
  // Make sure it isn't NaN as this messes up the part mass and hence most of the values
  // This can happen if a resource capacity is 0 and tweakable
  if (!Double.IsNaN(resource.amount))
  {
  if (SimManager.logOutput)
  MonoBehaviour.print(resource.resourceName + " = " + resource.amount);
 
  this.resources.Add(resource.info.id, resource.amount);
  this.resourceFlowStates.Add(resource.info.id, resource.flowState ? 1 : 0);
  }
  else
  {
  MonoBehaviour.print(resource.resourceName + " is NaN. Skipping.");
  }
  }
 
  this.startMass = this.GetMass();
 
  this.hasVessel = (this.part.vessel != null);
  this.isLanded = this.hasVessel && this.part.vessel.Landed;
  if (this.hasVessel)
  {
  this.vesselName = this.part.vessel.vesselName;
  this.vesselType = this.part.vesselType;
  }
  this.initialVesselName = this.part.initialVesselName;
 
  this.hasMultiModeEngine = this.part.HasModule<MultiModeEngine>();
  this.hasModuleEnginesFX = this.part.HasModule<ModuleEnginesFX>();
  this.hasModuleEngines = this.part.HasModule<ModuleEngines>();
 
  this.isEngine = this.hasMultiModeEngine || this.hasModuleEnginesFX || this.hasModuleEngines;
 
  if (SimManager.logOutput)
  MonoBehaviour.print("Created " + this.name + ". Decoupled in stage " + this.decoupledInStage);
  }
 
  public void CreateEngineSims(List<EngineSim> allEngines, double atmosphere, double velocity, bool vectoredThrust, LogMsg log)
  {
  bool correctThrust = SimManager.DoesEngineUseCorrectedThrust(this.part);
  if (log != null)
  {
  log.buf.AppendLine("CreateEngineSims for " + this.name);
 
  foreach (PartModule partMod in this.part.Modules)
  {
  log.buf.AppendLine("Module: " + partMod.moduleName);
  }
 
  log.buf.AppendLine("correctThrust = " + correctThrust);
  }
 
  if (this.hasMultiModeEngine)
  {
  // A multi-mode engine has multiple ModuleEnginesFX but only one is active at any point
  // The mode of the engine is the engineID of the ModuleEnginesFX that is active
  string mode = this.part.GetModule<MultiModeEngine>().mode;
 
  foreach (ModuleEnginesFX engine in this.part.GetModules<ModuleEnginesFX>())
  {
  if (engine.engineID == mode)
  {
  if (log != null)
  log.buf.AppendLine("Module: " + engine.moduleName);
 
  Vector3 thrustvec = this.CalculateThrustVector(vectoredThrust ? engine.thrustTransforms : null, log);
 
  EngineSim engineSim = new EngineSim(this,
  atmosphere,
  velocity,
  engine.maxThrust,
  engine.thrustPercentage,
  engine.requestedThrust,
  thrustvec,
  engine.realIsp,
  engine.atmosphereCurve,
  engine.useVelocityCurve ? engine.velocityCurve : null,
  engine.throttleLocked,
  engine.propellants,
  engine.isOperational,
  correctThrust);
  allEngines.Add(engineSim);
  }
  }
  }
  else
  {
  if (this.hasModuleEnginesFX)
  {
  foreach (ModuleEnginesFX engine in this.part.GetModules<ModuleEnginesFX>())
  {
  if (log != null)
  log.buf.AppendLine("Module: " + engine.moduleName);
 
  Vector3 thrustvec = this.CalculateThrustVector(vectoredThrust ? engine.thrustTransforms : null, log);
 
  EngineSim engineSim = new EngineSim(this,
  atmosphere,
  velocity,
  engine.maxThrust,
  engine.thrustPercentage,
  engine.requestedThrust,
  thrustvec,
  engine.realIsp,
  engine.atmosphereCurve,
  engine.useVelocityCurve ? engine.velocityCurve : null,
  engine.throttleLocked,
  engine.propellants,
  engine.isOperational,
  correctThrust);
  allEngines.Add(engineSim);
  }
  }
 
  if (this.hasModuleEngines)
  {
  foreach (ModuleEngines engine in this.part.GetModules<ModuleEngines>())
  {
  if (log != null)
  log.buf.AppendLine("Module: " + engine.moduleName);
 
  Vector3 thrustvec = this.CalculateThrustVector(vectoredThrust ? engine.thrustTransforms : null, log);
 
  EngineSim engineSim = new EngineSim(this,
  atmosphere,
  velocity,
  engine.maxThrust,
  engine.thrustPercentage,
  engine.requestedThrust,
  thrustvec,
  engine.realIsp,
  engine.atmosphereCurve,
  engine.useVelocityCurve ? engine.velocityCurve : null,
  engine.throttleLocked,
  engine.propellants,
  engine.isOperational,
  correctThrust);
  allEngines.Add(engineSim);
  }
  }
  }
 
  if (log != null)
  log.Flush();
  }
 
  private Vector3 CalculateThrustVector(List<Transform> thrustTransforms, LogMsg log)
  {
  if (thrustTransforms == null)
  return Vector3.forward;
 
  Vector3 thrustvec = Vector3.zero;
  foreach (Transform trans in thrustTransforms)
  {
  if (log != null)
  log.buf.AppendFormat("Transform = ({0:g6}, {1:g6}, {2:g6}) length = {3:g6}\n", trans.forward.x, trans.forward.y, trans.forward.z, trans.forward.magnitude);
 
  thrustvec -= trans.forward;
  }
 
  if (log != null)
  log.buf.AppendFormat("ThrustVec = ({0:g6}, {1:g6}, {2:g6}) length = {3:g6}\n", thrustvec.x, thrustvec.y, thrustvec.z, thrustvec.magnitude);
 
  thrustvec.Normalize();
 
  if (log != null)
  log.buf.AppendFormat("ThrustVecN = ({0:g6}, {1:g6}, {2:g6}) length = {3:g6}\n", thrustvec.x, thrustvec.y, thrustvec.z, thrustvec.magnitude);
 
  return thrustvec;
  }
 
  public void SetupParent(Dictionary<Part, PartSim> partSimLookup, LogMsg log)
  {
  if (this.part.parent != null)
  {
  this.parent = null;
  if (partSimLookup.TryGetValue(this.part.parent, out this.parent))
  {
  if (log != null)
  log.buf.AppendLine("Parent part is " + this.parent.name + ":" + this.parent.partId);
  }
  else
  {
  if (log != null)
  log.buf.AppendLine("No PartSim for parent part (" + this.part.parent.partInfo.name + ")");
  }
  }
  }
 
  public void SetupAttachNodes(Dictionary<Part, PartSim> partSimLookup, LogMsg log)
  {
  if (log != null)
  log.buf.AppendLine("SetupAttachNodes for " + this.name + ":" + this.partId + "");
 
  this.attachNodes.Clear();
  foreach (AttachNode attachNode in this.part.attachNodes)
  {
  if (log != null)
  log.buf.AppendLine("AttachNode " + attachNode.id + " = " + (attachNode.attachedPart != null ? attachNode.attachedPart.partInfo.name : "null"));
 
  if (attachNode.attachedPart != null && attachNode.id != "Strut")
  {
  PartSim attachedSim;
  if (partSimLookup.TryGetValue(attachNode.attachedPart, out attachedSim))
  {
  if (log != null)
  log.buf.AppendLine("Adding attached node " + attachedSim.name + ":" + attachedSim.partId + "");
 
  this.attachNodes.Add(new AttachNodeSim(attachedSim, attachNode.id, attachNode.nodeType));
  }
  else
  {
  if (log != null)
  log.buf.AppendLine("No PartSim for attached part (" + attachNode.attachedPart.partInfo.name + ")");
  }
  }
  }
 
  foreach (Part p in this.part.fuelLookupTargets)
  {
  PartSim targetSim;
  if (partSimLookup.TryGetValue(p, out targetSim))
  {
  if (log != null)
  log.buf.AppendLine("Fuel target: " + targetSim.name + ":" + targetSim.partId);
 
  this.fuelTargets.Add(targetSim);
  }
  else
  {
  if (log != null)
  log.buf.AppendLine("No PartSim for fuel target (" + p.name + ")");
  }
  }
  }
 
  private int DecoupledInStage(Part thePart, int stage = -1)
  {
  if (this.IsDecoupler(thePart))
  {
  if (thePart.inverseStage > stage)
  {
  stage = thePart.inverseStage;
  }
  }
 
  if (thePart.parent != null)
  {
  stage = this.DecoupledInStage(thePart.parent, stage);
  }
 
  return stage;
  }
 
  private bool IsDecoupler(Part thePart)
  {
  return thePart.HasModule<ModuleDecouple>() ||
  thePart.HasModule<ModuleAnchoredDecoupler>();
  }
 
  private bool IsActiveDecoupler(Part thePart)
  {
  return thePart.FindModulesImplementing<ModuleDecouple>().Any(mod => !mod.isDecoupled) ||
  thePart.FindModulesImplementing<ModuleAnchoredDecoupler>().Any(mod => !mod.isDecoupled);
  }
 
  private bool IsSepratron()
  {
  if (!this.part.ActivatesEvenIfDisconnected)
  return false;
 
  if (this.part is SolidRocket)
  return true;
 
  var modList = this.part.Modules.OfType<ModuleEngines>();
  if (modList.Count() == 0)
  return false;
 
  if (modList.First().throttleLocked == true)
  return true;
 
  return false;
  }
 
  public void ReleasePart()
  {
  this.part = null;
  }
 
 
  // All functions below this point must not rely on the part member (it may be null)
  //
 
  public HashSet<PartSim> GetSourceSet(int type, List<PartSim> allParts, HashSet<PartSim> visited, LogMsg log, String indent)
  {
  if (log != null)
  {
  log.buf.AppendLine(indent + "GetSourceSet(" + ResourceContainer.GetResourceName(type) + ") for " + this.name + ":" + this.partId);
  indent += " ";
  }
 
  HashSet<PartSim> allSources = new HashSet<PartSim>();
  HashSet<PartSim> partSources = null;
 
  // Rule 1: Each part can be only visited once, If it is visited for second time in particular search it returns empty list.
  if (visited.Contains(this))
  {
  if (log != null)
  log.buf.AppendLine(indent + "Returning empty set, already visited (" + this.name + ":" + this.partId + ")");
 
  return allSources;
  }
 
  //if (log != null)
  // log.buf.AppendLine(indent + "Adding this to visited");
 
  visited.Add(this);
 
  // Rule 2: Part performs scan on start of every fuel pipe ending in it. This scan is done in order in which pipes were installed. Then it makes an union of fuel tank sets each pipe scan returned. If the resulting list is not empty, it is returned as result.
  //MonoBehaviour.print("foreach fuel line");
 
  foreach (PartSim partSim in this.fuelTargets)
  {
  if (visited.Contains(partSim))
  {
  //if (log != null)
  // log.buf.AppendLine(indent + "Fuel target already visited, skipping (" + partSim.name + ":" + partSim.partId + ")");
  }
  else
  {
  //if (log != null)
  // log.buf.AppendLine(indent + "Adding fuel target as source (" + partSim.name + ":" + partSim.partId + ")");
 
  partSources = partSim.GetSourceSet(type, allParts, visited, log, indent);
  if (partSources.Count > 0)
  {
  allSources.UnionWith(partSources);
  partSources.Clear();
  }
  }
  }
 
  if (allSources.Count > 0)
  {
  if (log != null)
  log.buf.AppendLine(indent + "Returning " + allSources.Count + " fuel target sources (" + this.name + ":" + this.partId + ")");
 
  return allSources;
  }
 
  // Rule 3: This rule has been removed and merged with rules 4 and 7 to fix issue with fuel tanks with disabled crossfeed
 
  // Rule 4: Part performs scan on each of its axially mounted neighbors.
  // Couplers (bicoupler, tricoupler, ...) are an exception, they only scan one attach point on the single attachment side, skip the points on the side where multiple points are. [Experiment]
  // Again, the part creates union of scan lists from each of its neighbor and if it is not empty, returns this list.
  // The order in which mount points of a part are scanned appears to be fixed and defined by the part specification file. [Experiment]
  if (this.fuelCrossFeed)
  {
  //MonoBehaviour.print("foreach attach node");
  foreach (AttachNodeSim attachSim in this.attachNodes)
  {
  if (attachSim.attachedPartSim != null)
  {
  if (/*attachSim.nodeType != AttachNode.NodeType.Surface &&*/
  !(this.noCrossFeedNodeKey != null && this.noCrossFeedNodeKey.Length > 0 && attachSim.id.Contains(this.noCrossFeedNodeKey)))
  {
  if (visited.Contains(attachSim.attachedPartSim))
  {
  //if (log != null)
  // log.buf.AppendLine(indent + "Attached part already visited, skipping (" + attachSim.attachedPartSim.name + ":" + attachSim.attachedPartSim.partId + ")");
  }
  else
  {
  //if (log != null)
  // log.buf.AppendLine(indent + "Adding attached part as source (" + attachSim.attachedPartSim.name + ":" + attachSim.attachedPartSim.partId + ")");
 
  partSources = attachSim.attachedPartSim.GetSourceSet(type, allParts, visited, log, indent);
  if (partSources.Count > 0)
  {
  allSources.UnionWith(partSources);
  partSources.Clear();
  }
  }
  }
  else
  {
  //if (log != null)
  // log.buf.AppendLine(indent + "AttachNode is noCrossFeedKey, skipping (" + attachSim.attachedPartSim.name + ":" + attachSim.attachedPartSim.partId + ")");
  }
  }
  }
 
  if (allSources.Count > 0)
  {
  if (log != null)
  log.buf.AppendLine(indent + "Returning " + allSources.Count + " attached sources (" + this.name + ":" + this.partId + ")");
 
  return allSources;
  }
  }
  else
  {
  //if (log != null)
  // log.buf.AppendLine(indent + "Crossfeed disabled, skipping axial connected parts (" + name + ":" + partId + ")");
  }
 
  // Rule 5: If the part is fuel container for searched type of fuel (i.e. it has capability to contain that type of fuel and the fuel type was not disabled [Experiment]) and it contains fuel, it returns itself.
  // Rule 6: If the part is fuel container for searched type of fuel (i.e. it has capability to contain that type of fuel and the fuel type was not disabled) but it does not contain the requested fuel, it returns empty list. [Experiment]
  if (this.resources.HasType(type) && this.resourceFlowStates[type] != 0)
  {
  if (this.resources[type] > SimManager.RESOURCE_MIN)
  {
  allSources.Add(this);
 
  if (log != null)
  log.buf.AppendLine(indent + "Returning enabled tank as only source (" + this.name + ":" + this.partId + ")");
  }
  else
  {
  //if (log != null)
  // log.buf.AppendLine(indent + "Returning empty set, enabled tank is empty (" + name + ":" + partId + ")");
  }
 
  return allSources;
  }
 
  // Rule 7: If the part is radially attached to another part and it is child of that part in the ship's tree structure, it scans its parent and returns whatever the parent scan returned. [Experiment] [Experiment]
  if (this.parent != null)
  {
  if (this.fuelCrossFeed)
  {
  if (visited.Contains(this.parent))
  {
  //if (log != null)
  // log.buf.AppendLine(indent + "Parent part already visited, skipping (" + parent.name + ":" + parent.partId + ")");
  }
  else
  {
  allSources = this.parent.GetSourceSet(type, allParts, visited, log, indent);
  if (allSources.Count > 0)
  {
  if (log != null)
  log.buf.AppendLine(indent + "Returning " + allSources.Count + " parent sources (" + this.name + ":" + this.partId + ")");
 
  return allSources;
  }
  }
  }
  else
  {
  //if (log != null)
  // log.buf.AppendLine(indent + "Crossfeed disabled, skipping radial parent (" + name + ":" + partId + ")");
  }
  }
 
  // Rule 8: If all preceding rules failed, part returns empty list.
  //if (log != null)
  // log.buf.AppendLine(indent + "Returning empty set, no sources found (" + name + ":" + partId + ")");
 
  return allSources;
  }
 
 
  public void RemoveAttachedParts(HashSet<PartSim> partSims)
  {
  // Loop through the attached parts
  foreach (AttachNodeSim attachSim in this.attachNodes)
  {
  // If the part is in the set then "remove" it by clearing the PartSim reference
  if (partSims.Contains(attachSim.attachedPartSim))
  attachSim.attachedPartSim = null;
  }
  }
 
 
  public void DrainResources(double time)
  {
  //MonoBehaviour.print("DrainResources(" + name + ":" + partId + ", " + time + ")");
  foreach (int type in this.resourceDrains.Types)
  {
  //MonoBehaviour.print("draining " + (time * resourceDrains[type]) + " " + ResourceContainer.GetResourceName(type));
  this.resources.Add(type, -time * this.resourceDrains[type]);
  //MonoBehaviour.print(ResourceContainer.GetResourceName(type) + " left = " + resources[type]);
  }
  }
 
  public double TimeToDrainResource()
  {
  //MonoBehaviour.print("TimeToDrainResource(" + name + ":" + partId + ")");
  double time = double.MaxValue;
 
  foreach (int type in this.resourceDrains.Types)
  {
  if (this.resourceDrains[type] > 0)
  {
  time = Math.Min(time, this.resources[type] / this.resourceDrains[type]);
  //MonoBehaviour.print("type = " + ResourceContainer.GetResourceName(type) + " amount = " + resources[type] + " rate = " + resourceDrains[type] + " time = " + time);
  }
  }
 
  //if (time < double.MaxValue)
  // MonoBehaviour.print("TimeToDrainResource(" + name + ":" + partId + ") = " + time);
  return time;
  }
 
  public int DecouplerCount()
  {
  int count = 0;
  PartSim partSim = this;
  while (partSim != null)
  {
  if (partSim.isDecoupler)
  count++;
 
  partSim = partSim.parent;
  }
  return count;
  }
 
  public double GetStartMass()
  {
  return this.startMass;
  }
 
  public double GetMass()
  {
  double mass = this.baseMass;
 
  foreach (int type in this.resources.Types)
  mass += this.resources.GetResourceMass(type);
 
  return mass;
  }
 
  public ResourceContainer Resources
  {
  get
  {
  return this.resources;
  }
  }
 
  public ResourceContainer ResourceDrains
  {
  get
  {
  return this.resourceDrains;
  }
  }
 
  public String DumpPartAndParentsToBuffer(StringBuilder buffer, String prefix)
  {
  if (this.parent != null)
  {
  prefix = this.parent.DumpPartAndParentsToBuffer(buffer, prefix) + " ";
  }
 
  this.DumpPartToBuffer(buffer, prefix);
 
  return prefix;
  }
 
  public void DumpPartToBuffer(StringBuilder buffer, String prefix, List<PartSim> allParts = null)
  {
  buffer.Append(prefix);
  buffer.Append(this.name);
  buffer.AppendFormat(":[id = {0:d}, decouple = {1:d}, invstage = {2:d}", this.partId, this.decoupledInStage, this.inverseStage);
 
  buffer.AppendFormat(", vesselName = '{0}'", this.vesselName);
  buffer.AppendFormat(", vesselType = {0}", SimManager.GetVesselTypeString(this.vesselType));
  buffer.AppendFormat(", initialVesselName = '{0}'", this.initialVesselName);
 
  buffer.AppendFormat(", fuelCF = {0}", this.fuelCrossFeed);
  buffer.AppendFormat(", noCFNKey = '{0}'", this.noCrossFeedNodeKey);
 
  buffer.AppendFormat(", isSep = {0}", this.isSepratron);
 
  foreach (int type in this.resources.Types)
  buffer.AppendFormat(", {0} = {1:g6}", ResourceContainer.GetResourceName(type), this.resources[type]);
 
  if (this.attachNodes.Count > 0)
  {
  buffer.Append(", attached = <");
  this.attachNodes[0].DumpToBuffer(buffer);
  for (int i = 1; i < this.attachNodes.Count; i++)
  {
  buffer.Append(", ");
  this.attachNodes[i].DumpToBuffer(buffer);
  }
  buffer.Append(">");
  }
 
  // Add more info here
 
  buffer.Append("]\n");
 
  if (allParts != null)
  {
  String newPrefix = prefix + " ";
  foreach (PartSim partSim in allParts)
  {
  if (partSim.parent == this)
  partSim.DumpPartToBuffer(buffer, newPrefix, allParts);
  }
  }
  }
  }
  }
 
  // Kerbal Engineer Redux
  // Author: CYBUTEK
  // License: Attribution-NonCommercial-ShareAlike 3.0 Unported
 
  using System.Collections;
  using System.Collections.Generic;
 
  namespace KerbalEngineer.VesselSimulator
  {
  public class ResourceContainer
  {
  Hashtable resources = new Hashtable();
 
  public double this[int type]
  {
  get
  {
  if (this.resources.ContainsKey(type))
  return (double)this.resources[type];
 
  return 0d;
  }
  set
  {
  if (this.resources.ContainsKey(type))
  this.resources[type] = value;
  else
  this.resources.Add(type, value);
  }
  }
 
  public bool HasType(int type)
  {
  return this.resources.ContainsKey(type);
  }
 
  public List<int> Types
  {
  get
  {
  List<int> types = new List<int>();
 
  foreach (int key in this.resources.Keys)
  types.Add(key);
 
  return types;
  }
  }
 
  public double Mass
  {
  get
  {
  double mass = 0d;
 
  foreach (double resource in this.resources.Values)
  mass += resource;
 
  return mass;
  }
  }
 
  public bool Empty
  {
  get
  {
  foreach (int type in this.resources.Keys)
  {
  if ((double)this.resources[type] > SimManager.RESOURCE_MIN)
  return false;
  }
 
  return true;
  }
  }
 
  public bool EmptyOf(HashSet<int> types)
  {
  foreach (int type in types)
  {
  if (this.HasType(type) && (double)this.resources[type] > SimManager.RESOURCE_MIN)
  return false;
  }
 
  return true;
  }
 
  public void Add(int type, double amount)
  {
  if (this.resources.ContainsKey(type))
  this.resources[type] = (double)this.resources[type] + amount;
  else
  this.resources.Add(type, amount);
  }
 
  public void Reset()
  {
  this.resources = new Hashtable();
  }
 
  public void Debug()
  {
  foreach (int key in this.resources.Keys)
  {
  UnityEngine.MonoBehaviour.print(" -> " + GetResourceName(key) + " = " + this.resources[key]);
  }
  }
 
  public double GetResourceMass(int type)
  {
  double density = GetResourceDensity(type);
  return density == 0d ? 0d : (double)this.resources[type] * density;
  }
 
  public static ResourceFlowMode GetResourceFlowMode(int type)
  {
  return PartResourceLibrary.Instance.GetDefinition(type).resourceFlowMode;
  }
 
  public static ResourceTransferMode GetResourceTransferMode(int type)
  {
  return PartResourceLibrary.Instance.GetDefinition(type).resourceTransferMode;
  }
 
  public static float GetResourceDensity(int type)
  {
  return PartResourceLibrary.Instance.GetDefinition(type).density;
  }
 
  public static string GetResourceName(int type)
  {
  return PartResourceLibrary.Instance.GetDefinition(type).name;
  }
  }
  }
 
  using System;
  using System.Collections.Generic;
  using System.Diagnostics;
  using System.Linq;
  using System.Threading;
 
  using KerbalEngineer.Flight;
 
  using UnityEngine;
 
  namespace KerbalEngineer.VesselSimulator
  {
  public class SimManager: IUpdatable, IUpdateRequest
  {
  public static SimManager Instance = new SimManager();
  public const double RESOURCE_MIN = 0.0001;
 
  private static bool bRequested = false;
  private static bool bRunning = false;
  private static Stopwatch timer = new Stopwatch();
  private static long delayBetweenSims = 0;
 
  public static Stage[] Stages { get; private set; }
  public static Stage LastStage { get; private set; }
  public static String failMessage { get; private set; }
 
  public static bool dumpTree = false;
  public static bool logOutput = false;
  public static bool vectoredThrust = false;
  public static long minSimTime = 150;
  public static double Gravity { get; set; }
  public static double Atmosphere { get; set; }
  public static double Velocity { get; set; }
 
  // Support for RealFuels using reflection to check localCorrectThrust without dependency
  private static bool hasCheckedForRealFuels = false;
  private static bool hasInstalledRealFuels = false;
 
  private static System.Reflection.FieldInfo RF_ModuleEngineConfigs_locaCorrectThrust = null;
  private static System.Reflection.FieldInfo RF_ModuleHybridEngine_locaCorrectThrust = null;
  private static System.Reflection.FieldInfo RF_ModuleHybridEngines_locaCorrectThrust = null;
 
  private static void GetRealFuelsTypes()
  {
  hasCheckedForRealFuels = true;
 
  foreach (AssemblyLoader.LoadedAssembly assembly in AssemblyLoader.loadedAssemblies)
  {
  MonoBehaviour.print("Assembly:" + assembly.assembly.ToString());
 
  if (assembly.assembly.ToString().Split(',')[0] == "RealFuels")
  {
  MonoBehaviour.print("Found RealFuels mod");
 
  Type RF_ModuleEngineConfigs_Type = assembly.assembly.GetType("RealFuels.ModuleEngineConfigs");
  if (RF_ModuleEngineConfigs_Type != null)
  RF_ModuleEngineConfigs_locaCorrectThrust = RF_ModuleEngineConfigs_Type.GetField("localCorrectThrust");
 
  Type RF_ModuleHybridEngine_Type = assembly.assembly.GetType("RealFuels.ModuleHybridEngine");
  if (RF_ModuleHybridEngine_Type != null)
  RF_ModuleHybridEngine_locaCorrectThrust = RF_ModuleHybridEngine_Type.GetField("localCorrectThrust");
 
  Type RF_ModuleHybridEngines_Type = assembly.assembly.GetType("RealFuels.ModuleHybridEngines");
  if (RF_ModuleHybridEngines_Type != null)
  RF_ModuleHybridEngines_locaCorrectThrust = RF_ModuleHybridEngines_Type.GetField("localCorrectThrust");
 
  hasInstalledRealFuels = true;
  break;
  }
 
  }
 
  }
 
  public static bool DoesEngineUseCorrectedThrust(Part theEngine)
  {
  if (!hasInstalledRealFuels /*|| HighLogic.LoadedSceneIsFlight*/)
  return false;
 
  // Look for any of the Real Fuels engine modules and call the relevant method to find out
  if (RF_ModuleEngineConfigs_locaCorrectThrust != null && theEngine.Modules.Contains("ModuleEngineConfigs"))
  {
  PartModule modEngineConfigs = theEngine.Modules["ModuleEngineConfigs"];
  if (modEngineConfigs != null)
  {
  // Check the localCorrectThrust
  if ((bool)RF_ModuleEngineConfigs_locaCorrectThrust.GetValue(modEngineConfigs))
  return true;
  }
  }
 
  if (RF_ModuleHybridEngine_locaCorrectThrust != null && theEngine.Modules.Contains("ModuleHybridEngine"))
  {
  PartModule modHybridEngine = theEngine.Modules["ModuleHybridEngine"];
  if (modHybridEngine != null)
  {
  // Check the localCorrectThrust
  if ((bool)RF_ModuleHybridEngine_locaCorrectThrust.GetValue(modHybridEngine))
  return true;
  }
  }
 
  if (RF_ModuleHybridEngines_locaCorrectThrust != null && theEngine.Modules.Contains("ModuleHybridEngines"))
  {
  PartModule modHybridEngines = theEngine.Modules["ModuleHybridEngines"];
  if (modHybridEngines != null)
  {
  // Check the localCorrectThrust
  if ((bool)RF_ModuleHybridEngines_locaCorrectThrust.GetValue(modHybridEngines))
  return true;
  }
  }
 
  return false;
  }
 
 
  public static void RequestSimulation()
  {
  if (!hasCheckedForRealFuels)
  GetRealFuelsTypes();
 
  bRequested = true;
  if (!timer.IsRunning)
  timer.Start();
  }
 
  public static void TryStartSimulation()
  {
  if (bRequested && !bRunning && (HighLogic.LoadedSceneIsEditor || FlightGlobals.ActiveVessel != null) && timer.ElapsedMilliseconds > delayBetweenSims)
  {
  bRequested = false;
  timer.Reset();
  StartSimulation();
  }
  }
 
  public static bool ResultsReady()
  {
  return !bRunning;
  }
 
  private static void ClearResults()
  {
  failMessage = "";
  Stages = null;
  LastStage = null;
  }
 
  private static void StartSimulation()
  {
  try
  {
  bRunning = true;
  ClearResults();
  timer.Start();
 
  List<Part> parts = HighLogic.LoadedSceneIsEditor ? EditorLogic.SortedShipList : FlightGlobals.ActiveVessel.Parts;
 
  // Create the Simulation object in this thread
  Simulation sim = new Simulation();
 
  // This call doesn't ever fail at the moment but we'll check and return a sensible error for display
  if (sim.PrepareSimulation(parts, Gravity, Atmosphere, Velocity, dumpTree, vectoredThrust))
  {
  ThreadPool.QueueUserWorkItem(new WaitCallback(RunSimulation), sim);
  }
  else
  {
  failMessage = "PrepareSimulation failed";
  bRunning = false;
  logOutput = false;
  }
  }
  catch (Exception e)
  {
  MonoBehaviour.print("Exception in StartSimulation: " + e);
  failMessage = e.ToString();
  bRunning = false;
  logOutput = false;
  }
  dumpTree = false;
  }
 
  private static void RunSimulation(object simObject)
  {
  try
  {
  Stages = (simObject as Simulation).RunSimulation();
  if (Stages != null)
  {
  if (logOutput)
  {
  foreach (Stage stage in Stages)
  stage.Dump();
  }
  LastStage = Stages.Last();
  }
  }
  catch (Exception e)
  {
  MonoBehaviour.print("Exception in RunSimulation: " + e);
  Stages = null;
  LastStage = null;
  failMessage = e.ToString();
  }
 
  timer.Stop();
  #if TIMERS
  MonoBehaviour.print("Total simulation time: " + timer.ElapsedMilliseconds + "ms");
  #else
  if (logOutput)
  MonoBehaviour.print("Total simulation time: " + timer.ElapsedMilliseconds + "ms");
  #endif
  delayBetweenSims = minSimTime - timer.ElapsedMilliseconds;
  if (delayBetweenSims < 0)
  delayBetweenSims = 0;
 
  timer.Reset();
  timer.Start();
 
  bRunning = false;
  logOutput = false;
  }
 
  public static String GetVesselTypeString(VesselType vesselType)
  {
  switch (vesselType)
  {
  case VesselType.Debris:
  return "Debris";
  case VesselType.SpaceObject:
  return "SpaceObject";
  case VesselType.Unknown:
  return "Unknown";
  case VesselType.Probe:
  return "Probe";
  case VesselType.Rover:
  return "Rover";
  case VesselType.Lander:
  return "Lander";
  case VesselType.Ship:
  return "Ship";
  case VesselType.Station:
  return "Station";
  case VesselType.Base:
  return "Base";
  case VesselType.EVA:
  return "EVA";
  case VesselType.Flag:
  return "Flag";
  }
  return "Undefined";
  }
 
  public void Update()
  {
  TryStartSimulation();
  }
 
  public bool UpdateRequested { get; set; }
 
  public static void RequestUpdate()
  {
  Instance.UpdateRequested = true;
  }
  }
  }
 
  // Kerbal Engineer Redux
  // Author: CYBUTEK
  // License: Attribution-NonCommercial-ShareAlike 3.0 Unported
  //
  // This class has taken a lot of inspiration from r4m0n's MuMech FuelFlowSimulator. Although extremely
  // similar to the code used within MechJeb, it is a clean re-write. The similarities are a testiment
  // to how well the MuMech code works and the robustness of the simulation algorithem used.
 
  using System;
  using System.Collections.Generic;
  using System.Diagnostics;
  using System.Text;
 
  using UnityEngine;
 
  namespace KerbalEngineer.VesselSimulator
  {
  public class Simulation
  {
  private List<Part> partList;
 
  private List<PartSim> allParts;
  private List<PartSim> allFuelLines;
  private HashSet<PartSim> drainingParts;
  private List<EngineSim> allEngines;
  private List<EngineSim> activeEngines;
  private HashSet<int> drainingResources;
 
  private int lastStage = 0;
  private int currentStage = 0;
  private bool doingCurrent = false;
 
  public String vesselName;
  public VesselType vesselType;
 
  private double stageTime = 0d;
  private Vector3 vecStageDeltaV;
  private double simpleTotalThrust = 0d;
  private double totalStageThrust = 0d;
  private double totalStageActualThrust = 0d;
  private Vector3 vecThrust;
  private Vector3 vecActualThrust;
  private double totalStageFlowRate = 0d;
  private double totalStageIspFlowRate = 0d;
  private double currentisp = 0d;
  private double stageStartMass = 0d;
  private double stepStartMass = 0d;
  private double stepEndMass = 0d;
 
  private double gravity = 0d;
  private double atmosphere = 0d;
  private double velocity = 0d;
  private Stopwatch _timer = new Stopwatch();
  private const double STD_GRAVITY = 9.81d;
  private const double SECONDS_PER_DAY = 86400d;
 
  public Simulation()
  {
  if (SimManager.logOutput)
  MonoBehaviour.print("Simulation created");
  }
 
  // This function prepares the simulation by creating all the necessary data structures it will
  // need during the simulation. All required data is copied from the core game data structures
  // so that the simulation itself can be run in a background thread without having issues with
  // the core game changing the data while the simulation is running.
  public bool PrepareSimulation(List<Part> parts, double theGravity, double theAtmosphere = 0, double theVelocity = 0, bool dumpTree = false, bool vectoredThrust = false)
  {
  LogMsg log = null;
  if (SimManager.logOutput)
  {
  log = new LogMsg();
  log.buf.AppendLine("PrepareSimulation started");
  dumpTree = true;
  }
  this._timer.Start();
 
  // Store the parameters in members for ease of access in other functions
  this.partList = parts;
  this.gravity = theGravity;
  this.atmosphere = theAtmosphere;
  this.velocity = theVelocity;
  this.lastStage = Staging.lastStage;
  //MonoBehaviour.print("lastStage = " + lastStage);
 
  // Create the lists for our simulation parts
  this.allParts = new List<PartSim>();
  this.allFuelLines = new List<PartSim>();
  this.drainingParts = new HashSet<PartSim>();
  this.allEngines = new List<EngineSim>();
  this.activeEngines = new List<EngineSim>();
  this.drainingResources = new HashSet<int>();
 
  // A dictionary for fast lookup of Part->PartSim during the preparation phase
  Dictionary<Part, PartSim> partSimLookup = new Dictionary<Part, PartSim>();
 
  if (this.partList.Count > 0 && this.partList[0].vessel != null)
  {
  this.vesselName = this.partList[0].vessel.vesselName;
  this.vesselType = this.partList[0].vessel.vesselType;
  }
 
  // First we create a PartSim for each Part (giving each a unique id)
  int partId = 1;
  foreach (Part part in this.partList)
  {
  // If the part is already in the lookup dictionary then log it and skip to the next part
  if (partSimLookup.ContainsKey(part))
  {
  if (log != null)
  log.buf.AppendLine("Part " + part.name + " appears in vessel list more than once");
  continue;
  }
 
  // Create the PartSim
  PartSim partSim = new PartSim(part, partId, this.atmosphere, log);
 
  // Add it to the Part lookup dictionary and the necessary lists
  partSimLookup.Add(part, partSim);
  this.allParts.Add(partSim);
  if (partSim.isFuelLine)
  this.allFuelLines.Add(partSim);
  if (partSim.isEngine)
  partSim.CreateEngineSims(this.allEngines, this.atmosphere, this.velocity, vectoredThrust, log);
 
  partId++;
  }
 
  this.UpdateActiveEngines();
 
  // Now that all the PartSims have been created we can do any set up that needs access to other parts
  // First we set up all the parent links
  foreach (PartSim partSim in this.allParts)
  {
  partSim.SetupParent(partSimLookup, log);
  }
 
  // Then, in the VAB/SPH, we add the parent of each fuel line to the fuelTargets list of their targets
  if (HighLogic.LoadedSceneIsEditor)
  {
  foreach (PartSim partSim in this.allFuelLines)
  {
  if ((partSim.part as FuelLine).target != null)
  {
  PartSim targetSim;
  if (partSimLookup.TryGetValue((partSim.part as FuelLine).target, out targetSim))
  {
  if (log != null)
  log.buf.AppendLine("Fuel line target is " + targetSim.name + ":" + targetSim.partId);
 
  targetSim.fuelTargets.Add(partSim.parent);
  }
  else
  {
  if (log != null)
  log.buf.AppendLine("No PartSim for fuel line target (" + partSim.part.partInfo.name + ")");
  }
  }
  else
  {
  if (log != null)
  log.buf.AppendLine("Fuel line target is null");
  }
  }
  }
 
  //MonoBehaviour.print("SetupAttachNodes and count stages");
  foreach (PartSim partSim in this.allParts)
  {
  partSim.SetupAttachNodes(partSimLookup, log);
  if (partSim.decoupledInStage >= this.lastStage)
  this.lastStage = partSim.decoupledInStage + 1;
  }
 
  // And finally release the Part references from all the PartSims
  //MonoBehaviour.print("ReleaseParts");
  foreach (PartSim partSim in this.allParts)
  partSim.ReleasePart();
 
  // And dereference the core's part list
  this.partList = null;
 
  this._timer.Stop();
  if (log != null)
  {
  log.buf.AppendLine("PrepareSimulation: " + this._timer.ElapsedMilliseconds + "ms");
  log.Flush();
  }
 
  if (dumpTree)
  this.Dump();
 
  return true;
  }
 
 
  // This function runs the simulation and returns a newly created array of Stage objects
  public Stage[] RunSimulation()
  {
  if (SimManager.logOutput)
  MonoBehaviour.print("RunSimulation started");
  this._timer.Start();
  // Start with the last stage to simulate
  // (this is in a member variable so it can be accessed by AllowedToStage and ActivateStage)
  this.currentStage = this.lastStage;
 
  LogMsg log = null;
  if (SimManager.logOutput)
  log = new LogMsg();
 
  // Work out which engines would be active if just doing the staging and if this is different to the
  // currently active engines then generate an extra stage
  // Loop through all the engines
  foreach (EngineSim engine in this.allEngines)
  {
  if (log != null)
  log.buf.AppendLine("Testing engine mod of " + engine.partSim.name + ":" + engine.partSim.partId);
  bool bActive = engine.isActive;
  bool bStage = (engine.partSim.inverseStage >= this.currentStage);
  if (log != null)
  log.buf.AppendLine("bActive = " + bActive + " bStage = " + bStage);
  if (HighLogic.LoadedSceneIsFlight)
  {
  if (bActive != bStage)
  {
  // If the active state is different to the state due to staging
  if (log != null)
  log.buf.AppendLine("Need to do current active engines first");
 
  this.doingCurrent = true;
  this.currentStage++;
  break;
  }
  }
  else
  {
  if (bStage)
  {
  if (log != null)
  log.buf.AppendLine("Marking as active");
 
  engine.isActive = true;
  }
  }
  }
 
  if (log != null)
  log.Flush();
 
  // Create the array of stages that will be returned
  Stage[] stages = new Stage[this.currentStage + 1];
 
  // Loop through the stages
  while (this.currentStage >= 0)
  {
  if (log != null)
  {
  log.buf.AppendLine("Simulating stage " + this.currentStage);
  log.buf.AppendLine("ShipMass = " + this.ShipMass);
  log.Flush();
  this._timer.Reset();
  this._timer.Start();
  }
 
  // Update active engines and resource drains
  this.UpdateResourceDrains();
 
  // Create the Stage object for this stage
  Stage stage = new Stage();
 
  this.stageTime = 0d;
  this.vecStageDeltaV = Vector3.zero;
  this.stageStartMass = this.ShipMass;
  this.stepStartMass = this.stageStartMass;
  this.stepEndMass = 0;
 
  this.CalculateThrustAndISP();
 
  // Store various things in the Stage object
  stage.thrust = this.totalStageThrust;
  //MonoBehaviour.print("stage.thrust = " + stage.thrust);
  stage.thrustToWeight = this.totalStageThrust / (this.stageStartMass * this.gravity);
  stage.maxThrustToWeight = stage.thrustToWeight;
  //MonoBehaviour.print("StageMass = " + stageStartMass);
  //MonoBehaviour.print("Initial maxTWR = " + stage.maxThrustToWeight);
  stage.actualThrust = this.totalStageActualThrust;
  stage.actualThrustToWeight = this.totalStageActualThrust / (this.stageStartMass * this.gravity);
 
  // Calculate the cost and mass of this stage
  foreach (PartSim partSim in this.allParts)
  {
  if (partSim.decoupledInStage == this.currentStage - 1)
  {
  stage.cost += partSim.cost;
  stage.mass += partSim.GetStartMass();
  }
  }
 
  if (log != null)
  MonoBehaviour.print("Stage setup took " + this._timer.ElapsedMilliseconds + "ms");
 
  // Now we will loop until we are allowed to stage
  int loopCounter = 0;
  while (!this.AllowedToStage())
  {
  loopCounter++;
  //MonoBehaviour.print("loop = " + loopCounter);
 
  // Calculate how long each draining tank will take to drain and run for the minimum time
  double resourceDrainTime = double.MaxValue;
  PartSim partMinDrain = null;
  foreach (PartSim partSim in this.drainingParts)
  {
  double time = partSim.TimeToDrainResource();
  if (time < resourceDrainTime)
  {
  resourceDrainTime = time;
  partMinDrain = partSim;
  }
  }
 
  if (log != null)
  MonoBehaviour.print("Drain time = " + resourceDrainTime + " (" + partMinDrain.name + ":" + partMinDrain.partId + ")");
 
  foreach (PartSim partSim in this.drainingParts)
  partSim.DrainResources(resourceDrainTime);
 
  // Get the mass after draining
  this.stepEndMass = this.ShipMass;
  this.stageTime += resourceDrainTime;
 
  double stepEndTWR = this.totalStageThrust / (this.stepEndMass * this.gravity);
  //MonoBehaviour.print("After drain mass = " + stepEndMass);
  //MonoBehaviour.print("currentThrust = " + totalStageThrust);
  //MonoBehaviour.print("currentTWR = " + stepEndTWR);
  if (stepEndTWR > stage.maxThrustToWeight)
  stage.maxThrustToWeight = stepEndTWR;
 
  //MonoBehaviour.print("newMaxTWR = " + stage.maxThrustToWeight);
 
  // If we have drained anything and the masses make sense then add this step's deltaV to the stage total
  if (resourceDrainTime > 0d && this.stepStartMass > this.stepEndMass && this.stepStartMass > 0d && this.stepEndMass > 0d)
  this.vecStageDeltaV += this.vecThrust * (float)((this.currentisp * STD_GRAVITY * Math.Log(this.stepStartMass / this.stepEndMass)) / this.simpleTotalThrust);
 
  // Update the active engines and resource drains for the next step
  this.UpdateResourceDrains();
 
  // Recalculate the current thrust and isp for the next step
  this.CalculateThrustAndISP();
 
  // Check if we actually changed anything
  if (this.stepStartMass == this.stepEndMass)
  {
  //MonoBehaviour.print("No change in mass");
  break;
  }
 
  // Check to stop rampant looping
  if (loopCounter == 1000)
  {
  MonoBehaviour.print("exceeded loop count");
  MonoBehaviour.print("stageStartMass = " + this.stageStartMass);
  MonoBehaviour.print("stepStartMass = " + this.stepStartMass);
  MonoBehaviour.print("StepEndMass = " + this.stepEndMass);
  break;
  }
 
  // The next step starts at the mass this one ended at
  this.stepStartMass = this.stepEndMass;
  }
 
  // Store more values in the Stage object and stick it in the array
 
  // Store the magnitude of the deltaV vector
  stage.deltaV = this.vecStageDeltaV.magnitude;
 
  // Recalculate effective stage isp from the stage deltaV (flip the standard deltaV calculation around)
  // Note: If the mass doesn't change then this is a divide by zero
  if (this.stageStartMass != this.stepStartMass)
  stage.isp = stage.deltaV / (STD_GRAVITY * Math.Log(this.stageStartMass / this.stepStartMass));
  else
  stage.isp = 0;
 
  // Zero stage time if more than a day (this should be moved into the window code)
  stage.time = (this.stageTime < SECONDS_PER_DAY) ? this.stageTime : 0d;
  stage.number = this.doingCurrent ? -1 : this.currentStage; // Set the stage number to -1 if doing current engines
  stages[this.currentStage] = stage;
 
  // Now activate the next stage
  this.currentStage--;
  this.doingCurrent = false;
 
  if (log != null)
  {
  // Log how long the stage took
  this._timer.Stop();
  MonoBehaviour.print("Simulating stage took " + this._timer.ElapsedMilliseconds + "ms");
  stage.Dump();
  this._timer.Reset();
  this._timer.Start();
  }
 
  // Activate the next stage
  this.ActivateStage();
 
  if (log != null)
  {
  // Log how long it took to activate
  this._timer.Stop();
  MonoBehaviour.print("ActivateStage took " + this._timer.ElapsedMilliseconds + "ms");
  }
  }
 
  // Now we add up the various total fields in the stages
  for (int i = 0; i < stages.Length; i++)
  {
  // For each stage we total up the cost, mass, deltaV and time for this stage and all the stages above
  for (int j = i; j >= 0; j--)
  {
  stages[i].totalCost += stages[j].cost;
  stages[i].totalMass += stages[j].mass;
  stages[i].totalDeltaV += stages[j].deltaV;
  stages[i].totalTime += stages[j].time;
  }
  // We also total up the deltaV for stage and all stages below
  for (int j = i; j < stages.Length; j++)
  {
  stages[i].inverseTotalDeltaV += stages[j].deltaV;
  }
 
  // Zero the total time if the value will be huge (24 hours?) to avoid the display going weird
  // (this should be moved into the window code)
  if (stages[i].totalTime > SECONDS_PER_DAY)
  stages[i].totalTime = 0d;
  }
 
  if (log != null)
  {
  this._timer.Stop();
  MonoBehaviour.print("RunSimulation: " + this._timer.ElapsedMilliseconds + "ms");
  }
 
  return stages;
  }
 
  // This function simply rebuilds the active engines by testing the isActive flag of all the engines
  private void UpdateActiveEngines()
  {
  this.activeEngines.Clear();
  foreach (EngineSim engine in this.allEngines)
  {
  if (engine.isActive)
  this.activeEngines.Add(engine);
  }
  }
 
  private void CalculateThrustAndISP()
  {
  // Reset all the values
  this.vecThrust = Vector3.zero;
  this.vecActualThrust = Vector3.zero;
  this.simpleTotalThrust = 0d;
  this.totalStageThrust = 0d;
  this.totalStageActualThrust = 0d;
  this.totalStageFlowRate = 0d;
  this.totalStageIspFlowRate = 0d;
 
  // Loop through all the active engines totalling the thrust, actual thrust and mass flow rates
  // The thrust is totalled as vectors
  foreach (EngineSim engine in this.activeEngines)
  {
  this.simpleTotalThrust += engine.thrust;
  this.vecThrust += ((float)engine.thrust * engine.thrustVec);
  this.vecActualThrust += ((float)engine.actualThrust * engine.thrustVec);
 
  this.totalStageFlowRate += engine.ResourceConsumptions.Mass;
  this.totalStageIspFlowRate += engine.ResourceConsumptions.Mass * engine.isp;
  }
 
  //MonoBehaviour.print("vecThrust = " + vecThrust.ToString() + " magnitude = " + vecThrust.magnitude);
 
  this.totalStageThrust = this.vecThrust.magnitude;
  this.totalStageActualThrust = this.vecActualThrust.magnitude;
 
  // Calculate the effective isp at this point
  if (this.totalStageFlowRate > 0d && this.totalStageIspFlowRate > 0d)
  this.currentisp = this.totalStageIspFlowRate / this.totalStageFlowRate;
  else
  this.currentisp = 0;
  }
 
  // This function does all the hard work of working out which engines are burning, which tanks are being drained
  // and setting the drain rates
  private void UpdateResourceDrains()
  {
  // Update the active engines
  this.UpdateActiveEngines();
 
  // Empty the draining resources set
  this.drainingResources.Clear();
 
  // Reset the resource drains of all draining parts
  foreach (PartSim partSim in this.drainingParts)
  partSim.ResourceDrains.Reset();
 
  // Empty the draining parts set
  this.drainingParts.Clear();
 
  // Loop through all the active engine modules
  foreach (EngineSim engine in this.activeEngines)
  {
  // Set the resource drains for this engine
  if (engine.SetResourceDrains(this.allParts, this.allFuelLines, this.drainingParts))
  {
  // If it is active then add the consumed resource types to the set
  foreach (int type in engine.ResourceConsumptions.Types)
  this.drainingResources.Add(type);
  }
  }
 
  // Update the active engines again to remove any engines that have no fuel supply
  this.UpdateActiveEngines();
 
  if (SimManager.logOutput)
  {
  StringBuilder buffer = new StringBuilder(1024);
  buffer.AppendFormat("Active engines = {0:d}\n", this.activeEngines.Count);
  int i = 0;
  foreach (EngineSim engine in this.activeEngines)
  engine.DumpEngineToBuffer(buffer, "Engine " + (i++) + ":");
  MonoBehaviour.print(buffer);
  }
  }
 
  // This function works out if it is time to stage
  private bool AllowedToStage()
  {
  StringBuilder buffer = null;
  if (SimManager.logOutput)
  {
  buffer = new StringBuilder(1024);
  buffer.AppendLine("AllowedToStage");
  buffer.AppendFormat("currentStage = {0:d}\n", this.currentStage);
  }
 
  if (this.activeEngines.Count == 0)
  {
  if (SimManager.logOutput)
  {
  buffer.AppendLine("No active engines => true");
  MonoBehaviour.print(buffer);
  }
 
  return true;
  }
 
  bool partDecoupled = false;
  bool engineDecoupled = false;
 
  foreach (PartSim partSim in this.allParts)
  {
  //partSim.DumpPartToBuffer(buffer, "Testing: ", allParts);
  //buffer.AppendFormat("isSepratron = {0}\n", partSim.isSepratron ? "true" : "false");
  if (partSim.decoupledInStage == (this.currentStage - 1) && (!partSim.isSepratron || partSim.decoupledInStage < partSim.inverseStage))
  {
  partDecoupled = true;
 
  if (!partSim.Resources.EmptyOf(this.drainingResources))
  {
  if (SimManager.logOutput)
  {
  partSim.DumpPartToBuffer(buffer, "Decoupled part not empty => false: ");
  MonoBehaviour.print(buffer);
  }
 
  return false;
  }
 
  if (partSim.isEngine)
  {
  foreach (EngineSim engine in this.activeEngines)
  {
  if (engine.partSim == partSim)
  {
  if (SimManager.logOutput)
  {
  partSim.DumpPartToBuffer(buffer, "Decoupled part is active engine => false: ");
  MonoBehaviour.print(buffer);
  }
  return false;
  }
  }
 
  engineDecoupled = true;
  }
  }
  }
 
  if (!partDecoupled)
  {
  if (SimManager.logOutput)
  {
  buffer.AppendLine("No engine decoupled but something is => false");
  MonoBehaviour.print(buffer);
  }
  return false;
  }
 
  if (this.currentStage > 0 && !this.doingCurrent)
  {
  if (SimManager.logOutput)
  {
  buffer.AppendLine("Current stage > 0 && !doingCurrent => true");
  MonoBehaviour.print(buffer);
  }
  return true;
  }
 
  if (SimManager.logOutput)
  {
  buffer.AppendLine("Returning false");
  MonoBehaviour.print(buffer);
  }
  return false;
  }
 
  // This function activates the next stage
  // currentStage must be updated before calling this function
  private void ActivateStage()
  {
  // Build a set of all the parts that will be decoupled
  HashSet<PartSim> decoupledParts = new HashSet<PartSim>();
  foreach (PartSim partSim in this.allParts)
  {
  if (partSim.decoupledInStage >= this.currentStage)
  decoupledParts.Add(partSim);
  }
 
  foreach (PartSim partSim in decoupledParts)
  {
  // Remove it from the all parts list
  this.allParts.Remove(partSim);
  if (partSim.isEngine)
  {
  // If it is an engine then loop through all the engine modules and remove all the ones from this engine part
  for (int i = this.allEngines.Count - 1; i >= 0; i--)
  {
  if (this.allEngines[i].partSim == partSim)
  this.allEngines.RemoveAt(i);
  }
  }
  // If it is a fuel line then remove it from the list of all fuel lines
  if (partSim.isFuelLine)
  this.allFuelLines.Remove(partSim);
  }
 
  // Loop through all the (remaining) parts
  foreach (PartSim partSim in this.allParts)
  {
  // Ask the part to remove all the parts that are decoupled
  partSim.RemoveAttachedParts(decoupledParts);
  }
 
  // Now we loop through all the engines and activate those that are ignited in this stage
  foreach(EngineSim engine in this.allEngines)
  {
  if (engine.partSim.inverseStage == this.currentStage)
  engine.isActive = true;
  }
  }
 
  private double ShipStartMass
  {
  get
  {
  double mass = 0d;
 
  foreach (PartSim partSim in this.allParts)
  {
  mass += partSim.GetStartMass();
  }
 
  return mass;
  }
  }
 
  private double ShipMass
  {
  get
  {
  double mass = 0d;
 
  foreach (PartSim partSim in this.allParts)
  {
  mass += partSim.GetMass();
  }
 
  return mass;
  }
  }
 
  public void Dump()
  {
  StringBuilder buffer = new StringBuilder(1024);
  buffer.AppendFormat("Part count = {0:d}\n", this.allParts.Count);
 
  // Output a nice tree view of the rocket
  if (this.allParts.Count > 0)
  {
  PartSim root = this.allParts[0];
  while (root.parent != null)
  root = root.parent;
 
  if (root.hasVessel)
  buffer.AppendFormat("vesselName = '{0}' vesselType = {1}\n", this.vesselName, SimManager.GetVesselTypeString(this.vesselType));
 
  root.DumpPartToBuffer(buffer, "", this.allParts);
  }
 
  MonoBehaviour.print(buffer);
  }
  }
  }
 
  // Kerbal Engineer Redux
  // Author: CYBUTEK
  // License: Attribution-NonCommercial-ShareAlike 3.0 Unported
 
  using System.Text;
 
  using UnityEngine;
 
  namespace KerbalEngineer.VesselSimulator
  {
  public class Stage
  {
  public int number = 0;
  public float cost = 0;
  public float totalCost = 0;
  public double time = 0f;
  public double totalTime = 0f;
  public double mass = 0f;
  public double totalMass = 0f;
  public double isp = 0f;
  public double thrust = 0f;
  public double actualThrust = 0f;
  public double thrustToWeight = 0f;
  public double maxThrustToWeight = 0f;
  public double actualThrustToWeight = 0f;
  public double deltaV = 0f;
  public double totalDeltaV = 0f;
  public double inverseTotalDeltaV = 0f;
 
  public void Dump()
  {
  StringBuilder str = new StringBuilder("", 512);
  str.AppendFormat("number : {0:d}\n", this.number);
  str.AppendFormat("cost : {0:d}\n", this.cost);
  str.AppendFormat("totalCost : {0:d}\n", this.totalCost);
  str.AppendFormat("time : {0:g6}\n", this.time);
  str.AppendFormat("totalTime : {0:g6}\n", this.totalTime);
  str.AppendFormat("mass : {0:g6}\n", this.mass);
  str.AppendFormat("totalMass : {0:g6}\n", this.totalMass);
  str.AppendFormat("isp : {0:g6}\n", this.isp);
  str.AppendFormat("thrust : {0:g6}\n", this.thrust);
  str.AppendFormat("actualThrust : {0:g6}\n", this.actualThrust);
  str.AppendFormat("thrustToWeight: {0:g6}\n", this.thrustToWeight);
  str.AppendFormat("maxTWR : {0:g6}\n", this.maxThrustToWeight);
  str.AppendFormat("actualTWR : {0:g6}\n", this.actualThrustToWeight);
  str.AppendFormat("deltaV : {0:g6}\n", this.deltaV);
  str.AppendFormat("totalDeltaV : {0:g6}\n", this.totalDeltaV);
  str.AppendFormat("invTotDeltaV : {0:g6}\n", this.inverseTotalDeltaV);
 
  MonoBehaviour.print(str);
  }
  }
  }
 
 Binary files a/Output/KerbalEngineer/KerbalEngineer.dll and b/Output/KerbalEngineer/KerbalEngineer.dll differ