Temporary tweak to threshold at which parts are considered empty for staging purposes.
Temporary tweak to threshold at which parts are considered empty for staging purposes.
This needs to be fixed properly as the staging fails if an asparagus booster is tweaked to contain unbalanced resource amounts.

// //
// 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 baseMass; public double baseMass;
public Vector3d centerOfMass; public Vector3d centerOfMass;
public double cost; public double cost;
public int decoupledInStage; public int decoupledInStage;
public bool fuelCrossFeed; public bool fuelCrossFeed;
public List<PartSim> fuelTargets = new List<PartSim>(); public List<PartSim> fuelTargets = new List<PartSim>();
public bool hasModuleEngines; public bool hasModuleEngines;
public bool hasModuleEnginesFX; public bool hasModuleEnginesFX;
public bool hasMultiModeEngine; public bool hasMultiModeEngine;
   
public bool hasVessel; public bool hasVessel;
public String initialVesselName; public String initialVesselName;
public int inverseStage; public int inverseStage;
public bool isDecoupler; public bool isDecoupler;
public bool isEngine; public bool isEngine;
public bool isFuelLine; public bool isFuelLine;
public bool isFuelTank; public bool isFuelTank;
public bool isLanded; public bool isLanded;
public bool isNoPhysics; public bool isNoPhysics;
public bool isSepratron; public bool isSepratron;
public bool isFairing; public bool isFairing;
public bool localCorrectThrust; public bool localCorrectThrust;
public float moduleMass; public float moduleMass;
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.resourceDrains.Reset(); partSim.resourceDrains.Reset();
partSim.resourceFlowStates.Reset(); partSim.resourceFlowStates.Reset();
partSim.resources.Reset(); partSim.resources.Reset();
partSim.baseMass = 0d; partSim.baseMass = 0d;
partSim.startMass = 0d; partSim.startMass = 0d;
} }
   
public void Release() public void Release()
{ {
pool.Release(this); pool.Release(this);
} }
   
