//return the mass of the simulated FuelNode. This is not the same as the mass of the Part, //because the simulated node may have lost resources, and thus mass, during the simulation. public float Mass(int simStage) { //print("\n(" + simStage + ") " + partName.PadRight(25) + " dryMass " + dryMass.ToString("F3") // + " ResMass " + (resources.Keys.Sum(id => resources[id] * MuUtils.ResourceDensity(id))).ToString("F3") // + " Fairing Mass " + (inverseStage < simStage ? fairingMass : 0).ToString("F3") // + " (" + fairingMass.ToString("F3") + ")" // + " ModuleMass " + moduleMass.ToString("F3") // ); return(dryMass + resources.Keys.Sum(id => resources[id] * MuUtils.ResourceDensity(id)) + (inverseStage < simStage ? fairingMass : 0)); }
//return the mass of the simulated FuelNode. This is not the same as the mass of the Part, //because the simulated node may have lost resources, and thus mass, during the simulation. public double Mass(int simStage) { //print("\n(" + simStage + ") " + partName.PadRight(25) + " dryMass " + dryMass.ToString("F3") // + " ResMass " + (resources.Keys.Sum(id => resources[id] * MuUtils.ResourceDensity(id))).ToString("F3") // + " Fairing Mass " + (inverseStage < simStage ? fairingMass : 0).ToString("F3") // + " (" + fairingMass.ToString("F3") + ")" // + " ModuleMass " + moduleMass.ToString("F3") // ); //return dryMass + resources.Keys.Sum(id => resources[id] * MuUtils.ResourceDensity(id)) + double resMass = resources.KeysList.Slinq().Select((r, rs) => rs[r] * MuUtils.ResourceDensity(r), resources).Sum(); return(dryMass + resMass + (inverseStage < simStage ? modulesUnstagedMass : modulesStagedMass)); }
public float moduleMass; // for debugging public FuelNode(Part part, bool dVLinearThrust) { if (!part.IsLaunchClamp()) { //print(part.partInfo.name.PadRight(25) + " " + part.mass.ToString("F4") + " " + part.GetPhysicslessChildMass().ToString("F4") + " " + part.GetModuleMass(part.partInfo.partPrefab.mass).ToString("F4")); dryMass = part.mass; // Intentionally ignore the physic flag. moduleMass = part.GetModuleMass(part.partInfo.partPrefab.mass); if (part.HasModule <ModuleProceduralFairing>()) { fairingMass = moduleMass; } } inverseStage = part.inverseStage; partName = part.partInfo.name; //note which resources this part has stored for (int i = 0; i < part.Resources.Count; i++) { PartResource r = part.Resources[i]; if (r.info.density > 0 && r.info.name != "IntakeAir") { if (r.flowState) { resources[r.info.id] = (float)r.amount; } else { dryMass += (float)(r.amount * r.info.density); // disabled resources are just dead weight } } } // TODO : handle the multiple active ModuleEngine case ( SXT engines with integrated vernier ) //record relevant engine stats ModuleEngines engine = part.Modules.OfType <ModuleEngines>().FirstOrDefault(e => e.isEnabled); if (engine != null) { //Only count engines that either are ignited or will ignite in the future: if ((HighLogic.LoadedSceneIsEditor || inverseStage < Staging.CurrentStage || engine.getIgnitionState) && (engine.thrustPercentage > 0 || engine.minThrust > 0)) { //if an engine has been activated early, pretend it is in the current stage: if (engine.getIgnitionState && inverseStage < Staging.CurrentStage) { inverseStage = Staging.CurrentStage; } isEngine = true; g = engine.g; // If we take into account the engine rotation if (dVLinearThrust) { Vector3 thrust = Vector3d.zero; for (int i = 0; i < engine.thrustTransforms.Count; i++) { thrust -= engine.thrustTransforms[i].forward / engine.thrustTransforms.Count; } Vector3d fwd = HighLogic.LoadedScene == GameScenes.EDITOR ? EditorLogic.VesselRotation * Vector3d.up : engine.part.vessel.GetTransform().up; fwdThrustRatio = Vector3.Dot(fwd, thrust); } thrustPercentage = engine.thrustPercentage; minFuelFlow = engine.minFuelFlow; maxFuelFlow = engine.maxFuelFlow; atmosphereCurve = new FloatCurve(engine.atmosphereCurve.Curve.keys); atmChangeFlow = engine.atmChangeFlow; useAtmCurve = engine.useAtmCurve; if (useAtmCurve) { atmCurve = new FloatCurve(engine.atmCurve.Curve.keys); } useVelCurve = engine.useVelCurve; if (useAtmCurve) { velCurve = new FloatCurve(engine.velCurve.Curve.keys); } propellantSumRatioTimesDensity = engine.propellants.Where(prop => !prop.ignoreForIsp).Sum(prop => prop.ratio * MuUtils.ResourceDensity(prop.id)); propellantRatios = engine.propellants.Where(prop => MuUtils.ResourceDensity(prop.id) > 0 && !prop.ignoreForIsp).ToDictionary(prop => prop.id, prop => prop.ratio); } } }
public string partName; //for debugging public FuelNode(Part part, bool dVLinearThrust) { if (part.IsPhysicallySignificant()) { dryMass = part.mass; } inverseStage = part.inverseStage; partName = part.partInfo.name; //note which resources this part has stored foreach (PartResource r in part.Resources) { if (r.info.density > 0 && r.name != "IntakeAir") { if (r.flowState) { resources[r.info.id] = (float)r.amount; } else { dryMass += (float)(r.amount * r.info.density); // disabled resources are just dead weight } } } //record relevant engine stats ModuleEngines engine = part.Modules.OfType <ModuleEngines>().FirstOrDefault(); if (engine != null) { //Only count engines that either are ignited or will ignite in the future: if ((HighLogic.LoadedSceneIsEditor || inverseStage < Staging.CurrentStage || engine.getIgnitionState) && engine.thrustPercentage > 0) { //if an engine has been activated early, pretend it is in the current stage: if (engine.getIgnitionState && inverseStage < Staging.CurrentStage) { inverseStage = Staging.CurrentStage; } isEngine = true; g = engine.g; // If we take into account the engine rotation if (dVLinearThrust) { Vector3 thrust = Vector3d.zero; foreach (var t in engine.thrustTransforms) { thrust -= t.forward / engine.thrustTransforms.Count; } Vector3 fwd = HighLogic.LoadedScene == GameScenes.EDITOR ? Vector3d.up : (HighLogic.LoadedScene == GameScenes.SPH ? Vector3d.forward : (Vector3d)engine.part.vessel.GetTransform().up); fwdThrustRatio = Vector3.Dot(fwd, thrust); } maxThrust = engine.thrustPercentage / 100f * engine.maxThrust; if (part.IsMFE()) { correctThrust = true; if (HighLogic.LoadedSceneIsFlight && engine.realIsp > 0.0f) { maxThrust = maxThrust * engine.atmosphereCurve.Evaluate(0) / engine.realIsp; //engine.atmosphereCurve.Evaluate((float)FlightGlobals.ActiveVessel.atmDensity); } } else { correctThrust = false; } ispCurve = engine.atmosphereCurve; propellantSumRatioTimesDensity = engine.propellants.Sum(prop => prop.ratio * MuUtils.ResourceDensity(prop.id)); propellantRatios = engine.propellants.Where(prop => MuUtils.ResourceDensity(prop.id) > 0 && prop.name != "IntakeAir").ToDictionary(prop => prop.id, prop => prop.ratio); } } // And do the same for ModuleEnginesFX :( ModuleEnginesFX enginefx = part.Modules.OfType <ModuleEnginesFX>().FirstOrDefault(e => e.isEnabled); if (enginefx != null) { //Only count engines that either are ignited or will ignite in the future: if ((HighLogic.LoadedSceneIsEditor || inverseStage < Staging.CurrentStage || enginefx.getIgnitionState) && enginefx.thrustPercentage > 0) { //if an engine has been activated early, pretend it is in the current stage: if (enginefx.getIgnitionState && inverseStage < Staging.CurrentStage) { inverseStage = Staging.CurrentStage; } isEngine = true; g = enginefx.g; // If we take into account the engine rotation if (dVLinearThrust) { Vector3 thrust = Vector3d.zero; foreach (var t in enginefx.thrustTransforms) { thrust -= t.forward / enginefx.thrustTransforms.Count; } Vector3 fwd = HighLogic.LoadedScene == GameScenes.EDITOR ? Vector3d.up : (HighLogic.LoadedScene == GameScenes.SPH ? Vector3d.forward : (Vector3d)enginefx.part.vessel.GetTransform().up); fwdThrustRatio = Vector3.Dot(fwd, thrust); } maxThrust = enginefx.thrustPercentage / 100f * enginefx.maxThrust; if (part.IsMFE()) { correctThrust = true; if (HighLogic.LoadedSceneIsFlight && enginefx.realIsp > 0.0f) { maxThrust = maxThrust * enginefx.atmosphereCurve.Evaluate(0) / enginefx.realIsp; //engine.atmosphereCurve.Evaluate((float)FlightGlobals.ActiveVessel.atmDensity); } } else { correctThrust = false; } ispCurve = enginefx.atmosphereCurve; propellantSumRatioTimesDensity = enginefx.propellants.Sum(prop => prop.ratio * MuUtils.ResourceDensity(prop.id)); propellantRatios = enginefx.propellants.Where(prop => PartResourceLibrary.Instance.GetDefinition(prop.id).density > 0 && prop.name != "IntakeAir").ToDictionary(prop => prop.id, prop => prop.ratio); } } }
public string partName; //for debugging public FuelNode(Part part, bool dVLinearThrust) { if (part.IsPhysicallySignificant()) { dryMass = part.mass; } inverseStage = part.inverseStage; isFuelLine = (part is FuelLine); isSepratron = part.IsSepratron(); partName = part.partInfo.name; //note which resources this part has stored foreach (PartResource r in part.Resources) { if (r.info.density > 0 && r.name != "IntakeAir") { resources[r.info.id] = (float)r.amount; } resourcesUnobtainableFromParent.Add(r.info.id); } //record relevant engine stats ModuleEngines engine = part.Modules.OfType <ModuleEngines>().FirstOrDefault(); if (engine != null) { //Only count engines that either are ignited or will ignite in the future: if (HighLogic.LoadedSceneIsEditor || inverseStage < Staging.CurrentStage || engine.getIgnitionState) { //if an engine has been activated early, pretend it is in the current stage: if (engine.getIgnitionState && inverseStage < Staging.CurrentStage) { inverseStage = Staging.CurrentStage; } isEngine = true; g = engine.g; // If we take into account the engine rotation if (dVLinearThrust) { Vector3 thrust = Vector3d.zero; foreach (var t in engine.thrustTransforms) { thrust -= t.forward / engine.thrustTransforms.Count; } Vector3 fwd = HighLogic.LoadedScene == GameScenes.EDITOR ? Vector3d.up : (HighLogic.LoadedScene == GameScenes.SPH ? Vector3d.forward : (Vector3d)engine.part.vessel.GetTransform().up); fwdThrustRatio = Vector3.Dot(fwd, thrust); } maxThrust = engine.thrustPercentage / 100f * engine.maxThrust; if (part.Modules.Contains("ModuleEngineConfigs") || part.Modules.Contains("ModuleHybridEngine") || part.Modules.Contains("ModuleHybridEngines")) { correctThrust = true; if (HighLogic.LoadedSceneIsFlight && engine.realIsp > 0.0f) { maxThrust = maxThrust * engine.atmosphereCurve.Evaluate(0) / engine.realIsp; //engine.atmosphereCurve.Evaluate((float)FlightGlobals.ActiveVessel.atmDensity); } } else { correctThrust = false; } ispCurve = engine.atmosphereCurve; propellantSumRatioTimesDensity = engine.propellants.Sum(prop => prop.ratio * MuUtils.ResourceDensity(prop.id)); propellantRatios = engine.propellants.Where(prop => PartResourceLibrary.Instance.GetDefinition(prop.id).density > 0 && prop.name != "IntakeAir").ToDictionary(prop => prop.id, prop => prop.ratio); } } // And do the same for ModuleEnginesFX :( ModuleEnginesFX enginefx = part.Modules.OfType <ModuleEnginesFX>().Where(e => e.isEnabled).FirstOrDefault(); if (enginefx != null) { //Only count engines that either are ignited or will ignite in the future: if (HighLogic.LoadedSceneIsEditor || inverseStage < Staging.CurrentStage || enginefx.getIgnitionState) { //if an engine has been activated early, pretend it is in the current stage: if (enginefx.getIgnitionState && inverseStage < Staging.CurrentStage) { inverseStage = Staging.CurrentStage; } isEngine = true; g = enginefx.g; // If we take into account the engine rotation if (dVLinearThrust) { Vector3 thrust = Vector3d.zero; foreach (var t in enginefx.thrustTransforms) { thrust -= t.forward / enginefx.thrustTransforms.Count; } Vector3 fwd = HighLogic.LoadedScene == GameScenes.EDITOR ? Vector3d.up : (HighLogic.LoadedScene == GameScenes.SPH ? Vector3d.forward : (Vector3d)enginefx.part.vessel.GetTransform().up); fwdThrustRatio = Vector3.Dot(fwd, thrust); } maxThrust = enginefx.thrustPercentage / 100f * enginefx.maxThrust; if (part.Modules.Contains("ModuleEngineConfigs") || part.Modules.Contains("ModuleHybridEngine") || part.Modules.Contains("ModuleHybridEngines")) { correctThrust = true; if (HighLogic.LoadedSceneIsFlight && enginefx.realIsp > 0.0f) { maxThrust = maxThrust * enginefx.atmosphereCurve.Evaluate(0) / enginefx.realIsp; //engine.atmosphereCurve.Evaluate((float)FlightGlobals.ActiveVessel.atmDensity); } } else { correctThrust = false; } ispCurve = enginefx.atmosphereCurve; propellantSumRatioTimesDensity = enginefx.propellants.Sum(prop => prop.ratio * MuUtils.ResourceDensity(prop.id)); propellantRatios = enginefx.propellants.Where(prop => PartResourceLibrary.Instance.GetDefinition(prop.id).density > 0 && prop.name != "IntakeAir").ToDictionary(prop => prop.id, prop => prop.ratio); } } //figure out when this part gets decoupled. We do this by looking through this part and all this part's ancestors //and noting which one gets decoupled earliest (i.e., has the highest inverseStage). Some parts never get decoupled //and these are assigned decoupledInStage = -1. decoupledInStage = -1; Part p = part; while (true) { if (p.IsDecoupler() || p.IsLaunchClamp()) { if (p.inverseStage > decoupledInStage) { decoupledInStage = p.inverseStage; } } if (p.parent == null) { break; } else { p = p.parent; } } }
public string partName; //for debugging public FuelNode(Part part) { bool physicallySignificant = (part.physicalSignificance != Part.PhysicalSignificance.NONE); if (part.HasModule <ModuleLandingGear>() || part.HasModule <LaunchClamp>()) { //Landing gear set physicalSignificance = NONE when they enter the flight scene //Launch clamp mass should be ignored. physicallySignificant = false; } if (physicallySignificant) { dryMass = part.mass; } inverseStage = part.inverseStage; isFuelLine = (part is FuelLine); isSepratron = part.IsSepratron(); partName = part.partInfo.name; //note which resources this part has stored foreach (PartResource r in part.Resources) { if (r.info.name != "ElectricCharge") { resources[r.info.id] = (float)r.amount; } resourcesUnobtainableFromParent.Add(r.info.id); } //record relevant engine stats ModuleEngines engine = part.Modules.OfType <ModuleEngines>().FirstOrDefault(); if (engine != null) { //Only count engines that either are ignited or will ignite in the future: if (HighLogic.LoadedSceneIsEditor || inverseStage < Staging.CurrentStage || engine.getIgnitionState) { //if an engine has been activated early, pretend it is in the current stage: if (engine.getIgnitionState && inverseStage < Staging.CurrentStage) { inverseStage = Staging.CurrentStage; } isEngine = true; maxThrust = engine.maxThrust; ispCurve = engine.atmosphereCurve; propellantSumRatioTimesDensity = engine.propellants.Sum(prop => prop.ratio * MuUtils.ResourceDensity(prop.id)); propellantRatios = engine.propellants.Where(prop => prop.name != "ElectricCharge").ToDictionary(prop => prop.id, prop => prop.ratio); } } //figure out when this part gets decoupled. We do this by looking through this part and all this part's ancestors //and noting which one gets decoupled earliest (i.e., has the highest inverseStage). Some parts never get decoupled //and these are assigned decoupledInStage = -1. decoupledInStage = -1; Part p = part; while (true) { if (p.IsDecoupler()) { if (p.inverseStage > decoupledInStage) { decoupledInStage = p.inverseStage; } } if (p.parent == null) { break; } else { p = p.parent; } } }
private void Init(Part part, bool dVLinearThrust) { resources.Clear(); resourceConsumptions.Clear(); resourceDrains.Clear(); freeResources.Clear(); propellantRatios.Clear(); propellantFlows.Clear(); crossfeedSources.Clear(); isEngine = false; dryMass = 0; modulesStagedMass = 0; decoupledInStage = int.MinValue; modulesUnstagedMass = 0; if (!part.IsLaunchClamp()) { dryMass = part.prefabMass; // Intentionally ignore the physic flag. modulesUnstagedMass = part.GetModuleMassNoAlloc((float)dryMass, ModifierStagingSituation.UNSTAGED); modulesStagedMass = part.GetModuleMassNoAlloc((float)dryMass, ModifierStagingSituation.STAGED); float currentModulesMass = part.GetModuleMassNoAlloc((float)dryMass, ModifierStagingSituation.CURRENT); // if it was manually staged if (currentModulesMass == modulesStagedMass) { modulesUnstagedMass = modulesStagedMass; } //print(part.partInfo.name.PadRight(25) + " " + part.mass.ToString("F4") + " " + part.GetPhysicslessChildMass().ToString("F4") + " " + modulesUnstagedMass.ToString("F4") + " " + modulesStagedMass.ToString("F4")); } inverseStage = part.inverseStage; partName = part.partInfo.name; resourceRequestRemainingThreshold = Math.Max(part.resourceRequestRemainingThreshold, DRAINED); resourcePriority = part.GetResourcePriority(); //note which resources this part has stored for (int i = 0; i < part.Resources.Count; i++) { PartResource r = part.Resources[i]; if (r.info.density > 0) { if (r.flowState) { resources[r.info.id] = r.amount; } else { dryMass += (r.amount * r.info.density); // disabled resources are just dead weight } } if (r.info.name == "IntakeAir") { freeResources[PartResourceLibrary.Instance.GetDefinition("IntakeAir").id] = true; } // Those two are in the CRP. if (r.info.name == "IntakeLqd") { freeResources[PartResourceLibrary.Instance.GetDefinition("IntakeLqd").id] = true; } if (r.info.name == "IntakeAtm") { freeResources[PartResourceLibrary.Instance.GetDefinition("IntakeAtm").id] = true; } } // TODO : handle the multiple active ModuleEngine case ( SXT engines with integrated vernier ) //record relevant engine stats //ModuleEngines engine = part.Modules.OfType<ModuleEngines>().FirstOrDefault(e => e.isEnabled); ModuleEngines engine = null; for (int i = 0; i < part.Modules.Count; i++) { PartModule pm = part.Modules[i]; ModuleEngines e = pm as ModuleEngines; if (e != null && e.isEnabled) { engine = e; break; } } if (engine != null) { //Only count engines that either are ignited or will ignite in the future: if ((HighLogic.LoadedSceneIsEditor || inverseStage < StageManager.CurrentStage || engine.getIgnitionState) && (engine.thrustPercentage > 0 || engine.minThrust > 0)) { //if an engine has been activated early, pretend it is in the current stage: if (engine.getIgnitionState && inverseStage < StageManager.CurrentStage) { inverseStage = StageManager.CurrentStage; } isEngine = true; g = engine.g; // If we take into account the engine rotation if (dVLinearThrust) { Vector3 thrust = Vector3.zero; for (int i = 0; i < engine.thrustTransforms.Count; i++) { thrust -= engine.thrustTransforms[i].forward * engine.thrustTransformMultipliers[i]; } Vector3d fwd = HighLogic.LoadedScene == GameScenes.EDITOR ? EditorLogic.VesselRotation * Vector3d.up : engine.part.vessel.GetTransform().up; fwdThrustRatio = Vector3.Dot(fwd, thrust); } else { fwdThrustRatio = 1; } thrustPercentage = engine.thrustPercentage; minFuelFlow = engine.minFuelFlow; maxFuelFlow = engine.maxFuelFlow; // Some brilliant engine mod seems to consider that FuelFlow is not something they should properly initialize if (minFuelFlow == 0 && engine.minThrust > 0) { maxFuelFlow = engine.minThrust / (engine.atmosphereCurve.Evaluate(0f) * engine.g); } if (maxFuelFlow == 0 && engine.maxThrust > 0) { maxFuelFlow = engine.maxThrust / (engine.atmosphereCurve.Evaluate(0f) * engine.g); } atmosphereCurve = new FloatCurve(engine.atmosphereCurve.Curve.keys); atmChangeFlow = engine.atmChangeFlow; useAtmCurve = engine.useAtmCurve; if (useAtmCurve) { atmCurve = new FloatCurve(engine.atmCurve.Curve.keys); } useVelCurve = engine.useVelCurve; if (useVelCurve) { velCurve = new FloatCurve(engine.velCurve.Curve.keys); } propellantSumRatioTimesDensity = engine.propellants.Slinq().Where(prop => !prop.ignoreForIsp).Select(prop => prop.ratio * MuUtils.ResourceDensity(prop.id)).Sum(); propellantRatios.Clear(); propellantFlows.Clear(); var dics = new Tuple <KeyableDictionary <int, float>, KeyableDictionary <int, ResourceFlowMode> >(propellantRatios, propellantFlows); engine.propellants.Slinq() .Where(prop => MuUtils.ResourceDensity(prop.id) > 0 && !prop.ignoreForIsp) .ForEach((p, dic) => { dic.Item1.Add(p.id, p.ratio); dic.Item2.Add(p.id, p.GetFlowMode()); }, dics); } } }
private void Init(Part part, bool dVLinearThrust) { resources.Clear(); resourceConsumptions.Clear(); resourceDrains.Clear(); freeResources.Clear(); propellantRatios.Clear(); propellantFlows.Clear(); fuelLineSources.Clear(); stackNodeSources.Clear(); surfaceMountSources.Clear(); surfaceMountParent = null; isEngine = false; dryMass = 0; fairingMass = 0; moduleMass = 0; if (!part.IsLaunchClamp()) { //print(part.partInfo.name.PadRight(25) + " " + part.mass.ToString("F4") + " " + part.GetPhysicslessChildMass().ToString("F4") + " " + part.GetModuleMass(part.partInfo.partPrefab.mass).ToString("F4")); dryMass = part.mass; // Intentionally ignore the physic flag. moduleMass = part.GetModuleMass(part.partInfo.partPrefab != null ? part.partInfo.partPrefab.mass : dryMass); if (part.HasModule <ModuleProceduralFairing>()) { fairingMass = moduleMass; } } inverseStage = part.inverseStage; partName = part.partInfo.name; //note which resources this part has stored for (int i = 0; i < part.Resources.Count; i++) { PartResource r = part.Resources[i]; if (r.info.density > 0) { if (r.flowState) { resources[r.info.id] = (float)r.amount; } else { dryMass += (float)(r.amount * r.info.density); // disabled resources are just dead weight } } if (r.info.name == "IntakeAir") { freeResources[PartResourceLibrary.Instance.GetDefinition("IntakeAir").id] = true; } // Those two are in the CRP. if (r.info.name == "IntakeLqd") { freeResources[PartResourceLibrary.Instance.GetDefinition("IntakeLqd").id] = true; } if (r.info.name == "IntakeAtm") { freeResources[PartResourceLibrary.Instance.GetDefinition("IntakeAtm").id] = true; } } // TODO : handle the multiple active ModuleEngine case ( SXT engines with integrated vernier ) //record relevant engine stats //ModuleEngines engine = part.Modules.OfType<ModuleEngines>().FirstOrDefault(e => e.isEnabled); ModuleEngines engine = null; for (int i = 0; i < part.Modules.Count; i++) { PartModule pm = part.Modules[i]; ModuleEngines e = pm as ModuleEngines; if (e != null && e.isEnabled) { engine = e; break; } } if (engine != null) { //Only count engines that either are ignited or will ignite in the future: if ((HighLogic.LoadedSceneIsEditor || inverseStage < Staging.CurrentStage || engine.getIgnitionState) && (engine.thrustPercentage > 0 || engine.minThrust > 0)) { //if an engine has been activated early, pretend it is in the current stage: if (engine.getIgnitionState && inverseStage < Staging.CurrentStage) { inverseStage = Staging.CurrentStage; } isEngine = true; g = engine.g; // If we take into account the engine rotation if (dVLinearThrust) { Vector3 thrust = Vector3d.zero; for (int i = 0; i < engine.thrustTransforms.Count; i++) { thrust -= engine.thrustTransforms[i].forward / engine.thrustTransforms.Count; } Vector3d fwd = HighLogic.LoadedScene == GameScenes.EDITOR ? EditorLogic.VesselRotation * Vector3d.up : engine.part.vessel.GetTransform().up; fwdThrustRatio = Vector3.Dot(fwd, thrust); } else { fwdThrustRatio = 1; } thrustPercentage = engine.thrustPercentage; minFuelFlow = engine.minFuelFlow; maxFuelFlow = engine.maxFuelFlow; atmosphereCurve = new FloatCurve(engine.atmosphereCurve.Curve.keys); atmChangeFlow = engine.atmChangeFlow; useAtmCurve = engine.useAtmCurve; if (useAtmCurve) { atmCurve = new FloatCurve(engine.atmCurve.Curve.keys); } useVelCurve = engine.useVelCurve; if (useVelCurve) { velCurve = new FloatCurve(engine.velCurve.Curve.keys); } propellantSumRatioTimesDensity = engine.propellants.Slinq().Where(prop => !prop.ignoreForIsp).Select(prop => prop.ratio * MuUtils.ResourceDensity(prop.id)).Sum(); propellantRatios.Clear(); propellantFlows.Clear(); var dics = new Tuple <KeyableDictionary <int, float>, KeyableDictionary <int, ResourceFlowMode> >(propellantRatios, propellantFlows); engine.propellants.Slinq() .Where(prop => MuUtils.ResourceDensity(prop.id) > 0 && !prop.ignoreForIsp) .ForEach((p, dic) => { dic._1.Add(p.id, p.ratio); dic._2.Add(p.id, p.GetFlowMode()); }, dics); } } }