Merge branch 'sarbian'
Merge branch 'sarbian'

// //
// 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/>.
// //
   
namespace KerbalEngineer.Editor namespace KerbalEngineer.Editor
{ {
#region Using Directives #region Using Directives
   
using System; using System;
using System.Linq; using System.Linq;
using Extensions; using Extensions;
using Flight; using Flight;
using Helpers; using Helpers;
using Settings; using Settings;
using UIControls; using UIControls;
using UnityEngine; using UnityEngine;
using VesselSimulator; using VesselSimulator;
   
#endregion #endregion
   
[KSPAddon(KSPAddon.Startup.EditorAny, false)] [KSPAddon(KSPAddon.Startup.EditorAny, false)]
public class BuildAdvanced : MonoBehaviour public class BuildAdvanced : MonoBehaviour
{ {
#region Fields #region Fields
   
private GUIStyle areaSettingStyle; private GUIStyle areaSettingStyle;
private GUIStyle areaStyle; private GUIStyle areaStyle;
private float atmosphericPercentage = 1.0f; private float atmosphericPercentage = 1.0f;
private float atmosphericVelocity; private float atmosphericMach;
private GUIStyle bodiesButtonActiveStyle; private GUIStyle bodiesButtonActiveStyle;
private GUIStyle bodiesButtonStyle; private GUIStyle bodiesButtonStyle;
private DropDown bodiesList; private DropDown bodiesList;
private Rect bodiesListPosition; private Rect bodiesListPosition;
private GUIStyle buttonStyle; private GUIStyle buttonStyle;
private int compactCheck; private int compactCheck;
private bool compactCollapseRight; private bool compactCollapseRight;
private bool compactMode; private bool compactMode;
private float compactRight; private float compactRight;
private bool hasChanged; private bool hasChanged;
private GUIStyle infoStyle; private GUIStyle infoStyle;
private bool isEditorLocked; private bool isEditorLocked;
private int numberOfStages; private int numberOfStages;
private Rect position = new Rect(265.0f, 45.0f, 0, 0); private Rect position = new Rect(265.0f, 45.0f, 0, 0);
private GUIStyle settingAtmoStyle; private GUIStyle settingAtmoStyle;
private GUIStyle settingStyle; private GUIStyle settingStyle;
private bool showAllStages; private bool showAllStages;
private bool showAtmosphericDetails; private bool showAtmosphericDetails;
private bool showSettings; private bool showSettings;
private Stage[] stages; private Stage[] stages;
private GUIStyle titleStyle; private GUIStyle titleStyle;
private bool visible = true; private bool visible = true;
private GUIStyle windowStyle; private GUIStyle windowStyle;
   
#endregion #endregion
   
#region Properties #region Properties
   
/// <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; }
   
/// <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 ShowAtmosphericDetails public bool ShowAtmosphericDetails
{ {
get { return this.showAtmosphericDetails; } get { return this.showAtmosphericDetails; }
set { this.showAtmosphericDetails = value; } set { this.showAtmosphericDetails = value; }
} }
   
/// <summary> /// <summary>
/// Gets and sets whether to show the settings display. /// Gets and sets whether to show the settings display.
/// </summary> /// </summary>
public bool ShowSettings public bool ShowSettings
{ {
get { return this.showSettings; } get { return this.showSettings; }
set { this.showSettings = value; } set { this.showSettings = 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 Methods #region Methods
   
protected void Awake() protected void Awake()
{ {
try try
{ {
Instance = this; Instance = this;
this.bodiesList = this.gameObject.AddComponent<DropDown>(); this.bodiesList = this.gameObject.AddComponent<DropDown>();
this.bodiesList.DrawCallback = this.DrawBodiesList; this.bodiesList.DrawCallback = this.DrawBodiesList;
this.Load(); this.Load();
   
SimManager.UpdateModSettings(); SimManager.UpdateModSettings();
SimManager.OnReady -= this.GetStageInfo; SimManager.OnReady -= this.GetStageInfo;
SimManager.OnReady += this.GetStageInfo; SimManager.OnReady += this.GetStageInfo;
} }
catch (Exception ex) catch (Exception ex)
{ {
Logger.Exception(ex); Logger.Exception(ex);
} }
} }
   
/// <summary> /// <summary>
/// Saves the settings when this object is destroyed. /// Saves the settings when this object is destroyed.
/// </summary> /// </summary>
protected void OnDestroy() protected 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.position.x); handler.Set("windowPositionX", this.position.x);
handler.Set("windowPositionY", this.position.y); handler.Set("windowPositionY", this.position.y);
handler.Set("compactMode", this.compactMode); handler.Set("compactMode", this.compactMode);
handler.Set("compactCollapseRight", this.compactCollapseRight); handler.Set("compactCollapseRight", this.compactCollapseRight);
handler.Set("showAllStages", this.showAllStages); handler.Set("showAllStages", this.showAllStages);
handler.Set("showAtmosphericDetails", this.showAtmosphericDetails); handler.Set("showAtmosphericDetails", this.showAtmosphericDetails);
handler.Set("showSettings", this.showSettings); handler.Set("showSettings", this.showSettings);
handler.Set("selectedBodyName", CelestialBodies.SelectedBody.Name); handler.Set("selectedBodyName", CelestialBodies.SelectedBody.Name);
handler.Save("BuildAdvanced.xml"); handler.Save("BuildAdvanced.xml");
GuiDisplaySize.OnSizeChanged -= this.OnSizeChanged; GuiDisplaySize.OnSizeChanged -= this.OnSizeChanged;
} }
catch (Exception ex) catch (Exception ex)
{ {
Logger.Exception(ex); Logger.Exception(ex);
} }
} }
   
protected void OnGUI() protected void OnGUI()
{ {
try try
{ {
if (!this.visible || EditorLogic.fetch == null || EditorLogic.fetch.ship.parts.Count == 0 || EditorLogic.fetch.editorScreen != EditorScreen.Parts) if (!this.visible || EditorLogic.fetch == null || EditorLogic.fetch.ship.parts.Count == 0 || EditorLogic.fetch.editorScreen != EditorScreen.Parts)
{ {
return; return;
} }
   
if (this.stages == null) if (this.stages == null)
{ {
return; return;
} }
   
// 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 + (this.showAtmosphericDetails ? " (ATMOS.)" : String.Empty); var title = !this.compactMode ? "KERBAL ENGINEER REDUX " + EngineerGlobals.AssemblyVersion : "K.E.R. " + EngineerGlobals.AssemblyVersion + (this.showAtmosphericDetails ? " (ATMOS.)" : String.Empty);
   
// 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 = this.stages.Count(stage => this.showAllStages || stage.deltaV > 0); var stageCount = this.stages.Count(stage => this.showAllStages || stage.deltaV > 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.position.width = 0; this.position.width = 0;
this.position.height = 0; this.position.height = 0;
} }
   
GUI.skin = null; GUI.skin = null;
this.position = GUILayout.Window(this.GetInstanceID(), this.position, this.Window, title, this.windowStyle).ClampToScreen(); this.position = GUILayout.Window(this.GetInstanceID(), this.position, this.Window, title, this.windowStyle).ClampToScreen();
   
if (this.compactCheck > 0 && this.compactCollapseRight) if (this.compactCheck > 0 && this.compactCollapseRight)
{ {
this.position.x = this.compactRight - this.position.width; this.position.x = this.compactRight - this.position.width;
this.compactCheck--; this.compactCheck--;
} }
else if (this.compactCheck > 0) else if (this.compactCheck > 0)
{ {
this.compactCheck = 0; this.compactCheck = 0;
} }
   
// 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.Exception(ex); Logger.Exception(ex);
} }
} }
   
protected void Start() protected void Start()
{ {
try try
{ {
this.InitialiseStyles(); this.InitialiseStyles();
GuiDisplaySize.OnSizeChanged += this.OnSizeChanged; GuiDisplaySize.OnSizeChanged += this.OnSizeChanged;
} }
catch (Exception ex) catch (Exception ex)
{ {
Logger.Exception(ex); Logger.Exception(ex);
} }
} }
   
protected void Update() protected void Update()
{ {
try try
{ {
if (Input.GetKeyDown(KeyBinder.EditorShowHide)) if (Input.GetKeyDown(KeyBinder.EditorShowHide))
{ {
this.visible = !this.visible; this.visible = !this.visible;
if (!this.visible) if (!this.visible)
{ {
this.EditorLock(false); this.EditorLock(false);
} }
} }
   
if (!this.visible || EditorLogic.fetch == null || EditorLogic.fetch.ship.parts.Count == 0) if (!this.visible || EditorLogic.fetch == null || EditorLogic.fetch.ship.parts.Count == 0)
{ {
this.bodiesList.enabled = false; this.bodiesList.enabled = false;
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.SelectedBody.Gravity; SimManager.Gravity = CelestialBodies.SelectedBody.Gravity;
   
if (this.showAtmosphericDetails) if (this.showAtmosphericDetails)
{ {
SimManager.Atmosphere = CelestialBodies.SelectedBody.Atmosphere * 0.01d * this.atmosphericPercentage; SimManager.Atmosphere = CelestialBodies.SelectedBody.Atmosphere * 0.01d * this.atmosphericPercentage;
} }
else else
{ {
SimManager.Atmosphere = 0; SimManager.Atmosphere = 0;
} }
   
SimManager.Velocity = this.atmosphericVelocity; SimManager.Mach = this.atmosphericMach;
   
SimManager.RequestSimulation(); SimManager.RequestSimulation();
SimManager.TryStartSimulation(); SimManager.TryStartSimulation();
} }
catch (Exception ex) catch (Exception ex)
{ {
Logger.Exception(ex, "BuildAdvanced->Update"); Logger.Exception(ex, "BuildAdvanced->Update");
} }
} }
   
/// <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.position.MouseIsOver() || this.bodiesList.Position.MouseIsOver()) && !this.isEditorLocked) if ((this.position.MouseIsOver() || this.bodiesList.Position.MouseIsOver()) && !this.isEditorLocked)
{ {
this.EditorLock(true); this.EditorLock(true);
} }
else if (!this.position.MouseIsOver() && !this.bodiesList.Position.MouseIsOver() && this.isEditorLocked) else if (!this.position.MouseIsOver() && !this.bodiesList.Position.MouseIsOver() && this.isEditorLocked)
{ {
this.EditorLock(false); this.EditorLock(false);
} }
} }
   
/// <summary> /// <summary>
/// Draws the atmospheric settings. /// Draws the atmospheric settings.
/// </summary> /// </summary>
private void DrawAtmosphericDetails() private void DrawAtmosphericDetails()
{ {
GUILayout.BeginHorizontal(); GUILayout.BeginHorizontal();
GUILayout.BeginVertical(); GUILayout.BeginVertical();
GUILayout.Label("Pressure: " + (this.atmosphericPercentage * 100.0f).ToString("F1") + "%", this.settingAtmoStyle, GUILayout.Width(125.0f * GuiDisplaySize.Offset)); GUILayout.Label("Pressure: " + (this.atmosphericPercentage * 100.0f).ToString("F1") + "%", this.settingAtmoStyle, GUILayout.Width(125.0f * GuiDisplaySize.Offset));
GUI.skin = HighLogic.Skin; GUI.skin = HighLogic.Skin;
this.atmosphericPercentage = GUILayout.HorizontalSlider(this.atmosphericPercentage, 0, 1.0f); this.atmosphericPercentage = GUILayout.HorizontalSlider(this.atmosphericPercentage, 0, 1.0f);
GUI.skin = null; GUI.skin = null;
GUILayout.EndVertical(); GUILayout.EndVertical();
   
GUILayout.Space(5.0f); GUILayout.Space(5.0f);
   
GUILayout.BeginVertical(); GUILayout.BeginVertical();
GUILayout.Label("Velocity: " + this.atmosphericVelocity.ToString("F1") + "m/s", this.settingAtmoStyle, GUILayout.Width(125.0f * GuiDisplaySize.Offset)); GUILayout.Label("Mach: " + this.atmosphericMach.ToString("F1") + "m/s", this.settingAtmoStyle, GUILayout.Width(125.0f * GuiDisplaySize.Offset));
GUI.skin = HighLogic.Skin; GUI.skin = HighLogic.Skin;
this.atmosphericVelocity = GUILayout.HorizontalSlider(this.atmosphericVelocity, 0, 2500f); this.atmosphericMach = GUILayout.HorizontalSlider(this.atmosphericMach, 0, 25f); // the game limits mach to 50 but I did not see curve with more than 25
GUI.skin = null; GUI.skin = null;
GUILayout.EndVertical(); GUILayout.EndVertical();
GUILayout.EndHorizontal(); GUILayout.EndHorizontal();
} }
   
private void DrawBodiesList() private void DrawBodiesList()
{ {
if (CelestialBodies.SystemBody == CelestialBodies.SelectedBody) if (CelestialBodies.SystemBody == CelestialBodies.SelectedBody)
{ {
this.DrawBody(CelestialBodies.SystemBody); this.DrawBody(CelestialBodies.SystemBody);
} }
else else
{ {
foreach (var body in CelestialBodies.SystemBody.Children) foreach (var body in CelestialBodies.SystemBody.Children)
{ {
this.DrawBody(body); this.DrawBody(body);
} }
} }
} }
   
private void DrawBody(CelestialBodies.BodyInfo bodyInfo, int depth = 0) private void DrawBody(CelestialBodies.BodyInfo bodyInfo, int depth = 0)
{ {
GUILayout.BeginHorizontal(); GUILayout.BeginHorizontal();
GUILayout.Space(20.0f * depth); GUILayout.Space(20.0f * depth);
if (GUILayout.Button(bodyInfo.Children.Count > 0 ? bodyInfo.Name + " [" + bodyInfo.Children.Count + "]" : bodyInfo.Name, bodyInfo.Selected && bodyInfo.SelectedDepth == 0 ? this.bodiesButtonActiveStyle : this.bodiesButtonStyle)) if (GUILayout.Button(bodyInfo.Children.Count > 0 ? bodyInfo.Name + " [" + bodyInfo.Children.Count + "]" : bodyInfo.Name, bodyInfo.Selected && bodyInfo.SelectedDepth == 0 ? this.bodiesButtonActiveStyle : this.bodiesButtonStyle))
{ {
CelestialBodies.SetSelectedBody(bodyInfo.Name); CelestialBodies.SetSelectedBody(bodyInfo.Name);
this.bodiesList.Resize = true; this.bodiesList.Resize = true;
} }
GUILayout.EndHorizontal(); GUILayout.EndHorizontal();
   
if (bodyInfo.Selected) if (bodyInfo.Selected)
{ {
foreach (var body in bodyInfo.Children) foreach (var body in bodyInfo.Children)
{ {
this.DrawBody(body, depth + 1); this.DrawBody(body, depth + 1);
} }
} }
} }
   