public static PartSim New(Part thePart, int id, double atmosphere, LogMsg log) public static PartSim New(Part thePart, int id, double atmosphere, LogMsg log)
{ {
PartSim partSim = pool.Borrow(); PartSim partSim = pool.Borrow();
   
   
partSim.part = thePart; partSim.part = thePart;
partSim.centerOfMass = thePart.transform.TransformPoint(thePart.CoMOffset); partSim.centerOfMass = thePart.transform.TransformPoint(thePart.CoMOffset);
partSim.partId = id; partSim.partId = id;
partSim.name = partSim.part.partInfo.name; partSim.name = partSim.part.partInfo.name;
   
if (log != null) if (log != null)
{ {
log.buf.AppendLine("Create PartSim for " + partSim.name); log.buf.AppendLine("Create PartSim for " + partSim.name);
} }
   
partSim.parent = null; partSim.parent = null;
partSim.parentAttach = partSim.part.attachMode; partSim.parentAttach = partSim.part.attachMode;
partSim.fuelCrossFeed = partSim.part.fuelCrossFeed; partSim.fuelCrossFeed = partSim.part.fuelCrossFeed;
partSim.noCrossFeedNodeKey = partSim.part.NoCrossFeedNodeKey; partSim.noCrossFeedNodeKey = partSim.part.NoCrossFeedNodeKey;
partSim.decoupledInStage = partSim.DecoupledInStage(partSim.part); partSim.decoupledInStage = partSim.DecoupledInStage(partSim.part);
partSim.isFuelLine = partSim.part.HasModule<CModuleFuelLine>(); partSim.isFuelLine = partSim.part.HasModule<CModuleFuelLine>();
partSim.isFuelTank = partSim.part is FuelTank; partSim.isFuelTank = partSim.part is FuelTank;
partSim.isSepratron = partSim.IsSepratron(); partSim.isSepratron = partSim.IsSepratron();
partSim.inverseStage = partSim.part.inverseStage; partSim.inverseStage = partSim.part.inverseStage;
//MonoBehaviour.print("inverseStage = " + inverseStage); //MonoBehaviour.print("inverseStage = " + inverseStage);
   
partSim.cost = partSim.part.GetCostWet(); partSim.cost = partSim.part.GetCostWet();
   
// Work out if the part should have no physical significance // Work out if the part should have no physical significance
partSim.isNoPhysics = partSim.part.HasModule<LaunchClamp>() || partSim.isNoPhysics = partSim.part.HasModule<LaunchClamp>() ||
partSim.part.physicalSignificance == Part.PhysicalSignificance.NONE || partSim.part.physicalSignificance == Part.PhysicalSignificance.NONE ||
partSim.part.PhysicsSignificance == 1; partSim.part.PhysicsSignificance == 1;
   
if (!partSim.isNoPhysics) if (!partSim.isNoPhysics)
{ {
partSim.baseMass = partSim.part.mass; partSim.baseMass = partSim.part.mass;
} }
   
if (SimManager.logOutput) if (SimManager.logOutput)
{ {
MonoBehaviour.print((partSim.isNoPhysics ? "Ignoring" : "Using") + " part.mass of " + partSim.part.mass); MonoBehaviour.print((partSim.isNoPhysics ? "Ignoring" : "Using") + " part.mass of " + partSim.part.mass);
} }
   
for (int i = 0; i < partSim.part.Resources.Count; i++) for (int i = 0; i < partSim.part.Resources.Count; i++)
{ {
PartResource resource = partSim.part.Resources[i]; PartResource resource = partSim.part.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 (SimManager.logOutput) if (SimManager.logOutput)
{ {
MonoBehaviour.print(resource.resourceName + " = " + resource.amount); MonoBehaviour.print(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
{ {
MonoBehaviour.print(resource.resourceName + " is NaN. Skipping."); MonoBehaviour.print(resource.resourceName + " is NaN. Skipping.");
} }
} }
   
partSim.startMass = partSim.GetMass(-1); partSim.startMass = partSim.GetMass(-1);
   
partSim.hasVessel = (partSim.part.vessel != null); partSim.hasVessel = (partSim.part.vessel != null);
partSim.isLanded = partSim.hasVessel && partSim.part.vessel.Landed; partSim.isLanded = partSim.hasVessel && partSim.part.vessel.Landed;
if (partSim.hasVessel) if (partSim.hasVessel)
{ {
partSim.vesselName = partSim.part.vessel.vesselName; partSim.vesselName = partSim.part.vessel.vesselName;
partSim.vesselType = partSim.part.vesselType; partSim.vesselType = partSim.part.vesselType;
} }
partSim.initialVesselName = partSim.part.initialVesselName; partSim.initialVesselName = partSim.part.initialVesselName;
   
partSim.hasMultiModeEngine = partSim.part.HasModule<MultiModeEngine>(); partSim.hasMultiModeEngine = partSim.part.HasModule<MultiModeEngine>();
partSim.hasModuleEnginesFX = partSim.part.HasModule<ModuleEnginesFX>(); partSim.hasModuleEnginesFX = partSim.part.HasModule<ModuleEnginesFX>();
partSim.hasModuleEngines = partSim.part.HasModule<ModuleEngines>(); partSim.hasModuleEngines = partSim.part.HasModule<ModuleEngines>();
   
partSim.isEngine = partSim.hasMultiModeEngine || partSim.hasModuleEnginesFX || partSim.hasModuleEngines; partSim.isEngine = partSim.hasMultiModeEngine || partSim.hasModuleEnginesFX || partSim.hasModuleEngines;
   
if (SimManager.logOutput) if (SimManager.logOutput)
{ {
MonoBehaviour.print("Created " + partSim.name + ". Decoupled in stage " + partSim.decoupledInStage); MonoBehaviour.print("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)
{ {
bool correctThrust = SimManager.DoesEngineUseCorrectedThrust(part); bool correctThrust = SimManager.DoesEngineUseCorrectedThrust(part);
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);
} }
   
log.buf.AppendLine("correctThrust = " + correctThrust); log.buf.AppendLine("correctThrust = " + correctThrust);
} }
   
if (hasMultiModeEngine) if (hasMultiModeEngine)
{ {
// A multi-mode engine has multiple ModuleEnginesFX but only one is active at any point // A multi-mode engine has multiple ModuleEnginesFX but only one is active at any point
// The mode of the engine is the engineID of the ModuleEnginesFX that is active // The mode of the engine is the engineID of the ModuleEnginesFX that is active
string mode = part.GetModule<MultiModeEngine>().mode; string mode = part.GetModule<MultiModeEngine>().mode;
   
List<ModuleEnginesFX> engines = part.GetModules<ModuleEnginesFX>(); List<ModuleEnginesFX> engines = part.GetModules<ModuleEnginesFX>();
for (int i = 0; i < engines.Count; ++i) for (int i = 0; i < engines.Count; ++i)
{ {
ModuleEnginesFX engine = engines[i]; ModuleEnginesFX engine = engines[i];
if (engine.engineID == mode) if (engine.engineID == mode)
{ {
if (log != null) if (log != null)
{ {
log.buf.AppendLine("Module: " + engine.moduleName); log.buf.AppendLine("Module: " + engine.moduleName);
} }
   
Vector3 thrustvec = this.CalculateThrustVector(vectoredThrust ? engine.thrustTransforms : null, log); Vector3 thrustvec = this.CalculateThrustVector(vectoredThrust ? engine.thrustTransforms : null, log);
   
EngineSim engineSim = EngineSim.New( EngineSim engineSim = EngineSim.New(
this, this,
atmosphere, atmosphere,
(float)mach, (float)mach,
engine.maxFuelFlow, engine.maxFuelFlow,
engine.minFuelFlow, engine.minFuelFlow,
engine.thrustPercentage, engine.thrustPercentage,
thrustvec, thrustvec,
engine.atmosphereCurve, engine.atmosphereCurve,
engine.atmChangeFlow, engine.atmChangeFlow,
engine.useAtmCurve ? engine.atmCurve : null, engine.useAtmCurve ? engine.atmCurve : null,
engine.useVelCurve ? engine.velCurve : null, engine.useVelCurve ? engine.velCurve : null,
engine.currentThrottle, engine.currentThrottle,
engine.g, engine.g,
engine.throttleLocked || fullThrust, engine.throttleLocked || fullThrust,
engine.propellants, engine.propellants,
engine.isOperational, engine.isOperational,
engine.resultingThrust, engine.resultingThrust,
engine.thrustTransforms); engine.thrustTransforms);
allEngines.Add(engineSim); allEngines.Add(engineSim);
} }
} }
} }
else else
{ {
if (hasModuleEngines) 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) if (log != null)
{ {
log.buf.AppendLine("Module: " + engine.moduleName); log.buf.AppendLine("Module: " + engine.moduleName);
} }
   
Vector3 thrustvec = this.CalculateThrustVector(vectoredThrust ? engine.thrustTransforms : null, log); Vector3 thrustvec = this.CalculateThrustVector(vectoredThrust ? engine.thrustTransforms : null, log);
   
EngineSim engineSim = EngineSim.New( EngineSim engineSim = EngineSim.New(
this, this,
atmosphere, atmosphere,
(float)mach, (float)mach,
engine.maxFuelFlow, engine.maxFuelFlow,
engine.minFuelFlow, engine.minFuelFlow,
engine.thrustPercentage, engine.thrustPercentage,
thrustvec, thrustvec,
engine.atmosphereCurve, engine.atmosphereCurve,
engine.atmChangeFlow, engine.atmChangeFlow,
engine.useAtmCurve ? engine.atmCurve : null, engine.useAtmCurve ? engine.atmCurve : null,
engine.useVelCurve ? engine.velCurve : null, engine.useVelCurve ? engine.velCurve : null,
engine.currentThrottle, engine.currentThrottle,
engine.g, engine.g,
engine.throttleLocked || fullThrust, engine.throttleLocked || fullThrust,
engine.propellants, engine.propellants,
engine.isOperational, engine.isOperational,
engine.resultingThrust, engine.resultingThrust,
engine.thrustTransforms); engine.thrustTransforms);
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(", 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);
   
foreach (int type in resources.Types) foreach (int type in resources.Types)
{ {
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 + " ";
foreach (PartSim partSim in allParts) foreach (PartSim partSim in allParts)
{ {
if (partSim.parent == this) if (partSim.parent == this)
{ {
partSim.DumpPartToBuffer(buffer, newPrefix, allParts); partSim.DumpPartToBuffer(buffer, newPrefix, allParts);
} }
} }
} }
} }
   
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_MIN) 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) public double GetMass(int currentStage)
{ {
double mass = baseMass; double mass = 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 (hasVessel == false && isFairing && inverseStage < currentStage) if (hasVessel == false && isFairing && inverseStage < currentStage)
{ {
mass = mass + moduleMass; mass = mass + moduleMass;
} }
   
return mass; return mass;
} }
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, List<PartSim> allParts, HashSet<PartSim> visited, HashSet<PartSim> allSources, LogMsg log, String indent) public void GetSourceSet(int type, 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) if (log != null)
{ {
log.buf.AppendLine(indent + "Returning empty set, already visited (" + name + ":" + partId + ")"); log.buf.AppendLine(indent + "Returning empty set, already visited (" + name + ":" + partId + ")");
} }
   
return; return;
} }
   
