Thrust calculated with minThrust
[VesselSimulator.git] / KerbalEngineer / VesselSimulator / PartSim.cs
blob:a/KerbalEngineer/VesselSimulator/PartSim.cs -> blob:b/KerbalEngineer/VesselSimulator/PartSim.cs
// Kerbal Engineer Redux // Kerbal Engineer Redux
// Author: CYBUTEK // Author: CYBUTEK
// License: Attribution-NonCommercial-ShareAlike 3.0 Unported // License: Attribution-NonCommercial-ShareAlike 3.0 Unported
// //
// This class has taken a lot of inspiration from r4m0n's MuMech FuelFlowSimulator. Although extremely // This class has taken a lot of inspiration from r4m0n's MuMech FuelFlowSimulator. Although extremely
// similar to the code used within MechJeb, it is a clean re-write. The similarities are a testiment // similar to the code used within MechJeb, it is a clean re-write. The similarities are a testiment
// to how well the MuMech code works and the robustness of the simulation algorithem used. // to how well the MuMech code works and the robustness of the simulation algorithem used.
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using KerbalEngineer.Extensions; using KerbalEngineer.Extensions;
using UnityEngine; using UnityEngine;
namespace KerbalEngineer.VesselSimulator namespace KerbalEngineer.VesselSimulator
{ {
public class PartSim public class PartSim
{ {
public ResourceContainer resources = new ResourceContainer(); public ResourceContainer resources = new ResourceContainer();
public ResourceContainer resourceDrains = new ResourceContainer(); public ResourceContainer resourceDrains = new ResourceContainer();
public ResourceContainer resourceFlowStates = new ResourceContainer(); public ResourceContainer resourceFlowStates = new ResourceContainer();
   
List<AttachNodeSim> attachNodes = new List<AttachNodeSim>(); List<AttachNodeSim> attachNodes = new List<AttachNodeSim>();
public List<PartSim> fuelTargets = new List<PartSim>(); public List<PartSim> fuelTargets = new List<PartSim>();
   
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 String name; public String name;
public PartSim parent; public PartSim parent;
public bool hasVessel; public bool hasVessel;
public String vesselName; public String vesselName;
public VesselType vesselType; public VesselType vesselType;
public String initialVesselName; public String initialVesselName;
public bool isLanded; public bool isLanded;
public bool isDecoupler; public bool isDecoupler;
public int decoupledInStage; public int decoupledInStage;
public int inverseStage; public int inverseStage;
public float cost; public float cost;
public double baseMass = 0d; public double baseMass = 0d;
public double startMass = 0d; public double startMass = 0d;
public String noCrossFeedNodeKey; public String noCrossFeedNodeKey;
public bool fuelCrossFeed; public bool fuelCrossFeed;
public bool isEngine; public bool isEngine;
public bool isFuelLine; public bool isFuelLine;
public bool isFuelTank; public bool isFuelTank;
public bool isSepratron; public bool isSepratron;
public bool hasMultiModeEngine; public bool hasMultiModeEngine;
public bool hasModuleEnginesFX; public bool hasModuleEnginesFX;
public bool hasModuleEngines; public bool hasModuleEngines;
public bool isNoPhysics; public bool isNoPhysics;
public bool localCorrectThrust; public bool localCorrectThrust;
   
public PartSim(Part thePart, int id, double atmosphere, LogMsg log) public PartSim(Part thePart, int id, double atmosphere, LogMsg log)
{ {
this.part = thePart; this.part = thePart;
this.partId = id; this.partId = id;
this.name = this.part.partInfo.name; this.name = this.part.partInfo.name;
   
if (log != null) if (log != null)
log.buf.AppendLine("Create PartSim for " + this.name); log.buf.AppendLine("Create PartSim for " + this.name);
   
this.parent = null; this.parent = null;
this.fuelCrossFeed = this.part.fuelCrossFeed; this.fuelCrossFeed = this.part.fuelCrossFeed;
this.noCrossFeedNodeKey = this.part.NoCrossFeedNodeKey; this.noCrossFeedNodeKey = this.part.NoCrossFeedNodeKey;
this.decoupledInStage = this.DecoupledInStage(this.part); this.decoupledInStage = this.DecoupledInStage(this.part);
this.isFuelLine = this.part is FuelLine; this.isFuelLine = this.part is FuelLine;
this.isFuelTank = this.part is FuelTank; this.isFuelTank = this.part is FuelTank;
this.isSepratron = this.IsSepratron(); this.isSepratron = this.IsSepratron();
this.inverseStage = this.part.inverseStage; this.inverseStage = this.part.inverseStage;
//MonoBehaviour.print("inverseStage = " + inverseStage); //MonoBehaviour.print("inverseStage = " + inverseStage);
   
this.cost = this.part.partInfo.cost; this.cost = this.part.partInfo.cost;
foreach (PartResource resource in this.part.Resources) foreach (PartResource resource in this.part.Resources)
{ {
this.cost -= (float)((resource.maxAmount - resource.amount) * resource.info.unitCost); this.cost -= (float)((resource.maxAmount - resource.amount) * resource.info.unitCost);
} }
   
// Work out if the part should have no physical significance // Work out if the part should have no physical significance
this.isNoPhysics = this.part.HasModule<ModuleLandingGear>() || this.isNoPhysics = this.part.HasModule<ModuleLandingGear>() ||
this.part.HasModule<LaunchClamp>() || this.part.HasModule<LaunchClamp>() ||
this.part.physicalSignificance == Part.PhysicalSignificance.NONE || this.part.physicalSignificance == Part.PhysicalSignificance.NONE ||
this.part.PhysicsSignificance == 1; this.part.PhysicsSignificance == 1;
   
if (!this.isNoPhysics) if (!this.isNoPhysics)
this.baseMass = this.part.mass; this.baseMass = this.part.mass;
   
if (SimManager.logOutput) if (SimManager.logOutput)
MonoBehaviour.print((this.isNoPhysics ? "Ignoring" : "Using") + " part.mass of " + this.part.mass); MonoBehaviour.print((this.isNoPhysics ? "Ignoring" : "Using") + " part.mass of " + this.part.mass);
   
foreach (PartResource resource in this.part.Resources) foreach (PartResource resource in this.part.Resources)
{ {
// Make sure it isn't NaN as this messes up the part mass and hence most of the values // Make sure it isn't NaN as this messes up the part mass and hence most of the values
// This can happen if a resource capacity is 0 and tweakable // This can happen if a resource capacity is 0 and tweakable
if (!Double.IsNaN(resource.amount)) if (!Double.IsNaN(resource.amount))
{ {
if (SimManager.logOutput) if (SimManager.logOutput)
MonoBehaviour.print(resource.resourceName + " = " + resource.amount); MonoBehaviour.print(resource.resourceName + " = " + resource.amount);
   
this.resources.Add(resource.info.id, resource.amount); this.resources.Add(resource.info.id, resource.amount);
this.resourceFlowStates.Add(resource.info.id, resource.flowState ? 1 : 0); this.resourceFlowStates.Add(resource.info.id, resource.flowState ? 1 : 0);
} }
else else
{ {
MonoBehaviour.print(resource.resourceName + " is NaN. Skipping."); MonoBehaviour.print(resource.resourceName + " is NaN. Skipping.");
} }
} }
   
this.startMass = this.GetMass(); this.startMass = this.GetMass();
   
this.hasVessel = (this.part.vessel != null); this.hasVessel = (this.part.vessel != null);
this.isLanded = this.hasVessel && this.part.vessel.Landed; this.isLanded = this.hasVessel && this.part.vessel.Landed;
if (this.hasVessel) if (this.hasVessel)
{ {
this.vesselName = this.part.vessel.vesselName; this.vesselName = this.part.vessel.vesselName;
this.vesselType = this.part.vesselType; this.vesselType = this.part.vesselType;
} }
this.initialVesselName = this.part.initialVesselName; this.initialVesselName = this.part.initialVesselName;
   
this.hasMultiModeEngine = this.part.HasModule<MultiModeEngine>(); this.hasMultiModeEngine = this.part.HasModule<MultiModeEngine>();
this.hasModuleEnginesFX = this.part.HasModule<ModuleEnginesFX>(); this.hasModuleEnginesFX = this.part.HasModule<ModuleEnginesFX>();
this.hasModuleEngines = this.part.HasModule<ModuleEngines>(); this.hasModuleEngines = this.part.HasModule<ModuleEngines>();
   
this.isEngine = this.hasMultiModeEngine || this.hasModuleEnginesFX || this.hasModuleEngines; this.isEngine = this.hasMultiModeEngine || this.hasModuleEnginesFX || this.hasModuleEngines;
   
if (SimManager.logOutput) if (SimManager.logOutput)
MonoBehaviour.print("Created " + this.name + ". Decoupled in stage " + this.decoupledInStage); MonoBehaviour.print("Created " + this.name + ". Decoupled in stage " + this.decoupledInStage);
} }
   
public void CreateEngineSims(List<EngineSim> allEngines, double atmosphere, double velocity, bool vectoredThrust, LogMsg log) public void CreateEngineSims(List<EngineSim> allEngines, double atmosphere, double velocity, bool vectoredThrust, LogMsg log)
{ {
bool correctThrust = SimManager.DoesEngineUseCorrectedThrust(this.part); bool correctThrust = SimManager.DoesEngineUseCorrectedThrust(this.part);
if (log != null) if (log != null)
{ {
log.buf.AppendLine("CreateEngineSims for " + this.name); log.buf.AppendLine("CreateEngineSims for " + this.name);
   
foreach (PartModule partMod in this.part.Modules) foreach (PartModule partMod in this.part.Modules)
{ {
log.buf.AppendLine("Module: " + partMod.moduleName); log.buf.AppendLine("Module: " + partMod.moduleName);
} }
   
log.buf.AppendLine("correctThrust = " + correctThrust); log.buf.AppendLine("correctThrust = " + correctThrust);
} }
   
if (this.hasMultiModeEngine) if (this.hasMultiModeEngine)
{ {
// A multi-mode engine has multiple ModuleEnginesFX but only one is active at any point // A multi-mode engine has multiple ModuleEnginesFX but only one is active at any point
// The mode of the engine is the engineID of the ModuleEnginesFX that is active // The mode of the engine is the engineID of the ModuleEnginesFX that is active
string mode = this.part.GetModule<MultiModeEngine>().mode; string mode = this.part.GetModule<MultiModeEngine>().mode;
   
foreach (ModuleEnginesFX engine in this.part.GetModules<ModuleEnginesFX>()) foreach (ModuleEnginesFX engine in this.part.GetModules<ModuleEnginesFX>())
{ {
if (engine.engineID == mode) if (engine.engineID == mode)
{ {
if (log != null) if (log != null)
log.buf.AppendLine("Module: " + engine.moduleName); log.buf.AppendLine("Module: " + engine.moduleName);
   
Vector3 thrustvec = this.CalculateThrustVector(vectoredThrust ? engine.thrustTransforms : null, log); Vector3 thrustvec = this.CalculateThrustVector(vectoredThrust ? engine.thrustTransforms : null, log);
   
EngineSim engineSim = new EngineSim(this, EngineSim engineSim = new EngineSim(this,
atmosphere, atmosphere,
velocity, velocity,
engine.maxThrust, engine.maxThrust,
  engine.minThrust,
engine.thrustPercentage, engine.thrustPercentage,
engine.requestedThrust, engine.requestedThrust,
thrustvec, thrustvec,
engine.realIsp, engine.realIsp,
engine.atmosphereCurve, engine.atmosphereCurve,
engine.useVelocityCurve ? engine.velocityCurve : null, engine.useVelocityCurve ? engine.velocityCurve : null,
engine.throttleLocked, engine.throttleLocked,
engine.propellants, engine.propellants,
engine.isOperational, engine.isOperational,
correctThrust); correctThrust);
allEngines.Add(engineSim); allEngines.Add(engineSim);
} }
} }
} }
else else
{ {
if (this.hasModuleEnginesFX) if (this.hasModuleEnginesFX)
{ {
foreach (ModuleEnginesFX engine in this.part.GetModules<ModuleEnginesFX>()) foreach (ModuleEnginesFX engine in this.part.GetModules<ModuleEnginesFX>())
{ {
if (log != null) if (log != null)
log.buf.AppendLine("Module: " + engine.moduleName); log.buf.AppendLine("Module: " + engine.moduleName);
   
Vector3 thrustvec = this.CalculateThrustVector(vectoredThrust ? engine.thrustTransforms : null, log); Vector3 thrustvec = this.CalculateThrustVector(vectoredThrust ? engine.thrustTransforms : null, log);
EngineSim engineSim = new EngineSim(this, EngineSim engineSim = new EngineSim(this,
atmosphere, atmosphere,
velocity, velocity,
engine.maxThrust, engine.maxThrust,
  engine.minThrust,
engine.thrustPercentage, engine.thrustPercentage,
engine.requestedThrust, engine.requestedThrust,
thrustvec, thrustvec,
engine.realIsp, engine.realIsp,
engine.atmosphereCurve, engine.atmosphereCurve,
engine.useVelocityCurve ? engine.velocityCurve : null, engine.useVelocityCurve ? engine.velocityCurve : null,
engine.throttleLocked, engine.throttleLocked,
engine.propellants, engine.propellants,
engine.isOperational, engine.isOperational,
correctThrust); correctThrust);
allEngines.Add(engineSim); allEngines.Add(engineSim);
} }
} }
   
if (this.hasModuleEngines) if (this.hasModuleEngines)
{ {
foreach (ModuleEngines engine in this.part.GetModules<ModuleEngines>()) foreach (ModuleEngines engine in this.part.GetModules<ModuleEngines>())
{ {
if (log != null) if (log != null)
log.buf.AppendLine("Module: " + engine.moduleName); log.buf.AppendLine("Module: " + engine.moduleName);
   
Vector3 thrustvec = this.CalculateThrustVector(vectoredThrust ? engine.thrustTransforms : null, log); Vector3 thrustvec = this.CalculateThrustVector(vectoredThrust ? engine.thrustTransforms : null, log);
   
EngineSim engineSim = new EngineSim(this, EngineSim engineSim = new EngineSim(this,
atmosphere, atmosphere,
velocity, velocity,
engine.maxThrust, engine.maxThrust,
  engine.minThrust,
engine.thrustPercentage, engine.thrustPercentage,
engine.requestedThrust, engine.requestedThrust,
thrustvec, thrustvec,
engine.realIsp, engine.realIsp,
engine.atmosphereCurve, engine.atmosphereCurve,
engine.useVelocityCurve ? engine.velocityCurve : null, engine.useVelocityCurve ? engine.velocityCurve : null,
engine.throttleLocked, engine.throttleLocked,
engine.propellants, engine.propellants,
engine.isOperational, engine.isOperational,
correctThrust); correctThrust);
allEngines.Add(engineSim); allEngines.Add(engineSim);
} }
} }
} }
   
if (log != null) if (log != null)
log.Flush(); log.Flush();
} }
   
private Vector3 CalculateThrustVector(List<Transform> thrustTransforms, LogMsg log) private Vector3 CalculateThrustVector(List<Transform> thrustTransforms, LogMsg log)
{ {
if (thrustTransforms == null) if (thrustTransforms == null)
return Vector3.forward; return Vector3.forward;
Vector3 thrustvec = Vector3.zero; Vector3 thrustvec = Vector3.zero;
foreach (Transform trans in thrustTransforms) foreach (Transform trans in thrustTransforms)
{ {
if (log != null) if (log != null)
log.buf.AppendFormat("Transform = ({0:g6}, {1:g6}, {2:g6}) length = {3:g6}\n", trans.forward.x, trans.forward.y, trans.forward.z, trans.forward.magnitude); log.buf.AppendFormat("Transform = ({0:g6}, {1:g6}, {2:g6}) length = {3:g6}\n", trans.forward.x, trans.forward.y, trans.forward.z, trans.forward.magnitude);
   
thrustvec -= trans.forward; thrustvec -= trans.forward;
} }
   
if (log != null) if (log != null)
log.buf.AppendFormat("ThrustVec = ({0:g6}, {1:g6}, {2:g6}) length = {3:g6}\n", thrustvec.x, thrustvec.y, thrustvec.z, thrustvec.magnitude); log.buf.AppendFormat("ThrustVec = ({0:g6}, {1:g6}, {2:g6}) length = {3:g6}\n", thrustvec.x, thrustvec.y, thrustvec.z, thrustvec.magnitude);
   
thrustvec.Normalize(); thrustvec.Normalize();
   
if (log != null) if (log != null)
log.buf.AppendFormat("ThrustVecN = ({0:g6}, {1:g6}, {2:g6}) length = {3:g6}\n", thrustvec.x, thrustvec.y, thrustvec.z, thrustvec.magnitude); log.buf.AppendFormat("ThrustVecN = ({0:g6}, {1:g6}, {2:g6}) length = {3:g6}\n", thrustvec.x, thrustvec.y, thrustvec.z, thrustvec.magnitude);
   
return thrustvec; return thrustvec;
} }
   
public void SetupParent(Dictionary<Part, PartSim> partSimLookup, LogMsg log) public void SetupParent(Dictionary<Part, PartSim> partSimLookup, LogMsg log)
{ {
if (this.part.parent != null) if (this.part.parent != null)
{ {
this.parent = null; this.parent = null;
if (partSimLookup.TryGetValue(this.part.parent, out this.parent)) if (partSimLookup.TryGetValue(this.part.parent, out this.parent))
{ {
if (log != null) if (log != null)
log.buf.AppendLine("Parent part is " + this.parent.name + ":" + this.parent.partId); log.buf.AppendLine("Parent part is " + this.parent.name + ":" + this.parent.partId);
} }
else else
{ {
if (log != null) if (log != null)
log.buf.AppendLine("No PartSim for parent part (" + this.part.parent.partInfo.name + ")"); log.buf.AppendLine("No PartSim for parent part (" + this.part.parent.partInfo.name + ")");
} }
} }
} }
   
public void SetupAttachNodes(Dictionary<Part, PartSim> partSimLookup, LogMsg log) public void SetupAttachNodes(Dictionary<Part, PartSim> partSimLookup, LogMsg log)
{ {
if (log != null) if (log != null)
log.buf.AppendLine("SetupAttachNodes for " + this.name + ":" + this.partId + ""); log.buf.AppendLine("SetupAttachNodes for " + this.name + ":" + this.partId + "");
   
this.attachNodes.Clear(); this.attachNodes.Clear();
foreach (AttachNode attachNode in this.part.attachNodes) foreach (AttachNode attachNode in this.part.attachNodes)
{ {
if (log != null) if (log != null)
log.buf.AppendLine("AttachNode " + attachNode.id + " = " + (attachNode.attachedPart != null ? attachNode.attachedPart.partInfo.name : "null")); log.buf.AppendLine("AttachNode " + attachNode.id + " = " + (attachNode.attachedPart != null ? attachNode.attachedPart.partInfo.name : "null"));
   
if (attachNode.attachedPart != null && attachNode.id != "Strut") if (attachNode.attachedPart != null && attachNode.id != "Strut")
{ {
PartSim attachedSim; PartSim attachedSim;
if (partSimLookup.TryGetValue(attachNode.attachedPart, out attachedSim)) if (partSimLookup.TryGetValue(attachNode.attachedPart, out attachedSim))
{ {
if (log != null) if (log != null)
log.buf.AppendLine("Adding attached node " + attachedSim.name + ":" + attachedSim.partId + ""); log.buf.AppendLine("Adding attached node " + attachedSim.name + ":" + attachedSim.partId + "");
   
this.attachNodes.Add(new AttachNodeSim(attachedSim, attachNode.id, attachNode.nodeType)); this.attachNodes.Add(new AttachNodeSim(attachedSim, attachNode.id, attachNode.nodeType));
} }
else else
{ {
if (log != null) if (log != null)
log.buf.AppendLine("No PartSim for attached part (" + attachNode.attachedPart.partInfo.name + ")"); log.buf.AppendLine("No PartSim for attached part (" + attachNode.attachedPart.partInfo.name + ")");
} }
} }
} }
   
foreach (Part p in this.part.fuelLookupTargets) foreach (Part p in this.part.fuelLookupTargets)
{ {
PartSim targetSim; PartSim targetSim;
if (partSimLookup.TryGetValue(p, out targetSim)) if (partSimLookup.TryGetValue(p, out targetSim))
{ {
if (log != null) if (log != null)
log.buf.AppendLine("Fuel target: " + targetSim.name + ":" + targetSim.partId); log.buf.AppendLine("Fuel target: " + targetSim.name + ":" + targetSim.partId);
   
this.fuelTargets.Add(targetSim); this.fuelTargets.Add(targetSim);
} }
else else
{ {
if (log != null) if (log != null)
log.buf.AppendLine("No PartSim for fuel target (" + p.name + ")"); log.buf.AppendLine("No PartSim for fuel target (" + p.name + ")");
} }
} }
} }
   
private int DecoupledInStage(Part thePart, int stage = -1) private int DecoupledInStage(Part thePart, int stage = -1)
{ {
if (this.IsDecoupler(thePart)) if (this.IsDecoupler(thePart))
{ {
if (thePart.inverseStage > stage) if (thePart.inverseStage > stage)
{ {
stage = thePart.inverseStage; stage = thePart.inverseStage;
} }
} }
   
if (thePart.parent != null) if (thePart.parent != null)
{ {
stage = this.DecoupledInStage(thePart.parent, stage); stage = this.DecoupledInStage(thePart.parent, stage);
} }
   
return stage; return stage;
} }
   
private bool IsDecoupler(Part thePart) private bool IsDecoupler(Part thePart)
{ {
return thePart.HasModule<ModuleDecouple>() || return thePart.HasModule<ModuleDecouple>() ||
thePart.HasModule<ModuleAnchoredDecoupler>(); thePart.HasModule<ModuleAnchoredDecoupler>();
} }
   
private bool IsActiveDecoupler(Part thePart) private bool IsActiveDecoupler(Part thePart)
{ {
return thePart.FindModulesImplementing<ModuleDecouple>().Any(mod => !mod.isDecoupled) || return thePart.FindModulesImplementing<ModuleDecouple>().Any(mod => !mod.isDecoupled) ||
thePart.FindModulesImplementing<ModuleAnchoredDecoupler>().Any(mod => !mod.isDecoupled); thePart.FindModulesImplementing<ModuleAnchoredDecoupler>().Any(mod => !mod.isDecoupled);
} }
   
private bool IsSepratron() private bool IsSepratron()
{ {
if (!this.part.ActivatesEvenIfDisconnected) if (!this.part.ActivatesEvenIfDisconnected)
return false; return false;
   
if (this.part is SolidRocket) if (this.part is SolidRocket)
return true; return true;
   
var modList = this.part.Modules.OfType<ModuleEngines>(); var modList = this.part.Modules.OfType<ModuleEngines>();
if (modList.Count() == 0) if (modList.Count() == 0)
return false; return false;
   
if (modList.First().throttleLocked == true) if (modList.First().throttleLocked == true)
return true; return true;
   
return false; return false;
} }
   
public void ReleasePart() public void ReleasePart()
{ {
this.part = null; this.part = null;
} }
   
   
// All functions below this point must not rely on the part member (it may be null) // All functions below this point must not rely on the part member (it may be null)
// //
   
public HashSet<PartSim> GetSourceSet(int type, List<PartSim> allParts, HashSet<PartSim> visited, LogMsg log, String indent) public HashSet<PartSim> GetSourceSet(int type, List<PartSim> allParts, HashSet<PartSim> visited, LogMsg log, String indent)
{ {
if (log != null) if (log != null)
{ {
log.buf.AppendLine(indent + "GetSourceSet(" + ResourceContainer.GetResourceName(type) + ") for " + this.name + ":" + this.partId); log.buf.AppendLine(indent + "GetSourceSet(" + ResourceContainer.GetResourceName(type) + ") for " + this.name + ":" + this.partId);
indent += " "; indent += " ";
} }
   
HashSet<PartSim> allSources = new HashSet<PartSim>(); HashSet<PartSim> allSources = new HashSet<PartSim>();
HashSet<PartSim> partSources = null; HashSet<PartSim> partSources = null;
   
// Rule 1: Each part can be only visited once, If it is visited for second time in particular search it returns empty list. // Rule 1: Each part can be only visited once, If it is visited for second time in particular search it returns empty list.
if (visited.Contains(this)) if (visited.Contains(this))
{ {
if (log != null) if (log != null)
log.buf.AppendLine(indent + "Returning empty set, already visited (" + this.name + ":" + this.partId + ")"); log.buf.AppendLine(indent + "Returning empty set, already visited (" + this.name + ":" + this.partId + ")");
   
return allSources; return allSources;
} }
   
//if (log != null) //if (log != null)
// log.buf.AppendLine(indent + "Adding this to visited"); // log.buf.AppendLine(indent + "Adding this to visited");
   
visited.Add(this); visited.Add(this);
   
// Rule 2: Part performs scan on start of every fuel pipe ending in it. This scan is done in order in which pipes were installed. 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. // Rule 2: Part performs scan on start of every fuel pipe ending in it. This scan is done in order in which pipes were installed. Then it makes an union of fuel tank sets each pipe scan returned. If the resulting list is not empty, it is returned as result.
//MonoBehaviour.print("foreach fuel line"); //MonoBehaviour.print("foreach fuel line");
   
foreach (PartSim partSim in this.fuelTargets) foreach (PartSim partSim in this.fuelTargets)
{ {
if (visited.Contains(partSim)) if (visited.Contains(partSim))
{ {
//if (log != null) //if (log != null)
// log.buf.AppendLine(indent + "Fuel target already visited, skipping (" + partSim.name + ":" + partSim.partId + ")"); // log.buf.AppendLine(indent + "Fuel target already visited, skipping (" + partSim.name + ":" + partSim.partId + ")");
} }
else else
{ {
//if (log != null) //if (log != null)
// log.buf.AppendLine(indent + "Adding fuel target as source (" + partSim.name + ":" + partSim.partId + ")"); // log.buf.AppendLine(indent + "Adding fuel target as source (" + partSim.name + ":" + partSim.partId + ")");
   
partSources = partSim.GetSourceSet(type, allParts, visited, log, indent); partSources = partSim.GetSourceSet(type, allParts, visited, log, indent);
if (partSources.Count > 0) if (partSources.Count > 0)
{ {
allSources.UnionWith(partSources); allSources.UnionWith(partSources);
partSources.Clear(); partSources.Clear();
} }
} }
} }
   
if (allSources.Count > 0) if (allSources.Count > 0)
{ {
if (log != null) if (log != null)
log.buf.AppendLine(indent + "Returning " + allSources.Count + " fuel target sources (" + this.name + ":" + this.partId + ")"); log.buf.AppendLine(indent + "Returning " + allSources.Count + " fuel target sources (" + this.name + ":" + this.partId + ")");
   
return allSources; return allSources;
} }
   
// Rule 3: This rule has been removed and merged with rules 4 and 7 to fix issue with fuel tanks with disabled crossfeed // Rule 3: This rule has been removed and merged with rules 4 and 7 to fix issue with fuel tanks with disabled crossfeed
   
// Rule 4: Part performs scan on each of its axially mounted neighbors. // Rule 4: Part performs scan on each of its axially mounted neighbors.
// Couplers (bicoupler, tricoupler, ...) are an exception, they only scan one attach point on the single attachment side, skip the points on the side where multiple points are. [Experiment] // Couplers (bicoupler, tricoupler, ...) are an exception, they only scan one attach point on the single attachment side, skip the points on the side where multiple points are. [Experiment]
// Again, the part creates union of scan lists from each of its neighbor and if it is not empty, returns this list. // Again, the part creates union of scan lists from each of its neighbor and if it is not empty, returns this list.
// The order in which mount points of a part are scanned appears to be fixed and defined by the part specification file. [Experiment] // The order in which mount points of a part are scanned appears to be fixed and defined by the part specification file. [Experiment]
if (this.fuelCrossFeed) if (this.fuelCrossFeed)
{ {
//MonoBehaviour.print("foreach attach node"); //MonoBehaviour.print("foreach attach node");
foreach (AttachNodeSim attachSim in this.attachNodes) foreach (AttachNodeSim attachSim in this.attachNodes)
{ {
if (attachSim.attachedPartSim != null) if (attachSim.attachedPartSim != null)
{ {
if (/*attachSim.nodeType != AttachNode.NodeType.Surface &&*/ if (/*attachSim.nodeType != AttachNode.NodeType.Surface &&*/
!(this.noCrossFeedNodeKey != null && this.noCrossFeedNodeKey.Length > 0 && attachSim.id.Contains(this.noCrossFeedNodeKey))) !(this.noCrossFeedNodeKey != null && this.noCrossFeedNodeKey.Length > 0 && attachSim.id.Contains(this.noCrossFeedNodeKey)))
{ {
if (visited.Contains(attachSim.attachedPartSim)) if (visited.Contains(attachSim.attachedPartSim))
{ {
//if (log != null) //if (log != null)
// log.buf.AppendLine(indent + "Attached part already visited, skipping (" + attachSim.attachedPartSim.name + ":" + attachSim.attachedPartSim.partId + ")"); // log.buf.AppendLine(indent + "Attached part already visited, skipping (" + attachSim.attachedPartSim.name + ":" + attachSim.attachedPartSim.partId + ")");
} }
else else
{ {
//if (log != null) //if (log != null)
// log.buf.AppendLine(indent + "Adding attached part as source (" + attachSim.attachedPartSim.name + ":" + attachSim.attachedPartSim.partId + ")"); // log.buf.AppendLine(indent + "Adding attached part as source (" + attachSim.attachedPartSim.name + ":" + attachSim.attachedPartSim.partId + ")");
   
partSources = attachSim.attachedPartSim.GetSourceSet(type, allParts, visited, log, indent); partSources = attachSim.attachedPartSim.GetSourceSet(type, allParts, visited, log, indent);
if (partSources.Count > 0) if (partSources.Count > 0)
{ {
allSources.UnionWith(partSources); allSources.UnionWith(partSources);
partSources.Clear(); partSources.Clear();
} }
} }
} }
else else
{ {
//if (log != null) //if (log != null)
// log.buf.AppendLine(indent + "AttachNode is noCrossFeedKey, skipping (" + attachSim.attachedPartSim.name + ":" + attachSim.attachedPartSim.partId + ")"); // log.buf.AppendLine(indent + "AttachNode is noCrossFeedKey, skipping (" + attachSim.attachedPartSim.name + ":" + attachSim.attachedPartSim.partId + ")");
} }
} }
} }
   
if (allSources.Count > 0) if (allSources.Count > 0)
{ {
if (log != null) if (log != null)
log.buf.AppendLine(indent + "Returning " + allSources.Count + " attached sources (" + this.name + ":" + this.partId + ")"); log.buf.AppendLine(indent + "Returning " + allSources.Count + " attached sources (" + this.name + ":" + this.partId + ")");
   
return allSources; return allSources;
} }
} }
else else
{ {
//if (log != null) //if (log != null)
// log.buf.AppendLine(indent + "Crossfeed disabled, skipping axial connected parts (" + name + ":" + partId + ")"); // log.buf.AppendLine(indent + "Crossfeed disabled, skipping axial connected parts (" + name + ":" + partId + ")");
} }
   
// Rule 5: If the part is fuel container for searched type of fuel (i.e. it has capability to contain that type of fuel and the fuel type was not disabled [Experiment]) and it contains fuel, it returns itself. // Rule 5: If the part is fuel container for searched type of fuel (i.e. it has capability to contain that type of fuel and the fuel type was not disabled [Experiment]) and it contains fuel, it returns itself.
// Rule 6: If the part is fuel container for searched type of fuel (i.e. it has capability to contain that type of fuel and the fuel type was not disabled) but it does not contain the requested fuel, it returns empty list. [Experiment] // Rule 6: If the part is fuel container for searched type of fuel (i.e. it has capability to contain that type of fuel and the fuel type was not disabled) but it does not contain the requested fuel, it returns empty list. [Experiment]
if (this.resources.HasType(type) && this.resourceFlowStates[type] != 0) if (this.resources.HasType(type) && this.resourceFlowStates[type] != 0)
{ {
if (this.resources[type] > SimManager.RESOURCE_MIN) if (this.resources[type] > SimManager.RESOURCE_MIN)
{ {
allSources.Add(this); allSources.Add(this);
   
if (log != null) if (log != null)
log.buf.AppendLine(indent + "Returning enabled tank as only source (" + this.name + ":" + this.partId + ")"); log.buf.AppendLine(indent + "Returning enabled tank as only source (" + this.name + ":" + this.partId + ")");
} }
else else
{ {
//if (log != null) //if (log != null)
// log.buf.AppendLine(indent + "Returning empty set, enabled tank is empty (" + name + ":" + partId + ")"); // log.buf.AppendLine(indent + "Returning empty set, enabled tank is empty (" + name + ":" + partId + ")");
} }
   
return allSources; return allSources;
} }
   
// Rule 7: If the part is radially attached to another part and it is child of that part in the ship's tree structure, it scans its parent and returns whatever the parent scan returned. [Experiment] [Experiment] // Rule 7: If the part is radially attached to another part and it is child of that part in the ship's tree structure, it scans its parent and returns whatever the parent scan returned. [Experiment] [Experiment]
if (this.parent != null) if (this.parent != null)
{ {
if (this.fuelCrossFeed) if (this.fuelCrossFeed)
{ {
if (visited.Contains(this.parent)) if (visited.Contains(this.parent))
{ {
//if (log != null) //if (log != null)
// log.buf.AppendLine(indent + "Parent part already visited, skipping (" + parent.name + ":" + parent.partId + ")"); // log.buf.AppendLine(indent + "Parent part already visited, skipping (" + parent.name + ":" + parent.partId + ")");
} }
else else
{ {
allSources = this.parent.GetSourceSet(type, allParts, visited, log, indent); allSources = this.parent.GetSourceSet(type, allParts, visited, log, indent);
if (allSources.Count > 0) if (allSources.Count > 0)
{ {
if (log != null) if (log != null)
log.buf.AppendLine(indent + "Returning " + allSources.Count + " parent sources (" + this.name + ":" + this.partId + ")"); log.buf.AppendLine(indent + "Returning " + allSources.Count + " parent sources (" + this.name + ":" + this.partId + ")");
   
return allSources; return allSources;
} }
} }
} }
else else
{ {
//if (log != null) //if (log != null)
// log.buf.AppendLine(indent + "Crossfeed disabled, skipping radial parent (" + name + ":" + partId + ")"); // log.buf.AppendLine(indent + "Crossfeed disabled, skipping radial parent (" + name + ":" + partId + ")");
} }
} }
   
// Rule 8: If all preceding rules failed, part returns empty list. // Rule 8: If all preceding rules failed, part returns empty list.
//if (log != null) //if (log != null)
// log.buf.AppendLine(indent + "Returning empty set, no sources found (" + name + ":" + partId + ")"); // log.buf.AppendLine(indent + "Returning empty set, no sources found (" + name + ":" + partId + ")");
   
return allSources; return allSources;
} }
   
   
public void RemoveAttachedParts(HashSet<PartSim> partSims) public void RemoveAttachedParts(HashSet<PartSim> partSims)
{ {
// Loop through the attached parts // Loop through the attached parts
foreach (AttachNodeSim attachSim in this.attachNodes) foreach (AttachNodeSim attachSim in this.attachNodes)
{ {
// If the part is in the set then "remove" it by clearing the PartSim reference // If the part is in the set then "remove" it by clearing the PartSim reference
if (partSims.Contains(attachSim.attachedPartSim)) if (partSims.Contains(attachSim.attachedPartSim))
attachSim.attachedPartSim = null; attachSim.attachedPartSim = null;
} }
} }
   
   
public void DrainResources(double time) public void DrainResources(double time)
{ {
//MonoBehaviour.print("DrainResources(" + name + ":" + partId + ", " + time + ")"); //MonoBehaviour.print("DrainResources(" + name + ":" + partId + ", " + time + ")");
foreach (int type in this.resourceDrains.Types) foreach (int type in this.resourceDrains.Types)
{ {
//MonoBehaviour.print("draining " + (time * resourceDrains[type]) + " " + ResourceContainer.GetResourceName(type)); //MonoBehaviour.print("draining " + (time * resourceDrains[type]) + " " + ResourceContainer.GetResourceName(type));
this.resources.Add(type, -time * this.resourceDrains[type]); this.resources.Add(type, -time * this.resourceDrains[type]);
//MonoBehaviour.print(ResourceContainer.GetResourceName(type) + " left = " + resources[type]); //MonoBehaviour.print(ResourceContainer.GetResourceName(type) + " left = " + resources[type]);
} }
} }
   
public double TimeToDrainResource() public double TimeToDrainResource()
{ {
//MonoBehaviour.print("TimeToDrainResource(" + name + ":" + partId + ")"); //MonoBehaviour.print("TimeToDrainResource(" + name + ":" + partId + ")");
double time = double.MaxValue; double time = double.MaxValue;
   
foreach (int type in this.resourceDrains.Types) foreach (int type in this.resourceDrains.Types)
{ {
if (this.resourceDrains[type] > 0) if (this.resourceDrains[type] > 0)
{ {
time = Math.Min(time, this.resources[type] / this.resourceDrains[type]); time = Math.Min(time, this.resources[type] / this.resourceDrains[type]);
//MonoBehaviour.print("type = " + ResourceContainer.GetResourceName(type) + " amount = " + resources[type] + " rate = " + resourceDrains[type] + " time = " + time); //MonoBehaviour.print("type = " + ResourceContainer.GetResourceName(type) + " amount = " + resources[type] + " rate = " + resourceDrains[type] + " time = " + time);
} }
} }
   
//if (time < double.MaxValue) //if (time < double.MaxValue)
// MonoBehaviour.print("TimeToDrainResource(" + name + ":" + partId + ") = " + time); // MonoBehaviour.print("TimeToDrainResource(" + name + ":" + partId + ") = " + time);
return time; return time;
} }
   
public int DecouplerCount() public int DecouplerCount()
{ {
int count = 0; int count = 0;
PartSim partSim = this; PartSim partSim = this;
while (partSim != null) while (partSim != null)
{ {
if (partSim.isDecoupler) if (partSim.isDecoupler)
count++; count++;
   
partSim = partSim.parent; partSim = partSim.parent;
} }
return count; return count;
} }
   
public double GetStartMass() public double GetStartMass()
{ {
return this.startMass; return this.startMass;
} }
   
public double GetMass() public double GetMass()
{ {
double mass = this.baseMass; double mass = this.baseMass;
   
foreach (int type in this.resources.Types) foreach (int type in this.resources.Types)
mass += this.resources.GetResourceMass(type); mass += this.resources.GetResourceMass(type);
   
return mass; return mass;
} }
   
public ResourceContainer Resources public ResourceContainer Resources
{ {
get get
{ {
return this.resources; return this.resources;
} }
} }
   
public ResourceContainer ResourceDrains public ResourceContainer ResourceDrains
{ {
get get
{ {
return this.resourceDrains; return this.resourceDrains;
} }
} }
   
public String DumpPartAndParentsToBuffer(StringBuilder buffer, String prefix) public String DumpPartAndParentsToBuffer(StringBuilder buffer, String prefix)
{ {
if (this.parent != null) if (this.parent != null)
{ {
prefix = this.parent.DumpPartAndParentsToBuffer(buffer, prefix) + " "; prefix = this.parent.DumpPartAndParentsToBuffer(buffer, prefix) + " ";
} }
   
this.DumpPartToBuffer(buffer, prefix); this.DumpPartToBuffer(buffer, prefix);
   
return prefix; return prefix;
} }
   
public void DumpPartToBuffer(StringBuilder buffer, String prefix, List<PartSim> allParts = null) public void DumpPartToBuffer(StringBuilder buffer, String prefix, List<PartSim> allParts = null)
{ {
buffer.Append(prefix); buffer.Append(prefix);
buffer.Append(this.name); buffer.Append(this.name);
buffer.AppendFormat(":[id = {0:d}, decouple = {1:d}, invstage = {2:d}", this.partId, this.decoupledInStage, this.inverseStage); buffer.AppendFormat(":[id = {0:d}, decouple = {1:d}, invstage = {2:d}", this.partId, this.decoupledInStage, this.inverseStage);
   
buffer.AppendFormat(", vesselName = '{0}'", this.vesselName); buffer.AppendFormat(", vesselName = '{0}'", this.vesselName);
buffer.AppendFormat(", vesselType = {0}", SimManager.GetVesselTypeString(this.vesselType)); buffer.AppendFormat(", vesselType = {0}", SimManager.GetVesselTypeString(this.vesselType));
buffer.AppendFormat(", initialVesselName = '{0}'", this.initialVesselName); buffer.AppendFormat(", initialVesselName = '{0}'", this.initialVesselName);
   
buffer.AppendFormat(", fuelCF = {0}", this.fuelCrossFeed); buffer.AppendFormat(", fuelCF = {0}", this.fuelCrossFeed);
buffer.AppendFormat(", noCFNKey = '{0}'", this.noCrossFeedNodeKey); buffer.AppendFormat(", noCFNKey = '{0}'", this.noCrossFeedNodeKey);
   
buffer.AppendFormat(", isSep = {0}", this.isSepratron); buffer.AppendFormat(", isSep = {0}", this.isSepratron);
   
foreach (int type in this.resources.Types) foreach (int type in this.resources.Types)
buffer.AppendFormat(", {0} = {1:g6}", ResourceContainer.GetResourceName(type), this.resources[type]); buffer.AppendFormat(", {0} = {1:g6}", ResourceContainer.GetResourceName(type), this.resources[type]);
   
if (this.attachNodes.Count > 0) if (this.attachNodes.Count > 0)
{ {
buffer.Append(", attached = <"); buffer.Append(", attached = <");
this.attachNodes[0].DumpToBuffer(buffer); this.attachNodes[0].DumpToBuffer(buffer);
for (int i = 1; i < this.attachNodes.Count; i++) for (int i = 1; i < this.attachNodes.Count; i++)
{ {
buffer.Append(", "); buffer.Append(", ");
this.attachNodes[i].DumpToBuffer(buffer); this.attachNodes[i].DumpToBuffer(buffer);
} }
buffer.Append(">"); buffer.Append(">");
} }
   
// Add more info here // Add more info here
   
buffer.Append("]\n"); buffer.Append("]\n");
   
if (allParts != null) if (allParts != null)
{ {
String newPrefix = prefix + " "; String newPrefix = prefix + " ";
foreach (PartSim partSim in allParts) foreach (PartSim partSim in allParts)
{ {
if (partSim.parent == this) if (partSim.parent == this)
partSim.DumpPartToBuffer(buffer, newPrefix, allParts); partSim.DumpPartToBuffer(buffer, newPrefix, allParts);
} }
} }
} }
} }
} }