Moved ActiveVessel check from UpdateModules to FixedUpdate
Moved ActiveVessel check from UpdateModules to FixedUpdate

// //
// 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 namespace KerbalEngineer.Flight
{ {
#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 Extensions; using Extensions;
using Readouts; using Readouts;
using Sections; using Sections;
using Settings; using Settings;
using UnityEngine; using UnityEngine;
using VesselSimulator; using VesselSimulator;
   
#endregion #endregion
   
/// <summary> /// <summary>
/// Core management system for the Flight Engineer. /// Core management system for the Flight Engineer.
/// </summary> /// </summary>
[KSPAddon(KSPAddon.Startup.Flight, false)] [KSPAddon(KSPAddon.Startup.Flight, false)]
public sealed class FlightEngineerCore : MonoBehaviour public sealed class FlightEngineerCore : MonoBehaviour
{ {
#region Instance #region Instance
   
/// <summary> /// <summary>
/// Gets the current instance of FlightEngineerCore. /// Gets the current instance of FlightEngineerCore.
/// </summary> /// </summary>
public static FlightEngineerCore Instance { get; private set; } public static FlightEngineerCore Instance { get; private set; }
   
#endregion #endregion
   
#region Fields #region Fields
   
private static bool isCareerMode = true; private static bool isCareerMode = true;
private static bool isKerbalLimited = true; private static bool isKerbalLimited = true;
private static bool isTrackingStationLimited = true; private static bool isTrackingStationLimited = true;
   
#endregion #endregion
   
#region Constructors #region Constructors
   
static FlightEngineerCore() static FlightEngineerCore()
{ {
try try
{ {
var handler = SettingHandler.Load("FlightEngineerCore.xml"); var handler = SettingHandler.Load("FlightEngineerCore.xml");
handler.Get("isCareerMode", ref isCareerMode); handler.Get("isCareerMode", ref isCareerMode);
handler.Get("isKerbalLimited", ref isKerbalLimited); handler.Get("isKerbalLimited", ref isKerbalLimited);
handler.Get("isTrackingStationLimited", ref isTrackingStationLimited); handler.Get("isTrackingStationLimited", ref isTrackingStationLimited);
} }
catch (Exception ex) catch (Exception ex)
{ {
Logger.Exception(ex); Logger.Exception(ex);
} }
} }
   
#endregion #endregion
   
#region Properties #region Properties
   
/// <summary> /// <summary>
/// Gets and sets whether to the Flight Engineer should be run using career limitations. /// Gets and sets whether to the Flight Engineer should be run using career limitations.
/// </summary> /// </summary>
public static bool IsCareerMode public static bool IsCareerMode
{ {
get { return isCareerMode; } get { return isCareerMode; }
set set
{ {
try try
{ {
if (isCareerMode != value) if (isCareerMode != value)
{ {
var handler = SettingHandler.Load("FlightEngineerCore.xml"); var handler = SettingHandler.Load("FlightEngineerCore.xml");
handler.Set("isCareerMode", value); handler.Set("isCareerMode", value);
handler.Save("FlightEngineerCore.xml"); handler.Save("FlightEngineerCore.xml");
} }
isCareerMode = value; isCareerMode = value;
} }
catch (Exception ex) catch (Exception ex)
{ {
Logger.Exception(ex); Logger.Exception(ex);
} }
} }
} }
   
/// <summary> /// <summary>
/// Gets whether the FlightEngineer should be displayed. /// Gets whether the FlightEngineer should be displayed.
/// </summary> /// </summary>
public static bool IsDisplayable public static bool IsDisplayable
{ {
get get
{ {
if (MainCanvasUtil.MainCanvas.enabled == false) if (MainCanvasUtil.MainCanvas.enabled == false)
{ {
return false; return false;
} }
   
if (isCareerMode) if (isCareerMode)
{ {
if (isKerbalLimited && FlightGlobals.ActiveVessel.GetVesselCrew().Exists(c => c.experienceTrait.TypeName == "Engineer")) if (isKerbalLimited && FlightGlobals.ActiveVessel.GetVesselCrew().Exists(c => c.experienceTrait.TypeName == "Engineer"))
{ {
return true; return true;
} }
if (isTrackingStationLimited && ScenarioUpgradeableFacilities.GetFacilityLevel(SpaceCenterFacility.TrackingStation) == 1.0f) if (isTrackingStationLimited && ScenarioUpgradeableFacilities.GetFacilityLevel(SpaceCenterFacility.TrackingStation) == 1.0f)
{ {
return true; return true;
} }
return FlightGlobals.ActiveVessel.parts.Any(p => p.HasModule<FlightEngineerModule>()); return FlightGlobals.ActiveVessel.parts.Any(p => p.HasModule<FlightEngineerModule>());
} }
   
return true; return true;
} }
} }
   
/// <summary> /// <summary>
/// Gets and sets whether to the Flight Engineer should be kerbal limited. /// Gets and sets whether to the Flight Engineer should be kerbal limited.
/// </summary> /// </summary>
public static bool IsKerbalLimited public static bool IsKerbalLimited
{ {
get { return isKerbalLimited; } get { return isKerbalLimited; }
set set
{ {
try try
{ {
if (isKerbalLimited != value) if (isKerbalLimited != value)
{ {
var handler = SettingHandler.Load("FlightEngineerCore.xml"); var handler = SettingHandler.Load("FlightEngineerCore.xml");
handler.Set("isKerbalLimited", value); handler.Set("isKerbalLimited", value);
handler.Save("FlightEngineerCore.xml"); handler.Save("FlightEngineerCore.xml");
} }
isKerbalLimited = value; isKerbalLimited = value;
} }
catch (Exception ex) catch (Exception ex)
{ {
Logger.Exception(ex); Logger.Exception(ex);
} }
} }
} }
   
/// <summary> /// <summary>
/// Gets and sets whether to the Flight Engineer should be tracking station limited. /// Gets and sets whether to the Flight Engineer should be tracking station limited.
/// </summary> /// </summary>
public static bool IsTrackingStationLimited public static bool IsTrackingStationLimited
{ {
get { return isTrackingStationLimited; } get { return isTrackingStationLimited; }
set set
{ {
try try
{ {
if (isTrackingStationLimited != value) if (isTrackingStationLimited != value)
{ {
var handler = SettingHandler.Load("FlightEngineerCore.xml"); var handler = SettingHandler.Load("FlightEngineerCore.xml");
handler.Set("isTrackingStationLimited", value); handler.Set("isTrackingStationLimited", value);
handler.Save("FlightEngineerCore.xml"); handler.Save("FlightEngineerCore.xml");
} }
isTrackingStationLimited = value; isTrackingStationLimited = value;
} }
catch (Exception ex) catch (Exception ex)
{ {
Logger.Exception(ex); Logger.Exception(ex);
} }
} }
} }
   
/// <summary> /// <summary>
/// Gets the editor windows for sections with open editors. /// Gets the editor windows for sections with open editors.
/// </summary> /// </summary>
public List<SectionEditor> SectionEditors { get; private set; } public List<SectionEditor> SectionEditors { get; private set; }
   
/// <summary> /// <summary>
/// Gets the section windows for floating sections. /// Gets the section windows for floating sections.
/// </summary> /// </summary>
public List<SectionWindow> SectionWindows { get; private set; } public List<SectionWindow> SectionWindows { get; private set; }
   
/// <summary> /// <summary>
/// Gets the list of currently running updatable modules. /// Gets the list of currently running updatable modules.
/// </summary> /// </summary>
public List<IUpdatable> UpdatableModules { get; private set; } public List<IUpdatable> UpdatableModules { get; private set; }
   
#endregion #endregion
   
#region Methods #region Methods
   
/// <summary> /// <summary>
/// Creates a section editor, adds it to the FlightEngineerCore and returns a reference to it. /// Creates a section editor, adds it to the FlightEngineerCore and returns a reference to it.
/// </summary> /// </summary>
public SectionEditor AddSectionEditor(SectionModule section) public SectionEditor AddSectionEditor(SectionModule section)
{ {
try try
{ {
var editor = this.gameObject.AddComponent<SectionEditor>(); var editor = this.gameObject.AddComponent<SectionEditor>();
editor.ParentSection = section; editor.ParentSection = section;
editor.Position = new Rect(section.EditorPositionX, section.EditorPositionY, SectionEditor.Width, SectionEditor.Height); editor.Position = new Rect(section.EditorPositionX, section.EditorPositionY, SectionEditor.Width, SectionEditor.Height);
this.SectionEditors.Add(editor); this.SectionEditors.Add(editor);
return editor; return editor;
} }
catch (Exception ex) catch (Exception ex)
{ {
Logger.Exception(ex); Logger.Exception(ex);
return null; return null;
} }
} }
   
/// <summary> /// <summary>
/// Creates a section window, adds it to the FlightEngineerCore and returns a reference to it. /// Creates a section window, adds it to the FlightEngineerCore and returns a reference to it.
/// </summary> /// </summary>
public SectionWindow AddSectionWindow(SectionModule section) public SectionWindow AddSectionWindow(SectionModule section)
{ {
try try
{ {
var window = this.gameObject.AddComponent<SectionWindow>(); var window = this.gameObject.AddComponent<SectionWindow>();
window.ParentSection = section; window.ParentSection = section;
window.WindowPosition = new Rect(section.FloatingPositionX, section.FloatingPositionY, 0, 0); window.WindowPosition = new Rect(section.FloatingPositionX, section.FloatingPositionY, 0, 0);
this.SectionWindows.Add(window); this.SectionWindows.Add(window);
return window; return window;
} }
catch (Exception ex) catch (Exception ex)
{ {
Logger.Exception(ex); Logger.Exception(ex);
return null; return null;
} }
} }
   
/// <summary> /// <summary>
/// Adds an updatable object to be automatically updated every frame and will ignore duplicate objects. /// Adds an updatable object to be automatically updated every frame and will ignore duplicate objects.
/// </summary> /// </summary>
public void AddUpdatable(IUpdatable updatable) public void AddUpdatable(IUpdatable updatable)
{ {
try try
{ {
if (!this.UpdatableModules.Contains(updatable)) if (!this.UpdatableModules.Contains(updatable))
{ {
this.UpdatableModules.Add(updatable); this.UpdatableModules.Add(updatable);
} }
} }
catch (Exception ex) catch (Exception ex)
{ {
Logger.Exception(ex); Logger.Exception(ex);
} }
} }
   
/// <summary> /// <summary>
/// Create base Flight Engineer child objects. /// Create base Flight Engineer child objects.
/// </summary> /// </summary>
private void Awake() private void Awake()
{ {
try try
{ {
Instance = this; Instance = this;
   
this.SectionWindows = new List<SectionWindow>(); this.SectionWindows = new List<SectionWindow>();
this.SectionEditors = new List<SectionEditor>(); this.SectionEditors = new List<SectionEditor>();
this.UpdatableModules = new List<IUpdatable>(); this.UpdatableModules = new List<IUpdatable>();
   
SimManager.UpdateModSettings(); SimManager.UpdateModSettings();
   
Logger.Log("FlightEngineerCore->Awake"); Logger.Log("FlightEngineerCore->Awake");
} }
catch (Exception ex) catch (Exception ex)
{ {
Logger.Exception(ex); Logger.Exception(ex);
} }
} }
   
/// <summary> /// <summary>
/// Fixed update all required Flight Engineer objects. /// Fixed update all required Flight Engineer objects.
/// </summary> /// </summary>
private void FixedUpdate() private void FixedUpdate()
{ {
  if (FlightGlobals.ActiveVessel == null)
  return;
   
try try
{ {
SectionLibrary.FixedUpdate(); SectionLibrary.FixedUpdate();
} }
catch (Exception ex) catch (Exception ex)
{ {
Logger.Exception(ex); Logger.Exception(ex);
} }
} }
   
/// <summary> /// <summary>
/// Force the destruction of child objects on core destruction. /// Force the destruction of child objects on core destruction.
/// </summary> /// </summary>
private void OnDestroy() private void OnDestroy()
{ {
try try
{ {
SectionLibrary.Save(); SectionLibrary.Save();
   
foreach (var window in this.SectionWindows) foreach (var window in this.SectionWindows)
{ {
print("[FlightEngineer]: Destroying Floating Window for " + window.ParentSection.Name); print("[FlightEngineer]: Destroying Floating Window for " + window.ParentSection.Name);
Destroy(window); Destroy(window);
} }
   
foreach (var editor in this.SectionEditors) foreach (var editor in this.SectionEditors)
{ {
print("[FlightEngineer]: Destroying Editor Window for " + editor.ParentSection.Name); print("[FlightEngineer]: Destroying Editor Window for " + editor.ParentSection.Name);
Destroy(editor); Destroy(editor);
} }
   
Logger.Log("FlightEngineerCore->OnDestroy"); Logger.Log("FlightEngineerCore->OnDestroy");
} }
catch (Exception ex) catch (Exception ex)
{ {
Logger.Exception(ex); Logger.Exception(ex);
} }
} }
   
/// <summary> /// <summary>
/// Initialises the object's state on creation. /// Initialises the object's state on creation.
/// </summary> /// </summary>
private void Start() private void Start()
{ {
try try
{ {
SectionLibrary.Load(); SectionLibrary.Load();
ReadoutLibrary.Reset(); ReadoutLibrary.Reset();
Logger.Log("FlightEngineerCore->Start"); Logger.Log("FlightEngineerCore->Start");
} }
catch (Exception ex) catch (Exception ex)
{ {
Logger.Exception(ex); Logger.Exception(ex);
} }
} }
   
/// <summary> /// <summary>
/// Update all required Flight Engineer objects. /// Update all required Flight Engineer objects.
/// </summary> /// </summary>
private void Update() private void Update()
{ {
if (FlightGlobals.ActiveVessel == null) if (FlightGlobals.ActiveVessel == null)
return; return;
   
try try
{ {
SectionLibrary.Update(); SectionLibrary.Update();
this.UpdateModules(); this.UpdateModules();
} }
catch (Exception ex) catch (Exception ex)
{ {
Logger.Exception(ex); Logger.Exception(ex);
} }
} }
   
/// <summary> /// <summary>
/// Update all updatable modules. /// Update all updatable modules.
/// </summary> /// </summary>
private void UpdateModules() private void UpdateModules()
{ {
if (FlightGlobals.ActiveVessel == null)  
return;  
   
try try
{ {
foreach (var updatable in this.UpdatableModules) foreach (var updatable in this.UpdatableModules)
{ {
if (updatable is IUpdateRequest) if (updatable is IUpdateRequest)
{ {
var request = updatable as IUpdateRequest; var request = updatable as IUpdateRequest;
if (request.UpdateRequested) if (request.UpdateRequested)
{ {
updatable.Update(); updatable.Update();
request.UpdateRequested = false; request.UpdateRequested = false;
} }
} }
else else
{ {
updatable.Update(); updatable.Update();
} }
} }
} }
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
   
#endregion #endregion
   
namespace KerbalEngineer.VesselSimulator namespace KerbalEngineer.VesselSimulator
{ {
using System; using System;
using System.Text; using System.Text;
   
internal class AttachNodeSim internal class AttachNodeSim
{ {
   
private static readonly Pool<AttachNodeSim> pool = new Pool<AttachNodeSim>(Create, Reset); private static readonly Pool<AttachNodeSim> pool = new Pool<AttachNodeSim>(Create, Reset);
   
public PartSim attachedPartSim; public PartSim attachedPartSim;
public String id; public String id;
public AttachNode.NodeType nodeType; public AttachNode.NodeType nodeType;
   
private static AttachNodeSim Create() private static AttachNodeSim Create()
{ {
return new AttachNodeSim(); return new AttachNodeSim();
} }
   
public static AttachNodeSim New(PartSim partSim, String newId, AttachNode.NodeType newNodeType) public static AttachNodeSim New(PartSim partSim, String newId, AttachNode.NodeType newNodeType)
{ {
AttachNodeSim nodeSim = pool.Borrow(); AttachNodeSim nodeSim = pool.Borrow();
   
nodeSim.attachedPartSim = partSim; nodeSim.attachedPartSim = partSim;
nodeSim.nodeType = newNodeType; nodeSim.nodeType = newNodeType;
nodeSim.id = newId; nodeSim.id = newId;
   
return nodeSim; return nodeSim;
} }
   
static private void Reset(AttachNodeSim attachNodeSim) { } static private void Reset(AttachNodeSim attachNodeSim)
  {
  attachNodeSim.attachedPartSim = null;
  }
   
   
public void Release() public void Release()
{ {
pool.Release(this); pool.Release(this);
} }
   
public void DumpToBuffer(StringBuilder buffer) public void DumpToBuffer(StringBuilder buffer)
{ {
if (attachedPartSim == null) if (attachedPartSim == null)
{ {
buffer.Append("<staged>:<n>"); buffer.Append("<staged>:<n>");
} }
else else
{ {
buffer.Append(attachedPartSim.name); buffer.Append(attachedPartSim.name);
buffer.Append(":"); buffer.Append(":");
buffer.Append(attachedPartSim.partId); buffer.Append(attachedPartSim.partId);
} }
buffer.Append("#"); buffer.Append("#");
buffer.Append(nodeType); buffer.Append(nodeType);
buffer.Append(":"); buffer.Append(":");
buffer.Append(id); buffer.Append(id);
} }
} }
} }
// //
// 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
{ {
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Text; using System.Text;
using Editor; using Editor;
using Helpers; using Helpers;
using UnityEngine; using UnityEngine;
   
public class EngineSim public class EngineSim
{ {
private static readonly Pool<EngineSim> pool = new Pool<EngineSim>(Create, Reset); private static readonly Pool<EngineSim> pool = new Pool<EngineSim>(Create, Reset);
   
private readonly ResourceContainer resourceConsumptions = new ResourceContainer(); private readonly ResourceContainer resourceConsumptions = new ResourceContainer();
private readonly ResourceContainer resourceFlowModes = new ResourceContainer(); private readonly ResourceContainer resourceFlowModes = 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 = new List<AppliedForce>(); public List<AppliedForce> appliedForces = new List<AppliedForce>();
public float maxMach; public float maxMach;
   
public double thrust = 0; public double thrust = 0;
   
// Add thrust vector to account for directional losses // Add thrust vector to account for directional losses
public Vector3 thrustVec; public Vector3 thrustVec;
   
private static EngineSim Create() private static EngineSim Create()
{ {
return new EngineSim(); return new EngineSim();
} }
   
private static void Reset(EngineSim engineSim) private static void Reset(EngineSim engineSim)
{ {
engineSim.resourceConsumptions.Reset(); engineSim.resourceConsumptions.Reset();
engineSim.resourceFlowModes.Reset(); engineSim.resourceFlowModes.Reset();
  engineSim.partSim = null;
engineSim.actualThrust = 0; engineSim.actualThrust = 0;
engineSim.isActive = false; engineSim.isActive = false;
engineSim.isp = 0; engineSim.isp = 0;
for (int i = 0; i < engineSim.appliedForces.Count; i++) for (int i = 0; i < engineSim.appliedForces.Count; i++)
{ {
engineSim.appliedForces[i].Release(); engineSim.appliedForces[i].Release();
} }
engineSim.appliedForces.Clear(); engineSim.appliedForces.Clear();
engineSim.thrust = 0; engineSim.thrust = 0;
engineSim.maxMach = 0f; engineSim.maxMach = 0f;
} }
   
public void Release() public void Release()
{ {
pool.Release(this); pool.Release(this);
} }
   
public static EngineSim New(PartSim theEngine, public static EngineSim New(PartSim theEngine,
ModuleEngines engineMod, ModuleEngines engineMod,
double atmosphere, double atmosphere,
float machNumber, float machNumber,
bool vectoredThrust, bool vectoredThrust,
bool fullThrust, bool fullThrust,
LogMsg log) LogMsg log)
{ {
float maxFuelFlow = engineMod.maxFuelFlow; float maxFuelFlow = engineMod.maxFuelFlow;
float minFuelFlow = engineMod.minFuelFlow; float minFuelFlow = engineMod.minFuelFlow;
float thrustPercentage = engineMod.thrustPercentage; float thrustPercentage = engineMod.thrustPercentage;
List<Transform> thrustTransforms = engineMod.thrustTransforms; List<Transform> thrustTransforms = engineMod.thrustTransforms;
List<float> thrustTransformMultipliers = engineMod.thrustTransformMultipliers; List<float> thrustTransformMultipliers = engineMod.thrustTransformMultipliers;
Vector3 vecThrust = CalculateThrustVector(vectoredThrust ? thrustTransforms : null, Vector3 vecThrust = CalculateThrustVector(vectoredThrust ? thrustTransforms : null,
vectoredThrust ? thrustTransformMultipliers : null, vectoredThrust ? thrustTransformMultipliers : null,
log); log);
FloatCurve atmosphereCurve = engineMod.atmosphereCurve; FloatCurve atmosphereCurve = engineMod.atmosphereCurve;
bool atmChangeFlow = engineMod.atmChangeFlow; bool atmChangeFlow = engineMod.atmChangeFlow;
FloatCurve atmCurve = engineMod.useAtmCurve ? engineMod.atmCurve : null; FloatCurve atmCurve = engineMod.useAtmCurve ? engineMod.atmCurve : null;
FloatCurve velCurve = engineMod.useVelCurve ? engineMod.velCurve : null; FloatCurve velCurve = engineMod.useVelCurve ? engineMod.velCurve : null;
float currentThrottle = engineMod.currentThrottle; float currentThrottle = engineMod.currentThrottle;
float IspG = engineMod.g; float IspG = engineMod.g;
bool throttleLocked = engineMod.throttleLocked || fullThrust; bool throttleLocked = engineMod.throttleLocked || fullThrust;
List<Propellant> propellants = engineMod.propellants; List<Propellant> propellants = engineMod.propellants;
bool active = engineMod.isOperational; bool active = engineMod.isOperational;
float resultingThrust = engineMod.resultingThrust; float resultingThrust = engineMod.resultingThrust;
EngineSim engineSim = pool.Borrow(); EngineSim engineSim = pool.Borrow();
   
engineSim.isp = 0.0; engineSim.isp = 0.0;
engineSim.maxMach = 0.0f; engineSim.maxMach = 0.0f;
engineSim.actualThrust = 0.0; engineSim.actualThrust = 0.0;
engineSim.partSim = theEngine; engineSim.partSim = theEngine;
engineSim.isActive = active; engineSim.isActive = active;
engineSim.thrustVec = vecThrust; engineSim.thrustVec = vecThrust;
engineSim.resourceConsumptions.Reset(); engineSim.resourceConsumptions.Reset();
engineSim.resourceFlowModes.Reset(); engineSim.resourceFlowModes.Reset();
engineSim.appliedForces.Clear(); engineSim.appliedForces.Clear();
   
double flowRate = 0.0; double flowRate = 0.0;
if (engineSim.partSim.hasVessel) if (engineSim.partSim.hasVessel)
{ {
if (log != null) log.buf.AppendLine("hasVessel is true"); if (log != null) log.buf.AppendLine("hasVessel is true");
   
float flowModifier = GetFlowModifier(atmChangeFlow, atmCurve, engineSim.partSim.part.atmDensity, velCurve, machNumber, ref engineSim.maxMach); float flowModifier = GetFlowModifier(atmChangeFlow, atmCurve, engineSim.partSim.part.atmDensity, velCurve, machNumber, ref engineSim.maxMach);
engineSim.isp = atmosphereCurve.Evaluate((float)atmosphere); engineSim.isp = atmosphereCurve.Evaluate((float)atmosphere);
engineSim.thrust = GetThrust(Mathf.Lerp(minFuelFlow, maxFuelFlow, GetThrustPercent(thrustPercentage)) * flowModifier, engineSim.isp); engineSim.thrust = GetThrust(Mathf.Lerp(minFuelFlow, maxFuelFlow, GetThrustPercent(thrustPercentage)) * flowModifier, engineSim.isp);
engineSim.actualThrust = engineSim.isActive ? resultingThrust : 0.0; engineSim.actualThrust = engineSim.isActive ? resultingThrust : 0.0;
if (log != null) if (log != null)
{ {
log.buf.AppendFormat("flowMod = {0:g6}\n", flowModifier); log.buf.AppendFormat("flowMod = {0:g6}\n", flowModifier);
log.buf.AppendFormat("isp = {0:g6}\n", engineSim.isp); log.buf.AppendFormat("isp = {0:g6}\n", engineSim.isp);
log.buf.AppendFormat("thrust = {0:g6}\n", engineSim.thrust); log.buf.AppendFormat("thrust = {0:g6}\n", engineSim.thrust);
log.buf.AppendFormat("actual = {0:g6}\n", engineSim.actualThrust); log.buf.AppendFormat("actual = {0:g6}\n", engineSim.actualThrust);
} }
   
if (throttleLocked) if (throttleLocked)
{ {
if (log != null) log.buf.AppendLine("throttleLocked is true, using thrust for flowRate"); if (log != null) log.buf.AppendLine("throttleLocked is true, using thrust for flowRate");
flowRate = GetFlowRate(engineSim.thrust, engineSim.isp); flowRate = GetFlowRate(engineSim.thrust, engineSim.isp);
} }
else else
{ {
if (currentThrottle > 0.0f && engineSim.partSim.isLanded == false) if (currentThrottle > 0.0f && engineSim.partSim.isLanded == false)
{ {
// TODO: This bit doesn't work for RF engines // TODO: This bit doesn't work for RF engines
if (log != null) log.buf.AppendLine("throttled up and not landed, using actualThrust for flowRate"); if (log != null) log.buf.AppendLine("throttled up and not landed, using actualThrust for flowRate");
flowRate = GetFlowRate(engineSim.actualThrust, engineSim.isp); flowRate = GetFlowRate(engineSim.actualThrust, engineSim.isp);
} }
else else
{ {
if (log != null) log.buf.AppendLine("throttled down or landed, using thrust for flowRate"); if (log != null) log.buf.AppendLine("throttled down or landed, using thrust for flowRate");
flowRate = GetFlowRate(engineSim.thrust, engineSim.isp); flowRate = GetFlowRate(engineSim.thrust, engineSim.isp);
} }
} }
} }
else else
{ {
if (log != null) log.buf.AppendLine("hasVessel is false"); if (log != null) log.buf.AppendLine("hasVessel is false");
float flowModifier = GetFlowModifier(atmChangeFlow, atmCurve, CelestialBodies.SelectedBody.GetDensity(BuildAdvanced.Altitude), velCurve, machNumber, ref engineSim.maxMach); float flowModifier = GetFlowModifier(atmChangeFlow, atmCurve, CelestialBodies.SelectedBody.GetDensity(BuildAdvanced.Altitude), velCurve, machNumber, ref engineSim.maxMach);
engineSim.isp = atmosphereCurve.Evaluate((float)atmosphere); engineSim.isp = atmosphereCurve.Evaluate((float)atmosphere);
engineSim.thrust = GetThrust(Mathf.Lerp(minFuelFlow, maxFuelFlow, GetThrustPercent(thrustPercentage)) * flowModifier, engineSim.isp); engineSim.thrust = GetThrust(Mathf.Lerp(minFuelFlow, maxFuelFlow, GetThrustPercent(thrustPercentage)) * flowModifier, engineSim.isp);
engineSim.actualThrust = 0d; engineSim.actualThrust = 0d;
if (log != null) if (log != null)
{ {
log.buf.AppendFormat("flowMod = {0:g6}\n", flowModifier); log.buf.AppendFormat("flowMod = {0:g6}\n", flowModifier);
log.buf.AppendFormat("isp = {0:g6}\n", engineSim.isp); log.buf.AppendFormat("isp = {0:g6}\n", engineSim.isp);
log.buf.AppendFormat("thrust = {0:g6}\n", engineSim.thrust); log.buf.AppendFormat("thrust = {0:g6}\n", engineSim.thrust);
log.buf.AppendFormat("actual = {0:g6}\n", engineSim.actualThrust); log.buf.AppendFormat("actual = {0:g6}\n", engineSim.actualThrust);
} }
   
if (log != null) log.buf.AppendLine("no vessel, using thrust for flowRate"); if (log != null) log.buf.AppendLine("no vessel, using thrust for flowRate");
flowRate = GetFlowRate(engineSim.thrust, engineSim.isp); flowRate = GetFlowRate(engineSim.thrust, engineSim.isp);
} }
   
if (log != null) log.buf.AppendFormat("flowRate = {0:g6}\n", flowRate); if (log != null) log.buf.AppendFormat("flowRate = {0:g6}\n", flowRate);
   
float flowMass = 0f; float flowMass = 0f;
for (int i = 0; i < propellants.Count; ++i) for (int i = 0; i < propellants.Count; ++i)
{ {
Propellant propellant = propellants[i]; Propellant propellant = propellants[i];
if (!propellant.ignoreForIsp) if (!propellant.ignoreForIsp)
flowMass += propellant.ratio * ResourceContainer.GetResourceDensity(propellant.id); flowMass += propellant.ratio * ResourceContainer.GetResourceDensity(propellant.id);
} }
   
if (log != null) log.buf.AppendFormat("flowMass = {0:g6}\n", flowMass); if (log != null) log.buf.AppendFormat("flowMass = {0:g6}\n", flowMass);
   
for (int i = 0; i < propellants.Count; ++i) for (int i = 0; i < propellants.Count; ++i)
{ {
Propellant propellant = propellants[i]; Propellant propellant = propellants[i];
   
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 (log != null) log.buf.AppendFormat( if (log != null) log.buf.AppendFormat(
"Add consumption({0}, {1}:{2:d}) = {3:g6}\n", "Add consumption({0}, {1}:{2:d}) = {3:g6}\n",
ResourceContainer.GetResourceName(propellant.id), ResourceContainer.GetResourceName(propellant.id),
theEngine.name, theEngine.name,
theEngine.partId, theEngine.partId,
consumptionRate); consumptionRate);
engineSim.resourceConsumptions.Add(propellant.id, consumptionRate); engineSim.resourceConsumptions.Add(propellant.id, consumptionRate);
engineSim.resourceFlowModes.Add(propellant.id, (double)propellant.GetFlowMode()); engineSim.resourceFlowModes.Add(propellant.id, (double)propellant.GetFlowMode());
} }
   
for (int i = 0; i < thrustTransforms.Count; i++) for (int i = 0; i < thrustTransforms.Count; i++)
{ {
Transform thrustTransform = thrustTransforms[i]; Transform thrustTransform = thrustTransforms[i];
Vector3d direction = thrustTransform.forward.normalized; Vector3d direction = thrustTransform.forward.normalized;
Vector3d position = thrustTransform.position; Vector3d position = thrustTransform.position;
   
AppliedForce appliedForce = AppliedForce.New(direction * engineSim.thrust * thrustTransformMultipliers[i], position); AppliedForce appliedForce = AppliedForce.New(direction * engineSim.thrust * thrustTransformMultipliers[i], position);
engineSim.appliedForces.Add(appliedForce); engineSim.appliedForces.Add(appliedForce);
} }
   
return engineSim; return engineSim;
} }
   
private static Vector3 CalculateThrustVector(List<Transform> thrustTransforms, List<float> thrustTransformMultipliers, LogMsg log) private static Vector3 CalculateThrustVector(List<Transform> thrustTransforms, List<float> thrustTransformMultipliers, LogMsg log)
{ {
if (thrustTransforms == null) if (thrustTransforms == null)
{ {
return Vector3.forward; return Vector3.forward;
} }
   
Vector3 thrustvec = Vector3.zero; Vector3 thrustvec = Vector3.zero;
for (int i = 0; i < thrustTransforms.Count; ++i) for (int i = 0; i < thrustTransforms.Count; ++i)
{ {
Transform trans = thrustTransforms[i]; Transform trans = thrustTransforms[i];
   
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); if (log != null) log.buf.AppendFormat("Transform = ({0:g6}, {1:g6}, {2:g6}) length = {3:g6}\n", trans.forward.x, trans.forward.y, trans.forward.z, trans.forward.magnitude);
   
thrustvec -= (trans.forward * thrustTransformMultipliers[i]); thrustvec -= (trans.forward * thrustTransformMultipliers[i]);
} }
   
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); if (log != null) log.buf.AppendFormat("ThrustVec = ({0:g6}, {1:g6}, {2:g6}) length = {3:g6}\n", thrustvec.x, thrustvec.y, thrustvec.z, thrustvec.magnitude);
   
thrustvec.Normalize(); thrustvec.Normalize();
   
if (log != null) log.buf.AppendFormat("ThrustVecN = ({0:g6}, {1:g6}, {2:g6}) length = {3:g6}\n", thrustvec.x, thrustvec.y, thrustvec.z, thrustvec.magnitude); if (log != null) log.buf.AppendFormat("ThrustVecN = ({0:g6}, {1:g6}, {2:g6}) length = {3:g6}\n", thrustvec.x, thrustvec.y, thrustvec.z, thrustvec.magnitude);
   
return thrustvec; return thrustvec;
} }
   
public ResourceContainer ResourceConsumptions public ResourceContainer ResourceConsumptions
{ {
get get
{ {
return resourceConsumptions; return resourceConsumptions;
} }
} }
   
public static double GetExhaustVelocity(double isp) public static double GetExhaustVelocity(double isp)
{ {
return isp * Units.GRAVITY; return isp * Units.GRAVITY;
} }
   
public static float GetFlowModifier(bool atmChangeFlow, FloatCurve atmCurve, double atmDensity, FloatCurve velCurve, float machNumber, ref float maxMach) public static float GetFlowModifier(bool atmChangeFlow, FloatCurve atmCurve, double atmDensity, FloatCurve velCurve, float machNumber, ref float maxMach)
{ {
float flowModifier = 1.0f; float flowModifier = 1.0f;
if (atmChangeFlow) if (atmChangeFlow)
{ {
flowModifier = (float)(atmDensity / 1.225); flowModifier = (float)(atmDensity / 1.225);
if (atmCurve != null) if (atmCurve != null)
{ {
flowModifier = atmCurve.Evaluate(flowModifier); flowModifier = atmCurve.Evaluate(flowModifier);
} }
} }
if (velCurve != null) if (velCurve != null)
{ {
flowModifier = flowModifier * velCurve.Evaluate(machNumber); flowModifier = flowModifier * velCurve.Evaluate(machNumber);
maxMach = velCurve.maxTime; maxMach = velCurve.maxTime;
} }
if (flowModifier < float.Epsilon) if (flowModifier < float.Epsilon)
{ {
flowModifier = float.Epsilon; flowModifier = float.Epsilon;
} }
return flowModifier; return flowModifier;
} }
   
public static double GetFlowRate(double thrust, double isp) public static double GetFlowRate(double thrust, double isp)
{ {
return thrust / GetExhaustVelocity(isp); return thrust / GetExhaustVelocity(isp);
} }
   
public static float GetThrottlePercent(float currentThrottle, float thrustPercentage) public static float GetThrottlePercent(float currentThrottle, float thrustPercentage)
{ {
return currentThrottle * GetThrustPercent(thrustPercentage); return currentThrottle * GetThrustPercent(thrustPercentage);
} }
   
public static double GetThrust(double flowRate, double isp) public static double GetThrust(double flowRate, double isp)
{ {
return flowRate * GetExhaustVelocity(isp); return flowRate * GetExhaustVelocity(isp);
} }
   
public static float GetThrustPercent(float thrustPercentage) public static float GetThrustPercent(float thrustPercentage)
{ {
return thrustPercentage * 0.01f; return thrustPercentage * 0.01f;
} }
   
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", thrust, actualThrust, isp); buffer.AppendFormat("[thrust = {0:g6}, actual = {1:g6}, isp = {2:g6}\n", thrust, actualThrust, isp);
} }
   
// 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>>();
   
Dictionary<int, HashSet<PartSim>> stagePartSets = new Dictionary<int, HashSet<PartSim>>(); Dictionary<int, HashSet<PartSim>> stagePartSets = new Dictionary<int, HashSet<PartSim>>();
   
HashSet<PartSim> visited = new HashSet<PartSim>(); HashSet<PartSim> visited = new HashSet<PartSim>();
   
public void DumpSourcePartSets(String msg) public void DumpSourcePartSets(String msg)
{ {
MonoBehaviour.print("DumpSourcePartSets " + msg); MonoBehaviour.print("DumpSourcePartSets " + msg);
foreach (int type in sourcePartSets.Keys) foreach (int type in sourcePartSets.Keys)
{ {
MonoBehaviour.print("SourcePartSet for " + ResourceContainer.GetResourceName(type)); MonoBehaviour.print("SourcePartSet for " + ResourceContainer.GetResourceName(type));
HashSet<PartSim> sourcePartSet = sourcePartSets[type]; HashSet<PartSim> sourcePartSet = sourcePartSets[type];
if (sourcePartSet.Count > 0) if (sourcePartSet.Count > 0)
{ {
foreach (PartSim partSim in sourcePartSet) foreach (PartSim partSim in sourcePartSet)
{ {
MonoBehaviour.print("Part " + partSim.name + ":" + partSim.partId); MonoBehaviour.print("Part " + partSim.name + ":" + partSim.partId);
} }
} }
else else
{ {
MonoBehaviour.print("No parts"); MonoBehaviour.print("No parts");
} }
} }
} }
   
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;
//DumpSourcePartSets("before clear"); //DumpSourcePartSets("before clear");
foreach (HashSet<PartSim> sourcePartSet in sourcePartSets.Values) foreach (HashSet<PartSim> sourcePartSet in sourcePartSets.Values)
{ {
sourcePartSet.Clear(); sourcePartSet.Clear();
} }
//DumpSourcePartSets("after clear"); //DumpSourcePartSets("after clear");
   
for (int index = 0; index < this.resourceConsumptions.Types.Count; index++) for (int index = 0; index < this.resourceConsumptions.Types.Count; index++)
{ {
int type = this.resourceConsumptions.Types[index]; int type = this.resourceConsumptions.Types[index];
   
HashSet<PartSim> sourcePartSet; HashSet<PartSim> sourcePartSet;
if (!sourcePartSets.TryGetValue(type, out sourcePartSet)) if (!sourcePartSets.TryGetValue(type, out sourcePartSet))
{ {
sourcePartSet = new HashSet<PartSim>(); sourcePartSet = new HashSet<PartSim>();
sourcePartSets.Add(type, sourcePartSet); sourcePartSets.Add(type, sourcePartSet);
} }
   
switch ((ResourceFlowMode)this.resourceFlowModes[type]) switch ((ResourceFlowMode)this.resourceFlowModes[type])
{ {
case ResourceFlowMode.NO_FLOW: case ResourceFlowMode.NO_FLOW:
if (partSim.resources[type] > SimManager.RESOURCE_MIN && partSim.resourceFlowStates[type] != 0) if (partSim.resources[type] > SimManager.RESOURCE_MIN && 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(partSim); sourcePartSet.Add(partSim);
} }
break; break;
   
case ResourceFlowMode.ALL_VESSEL: case ResourceFlowMode.ALL_VESSEL:
case ResourceFlowMode.ALL_VESSEL_BALANCE: case ResourceFlowMode.ALL_VESSEL_BALANCE:
for (int i = 0; i < allParts.Count; i++) for (int i = 0; i < allParts.Count; i++)
{ {
PartSim aPartSim = allParts[i]; PartSim aPartSim = allParts[i];
if (aPartSim.resources[type] > SimManager.RESOURCE_MIN && aPartSim.resourceFlowStates[type] != 0) if (aPartSim.resources[type] > SimManager.RESOURCE_MIN && aPartSim.resourceFlowStates[type] != 0)
{ {
sourcePartSet.Add(aPartSim); sourcePartSet.Add(aPartSim);
} }
} }
break; break;
   
case ResourceFlowMode.STAGE_PRIORITY_FLOW: case ResourceFlowMode.STAGE_PRIORITY_FLOW:
case ResourceFlowMode.STAGE_PRIORITY_FLOW_BALANCE: case ResourceFlowMode.STAGE_PRIORITY_FLOW_BALANCE:
   
foreach (HashSet<PartSim> stagePartSet in stagePartSets.Values) foreach (HashSet<PartSim> stagePartSet in stagePartSets.Values)
{ {
stagePartSet.Clear(); stagePartSet.Clear();
} }
var maxStage = -1; var maxStage = -1;
   
//Logger.Log(type); //Logger.Log(type);
for (int i = 0; i < allParts.Count; i++) for (int i = 0; i < allParts.Count; i++)
{ {
var aPartSim = allParts[i]; var aPartSim = allParts[i];
if (aPartSim.resources[type] <= SimManager.RESOURCE_MIN || aPartSim.resourceFlowStates[type] == 0) if (aPartSim.resources[type] <= SimManager.RESOURCE_MIN || aPartSim.resourceFlowStates[type] == 0)
{ {
continue; continue;
} }
   
int stage = aPartSim.DecouplerCount(); int stage = aPartSim.DecouplerCount();
if (stage > maxStage) if (stage > maxStage)
{ {
maxStage = stage; maxStage = stage;
} }
   
HashSet<PartSim> tempPartSet; HashSet<PartSim> tempPartSet;
if (!stagePartSets.TryGetValue(stage, out tempPartSet)) if (!stagePartSets.TryGetValue(stage, out tempPartSet))
{ {
tempPartSet = new HashSet<PartSim>(); tempPartSet = new HashSet<PartSim>();
stagePartSets.Add(stage, tempPartSet); stagePartSets.Add(stage, tempPartSet);
} }
tempPartSet.Add(aPartSim); tempPartSet.Add(aPartSim);
} }
   
for (int j = maxStage; j >= 0; j--) for (int j = maxStage; j >= 0; j--)
{ {
HashSet<PartSim> stagePartSet; HashSet<PartSim> stagePartSet;
if (stagePartSets.TryGetValue(j, out stagePartSet) && stagePartSet.Count > 0) if (stagePartSets.TryGetValue(j, out stagePartSet) && stagePartSet.Count > 0)
{ {
// We have to copy the contents of the set here rather than copying the set reference or // We have to copy the contents of the set here rather than copying the set reference or
// bad things (tm) happen // bad things (tm) happen
foreach (PartSim aPartSim in stagePartSet) foreach (PartSim aPartSim in stagePartSet)
{ {
sourcePartSet.Add(aPartSim); sourcePartSet.Add(aPartSim);
} }
break; break;
} }
} }
break; break;
   
case ResourceFlowMode.STACK_PRIORITY_SEARCH: case ResourceFlowMode.STACK_PRIORITY_SEARCH:
visited.Clear(); visited.Clear();
   
if (SimManager.logOutput) if (SimManager.logOutput)
{ {
log = new LogMsg(); log = new LogMsg();
log.buf.AppendLine("Find " + ResourceContainer.GetResourceName(type) + " sources for " + partSim.name + ":" + partSim.partId); log.buf.AppendLine("Find " + ResourceContainer.GetResourceName(type) + " sources for " + partSim.name + ":" + partSim.partId);
} }
partSim.GetSourceSet(type, PhysicsGlobals.Stack_PriUsesSurf, allParts, visited, sourcePartSet, log, ""); partSim.GetSourceSet(type, PhysicsGlobals.Stack_PriUsesSurf, allParts, visited, sourcePartSet, log, "");
if (SimManager.logOutput && log != null) if (SimManager.logOutput && log != null)
{ {
MonoBehaviour.print(log.buf); MonoBehaviour.print(log.buf);
} }
break; break;
   
case ResourceFlowMode.STAGE_STACK_FLOW: case ResourceFlowMode.STAGE_STACK_FLOW:
case ResourceFlowMode.STAGE_STACK_FLOW_BALANCE: case ResourceFlowMode.STAGE_STACK_FLOW_BALANCE:
visited.Clear(); visited.Clear();
   
if (SimManager.logOutput) if (SimManager.logOutput)
{ {
log = new LogMsg(); log = new LogMsg();
log.buf.AppendLine("Find " + ResourceContainer.GetResourceName(type) + " sources for " + partSim.name + ":" + partSim.partId); log.buf.AppendLine("Find " + ResourceContainer.GetResourceName(type) + " sources for " + partSim.name + ":" + partSim.partId);
} }
partSim.GetSourceSet(type, true, allParts, visited, sourcePartSet, log, ""); partSim.GetSourceSet(type, true, allParts, visited, sourcePartSet, log, "");
if (SimManager.logOutput && log != null) if (SimManager.logOutput && log != null)
{ {
MonoBehaviour.print(log.buf); MonoBehaviour.print(log.buf);
} }
break; break;
   
default: default:
MonoBehaviour.print("SetResourceDrains(" + partSim.name + ":" + partSim.partId + ") Unexpected flow type for " + ResourceContainer.GetResourceName(type) + ")"); MonoBehaviour.print("SetResourceDrains(" + partSim.name + ":" + partSim.partId + ") Unexpected flow type for " + ResourceContainer.GetResourceName(type) + ")");
break; break;
} }
   
if (SimManager.logOutput) if (SimManager.logOutput)
{ {
if (sourcePartSet.Count > 0) if (sourcePartSet.Count > 0)
{ {
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);
} }
} }
   
//DumpSourcePartSets("after " + ResourceContainer.GetResourceName(type)); //DumpSourcePartSets("after " + ResourceContainer.GetResourceName(type));
} }
// 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
for (int i = 0; i < this.resourceConsumptions.Types.Count; i++) for (int i = 0; i < this.resourceConsumptions.Types.Count; i++)
{ {
int type = this.resourceConsumptions.Types[i]; int type = this.resourceConsumptions.Types[i];
HashSet<PartSim> sourcePartSet; HashSet<PartSim> sourcePartSet;
if (!sourcePartSets.TryGetValue(type, out sourcePartSet) || sourcePartSet.Count == 0) if (!sourcePartSets.TryGetValue(type, out sourcePartSet) || sourcePartSet.Count == 0)
{ {
if (SimManager.logOutput) if (SimManager.logOutput)
{ {
MonoBehaviour.print("No source of " + ResourceContainer.GetResourceName(type)); MonoBehaviour.print("No source of " + ResourceContainer.GetResourceName(type));
} }
   
isActive = false; 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
for (int i = 0; i < this.resourceConsumptions.Types.Count; i++) for (int i = 0; i < this.resourceConsumptions.Types.Count; i++)
{ {
int type = this.resourceConsumptions.Types[i]; int type = this.resourceConsumptions.Types[i];
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 = resourceConsumptions[type] / sourcePartSet.Count; double amount = resourceConsumptions[type] / sourcePartSet.Count;
foreach (PartSim partSim in sourcePartSet) foreach (PartSim partSim in sourcePartSet)
{ {
if (SimManager.logOutput) if (SimManager.logOutput)
{ {
MonoBehaviour.print( MonoBehaviour.print(
"Adding drain of " + amount + " " + ResourceContainer.GetResourceName(type) + " to " + partSim.name + ":" + "Adding drain of " + amount + " " + ResourceContainer.GetResourceName(type) + " to " + partSim.name + ":" +
partSim.partId); partSim.partId);
} }
   
partSim.resourceDrains.Add(type, amount); partSim.resourceDrains.Add(type, amount);
drainingParts.Add(partSim); drainingParts.Add(partSim);
} }
} }
return true; return true;
} }
} }
} }
// //
// 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
{ {
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 CompoundParts; using CompoundParts;
using Extensions; using Extensions;
using UnityEngine; using UnityEngine;
   
public class PartSim public class PartSim
{ {
private static readonly Pool<PartSim> pool = new Pool<PartSim>(Create, Reset); private static readonly Pool<PartSim> pool = new Pool<PartSim>(Create, Reset);
   
private readonly List<AttachNodeSim> attachNodes = new List<AttachNodeSim>(); private readonly List<AttachNodeSim> attachNodes = new List<AttachNodeSim>();
   
public double realMass; public double realMass;
public double baseMass; public double baseMass;
public double baseMassForCoM; public double baseMassForCoM;
public Vector3d centerOfMass; public Vector3d centerOfMass;
public double baseCost; public double baseCost;
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 List<PartSim> surfaceMountFuelTargets = new List<PartSim>(); public List<PartSim> surfaceMountFuelTargets = new List<PartSim>();
public bool hasModuleEngines; public bool hasModuleEngines;
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 isFairing; //public bool isFairing;
public float postStageMassAdjust; public float postStageMassAdjust;
public int stageIndex; public int stageIndex;
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;
   
private static PartSim Create() private static PartSim Create()
{ {
return new PartSim(); return new PartSim();
} }
   
private static void Reset(PartSim partSim) private static void Reset(PartSim partSim)
{ {
for (int i = 0; i < partSim.attachNodes.Count; i++) for (int i = 0; i < partSim.attachNodes.Count; i++)
{ {
partSim.attachNodes[i].Release(); partSim.attachNodes[i].Release();
} }
partSim.attachNodes.Clear(); partSim.attachNodes.Clear();
partSim.fuelTargets.Clear(); partSim.fuelTargets.Clear();
  partSim.surfaceMountFuelTargets.Clear();
partSim.resourceDrains.Reset(); partSim.resourceDrains.Reset();
partSim.resourceFlowStates.Reset(); partSim.resourceFlowStates.Reset();
partSim.resources.Reset(); partSim.resources.Reset();
  partSim.parent = null;
partSim.baseCost = 0d; partSim.baseCost = 0d;
partSim.baseMass = 0d; partSim.baseMass = 0d;
partSim.baseMassForCoM = 0d; partSim.baseMassForCoM = 0d;
partSim.startMass = 0d; partSim.startMass = 0d;
} }
   
public void Release() public void Release()
{ {
pool.Release(this); pool.Release(this);
} }
   
public static PartSim New(Part p, int id, double atmosphere, LogMsg log) public static PartSim New(Part p, int id, double atmosphere, LogMsg log)
{ {
PartSim partSim = pool.Borrow(); PartSim partSim = pool.Borrow();
   
partSim.part = p; partSim.part = p;
partSim.centerOfMass = p.transform.TransformPoint(p.CoMOffset); partSim.centerOfMass = p.transform.TransformPoint(p.CoMOffset);
partSim.partId = id; partSim.partId = id;
partSim.name = p.partInfo.name; partSim.name = p.partInfo.name;
   
if (log != null) log.buf.AppendLine("Create PartSim for " + partSim.name); if (log != null) log.buf.AppendLine("Create PartSim for " + partSim.name);
   
partSim.parent = null; partSim.parent = null;
partSim.parentAttach = p.attachMode; partSim.parentAttach = p.attachMode;
partSim.fuelCrossFeed = p.fuelCrossFeed; partSim.fuelCrossFeed = p.fuelCrossFeed;
partSim.noCrossFeedNodeKey = p.NoCrossFeedNodeKey; partSim.noCrossFeedNodeKey = p.NoCrossFeedNodeKey;
partSim.decoupledInStage = partSim.DecoupledInStage(p); partSim.decoupledInStage = partSim.DecoupledInStage(p);
partSim.isFuelLine = p.HasModule<CModuleFuelLine>(); partSim.isFuelLine = p.HasModule<CModuleFuelLine>();
partSim.isFuelTank = p is FuelTank; partSim.isFuelTank = p is FuelTank;
partSim.isSepratron = partSim.IsSepratron(); partSim.isSepratron = partSim.IsSepratron();
partSim.inverseStage = p.inverseStage; partSim.inverseStage = p.inverseStage;
//MonoBehaviour.print("inverseStage = " + inverseStage); //MonoBehaviour.print("inverseStage = " + inverseStage);
   
partSim.baseCost = p.GetCostDry(); partSim.baseCost = p.GetCostDry();
   
if (log != null) if (log != null)
{ {
log.buf.AppendLine("Parent part = " + (p.parent == null ? "null" : p.parent.partInfo.name)); log.buf.AppendLine("Parent part = " + (p.parent == null ? "null" : p.parent.partInfo.name));
log.buf.AppendLine("physicalSignificance = " + p.physicalSignificance); log.buf.AppendLine("physicalSignificance = " + p.physicalSignificance);
log.buf.AppendLine("PhysicsSignificance = " + p.PhysicsSignificance); log.buf.AppendLine("PhysicsSignificance = " + p.PhysicsSignificance);
} }
   
// Work out if the part should have no physical significance // Work out if the part should have no physical significance
// The root part is never "no physics" // The root part is never "no physics"
partSim.isNoPhysics = p.physicalSignificance == Part.PhysicalSignificance.NONE || partSim.isNoPhysics = p.physicalSignificance == Part.PhysicalSignificance.NONE ||
p.PhysicsSignificance == 1; p.PhysicsSignificance == 1;
   
if (p.HasModule<LaunchClamp>()) if (p.HasModule<LaunchClamp>())
{ {
partSim.realMass = 0d; partSim.realMass = 0d;
if (log != null) log.buf.AppendLine("Ignoring mass of launch clamp"); if (log != null) log.buf.AppendLine("Ignoring mass of launch clamp");
} }
else else
{ {
partSim.realMass = p.mass; partSim.realMass = p.mass;
if (log != null) log.buf.AppendLine("Using part.mass of " + p.mass); if (log != null) log.buf.AppendLine("Using part.mass of " + p.mass);
} }
   
partSim.postStageMassAdjust = 0f; partSim.postStageMassAdjust = 0f;
if (log != null) log.buf.AppendLine("Calculating postStageMassAdjust, prefabMass = " + p.prefabMass); if (log != null) log.buf.AppendLine("Calculating postStageMassAdjust, prefabMass = " + p.prefabMass);
int count = p.Modules.Count; int count = p.Modules.Count;
for (int i = 0; i < count; i++) for (int i = 0; i < count; i++)
{ {
if (log != null) log.buf.AppendLine("Module: " + p.Modules[i].moduleName); if (log != null) log.buf.AppendLine("Module: " + p.Modules[i].moduleName);
IPartMassModifier partMassModifier = p.Modules[i] as IPartMassModifier; IPartMassModifier partMassModifier = p.Modules[i] as IPartMassModifier;
if (partMassModifier != null) if (partMassModifier != null)
{ {
if (log != null) log.buf.AppendLine("ChangeWhen = " + partMassModifier.GetModuleMassChangeWhen()); if (log != null) log.buf.AppendLine("ChangeWhen = " + partMassModifier.GetModuleMassChangeWhen());
if (partMassModifier.GetModuleMassChangeWhen() == ModifierChangeWhen.STAGED) if (partMassModifier.GetModuleMassChangeWhen() == ModifierChangeWhen.STAGED)
{ {
float preStage = partMassModifier.GetModuleMass(p.prefabMass, ModifierStagingSituation.UNSTAGED); float preStage = partMassModifier.GetModuleMass(p.prefabMass, ModifierStagingSituation.UNSTAGED);
float postStage = partMassModifier.GetModuleMass(p.prefabMass, ModifierStagingSituation.STAGED); float postStage = partMassModifier.GetModuleMass(p.prefabMass, ModifierStagingSituation.STAGED);
if (log != null) log.buf.AppendLine("preStage = " + preStage + " postStage = " + postStage); if (log != null) log.buf.AppendLine("preStage = " + preStage + " postStage = " + postStage);
partSim.postStageMassAdjust += (postStage - preStage); partSim.postStageMassAdjust += (postStage - preStage);
} }
} }
} }
if (log != null) log.buf.AppendLine("postStageMassAdjust = " + partSim.postStageMassAdjust); if (log != null) log.buf.AppendLine("postStageMassAdjust = " + partSim.postStageMassAdjust);
   
for (int i = 0; i < p.Resources.Count; i++) for (int i = 0; i < p.Resources.Count; i++)
{ {
PartResource resource = p.Resources[i]; PartResource resource = p.Resources[i];
   
// 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 (log != null) if (log != null)
log.buf.AppendLine(resource.resourceName + " = " + resource.amount); log.buf.AppendLine(resource.resourceName + " = " + resource.amount);
   
partSim.resources.Add(resource.info.id, resource.amount); partSim.resources.Add(resource.info.id, resource.amount);
partSim.resourceFlowStates.Add(resource.info.id, resource.flowState ? 1 : 0); partSim.resourceFlowStates.Add(resource.info.id, resource.flowState ? 1 : 0);
} }
else else
{ {
if (log != null) log.buf.AppendLine(resource.resourceName + " is NaN. Skipping."); if (log != null) log.buf.AppendLine(resource.resourceName + " is NaN. Skipping.");
} }
} }
   
partSim.hasVessel = (p.vessel != null); partSim.hasVessel = (p.vessel != null);
partSim.isLanded = partSim.hasVessel && p.vessel.Landed; partSim.isLanded = partSim.hasVessel && p.vessel.Landed;
if (partSim.hasVessel) if (partSim.hasVessel)
{ {
partSim.vesselName = p.vessel.vesselName; partSim.vesselName = p.vessel.vesselName;
partSim.vesselType = p.vesselType; partSim.vesselType = p.vesselType;
} }
partSim.initialVesselName = p.initialVesselName; partSim.initialVesselName = p.initialVesselName;
   
partSim.hasMultiModeEngine = p.HasModule<MultiModeEngine>(); partSim.hasMultiModeEngine = p.HasModule<MultiModeEngine>();
partSim.hasModuleEngines = p.HasModule<ModuleEngines>(); partSim.hasModuleEngines = p.HasModule<ModuleEngines>();
   
partSim.isEngine = partSim.hasMultiModeEngine || partSim.hasModuleEngines; partSim.isEngine = partSim.hasMultiModeEngine || partSim.hasModuleEngines;
   
if (log != null) log.buf.AppendLine("Created " + partSim.name + ". Decoupled in stage " + partSim.decoupledInStage); if (log != null) log.buf.AppendLine("Created " + partSim.name + ". Decoupled in stage " + partSim.decoupledInStage);
   
return partSim; return partSim;
} }
   
public ResourceContainer ResourceDrains public ResourceContainer ResourceDrains
{ {
get get
{ {
return resourceDrains; return resourceDrains;
} }
} }
   
public ResourceContainer Resources public ResourceContainer Resources
{ {
get get
{ {
return resources; return resources;
} }
} }
   
public void CreateEngineSims(List<EngineSim> allEngines, double atmosphere, double mach, bool vectoredThrust, bool fullThrust, LogMsg log) public void CreateEngineSims(List<EngineSim> allEngines, double atmosphere, double mach, bool vectoredThrust, bool fullThrust, LogMsg log)
{ {
if (log != null) if (log != null)
{ {
log.buf.AppendLine("CreateEngineSims for " + this.name); log.buf.AppendLine("CreateEngineSims for " + this.name);
for (int i = 0; i < this.part.Modules.Count; i++) for (int i = 0; i < this.part.Modules.Count; i++)
{ {
PartModule partMod = this.part.Modules[i]; PartModule partMod = this.part.Modules[i];
log.buf.AppendLine("Module: " + partMod.moduleName); log.buf.AppendLine("Module: " + partMod.moduleName);
} }
} }
   
if (hasMultiModeEngine) if (hasMultiModeEngine)
{ {
// A multi-mode engine has multiple ModuleEngines but only one is active at any point // A multi-mode engine has multiple ModuleEngines but only one is active at any point
// The mode of the engine is the engineID of the ModuleEngines that is active // The mode of the engine is the engineID of the ModuleEngines that is active
string mode = part.GetModule<MultiModeEngine>().mode; string mode = part.GetModule<MultiModeEngine>().mode;
   
List<ModuleEngines> engines = part.GetModules<ModuleEngines>(); List<ModuleEngines> engines = part.GetModules<ModuleEngines>();
for (int i = 0; i < engines.Count; ++i) for (int i = 0; i < engines.Count; ++i)
{ {
ModuleEngines engine = engines[i]; ModuleEngines engine = engines[i];
if (engine.engineID == mode) if (engine.engineID == mode)
{ {
if (log != null) log.buf.AppendLine("Module: " + engine.moduleName); if (log != null) log.buf.AppendLine("Module: " + engine.moduleName);
   
EngineSim engineSim = EngineSim.New( EngineSim engineSim = EngineSim.New(
this, this,
engine, engine,
atmosphere, atmosphere,
(float)mach, (float)mach,
vectoredThrust, vectoredThrust,
fullThrust, fullThrust,
log); log);
allEngines.Add(engineSim); allEngines.Add(engineSim);
} }
} }
} }
else if (hasModuleEngines) else if (hasModuleEngines)
{ {
List<ModuleEngines> engines = part.GetModules<ModuleEngines>(); List<ModuleEngines> engines = part.GetModules<ModuleEngines>();
for (int i = 0; i < engines.Count; ++i) for (int i = 0; i < engines.Count; ++i)
{ {
ModuleEngines engine = engines[i]; ModuleEngines engine = engines[i];
if (log != null) log.buf.AppendLine("Module: " + engine.moduleName); if (log != null) log.buf.AppendLine("Module: " + engine.moduleName);
   
EngineSim engineSim = EngineSim.New( EngineSim engineSim = EngineSim.New(
this, this,
engine, engine,
atmosphere, atmosphere,
(float)mach, (float)mach,
vectoredThrust, vectoredThrust,
fullThrust, fullThrust,
log); log);
allEngines.Add(engineSim); allEngines.Add(engineSim);
} }
} }
   
if (log != null) if (log != null)
{ {
log.Flush(); log.Flush();
} }
} }
   
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 void DrainResources(double time) public void DrainResources(double time)
{ {
//MonoBehaviour.print("DrainResources(" + name + ":" + partId + ", " + time + ")"); //MonoBehaviour.print("DrainResources(" + name + ":" + partId + ", " + time + ")");
for (int i = 0; i < resourceDrains.Types.Count; ++i) for (int i = 0; i < resourceDrains.Types.Count; ++i)
{ {
int type = resourceDrains.Types[i]; int type = resourceDrains.Types[i];
   
//MonoBehaviour.print("draining " + (time * resourceDrains[type]) + " " + ResourceContainer.GetResourceName(type)); //MonoBehaviour.print("draining " + (time * resourceDrains[type]) + " " + ResourceContainer.GetResourceName(type));
resources.Add(type, -time * resourceDrains[type]); resources.Add(type, -time * resourceDrains[type]);
//MonoBehaviour.print(ResourceContainer.GetResourceName(type) + " left = " + resources[type]); //MonoBehaviour.print(ResourceContainer.GetResourceName(type) + " left = " + resources[type]);
} }
} }
   
public String DumpPartAndParentsToBuffer(StringBuilder buffer, String prefix) public String DumpPartAndParentsToBuffer(StringBuilder buffer, String prefix)
{ {
if (parent != null) if (parent != null)
{ {
prefix = parent.DumpPartAndParentsToBuffer(buffer, prefix) + " "; prefix = parent.DumpPartAndParentsToBuffer(buffer, prefix) + " ";
} }
   
DumpPartToBuffer(buffer, prefix); 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(name); buffer.Append(name);
buffer.AppendFormat(":[id = {0:d}, decouple = {1:d}, invstage = {2:d}", partId, decoupledInStage, inverseStage); buffer.AppendFormat(":[id = {0:d}, decouple = {1:d}, invstage = {2:d}", partId, decoupledInStage, inverseStage);
   
//buffer.AppendFormat(", vesselName = '{0}'", vesselName); //buffer.AppendFormat(", vesselName = '{0}'", vesselName);
//buffer.AppendFormat(", vesselType = {0}", SimManager.GetVesselTypeString(vesselType)); //buffer.AppendFormat(", vesselType = {0}", SimManager.GetVesselTypeString(vesselType));
//buffer.AppendFormat(", initialVesselName = '{0}'", initialVesselName); //buffer.AppendFormat(", initialVesselName = '{0}'", initialVesselName);
   
buffer.AppendFormat(", isNoPhys = {0}", isNoPhysics); buffer.AppendFormat(", isNoPhys = {0}", isNoPhysics);
buffer.AppendFormat(", baseMass = {0}", baseMass); buffer.AppendFormat(", baseMass = {0}", baseMass);
buffer.AppendFormat(", baseMassForCoM = {0}", baseMassForCoM); buffer.AppendFormat(", baseMassForCoM = {0}", baseMassForCoM);
   
buffer.AppendFormat(", fuelCF = {0}", fuelCrossFeed); buffer.AppendFormat(", fuelCF = {0}", fuelCrossFeed);
buffer.AppendFormat(", noCFNKey = '{0}'", noCrossFeedNodeKey); buffer.AppendFormat(", noCFNKey = '{0}'", noCrossFeedNodeKey);
   
buffer.AppendFormat(", isSep = {0}", isSepratron); buffer.AppendFormat(", isSep = {0}", isSepratron);
   
for (int i = 0; i < resources.Types.Count; i++) for (int i = 0; i < resources.Types.Count; i++)
{ {
int type = resources.Types[i]; int type = resources.Types[i];
buffer.AppendFormat(", {0} = {1:g6}", ResourceContainer.GetResourceName(type), resources[type]); buffer.AppendFormat(", {0} = {1:g6}", ResourceContainer.GetResourceName(type), resources[type]);
} }
   
if (attachNodes.Count > 0) if (attachNodes.Count > 0)
{ {
buffer.Append(", attached = <"); buffer.Append(", attached = <");
attachNodes[0].DumpToBuffer(buffer); attachNodes[0].DumpToBuffer(buffer);
for (int i = 1; i < attachNodes.Count; i++) for (int i = 1; i < attachNodes.Count; i++)
{ {
buffer.Append(", "); buffer.Append(", ");
attachNodes[i].DumpToBuffer(buffer); 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 + " ";
for (int i = 0; i < allParts.Count; i++) for (int i = 0; i < allParts.Count; i++)
{ {
PartSim partSim = allParts[i]; PartSim partSim = allParts[i];
if (partSim.parent == this) if (partSim.parent == this)
partSim.DumpPartToBuffer(buffer, newPrefix, allParts); partSim.DumpPartToBuffer(buffer, newPrefix, allParts);
} }
} }
} }
   
public bool EmptyOf(HashSet<int> types) public bool EmptyOf(HashSet<int> types)
{ {
foreach (int type in types) foreach (int type in types)
{ {
if (resources.HasType(type) && resourceFlowStates[type] != 0 && resources[type] > SimManager.RESOURCE_PART_EMPTY_THRESH) if (resources.HasType(type) && resourceFlowStates[type] != 0 && resources[type] > SimManager.RESOURCE_PART_EMPTY_THRESH)
return false; return false;
} }
   
return true; return true;
} }
   
public double GetMass(int currentStage, bool forCoM = false) public double GetMass(int currentStage, bool forCoM = false)
{ {
if (decoupledInStage >= currentStage) if (decoupledInStage >= currentStage)
return 0d; return 0d;
   
double mass = forCoM ? baseMassForCoM : baseMass; double mass = forCoM ? baseMassForCoM : baseMass;
   
for (int i = 0; i < resources.Types.Count; ++i) for (int i = 0; i < resources.Types.Count; ++i)
{ {
mass += resources.GetResourceMass(resources.Types[i]); mass += resources.GetResourceMass(resources.Types[i]);
} }
   
if (postStageMassAdjust != 0.0 && currentStage <= inverseStage) if (postStageMassAdjust != 0.0 && currentStage <= inverseStage)
{ {
mass += postStageMassAdjust; mass += postStageMassAdjust;
} }
   
return mass; return mass;
} }
   
public double GetCost(int currentStage) public double GetCost(int currentStage)
{ {
if (decoupledInStage >= currentStage) if (decoupledInStage >= currentStage)
return 0d; return 0d;
   
double cost = baseCost; double cost = baseCost;
   
for (int i = 0; i < resources.Types.Count; ++i) for (int i = 0; i < resources.Types.Count; ++i)
{ {
cost += resources.GetResourceCost(resources.Types[i]); cost += resources.GetResourceCost(resources.Types[i]);
} }
   
return cost; return cost;
} }
   
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 void GetSourceSet(int type, bool includeSurfaceMountedParts, List<PartSim> allParts, HashSet<PartSim> visited, HashSet<PartSim> allSources, LogMsg log, String indent) public void GetSourceSet(int type, bool includeSurfaceMountedParts, List<PartSim> allParts, HashSet<PartSim> visited, HashSet<PartSim> allSources, LogMsg log, String indent)
{ {
if (log != null) if (log != null)
{ {
log.buf.AppendLine(indent + "GetSourceSet(" + ResourceContainer.GetResourceName(type) + ") for " + name + ":" + partId); log.buf.AppendLine(indent + "GetSourceSet(" + ResourceContainer.GetResourceName(type) + ") for " + name + ":" + partId);
indent += " "; indent += " ";
} }
   
// Rule 1: Each part can be only visited once, If it is visited for second time in particular search it returns as is. // Rule 1: Each part can be only visited once, If it is visited for second time in particular search it returns as is.
if (visited.Contains(this)) if (visited.Contains(this))
{ {
if (log != null) log.buf.AppendLine(indent + "Returning empty set, already visited (" + name + ":" + partId + ")"); if (log != null) log.buf.AppendLine(indent + "Returning empty set, already visited (" + name + ":" + partId + ")");
return; return;
} }
   
if (log != null) log.buf.AppendLine(indent + "Adding this to visited"); if (log != null) 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("for each fuel line"); //MonoBehaviour.print("for each fuel line");
   
int lastCount = allSources.Count; int lastCount = allSources.Count;
   
for (int i = 0; i < this.fuelTargets.Count; i++) for (int i = 0; i < this.fuelTargets.Count; i++)
{ {
PartSim partSim = this.fuelTargets[i]; PartSim partSim = this.fuelTargets[i];
if (partSim != null) if (partSim != null)
{ {
if (visited.Contains(partSim)) if (visited.Contains(partSim))
{ {
if (log != null) log.buf.AppendLine(indent + "Fuel target already visited, skipping (" + partSim.name + ":" + partSim.partId + ")"); if (log != null) log.buf.AppendLine(indent + "Fuel target already visited, skipping (" + partSim.name + ":" + partSim.partId + ")");
} }
else else
{ {
if (log != null) log.buf.AppendLine(indent + "Adding fuel target as source (" + partSim.name + ":" + partSim.partId + ")"); if (log != null) log.buf.AppendLine(indent + "Adding fuel target as source (" + partSim.name + ":" + partSim.partId + ")");
   
partSim.GetSourceSet(type, includeSurfaceMountedParts, allParts, visited, allSources, log, indent); partSim.GetSourceSet(type, includeSurfaceMountedParts, allParts, visited, allSources, log, indent);
} }
} }
} }
   
// check surface mounted fuel targets // check surface mounted fuel targets
if (includeSurfaceMountedParts) if (includeSurfaceMountedParts)
{ {
for (int i = 0; i < surfaceMountFuelTargets.Count; i++) for (int i = 0; i < surfaceMountFuelTargets.Count; i++)
{ {
PartSim partSim = this.surfaceMountFuelTargets[i]; PartSim partSim = this.surfaceMountFuelTargets[i];
if (partSim != null) if (partSim != null)
{ {
if (visited.Contains(partSim)) if (visited.Contains(partSim))
{ {
if (log != null) log.buf.AppendLine(indent + "Fuel target already visited, skipping (" + partSim.name + ":" + partSim.partId + ")"); if (log != null) log.buf.AppendLine(indent + "Fuel target already visited, skipping (" + partSim.name + ":" + partSim.partId + ")");
} }
else else
{ {
if (log != null) log.buf.AppendLine(indent + "Adding fuel target as source (" + partSim.name + ":" + partSim.partId + ")"); if (log != null) log.buf.AppendLine(indent + "Adding fuel target as source (" + partSim.name + ":" + partSim.partId + ")");
   
partSim.GetSourceSet(type, true, allParts, visited, allSources, log, indent); partSim.GetSourceSet(type, true, allParts, visited, allSources, log, indent);
} }
} }
} }
} }
   
if (allSources.Count > lastCount) if (allSources.Count > lastCount)
{ {
if (log != null) log.buf.AppendLine(indent + "Returning " + (allSources.Count - lastCount) + " fuel target sources (" + this.name + ":" + this.partId + ")"); if (log != null) log.buf.AppendLine(indent + "Returning " + (allSources.Count - lastCount) + " fuel target sources (" + this.name + ":" + this.partId + ")");
return; return;
} }
   
   
// 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 (fuelCrossFeed) if (fuelCrossFeed)
{ {
lastCount = allSources.Count; lastCount = allSources.Count;
//MonoBehaviour.print("for each attach node"); //MonoBehaviour.print("for each attach node");
for (int i = 0; i < this.attachNodes.Count; i++) for (int i = 0; i < this.attachNodes.Count; i++)
{ {
AttachNodeSim attachSim = this.attachNodes[i]; AttachNodeSim attachSim = this.attachNodes[i];
if (attachSim.attachedPartSim != null) if (attachSim.attachedPartSim != null)
{ {
if (attachSim.nodeType == AttachNode.NodeType.Stack) if (attachSim.nodeType == AttachNode.NodeType.Stack)
{ {
if ((string.IsNullOrEmpty(noCrossFeedNodeKey) == false && attachSim.id.Contains(noCrossFeedNodeKey)) == false) if ((string.IsNullOrEmpty(noCrossFeedNodeKey) == false && attachSim.id.Contains(noCrossFeedNodeKey)) == false)
{ {
if (visited.Contains(attachSim.attachedPartSim)) if (visited.Contains(attachSim.attachedPartSim))
{ {
if (log != null) log.buf.AppendLine(indent + "Attached part already visited, skipping (" + attachSim.attachedPartSim.name + ":" + attachSim.attachedPartSim.partId + ")"); if (log != null) log.buf.AppendLine(indent + "Attached part already visited, skipping (" + attachSim.attachedPartSim.name + ":" + attachSim.attachedPartSim.partId + ")");
} }
else else
{ {
if (log != null) log.buf.AppendLine(indent + "Adding attached part as source (" + attachSim.attachedPartSim.name + ":" + attachSim.attachedPartSim.partId + ")"); if (log != null) log.buf.AppendLine(indent + "Adding attached part as source (" + attachSim.attachedPartSim.name + ":" + attachSim.attachedPartSim.partId + ")");
   
attachSim.attachedPartSim.GetSourceSet(type, includeSurfaceMountedParts, allParts, visited, allSources, log, indent); attachSim.attachedPartSim.GetSourceSet(type, includeSurfaceMountedParts, allParts, visited, allSources, log, indent);
} }
} }
} }
} }
} }
   
if (allSources.Count > lastCount) if (allSources.Count > lastCount)
{ {
if (log != null) log.buf.AppendLine(indent + "Returning " + (allSources.Count - lastCount) + " attached sources (" + this.name + ":" + this.partId + ")"); if (log != null) log.buf.AppendLine(indent + "Returning " + (allSources.Count - lastCount) + " attached sources (" + this.name + ":" + this.partId + ")");
return; return;
} }
} }
   
// 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 (resources.HasType(type) && resourceFlowStates[type] > 0.0) if (resources.HasType(type) && resourceFlowStates[type] > 0.0)
{ {
if (resources[type] > SimManager.RESOURCE_MIN) if (resources[type] > SimManager.RESOURCE_MIN)
{ {
allSources.Add(this); allSources.Add(this);
   
if (log != null) log.buf.AppendLine(indent + "Returning enabled tank as only source (" + name + ":" + partId + ")"); if (log != null) log.buf.AppendLine(indent + "Returning enabled tank as only source (" + name + ":" + partId + ")");
} }
   
return; return;
} }
else else
{ {
if (log != null) log.buf.AppendLine(indent + "Not fuel tank or disabled. HasType = " + resources.HasType(type) + " FlowState = " + resourceFlowStates[type]); if (log != null) log.buf.AppendLine(indent + "Not fuel tank or disabled. HasType = " + resources.HasType(type) + " FlowState = " + resourceFlowStates[type]);
} }
   
// 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 (parent != null && parentAttach == AttachModes.SRF_ATTACH) if (parent != null && parentAttach == AttachModes.SRF_ATTACH)
{ {
if (fuelCrossFeed) if (fuelCrossFeed)
{ {
if (visited.Contains(parent)) if (visited.Contains(parent))
{ {
if (log != null) log.buf.AppendLine(indent + "Parent part already visited, skipping (" + parent.name + ":" + parent.partId + ")"); if (log != null) log.buf.AppendLine(indent + "Parent part already visited, skipping (" + parent.name + ":" + parent.partId + ")");
} }
else else
{ {
lastCount = allSources.Count; lastCount = allSources.Count;
this.parent.GetSourceSet(type, includeSurfaceMountedParts, allParts, visited, allSources, log, indent); this.parent.GetSourceSet(type, includeSurfaceMountedParts, allParts, visited, allSources, log, indent);
if (allSources.Count > lastCount) if (allSources.Count > lastCount)
{ {
if (log != null) log.buf.AppendLine(indent + "Returning " + (allSources.Count - lastCount) + " parent sources (" + this.name + ":" + this.partId + ")"); if (log != null) log.buf.AppendLine(indent + "Returning " + (allSources.Count - lastCount) + " parent sources (" + this.name + ":" + this.partId + ")");
return; return;
} }
} }
} }
} }
   
// 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) log.buf.AppendLine(indent + "Returning empty set, no sources found (" + name + ":" + partId + ")"); if (log != null) log.buf.AppendLine(indent + "Returning empty set, no sources found (" + name + ":" + partId + ")");
   
return; return;
} }
   
public double GetStartMass() public double GetStartMass()
{ {
return startMass; return startMass;
} }
   
public void RemoveAttachedParts(HashSet<PartSim> partSims) public void RemoveAttachedParts(HashSet<PartSim> partSims)
{ {
// Loop through the attached parts // Loop through the attached parts
for (int i = 0; i < this.attachNodes.Count; i++) for (int i = 0; i < this.attachNodes.Count; i++)
{ {
AttachNodeSim attachSim = this.attachNodes[i]; AttachNodeSim attachSim = this.attachNodes[i];
// 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;
} }
} }
   
// Loop through the fuel targets (fuel line sources) // Loop through the fuel targets (fuel line sources)
for (int i = 0; i < this.fuelTargets.Count; i++) for (int i = 0; i < this.fuelTargets.Count; i++)
{ {
PartSim fuelTargetSim = this.fuelTargets[i]; PartSim fuelTargetSim = this.fuelTargets[i];
// 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 (fuelTargetSim != null && partSims.Contains(fuelTargetSim)) if (fuelTargetSim != null && partSims.Contains(fuelTargetSim))
{ {
this.fuelTargets[i] = null; this.fuelTargets[i] = null;
} }
} }
   
  // Loop through the surface attached fuel targets (surface attached parts for new flow modes)
  for (int i = 0; i < this.surfaceMountFuelTargets.Count; i++)
  {
  PartSim fuelTargetSim = this.surfaceMountFuelTargets[i];
  // If the part is in the set then "remove" it by clearing the PartSim reference
  if (fuelTargetSim != null && partSims.Contains(fuelTargetSim))
  {
  this.surfaceMountFuelTargets[i] = null;
  }
  }
} }
   
public void SetupAttachNodes(Dictionary<Part, PartSim> partSimLookup, LogMsg log) public void SetupAttachNodes(Dictionary<Part, PartSim> partSimLookup, LogMsg log)
{ {
if (log != null) log.buf.AppendLine("SetupAttachNodes for " + name + ":" + partId + ""); if (log != null) log.buf.AppendLine("SetupAttachNodes for " + name + ":" + partId + "");
   
attachNodes.Clear(); attachNodes.Clear();
   
for (int i = 0; i < part.attachNodes.Count; ++i) for (int i = 0; i < part.attachNodes.Count; ++i)
{ {
AttachNode attachNode = part.attachNodes[i]; AttachNode attachNode = part.attachNodes[i];
   
if (log != null) log.buf.AppendLine("AttachNode " + attachNode.id + " = " + (attachNode.attachedPart != null ? attachNode.attachedPart.partInfo.name : "null")); if (log != 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) log.buf.AppendLine("Adding attached node " + attachedSim.name + ":" + attachedSim.partId + ""); if (log != null) log.buf.AppendLine("Adding attached node " + attachedSim.name + ":" + attachedSim.partId + "");
   
attachNodes.Add(AttachNodeSim.New(attachedSim, attachNode.id, attachNode.nodeType)); attachNodes.Add(AttachNodeSim.New(attachedSim, attachNode.id, attachNode.nodeType));
} }
else else
{ {
if (log != null) log.buf.AppendLine("No PartSim for attached part (" + attachNode.attachedPart.partInfo.name + ")"); if (log != null) log.buf.AppendLine("No PartSim for attached part (" + attachNode.attachedPart.partInfo.name + ")");
} }
} }
} }
   
for (int i = 0; i < part.fuelLookupTargets.Count; ++i) for (int i = 0; i < part.fuelLookupTargets.Count; ++i)
{ {
Part p = part.fuelLookupTargets[i]; Part p = part.fuelLookupTargets[i];
   
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) log.buf.AppendLine("Fuel target: " + targetSim.name + ":" + targetSim.partId); if (log != null) log.buf.AppendLine("Fuel target: " + targetSim.name + ":" + targetSim.partId);
   
fuelTargets.Add(targetSim); fuelTargets.Add(targetSim);
} }
else else
{ {
if (log != null) log.buf.AppendLine("No PartSim for fuel target (" + p.name + ")"); if (log != null) log.buf.AppendLine("No PartSim for fuel target (" + p.name + ")");
} }
} }
} }
} }
   