//if (log != null) //if (log != null)
// log.buf.AppendLine(indent + "Adding this to visited"); // log.buf.AppendLine(indent + "Adding this to visited");
   
visited.Add(this); visited.Add(this);
   
// Rule 2: Part performs scan on start of every fuel pipe ending in it. This scan is done in order in which pipes were installed. // Rule 2: Part performs scan on start of every fuel pipe ending in it. This scan is done in order in which pipes were installed.
// Then it makes an union of fuel tank sets each pipe scan returned. If the resulting list is not empty, it is returned as result. // Then it makes an union of fuel tank sets each pipe scan returned. If the resulting list is not empty, it is returned as result.
//MonoBehaviour.print("foreach fuel line"); //MonoBehaviour.print("foreach fuel line");
   
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 (visited.Contains(partSim)) if (visited.Contains(partSim))
{ {
//if (log != null) //if (log != null)
// log.buf.AppendLine(indent + "Fuel target already visited, skipping (" + partSim.name + ":" + partSim.partId + ")"); // log.buf.AppendLine(indent + "Fuel target already visited, skipping (" + partSim.name + ":" + partSim.partId + ")");
} }
else else
{ {
//if (log != null) //if (log != null)
// log.buf.AppendLine(indent + "Adding fuel target as source (" + partSim.name + ":" + partSim.partId + ")"); // log.buf.AppendLine(indent + "Adding fuel target as source (" + partSim.name + ":" + partSim.partId + ")");
   
partSim.GetSourceSet(type, allParts, visited, allSources, log, indent); partSim.GetSourceSet(type, allParts, visited, allSources, log, indent);
} }
} }
   
if (allSources.Count > lastCount) if (allSources.Count > lastCount)
{ {
if (log != null) if (log != null)
{ {
log.buf.AppendLine(indent + "Returning " + (allSources.Count - lastCount) + " fuel target sources (" + this.name + ":" + this.partId + ")"); 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("foreach attach node"); //MonoBehaviour.print("foreach 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 ( if (
!(this.noCrossFeedNodeKey != null && this.noCrossFeedNodeKey.Length > 0 && !(this.noCrossFeedNodeKey != null && this.noCrossFeedNodeKey.Length > 0 &&
attachSim.id.Contains(this.noCrossFeedNodeKey))) attachSim.id.Contains(this.noCrossFeedNodeKey)))
{ {
if (visited.Contains(attachSim.attachedPartSim)) if (visited.Contains(attachSim.attachedPartSim))
{ {
//if (log != null) //if (log != null)
// log.buf.AppendLine(indent + "Attached part already visited, skipping (" + attachSim.attachedPartSim.name + ":" + attachSim.attachedPartSim.partId + ")"); // log.buf.AppendLine(indent + "Attached part already visited, skipping (" + attachSim.attachedPartSim.name + ":" + attachSim.attachedPartSim.partId + ")");
} }
else else
{ {
//if (log != null) //if (log != null)
// log.buf.AppendLine(indent + "Adding attached part as source (" + attachSim.attachedPartSim.name + ":" + attachSim.attachedPartSim.partId + ")"); // log.buf.AppendLine(indent + "Adding attached part as source (" + attachSim.attachedPartSim.name + ":" + attachSim.attachedPartSim.partId + ")");
   
attachSim.attachedPartSim.GetSourceSet(type, allParts, visited, allSources, log, indent); attachSim.attachedPartSim.GetSourceSet(type, allParts, visited, allSources, log, indent);
} }
} }
} }
} }
} }
   