/// <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 * GuiDisplaySize.Offset)); GUILayout.BeginVertical(GUILayout.Width(75.0f * GuiDisplaySize.Offset));
GUILayout.Label("BURN", this.titleStyle); GUILayout.Label("BURN", this.titleStyle);
foreach (var stage in this.stages) foreach (var stage in this.stages)
{ {
if (this.showAllStages || stage.deltaV > 0) if (this.showAllStages || stage.deltaV > 0)
{ {
GUILayout.Label(TimeFormatter.ConvertToString(stage.time), this.infoStyle); GUILayout.Label(TimeFormatter.ConvertToString(stage.time), 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(110.0f * GuiDisplaySize.Offset)); GUILayout.BeginVertical(GUILayout.Width(110.0f * GuiDisplaySize.Offset));
GUILayout.Label("COST", this.titleStyle); GUILayout.Label("COST", this.titleStyle);
foreach (var stage in this.stages) foreach (var stage in this.stages)
{ {
if (this.showAllStages || stage.deltaV > 0) if (this.showAllStages || stage.deltaV > 0)
{ {
GUILayout.Label(Units.Cost(stage.cost, stage.totalCost), this.infoStyle); GUILayout.Label(Units.Cost(stage.cost, stage.totalCost), 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 * GuiDisplaySize.Offset)); GUILayout.BeginVertical(GUILayout.Width(100.0f * GuiDisplaySize.Offset));
GUILayout.Label("DELTA-V", this.titleStyle); GUILayout.Label("DELTA-V", this.titleStyle);
foreach (var stage in this.stages) foreach (var stage in this.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 specific impluse column. /// Draws the specific impluse column.
/// </summary> /// </summary>
private void DrawIsp() private void DrawIsp()
{ {
GUILayout.BeginVertical(GUILayout.Width(75.0f * GuiDisplaySize.Offset)); GUILayout.BeginVertical(GUILayout.Width(75.0f * GuiDisplaySize.Offset));
GUILayout.Label("ISP", this.titleStyle); GUILayout.Label("ISP", this.titleStyle);
foreach (var stage in this.stages) foreach (var stage in this.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 mass column. /// Draws the mass column.
/// </summary> /// </summary>
private void DrawMass() private void DrawMass()
{ {
GUILayout.BeginVertical(GUILayout.Width(110.0f * GuiDisplaySize.Offset)); GUILayout.BeginVertical(GUILayout.Width(110.0f * GuiDisplaySize.Offset));
GUILayout.Label("MASS", this.titleStyle); GUILayout.Label("MASS", this.titleStyle);
foreach (var stage in this.stages) foreach (var stage in this.stages)
{ {
if (this.showAllStages || stage.deltaV > 0) if (this.showAllStages || stage.deltaV > 0)
{ {
GUILayout.Label(Units.ToMass(stage.mass, stage.totalMass), this.infoStyle); GUILayout.Label(Units.ToMass(stage.mass, stage.totalMass), this.infoStyle);
} }
} }
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 * GuiDisplaySize.Offset)); GUILayout.BeginVertical(GUILayout.Width(50.0f * GuiDisplaySize.Offset));
GUILayout.Label("PARTS", this.titleStyle); GUILayout.Label("PARTS", this.titleStyle);
foreach (var stage in this.stages) foreach (var stage in this.stages)
{ {
if (this.showAllStages || stage.deltaV > 0) if (this.showAllStages || stage.deltaV > 0)
{ {
GUILayout.Label(stage.partCount + " / " + stage.totalPartCount, this.infoStyle); GUILayout.Label(stage.partCount + " / " + stage.totalPartCount, this.infoStyle);
} }
} }
GUILayout.EndVertical(); GUILayout.EndVertical();
} }
   
/// <summary> /// <summary>
/// Draws the settings panel. /// Draws the settings panel.
/// </summary> /// </summary>
private void DrawSettings() private void DrawSettings()
{ {
GUILayout.BeginHorizontal(); GUILayout.BeginHorizontal();
GUILayout.Label("Compact mode collapses to the:", this.settingStyle); GUILayout.Label("Compact mode collapses to the:", this.settingStyle);
this.compactCollapseRight = !GUILayout.Toggle(!this.compactCollapseRight, "LEFT", this.buttonStyle, GUILayout.Width(100.0f * GuiDisplaySize.Offset)); this.compactCollapseRight = !GUILayout.Toggle(!this.compactCollapseRight, "LEFT", this.buttonStyle, GUILayout.Width(100.0f * GuiDisplaySize.Offset));
this.compactCollapseRight = GUILayout.Toggle(this.compactCollapseRight, "RIGHT", this.buttonStyle, GUILayout.Width(100.0f * GuiDisplaySize.Offset)); this.compactCollapseRight = GUILayout.Toggle(this.compactCollapseRight, "RIGHT", this.buttonStyle, GUILayout.Width(100.0f * GuiDisplaySize.Offset));
GUILayout.EndHorizontal(); GUILayout.EndHorizontal();
   
GUILayout.BeginHorizontal(); GUILayout.BeginHorizontal();
GUILayout.Label("Simulate using vectored thrust values:"); GUILayout.Label("Simulate using vectored thrust values:");
SimManager.vectoredThrust = GUILayout.Toggle(SimManager.vectoredThrust, "ENABLED", this.buttonStyle, GUILayout.Width(100.0f * GuiDisplaySize.Offset)); SimManager.vectoredThrust = GUILayout.Toggle(SimManager.vectoredThrust, "ENABLED", this.buttonStyle, GUILayout.Width(100.0f * GuiDisplaySize.Offset));
GUILayout.EndHorizontal(); GUILayout.EndHorizontal();
   
GUILayout.BeginHorizontal(); GUILayout.BeginHorizontal();
GUILayout.Label("Build Engineer Overlay:", this.settingStyle); GUILayout.Label("Build Engineer Overlay:", this.settingStyle);
BuildOverlay.Visible = GUILayout.Toggle(BuildOverlay.Visible, "VISIBLE", this.buttonStyle, GUILayout.Width(100.0f * GuiDisplaySize.Offset)); BuildOverlay.Visible = GUILayout.Toggle(BuildOverlay.Visible, "VISIBLE", this.buttonStyle, GUILayout.Width(100.0f * GuiDisplaySize.Offset));
BuildOverlayPartInfo.NamesOnly = GUILayout.Toggle(BuildOverlayPartInfo.NamesOnly, "NAMES ONLY", this.buttonStyle, GUILayout.Width(100.0f * GuiDisplaySize.Offset)); BuildOverlayPartInfo.NamesOnly = GUILayout.Toggle(BuildOverlayPartInfo.NamesOnly, "NAMES ONLY", this.buttonStyle, GUILayout.Width(100.0f * GuiDisplaySize.Offset));
BuildOverlayPartInfo.ClickToOpen = GUILayout.Toggle(BuildOverlayPartInfo.ClickToOpen, "CLICK TO OPEN", this.buttonStyle, GUILayout.Width(100.0f * GuiDisplaySize.Offset)); BuildOverlayPartInfo.ClickToOpen = GUILayout.Toggle(BuildOverlayPartInfo.ClickToOpen, "CLICK TO OPEN", this.buttonStyle, GUILayout.Width(100.0f * GuiDisplaySize.Offset));
GUILayout.EndHorizontal(); GUILayout.EndHorizontal();
   
GUILayout.BeginHorizontal(); GUILayout.BeginHorizontal();
GUILayout.Label("Flight Engineer activation mode:", this.settingStyle); GUILayout.Label("Flight Engineer activation mode:", this.settingStyle);
FlightEngineerCore.IsCareerMode = GUILayout.Toggle(FlightEngineerCore.IsCareerMode, "CAREER", this.buttonStyle, GUILayout.Width(100.0f * GuiDisplaySize.Offset)); FlightEngineerCore.IsCareerMode = GUILayout.Toggle(FlightEngineerCore.IsCareerMode, "CAREER", this.buttonStyle, GUILayout.Width(100.0f * GuiDisplaySize.Offset));
FlightEngineerCore.IsCareerMode = !GUILayout.Toggle(!FlightEngineerCore.IsCareerMode, "PARTLESS", this.buttonStyle, GUILayout.Width(100.0f * GuiDisplaySize.Offset)); FlightEngineerCore.IsCareerMode = !GUILayout.Toggle(!FlightEngineerCore.IsCareerMode, "PARTLESS", this.buttonStyle, GUILayout.Width(100.0f * GuiDisplaySize.Offset));
GUILayout.EndHorizontal(); GUILayout.EndHorizontal();
   
GUILayout.BeginHorizontal(); GUILayout.BeginHorizontal();
GUILayout.Label("Flight Engineer Career Limitations:", this.settingStyle); GUILayout.Label("Flight Engineer Career Limitations:", this.settingStyle);
FlightEngineerCore.IsKerbalLimited = GUILayout.Toggle(FlightEngineerCore.IsKerbalLimited, "KERBAL", this.buttonStyle, GUILayout.Width(100.0f * GuiDisplaySize.Offset)); FlightEngineerCore.IsKerbalLimited = GUILayout.Toggle(FlightEngineerCore.IsKerbalLimited, "KERBAL", this.buttonStyle, GUILayout.Width(100.0f * GuiDisplaySize.Offset));
FlightEngineerCore.IsTrackingStationLimited = GUILayout.Toggle(FlightEngineerCore.IsTrackingStationLimited, "TRACKING", this.buttonStyle, GUILayout.Width(100.0f * GuiDisplaySize.Offset)); FlightEngineerCore.IsTrackingStationLimited = GUILayout.Toggle(FlightEngineerCore.IsTrackingStationLimited, "TRACKING", this.buttonStyle, GUILayout.Width(100.0f * GuiDisplaySize.Offset));
GUILayout.EndHorizontal(); GUILayout.EndHorizontal();
   
GUILayout.BeginHorizontal(); GUILayout.BeginHorizontal();
GUILayout.Label("GUI Size: " + GuiDisplaySize.Increment, this.settingStyle); GUILayout.Label("GUI Size: " + GuiDisplaySize.Increment, this.settingStyle);
if (GUILayout.Button("<", this.buttonStyle, GUILayout.Width(100.0f * GuiDisplaySize.Offset))) if (GUILayout.Button("<", this.buttonStyle, GUILayout.Width(100.0f * GuiDisplaySize.Offset)))
{ {
GuiDisplaySize.Increment--; GuiDisplaySize.Increment--;
} }
if (GUILayout.Button(">", this.buttonStyle, GUILayout.Width(100.0f * GuiDisplaySize.Offset))) if (GUILayout.Button(">", this.buttonStyle, GUILayout.Width(100.0f * GuiDisplaySize.Offset)))
{ {
GuiDisplaySize.Increment++; GuiDisplaySize.Increment++;
} }
GUILayout.EndHorizontal(); GUILayout.EndHorizontal();
   
GUILayout.Label("Minimum delay between simulations: " + SimManager.minSimTime.Milliseconds + "ms", this.settingStyle); GUILayout.Label("Minimum delay between simulations: " + SimManager.minSimTime.Milliseconds + "ms", this.settingStyle);
GUI.skin = HighLogic.Skin; GUI.skin = HighLogic.Skin;
SimManager.minSimTime = new TimeSpan(0, 0, 0, 0, (int)GUILayout.HorizontalSlider(SimManager.minSimTime.Milliseconds, 0, 2000.0f)); SimManager.minSimTime = new TimeSpan(0, 0, 0, 0, (int)GUILayout.HorizontalSlider(SimManager.minSimTime.Milliseconds, 0, 2000.0f));
GUI.skin = null; GUI.skin = null;
} }
   
/// <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 * GuiDisplaySize.Offset)); GUILayout.BeginVertical(GUILayout.Width(30.0f * GuiDisplaySize.Offset));
GUILayout.Label(string.Empty, this.titleStyle); GUILayout.Label(string.Empty, this.titleStyle);
foreach (var stage in this.stages) foreach (var stage in this.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 thrust column. /// Draws the thrust column.
/// </summary> /// </summary>
private void DrawThrust() private void DrawThrust()
{ {
GUILayout.BeginVertical(GUILayout.Width(75.0f * GuiDisplaySize.Offset)); GUILayout.BeginVertical(GUILayout.Width(75.0f * GuiDisplaySize.Offset));
GUILayout.Label("THRUST", this.titleStyle); GUILayout.Label("THRUST", this.titleStyle);
foreach (var stage in this.stages) foreach (var stage in this.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>
/// Draws the torque column. /// Draws the torque column.
/// </summary> /// </summary>
private void DrawTorque() private void DrawTorque()
{ {
GUILayout.BeginVertical(GUILayout.Width(75.0f * GuiDisplaySize.Offset)); GUILayout.BeginVertical(GUILayout.Width(75.0f * GuiDisplaySize.Offset));
GUILayout.Label("TORQUE", this.titleStyle); GUILayout.Label("TORQUE", this.titleStyle);
foreach (var stage in this.stages) foreach (var stage in this.stages)
{ {
if (this.showAllStages || stage.deltaV > 0) if (this.showAllStages || stage.deltaV > 0)
{ {
GUILayout.Label(stage.maxThrustTorque.ToTorque(), this.infoStyle); GUILayout.Label(stage.maxThrustTorque.ToTorque(), 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(100.0f * GuiDisplaySize.Offset)); GUILayout.BeginVertical(GUILayout.Width(100.0f * GuiDisplaySize.Offset));
GUILayout.Label("TWR (MAX)", this.titleStyle); GUILayout.Label("TWR (MAX)", this.titleStyle);
foreach (var stage in this.stages) foreach (var stage in this.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();
} }
   
private void EditorLock(bool state) private void EditorLock(bool state)
{ {
if (state) if (state)
{ {
EditorLogic.fetch.Lock(true, true, true, "KER_BuildAdvanced"); EditorLogic.fetch.Lock(true, true, true, "KER_BuildAdvanced");
BuildOverlayPartInfo.Hidden = true; BuildOverlayPartInfo.Hidden = true;
this.isEditorLocked = true; this.isEditorLocked = true;
} }
else else
{ {
EditorLogic.fetch.Unlock("KER_BuildAdvanced"); EditorLogic.fetch.Unlock("KER_BuildAdvanced");
BuildOverlayPartInfo.Hidden = false; BuildOverlayPartInfo.Hidden = false;
this.isEditorLocked = false; this.isEditorLocked = false;
} }
} }
   
private void GetStageInfo() private void GetStageInfo()
{ {
this.stages = SimManager.Stages; this.stages = SimManager.Stages;
} }
   
/// <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.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.areaSettingStyle = new GUIStyle(HighLogic.Skin.box) this.areaSettingStyle = new GUIStyle(HighLogic.Skin.box)
{ {
padding = new RectOffset(10, 10, 10, 10) padding = new RectOffset(10, 10, 10, 10)
}; };
   
this.buttonStyle = new GUIStyle(HighLogic.Skin.button) this.buttonStyle = new GUIStyle(HighLogic.Skin.button)
{ {
normal = normal =
{ {
textColor = Color.white textColor = Color.white
}, },
fontSize = (int)(11 * GuiDisplaySize.Offset), fontSize = (int)(11 * GuiDisplaySize.Offset),
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 = (int)(11 * GuiDisplaySize.Offset), fontSize = (int)(11 * GuiDisplaySize.Offset),
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 = (int)(11 * GuiDisplaySize.Offset), fontSize = (int)(11 * GuiDisplaySize.Offset),
fontStyle = FontStyle.Bold, fontStyle = FontStyle.Bold,
alignment = TextAnchor.MiddleCenter, alignment = TextAnchor.MiddleCenter,
stretchWidth = true stretchWidth = true
}; };
   
this.settingStyle = new GUIStyle(this.titleStyle) this.settingStyle = new GUIStyle(this.titleStyle)
{ {
alignment = TextAnchor.MiddleLeft, alignment = TextAnchor.MiddleLeft,
stretchWidth = true, stretchWidth = true,
stretchHeight = true stretchHeight = true
}; };
   
this.settingAtmoStyle = new GUIStyle(this.titleStyle) this.settingAtmoStyle = new GUIStyle(this.titleStyle)
{ {
margin = new RectOffset(), margin = new RectOffset(),
padding = new RectOffset(), padding = new RectOffset(),
alignment = TextAnchor.UpperLeft alignment = TextAnchor.UpperLeft
}; };
   
this.bodiesButtonStyle = new GUIStyle(HighLogic.Skin.button) this.bodiesButtonStyle = new GUIStyle(HighLogic.Skin.button)
{ {
margin = new RectOffset(0, 0, 2, 0), margin = new RectOffset(0, 0, 2, 0),
padding = new RectOffset(5, 5, 5, 5), padding = new RectOffset(5, 5, 5, 5),
normal = normal =
{ {
textColor = Color.white textColor = Color.white
}, },
active = active =
{ {
textColor = Color.white textColor = Color.white
}, },
fontSize = (int)(11 * GuiDisplaySize.Offset), fontSize = (int)(11 * GuiDisplaySize.Offset),
fontStyle = FontStyle.Bold, fontStyle = FontStyle.Bold,
alignment = TextAnchor.MiddleCenter, alignment = TextAnchor.MiddleCenter,
fixedHeight = 20.0f fixedHeight = 20.0f
}; };
   
this.bodiesButtonActiveStyle = new GUIStyle(this.bodiesButtonStyle) this.bodiesButtonActiveStyle = new GUIStyle(this.bodiesButtonStyle)
{ {
normal = this.bodiesButtonStyle.onNormal, normal = this.bodiesButtonStyle.onNormal,
hover = this.bodiesButtonStyle.onHover hover = this.bodiesButtonStyle.onHover
}; };
} }
   
/// <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.position.x = handler.Get("windowPositionX", this.position.x); this.position.x = handler.Get("windowPositionX", this.position.x);
this.position.y = handler.Get("windowPositionY", this.position.y); this.position.y = handler.Get("windowPositionY", this.position.y);
handler.Get("compactMode", ref this.compactMode); handler.Get("compactMode", ref this.compactMode);
handler.Get("compactCollapseRight", ref this.compactCollapseRight); handler.Get("compactCollapseRight", ref this.compactCollapseRight);
handler.Get("showAllStages", ref this.showAllStages); handler.Get("showAllStages", ref this.showAllStages);
handler.Get("showAtmosphericDetails", ref this.showAtmosphericDetails); handler.Get("showAtmosphericDetails", ref this.showAtmosphericDetails);
handler.Get("showSettings", ref this.showSettings); handler.Get("showSettings", ref this.showSettings);
CelestialBodies.SetSelectedBody(handler.Get("selectedBodyName", CelestialBodies.SelectedBody.Name)); CelestialBodies.SetSelectedBody(handler.Get("selectedBodyName", CelestialBodies.SelectedBody.Name));
} }
catch (Exception ex) catch (Exception ex)
{ {
Logger.Exception(ex, "BuildAdvanced->Load"); Logger.Exception(ex, "BuildAdvanced->Load");
} }
} }
   
private void OnSizeChanged() private void OnSizeChanged()
{ {
this.InitialiseStyles(); this.InitialiseStyles();
this.hasChanged = true; this.hasChanged = true;
} }
   
/// <summary> /// <summary>
/// Draws the OnGUI window. /// Draws the OnGUI window.
/// </summary> /// </summary>
private void Window(int windowId) private void Window(int windowId)
{ {
try try
{ {
// Draw the compact mode toggle. // Draw the compact mode toggle.
if (GUI.Toggle(new Rect(this.position.width - 70.0f * GuiDisplaySize.Offset, 5.0f, 65.0f * GuiDisplaySize.Offset, 20.0f), this.compactMode, "COMPACT", this.buttonStyle) != this.compactMode) if (GUI.Toggle(new Rect(this.position.width - 70.0f * GuiDisplaySize.Offset, 5.0f, 65.0f * GuiDisplaySize.Offset, 20.0f), this.compactMode, "COMPACT", this.buttonStyle) != this.compactMode)
{ {
this.hasChanged = true; this.hasChanged = true;
this.compactCheck = 2; this.compactCheck = 2;
this.compactRight = this.position.xMax; this.compactRight = this.position.xMax;
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.position.width - 143.0f * GuiDisplaySize.Offset, 5.0f, 70.0f * GuiDisplaySize.Offset, 20.0f), this.showSettings, "SETTINGS", this.buttonStyle) != this.showSettings) if (GUI.Toggle(new Rect(this.position.width - 143.0f * GuiDisplaySize.Offset, 5.0f, 70.0f * GuiDisplaySize.Offset, 20.0f), this.showSettings, "SETTINGS", this.buttonStyle) != this.showSettings)
{ {
this.hasChanged = true; this.hasChanged = true;
this.showSettings = !this.showSettings; this.showSettings = !this.showSettings;
} }
   
if (GUI.Toggle(new Rect(this.position.width - 226.0f * GuiDisplaySize.Offset, 5.0f, 80.0f * GuiDisplaySize.Offset, 20.0f), this.showAllStages, "ALL STAGES", this.buttonStyle) != this.showAllStages) if (GUI.Toggle(new Rect(this.position.width - 226.0f * GuiDisplaySize.Offset, 5.0f, 80.0f * GuiDisplaySize.Offset, 20.0f), this.showAllStages, "ALL STAGES", this.buttonStyle) != this.showAllStages)
{ {
this.hasChanged = true; this.hasChanged = true;
this.showAllStages = !this.showAllStages; this.showAllStages = !this.showAllStages;
} }
   
if (GUI.Toggle(new Rect(this.position.width - 324.0f * GuiDisplaySize.Offset, 5.0f, 95.0f * GuiDisplaySize.Offset, 20.0f), this.showAtmosphericDetails, "ATMOSPHERIC", this.buttonStyle) != this.showAtmosphericDetails) if (GUI.Toggle(new Rect(this.position.width - 324.0f * GuiDisplaySize.Offset, 5.0f, 95.0f * GuiDisplaySize.Offset, 20.0f), this.showAtmosphericDetails, "ATMOSPHERIC", this.buttonStyle) != this.showAtmosphericDetails)
{ {
this.hasChanged = true; this.hasChanged = true;
this.showAtmosphericDetails = !this.showAtmosphericDetails; this.showAtmosphericDetails = !this.showAtmosphericDetails;
} }
   
this.bodiesListPosition = new Rect(this.position.width - 452.0f * GuiDisplaySize.Offset, 5.0f, 125.0f * GuiDisplaySize.Offset, 20.0f); this.bodiesListPosition = new Rect(this.position.width - 452.0f * GuiDisplaySize.Offset, 5.0f, 125.0f * GuiDisplaySize.Offset, 20.0f);
this.bodiesList.enabled = GUI.Toggle(this.bodiesListPosition, this.bodiesList.enabled, "BODY: " + CelestialBodies.SelectedBody.Name.ToUpper(), this.buttonStyle); this.bodiesList.enabled = GUI.Toggle(this.bodiesListPosition, this.bodiesList.enabled, "BODY: " + CelestialBodies.SelectedBody.Name.ToUpper(), this.buttonStyle);
this.bodiesList.SetPosition(this.bodiesListPosition.Translate(this.position)); this.bodiesList.SetPosition(this.bodiesListPosition.Translate(this.position));
} }
   
// 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.DrawTorque(); this.DrawTorque();
this.DrawTwr(); this.DrawTwr();
this.DrawDeltaV(); this.DrawDeltaV();
this.DrawBurnTime(); this.DrawBurnTime();
GUILayout.EndHorizontal(); GUILayout.EndHorizontal();
   
if (this.showAtmosphericDetails) if (this.showAtmosphericDetails)
{ {
GUILayout.BeginVertical(this.areaSettingStyle); GUILayout.BeginVertical(this.areaSettingStyle);
this.DrawAtmosphericDetails(); this.DrawAtmosphericDetails();
GUILayout.EndVertical(); GUILayout.EndVertical();
} }
   
if (this.showSettings) if (this.showSettings)
{ {
GUILayout.BeginVertical(this.areaSettingStyle); GUILayout.BeginVertical(this.areaSettingStyle);
this.DrawSettings(); this.DrawSettings();
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();
} }
catch (Exception ex) catch (Exception ex)
{ {
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 System.Linq; using System.Linq;
   
#endregion #endregion
   
namespace KerbalEngineer.Extensions namespace KerbalEngineer.Extensions
{ {
using CompoundParts; using CompoundParts;
   
public static class PartExtensions public static class PartExtensions
{ {
#region Methods: public #region Methods: public
   
/// <summary> /// <summary>
/// Gets whether the part contains a specific resource. /// Gets whether the part contains a specific resource.
/// </summary> /// </summary>
public static bool ContainsResource(this Part part, int resourceId) public static bool ContainsResource(this Part part, int resourceId)
{ {
return part.Resources.Contains(resourceId); return part.Resources.Contains(resourceId);
} }
   
/// <summary> /// <summary>
/// Gets whether the part contains resources. /// Gets whether the part contains resources.
/// </summary> /// </summary>
public static bool ContainsResources(this Part part) public static bool ContainsResources(this Part part)
{ {
return part.Resources.list.Count(p => p.amount > 0d) > 0; return part.Resources.list.Count(p => p.amount > 0d) > 0;
} }
   
/// <summary> /// <summary>
/// Gets whether the part has fuel. /// Gets whether the part has fuel.
/// </summary> /// </summary>
public static bool EngineHasFuel(this Part part) public static bool EngineHasFuel(this Part part)
{ {
if (part.HasModule<ModuleEngines>()) if (part.HasModule<ModuleEngines>())
{ {
return part.GetModuleEngines().getFlameoutState; return part.GetModuleEngines().getFlameoutState;
} }
if (part.HasModule<MultiModeEngine>()) if (part.HasModule<MultiModeEngine>())
{ {
return part.GetModuleMultiModeEngine().getFlameoutState; return part.GetModuleMultiModeEngine().getFlameoutState;
} }
   
return false; return false;
} }
   
/// <summary> /// <summary>
/// Gets the cost of the part excluding resources. /// Gets the cost of the part excluding resources.
/// </summary> /// </summary>
public static double GetCostDry(this Part part) public static double GetCostDry(this Part part)
{ {
return part.partInfo.cost - GetResourceCostMax(part) + part.GetModuleCosts(0.0f); return part.partInfo.cost - GetResourceCostMax(part) + part.GetModuleCosts(0.0f);
} }
   
/// <summary> /// <summary>
/// Gets the cost of the part including maximum resources. /// Gets the cost of the part including maximum resources.
/// </summary> /// </summary>
public static double GetCostMax(this Part part) public static double GetCostMax(this Part part)
{ {
return part.partInfo.cost + part.GetModuleCosts(0.0f); return part.partInfo.cost + part.GetModuleCosts(0.0f);
} }
   
/// <summary> /// <summary>
/// Gets the cost of the part including resources. /// Gets the cost of the part including resources.
/// </summary> /// </summary>
public static double GetCostWet(this Part part) public static double GetCostWet(this Part part)
{ {
return part.partInfo.cost - GetResourceCostInverted(part) + part.GetModuleCosts(0.0f); return part.partInfo.cost - GetResourceCostInverted(part) + part.GetModuleCosts(0.0f);
} }
   
/// <summary> /// <summary>
/// Gets the dry mass of the part. /// Gets the dry mass of the part.
/// </summary> /// </summary>
public static double GetDryMass(this Part part) public static double GetDryMass(this Part part)
{ {
return (part.physicalSignificance == Part.PhysicalSignificance.FULL) ? part.mass : 0d; return (part.physicalSignificance == Part.PhysicalSignificance.FULL) ? part.mass : 0d;
} }
   
/// <summary> /// <summary>
/// Gets the maximum thrust of the part if it's an engine. /// Gets the maximum thrust of the part if it's an engine.
/// </summary> /// </summary>
public static double GetMaxThrust(this Part part) public static double GetMaxThrust(this Part part)
{ {
if (part.HasModule<ModuleEngines>()) if (part.HasModule<ModuleEngines>())
{ {
return part.GetModuleEngines().maxThrust; return part.GetModuleEngines().maxThrust;
} }
if (part.HasModule<MultiModeEngine>()) if (part.HasModule<MultiModeEngine>())
{ {
return part.GetModuleMultiModeEngine().maxThrust; return part.GetModuleMultiModeEngine().maxThrust;
} }
if (part.HasModule<ModuleEnginesFX>()) if (part.HasModule<ModuleEnginesFX>())
{ {
return part.GetModuleEnginesFx().maxThrust; return part.GetModuleEnginesFx().maxThrust;
} }
   
return 0d; return 0d;
} }
   
/// <summary> /// <summary>
/// Gets the first typed PartModule in the part's module list. /// Gets the first typed PartModule in the part's module list.
/// </summary> /// </summary>
public static T GetModule<T>(this Part part) where T : PartModule public static T GetModule<T>(this Part part) where T : PartModule
{ {
return part.Modules.OfType<T>().FirstOrDefault(); return part.Modules.OfType<T>().FirstOrDefault();
} }
   
/// <summary> /// <summary>
/// Gets a typed PartModule. /// Gets a typed PartModule.
/// </summary> /// </summary>
public static T GetModule<T>(this Part part, string className) where T : PartModule public static T GetModule<T>(this Part part, string className) where T : PartModule
{ {
return (T)Convert.ChangeType(part.Modules[className], typeof(T)); return (T)Convert.ChangeType(part.Modules[className], typeof(T));
} }
   
/// <summary> /// <summary>
/// Gets a typed PartModule. /// Gets a typed PartModule.
/// </summary> /// </summary>
public static T GetModule<T>(this Part part, int classId) where T : PartModule public static T GetModule<T>(this Part part, int classId) where T : PartModule
{ {
return (T)Convert.ChangeType(part.Modules[classId], typeof(T)); return (T)Convert.ChangeType(part.Modules[classId], typeof(T));
} }
   
/// <summary> /// <summary>
/// Gets a ModuleAlternator typed PartModule. /// Gets a ModuleAlternator typed PartModule.
/// </summary> /// </summary>
public static ModuleAlternator GetModuleAlternator(this Part part) public static ModuleAlternator GetModuleAlternator(this Part part)
{ {
return part.GetModule<ModuleAlternator>(); return part.GetModule<ModuleAlternator>();
} }
   
/// <summary> /// <summary>
/// Gets a ModuleDeployableSolarPanel typed PartModule. /// Gets a ModuleDeployableSolarPanel typed PartModule.
/// </summary> /// </summary>
public static ModuleDeployableSolarPanel GetModuleDeployableSolarPanel(this Part part) public static ModuleDeployableSolarPanel GetModuleDeployableSolarPanel(this Part part)
{ {
return part.GetModule<ModuleDeployableSolarPanel>(); return part.GetModule<ModuleDeployableSolarPanel>();
} }
   
/// <summary> /// <summary>
/// Gets a ModuleEngines typed PartModule. /// Gets a ModuleEngines typed PartModule.
/// </summary> /// </summary>
public static ModuleEngines GetModuleEngines(this Part part) public static ModuleEngines GetModuleEngines(this Part part)
{ {
return part.GetModule<ModuleEngines>(); return part.GetModule<ModuleEngines>();
} }
   
public static ModuleEnginesFX GetModuleEnginesFx(this Part part) public static ModuleEnginesFX GetModuleEnginesFx(this Part part)
{ {
return part.GetModule<ModuleEnginesFX>(); return part.GetModule<ModuleEnginesFX>();
} }
   
/// <summary> /// <summary>
/// Gets a ModuleGenerator typed PartModule. /// Gets a ModuleGenerator typed PartModule.
/// </summary> /// </summary>
public static ModuleGenerator GetModuleGenerator(this Part part) public static ModuleGenerator GetModuleGenerator(this Part part)
{ {
return part.GetModule<ModuleGenerator>(); return part.GetModule<ModuleGenerator>();
} }
   
/// <summary> /// <summary>
/// Gets a ModuleGimbal typed PartModule. /// Gets a ModuleGimbal typed PartModule.
/// </summary> /// </summary>
public static ModuleGimbal GetModuleGimbal(this Part part) public static ModuleGimbal GetModuleGimbal(this Part part)
{ {
return part.GetModule<ModuleGimbal>(); return part.GetModule<ModuleGimbal>();
} }
   
/// <summary> /// <summary>
/// Gets the current selected ModuleEnginesFX. /// Gets the current selected ModuleEnginesFX.
/// </summary> /// </summary>
public static ModuleEnginesFX GetModuleMultiModeEngine(this Part part) public static ModuleEnginesFX GetModuleMultiModeEngine(this Part part)
{ {
var mode = part.GetModule<MultiModeEngine>().mode; var mode = part.GetModule<MultiModeEngine>().mode;
return part.Modules.OfType<ModuleEnginesFX>().FirstOrDefault(engine => engine.engineID == mode); return part.Modules.OfType<ModuleEnginesFX>().FirstOrDefault(engine => engine.engineID == mode);
} }
   
/// <summary> /// <summary>
/// Gets a ModuleParachute typed PartModule. /// Gets a ModuleParachute typed PartModule.
/// </summary> /// </summary>
public static ModuleParachute GetModuleParachute(this Part part) public static ModuleParachute GetModuleParachute(this Part part)
{ {
return part.GetModule<ModuleParachute>(); return part.GetModule<ModuleParachute>();
} }
   
public static ModuleRCS GetModuleRcs(this Part part) public static ModuleRCS GetModuleRcs(this Part part)
{ {
return part.GetModule<ModuleRCS>(); return part.GetModule<ModuleRCS>();
} }
   
/// <summary> /// <summary>
/// Gets a typed list of PartModules. /// Gets a typed list of PartModules.
/// </summary> /// </summary>
public static List<T> GetModules<T>(this Part part) where T : PartModule public static List<T> GetModules<T>(this Part part) where T : PartModule
{ {
return part.Modules.OfType<T>().ToList(); return part.Modules.OfType<T>().ToList();
} }
   
public static ProtoModuleDecoupler GetProtoModuleDecoupler(this Part part) public static ProtoModuleDecoupler GetProtoModuleDecoupler(this Part part)
{ {
if (HasModule<ModuleDecouple>(part)) if (HasModule<ModuleDecouple>(part))
{ {
return new ProtoModuleDecoupler(GetModule<ModuleDecouple>(part)); return new ProtoModuleDecoupler(GetModule<ModuleDecouple>(part));
} }
if (HasModule<ModuleAnchoredDecoupler>(part)) if (HasModule<ModuleAnchoredDecoupler>(part))
{ {
return new ProtoModuleDecoupler(GetModule<ModuleAnchoredDecoupler>(part)); return new ProtoModuleDecoupler(GetModule<ModuleAnchoredDecoupler>(part));
} }
return null; return null;
} }
   
/// <summary> /// <summary>
/// Gets a generic proto engine for the current engine module attached to the part. /// Gets a generic proto engine for the current engine module attached to the part.
/// </summary> /// </summary>
public static ProtoModuleEngine GetProtoModuleEngine(this Part part) public static ProtoModuleEngine GetProtoModuleEngine(this Part part)
{ {
if (HasModule<ModuleEngines>(part)) if (HasModule<ModuleEngines>(part))
{ {
return new ProtoModuleEngine(GetModule<ModuleEngines>(part)); return new ProtoModuleEngine(GetModule<ModuleEngines>(part));
} }
if (HasModule<MultiModeEngine>(part)) if (HasModule<MultiModeEngine>(part))
{ {
return new ProtoModuleEngine(GetModuleMultiModeEngine(part)); return new ProtoModuleEngine(GetModuleMultiModeEngine(part));
} }
if (HasModule<ModuleEnginesFX>(part)) if (HasModule<ModuleEnginesFX>(part))
{ {
return new ProtoModuleEngine(GetModule<ModuleEnginesFX>(part)); return new ProtoModuleEngine(GetModule<ModuleEnginesFX>(part));
} }
return null; return null;
} }
   
/// <summary> /// <summary>
/// Gets the cost of the part's contained resources. /// Gets the cost of the part's contained resources.
/// </summary> /// </summary>
public static double GetResourceCost(this Part part) public static double GetResourceCost(this Part part)
{ {
return part.Resources.list.Sum(r => r.amount * r.info.unitCost); return part.Resources.list.Sum(r => r.amount * r.info.unitCost);
} }
   
/// <summary> /// <summary>
/// Gets the cost of the part's contained resources, inverted. /// Gets the cost of the part's contained resources, inverted.
/// </summary> /// </summary>
public static double GetResourceCostInverted(this Part part) public static double GetResourceCostInverted(this Part part)
{ {
return part.Resources.list.Sum(r => (r.maxAmount - r.amount) * r.info.unitCost); return part.Resources.list.Sum(r => (r.maxAmount - r.amount) * r.info.unitCost);
} }
   
/// <summary> /// <summary>
/// Gets the cost of the part's maximum contained resources. /// Gets the cost of the part's maximum contained resources.
/// </summary> /// </summary>
public static double GetResourceCostMax(this Part part) public static double GetResourceCostMax(this Part part)
{ {
return part.Resources.list.Sum(r => r.maxAmount * r.info.unitCost); return part.Resources.list.Sum(r => r.maxAmount * r.info.unitCost);
} }
   
/// <summary> /// <summary>
/// Gets the current specific impulse for the engine. /// Gets the current specific impulse for the engine.
/// </summary> /// </summary>
public static double GetSpecificImpulse(this Part part, float atmosphere) public static double GetSpecificImpulse(this Part part, float atmosphere)
{ {
if (part.HasModule<ModuleEngines>()) if (part.HasModule<ModuleEngines>())
{ {
return part.GetModuleEngines().atmosphereCurve.Evaluate(atmosphere); return part.GetModuleEngines().atmosphereCurve.Evaluate(atmosphere);
} }
if (part.HasModule<MultiModeEngine>()) if (part.HasModule<MultiModeEngine>())
{ {
return part.GetModuleMultiModeEngine().atmosphereCurve.Evaluate(atmosphere); return part.GetModuleMultiModeEngine().atmosphereCurve.Evaluate(atmosphere);
} }
if (part.HasModule<ModuleEnginesFX>()) if (part.HasModule<ModuleEnginesFX>())
{ {
return part.GetModuleEnginesFx().atmosphereCurve.Evaluate(atmosphere); return part.GetModuleEnginesFx().atmosphereCurve.Evaluate(atmosphere);
} }
   
return 0d; return 0d;
} }
   
/// <summary> /// <summary>
/// Gets the total mass of the part including resources. /// Gets the total mass of the part including resources.
/// </summary> /// </summary>
public static double GetWetMass(this Part part) public static double GetWetMass(this Part part)
{ {
return (part.physicalSignificance == Part.PhysicalSignificance.FULL) ? part.mass + part.GetResourceMass() : part.GetResourceMass(); return (part.physicalSignificance == Part.PhysicalSignificance.FULL) ? part.mass + part.GetResourceMass() : part.GetResourceMass();
} }
   
/// <summary> /// <summary>
/// Gets whether the part contains a PartModule. /// Gets whether the part contains a PartModule.
/// </summary> /// </summary>
public static bool HasModule<T>(this Part part) where T : PartModule public static bool HasModule<T>(this Part part) where T : PartModule
{ {
return part.Modules.OfType<T>().Any(); return part.Modules.OfType<T>().Any();
} }
   
/// <summary> /// <summary>
/// Gets whether the part contains a PartModule conforming to the supplied predicate. /// Gets whether the part contains a PartModule conforming to the supplied predicate.
/// </summary> /// </summary>
public static bool HasModule<T>(this Part part, Func<T, bool> predicate) where T : PartModule public static bool HasModule<T>(this Part part, Func<T, bool> predicate) where T : PartModule
{ {
return part.Modules.OfType<T>().Any(predicate); return part.Modules.OfType<T>().Any(predicate);
} }
   
/// <summary> /// <summary>
/// Gets whether the part contains a PartModule. /// Gets whether the part contains a PartModule.
/// </summary> /// </summary>
public static bool HasModule(this Part part, string className) public static bool HasModule(this Part part, string className)
{ {
return part.Modules.Contains(className); return part.Modules.Contains(className);
} }
   
/// <summary> /// <summary>
/// Gets whether the part contains a PartModule. /// Gets whether the part contains a PartModule.
/// </summary> /// </summary>
public static bool HasModule(this Part part, int moduleId) public static bool HasModule(this Part part, int moduleId)
{ {
return part.Modules.Contains(moduleId); return part.Modules.Contains(moduleId);
} }
   
/// <summary> /// <summary>
/// Gets whether the part has a one shot animation. /// Gets whether the part has a one shot animation.
/// </summary> /// </summary>
public static bool HasOneShotAnimation(this Part part) public static bool HasOneShotAnimation(this Part part)
{ {
return part.HasModule<ModuleAnimateGeneric>() && part.GetModule<ModuleAnimateGeneric>().isOneShot; return part.HasModule<ModuleAnimateGeneric>() && part.GetModule<ModuleAnimateGeneric>().isOneShot;
} }
   
/// <summary> /// <summary>
/// Gets whether the part is a command module. /// Gets whether the part is a command module.
/// </summary> /// </summary>
public static bool IsCommandModule(this Part part) public static bool IsCommandModule(this Part part)
{ {
return part.HasModule<ModuleCommand>(); return part.HasModule<ModuleCommand>();
} }
   
/// <summary> /// <summary>
/// Gets whether the part is decoupled in a specified stage. /// Gets whether the part is decoupled in a specified stage.
/// </summary> /// </summary>
public static bool IsDecoupledInStage(this Part part, int stage) public static bool IsDecoupledInStage(this Part part, int stage)
{ {
if ((part.IsDecoupler() || part.IsLaunchClamp()) && part.inverseStage == stage) if ((part.IsDecoupler() || part.IsLaunchClamp()) && part.inverseStage == stage)
{ {
return true; return true;
} }
if (part.parent == null) if (part.parent == null)
{ {
return false; return false;
} }
return part.parent.IsDecoupledInStage(stage); return part.parent.IsDecoupledInStage(stage);
} }
   
/// <summary> /// <summary>
/// Gets whether the part is a decoupler. /// Gets whether the part is a decoupler.
/// </summary> /// </summary>
public static bool IsDecoupler(this Part part) public static bool IsDecoupler(this Part part)
{ {
return part.HasModule<ModuleDecouple>() || part.HasModule<ModuleAnchoredDecoupler>(); return part.HasModule<ModuleDecouple>() || part.HasModule<ModuleAnchoredDecoupler>();
} }
   
/// <summary> /// <summary>
/// Gets whether the part is an active engine. /// Gets whether the part is an active engine.
/// </summary> /// </summary>
public static bool IsEngine(this Part part) public static bool IsEngine(this Part part)
{ {
return part.HasModule<ModuleEngines>() || part.HasModule<ModuleEnginesFX>(); return part.HasModule<ModuleEngines>() || part.HasModule<ModuleEnginesFX>();
} }
   
/// <summary> /// <summary>
/// Gets whether the part is a fuel line. /// Gets whether the part is a fuel line.
/// </summary> /// </summary>
public static bool IsFuelLine(this Part part) public static bool IsFuelLine(this Part part)
{ {
return (HasModule<CModuleFuelLine>(part)); return (HasModule<CModuleFuelLine>(part));
} }
   
/// <summary> /// <summary>
/// Gets whether the part is a generator. /// Gets whether the part is a generator.
/// </summary> /// </summary>
public static bool IsGenerator(this Part part) public static bool IsGenerator(this Part part)
{ {
return part.HasModule<ModuleGenerator>(); return part.HasModule<ModuleGenerator>();
} }
   
/// <summary> /// <summary>
/// Gets whether the part is a launch clamp. /// Gets whether the part is a launch clamp.
/// </summary> /// </summary>
public static bool IsLaunchClamp(this Part part) public static bool IsLaunchClamp(this Part part)
{ {
return part.HasModule<LaunchClamp>(); return part.HasModule<LaunchClamp>();
} }
   
/// <summary> /// <summary>
/// Gets whether the part is a parachute. /// Gets whether the part is a parachute.
/// </summary> /// </summary>
public static bool IsParachute(this Part part) public static bool IsParachute(this Part part)
{ {
return part.HasModule<ModuleParachute>(); return part.HasModule<ModuleParachute>();
} }
   
/// <summary> /// <summary>
/// Gets whether the part is considered a primary part on the vessel. /// Gets whether the part is considered a primary part on the vessel.
/// </summary> /// </summary>
public static bool IsPrimary(this Part part, List<Part> partsList, PartModule module) public static bool IsPrimary(this Part part, List<Part> partsList, PartModule module)
{ {
foreach (var vesselPart in partsList) foreach (var vesselPart in partsList)
{ {
if (!vesselPart.HasModule(module.ClassID)) if (!vesselPart.HasModule(module.ClassID))
{ {
continue; continue;
} }
   
if (vesselPart == part) if (vesselPart == part)
{ {
return true; return true;
} }
break; break;
} }
   
return false; return false;
} }
   
public static bool IsRcsModule(this Part part) public static bool IsRcsModule(this Part part)
{ {
return part.HasModule<ModuleRCS>(); return part.HasModule<ModuleRCS>();
} }
   
/// <summary> /// <summary>
/// Gets whether the part is a sepratron. /// Gets whether the part is a sepratron.
/// </summary> /// </summary>
public static bool IsSepratron(this Part part) public static bool IsSepratron(this Part part)
{ {
return (part.IsSolidRocket() && part.ActivatesEvenIfDisconnected && part.IsDecoupledInStage(part.inverseStage)); return (part.IsSolidRocket() && part.ActivatesEvenIfDisconnected && part.IsDecoupledInStage(part.inverseStage));
} }
   
/// <summary> /// <summary>
/// Gets whether the part is a deployable solar panel. /// Gets whether the part is a deployable solar panel.
/// </summary> /// </summary>
public static bool IsSolarPanel(this Part part) public static bool IsSolarPanel(this Part part)
{ {
return part.HasModule<ModuleDeployableSolarPanel>(); return part.HasModule<ModuleDeployableSolarPanel>();
} }
   
/// <summary> /// <summary>
/// Gets whether the part is a solid rocket motor. /// Gets whether the part is a solid rocket motor.
/// </summary> /// </summary>
public static bool IsSolidRocket(this Part part) public static bool IsSolidRocket(this Part part)
{ {
return part.HasModule<ModuleEngines>() && part.GetModuleEngines().throttleLocked; return (part.HasModule<ModuleEngines>() && part.GetModuleEngines().throttleLocked) || (part.HasModule<ModuleEnginesFX>() && part.GetModuleEnginesFx().throttleLocked);
} }
   
#endregion #endregion
   
#region Nested Type: ProtoModuleDecoupler #region Nested Type: ProtoModuleDecoupler
   
public class ProtoModuleDecoupler public class ProtoModuleDecoupler
{ {
#region Fields #region Fields
   
private readonly PartModule module; private readonly PartModule module;
   
#endregion #endregion
   
#region Constructors #region Constructors
   
public ProtoModuleDecoupler(PartModule module) public ProtoModuleDecoupler(PartModule module)
{ {
this.module = module; this.module = module;
   
if (this.module is ModuleDecouple) if (this.module is ModuleDecouple)
{ {
this.SetModuleDecouple(); this.SetModuleDecouple();
} }
else if (this.module is ModuleAnchoredDecoupler) else if (this.module is ModuleAnchoredDecoupler)
{ {
this.SetModuleAnchoredDecoupler(); this.SetModuleAnchoredDecoupler();
} }
} }
   
#endregion #endregion
   
#region Properties #region Properties
   
public double EjectionForce { get; private set; } public double EjectionForce { get; private set; }
public bool IsOmniDecoupler { get; private set; } public bool IsOmniDecoupler { get; private set; }
   
#endregion #endregion
   
#region Methods: private #region Methods: private
   
private void SetModuleAnchoredDecoupler() private void SetModuleAnchoredDecoupler()
{ {
var decoupler = this.module as ModuleAnchoredDecoupler; var decoupler = this.module as ModuleAnchoredDecoupler;
if (decoupler == null) if (decoupler == null)
{ {
return; return;
} }
   
this.EjectionForce = decoupler.ejectionForce; this.EjectionForce = decoupler.ejectionForce;
} }
   
private void SetModuleDecouple() private void SetModuleDecouple()
{ {
var decoupler = this.module as ModuleDecouple; var decoupler = this.module as ModuleDecouple;
if (decoupler == null) if (decoupler == null)
{ {
return; return;
} }
   
this.EjectionForce = decoupler.ejectionForce; this.EjectionForce = decoupler.ejectionForce;
this.IsOmniDecoupler = decoupler.isOmniDecoupler; this.IsOmniDecoupler = decoupler.isOmniDecoupler;
} }
   
#endregion #endregion
} }
   
#endregion #endregion
   
#region Nested Type: ProtoModuleEngine #region Nested Type: ProtoModuleEngine
   
public class ProtoModuleEngine public class ProtoModuleEngine
{ {
#region Fields #region Fields
   
private readonly PartModule module; private readonly PartModule module;
   
#endregion #endregion
   
#region Constructors #region Constructors
   
public ProtoModuleEngine(PartModule module) public ProtoModuleEngine(PartModule module)
{ {
this.module = module; this.module = module;
   
if (module is ModuleEngines) if (module is ModuleEngines)
{ {
this.SetModuleEngines(); this.SetModuleEngines();
} }
else if (module is ModuleEnginesFX) else if (module is ModuleEnginesFX)
{ {
this.SetModuleEnginesFx(); this.SetModuleEnginesFx();
} }
} }
   
#endregion #endregion
   
#region Properties #region Properties
   
public double MaximumThrust { get; private set; } public double MaximumThrust { get; private set; }
public double MinimumThrust { get; private set; } public double MinimumThrust { get; private set; }
public List<Propellant> Propellants { get; private set; } public List<Propellant> Propellants { get; private set; }
   
#endregion #endregion
   
#region Methods: public #region Methods: public
   
public float GetSpecificImpulse(float atmosphere) public float GetSpecificImpulse(float atmosphere)
{ {
if (this.module is ModuleEngines) if (this.module is ModuleEngines)
{ {
return (this.module as ModuleEngines).atmosphereCurve.Evaluate(atmosphere); return (this.module as ModuleEngines).atmosphereCurve.Evaluate(atmosphere);
} }
if (this.module is ModuleEnginesFX) if (this.module is ModuleEnginesFX)
{ {
return (this.module as ModuleEnginesFX).atmosphereCurve.Evaluate(atmosphere); return (this.module as ModuleEnginesFX).atmosphereCurve.Evaluate(atmosphere);
} }
return 0.0f; return 0.0f;
} }
   
#endregion #endregion
   
#region Methods: private #region Methods: private
   
private void SetModuleEngines() private void SetModuleEngines()
{ {
var engine = this.module as ModuleEngines; var engine = this.module as ModuleEngines;
if (engine == null) if (engine == null)
{ {
return; return;
} }
   
this.MaximumThrust = engine.maxThrust * (engine.thrustPercentage * 0.01); this.MaximumThrust = engine.maxThrust * (engine.thrustPercentage * 0.01);
this.MinimumThrust = engine.minThrust; this.MinimumThrust = engine.minThrust;
this.Propellants = engine.propellants; this.Propellants = engine.propellants;
} }
   
private void SetModuleEnginesFx() private void SetModuleEnginesFx()
{ {
var engine = this.module as ModuleEnginesFX; var engine = this.module as ModuleEnginesFX;
if (engine == null) if (engine == null)
{ {
return; return;
} }
   
this.MaximumThrust = engine.maxThrust * (engine.thrustPercentage * 0.01); this.MaximumThrust = engine.maxThrust * (engine.thrustPercentage * 0.01);
this.MinimumThrust = engine.minThrust; this.MinimumThrust = engine.minThrust;
this.Propellants = engine.propellants; this.Propellants = engine.propellants;
} }
   
#endregion #endregion
} }
   
#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/>.
// //
   
namespace KerbalEngineer.Flight.Readouts.Vessel namespace KerbalEngineer.Flight.Readouts.Vessel
{ {
#region Using Directives #region Using Directives
   
using UnityEngine; using UnityEngine;
   
#endregion #endregion
   
public class AttitudeProcessor : IUpdatable, IUpdateRequest public class AttitudeProcessor : IUpdatable, IUpdateRequest
{ {
#region Fields #region Fields
   
private static readonly AttitudeProcessor instance = new AttitudeProcessor(); private static readonly AttitudeProcessor instance = new AttitudeProcessor();
   
private Vector3 centreOfMass = Vector3.zero; private Vector3 centreOfMass = Vector3.zero;
   
private double heading; private double heading;
private double headingRate; private double headingRate;
private Vector3 north = Vector3.zero; private Vector3 north = Vector3.zero;
private double pitch; private double pitch;
private double pitchRate; private double pitchRate;
private double previousHeading; private double previousHeading;
private double previousPitch; private double previousPitch;
private double previousRoll; private double previousRoll;
private double roll; private double roll;
private double rollRate; private double rollRate;
private Quaternion surfaceRotation; private Quaternion surfaceRotation;
private Vector3 up = Vector3.zero; private Vector3 up = Vector3.zero;
   
#endregion #endregion
   
#region Properties #region Properties
   
public static double Heading public static double Heading
{ {
get { return instance.heading; } get { return instance.heading; }
} }
   
public static double HeadingRate public static double HeadingRate
{ {
get { return instance.headingRate; } get { return instance.headingRate; }
} }
   
public static AttitudeProcessor Instance public static AttitudeProcessor Instance
{ {
get { return instance; } get { return instance; }
} }
   
public static double Pitch public static double Pitch
{ {
get { return instance.pitch; } get { return instance.pitch; }
} }
   
public static double PitchRate public static double PitchRate
{ {
get { return instance.pitchRate; } get { return instance.pitchRate; }
} }
   
public static double Roll public static double Roll
{ {
get { return instance.roll; } get { return instance.roll; }
} }
   
public static double RollRate public static double RollRate
{ {
get { return instance.rollRate; } get { return instance.rollRate; }
} }
   
public bool UpdateRequested { get; set; } public bool UpdateRequested { get; set; }
   
#endregion #endregion
   
#region Methods #region Methods
   
public static void RequestUpdate() public static void RequestUpdate()
{ {
instance.UpdateRequested = true; instance.UpdateRequested = true;
} }
   
public void Update() public void Update()
{ {
this.surfaceRotation = this.GetSurfaceRotation(); this.surfaceRotation = this.GetSurfaceRotation();
   
this.previousHeading = this.heading; this.previousHeading = this.heading;
this.previousPitch = this.pitch; this.previousPitch = this.pitch;
this.previousRoll = this.roll; this.previousRoll = this.roll;
   
// This code was derived from MechJeb2's implementation for getting the vessel's surface relative rotation. // This code was derived from MechJeb2's implementation for getting the vessel's surface relative rotation.
this.heading = this.surfaceRotation.eulerAngles.y; this.heading = this.surfaceRotation.eulerAngles.y;
this.pitch = this.surfaceRotation.eulerAngles.x > 180.0f this.pitch = this.surfaceRotation.eulerAngles.x > 180.0f
? 360.0f - this.surfaceRotation.eulerAngles.x ? 360.0f - this.surfaceRotation.eulerAngles.x
: -this.surfaceRotation.eulerAngles.x; : -this.surfaceRotation.eulerAngles.x;
this.roll = this.surfaceRotation.eulerAngles.z > 180.0f this.roll = this.surfaceRotation.eulerAngles.z > 180.0f
? this.surfaceRotation.eulerAngles.z - 360.0f ? this.surfaceRotation.eulerAngles.z - 360.0f
: this.surfaceRotation.eulerAngles.z; : this.surfaceRotation.eulerAngles.z;
   
this.headingRate = this.heading - this.previousHeading; this.headingRate = this.heading - this.previousHeading;
this.pitchRate = this.pitch - this.previousPitch; this.pitchRate = this.pitch - this.previousPitch;
this.rollRate = this.roll - this.previousRoll; this.rollRate = this.roll - this.previousRoll;
} }
   
private Quaternion GetSurfaceRotation() private Quaternion GetSurfaceRotation()
{ {
// This code was derived from MechJeb2's implementation for getting the vessel's surface relative rotation. // This code was derived from MechJeb2's implementation for getting the vessel's surface relative rotation.
this.centreOfMass = FlightGlobals.ActiveVessel.findWorldCenterOfMass(); this.centreOfMass = FlightGlobals.ActiveVessel.findWorldCenterOfMass();
this.up = (this.centreOfMass - FlightGlobals.ActiveVessel.mainBody.position).normalized; this.up = (this.centreOfMass - FlightGlobals.ActiveVessel.mainBody.position).normalized;
this.north = Vector3.Exclude(this.up, (FlightGlobals.ActiveVessel.mainBody.position + FlightGlobals.ActiveVessel.mainBody.transform.up * (float)FlightGlobals.ActiveVessel.mainBody.Radius) - this.centreOfMass).normalized; this.north = Vector3.ProjectOnPlane((FlightGlobals.ActiveVessel.mainBody.position + FlightGlobals.ActiveVessel.mainBody.transform.up * (float)FlightGlobals.ActiveVessel.mainBody.Radius) - this.centreOfMass, this.up).normalized;
   
return Quaternion.Inverse(Quaternion.Euler(90.0f, 0.0f, 0.0f) * Quaternion.Inverse(FlightGlobals.ActiveVessel.transform.rotation) * Quaternion.LookRotation(this.north, this.up)); return Quaternion.Inverse(Quaternion.Euler(90.0f, 0.0f, 0.0f) * Quaternion.Inverse(FlightGlobals.ActiveVessel.transform.rotation) * Quaternion.LookRotation(this.north, this.up));
} }
   
#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
   
   
   
#endregion #endregion
   
namespace KerbalEngineer.Flight.Readouts.Vessel namespace KerbalEngineer.Flight.Readouts.Vessel
{ {
#region Using Directives #region Using Directives
   
using System; using System;
using VesselSimulator; using VesselSimulator;
   
#endregion #endregion
   
public class SimulationProcessor : IUpdatable, IUpdateRequest public class SimulationProcessor : IUpdatable, IUpdateRequest
{ {
#region Instance #region Instance
   
#region Fields #region Fields
   
private static readonly SimulationProcessor instance = new SimulationProcessor(); private static readonly SimulationProcessor instance = new SimulationProcessor();
   
#endregion #endregion
   
#region Constructors #region Constructors
   
static SimulationProcessor() static SimulationProcessor()
{ {
SimManager.OnReady += GetStageInfo; SimManager.OnReady += GetStageInfo;
} }
   
#endregion #endregion
   
#region Properties #region Properties
   
/// <summary> /// <summary>
/// Gets the current instance of the simulation processor. /// Gets the current instance of the simulation processor.
/// </summary> /// </summary>
public static SimulationProcessor Instance public static SimulationProcessor Instance
{ {
get { return instance; } get { return instance; }
} }
   
#endregion #endregion
   
#endregion #endregion
   
#region Properties #region Properties
   
/// <summary> /// <summary>
/// Gets the currently active vessel stage. /// Gets the currently active vessel stage.
/// </summary> /// </summary>
public static Stage LastStage { get; private set; } public static Stage LastStage { get; private set; }
   
/// <summary> /// <summary>
/// Gets whether the details are ready to be shown. /// Gets whether the details are ready to be shown.
/// </summary> /// </summary>
public static bool ShowDetails { get; private set; } public static bool ShowDetails { get; private set; }
   
/// <summary> /// <summary>
/// Gets an array of the vessel stages. /// Gets an array of the vessel stages.
/// </summary> /// </summary>
public static Stage[] Stages { get; private set; } public static Stage[] Stages { get; private set; }
   
public bool UpdateRequested { get; set; } public bool UpdateRequested { get; set; }
   
#endregion #endregion
   
#region Methods #region Methods
   
private static void GetStageInfo() private static void GetStageInfo()
{ {
Stages = SimManager.Stages; Stages = SimManager.Stages;
LastStage = SimManager.LastStage; LastStage = SimManager.LastStage;
} }
   
public static void RequestUpdate() public static void RequestUpdate()
{ {
instance.UpdateRequested = true; instance.UpdateRequested = true;
} }
   
public void Update() public void Update()
{ {
SimManager.RequestSimulation(); SimManager.RequestSimulation();
SimManager.TryStartSimulation(); SimManager.TryStartSimulation();
   
if (!SimManager.ResultsReady()) if (!SimManager.ResultsReady())
{ {
return; return;
} }
   
if (Stages != null && LastStage != null) if (Stages != null && LastStage != null)
{ {
ShowDetails = true; ShowDetails = true;
} }
   
if (FlightGlobals.ActiveVessel != null) if (FlightGlobals.ActiveVessel != null)
{ {
SimManager.Gravity = FlightGlobals.ActiveVessel.mainBody.gravParameter / SimManager.Gravity = FlightGlobals.ActiveVessel.mainBody.gravParameter /
Math.Pow(FlightGlobals.ActiveVessel.mainBody.Radius + Math.Pow(FlightGlobals.ActiveVessel.mainBody.Radius +
FlightGlobals.ActiveVessel.mainBody.GetAltitude(FlightGlobals.ActiveVessel.CoM), 2); FlightGlobals.ActiveVessel.mainBody.GetAltitude(FlightGlobals.ActiveVessel.CoM), 2);
SimManager.Velocity = FlightGlobals.ActiveVessel.srfSpeed; SimManager.Mach = FlightGlobals.ActiveVessel.mach;
} }
} }
   
#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 System.Linq; using System.Linq;
using System.Text; using System.Text;
   
using UnityEngine; using UnityEngine;
   
#endregion #endregion
   
namespace KerbalEngineer.VesselSimulator namespace KerbalEngineer.VesselSimulator
{ {
using Helpers;  
   
public class EngineSim public class EngineSim
{ {
private readonly ResourceContainer resourceConsumptions = new ResourceContainer(); private readonly ResourceContainer resourceConsumptions = new ResourceContainer();
   
public double actualThrust = 0; public double actualThrust = 0;
public bool isActive = false; public bool isActive = false;
public double isp = 0; public double isp = 0;
public PartSim partSim; public PartSim partSim;
public List<AppliedForce> appliedForces; public List<AppliedForce> appliedForces;
   
public double thrust = 0; public double thrust = 0;
   
public static double GetEngineThrust(float fuelFlow, double isp, float multiplier)  
{  
return fuelFlow * GetExhaustVelocity(isp) * multiplier;  
}  
   
public static double GetExhaustVelocity(double isp)  
{  
return isp * Units.GRAVITY;  
}  
   
public static double GetFlowRate(double thrust, double isp)  
{  
return thrust / GetExhaustVelocity(isp);  
}  
   
// Add thrust vector to account for directional losses // Add thrust vector to account for directional losses
public Vector3 thrustVec; public Vector3 thrustVec;
   
public EngineSim(PartSim theEngine, public EngineSim(PartSim theEngine,
double atmosphere, double atmosphere,
double velocity, double machNumber,
float maxThrust, float maxFuelFlow,
float minThrust, float minFuelFlow,
float thrustPercentage, float thrustPercentage,
float requestedThrust,  
Vector3 vecThrust, Vector3 vecThrust,
float realIsp,  
FloatCurve atmosphereCurve, FloatCurve atmosphereCurve,
FloatCurve velocityCurve, bool atmChangeFlow,
  FloatCurve atmCurve,
  FloatCurve velCurve,
  float currentThrottle,
  float IspG,
bool throttleLocked, bool throttleLocked,
List<Propellant> propellants, List<Propellant> propellants,
bool active, bool active,
bool correctThrust, bool correctThrust,
List<Transform> thrustTransforms, float minFuelFlow, float maxFuelFlow) List<Transform> thrustTransforms)
{ {
StringBuilder buffer = null; StringBuilder buffer = null;
//MonoBehaviour.print("Create EngineSim for " + theEngine.name); //MonoBehaviour.print("Create EngineSim for " + theEngine.name);
//MonoBehaviour.print("maxThrust = " + maxThrust); //MonoBehaviour.print("maxThrust = " + maxThrust);
//MonoBehaviour.print("minThrust = " + minThrust); //MonoBehaviour.print("minThrust = " + minThrust);
//MonoBehaviour.print("thrustPercentage = " + thrustPercentage); //MonoBehaviour.print("thrustPercentage = " + thrustPercentage);
//MonoBehaviour.print("requestedThrust = " + requestedThrust); //MonoBehaviour.print("requestedThrust = " + requestedThrust);
//MonoBehaviour.print("velocity = " + velocity); //MonoBehaviour.print("velocity = " + velocity);
   
this.partSim = theEngine; this.partSim = theEngine;
thrustPercentage = thrustPercentage * 0.01f;  
   
this.isActive = active; this.isActive = active;
//this.thrust = (maxThrust - minThrust) * (thrustPercentage / 100f) + minThrust; //this.thrust = (maxThrust - minThrust) * (thrustPercentage / 100f) + minThrust;
//MonoBehaviour.print("thrust = " + thrust); //MonoBehaviour.print("thrust = " + thrust);
   
this.thrustVec = vecThrust; this.thrustVec = vecThrust;
   
double flowRate = 0d; double flowRate = 0d;
if (this.partSim.hasVessel) if (this.partSim.hasVessel)
{ {
this.isp = atmosphereCurve.Evaluate((float)this.partSim.part.staticPressureAtm); //MonoBehaviour.print("hasVessel is true");
if (this.isp == 0.0)  
{ //this.actualThrust = this.isActive ? resultingThrust : 0.0;
MonoBehaviour.print("Isp at " + this.partSim.part.staticPressureAtm + " is zero. Flow rate will be NaN");  
} this.isp = atmosphereCurve.Evaluate((float)atmosphere);
   
this.actualThrust = isActive ? requestedThrust : 0.0; //if (this.isp == 0d)
this.thrust = GetEngineThrust(maxFuelFlow, isp, thrustPercentage); //{
if (velocityCurve != null) // MonoBehaviour.print("Isp at " + this.partSim.part.staticPressureAtm + " is zero. Flow rate will be NaN");
{ //}
this.actualThrust *= velocityCurve.Evaluate((float)velocity);  
this.thrust *= velocityCurve.Evaluate((float)velocity);  
  // correctThrust is less usefull now that the stock engines do it. Keep or remove.
   
  //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);
  //}
   
  float multiplier = 1;
  if (atmChangeFlow)
  {
  multiplier = (float)(partSim.part.atmDensity / 1.225);
  if (atmCurve != null)
  {
  multiplier = atmCurve.Evaluate(multiplier);
  }
  //MonoBehaviour.print("corrected thrust = " + thrust);
  }
  if (velCurve != null)
  {
  multiplier *= velCurve.Evaluate((float)machNumber);
} }
   
if (throttleLocked) if (throttleLocked)
{ {
//MonoBehaviour.print("throttleLocked is true"); //MonoBehaviour.print("throttleLocked is true");
flowRate = this.thrust / GetExhaustVelocity(isp); //flowRate = this.thrust / (this.isp * 9.82);
  flowRate = Mathf.Lerp(minFuelFlow, maxFuelFlow, (thrustPercentage / 100f)) * multiplier;
} }
else else
{ {
if (this.partSim.isLanded) if (partSim.isLanded)
{ {
//MonoBehaviour.print("partSim.isLanded is true, mainThrottle = " + FlightInputHandler.state.mainThrottle); //MonoBehaviour.print("partSim.isLanded is true, mainThrottle = " + FlightInputHandler.state.mainThrottle);
flowRate = GetFlowRate(Math.Max(float.Epsilon, this.thrust * FlightInputHandler.state.mainThrottle), isp); flowRate = Mathf.Lerp(minFuelFlow, maxFuelFlow, FlightInputHandler.state.mainThrottle * (thrustPercentage / 100f)) * multiplier;
} }
else else
{ {
if (requestedThrust > 0) if (currentThrottle > 0)
{ {
if (velocityCurve != null)  
{  
requestedThrust *= velocityCurve.Evaluate((float)velocity);  
//MonoBehaviour.print("requestedThrust at velocity = " + requestedThrust);  
}  
   
//MonoBehaviour.print("requestedThrust > 0"); //MonoBehaviour.print("requestedThrust > 0");
flowRate = GetFlowRate(requestedThrust, isp); //flowRate = requestedThrust / (this.isp * 9.82) * multiplier;
  flowRate = Mathf.Lerp(minFuelFlow, maxFuelFlow, currentThrottle * (thrustPercentage / 100f)) * multiplier;
   
} }
else else
{ {
//MonoBehaviour.print("requestedThrust <= 0"); //MonoBehaviour.print("requestedThrust <= 0");
flowRate = GetFlowRate(thrust, isp); flowRate = Mathf.Lerp(minFuelFlow, maxFuelFlow, (thrustPercentage / 100f)) * multiplier;
} }
} }
} }
} }
else else
{ {
  //MonoBehaviour.print("hasVessel is false");
this.isp = atmosphereCurve.Evaluate((float)atmosphere); this.isp = atmosphereCurve.Evaluate((float)atmosphere);
if (this.isp == 0d) if (this.isp == 0d)
{ {
MonoBehaviour.print("Isp at " + atmosphere + " is zero. Flow rate will be NaN"); MonoBehaviour.print("Isp at " + atmosphere + " is zero. Flow rate will be NaN");
} }
  //if (correctThrust)
thrust = GetEngineThrust(maxFuelFlow, isp, thrustPercentage); //{
  // float ispsl = atmosphereCurve.Evaluate(0);
if (velocityCurve != null) // if (ispsl != 0)
{ // {
this.thrust *= velocityCurve.Evaluate((float)velocity); // this.thrust = this.thrust * this.isp / ispsl;
} // }
  // else
flowRate = GetFlowRate(thrust, isp); // {
  // MonoBehaviour.print("Isp at sea level is zero. Unable to correct thrust.");
  // }
  // //MonoBehaviour.print("corrected thrust = " + thrust);
  //}
   
  float multiplier = 1;
  if (atmChangeFlow)
  {
  //multiplier = (float)(this.partSim.part.atmDensity / 1.225);
  multiplier = (float)atmosphere; // technically wrong but the same for my Editor need
  if (atmCurve != null)
  {
  multiplier = atmCurve.Evaluate(multiplier);
  }
  }
   
  if (velCurve != null)
  {
  multiplier *= velCurve.Evaluate((float)machNumber);
  }
   
  flowRate = Mathf.Lerp(minFuelFlow, maxFuelFlow, (thrustPercentage / 100f)) * multiplier;
} }
   
if (SimManager.logOutput) if (SimManager.logOutput)
{ {
buffer = new StringBuilder(1024); buffer = new StringBuilder(1024);
buffer.AppendFormat("flowRate = {0:g6}\n", flowRate); buffer.AppendFormat("flowRate = {0:g6}\n", flowRate);
} }
   
  thrust = flowRate * (isp * IspG);
  // I did not look into the diff between those 2 so I made them equal...
  actualThrust = thrust;
   
float flowMass = 0f; float flowMass = 0f;
foreach (Propellant propellant in propellants) foreach (Propellant propellant in propellants)
{ {
flowMass += propellant.ratio * ResourceContainer.GetResourceDensity(propellant.id); flowMass += propellant.ratio * ResourceContainer.GetResourceDensity(propellant.id);
} }
   
if (SimManager.logOutput) if (SimManager.logOutput)
{ {
buffer.AppendFormat("flowMass = {0:g6}\n", flowMass); buffer.AppendFormat("flowMass = {0:g6}\n", flowMass);
} }
   
foreach (Propellant propellant in propellants) foreach (Propellant propellant in propellants)
{ {
if (propellant.name == "ElectricCharge" || propellant.name == "IntakeAir") if (propellant.name == "ElectricCharge" || propellant.name == "IntakeAir")
{ {
continue; continue;
} }
   
double consumptionRate = propellant.ratio * flowRate / flowMass; double consumptionRate = propellant.ratio * flowRate / flowMass;
if (SimManager.logOutput) if (SimManager.logOutput)
{ {
buffer.AppendFormat("Add consumption({0}, {1}:{2:d}) = {3:g6}\n", ResourceContainer.GetResourceName(propellant.id), theEngine.name, theEngine.partId, consumptionRate); 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); this.resourceConsumptions.Add(propellant.id, consumptionRate);
} }
   
if (SimManager.logOutput) if (SimManager.logOutput)
{ {
MonoBehaviour.print(buffer); MonoBehaviour.print(buffer);
} }
   
appliedForces = new List<AppliedForce>(); appliedForces = new List<AppliedForce>();
double thrustPerThrustTransform = thrust / thrustTransforms.Count; double thrustPerThrustTransform = thrust / thrustTransforms.Count;
foreach (Transform thrustTransform in thrustTransforms) { foreach (Transform thrustTransform in thrustTransforms) {
Vector3d direction = thrustTransform.forward.normalized; Vector3d direction = thrustTransform.forward.normalized;
Vector3d position = thrustTransform.position; Vector3d position = thrustTransform.position;
appliedForces.Add(new AppliedForce(direction * thrustPerThrustTransform, position)); appliedForces.Add(new AppliedForce(direction * thrustPerThrustTransform, position));
} }
} }
   
public ResourceContainer ResourceConsumptions public ResourceContainer ResourceConsumptions
{ {
get { return this.resourceConsumptions; } get { return this.resourceConsumptions; }
} }
   
public bool SetResourceDrains(List<PartSim> allParts, List<PartSim> allFuelLines, HashSet<PartSim> drainingParts) public bool SetResourceDrains(List<PartSim> allParts, List<PartSim> allFuelLines, HashSet<PartSim> drainingParts)
{ {
LogMsg log = null; LogMsg log = null;
   
// A dictionary to hold a set of parts for each resource // A dictionary to hold a set of parts for each resource
Dictionary<int, HashSet<PartSim>> sourcePartSets = new Dictionary<int, HashSet<PartSim>>(); Dictionary<int, HashSet<PartSim>> sourcePartSets = new Dictionary<int, HashSet<PartSim>>();
   
foreach (int type in this.resourceConsumptions.Types) foreach (int type in this.resourceConsumptions.Types)
{ {
HashSet<PartSim> sourcePartSet = null; HashSet<PartSim> sourcePartSet = null;
switch (ResourceContainer.GetResourceFlowMode(type)) switch (ResourceContainer.GetResourceFlowMode(type))
{ {
case ResourceFlowMode.NO_FLOW: case ResourceFlowMode.NO_FLOW:
if (this.partSim.resources[type] > SimManager.RESOURCE_MIN && this.partSim.resourceFlowStates[type] != 0) if (this.partSim.resources[type] > SimManager.RESOURCE_MIN && this.partSim.resourceFlowStates[type] != 0)
{ {
sourcePartSet = new HashSet<PartSim>(); sourcePartSet = new HashSet<PartSim>();
//MonoBehaviour.print("SetResourceDrains(" + name + ":" + partId + ") setting sources to just this"); //MonoBehaviour.print("SetResourceDrains(" + name + ":" + partId + ") setting sources to just this");
sourcePartSet.Add(this.partSim); sourcePartSet.Add(this.partSim);
} }
break; break;
   
case ResourceFlowMode.ALL_VESSEL: case ResourceFlowMode.ALL_VESSEL:
foreach (PartSim aPartSim in allParts) foreach (PartSim aPartSim in allParts)
{ {
if (aPartSim.resources[type] > SimManager.RESOURCE_MIN && aPartSim.resourceFlowStates[type] != 0) if (aPartSim.resources[type] > SimManager.RESOURCE_MIN && aPartSim.resourceFlowStates[type] != 0)
{ {
if (sourcePartSet == null) if (sourcePartSet == null)
{ {
sourcePartSet = new HashSet<PartSim>(); sourcePartSet = new HashSet<PartSim>();
} }
   
sourcePartSet.Add(aPartSim); sourcePartSet.Add(aPartSim);
} }
} }
break; break;
   
case ResourceFlowMode.STAGE_PRIORITY_FLOW: case ResourceFlowMode.STAGE_PRIORITY_FLOW:
var stagePartSets = new Dictionary<int, HashSet<PartSim>>(); var stagePartSets = new Dictionary<int, HashSet<PartSim>>();
var maxStage = -1; var maxStage = -1;
   
//Logger.Log(type); //Logger.Log(type);
foreach (var aPartSim in allParts) foreach (var aPartSim in allParts)
{ {
if (aPartSim.resources[type] <= SimManager.RESOURCE_MIN || aPartSim.resourceFlowStates[type] == 0) continue; if (aPartSim.resources[type] <= SimManager.RESOURCE_MIN || aPartSim.resourceFlowStates[type] == 0) continue;
   
var stage = aPartSim.DecouplerCount(); var stage = aPartSim.DecouplerCount();
if (stage > maxStage) if (stage > maxStage)
{ {
maxStage = stage; maxStage = stage;
} }
   
if (!stagePartSets.TryGetValue(stage, out sourcePartSet)) if (!stagePartSets.TryGetValue(stage, out sourcePartSet))
{ {
sourcePartSet = new HashSet<PartSim>(); sourcePartSet = new HashSet<PartSim>();
stagePartSets.Add(stage, sourcePartSet); stagePartSets.Add(stage, sourcePartSet);
} }
sourcePartSet.Add(aPartSim); sourcePartSet.Add(aPartSim);
} }
   
for (var i = 0; i <= maxStage; i++) for (var i = 0; i <= maxStage; i++)
{ {
HashSet<PartSim> stagePartSet; HashSet<PartSim> stagePartSet;
if (stagePartSets.TryGetValue(i, out stagePartSet) && stagePartSet.Count > 0) if (stagePartSets.TryGetValue(i, out stagePartSet) && stagePartSet.Count > 0)
{ {
sourcePartSet = stagePartSet; sourcePartSet = stagePartSet;
} }
} }
break; break;
   
case ResourceFlowMode.STACK_PRIORITY_SEARCH: case ResourceFlowMode.STACK_PRIORITY_SEARCH:
HashSet<PartSim> visited = new HashSet<PartSim>(); HashSet<PartSim> visited = new HashSet<PartSim>();
   
if (SimManager.logOutput) if (SimManager.logOutput)
{ {
log = new LogMsg(); log = new LogMsg();
log.buf.AppendLine("Find " + ResourceContainer.GetResourceName(type) + " sources for " + this.partSim.name + ":" + this.partSim.partId); log.buf.AppendLine("Find " + ResourceContainer.GetResourceName(type) + " sources for " + this.partSim.name + ":" + this.partSim.partId);
} }
sourcePartSet = this.partSim.GetSourceSet(type, allParts, visited, log, ""); sourcePartSet = this.partSim.GetSourceSet(type, allParts, visited, log, "");
if (SimManager.logOutput) if (SimManager.logOutput)
{ {
MonoBehaviour.print(log.buf); MonoBehaviour.print(log.buf);
} }
break; break;
   
default: default:
MonoBehaviour.print("SetResourceDrains(" + this.partSim.name + ":" + this.partSim.partId + ") Unexpected flow type for " + ResourceContainer.GetResourceName(type) + ")"); MonoBehaviour.print("SetResourceDrains(" + this.partSim.name + ":" + this.partSim.partId + ") Unexpected flow type for " + ResourceContainer.GetResourceName(type) + ")");
break; break;
} }
   
if (sourcePartSet != null && sourcePartSet.Count > 0) if (sourcePartSet != null && sourcePartSet.Count > 0)
{ {
sourcePartSets[type] = sourcePartSet; sourcePartSets[type] = sourcePartSet;
if (SimManager.logOutput) if (SimManager.logOutput)
{ {
log = new LogMsg(); log = new LogMsg();
log.buf.AppendLine("Source parts for " + ResourceContainer.GetResourceName(type) + ":"); log.buf.AppendLine("Source parts for " + ResourceContainer.GetResourceName(type) + ":");
foreach (PartSim partSim in sourcePartSet) foreach (PartSim partSim in sourcePartSet)
{ {
log.buf.AppendLine(partSim.name + ":" + partSim.partId); log.buf.AppendLine(partSim.name + ":" + partSim.partId);
} }
MonoBehaviour.print(log.buf); MonoBehaviour.print(log.buf);
} }
} }
} }
   
// If we don't have sources for all the needed resources then return false without setting up any drains // 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) foreach (int type in this.resourceConsumptions.Types)
{ {
if (!sourcePartSets.ContainsKey(type)) if (!sourcePartSets.ContainsKey(type))
{ {
if (SimManager.logOutput) if (SimManager.logOutput)
{ {
MonoBehaviour.print("No source of " + ResourceContainer.GetResourceName(type)); MonoBehaviour.print("No source of " + ResourceContainer.GetResourceName(type));
} }
   
this.isActive = false; this.isActive = false;
return false; return false;
} }
} }
   
// Now we set the drains on the members of the sets and update the draining parts set // Now we set the drains on the members of the sets and update the draining parts set
foreach (int type in this.resourceConsumptions.Types) foreach (int type in this.resourceConsumptions.Types)
{ {
HashSet<PartSim> sourcePartSet = sourcePartSets[type]; HashSet<PartSim> sourcePartSet = sourcePartSets[type];
// Loop through the members of the set // Loop through the members of the set
double amount = this.resourceConsumptions[type] / sourcePartSet.Count; double amount = this.resourceConsumptions[type] / sourcePartSet.Count;
foreach (PartSim partSim in sourcePartSet) foreach (PartSim partSim in sourcePartSet)
{ {
if (SimManager.logOutput) if (SimManager.logOutput)
{ {
MonoBehaviour.print("Adding drain of " + amount + " " + ResourceContainer.GetResourceName(type) + " to " + partSim.name + ":" + partSim.partId); MonoBehaviour.print("Adding drain of " + amount + " " + ResourceContainer.GetResourceName(type) + " to " + partSim.name + ":" + partSim.partId);
} }
   
partSim.resourceDrains.Add(type, amount); partSim.resourceDrains.Add(type, amount);
drainingParts.Add(partSim); drainingParts.Add(partSim);
} }
} }
   
return true; return true;
} }
   
public void DumpEngineToBuffer(StringBuilder buffer, String prefix) public void DumpEngineToBuffer(StringBuilder buffer, String prefix)
{ {
buffer.Append(prefix); buffer.Append(prefix);
buffer.AppendFormat("[thrust = {0:g6}, actual = {1:g6}, isp = {2:g6}\n", this.thrust, this.actualThrust, this.isp); buffer.AppendFormat("[thrust = {0:g6}, actual = {1:g6}, isp = {2:g6}\n", this.thrust, this.actualThrust, this.isp);
} }
} }
} }
// //
// 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 System.Linq; using System.Linq;
using System.Text; using System.Text;
   
using KerbalEngineer.Extensions; using KerbalEngineer.Extensions;
   
using UnityEngine; using UnityEngine;
   
#endregion #endregion
   
namespace KerbalEngineer.VesselSimulator namespace KerbalEngineer.VesselSimulator
{ {
using CompoundParts; using CompoundParts;
   
public class PartSim public class PartSim
{ {
private readonly List<AttachNodeSim> attachNodes = new List<AttachNodeSim>(); private readonly List<AttachNodeSim> attachNodes = new List<AttachNodeSim>();
public Vector3d centerOfMass; public Vector3d centerOfMass;
public double baseMass = 0d; public double baseMass = 0d;
public double cost; public double cost;
public int decoupledInStage; public int decoupledInStage;
public bool fuelCrossFeed; public bool fuelCrossFeed;
public List<PartSim> fuelTargets = new List<PartSim>(); public List<PartSim> fuelTargets = new List<PartSim>();
public bool hasModuleEngines; public bool hasModuleEngines;
public bool hasModuleEnginesFX; public bool hasModuleEnginesFX;
public bool hasMultiModeEngine; public bool hasMultiModeEngine;
   
public bool hasVessel; public bool hasVessel;
public String initialVesselName; public String initialVesselName;
public int inverseStage; public int inverseStage;
public bool isDecoupler; public bool isDecoupler;
public bool isEngine; public bool isEngine;
public bool isFuelLine; public bool isFuelLine;
public bool isFuelTank; public bool isFuelTank;
public bool isLanded; public bool isLanded;
public bool isNoPhysics; public bool isNoPhysics;
public bool isSepratron; public bool isSepratron;
public bool localCorrectThrust; public bool localCorrectThrust;
public String name; public String name;
public String noCrossFeedNodeKey; public String noCrossFeedNodeKey;
public PartSim parent; public PartSim parent;
public AttachModes parentAttach; public AttachModes parentAttach;
public Part part; // This is only set while the data structures are being initialised public Part part; // This is only set while the data structures are being initialised
public int partId = 0; public int partId = 0;
public ResourceContainer resourceDrains = new ResourceContainer(); public ResourceContainer resourceDrains = new ResourceContainer();
public ResourceContainer resourceFlowStates = new ResourceContainer(); public ResourceContainer resourceFlowStates = new ResourceContainer();
public ResourceContainer resources = new ResourceContainer(); public ResourceContainer resources = new ResourceContainer();
public double startMass = 0d; public double startMass = 0d;
public String vesselName; public String vesselName;
public VesselType vesselType; public VesselType vesselType;
   
public PartSim(Part thePart, int id, double atmosphere, LogMsg log) public PartSim(Part thePart, int id, double atmosphere, LogMsg log)
{ {
this.part = thePart; this.part = thePart;
this.centerOfMass = thePart.transform.TransformPoint(thePart.CoMOffset); this.centerOfMass = thePart.transform.TransformPoint(thePart.CoMOffset);
this.partId = id; this.partId = id;
this.name = this.part.partInfo.name; this.name = this.part.partInfo.name;
   
if (log != null) if (log != null)
{ {
log.buf.AppendLine("Create PartSim for " + this.name); log.buf.AppendLine("Create PartSim for " + this.name);
} }
   
this.parent = null; this.parent = null;
this.parentAttach = part.attachMode; this.parentAttach = part.attachMode;
this.fuelCrossFeed = this.part.fuelCrossFeed; this.fuelCrossFeed = this.part.fuelCrossFeed;
this.noCrossFeedNodeKey = this.part.NoCrossFeedNodeKey; this.noCrossFeedNodeKey = this.part.NoCrossFeedNodeKey;
this.decoupledInStage = this.DecoupledInStage(this.part); this.decoupledInStage = this.DecoupledInStage(this.part);
this.isFuelLine = this.part.HasModule<CModuleFuelLine>(); this.isFuelLine = this.part.HasModule<CModuleFuelLine>();
this.isFuelTank = this.part is FuelTank; this.isFuelTank = this.part is FuelTank;
this.isSepratron = this.IsSepratron(); this.isSepratron = this.IsSepratron();
this.inverseStage = this.part.inverseStage; this.inverseStage = this.part.inverseStage;
//MonoBehaviour.print("inverseStage = " + inverseStage); //MonoBehaviour.print("inverseStage = " + inverseStage);
   
this.cost = this.part.GetCostWet(); this.cost = this.part.GetCostWet();
   
// Work out if the part should have no physical significance // Work out if the part should have no physical significance
this.isNoPhysics = this.part.HasModule<LaunchClamp>() || this.isNoPhysics = this.part.HasModule<LaunchClamp>() ||
this.part.physicalSignificance == Part.PhysicalSignificance.NONE || this.part.physicalSignificance == Part.PhysicalSignificance.NONE ||
this.part.PhysicsSignificance == 1; this.part.PhysicsSignificance == 1;
   
if (!this.isNoPhysics) if (!this.isNoPhysics)
{ {
this.baseMass = this.part.mass; this.baseMass = this.part.mass;
} }
   
if (SimManager.logOutput) if (SimManager.logOutput)
{ {
MonoBehaviour.print((this.isNoPhysics ? "Ignoring" : "Using") + " part.mass of " + this.part.mass); MonoBehaviour.print((this.isNoPhysics ? "Ignoring" : "Using") + " part.mass of " + this.part.mass);
} }
   
foreach (PartResource resource in this.part.Resources) 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 // 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 // This can happen if a resource capacity is 0 and tweakable
if (!Double.IsNaN(resource.amount)) if (!Double.IsNaN(resource.amount))
{ {
if (SimManager.logOutput) if (SimManager.logOutput)
{ {
MonoBehaviour.print(resource.resourceName + " = " + resource.amount); MonoBehaviour.print(resource.resourceName + " = " + resource.amount);
} }
   
this.resources.Add(resource.info.id, resource.amount); this.resources.Add(resource.info.id, resource.amount);
this.resourceFlowStates.Add(resource.info.id, resource.flowState ? 1 : 0); this.resourceFlowStates.Add(resource.info.id, resource.flowState ? 1 : 0);
} }
else else
{ {
MonoBehaviour.print(resource.resourceName + " is NaN. Skipping."); MonoBehaviour.print(resource.resourceName + " is NaN. Skipping.");
} }
} }
   
this.startMass = this.GetMass(); this.startMass = this.GetMass();
   
this.hasVessel = (this.part.vessel != null); this.hasVessel = (this.part.vessel != null);
this.isLanded = this.hasVessel && this.part.vessel.Landed; this.isLanded = this.hasVessel && this.part.vessel.Landed;
if (this.hasVessel) if (this.hasVessel)
{ {
this.vesselName = this.part.vessel.vesselName; this.vesselName = this.part.vessel.vesselName;
this.vesselType = this.part.vesselType; this.vesselType = this.part.vesselType;
} }
this.initialVesselName = this.part.initialVesselName; this.initialVesselName = this.part.initialVesselName;
   
this.hasMultiModeEngine = this.part.HasModule<MultiModeEngine>(); this.hasMultiModeEngine = this.part.HasModule<MultiModeEngine>();
this.hasModuleEnginesFX = this.part.HasModule<ModuleEnginesFX>(); this.hasModuleEnginesFX = this.part.HasModule<ModuleEnginesFX>();
this.hasModuleEngines = this.part.HasModule<ModuleEngines>(); this.hasModuleEngines = this.part.HasModule<ModuleEngines>();
   
this.isEngine = this.hasMultiModeEngine || this.hasModuleEnginesFX || this.hasModuleEngines; this.isEngine = this.hasMultiModeEngine || this.hasModuleEnginesFX || this.hasModuleEngines;
   
if (SimManager.logOutput) if (SimManager.logOutput)
{ {
MonoBehaviour.print("Created " + this.name + ". Decoupled in stage " + this.decoupledInStage); MonoBehaviour.print("Created " + this.name + ". Decoupled in stage " + this.decoupledInStage);
} }
} }
   
public ResourceContainer Resources public ResourceContainer Resources
{ {
get { return this.resources; } get { return this.resources; }
} }
   
public ResourceContainer ResourceDrains public ResourceContainer ResourceDrains
{ {
get { return this.resourceDrains; } get { return this.resourceDrains; }
} }
   
public void CreateEngineSims(List<EngineSim> allEngines, double atmosphere, double velocity, bool vectoredThrust, LogMsg log) public void CreateEngineSims(List<EngineSim> allEngines, double atmosphere, double mach, bool vectoredThrust, bool fullThrust, LogMsg log)
{ {
bool correctThrust = SimManager.DoesEngineUseCorrectedThrust(this.part); bool correctThrust = SimManager.DoesEngineUseCorrectedThrust(this.part);
if (log != null) if (log != null)
{ {
log.buf.AppendLine("CreateEngineSims for " + this.name); log.buf.AppendLine("CreateEngineSims for " + this.name);
   
foreach (PartModule partMod in this.part.Modules) foreach (PartModule partMod in this.part.Modules)
{ {
log.buf.AppendLine("Module: " + partMod.moduleName); log.buf.AppendLine("Module: " + partMod.moduleName);
} }
   
log.buf.AppendLine("correctThrust = " + correctThrust); log.buf.AppendLine("correctThrust = " + correctThrust);
} }
   
if (this.hasMultiModeEngine) if (this.hasMultiModeEngine)
{ {
// A multi-mode engine has multiple ModuleEnginesFX but only one is active at any point // 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 // The mode of the engine is the engineID of the ModuleEnginesFX that is active
string mode = this.part.GetModule<MultiModeEngine>().mode; string mode = this.part.GetModule<MultiModeEngine>().mode;
   
foreach (ModuleEnginesFX engine in this.part.GetModules<ModuleEnginesFX>()) foreach (ModuleEnginesFX engine in this.part.GetModules<ModuleEnginesFX>())
{ {
if (engine.engineID == mode) if (engine.engineID == mode)
{ {
if (log != null) if (log != null)
{ {
log.buf.AppendLine("Module: " + engine.moduleName); log.buf.AppendLine("Module: " + engine.moduleName);
} }
   
Vector3 thrustvec = this.CalculateThrustVector(vectoredThrust ? engine.thrustTransforms : null, log); Vector3 thrustvec = this.CalculateThrustVector(vectoredThrust ? engine.thrustTransforms : null, log);
   
EngineSim engineSim = new EngineSim(this, EngineSim engineSim = new EngineSim(this,
atmosphere, atmosphere,
velocity, mach,
engine.maxThrust, engine.maxFuelFlow,
engine.minThrust, engine.minFuelFlow,
engine.thrustPercentage, engine.thrustPercentage,
engine.resultingThrust, thrustvec,
thrustvec, engine.atmosphereCurve,
engine.realIsp, engine.atmChangeFlow,
engine.atmosphereCurve, engine.useAtmCurve ? engine.atmCurve : null,
engine.useVelocityCurve ? engine.velocityCurve : null, engine.useVelCurve ? engine.velCurve : null,
engine.throttleLocked, engine.currentThrottle,
engine.propellants, engine.g,
engine.isOperational, engine.throttleLocked || fullThrust,
correctThrust, engine.propellants,
engine.thrustTransforms, engine.minFuelFlow, engine.maxFuelFlow); engine.isOperational,
  correctThrust,
  engine.thrustTransforms);
allEngines.Add(engineSim); allEngines.Add(engineSim);
} }
} }
} }
else else
{ {
if (this.hasModuleEnginesFX) if (this.hasModuleEngines)
{ {
foreach (ModuleEnginesFX engine in this.part.GetModules<ModuleEnginesFX>()) foreach (ModuleEngines engine in this.part.GetModules<ModuleEngines>())
{ {
if (log != null) if (log != null)
{ {
log.buf.AppendLine("Module: " + engine.moduleName); log.buf.AppendLine("Module: " + engine.moduleName);
} }
   
Vector3 thrustvec = this.CalculateThrustVector(vectoredThrust ? engine.thrustTransforms : null, log); Vector3 thrustvec = this.CalculateThrustVector(vectoredThrust ? engine.thrustTransforms : null, log);
   
EngineSim engineSim = new EngineSim(this, EngineSim engineSim = new EngineSim(this,
atmosphere, atmosphere,
velocity, mach,
engine.maxThrust, engine.maxFuelFlow,
engine.minThrust, engine.minFuelFlow,
engine.thrustPercentage, engine.thrustPercentage,
engine.resultingThrust, thrustvec,
thrustvec, engine.atmosphereCurve,
engine.realIsp, engine.atmChangeFlow,
engine.atmosphereCurve, engine.useAtmCurve ? engine.atmCurve : null,
engine.useVelocityCurve ? engine.velocityCurve : null, engine.useVelCurve ? engine.velCurve : null,
engine.throttleLocked, engine.currentThrottle,
engine.propellants, engine.g,
engine.isOperational, engine.throttleLocked || fullThrust,
correctThrust, engine.propellants,
engine.thrustTransforms, engine.minFuelFlow, engine.maxFuelFlow); engine.isOperational,
allEngines.Add(engineSim); correctThrust,
} engine.thrustTransforms);
}  
   
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.minThrust,  
engine.thrustPercentage,  
engine.resultingThrust,  
thrustvec,  
engine.realIsp,  
engine.atmosphereCurve,  
engine.useVelocityCurve ? engine.velocityCurve : null,  
engine.throttleLocked,  
engine.propellants,  
engine.isOperational,  
correctThrust,  
engine.thrustTransforms, engine.minFuelFlow, engine.maxFuelFlow);  
allEngines.Add(engineSim); allEngines.Add(engineSim);
} }
} }
} }
   
if (log != null) if (log != null)
{ {
log.Flush(); log.Flush();
} }
} }
   
private Vector3 CalculateThrustVector(List<Transform> thrustTransforms, LogMsg log) private Vector3 CalculateThrustVector(List<Transform> thrustTransforms, LogMsg log)
{ {
if (thrustTransforms == null) if (thrustTransforms == null)
{ {
return Vector3.forward; return Vector3.forward;
} }
   
Vector3 thrustvec = Vector3.zero; Vector3 thrustvec = Vector3.zero;
foreach (Transform trans in thrustTransforms) foreach (Transform trans in thrustTransforms)
{ {
if (log != null) 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); 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; thrustvec -= trans.forward;
} }
   
if (log != null) 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); log.buf.AppendFormat("ThrustVec = ({0:g6}, {1:g6}, {2:g6}) length = {3:g6}\n", thrustvec.x, thrustvec.y, thrustvec.z, thrustvec.magnitude);
} }
   
thrustvec.Normalize(); thrustvec.Normalize();
   
if (log != null) 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); log.buf.AppendFormat("ThrustVecN = ({0:g6}, {1:g6}, {2:g6}) length = {3:g6}\n", thrustvec.x, thrustvec.y, thrustvec.z, thrustvec.magnitude);
} }
   
return thrustvec; return thrustvec;
} }
   
public void SetupParent(Dictionary<Part, PartSim> partSimLookup, LogMsg log) public void SetupParent(Dictionary<Part, PartSim> partSimLookup, LogMsg log)
{ {
if (this.part.parent != null) if (this.part.parent != null)
{ {
this.parent = null; this.parent = null;
if (partSimLookup.TryGetValue(this.part.parent, out this.parent)) if (partSimLookup.TryGetValue(this.part.parent, out this.parent))
{ {
if (log != null) if (log != null)
{ {
log.buf.AppendLine("Parent part is " + this.parent.name + ":" + this.parent.partId); log.buf.AppendLine("Parent part is " + this.parent.name + ":" + this.parent.partId);
} }
} }
else else
{ {
if (log != null) if (log != null)
{ {
log.buf.AppendLine("No PartSim for parent part (" + this.part.parent.partInfo.name + ")"); log.buf.AppendLine("No PartSim for parent part (" + this.part.parent.partInfo.name + ")");
} }
} }
} }
} }
   
public void SetupAttachNodes(Dictionary<Part, PartSim> partSimLookup, LogMsg log) public void SetupAttachNodes(Dictionary<Part, PartSim> partSimLookup, LogMsg log)
{ {
if (log != null) if (log != null)
{ {
log.buf.AppendLine("SetupAttachNodes for " + this.name + ":" + this.partId + ""); log.buf.AppendLine("SetupAttachNodes for " + this.name + ":" + this.partId + "");
} }
   
this.attachNodes.Clear(); this.attachNodes.Clear();
foreach (AttachNode attachNode in this.part.attachNodes) foreach (AttachNode attachNode in this.part.attachNodes)
{ {
if (log != null) if (log != null)
{ {
log.buf.AppendLine("AttachNode " + attachNode.id + " = " + (attachNode.attachedPart != null ? attachNode.attachedPart.partInfo.name : "null")); log.buf.AppendLine("AttachNode " + attachNode.id + " = " + (attachNode.attachedPart != null ? attachNode.attachedPart.partInfo.name : "null"));
} }
   
if (attachNode.attachedPart != null && attachNode.id != "Strut") if (attachNode.attachedPart != null && attachNode.id != "Strut")
{ {
PartSim attachedSim; PartSim attachedSim;
if (partSimLookup.TryGetValue(attachNode.attachedPart, out attachedSim)) if (partSimLookup.TryGetValue(attachNode.attachedPart, out attachedSim))
{ {
if (log != null) if (log != null)
{ {
log.buf.AppendLine("Adding attached node " + attachedSim.name + ":" + attachedSim.partId + ""); log.buf.AppendLine("Adding attached node " + attachedSim.name + ":" + attachedSim.partId + "");
} }
   
this.attachNodes.Add(new AttachNodeSim(attachedSim, attachNode.id, attachNode.nodeType)); this.attachNodes.Add(new AttachNodeSim(attachedSim, attachNode.id, attachNode.nodeType));
} }
else else
{ {
if (log != null) if (log != null)
{ {
log.buf.AppendLine("No PartSim for attached part (" + attachNode.attachedPart.partInfo.name + ")"); log.buf.AppendLine("No PartSim for attached part (" + attachNode.attachedPart.partInfo.name + ")");
} }
} }
} }
} }
   
foreach (Part p in this.part.fuelLookupTargets) foreach (Part p in this.part.fuelLookupTargets)
{ {
if (p != null) if (p != null)
{ {
PartSim targetSim; PartSim targetSim;
if (partSimLookup.TryGetValue(p, out targetSim)) if (partSimLookup.TryGetValue(p, out targetSim))
{ {
if (log != null) if (log != null)
{ {
log.buf.AppendLine("Fuel target: " + targetSim.name + ":" + targetSim.partId); log.buf.AppendLine("Fuel target: " + targetSim.name + ":" + targetSim.partId);
} }
   
this.fuelTargets.Add(targetSim); this.fuelTargets.Add(targetSim);
} }
else else
{ {
if (log != null) if (log != null)
{ {
log.buf.AppendLine("No PartSim for fuel target (" + p.name + ")"); log.buf.AppendLine("No PartSim for fuel target (" + p.name + ")");
} }
} }
} }
} }
} }
   
private int DecoupledInStage(Part thePart, int stage = -1) private int DecoupledInStage(Part thePart, int stage = -1)
{ {
if (this.IsDecoupler(thePart)) if (this.IsDecoupler(thePart))
{ {
if (thePart.inverseStage > stage) if (thePart.inverseStage > stage)
{ {
stage = thePart.inverseStage; stage = thePart.inverseStage;
} }
} }
   
if (thePart.parent != null) if (thePart.parent != null)
{ {
stage = this.DecoupledInStage(thePart.parent, stage); stage = this.DecoupledInStage(thePart.parent, stage);
} }
   
return stage; return stage;
} }
   
private bool IsDecoupler(Part thePart) private bool IsDecoupler(Part thePart)
{ {
return thePart.HasModule<ModuleDecouple>() || return thePart.HasModule<ModuleDecouple>() ||
thePart.HasModule<ModuleAnchoredDecoupler>(); thePart.HasModule<ModuleAnchoredDecoupler>();
} }
   
private bool IsActiveDecoupler(Part thePart) private bool IsActiveDecoupler(Part thePart)
{ {
return thePart.FindModulesImplementing<ModuleDecouple>().Any(mod => !mod.isDecoupled) || return thePart.FindModulesImplementing<ModuleDecouple>().Any(mod => !mod.isDecoupled) ||
thePart.FindModulesImplementing<ModuleAnchoredDecoupler>().Any(mod => !mod.isDecoupled); thePart.FindModulesImplementing<ModuleAnchoredDecoupler>().Any(mod => !mod.isDecoupled);
} }
   
private bool IsSepratron() private bool IsSepratron()
{ {
if (!this.part.ActivatesEvenIfDisconnected) if (!this.part.ActivatesEvenIfDisconnected)
{ {
return false; return false;
} }
   
if (this.part is SolidRocket) if (this.part is SolidRocket)
{ {
return true; return true;
} }
   
var modList = this.part.Modules.OfType<ModuleEngines>(); var modList = this.part.Modules.OfType<ModuleEngines>();
if (modList.Count() == 0) if (modList.Count() == 0)
{ {
return false; return false;
} }
   
if (modList.First().throttleLocked) if (modList.First().throttleLocked)
{ {
return true; return true;
} }
   
return false; return false;
} }
   
public void ReleasePart() public void ReleasePart()
{ {
this.part = null; this.part = null;
} }
   
// All functions below this point must not rely on the part member (it may be 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) public HashSet<PartSim> GetSourceSet(int type, List<PartSim> allParts, HashSet<PartSim> visited, LogMsg log, String indent)
{ {
if (log != null) if (log != null)
{ {
log.buf.AppendLine(indent + "GetSourceSet(" + ResourceContainer.GetResourceName(type) + ") for " + this.name + ":" + this.partId); log.buf.AppendLine(indent + "GetSourceSet(" + ResourceContainer.GetResourceName(type) + ") for " + this.name + ":" + this.partId);
indent += " "; indent += " ";
} }
   
HashSet<PartSim> allSources = new HashSet<PartSim>(); HashSet<PartSim> allSources = new HashSet<PartSim>();
HashSet<PartSim> partSources = null; 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. // 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 (visited.Contains(this))
{ {
if (log != null) if (log != null)
{ {
log.buf.AppendLine(indent + "Returning empty set, already visited (" + this.name + ":" + this.partId + ")"); log.buf.AppendLine(indent + "Returning empty set, already visited (" + this.name + ":" + this.partId + ")");
} }
   
return allSources; return allSources;
} }
   
//if (log != null) //if (log != null)
// log.buf.AppendLine(indent + "Adding this to visited"); // log.buf.AppendLine(indent + "Adding this to visited");
   
visited.Add(this); 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. // 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. // 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"); //MonoBehaviour.print("foreach fuel line");
   
foreach (PartSim partSim in this.fuelTargets) foreach (PartSim partSim in this.fuelTargets)
{ {
if (visited.Contains(partSim)) if (visited.Contains(partSim))
{ {
//if (log != null) //if (log != null)
// log.buf.AppendLine(indent + "Fuel target already visited, skipping (" + partSim.name + ":" + partSim.partId + ")"); // log.buf.AppendLine(indent + "Fuel target already visited, skipping (" + partSim.name + ":" + partSim.partId + ")");
} }
else else
{ {
//if (log != null) //if (log != null)
// log.buf.AppendLine(indent + "Adding fuel target as source (" + partSim.name + ":" + partSim.partId + ")"); // log.buf.AppendLine(indent + "Adding fuel target as source (" + partSim.name + ":" + partSim.partId + ")");
   
partSources = partSim.GetSourceSet(type, allParts, visited, log, indent); partSources = partSim.GetSourceSet(type, allParts, visited, log, indent);
if (partSources.Count > 0) if (partSources.Count > 0)
{ {
allSources.UnionWith(partSources); allSources.UnionWith(partSources);
partSources.Clear(); partSources.Clear();
} }
} }
} }
   
if (allSources.Count > 0) if (allSources.Count > 0)
{ {
if (log != null) if (log != null)
{ {
log.buf.AppendLine(indent + "Returning " + allSources.Count + " fuel target sources (" + this.name + ":" + this.partId + ")"); log.buf.AppendLine(indent + "Returning " + allSources.Count + " fuel target sources (" + this.name + ":" + this.partId + ")");
} }
   
return allSources; 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 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. // 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, // 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] // 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. // 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] // 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) if (this.fuelCrossFeed)
{ {
//MonoBehaviour.print("foreach attach node"); //MonoBehaviour.print("foreach attach node");
foreach (AttachNodeSim attachSim in this.attachNodes) foreach (AttachNodeSim attachSim in this.attachNodes)
{ {
if (attachSim.attachedPartSim != null) if (attachSim.attachedPartSim != null)
{ {
if (attachSim.nodeType == AttachNode.NodeType.Stack) if (attachSim.nodeType == AttachNode.NodeType.Stack)
{ {
if (!(this.noCrossFeedNodeKey != null && this.noCrossFeedNodeKey.Length > 0 && attachSim.id.Contains(this.noCrossFeedNodeKey))) if (!(this.noCrossFeedNodeKey != null && this.noCrossFeedNodeKey.Length > 0 && attachSim.id.Contains(this.noCrossFeedNodeKey)))
{ {
if (visited.Contains(attachSim.attachedPartSim)) if (visited.Contains(attachSim.attachedPartSim))
{ {
//if (log != null) //if (log != null)
// log.buf.AppendLine(indent + "Attached part already visited, skipping (" + attachSim.attachedPartSim.name + ":" + attachSim.attachedPartSim.partId + ")"); // log.buf.AppendLine(indent + "Attached part already visited, skipping (" + attachSim.attachedPartSim.name + ":" + attachSim.attachedPartSim.partId + ")");
} }
else else
{ {
//if (log != null) //if (log != null)
// log.buf.AppendLine(indent + "Adding attached part as source (" + attachSim.attachedPartSim.name + ":" + attachSim.attachedPartSim.partId + ")"); // log.buf.AppendLine(indent + "Adding attached part as source (" + attachSim.attachedPartSim.name + ":" + attachSim.attachedPartSim.partId + ")");
   
partSources = attachSim.attachedPartSim.GetSourceSet(type, allParts, visited, log, indent); partSources = attachSim.attachedPartSim.GetSourceSet(type, allParts, visited, log, indent);
if (partSources.Count > 0) if (partSources.Count > 0)
{ {
allSources.UnionWith(partSources); allSources.UnionWith(partSources);
partSources.Clear(); partSources.Clear();
} }
} }
} }
} }
} }
} }
   
if (allSources.Count > 0) if (allSources.Count > 0)
{ {
if (log != null) if (log != null)
{ {
log.buf.AppendLine(indent + "Returning " + allSources.Count + " attached sources (" + this.name + ":" + this.partId + ")"); log.buf.AppendLine(indent + "Returning " + allSources.Count + " attached sources (" + this.name + ":" + this.partId + ")");
} }
   
return allSources; 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 // 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. // 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 // 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] // 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.HasType(type) && this.resourceFlowStates[type] != 0)
{ {
if (this.resources[type] > SimManager.RESOURCE_MIN) if (this.resources[type] > SimManager.RESOURCE_MIN)
{ {
allSources.Add(this); allSources.Add(this);
   
if (log != null) if (log != null)
{ {
log.buf.AppendLine(indent + "Returning enabled tank as only source (" + this.name + ":" + this.partId + ")"); log.buf.AppendLine(indent + "Returning enabled tank as only source (" + this.name + ":" + this.partId + ")");
} }
} }
   
return allSources; 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 // 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] // parent and returns whatever the parent scan returned. [Experiment] [Experiment]
if (this.parent != null && this.parentAttach == AttachModes.SRF_ATTACH) if (this.parent != null && this.parentAttach == AttachModes.SRF_ATTACH)
{ {
if (this.fuelCrossFeed) if (this.fuelCrossFeed)
{ {
if (visited.Contains(this.parent)) if (visited.Contains(this.parent))
{ {
//if (log != null) //if (log != null)
// log.buf.AppendLine(indent + "Parent part already visited, skipping (" + parent.name + ":" + parent.partId + ")"); // log.buf.AppendLine(indent + "Parent part already visited, skipping (" + parent.name + ":" + parent.partId + ")");
} }
else else
{ {
allSources = this.parent.GetSourceSet(type, allParts, visited, log, indent); allSources = this.parent.GetSourceSet(type, allParts, visited, log, indent);
if (allSources.Count > 0) if (allSources.Count > 0)
{ {
if (log != null) if (log != null)
{ {
log.buf.AppendLine(indent + "Returning " + allSources.Count + " parent sources (" + this.name + ":" + this.partId + ")"); log.buf.AppendLine(indent + "Returning " + allSources.Count + " parent sources (" + this.name + ":" + this.partId + ")");
} }
   
return allSources; return allSources;
} }
} }
} }
} }
   
// Rule 8: If all preceding rules failed, part returns empty list. // Rule 8: If all preceding rules failed, part returns empty list.
//if (log != null) //if (log != null)
// log.buf.AppendLine(indent + "Returning empty set, no sources found (" + name + ":" + partId + ")"); // log.buf.AppendLine(indent + "Returning empty set, no sources found (" + name + ":" + partId + ")");
   
return allSources; return allSources;
} }
   
public void RemoveAttachedParts(HashSet<PartSim> partSims) public void RemoveAttachedParts(HashSet<PartSim> partSims)
{ {
// Loop through the attached parts // Loop through the attached parts
foreach (AttachNodeSim attachSim in this.attachNodes) foreach (AttachNodeSim attachSim in this.attachNodes)
{ {
// If the part is in the set then "remove" it by clearing the PartSim reference // If the part is in the set then "remove" it by clearing the PartSim reference
if (partSims.Contains(attachSim.attachedPartSim)) if (partSims.Contains(attachSim.attachedPartSim))
{ {
attachSim.attachedPartSim = null; attachSim.attachedPartSim = null;
} }
} }
} }
   
public void DrainResources(double time) public void DrainResources(double time)
{ {
//MonoBehaviour.print("DrainResources(" + name + ":" + partId + ", " + time + ")"); //MonoBehaviour.print("DrainResources(" + name + ":" + partId + ", " + time + ")");
foreach (int type in this.resourceDrains.Types) foreach (int type in this.resourceDrains.Types)
{ {
//MonoBehaviour.print("draining " + (time * resourceDrains[type]) + " " + ResourceContainer.GetResourceName(type)); //MonoBehaviour.print("draining " + (time * resourceDrains[type]) + " " + ResourceContainer.GetResourceName(type));
this.resources.Add(type, -time * this.resourceDrains[type]); this.resources.Add(type, -time * this.resourceDrains[type]);
//MonoBehaviour.print(ResourceContainer.GetResourceName(type) + " left = " + resources[type]); //MonoBehaviour.print(ResourceContainer.GetResourceName(type) + " left = " + resources[type]);
} }
} }
   
public double TimeToDrainResource() public double TimeToDrainResource()
{ {
//MonoBehaviour.print("TimeToDrainResource(" + name + ":" + partId + ")"); //MonoBehaviour.print("TimeToDrainResource(" + name + ":" + partId + ")");
double time = double.MaxValue; double time = double.MaxValue;
   
foreach (int type in this.resourceDrains.Types) foreach (int type in this.resourceDrains.Types)
{ {
if (this.resourceDrains[type] > 0) if (this.resourceDrains[type] > 0)
{ {
time = Math.Min(time, this.resources[type] / this.resourceDrains[type]); time = Math.Min(time, this.resources[type] / this.resourceDrains[type]);
//MonoBehaviour.print("type = " + ResourceContainer.GetResourceName(type) + " amount = " + resources[type] + " rate = " + resourceDrains[type] + " time = " + time); //MonoBehaviour.print("type = " + ResourceContainer.GetResourceName(type) + " amount = " + resources[type] + " rate = " + resourceDrains[type] + " time = " + time);
} }
} }
   
//if (time < double.MaxValue) //if (time < double.MaxValue)
// MonoBehaviour.print("TimeToDrainResource(" + name + ":" + partId + ") = " + time); // MonoBehaviour.print("TimeToDrainResource(" + name + ":" + partId + ") = " + time);
return time; return time;
} }
   
public bool EmptyOf(HashSet<int> types) public bool EmptyOf(HashSet<int> types)
{ {
foreach (int type in types) foreach (int type in types)
{ {
if (this.resources.HasType(type) && this.resourceFlowStates[type] != 0 && (double)this.resources[type] > SimManager.RESOURCE_MIN) if (this.resources.HasType(type) && this.resourceFlowStates[type] != 0 && (double)this.resources[type] > SimManager.RESOURCE_MIN)
{ {
return false; return false;
} }
} }
   
return true; return true;
} }
   
public int DecouplerCount() public int DecouplerCount()
{ {
int count = 0; int count = 0;
PartSim partSim = this; PartSim partSim = this;
while (partSim != null) while (partSim != null)
{ {
if (partSim.isDecoupler) if (partSim.isDecoupler)
{ {
count++; count++;
} }
   
partSim = partSim.parent; partSim = partSim.parent;
} }
return count; return count;
} }
   
public double GetStartMass() public double GetStartMass()
{ {
return this.startMass; return this.startMass;
} }
   
public double GetMass() public double GetMass()
{ {
double mass = this.baseMass; double mass = this.baseMass;
   
foreach (int type in this.resources.Types) foreach (int type in this.resources.Types)
{ {
mass += this.resources.GetResourceMass(type); mass += this.resources.GetResourceMass(type);
} }
   
return mass; return mass;
} }
   
public String DumpPartAndParentsToBuffer(StringBuilder buffer, String prefix) public String DumpPartAndParentsToBuffer(StringBuilder buffer, String prefix)
{ {
if (this.parent != null) if (this.parent != null)
{ {
prefix = this.parent.DumpPartAndParentsToBuffer(buffer, prefix) + " "; prefix = this.parent.DumpPartAndParentsToBuffer(buffer, prefix) + " ";
} }
   
this.DumpPartToBuffer(buffer, prefix); this.DumpPartToBuffer(buffer, prefix);
   
return prefix; return prefix;
} }
   
public void DumpPartToBuffer(StringBuilder buffer, String prefix, List<PartSim> allParts = null) public void DumpPartToBuffer(StringBuilder buffer, String prefix, List<PartSim> allParts = null)
{ {
buffer.Append(prefix); buffer.Append(prefix);
buffer.Append(this.name); buffer.Append(this.name);
buffer.AppendFormat(":[id = {0:d}, decouple = {1:d}, invstage = {2:d}", this.partId, this.decoupledInStage, this.inverseStage); 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(", vesselName = '{0}'", this.vesselName);
buffer.AppendFormat(", vesselType = {0}", SimManager.GetVesselTypeString(this.vesselType)); buffer.AppendFormat(", vesselType = {0}", SimManager.GetVesselTypeString(this.vesselType));
buffer.AppendFormat(", initialVesselName = '{0}'", this.initialVesselName); buffer.AppendFormat(", initialVesselName = '{0}'", this.initialVesselName);
   
buffer.AppendFormat(", fuelCF = {0}", this.fuelCrossFeed); buffer.AppendFormat(", fuelCF = {0}", this.fuelCrossFeed);
buffer.AppendFormat(", noCFNKey = '{0}'", this.noCrossFeedNodeKey); buffer.AppendFormat(", noCFNKey = '{0}'", this.noCrossFeedNodeKey);
   
buffer.AppendFormat(", isSep = {0}", this.isSepratron); buffer.AppendFormat(", isSep = {0}", this.isSepratron);
   
foreach (int type in this.resources.Types) foreach (int type in this.resources.Types)
{ {
buffer.AppendFormat(", {0} = {1:g6}", ResourceContainer.GetResourceName(type), this.resources[type]); buffer.AppendFormat(", {0} = {1:g6}", ResourceContainer.GetResourceName(type), this.resources[type]);
} }
   
if (this.attachNodes.Count > 0) if (this.attachNodes.Count > 0)
{ {
buffer.Append(", attached = <"); buffer.Append(", attached = <");
this.attachNodes[0].DumpToBuffer(buffer); this.attachNodes[0].DumpToBuffer(buffer);
for (int i = 1; i < this.attachNodes.Count; i++) for (int i = 1; i < this.attachNodes.Count; i++)
{ {
buffer.Append(", "); buffer.Append(", ");
this.attachNodes[i].DumpToBuffer(buffer); this.attachNodes[i].DumpToBuffer(buffer);
} }
buffer.Append(">"); buffer.Append(">");
} }
   
// Add more info here // Add more info here
   
buffer.Append("]\n"); buffer.Append("]\n");
   
if (allParts != null) if (allParts != null)
{ {
String newPrefix = prefix + " "; String newPrefix = prefix + " ";
foreach (PartSim partSim in allParts) foreach (PartSim partSim in allParts)
{ {
if (partSim.parent == this) if (partSim.parent == this)
{ {
partSim.DumpPartToBuffer(buffer, newPrefix, allParts); partSim.DumpPartToBuffer(buffer, newPrefix, allParts);
} }
} }
} }
} }
} }
} }
// //
// 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/>.
// //
   
namespace KerbalEngineer.VesselSimulator namespace KerbalEngineer.VesselSimulator
{ {
#region Using Directives #region Using Directives
   
using System; using System;
using System.Diagnostics; using System.Diagnostics;
using System.Reflection; using System.Reflection;
using System.Threading; using System.Threading;
using UnityEngine; using UnityEngine;
   
#endregion #endregion
   
public class SimManager public class SimManager
{ {
#region Constants #region Constants
   
public const double RESOURCE_MIN = 0.0001; public const double RESOURCE_MIN = 0.0001;
   
#endregion #endregion
   
#region Fields #region Fields
   
public static bool dumpTree = false; public static bool dumpTree = false;
public static bool logOutput = false; public static bool logOutput = false;
public static TimeSpan minSimTime = new TimeSpan(0, 0, 0, 0, 150); public static TimeSpan minSimTime = new TimeSpan(0, 0, 0, 0, 150);
public static bool vectoredThrust = false; public static bool vectoredThrust = false;
private static readonly object locker = new object(); private static readonly object locker = new object();
private static readonly Stopwatch timer = new Stopwatch(); private static readonly Stopwatch timer = new Stopwatch();
   
private static bool bRequested; private static bool bRequested;
private static bool bRunning; private static bool bRunning;
private static TimeSpan delayBetweenSims; private static TimeSpan delayBetweenSims;
   
// Support for RealFuels using reflection to check localCorrectThrust without dependency // Support for RealFuels using reflection to check localCorrectThrust without dependency
   
private static bool hasCheckedForMods; private static bool hasCheckedForMods;
private static bool hasInstalledRealFuels; private static bool hasInstalledRealFuels;
private static FieldInfo RF_ModuleEngineConfigs_localCorrectThrust; private static FieldInfo RF_ModuleEngineConfigs_localCorrectThrust;
private static FieldInfo RF_ModuleHybridEngine_localCorrectThrust; private static FieldInfo RF_ModuleHybridEngine_localCorrectThrust;
private static FieldInfo RF_ModuleHybridEngines_localCorrectThrust; private static FieldInfo RF_ModuleHybridEngines_localCorrectThrust;
private static bool hasInstalledKIDS; private static bool hasInstalledKIDS;
private static MethodInfo KIDS_Utils_GetIspMultiplier; private static MethodInfo KIDS_Utils_GetIspMultiplier;
private static bool bKIDSThrustISP = false; private static bool bKIDSThrustISP = false;
#endregion #endregion
   
#region Delegates #region Delegates
   
public delegate void ReadyEvent(); public delegate void ReadyEvent();
   
#endregion #endregion
   
#region Events #region Events
   
public static event ReadyEvent OnReady; public static event ReadyEvent OnReady;
   
#endregion #endregion
   
#region Properties #region Properties
   
public static double Atmosphere { get; set; } public static double Atmosphere { get; set; }
   
public static double Gravity { get; set; } public static double Gravity { get; set; }
   
public static Stage LastStage { get; private set; } public static Stage LastStage { get; private set; }
   
public static Stage[] Stages { get; private set; } public static Stage[] Stages { get; private set; }
   
public static double Velocity { get; set; } public static double Mach { get; set; }
   
public static String failMessage { get; private set; } public static String failMessage { get; private set; }
   
#endregion #endregion
   
#region Methods #region Methods
   
private static void CheckForMods() private static void CheckForMods()
{ {
hasCheckedForMods = true; hasCheckedForMods = true;
   
foreach (var assembly in AssemblyLoader.loadedAssemblies) foreach (var assembly in AssemblyLoader.loadedAssemblies)
{ {
MonoBehaviour.print("Assembly:" + assembly.assembly); MonoBehaviour.print("Assembly:" + assembly.assembly);
   
var name = assembly.assembly.ToString().Split(',')[0]; var name = assembly.assembly.ToString().Split(',')[0];
   
if (name == "RealFuels") if (name == "RealFuels")
{ {
MonoBehaviour.print("Found RealFuels mod"); MonoBehaviour.print("Found RealFuels mod");
   
var RF_ModuleEngineConfigs_Type = assembly.assembly.GetType("RealFuels.ModuleEngineConfigs"); var RF_ModuleEngineConfigs_Type = assembly.assembly.GetType("RealFuels.ModuleEngineConfigs");
if (RF_ModuleEngineConfigs_Type != null) if (RF_ModuleEngineConfigs_Type != null)
{ {
RF_ModuleEngineConfigs_localCorrectThrust = RF_ModuleEngineConfigs_Type.GetField("localCorrectThrust"); RF_ModuleEngineConfigs_localCorrectThrust = RF_ModuleEngineConfigs_Type.GetField("localCorrectThrust");
} }
   
var RF_ModuleHybridEngine_Type = assembly.assembly.GetType("RealFuels.ModuleHybridEngine"); var RF_ModuleHybridEngine_Type = assembly.assembly.GetType("RealFuels.ModuleHybridEngine");
if (RF_ModuleHybridEngine_Type != null) if (RF_ModuleHybridEngine_Type != null)
{ {
RF_ModuleHybridEngine_localCorrectThrust = RF_ModuleHybridEngine_Type.GetField("localCorrectThrust"); RF_ModuleHybridEngine_localCorrectThrust = RF_ModuleHybridEngine_Type.GetField("localCorrectThrust");
} }
   
var RF_ModuleHybridEngines_Type = assembly.assembly.GetType("RealFuels.ModuleHybridEngines"); var RF_ModuleHybridEngines_Type = assembly.assembly.GetType("RealFuels.ModuleHybridEngines");
if (RF_ModuleHybridEngines_Type != null) if (RF_ModuleHybridEngines_Type != null)
{ {
RF_ModuleHybridEngines_localCorrectThrust = RF_ModuleHybridEngines_Type.GetField("localCorrectThrust"); RF_ModuleHybridEngines_localCorrectThrust = RF_ModuleHybridEngines_Type.GetField("localCorrectThrust");
} }
   
hasInstalledRealFuels = true; hasInstalledRealFuels = true;
break; break;
} }
else if (name == "KerbalIspDifficultyScaler") else if (name == "KerbalIspDifficultyScaler")
{ {
var KIDS_Utils_Type = assembly.assembly.GetType("KerbalIspDifficultyScaler.KerbalIspDifficultyScalerUtils"); var KIDS_Utils_Type = assembly.assembly.GetType("KerbalIspDifficultyScaler.KerbalIspDifficultyScalerUtils");
if (KIDS_Utils_Type != null) if (KIDS_Utils_Type != null)
{ {
KIDS_Utils_GetIspMultiplier = KIDS_Utils_Type.GetMethod("GetIspMultiplier"); KIDS_Utils_GetIspMultiplier = KIDS_Utils_Type.GetMethod("GetIspMultiplier");
} }
   
hasInstalledKIDS = true; hasInstalledKIDS = true;
} }
} }
} }
   
public static bool DoesEngineUseCorrectedThrust(Part theEngine) public static bool DoesEngineUseCorrectedThrust(Part theEngine)
{ {
if (hasInstalledRealFuels) if (hasInstalledRealFuels)
{ {
// Look for any of the Real Fuels engine modules and call the relevant method to find out // Look for any of the Real Fuels engine modules and call the relevant method to find out
if (RF_ModuleEngineConfigs_localCorrectThrust != null && theEngine.Modules.Contains("ModuleEngineConfigs")) if (RF_ModuleEngineConfigs_localCorrectThrust != null && theEngine.Modules.Contains("ModuleEngineConfigs"))
{ {
var modEngineConfigs = theEngine.Modules["ModuleEngineConfigs"]; var modEngineConfigs = theEngine.Modules["ModuleEngineConfigs"];
if (modEngineConfigs != null) if (modEngineConfigs != null)
{ {
// Return the localCorrectThrust // Return the localCorrectThrust
return (bool)RF_ModuleEngineConfigs_localCorrectThrust.GetValue(modEngineConfigs); return (bool)RF_ModuleEngineConfigs_localCorrectThrust.GetValue(modEngineConfigs);
} }
} }
   
if (RF_ModuleHybridEngine_localCorrectThrust != null && theEngine.Modules.Contains("ModuleHybridEngine")) if (RF_ModuleHybridEngine_localCorrectThrust != null && theEngine.Modules.Contains("ModuleHybridEngine"))
{ {
var modHybridEngine = theEngine.Modules["ModuleHybridEngine"]; var modHybridEngine = theEngine.Modules["ModuleHybridEngine"];
if (modHybridEngine != null) if (modHybridEngine != null)
{ {
// Return the localCorrectThrust // Return the localCorrectThrust
return (bool)RF_ModuleHybridEngine_localCorrectThrust.GetValue(modHybridEngine); return (bool)RF_ModuleHybridEngine_localCorrectThrust.GetValue(modHybridEngine);
} }
} }
   
if (RF_ModuleHybridEngines_localCorrectThrust != null && theEngine.Modules.Contains("ModuleHybridEngines")) if (RF_ModuleHybridEngines_localCorrectThrust != null && theEngine.Modules.Contains("ModuleHybridEngines"))
{ {
var modHybridEngines = theEngine.Modules["ModuleHybridEngines"]; var modHybridEngines = theEngine.Modules["ModuleHybridEngines"];
if (modHybridEngines != null) if (modHybridEngines != null)
{ {
// Return the localCorrectThrust // Return the localCorrectThrust
return (bool)RF_ModuleHybridEngines_localCorrectThrust.GetValue(modHybridEngines); return (bool)RF_ModuleHybridEngines_localCorrectThrust.GetValue(modHybridEngines);
} }
} }
} }
   
if (hasInstalledKIDS && HighLogic.LoadedSceneIsEditor) if (hasInstalledKIDS && HighLogic.LoadedSceneIsEditor)
{ {
return bKIDSThrustISP; return bKIDSThrustISP;
} }
   
return false; return false;
} }
   
public static void UpdateModSettings() public static void UpdateModSettings()
{ {
if (!hasCheckedForMods) if (!hasCheckedForMods)
{ {
CheckForMods(); CheckForMods();
} }
   
if (hasInstalledKIDS) if (hasInstalledKIDS)
{ {
// (out ispMultiplierVac, out ispMultiplierAtm, out extendToZeroIsp, out thrustCorrection, out ispCutoff, out thrustCutoff); // (out ispMultiplierVac, out ispMultiplierAtm, out extendToZeroIsp, out thrustCorrection, out ispCutoff, out thrustCutoff);
object[] parameters = new object[6]; object[] parameters = new object[6];
KIDS_Utils_GetIspMultiplier.Invoke(null, parameters); KIDS_Utils_GetIspMultiplier.Invoke(null, parameters);
bKIDSThrustISP = (bool)parameters[3]; bKIDSThrustISP = (bool)parameters[3];
} }
} }
   
public static String GetVesselTypeString(VesselType vesselType) public static String GetVesselTypeString(VesselType vesselType)
{ {
switch (vesselType) switch (vesselType)
{ {
case VesselType.Debris: case VesselType.Debris:
return "Debris"; return "Debris";
case VesselType.SpaceObject: case VesselType.SpaceObject:
return "SpaceObject"; return "SpaceObject";
case VesselType.Unknown: case VesselType.Unknown:
return "Unknown"; return "Unknown";
case VesselType.Probe: case VesselType.Probe:
return "Probe"; return "Probe";
case VesselType.Rover: case VesselType.Rover:
return "Rover"; return "Rover";
case VesselType.Lander: case VesselType.Lander:
return "Lander"; return "Lander";
case VesselType.Ship: case VesselType.Ship:
return "Ship"; return "Ship";
case VesselType.Station: case VesselType.Station:
return "Station"; return "Station";
case VesselType.Base: case VesselType.Base:
return "Base"; return "Base";
case VesselType.EVA: case VesselType.EVA:
return "EVA"; return "EVA";
case VesselType.Flag: case VesselType.Flag:
return "Flag"; return "Flag";
} }
return "Undefined"; return "Undefined";
} }
   
public static void RequestSimulation() public static void RequestSimulation()
{ {
if (!hasCheckedForMods) if (!hasCheckedForMods)
{ {
CheckForMods(); CheckForMods();
} }
   
lock (locker) lock (locker)
{ {
bRequested = true; bRequested = true;
if (!timer.IsRunning) if (!timer.IsRunning)
{ {
timer.Start(); timer.Start();
} }
} }
} }
   
public static bool ResultsReady() public static bool ResultsReady()
{ {
lock (locker) lock (locker)
{ {
return !bRunning; return !bRunning;
} }
} }
   
public static void TryStartSimulation() public static void TryStartSimulation()
{ {
lock (locker) lock (locker)
{ {
if (!bRequested || bRunning || (timer.Elapsed < delayBetweenSims && timer.Elapsed >= TimeSpan.Zero) || (!HighLogic.LoadedSceneIsEditor && FlightGlobals.ActiveVessel == null)) if (!bRequested || bRunning || (timer.Elapsed < delayBetweenSims && timer.Elapsed >= TimeSpan.Zero) || (!HighLogic.LoadedSceneIsEditor && FlightGlobals.ActiveVessel == null))
{ {
return; return;
} }
   
bRequested = false; bRequested = false;
timer.Reset(); timer.Reset();
} }
   
StartSimulation(); StartSimulation();
} }
   
private static void ClearResults() private static void ClearResults()
{ {
failMessage = ""; failMessage = "";
Stages = null; Stages = null;
LastStage = null; LastStage = null;
} }
   
private static void RunSimulation(object simObject) private static void RunSimulation(object simObject)
{ {
try try
{ {
Stages = (simObject as Simulation).RunSimulation(); Stages = (simObject as Simulation).RunSimulation();
if (Stages != null && Stages.Length > 0) if (Stages != null && Stages.Length > 0)
{ {
if (logOutput) if (logOutput)
{ {
foreach (var stage in Stages) foreach (var stage in Stages)
{ {
stage.Dump(); stage.Dump();
} }
} }
LastStage = Stages[Stages.Length - 1]; LastStage = Stages[Stages.Length - 1];
} }
} }
catch (Exception e) catch (Exception e)
{ {
MonoBehaviour.print("Exception in RunSimulation: " + e); MonoBehaviour.print("Exception in RunSimulation: " + e);
Logger.Exception(e); Logger.Exception(e);
Stages = null; Stages = null;
LastStage = null; LastStage = null;
failMessage = e.ToString(); failMessage = e.ToString();
} }
lock (locker) lock (locker)
{ {
timer.Stop(); timer.Stop();
#if TIMERS #if TIMERS
MonoBehaviour.print("Total simulation time: " + timer.ElapsedMilliseconds + "ms"); MonoBehaviour.print("Total simulation time: " + timer.ElapsedMilliseconds + "ms");
#else #else
if (logOutput) if (logOutput)
{ {
MonoBehaviour.print("Total simulation time: " + timer.ElapsedMilliseconds + "ms"); MonoBehaviour.print("Total simulation time: " + timer.ElapsedMilliseconds + "ms");
} }
#endif #endif
   
delayBetweenSims = minSimTime - timer.Elapsed; delayBetweenSims = minSimTime - timer.Elapsed;
if (delayBetweenSims < TimeSpan.Zero) if (delayBetweenSims < TimeSpan.Zero)
{ {
delayBetweenSims = TimeSpan.Zero; delayBetweenSims = TimeSpan.Zero;
} }
   
timer.Reset(); timer.Reset();
timer.Start(); timer.Start();
   
bRunning = false; bRunning = false;
if (OnReady != null) if (OnReady != null)
{ {
OnReady(); OnReady();
} }
} }
   
logOutput = false; logOutput = false;
} }
   
private static void StartSimulation() private static void StartSimulation()
{ {
try try
{ {
lock (locker) lock (locker)
{ {
bRunning = true; bRunning = true;
} }
   
ClearResults(); ClearResults();
   
lock (locker) lock (locker)
{ {
timer.Start(); timer.Start();
} }
   
var parts = HighLogic.LoadedSceneIsEditor ? EditorLogic.fetch.ship.parts : FlightGlobals.ActiveVessel.Parts; var parts = HighLogic.LoadedSceneIsEditor ? EditorLogic.fetch.ship.parts : FlightGlobals.ActiveVessel.Parts;
   
// Create the Simulation object in this thread // Create the Simulation object in this thread
var sim = new Simulation(); var sim = new Simulation();
   
// This call doesn't ever fail at the moment but we'll check and return a sensible error for display // 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)) if (sim.PrepareSimulation(parts, Gravity, Atmosphere, Mach, dumpTree, vectoredThrust))
{ {
ThreadPool.QueueUserWorkItem(RunSimulation, sim); ThreadPool.QueueUserWorkItem(RunSimulation, sim);
} }
else else
{ {
failMessage = "PrepareSimulation failed"; failMessage = "PrepareSimulation failed";
lock (locker) lock (locker)
{ {
bRunning = false; bRunning = false;
} }
logOutput = false; logOutput = false;
} }
} }
catch (Exception e) catch (Exception e)
{ {
MonoBehaviour.print("Exception in StartSimulation: " + e); MonoBehaviour.print("Exception in StartSimulation: " + e);
Logger.Exception(e); Logger.Exception(e);
failMessage = e.ToString(); failMessage = e.ToString();
lock (locker) lock (locker)
{ {
bRunning = false; bRunning = false;
} }
logOutput = false; logOutput = false;
} }
dumpTree = false; dumpTree = false;
} }
   
#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 System.Diagnostics; using System.Diagnostics;
using System.Text; using System.Text;
   
using UnityEngine; using UnityEngine;
   
#endregion #endregion
   
namespace KerbalEngineer.VesselSimulator namespace KerbalEngineer.VesselSimulator
{ {
using CompoundParts; using CompoundParts;
using Extensions; using Extensions;
   
public class Simulation public class Simulation
{ {
private const double STD_GRAVITY = 9.82; private const double STD_GRAVITY = 9.82;
private const double SECONDS_PER_DAY = 86400; private const double SECONDS_PER_DAY = 86400;
private readonly Stopwatch _timer = new Stopwatch(); private readonly Stopwatch _timer = new Stopwatch();
private List<EngineSim> activeEngines; private List<EngineSim> activeEngines;
private List<EngineSim> allEngines; private List<EngineSim> allEngines;
private List<PartSim> allFuelLines; private List<PartSim> allFuelLines;
private List<PartSim> allParts; private List<PartSim> allParts;
private double atmosphere; private double atmosphere;
private int currentStage; private int currentStage;
private double currentisp; private double currentisp;
private bool doingCurrent; private bool doingCurrent;
private List<PartSim> dontStageParts; private List<PartSim> dontStageParts;
private HashSet<PartSim> drainingParts; private HashSet<PartSim> drainingParts;
private HashSet<int> drainingResources; private HashSet<int> drainingResources;
private double gravity; private double gravity;
   
private int lastStage; private int lastStage;
private List<Part> partList; private List<Part> partList;
private double simpleTotalThrust; private double simpleTotalThrust;
private double stageStartMass; private double stageStartMass;
private Vector3d stageStartCom; private Vector3d stageStartCom;
private double stageTime; private double stageTime;
private double stepEndMass; private double stepEndMass;
private double stepStartMass; private double stepStartMass;
private double totalStageActualThrust; private double totalStageActualThrust;
private double totalStageFlowRate; private double totalStageFlowRate;
private double totalStageIspFlowRate; private double totalStageIspFlowRate;
private double totalStageThrust; private double totalStageThrust;
private ForceAccumulator totalStageThrustForce; private ForceAccumulator totalStageThrustForce;
private Vector3 vecActualThrust; private Vector3 vecActualThrust;
private Vector3 vecStageDeltaV; private Vector3 vecStageDeltaV;
private Vector3 vecThrust; private Vector3 vecThrust;
private double velocity; private double mach;
public String vesselName; public String vesselName;
public VesselType vesselType; public VesselType vesselType;
   
public Simulation() public Simulation()
{ {
if (SimManager.logOutput) if (SimManager.logOutput)
{ {
MonoBehaviour.print("Simulation created"); MonoBehaviour.print("Simulation created");
} }
} }
   
private double ShipMass private double ShipMass
{ {
get get
{ {
double mass = 0d; double mass = 0d;
   
foreach (PartSim partSim in this.allParts) foreach (PartSim partSim in this.allParts)
{ {
mass += partSim.GetMass(); mass += partSim.GetMass();
} }
   
return mass; return mass;
} }
} }
   
private Vector3d ShipCom private Vector3d ShipCom
{ {
get get
{ {
WeightedVectorAverager averager = new WeightedVectorAverager(); WeightedVectorAverager averager = new WeightedVectorAverager();
   
foreach (PartSim partSim in this.allParts) foreach (PartSim partSim in this.allParts)
{ {
averager.Add(partSim.centerOfMass, partSim.GetMass()); averager.Add(partSim.centerOfMass, partSim.GetMass());
} }
   
return averager.Get(); return averager.Get();
} }
} }
   
// This function prepares the simulation by creating all the necessary data structures it will // 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 // 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 // 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. // 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) public bool PrepareSimulation(List<Part> parts, double theGravity, double theAtmosphere = 0, double theMach = 0, bool dumpTree = false, bool vectoredThrust = false, bool fullThrust = false)
{ {
LogMsg log = null; LogMsg log = null;
if (SimManager.logOutput) if (SimManager.logOutput)
{ {
log = new LogMsg(); log = new LogMsg();
log.buf.AppendLine("PrepareSimulation started"); log.buf.AppendLine("PrepareSimulation started");
dumpTree = true; dumpTree = true;
} }
this._timer.Start(); this._timer.Start();
   
// Store the parameters in members for ease of access in other functions // Store the parameters in members for ease of access in other functions
this.partList = parts; this.partList = parts;
this.gravity = theGravity; this.gravity = theGravity;
this.atmosphere = theAtmosphere; this.atmosphere = theAtmosphere;
this.velocity = theVelocity; this.mach = theMach;
this.lastStage = Staging.lastStage; this.lastStage = Staging.lastStage;
//MonoBehaviour.print("lastStage = " + lastStage); //MonoBehaviour.print("lastStage = " + lastStage);
   
// Create the lists for our simulation parts // Create the lists for our simulation parts
this.allParts = new List<PartSim>(); this.allParts = new List<PartSim>();
this.allFuelLines = new List<PartSim>(); this.allFuelLines = new List<PartSim>();
this.drainingParts = new HashSet<PartSim>(); this.drainingParts = new HashSet<PartSim>();
this.allEngines = new List<EngineSim>(); this.allEngines = new List<EngineSim>();
this.activeEngines = new List<EngineSim>(); this.activeEngines = new List<EngineSim>();
this.drainingResources = new HashSet<int>(); this.drainingResources = new HashSet<int>();
   
// A dictionary for fast lookup of Part->PartSim during the preparation phase // A dictionary for fast lookup of Part->PartSim during the preparation phase
Dictionary<Part, PartSim> partSimLookup = new Dictionary<Part, PartSim>(); Dictionary<Part, PartSim> partSimLookup = new Dictionary<Part, PartSim>();
   
if (this.partList.Count > 0 && this.partList[0].vessel != null) if (this.partList.Count > 0 && this.partList[0].vessel != null)
{ {
this.vesselName = this.partList[0].vessel.vesselName; this.vesselName = this.partList[0].vessel.vesselName;
this.vesselType = this.partList[0].vessel.vesselType; this.vesselType = this.partList[0].vessel.vesselType;
} }
   
// First we create a PartSim for each Part (giving each a unique id) // First we create a PartSim for each Part (giving each a unique id)
int partId = 1; int partId = 1;
foreach (Part part in this.partList) 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 the part is already in the lookup dictionary then log it and skip to the next part
if (partSimLookup.ContainsKey(part)) if (partSimLookup.ContainsKey(part))
{ {
if (log != null) if (log != null)
{ {
log.buf.AppendLine("Part " + part.name + " appears in vessel list more than once"); log.buf.AppendLine("Part " + part.name + " appears in vessel list more than once");
} }
continue; continue;
} }
   
// Create the PartSim // Create the PartSim
PartSim partSim = new PartSim(part, partId, this.atmosphere, log); PartSim partSim = new PartSim(part, partId, this.atmosphere, log);
   
// Add it to the Part lookup dictionary and the necessary lists // Add it to the Part lookup dictionary and the necessary lists
partSimLookup.Add(part, partSim); partSimLookup.Add(part, partSim);
this.allParts.Add(partSim); this.allParts.Add(partSim);
if (partSim.isFuelLine) if (partSim.isFuelLine)
{ {
this.allFuelLines.Add(partSim); this.allFuelLines.Add(partSim);
} }
if (partSim.isEngine) if (partSim.isEngine)
{ {
partSim.CreateEngineSims(this.allEngines, this.atmosphere, this.velocity, vectoredThrust, log); partSim.CreateEngineSims(this.allEngines, this.atmosphere, this.mach, vectoredThrust, fullThrust, log);
} }
   
partId++; partId++;
} }
   
this.UpdateActiveEngines(); this.UpdateActiveEngines();
   
// Now that all the PartSims have been created we can do any set up that needs access to other parts // 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 // First we set up all the parent links
foreach (PartSim partSim in this.allParts) foreach (PartSim partSim in this.allParts)
{ {
partSim.SetupParent(partSimLookup, log); partSim.SetupParent(partSimLookup, log);
} }
   
// Then, in the VAB/SPH, we add the parent of each fuel line to the fuelTargets list of their targets // Then, in the VAB/SPH, we add the parent of each fuel line to the fuelTargets list of their targets
if (HighLogic.LoadedSceneIsEditor) if (HighLogic.LoadedSceneIsEditor)
{ {
foreach (PartSim partSim in this.allFuelLines) foreach (PartSim partSim in this.allFuelLines)
{ {
CModuleFuelLine fuelLine = partSim.part.GetModule<CModuleFuelLine>(); CModuleFuelLine fuelLine = partSim.part.GetModule<CModuleFuelLine>();
if (fuelLine.target != null) if (fuelLine.target != null)
{ {
PartSim targetSim; PartSim targetSim;
if (partSimLookup.TryGetValue(fuelLine.target, out targetSim)) if (partSimLookup.TryGetValue(fuelLine.target, out targetSim))
{ {
if (log != null) if (log != null)
{ {
log.buf.AppendLine("Fuel line target is " + targetSim.name + ":" + targetSim.partId); log.buf.AppendLine("Fuel line target is " + targetSim.name + ":" + targetSim.partId);
} }
   
targetSim.fuelTargets.Add(partSim.parent); targetSim.fuelTargets.Add(partSim.parent);
} }
else else
{ {
if (log != null) if (log != null)
{ {
log.buf.AppendLine("No PartSim for fuel line target (" + partSim.part.partInfo.name + ")"); log.buf.AppendLine("No PartSim for fuel line target (" + partSim.part.partInfo.name + ")");
} }
} }
} }
else else
{ {
if (log != null) if (log != null)
{ {
log.buf.AppendLine("Fuel line target is null"); log.buf.AppendLine("Fuel line target is null");
} }
} }
} }
} }
   
//MonoBehaviour.print("SetupAttachNodes and count stages"); //MonoBehaviour.print("SetupAttachNodes and count stages");
foreach (PartSim partSim in this.allParts) foreach (PartSim partSim in this.allParts)
{ {
partSim.SetupAttachNodes(partSimLookup, log); partSim.SetupAttachNodes(partSimLookup, log);
if (partSim.decoupledInStage >= this.lastStage) if (partSim.decoupledInStage >= this.lastStage)
{ {
this.lastStage = partSim.decoupledInStage + 1; this.lastStage = partSim.decoupledInStage + 1;
} }
} }
   
// And finally release the Part references from all the PartSims // And finally release the Part references from all the PartSims
//MonoBehaviour.print("ReleaseParts"); //MonoBehaviour.print("ReleaseParts");
foreach (PartSim partSim in this.allParts) foreach (PartSim partSim in this.allParts)
{ {
partSim.ReleasePart(); partSim.ReleasePart();
} }
   
// And dereference the core's part list // And dereference the core's part list
this.partList = null; this.partList = null;
   
this._timer.Stop(); this._timer.Stop();
if (log != null) if (log != null)
{ {
log.buf.AppendLine("PrepareSimulation: " + this._timer.ElapsedMilliseconds + "ms"); log.buf.AppendLine("PrepareSimulation: " + this._timer.ElapsedMilliseconds + "ms");
log.Flush(); log.Flush();
} }
   
if (dumpTree) if (dumpTree)
{ {
this.Dump(); this.Dump();
} }
   
return true; return true;
} }
   
// This function runs the simulation and returns a newly created array of Stage objects // This function runs the simulation and returns a newly created array of Stage objects
public Stage[] RunSimulation() public Stage[] RunSimulation()
{ {
if (SimManager.logOutput) if (SimManager.logOutput)
{ {
MonoBehaviour.print("RunSimulation started"); MonoBehaviour.print("RunSimulation started");
} }
   
this._timer.Start(); this._timer.Start();
   
LogMsg log = null; LogMsg log = null;
if (SimManager.logOutput) if (SimManager.logOutput)
{ {
log = new LogMsg(); log = new LogMsg();
} }
   
// Start with the last stage to simulate // Start with the last stage to simulate
// (this is in a member variable so it can be accessed by AllowedToStage and ActivateStage) // (this is in a member variable so it can be accessed by AllowedToStage and ActivateStage)
this.currentStage = this.lastStage; this.currentStage = this.lastStage;
   
// Work out which engines would be active if just doing the staging and if this is different to the // 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 // currently active engines then generate an extra stage
// Loop through all the engines // Loop through all the engines
bool anyActive = false; bool anyActive = false;
foreach (EngineSim engine in this.allEngines) foreach (EngineSim engine in this.allEngines)
{ {
if (log != null) if (log != null)
{ {
log.buf.AppendLine("Testing engine mod of " + engine.partSim.name + ":" + engine.partSim.partId); log.buf.AppendLine("Testing engine mod of " + engine.partSim.name + ":" + engine.partSim.partId);
} }
bool bActive = engine.isActive; bool bActive = engine.isActive;
bool bStage = (engine.partSim.inverseStage >= this.currentStage); bool bStage = (engine.partSim.inverseStage >= this.currentStage);
if (log != null) if (log != null)
{ {
log.buf.AppendLine("bActive = " + bActive + " bStage = " + bStage); log.buf.AppendLine("bActive = " + bActive + " bStage = " + bStage);
} }
if (HighLogic.LoadedSceneIsFlight) if (HighLogic.LoadedSceneIsFlight)
{ {
if (bActive) if (bActive)
{ {
anyActive = true; anyActive = true;
} }
if (bActive != bStage) if (bActive != bStage)
{ {
// If the active state is different to the state due to staging // If the active state is different to the state due to staging
if (log != null) if (log != null)
{ {
log.buf.AppendLine("Need to do current active engines first"); log.buf.AppendLine("Need to do current active engines first");
} }
   
this.doingCurrent = true; this.doingCurrent = true;
} }
} }
else else
{ {
if (bStage) if (bStage)
{ {
if (log != null) if (log != null)
{ {
log.buf.AppendLine("Marking as active"); log.buf.AppendLine("Marking as active");
} }
   
engine.isActive = true; engine.isActive = true;
} }
} }
} }
   
// If we need to do current because of difference in engine activation and there actually are active engines // If we need to do current because of difference in engine activation and there actually are active engines
// then we do the extra stage otherwise activate the next stage and don't treat it as current // then we do the extra stage otherwise activate the next stage and don't treat it as current
if (this.doingCurrent && anyActive) if (this.doingCurrent && anyActive)
{ {
this.currentStage++; this.currentStage++;
} }
else else
{ {
this.ActivateStage(); this.ActivateStage();
this.doingCurrent = false; this.doingCurrent = false;
} }
   
// Create a list of lists of PartSims that prevent decoupling // Create a list of lists of PartSims that prevent decoupling
List<List<PartSim>> dontStagePartsLists = this.BuildDontStageLists(log); List<List<PartSim>> dontStagePartsLists = this.BuildDontStageLists(log);
   
if (log != null) if (log != null)
{ {
log.Flush(); log.Flush();
} }
   
// Create the array of stages that will be returned // Create the array of stages that will be returned
Stage[] stages = new Stage[this.currentStage + 1]; Stage[] stages = new Stage[this.currentStage + 1];
   
   
// Loop through the stages // Loop through the stages
while (this.currentStage >= 0) while (this.currentStage >= 0)
{ {
if (log != null) if (log != null)
{ {
log.buf.AppendLine("Simulating stage " + this.currentStage); log.buf.AppendLine("Simulating stage " + this.currentStage);
log.buf.AppendLine("ShipMass = " + this.ShipMass); log.buf.AppendLine("ShipMass = " + this.ShipMass);
log.Flush(); log.Flush();
this._timer.Reset(); this._timer.Reset();
this._timer.Start(); this._timer.Start();
} }
   
// Update active engines and resource drains // Update active engines and resource drains
this.UpdateResourceDrains(); this.UpdateResourceDrains();
   
// Create the Stage object for this stage // Create the Stage object for this stage
Stage stage = new Stage(); Stage stage = new Stage();
   
this.stageTime = 0d; this.stageTime = 0d;
this.vecStageDeltaV = Vector3.zero; this.vecStageDeltaV = Vector3.zero;
   
this.stageStartMass = this.ShipMass; this.stageStartMass = this.ShipMass;
this.stageStartCom = this.ShipCom; this.stageStartCom = this.ShipCom;
   
this.stepStartMass = this.stageStartMass; this.stepStartMass = this.stageStartMass;
this.stepEndMass = 0; this.stepEndMass = 0;
   
this.CalculateThrustAndISP(); this.CalculateThrustAndISP();
   
// Store various things in the Stage object // Store various things in the Stage object
stage.thrust = this.totalStageThrust; stage.thrust = this.totalStageThrust;
//MonoBehaviour.print("stage.thrust = " + stage.thrust); //MonoBehaviour.print("stage.thrust = " + stage.thrust);
stage.thrustToWeight = this.totalStageThrust / (this.stageStartMass * this.gravity); stage.thrustToWeight = this.totalStageThrust / (this.stageStartMass * this.gravity);
stage.maxThrustToWeight = stage.thrustToWeight; stage.maxThrustToWeight = stage.thrustToWeight;
//MonoBehaviour.print("StageMass = " + stageStartMass); //MonoBehaviour.print("StageMass = " + stageStartMass);
//MonoBehaviour.print("Initial maxTWR = " + stage.maxThrustToWeight); //MonoBehaviour.print("Initial maxTWR = " + stage.maxThrustToWeight);
stage.actualThrust = this.totalStageActualThrust; stage.actualThrust = this.totalStageActualThrust;
stage.actualThrustToWeight = this.totalStageActualThrust / (this.stageStartMass * this.gravity); stage.actualThrustToWeight = this.totalStageActualThrust / (this.stageStartMass * this.gravity);
   
// calculate torque and associates // calculate torque and associates
stage.maxThrustTorque = this.totalStageThrustForce.TorqueAt(this.stageStartCom).magnitude; stage.maxThrustTorque = this.totalStageThrustForce.TorqueAt(this.stageStartCom).magnitude;
   
// torque divided by thrust. imagine that all engines are at the end of a lever that tries to turn the ship. // torque divided by thrust. imagine that all engines are at the end of a lever that tries to turn the ship.
// this numerical value, in meters, would represent the length of that lever. // this numerical value, in meters, would represent the length of that lever.
double torqueLeverArmLength = (stage.thrust <= 0) ? 0 : stage.maxThrustTorque / stage.thrust; double torqueLeverArmLength = (stage.thrust <= 0) ? 0 : stage.maxThrustTorque / stage.thrust;
   
// how far away are the engines from the CoM, actually? // how far away are the engines from the CoM, actually?
double thrustDistance = (this.stageStartCom - this.totalStageThrustForce.GetAverageForceApplicationPoint()).magnitude; double thrustDistance = (this.stageStartCom - this.totalStageThrustForce.GetAverageForceApplicationPoint()).magnitude;
   
// the combination of the above two values gives an approximation of the offset angle. // the combination of the above two values gives an approximation of the offset angle.
double sinThrustOffsetAngle = 0; double sinThrustOffsetAngle = 0;
if (thrustDistance > 1e-7) { if (thrustDistance > 1e-7) {
sinThrustOffsetAngle = torqueLeverArmLength / thrustDistance; sinThrustOffsetAngle = torqueLeverArmLength / thrustDistance;
if (sinThrustOffsetAngle > 1) { if (sinThrustOffsetAngle > 1) {
sinThrustOffsetAngle = 1; sinThrustOffsetAngle = 1;
} }
} }
   
stage.thrustOffsetAngle = Math.Asin(sinThrustOffsetAngle) * 180 / Math.PI; stage.thrustOffsetAngle = Math.Asin(sinThrustOffsetAngle) * 180 / Math.PI;
   
// Calculate the cost and mass of this stage and add all engines and tanks that are decoupled // Calculate the cost and mass of this stage and add all engines and tanks that are decoupled
// in the next stage to the dontStageParts list // in the next stage to the dontStageParts list
foreach (PartSim partSim in this.allParts) foreach (PartSim partSim in this.allParts)
{ {
if (partSim.decoupledInStage == this.currentStage - 1) if (partSim.decoupledInStage == this.currentStage - 1)
{ {
stage.cost += partSim.cost; stage.cost += partSim.cost;
stage.mass += partSim.GetStartMass(); stage.mass += partSim.GetStartMass();
} }
} }
   
this.dontStageParts = dontStagePartsLists[this.currentStage]; this.dontStageParts = dontStagePartsLists[this.currentStage];
   
if (log != null) if (log != null)
{ {
log.buf.AppendLine("Stage setup took " + this._timer.ElapsedMilliseconds + "ms"); log.buf.AppendLine("Stage setup took " + this._timer.ElapsedMilliseconds + "ms");
   
if (this.dontStageParts.Count > 0) if (this.dontStageParts.Count > 0)
{ {
log.buf.AppendLine("Parts preventing staging:"); log.buf.AppendLine("Parts preventing staging:");
foreach (PartSim partSim in this.dontStageParts) foreach (PartSim partSim in this.dontStageParts)
{ {
partSim.DumpPartToBuffer(log.buf, ""); partSim.DumpPartToBuffer(log.buf, "");
} }
} }
else else
{ {
log.buf.AppendLine("No parts preventing staging"); log.buf.AppendLine("No parts preventing staging");
} }
   
log.Flush(); log.Flush();
} }
   
// Now we will loop until we are allowed to stage // Now we will loop until we are allowed to stage
int loopCounter = 0; int loopCounter = 0;
while (!this.AllowedToStage()) while (!this.AllowedToStage())
{ {
loopCounter++; loopCounter++;
//MonoBehaviour.print("loop = " + loopCounter); //MonoBehaviour.print("loop = " + loopCounter);
   
// Calculate how long each draining tank will take to drain and run for the minimum time // Calculate how long each draining tank will take to drain and run for the minimum time
double resourceDrainTime = double.MaxValue; double resourceDrainTime = double.MaxValue;
PartSim partMinDrain = null; PartSim partMinDrain = null;
foreach (PartSim partSim in this.drainingParts) foreach (PartSim partSim in this.drainingParts)
{ {
double time = partSim.TimeToDrainResource(); double time = partSim.TimeToDrainResource();
if (time < resourceDrainTime) if (time < resourceDrainTime)
{ {
resourceDrainTime = time; resourceDrainTime = time;
partMinDrain = partSim; partMinDrain = partSim;
} }
} }
   
if (log != null) if (log != null)
{ {
MonoBehaviour.print("Drain time = " + resourceDrainTime + " (" + partMinDrain.name + ":" + partMinDrain.partId + ")"); MonoBehaviour.print("Drain time = " + resourceDrainTime + " (" + partMinDrain.name + ":" + partMinDrain.partId + ")");
} }
   
foreach (PartSim partSim in this.drainingParts) foreach (PartSim partSim in this.drainingParts)
{ {
partSim.DrainResources(resourceDrainTime); partSim.DrainResources(resourceDrainTime);
} }
   
// Get the mass after draining // Get the mass after draining
this.stepEndMass = this.ShipMass; this.stepEndMass = this.ShipMass;
this.stageTime += resourceDrainTime; this.stageTime += resourceDrainTime;
   
double stepEndTWR = this.totalStageThrust / (this.stepEndMass * this.gravity); double stepEndTWR = this.totalStageThrust / (this.stepEndMass * this.gravity);
//MonoBehaviour.print("After drain mass = " + stepEndMass); //MonoBehaviour.print("After drain mass = " + stepEndMass);
//MonoBehaviour.print("currentThrust = " + totalStageThrust); //MonoBehaviour.print("currentThrust = " + totalStageThrust);
//MonoBehaviour.print("currentTWR = " + stepEndTWR); //MonoBehaviour.print("currentTWR = " + stepEndTWR);
if (stepEndTWR > stage.maxThrustToWeight) if (stepEndTWR > stage.maxThrustToWeight)
{ {
stage.maxThrustToWeight = stepEndTWR; stage.maxThrustToWeight = stepEndTWR;
} }
   
//MonoBehaviour.print("newMaxTWR = " + stage.maxThrustToWeight); //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 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) 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); 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 // Update the active engines and resource drains for the next step
this.UpdateResourceDrains(); this.UpdateResourceDrains();
   
// Recalculate the current thrust and isp for the next step // Recalculate the current thrust and isp for the next step
this.CalculateThrustAndISP(); this.CalculateThrustAndISP();
   
// Check if we actually changed anything // Check if we actually changed anything
if (this.stepStartMass == this.stepEndMass) if (this.stepStartMass == this.stepEndMass)
{ {
//MonoBehaviour.print("No change in mass"); //MonoBehaviour.print("No change in mass");
break; break;
} }
   
// Check to stop rampant looping // Check to stop rampant looping
if (loopCounter == 1000) if (loopCounter == 1000)
{ {
MonoBehaviour.print("exceeded loop count"); MonoBehaviour.print("exceeded loop count");
MonoBehaviour.print("stageStartMass = " + this.stageStartMass); MonoBehaviour.print("stageStartMass = " + this.stageStartMass);
MonoBehaviour.print("stepStartMass = " + this.stepStartMass); MonoBehaviour.print("stepStartMass = " + this.stepStartMass);
MonoBehaviour.print("StepEndMass = " + this.stepEndMass); MonoBehaviour.print("StepEndMass = " + this.stepEndMass);
Logger.Log("exceeded loop count"); Logger.Log("exceeded loop count");
Logger.Log("stageStartMass = " + this.stageStartMass); Logger.Log("stageStartMass = " + this.stageStartMass);
Logger.Log("stepStartMass = " + this.stepStartMass); Logger.Log("stepStartMass = " + this.stepStartMass);
Logger.Log("StepEndMass = " + this.stepEndMass); Logger.Log("StepEndMass = " + this.stepEndMass);
break; break;
} }
   
// The next step starts at the mass this one ended at // The next step starts at the mass this one ended at
this.stepStartMass = this.stepEndMass; this.stepStartMass = this.stepEndMass;
} }
   
// Store more values in the Stage object and stick it in the array // Store more values in the Stage object and stick it in the array
   
// Store the magnitude of the deltaV vector // Store the magnitude of the deltaV vector
stage.deltaV = this.vecStageDeltaV.magnitude; stage.deltaV = this.vecStageDeltaV.magnitude;
stage.resourceMass = this.stageStartMass - this.stepEndMass; stage.resourceMass = this.stageStartMass - this.stepEndMass;
   
// Recalculate effective stage isp from the stage deltaV (flip the standard deltaV calculation around) // 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 // Note: If the mass doesn't change then this is a divide by zero
if (this.stageStartMass != this.stepStartMass) if (this.stageStartMass != this.stepStartMass)
{ {
stage.isp = stage.deltaV / (STD_GRAVITY * Math.Log(this.stageStartMass / this.stepStartMass)); stage.isp = stage.deltaV / (STD_GRAVITY * Math.Log(this.stageStartMass / this.stepStartMass));
} }
else else
{ {
stage.isp = 0; stage.isp = 0;
} }
   
// Zero stage time if more than a day (this should be moved into the window code) // 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.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 stage.number = this.doingCurrent ? -1 : this.currentStage; // Set the stage number to -1 if doing current engines
stage.totalPartCount = this.allParts.Count; stage.totalPartCount = this.allParts.Count;
stages[this.currentStage] = stage; stages[this.currentStage] = stage;
   
// Now activate the next stage // Now activate the next stage
this.currentStage--; this.currentStage--;
this.doingCurrent = false; this.doingCurrent = false;
   
if (log != null) if (log != null)
{ {
// Log how long the stage took // Log how long the stage took
this._timer.Stop(); this._timer.Stop();
MonoBehaviour.print("Simulating stage took " + this._timer.ElapsedMilliseconds + "ms"); MonoBehaviour.print("Simulating stage took " + this._timer.ElapsedMilliseconds + "ms");
stage.Dump(); stage.Dump();
this._timer.Reset(); this._timer.Reset();
this._timer.Start(); this._timer.Start();
} }
   
// Activate the next stage // Activate the next stage
this.ActivateStage(); this.ActivateStage();
   
if (log != null) if (log != null)
{ {
// Log how long it took to activate // Log how long it took to activate
this._timer.Stop(); this._timer.Stop();
MonoBehaviour.print("ActivateStage took " + this._timer.ElapsedMilliseconds + "ms"); MonoBehaviour.print("ActivateStage took " + this._timer.ElapsedMilliseconds + "ms");
} }
} }
   
// Now we add up the various total fields in the stages // Now we add up the various total fields in the stages
for (int i = 0; i < stages.Length; i++) 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 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--) for (int j = i; j >= 0; j--)
{ {
stages[i].totalCost += stages[j].cost; stages[i].totalCost += stages[j].cost;
stages[i].totalMass += stages[j].mass; stages[i].totalMass += stages[j].mass;
stages[i].totalDeltaV += stages[j].deltaV; stages[i].totalDeltaV += stages[j].deltaV;
stages[i].totalTime += stages[j].time; stages[i].totalTime += stages[j].time;
stages[i].partCount = i > 0 ? stages[i].totalPartCount - stages[i - 1].totalPartCount : stages[i].totalPartCount; stages[i].partCount = i > 0 ? stages[i].totalPartCount - stages[i - 1].totalPartCount : stages[i].totalPartCount;
} }
// We also total up the deltaV for stage and all stages below // We also total up the deltaV for stage and all stages below
for (int j = i; j < stages.Length; j++) for (int j = i; j < stages.Length; j++)
{ {
stages[i].inverseTotalDeltaV += stages[j].deltaV; stages[i].inverseTotalDeltaV += stages[j].deltaV;
} }
   
// Zero the total time if the value will be huge (24 hours?) to avoid the display going weird // 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) // (this should be moved into the window code)
if (stages[i].totalTime > SECONDS_PER_DAY) if (stages[i].totalTime > SECONDS_PER_DAY)
{ {
stages[i].totalTime = 0d; stages[i].totalTime = 0d;
} }
} }
   
if (log != null) if (log != null)
{ {
this._timer.Stop(); this._timer.Stop();
MonoBehaviour.print("RunSimulation: " + this._timer.ElapsedMilliseconds + "ms"); MonoBehaviour.print("RunSimulation: " + this._timer.ElapsedMilliseconds + "ms");
} }
   
return stages; return stages;
} }
   
private List<List<PartSim>> BuildDontStageLists(LogMsg log) private List<List<PartSim>> BuildDontStageLists(LogMsg log)
{ {
if (log != null) if (log != null)
{ {
log.buf.AppendLine("Creating list with capacity of " + (this.currentStage + 1)); log.buf.AppendLine("Creating list with capacity of " + (this.currentStage + 1));
} }
List<List<PartSim>> lists = new List<List<PartSim>>(); List<List<PartSim>> lists = new List<List<PartSim>>();
for (int i = 0; i <= this.currentStage; i++) for (int i = 0; i <= this.currentStage; i++)
{ {
lists.Add(new List<PartSim>()); lists.Add(new List<PartSim>());
} }
   
foreach (PartSim partSim in this.allParts) foreach (PartSim partSim in this.allParts)
{ {
if (partSim.isEngine || !partSim.Resources.Empty) if (partSim.isEngine || !partSim.Resources.Empty)
{ {
if (log != null) if (log != null)
{ {
log.buf.AppendLine(partSim.name + ":" + partSim.partId + " is engine or tank, decoupled = " + partSim.decoupledInStage); log.buf.AppendLine(partSim.name + ":" + partSim.partId + " is engine or tank, decoupled = " + partSim.decoupledInStage);
} }
   
if (partSim.decoupledInStage < -1 || partSim.decoupledInStage > this.currentStage - 1) if (partSim.decoupledInStage < -1 || partSim.decoupledInStage > this.currentStage - 1)
{ {
if (log != null) if (log != null)
{ {
log.buf.AppendLine("decoupledInStage out of range"); log.buf.AppendLine("decoupledInStage out of range");
} }
} }
else else
{ {
lists[partSim.decoupledInStage + 1].Add(partSim); lists[partSim.decoupledInStage + 1].Add(partSim);
} }
} }
} }
   
for (int i = 1; i <= this.lastStage; i++) for (int i = 1; i <= this.lastStage; i++)
{ {
if (lists[i].Count == 0) if (lists[i].Count == 0)
{ {
lists[i] = lists[i - 1]; lists[i] = lists[i - 1];
} }
} }
   
return lists; return lists;
} }
   
// This function simply rebuilds the active engines by testing the isActive flag of all the engines // This function simply rebuilds the active engines by testing the isActive flag of all the engines
private void UpdateActiveEngines() private void UpdateActiveEngines()
{ {
this.activeEngines.Clear(); this.activeEngines.Clear();
foreach (EngineSim engine in this.allEngines) foreach (EngineSim engine in this.allEngines)
{ {
if (engine.isActive) if (engine.isActive)
{ {
this.activeEngines.Add(engine); this.activeEngines.Add(engine);
} }
} }
} }
   
private void CalculateThrustAndISP() private void CalculateThrustAndISP()
{ {
// Reset all the values // Reset all the values
this.vecThrust = Vector3.zero; this.vecThrust = Vector3.zero;
this.vecActualThrust = Vector3.zero; this.vecActualThrust = Vector3.zero;
this.simpleTotalThrust = 0d; this.simpleTotalThrust = 0d;
this.totalStageThrust = 0d; this.totalStageThrust = 0d;
this.totalStageActualThrust = 0d; this.totalStageActualThrust = 0d;
this.totalStageFlowRate = 0d; this.totalStageFlowRate = 0d;
this.totalStageIspFlowRate = 0d; this.totalStageIspFlowRate = 0d;
this.totalStageThrustForce = new ForceAccumulator(); this.totalStageThrustForce = new ForceAccumulator();
   
// Loop through all the active engines totalling the thrust, actual thrust and mass flow rates // Loop through all the active engines totalling the thrust, actual thrust and mass flow rates
// The thrust is totalled as vectors // The thrust is totalled as vectors
foreach (EngineSim engine in this.activeEngines) foreach (EngineSim engine in this.activeEngines)
{ {
this.simpleTotalThrust += engine.thrust; this.simpleTotalThrust += engine.thrust;
this.vecThrust += ((float)engine.thrust * engine.thrustVec); this.vecThrust += ((float)engine.thrust * engine.thrustVec);
this.vecActualThrust += ((float)engine.actualThrust * engine.thrustVec); this.vecActualThrust += ((float)engine.actualThrust * engine.thrustVec);
   
this.totalStageFlowRate += engine.ResourceConsumptions.Mass; this.totalStageFlowRate += engine.ResourceConsumptions.Mass;
this.totalStageIspFlowRate += engine.ResourceConsumptions.Mass * engine.isp; this.totalStageIspFlowRate += engine.ResourceConsumptions.Mass * engine.isp;
   
foreach (AppliedForce f in engine.appliedForces) { foreach (AppliedForce f in engine.appliedForces) {
this.totalStageThrustForce.AddForce(f); this.totalStageThrustForce.AddForce(f);
} }
} }
   
//MonoBehaviour.print("vecThrust = " + vecThrust.ToString() + " magnitude = " + vecThrust.magnitude); //MonoBehaviour.print("vecThrust = " + vecThrust.ToString() + " magnitude = " + vecThrust.magnitude);
   
this.totalStageThrust = this.vecThrust.magnitude; this.totalStageThrust = this.vecThrust.magnitude;
this.totalStageActualThrust = this.vecActualThrust.magnitude; this.totalStageActualThrust = this.vecActualThrust.magnitude;
   
// Calculate the effective isp at this point // Calculate the effective isp at this point
if (this.totalStageFlowRate > 0d && this.totalStageIspFlowRate > 0d) if (this.totalStageFlowRate > 0d && this.totalStageIspFlowRate > 0d)
{ {
this.currentisp = this.totalStageIspFlowRate / this.totalStageFlowRate; this.currentisp = this.totalStageIspFlowRate / this.totalStageFlowRate;
} }
else else
{ {
this.currentisp = 0; this.currentisp = 0;
} }
} }
   
// This function does all the hard work of working out which engines are burning, which tanks are being drained // This function does all the hard work of working out which engines are burning, which tanks are being drained
// and setting the drain rates // and setting the drain rates
private void UpdateResourceDrains() private void UpdateResourceDrains()
{ {
// Update the active engines // Update the active engines
this.UpdateActiveEngines(); this.UpdateActiveEngines();
   
// Empty the draining resources set // Empty the draining resources set
this.drainingResources.Clear(); this.drainingResources.Clear();
   
// Reset the resource drains of all draining parts // Reset the resource drains of all draining parts
foreach (PartSim partSim in this.drainingParts) foreach (PartSim partSim in this.drainingParts)
{ {
partSim.ResourceDrains.Reset(); partSim.ResourceDrains.Reset();
} }
   
// Empty the draining parts set // Empty the draining parts set
this.drainingParts.Clear(); this.drainingParts.Clear();
   
// Loop through all the active engine modules // Loop through all the active engine modules
foreach (EngineSim engine in this.activeEngines) foreach (EngineSim engine in this.activeEngines)
{ {
// Set the resource drains for this engine // Set the resource drains for this engine
if (engine.SetResourceDrains(this.allParts, this.allFuelLines, this.drainingParts)) if (engine.SetResourceDrains(this.allParts, this.allFuelLines, this.drainingParts))
{ {
// If it is active then add the consumed resource types to the set // If it is active then add the consumed resource types to the set
foreach (int type in engine.ResourceConsumptions.Types) foreach (int type in engine.ResourceConsumptions.Types)
{ {
this.drainingResources.Add(type); this.drainingResources.Add(type);
} }
} }
} }
   
// Update the active engines again to remove any engines that have no fuel supply // Update the active engines again to remove any engines that have no fuel supply
this.UpdateActiveEngines(); this.UpdateActiveEngines();
   
if (SimManager.logOutput) if (SimManager.logOutput)
{ {
StringBuilder buffer = new StringBuilder(1024); StringBuilder buffer = new StringBuilder(1024);
buffer.AppendFormat("Active engines = {0:d}\n", this.activeEngines.Count); buffer.AppendFormat("Active engines = {0:d}\n", this.activeEngines.Count);
int i = 0; int i = 0;
foreach (EngineSim engine in this.activeEngines) foreach (EngineSim engine in this.activeEngines)
{ {
engine.DumpEngineToBuffer(buffer, "Engine " + (i++) + ":"); engine.DumpEngineToBuffer(buffer, "Engine " + (i++) + ":");
} }
MonoBehaviour.print(buffer); MonoBehaviour.print(buffer);
} }
} }
   
// This function works out if it is time to stage // This function works out if it is time to stage
private bool AllowedToStage() private bool AllowedToStage()
{ {
StringBuilder buffer = null; StringBuilder buffer = null;
if (SimManager.logOutput) if (SimManager.logOutput)
{ {
buffer = new StringBuilder(1024); buffer = new StringBuilder(1024);
buffer.AppendLine("AllowedToStage"); buffer.AppendLine("AllowedToStage");
buffer.AppendFormat("currentStage = {0:d}\n", this.currentStage); buffer.AppendFormat("currentStage = {0:d}\n", this.currentStage);
} }
   
if (this.activeEngines.Count > 0) if (this.activeEngines.Count > 0)
{ {
foreach (PartSim partSim in this.dontStageParts) foreach (PartSim partSim in this.dontStageParts)
{ {
if (SimManager.logOutput) if (SimManager.logOutput)
{ {
partSim.DumpPartToBuffer(buffer, "Testing: "); partSim.DumpPartToBuffer(buffer, "Testing: ");
} }
//buffer.AppendFormat("isSepratron = {0}\n", partSim.isSepratron ? "true" : "false"); //buffer.AppendFormat("isSepratron = {0}\n", partSim.isSepratron ? "true" : "false");
   
if (!partSim.isSepratron && !partSim.EmptyOf(this.drainingResources)) if (!partSim.isSepratron && !partSim.EmptyOf(this.drainingResources))
{ {
if (SimManager.logOutput) if (SimManager.logOutput)
{ {
partSim.DumpPartToBuffer(buffer, "Decoupled part not empty => false: "); partSim.DumpPartToBuffer(buffer, "Decoupled part not empty => false: ");
MonoBehaviour.print(buffer); MonoBehaviour.print(buffer);
} }
return false; return false;
} }
   
if (partSim.isEngine) if (partSim.isEngine)
{ {
foreach (EngineSim engine in this.activeEngines) foreach (EngineSim engine in this.activeEngines)
{ {
if (engine.partSim == partSim) if (engine.partSim == partSim)
{ {
if (SimManager.logOutput) if (SimManager.logOutput)
{ {
partSim.DumpPartToBuffer(buffer, "Decoupled part is active engine => false: "); partSim.DumpPartToBuffer(buffer, "Decoupled part is active engine => false: ");
MonoBehaviour.print(buffer); MonoBehaviour.print(buffer);
} }
return false; return false;
} }
} }
} }
} }
} }
   
if (this.currentStage == 0 && this.doingCurrent) if (this.currentStage == 0 && this.doingCurrent)
{ {
if (SimManager.logOutput) if (SimManager.logOutput)
{ {
buffer.AppendLine("Current stage == 0 && doingCurrent => false"); buffer.AppendLine("Current stage == 0 && doingCurrent => false");
MonoBehaviour.print(buffer); MonoBehaviour.print(buffer);
} }
return false; return false;
} }
   
if (SimManager.logOutput) if (SimManager.logOutput)
{ {
buffer.AppendLine("Returning true"); buffer.AppendLine("Returning true");
MonoBehaviour.print(buffer); MonoBehaviour.print(buffer);
} }
return true; return true;
} }
   
// This function activates the next stage // This function activates the next stage
// currentStage must be updated before calling this function // currentStage must be updated before calling this function
private void ActivateStage() private void ActivateStage()
{ {
// Build a set of all the parts that will be decoupled // Build a set of all the parts that will be decoupled
HashSet<PartSim> decoupledParts = new HashSet<PartSim>(); HashSet<PartSim> decoupledParts = new HashSet<PartSim>();
foreach (PartSim partSim in this.allParts) foreach (PartSim partSim in this.allParts)
{ {
if (partSim.decoupledInStage >= this.currentStage) if (partSim.decoupledInStage >= this.currentStage)
{ {
decoupledParts.Add(partSim); decoupledParts.Add(partSim);
} }
} }
   
foreach (PartSim partSim in decoupledParts) foreach (PartSim partSim in decoupledParts)
{ {
// Remove it from the all parts list // Remove it from the all parts list
this.allParts.Remove(partSim); this.allParts.Remove(partSim);
if (partSim.isEngine) if (partSim.isEngine)
{ {
// If it is an engine then loop through all the engine modules and remove all the ones from this engine part // 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--) for (int i = this.allEngines.Count - 1; i >= 0; i--)
{ {
if (this.allEngines[i].partSim == partSim) if (this.allEngines[i].partSim == partSim)
{ {
this.allEngines.RemoveAt(i); this.allEngines.RemoveAt(i);
} }
} }
} }
// If it is a fuel line then remove it from the list of all fuel lines // If it is a fuel line then remove it from the list of all fuel lines
if (partSim.isFuelLine) if (partSim.isFuelLine)
{ {
this.allFuelLines.Remove(partSim); this.allFuelLines.Remove(partSim);
} }
} }
   
// Loop through all the (remaining) parts // Loop through all the (remaining) parts
foreach (PartSim partSim in this.allParts) foreach (PartSim partSim in this.allParts)
{ {
// Ask the part to remove all the parts that are decoupled // Ask the part to remove all the parts that are decoupled
partSim.RemoveAttachedParts(decoupledParts); partSim.RemoveAttachedParts(decoupledParts);
} }
   
// Now we loop through all the engines and activate those that are ignited in this stage // Now we loop through all the engines and activate those that are ignited in this stage
foreach (EngineSim engine in this.allEngines) foreach (EngineSim engine in this.allEngines)
{ {
if (engine.partSim.inverseStage == this.currentStage) if (engine.partSim.inverseStage == this.currentStage)
{ {
engine.isActive = true; engine.isActive = true;
} }
} }
} }
   
public void Dump() public void Dump()
{ {
StringBuilder buffer = new StringBuilder(1024); StringBuilder buffer = new StringBuilder(1024);
buffer.AppendFormat("Part count = {0:d}\n", this.allParts.Count); buffer.AppendFormat("Part count = {0:d}\n", this.allParts.Count);
   
// Output a nice tree view of the rocket // Output a nice tree view of the rocket
if (this.allParts.Count > 0) if (this.allParts.Count > 0)
{ {
PartSim root = this.allParts[0]; PartSim root = this.allParts[0];
while (root.parent != null) while (root.parent != null)
{ {
root = root.parent; root = root.parent;
} }
   
if (root.hasVessel) if (root.hasVessel)
{ {
buffer.AppendFormat("vesselName = '{0}' vesselType = {1}\n", this.vesselName, SimManager.GetVesselTypeString(this.vesselType)); buffer.AppendFormat("vesselName = '{0}' vesselType = {1}\n", this.vesselName, SimManager.GetVesselTypeString(this.vesselType));
} }
   
root.DumpPartToBuffer(buffer, "", this.allParts); root.DumpPartToBuffer(buffer, "", this.allParts);
} }
   
MonoBehaviour.print(buffer); MonoBehaviour.print(buffer);
} }
} }
} }