public void SetupParent(Dictionary<Part, PartSim> partSimLookup, LogMsg log) public void SetupParent(Dictionary<Part, PartSim> partSimLookup, LogMsg log)
{ {
if (part.parent != null) if (part.parent != null)
{ {
parent = null; parent = null;
if (partSimLookup.TryGetValue(part.parent, out parent)) if (partSimLookup.TryGetValue(part.parent, out parent))
{ {
if (log != null) log.buf.AppendLine("Parent part is " + parent.name + ":" + parent.partId); if (log != null) log.buf.AppendLine("Parent part is " + parent.name + ":" + parent.partId);
if (part.attachMode == AttachModes.SRF_ATTACH && part.attachRules.srfAttach && part.fuelCrossFeed && part.parent.fuelCrossFeed) if (part.attachMode == AttachModes.SRF_ATTACH && part.attachRules.srfAttach && part.fuelCrossFeed && part.parent.fuelCrossFeed)
{ {
if (log != null) log.buf.AppendLine("Added " + name + " to " + parent.name + " surface mounted fuel targets."); if (log != null) log.buf.AppendLine("Added " + name + " to " + parent.name + " surface mounted fuel targets.");
parent.surfaceMountFuelTargets.Add(this); parent.surfaceMountFuelTargets.Add(this);
} }
} }
else else
{ {
if (log != null) log.buf.AppendLine("No PartSim for parent part (" + part.parent.partInfo.name + ")"); if (log != null) log.buf.AppendLine("No PartSim for parent part (" + part.parent.partInfo.name + ")");
} }
} }
} }
   