if (allSources.Count > lastCount) if (allSources.Count > lastCount)
{ {
if (log != null) if (log != null)
{ {
log.buf.AppendLine(indent + "Returning " + (allSources.Count - lastCount) + " attached sources (" + this.name + ":" + this.partId + ")"); 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) if (resources.HasType(type) && resourceFlowStates[type] != 0)
{ {
if (resources[type] > SimManager.RESOURCE_MIN) if (resources[type] > SimManager.RESOURCE_MIN)
{ {
allSources.Add(this); allSources.Add(this);
   
if (log != null) if (log != null)
{ {
log.buf.AppendLine(indent + "Returning enabled tank as only source (" + name + ":" + partId + ")"); log.buf.AppendLine(indent + "Returning enabled tank as only source (" + name + ":" + partId + ")");
} }
} }
   
return; return;
} }
   
// 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) //if (log != null)
// log.buf.AppendLine(indent + "Parent part already visited, skipping (" + parent.name + ":" + parent.partId + ")"); // log.buf.AppendLine(indent + "Parent part already visited, skipping (" + parent.name + ":" + parent.partId + ")");
} }
else else
{ {
lastCount = allSources.Count; lastCount = allSources.Count;
this.parent.GetSourceSet(type, allParts, visited, allSources, log, indent); this.parent.GetSourceSet(type, allParts, visited, allSources, log, indent);
if (allSources.Count > lastCount) if (allSources.Count > lastCount)
{ {
if (log != null) if (log != null)
{ {
log.buf.AppendLine(indent + "Returning " + (allSources.Count - lastCount) + " parent sources (" + this.name + ":" + this.partId + ")"); 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) //if (log != null)
// log.buf.AppendLine(indent + "Returning empty set, no sources found (" + name + ":" + partId + ")"); // log.buf.AppendLine(indent + "Returning empty set, no sources found (" + name + ":" + partId + ")");
   
return; 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;
} }
} }
} }
   
public void SetupAttachNodes(Dictionary<Part, PartSim> partSimLookup, LogMsg log) public void SetupAttachNodes(Dictionary<Part, PartSim> partSimLookup, LogMsg log)
{ {
if (log != null) if (log != null)
{ {
log.buf.AppendLine("SetupAttachNodes for " + name + ":" + partId + ""); 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) if (log != null)
{ {
log.buf.AppendLine("AttachNode " + attachNode.id + " = " + (attachNode.attachedPart != null ? attachNode.attachedPart.partInfo.name : "null")); log.buf.AppendLine("AttachNode " + attachNode.id + " = " + (attachNode.attachedPart != null ? attachNode.attachedPart.partInfo.name : "null"));
} }
   
if (attachNode.attachedPart != null && attachNode.id != "Strut") if (attachNode.attachedPart != null && attachNode.id != "Strut")
{ {
PartSim attachedSim; PartSim attachedSim;
if (partSimLookup.TryGetValue(attachNode.attachedPart, out attachedSim)) if (partSimLookup.TryGetValue(attachNode.attachedPart, out attachedSim))
{ {
if (log != null) if (log != null)
{ {
log.buf.AppendLine("Adding attached node " + attachedSim.name + ":" + attachedSim.partId + ""); log.buf.AppendLine("Adding attached node " + attachedSim.name + ":" + attachedSim.partId + "");
} }
   
attachNodes.Add(AttachNodeSim.New(attachedSim, attachNode.id, attachNode.nodeType)); attachNodes.Add(AttachNodeSim.New(attachedSim, attachNode.id, attachNode.nodeType));
} }
else else
{ {
if (log != null) if (log != null)
{ {
log.buf.AppendLine("No PartSim for attached part (" + attachNode.attachedPart.partInfo.name + ")"); log.buf.AppendLine("No PartSim for attached part (" + attachNode.attachedPart.partInfo.name + ")");
} }
} }
} }
} }
   
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) if (log != null)
{ {
log.buf.AppendLine("Fuel target: " + targetSim.name + ":" + targetSim.partId); log.buf.AppendLine("Fuel target: " + targetSim.name + ":" + targetSim.partId);
} }
   
fuelTargets.Add(targetSim); fuelTargets.Add(targetSim);
} }
else else
{ {
if (log != null) if (log != null)
{ {
log.buf.AppendLine("No PartSim for fuel target (" + p.name + ")"); log.buf.AppendLine("No PartSim for fuel target (" + p.name + ")");
} }
} }
} }
} }
} }
   
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) if (log != null)
{ {
log.buf.AppendLine("Parent part is " + parent.name + ":" + parent.partId); log.buf.AppendLine("Parent part is " + parent.name + ":" + parent.partId);
} }
} }
else else
{ {
if (log != null) if (log != null)
{ {
log.buf.AppendLine("No PartSim for parent part (" + part.parent.partInfo.name + ")"); 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) if (log != null)
{ {
log.buf.AppendFormat("Transform = ({0:g6}, {1:g6}, {2:g6}) length = {3:g6}\n", trans.forward.x, trans.forward.y, trans.forward.z, trans.forward.magnitude); log.buf.AppendFormat("Transform = ({0:g6}, {1:g6}, {2:g6}) length = {3:g6}\n", trans.forward.x, trans.forward.y, trans.forward.z, trans.forward.magnitude);
} }
   
thrustvec -= trans.forward; thrustvec -= trans.forward;
} }
   
if (log != null) if (log != null)
{ {
log.buf.AppendFormat("ThrustVec = ({0:g6}, {1:g6}, {2:g6}) length = {3:g6}\n", thrustvec.x, thrustvec.y, thrustvec.z, thrustvec.magnitude); log.buf.AppendFormat("ThrustVec = ({0:g6}, {1:g6}, {2:g6}) length = {3:g6}\n", thrustvec.x, thrustvec.y, thrustvec.z, thrustvec.magnitude);
} }
   
thrustvec.Normalize(); thrustvec.Normalize();
   
if (log != null) if (log != null)
{ {
log.buf.AppendFormat("ThrustVecN = ({0:g6}, {1:g6}, {2:g6}) length = {3:g6}\n", thrustvec.x, thrustvec.y, thrustvec.z, thrustvec.magnitude); log.buf.AppendFormat("ThrustVecN = ({0:g6}, {1:g6}, {2:g6}) length = {3:g6}\n", thrustvec.x, thrustvec.y, thrustvec.z, thrustvec.magnitude);
} }
   
return thrustvec; return thrustvec;
} }
   
private int DecoupledInStage(Part thePart, int stage = -1) private int DecoupledInStage(Part thePart, int stage = -1)
{ {
if (IsDecoupler(thePart)) if (IsDecoupler(thePart))
{ {
if (thePart.inverseStage > stage) if (thePart.inverseStage > stage)
{ {
stage = thePart.inverseStage; stage = thePart.inverseStage;
} }
} }
   
if (thePart.parent != null) if (thePart.parent != null)
{ {
stage = 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.HasModule<ModuleDecouple>() || return thePart.HasModule<ModuleDecouple>() ||
thePart.HasModule<ModuleAnchoredDecoupler>(); thePart.HasModule<ModuleAnchoredDecoupler>();
} }
   
private bool IsFairing(Part thePart) private bool IsFairing(Part thePart)
{ {
return thePart.HasModule<ModuleProceduralFairing>(); return thePart.HasModule<ModuleProceduralFairing>();
} }
   
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;
} }
} }
} }
// //
// Kerbal Engineer Redux // Kerbal Engineer Redux
// //
// Copyright (C) 2014 CYBUTEK // Copyright (C) 2014 CYBUTEK
// //
// This program is free software: you can redistribute it and/or modify // This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by // it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or // the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version. // (at your option) any later version.
// //
// This program is distributed in the hope that it will be useful, // This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details. // GNU General Public License for more details.
// //
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>. // along with this program. If not, see <http://www.gnu.org/licenses/>.
// //
   