public double TimeToDrainResource() public double TimeToDrainResource()
{ {
//MonoBehaviour.print("TimeToDrainResource(" + name + ":" + partId + ")"); //MonoBehaviour.print("TimeToDrainResource(" + name + ":" + partId + ")");
double time = double.MaxValue; double time = double.MaxValue;
   
for (int i = 0; i < resourceDrains.Types.Count; ++i) for (int i = 0; i < resourceDrains.Types.Count; ++i)
{ {
int type = resourceDrains.Types[i]; int type = resourceDrains.Types[i];
   
if (resourceDrains[type] > 0) if (resourceDrains[type] > 0)
{ {
time = Math.Min(time, resources[type] / resourceDrains[type]); time = Math.Min(time, resources[type] / 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;
} }
   
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;
for (int i = 0; i < thrustTransforms.Count; ++i) for (int i = 0; i < thrustTransforms.Count; ++i)
{ {
Transform trans = thrustTransforms[i]; Transform trans = thrustTransforms[i];
   
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); if (log != null) log.buf.AppendFormat("Transform = ({0:g6}, {1:g6}, {2:g6}) length = {3:g6}\n", trans.forward.x, trans.forward.y, trans.forward.z, trans.forward.magnitude);
   
thrustvec -= trans.forward; thrustvec -= trans.forward;
} }
   
if (log != null) log.buf.AppendFormat("ThrustVec = ({0:g6}, {1:g6}, {2:g6}) length = {3:g6}\n", thrustvec.x, thrustvec.y, thrustvec.z, thrustvec.magnitude); if (log != null) log.buf.AppendFormat("ThrustVec = ({0:g6}, {1:g6}, {2:g6}) length = {3:g6}\n", thrustvec.x, thrustvec.y, thrustvec.z, thrustvec.magnitude);
   
thrustvec.Normalize(); thrustvec.Normalize();
   
if (log != null) log.buf.AppendFormat("ThrustVecN = ({0:g6}, {1:g6}, {2:g6}) length = {3:g6}\n", thrustvec.x, thrustvec.y, thrustvec.z, thrustvec.magnitude); if (log != null) log.buf.AppendFormat("ThrustVecN = ({0:g6}, {1:g6}, {2:g6}) length = {3:g6}\n", thrustvec.x, thrustvec.y, thrustvec.z, thrustvec.magnitude);
   
return thrustvec; return thrustvec;
} }
   
private int DecoupledInStage(Part thePart, int stage = -1) private int DecoupledInStage(Part thePart, int stage = -1)
{ {
if (IsDecoupler(thePart) && thePart.inverseStage > stage) if (IsDecoupler(thePart) && thePart.inverseStage > stage)
stage = thePart.inverseStage; stage = thePart.inverseStage;
   
if (thePart.parent != null) if (thePart.parent != null)
stage = DecoupledInStage(thePart.parent, stage); stage = DecoupledInStage(thePart.parent, stage);
   
return stage; return stage;
} }
   
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 IsDecoupler(Part thePart) private bool IsDecoupler(Part thePart)
{ {
return thePart.GetProtoModuleDecoupler()?.IsStageEnabled ?? false; return thePart.GetProtoModuleDecoupler()?.IsStageEnabled ?? false;
} }
   
private bool IsSepratron() private bool IsSepratron()
{ {
if (!part.ActivatesEvenIfDisconnected) if (!part.ActivatesEvenIfDisconnected)
{ {
return false; return false;
} }
   
if (part is SolidRocket) if (part is SolidRocket)
{ {
return true; return true;
} }
   
IEnumerable<ModuleEngines> modList = part.Modules.OfType<ModuleEngines>(); IEnumerable<ModuleEngines> modList = 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;
} }
} }
} }