namespace KerbalEngineer.VesselSimulator namespace KerbalEngineer.VesselSimulator
{ {
#region Using Directives #region Using Directives
   
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Reflection; using System.Reflection;
using System.Threading; using System.Threading;
using UnityEngine; using UnityEngine;
   
#endregion #endregion
   
public class SimManager public class SimManager
{ {
#region Constants #region Constants
   
public const double RESOURCE_MIN = 0.0001; public const double RESOURCE_MIN = 0.0001;
  public const double RESOURCE_PART_EMPTY_THRESH = 0.01;
   
#endregion #endregion
   
#region Fields #region Fields
   
public static bool dumpTree = false; public static bool dumpTree = false;
public static bool logOutput = false; public static bool logOutput = false;
public static TimeSpan minSimTime = new TimeSpan(0, 0, 0, 0, 150); public static TimeSpan minSimTime = new TimeSpan(0, 0, 0, 0, 150);
public static bool vectoredThrust = false; public static bool vectoredThrust = false;
private static readonly object locker = new object(); private static readonly object locker = new object();
private static readonly Stopwatch timer = new Stopwatch(); private static readonly Stopwatch timer = new Stopwatch();
   
private static bool bRequested; private static bool bRequested;
private static bool bRunning; private static bool bRunning;
private static TimeSpan delayBetweenSims; private static TimeSpan delayBetweenSims;
   
// Support for RealFuels using reflection to check localCorrectThrust without dependency // Support for RealFuels using reflection to check localCorrectThrust without dependency
   
private static bool hasCheckedForMods; private static bool hasCheckedForMods;
private static bool hasInstalledRealFuels; private static bool hasInstalledRealFuels;
private static FieldInfo RF_ModuleEngineConfigs_localCorrectThrust; private static FieldInfo RF_ModuleEngineConfigs_localCorrectThrust;
private static FieldInfo RF_ModuleHybridEngine_localCorrectThrust; private static FieldInfo RF_ModuleHybridEngine_localCorrectThrust;
private static FieldInfo RF_ModuleHybridEngines_localCorrectThrust; private static FieldInfo RF_ModuleHybridEngines_localCorrectThrust;
private static bool hasInstalledKIDS; private static bool hasInstalledKIDS;
private static MethodInfo KIDS_Utils_GetIspMultiplier; private static MethodInfo KIDS_Utils_GetIspMultiplier;
private static bool bKIDSThrustISP = false; private static bool bKIDSThrustISP = false;
private static List<Part> parts = new List<Part>(); private static List<Part> parts = new List<Part>();
   
private static Simulation simulation = new Simulation(); private static Simulation simulation = new Simulation();
#endregion #endregion
   
#region Delegates #region Delegates
   
public delegate void ReadyEvent(); public delegate void ReadyEvent();
   
#endregion #endregion
   
#region Events #region Events
   
public static event ReadyEvent OnReady; public static event ReadyEvent OnReady;
   
#endregion #endregion
   
#region Properties #region Properties
   
public static double Atmosphere { get; set; } public static double Atmosphere { get; set; }
   
public static double Gravity { get; set; } public static double Gravity { get; set; }
   
public static Stage LastStage { get; private set; } public static Stage LastStage { get; private set; }
   
public static Stage[] Stages { get; private set; } public static Stage[] Stages { get; private set; }
   
public static double Mach { get; set; } public static double Mach { get; set; }
   
public static String failMessage { get; private set; } public static String failMessage { get; private set; }
   
#endregion #endregion
   
#region Methods #region Methods
   
private static void CheckForMods() private static void CheckForMods()
{ {
hasCheckedForMods = true; hasCheckedForMods = true;
   
foreach (var assembly in AssemblyLoader.loadedAssemblies) foreach (var assembly in AssemblyLoader.loadedAssemblies)
{ {
MonoBehaviour.print("Assembly:" + assembly.assembly); MonoBehaviour.print("Assembly:" + assembly.assembly);
   
var name = assembly.assembly.ToString().Split(',')[0]; var name = assembly.assembly.ToString().Split(',')[0];
   
if (name == "RealFuels") if (name == "RealFuels")
{ {
MonoBehaviour.print("Found RealFuels mod"); MonoBehaviour.print("Found RealFuels mod");
   
var RF_ModuleEngineConfigs_Type = assembly.assembly.GetType("RealFuels.ModuleEngineConfigs"); var RF_ModuleEngineConfigs_Type = assembly.assembly.GetType("RealFuels.ModuleEngineConfigs");
if (RF_ModuleEngineConfigs_Type != null) if (RF_ModuleEngineConfigs_Type != null)
{ {
RF_ModuleEngineConfigs_localCorrectThrust = RF_ModuleEngineConfigs_Type.GetField("localCorrectThrust"); RF_ModuleEngineConfigs_localCorrectThrust = RF_ModuleEngineConfigs_Type.GetField("localCorrectThrust");
} }
   
var RF_ModuleHybridEngine_Type = assembly.assembly.GetType("RealFuels.ModuleHybridEngine"); var RF_ModuleHybridEngine_Type = assembly.assembly.GetType("RealFuels.ModuleHybridEngine");
if (RF_ModuleHybridEngine_Type != null) if (RF_ModuleHybridEngine_Type != null)
{ {
RF_ModuleHybridEngine_localCorrectThrust = RF_ModuleHybridEngine_Type.GetField("localCorrectThrust"); RF_ModuleHybridEngine_localCorrectThrust = RF_ModuleHybridEngine_Type.GetField("localCorrectThrust");
} }
   
var RF_ModuleHybridEngines_Type = assembly.assembly.GetType("RealFuels.ModuleHybridEngines"); var RF_ModuleHybridEngines_Type = assembly.assembly.GetType("RealFuels.ModuleHybridEngines");
if (RF_ModuleHybridEngines_Type != null) if (RF_ModuleHybridEngines_Type != null)
{ {
RF_ModuleHybridEngines_localCorrectThrust = RF_ModuleHybridEngines_Type.GetField("localCorrectThrust"); RF_ModuleHybridEngines_localCorrectThrust = RF_ModuleHybridEngines_Type.GetField("localCorrectThrust");
} }
   
hasInstalledRealFuels = true; hasInstalledRealFuels = true;
break; break;
} }
else if (name == "KerbalIspDifficultyScaler") else if (name == "KerbalIspDifficultyScaler")
{ {
var KIDS_Utils_Type = assembly.assembly.GetType("KerbalIspDifficultyScaler.KerbalIspDifficultyScalerUtils"); var KIDS_Utils_Type = assembly.assembly.GetType("KerbalIspDifficultyScaler.KerbalIspDifficultyScalerUtils");
if (KIDS_Utils_Type != null) if (KIDS_Utils_Type != null)
{ {
KIDS_Utils_GetIspMultiplier = KIDS_Utils_Type.GetMethod("GetIspMultiplier"); KIDS_Utils_GetIspMultiplier = KIDS_Utils_Type.GetMethod("GetIspMultiplier");
} }
   
hasInstalledKIDS = true; hasInstalledKIDS = true;
} }
} }
} }
   
public static bool DoesEngineUseCorrectedThrust(Part theEngine) public static bool DoesEngineUseCorrectedThrust(Part theEngine)
{ {
if (hasInstalledRealFuels) if (hasInstalledRealFuels)
{ {
// Look for any of the Real Fuels engine modules and call the relevant method to find out // Look for any of the Real Fuels engine modules and call the relevant method to find out
if (RF_ModuleEngineConfigs_localCorrectThrust != null && theEngine.Modules.Contains("ModuleEngineConfigs")) if (RF_ModuleEngineConfigs_localCorrectThrust != null && theEngine.Modules.Contains("ModuleEngineConfigs"))
{ {
var modEngineConfigs = theEngine.Modules["ModuleEngineConfigs"]; var modEngineConfigs = theEngine.Modules["ModuleEngineConfigs"];
if (modEngineConfigs != null) if (modEngineConfigs != null)
{ {
// Return the localCorrectThrust // Return the localCorrectThrust
return (bool)RF_ModuleEngineConfigs_localCorrectThrust.GetValue(modEngineConfigs); return (bool)RF_ModuleEngineConfigs_localCorrectThrust.GetValue(modEngineConfigs);
} }
} }
   
if (RF_ModuleHybridEngine_localCorrectThrust != null && theEngine.Modules.Contains("ModuleHybridEngine")) if (RF_ModuleHybridEngine_localCorrectThrust != null && theEngine.Modules.Contains("ModuleHybridEngine"))
{ {
var modHybridEngine = theEngine.Modules["ModuleHybridEngine"]; var modHybridEngine = theEngine.Modules["ModuleHybridEngine"];
if (modHybridEngine != null) if (modHybridEngine != null)
{ {
// Return the localCorrectThrust // Return the localCorrectThrust
return (bool)RF_ModuleHybridEngine_localCorrectThrust.GetValue(modHybridEngine); return (bool)RF_ModuleHybridEngine_localCorrectThrust.GetValue(modHybridEngine);
} }
} }
   
if (RF_ModuleHybridEngines_localCorrectThrust != null && theEngine.Modules.Contains("ModuleHybridEngines")) if (RF_ModuleHybridEngines_localCorrectThrust != null && theEngine.Modules.Contains("ModuleHybridEngines"))
{ {
var modHybridEngines = theEngine.Modules["ModuleHybridEngines"]; var modHybridEngines = theEngine.Modules["ModuleHybridEngines"];
if (modHybridEngines != null) if (modHybridEngines != null)
{ {
// Return the localCorrectThrust // Return the localCorrectThrust
return (bool)RF_ModuleHybridEngines_localCorrectThrust.GetValue(modHybridEngines); return (bool)RF_ModuleHybridEngines_localCorrectThrust.GetValue(modHybridEngines);
} }
} }
} }
   
if (hasInstalledKIDS && HighLogic.LoadedSceneIsEditor) if (hasInstalledKIDS && HighLogic.LoadedSceneIsEditor)
{ {
return bKIDSThrustISP; return bKIDSThrustISP;
} }
   
return false; return false;
} }
   
public static void UpdateModSettings() public static void UpdateModSettings()
{ {
if (!hasCheckedForMods) if (!hasCheckedForMods)
{ {
CheckForMods(); CheckForMods();
} }
   
if (hasInstalledKIDS) if (hasInstalledKIDS)
{ {
// (out ispMultiplierVac, out ispMultiplierAtm, out extendToZeroIsp, out thrustCorrection, out ispCutoff, out thrustCutoff); // (out ispMultiplierVac, out ispMultiplierAtm, out extendToZeroIsp, out thrustCorrection, out ispCutoff, out thrustCutoff);
object[] parameters = new object[6]; object[] parameters = new object[6];
KIDS_Utils_GetIspMultiplier.Invoke(null, parameters); KIDS_Utils_GetIspMultiplier.Invoke(null, parameters);
bKIDSThrustISP = (bool)parameters[3]; bKIDSThrustISP = (bool)parameters[3];
} }
} }
   
public static String GetVesselTypeString(VesselType vesselType) public static String GetVesselTypeString(VesselType vesselType)
{ {
switch (vesselType) switch (vesselType)
{ {
case VesselType.Debris: case VesselType.Debris:
return "Debris"; return "Debris";
case VesselType.SpaceObject: case VesselType.SpaceObject:
return "SpaceObject"; return "SpaceObject";
case VesselType.Unknown: case VesselType.Unknown:
return "Unknown"; return "Unknown";
case VesselType.Probe: case VesselType.Probe:
return "Probe"; return "Probe";
case VesselType.Rover: case VesselType.Rover:
return "Rover"; return "Rover";
case VesselType.Lander: case VesselType.Lander:
return "Lander"; return "Lander";
case VesselType.Ship: case VesselType.Ship:
return "Ship"; return "Ship";
case VesselType.Station: case VesselType.Station:
return "Station"; return "Station";
case VesselType.Base: case VesselType.Base:
return "Base"; return "Base";
case VesselType.EVA: case VesselType.EVA:
return "EVA"; return "EVA";
case VesselType.Flag: case VesselType.Flag:
return "Flag"; return "Flag";
} }
return "Undefined"; return "Undefined";
} }
   
public static void RequestSimulation() public static void RequestSimulation()
{ {
if (!hasCheckedForMods) if (!hasCheckedForMods)
{ {
CheckForMods(); CheckForMods();
} }
   
lock (locker) lock (locker)
{ {
bRequested = true; bRequested = true;
if (!timer.IsRunning) if (!timer.IsRunning)
{ {
timer.Start(); timer.Start();
} }
} }
} }
   
public static bool ResultsReady() public static bool ResultsReady()
{ {
lock (locker) lock (locker)
{ {
return !bRunning; return !bRunning;
} }
} }
   
public static void TryStartSimulation() public static void TryStartSimulation()
{ {
lock (locker) lock (locker)
{ {
if (!bRequested || bRunning || (timer.Elapsed < delayBetweenSims && timer.Elapsed >= TimeSpan.Zero) || (!HighLogic.LoadedSceneIsEditor && FlightGlobals.ActiveVessel == null)) if (!bRequested || bRunning || (timer.Elapsed < delayBetweenSims && timer.Elapsed >= TimeSpan.Zero) || (!HighLogic.LoadedSceneIsEditor && FlightGlobals.ActiveVessel == null))
{ {
return; return;
} }
   
bRequested = false; bRequested = false;
timer.Reset(); timer.Reset();
} }
   
StartSimulation(); StartSimulation();
} }
   
private static void ClearResults() private static void ClearResults()
{ {
failMessage = ""; failMessage = "";
Stages = null; Stages = null;
LastStage = null; LastStage = null;
} }
   
private static void RunSimulation(object simObject) private static void RunSimulation(object simObject)
{ {
try try
{ {
Stages = (simObject as Simulation).RunSimulation(); Stages = (simObject as Simulation).RunSimulation();
   
if (Stages != null && Stages.Length > 0) if (Stages != null && Stages.Length > 0)
{ {
if (logOutput) if (logOutput)
{ {
foreach (var stage in Stages) foreach (var stage in Stages)
{ {
stage.Dump(); stage.Dump();
} }
} }
LastStage = Stages[Stages.Length - 1]; LastStage = Stages[Stages.Length - 1];
} }
} }
catch (Exception e) catch (Exception e)
{ {
Logger.Exception(e, "SimManager.RunSimulation()"); Logger.Exception(e, "SimManager.RunSimulation()");
Stages = null; Stages = null;
LastStage = null; LastStage = null;
failMessage = e.ToString(); failMessage = e.ToString();
} }
lock (locker) lock (locker)
{ {
timer.Stop(); timer.Stop();
#if TIMERS #if TIMERS
MonoBehaviour.print("Total simulation time: " + timer.ElapsedMilliseconds + "ms"); MonoBehaviour.print("Total simulation time: " + timer.ElapsedMilliseconds + "ms");
#else #else
if (logOutput) if (logOutput)
{ {
MonoBehaviour.print("Total simulation time: " + timer.ElapsedMilliseconds + "ms"); MonoBehaviour.print("Total simulation time: " + timer.ElapsedMilliseconds + "ms");
} }
#endif #endif
   
delayBetweenSims = minSimTime - timer.Elapsed; delayBetweenSims = minSimTime - timer.Elapsed;
if (delayBetweenSims < TimeSpan.Zero) if (delayBetweenSims < TimeSpan.Zero)
{ {
delayBetweenSims = TimeSpan.Zero; delayBetweenSims = TimeSpan.Zero;
} }
   
timer.Reset(); timer.Reset();
timer.Start(); timer.Start();
   
bRunning = false; bRunning = false;
if (OnReady != null) if (OnReady != null)
{ {
OnReady(); OnReady();
} }
} }
   
logOutput = false; logOutput = false;
} }
   
private static void StartSimulation() private static void StartSimulation()
{ {
try try
{ {
lock (locker) lock (locker)
{ {
bRunning = true; bRunning = true;
} }
   
ClearResults(); ClearResults();
   
lock (locker) lock (locker)
{ {
timer.Start(); timer.Start();
} }
   
if (HighLogic.LoadedSceneIsEditor) if (HighLogic.LoadedSceneIsEditor)
{ {
parts = EditorLogic.fetch.ship.parts; parts = EditorLogic.fetch.ship.parts;
} }
else else
{ {
parts = FlightGlobals.ActiveVessel.Parts; parts = FlightGlobals.ActiveVessel.Parts;
Atmosphere = FlightGlobals.ActiveVessel.staticPressurekPa * PhysicsGlobals.KpaToAtmospheres; Atmosphere = FlightGlobals.ActiveVessel.staticPressurekPa * PhysicsGlobals.KpaToAtmospheres;
} }
   
// This call doesn't ever fail at the moment but we'll check and return a sensible error for display // This call doesn't ever fail at the moment but we'll check and return a sensible error for display
if (simulation.PrepareSimulation(parts, Gravity, Atmosphere, Mach, dumpTree, vectoredThrust)) if (simulation.PrepareSimulation(parts, Gravity, Atmosphere, Mach, dumpTree, vectoredThrust))
{ {
ThreadPool.QueueUserWorkItem(RunSimulation, simulation); ThreadPool.QueueUserWorkItem(RunSimulation, simulation);
//RunSimulation(simulation); //RunSimulation(simulation);
} }
else else
{ {
failMessage = "PrepareSimulation failed"; failMessage = "PrepareSimulation failed";
lock (locker) lock (locker)
{ {
bRunning = false; bRunning = false;
} }
logOutput = false; logOutput = false;
} }
} }
catch (Exception e) catch (Exception e)
{ {
Logger.Exception(e, "SimManager.StartSimulation()"); Logger.Exception(e, "SimManager.StartSimulation()");
failMessage = e.ToString(); failMessage = e.ToString();
lock (locker) lock (locker)
{ {
bRunning = false; bRunning = false;
} }
logOutput = false; logOutput = false;
} }
dumpTree = false; dumpTree = false;
} }
   
#endregion #endregion
} }
} }