//If we still have fuel, don't drain through the parent unless the parent node is a stack node. //For example, a fuel tank radial mounted to a parent fuel tank will drain itself before starting to drain its parent. //This is in contrast to the stacked case, where a fuel tank will drain the parent it is stacked under before draining itself. //This just seems to be an idiosyncracy of the KSP fuel flow system, which we faithfully simulate. bool drainFromSourceBeforeSelf(int type, FuelNode source) { if (this.resources.Amount(type) < 1.0F) { return(true); //if we have no fuel, then obviously we are allowed to drain any source node before this one } if (source.part != this.part.parent) { return(true); //we have no qualms about draining non-parents before ourselves } if (this.part.parent == null) { return(true); //so that the next line doesn't cause an error } foreach (AttachNode attachNode in this.part.parent.attachNodes) { //if we are attached to the parent by a non-stack node, it's not cool to draw fuel from them //(unless we have no fuel of our own. that case was handled above.) if (attachNode.attachedPart == this.part && attachNode.nodeType != AttachNode.NodeType.Stack) { return(false); } } //we only reach here if we have fuel, and source == parent, and parent != null, and we are not attached to the parent by a non-stack node //that is, we only reach here if we have fuel, and source == parent, and we are attached to the parent by a stack node //In this case it's OK to draw fuel from the parent. return(true); }
void AssignFuelDrainRateStagePriorityFlow(int type, float amount, List <FuelNode> vessel) { int maxInverseStage = -1; using (var dispoSources = ListPool <FuelNode> .Instance.BorrowDisposable()) { var sources = dispoSources.value; for (int i = 0; i < vessel.Count; i++) { FuelNode n = vessel[i]; if (n.resources[type] > DRAINED) { if (n.inverseStage > maxInverseStage) { maxInverseStage = n.inverseStage; sources.Clear(); sources.Add(n); } else if (n.inverseStage == maxInverseStage) { sources.Add(n); } } } for (int i = 0; i < sources.Count; i++) { if (!freeResources[type]) { sources[i].resourceDrains[type] += amount / sources.Count; } } } }
//call this when a node no longer exists, so that this node knows that it's no longer a valid source public void RemoveSourceNode(FuelNode n) { if (sourceNodes.Contains(n)) { sourceNodes.Remove(n); } }
public static FuelNode Borrow(Part part, bool dVLinearThrust) { FuelNode node = pool.Borrow(); node.Init(part, dVLinearThrust); return(node); }
// Find the set of nodes from which we can draw resources according to the STACK_PRIORITY_SEARCH flow scheme. // This gets called after all the FuelNodes have been constructed in order to set up the fuel flow graph. // Note that fuel flow through fuel lines and docked docking nodes is set up separately in // SetupFuelLineSources*() public void SetupRegularSources(Part part, Dictionary <Part, FuelNode> nodeLookup) { // When fuelCrossFeed is enabled we can draw fuel through stack and surface attachment if (part.fuelCrossFeed) { // Stack nodes: for (int i = 0; i < part.attachNodes.Count; i++) { AttachNode attachNode = part.attachNodes[i]; if (attachNode.attachedPart != null) { // For stack nodes, we can draw fuel unless this node is specifically // labeled as having crossfeed disabled (Kashua rule #4) FuelNode fuelnode; if (attachNode.id != "Strut" && attachNode.ResourceXFeed && !(part.NoCrossFeedNodeKey.Length > 0 && attachNode.id.Contains(part.NoCrossFeedNodeKey)) && nodeLookup.TryGetValue(attachNode.attachedPart, out fuelnode)) { stackNodeSources.Add(fuelnode); } } } // If we are surface-mounted to our parent we can draw fuel from it (Kashua rule #7) if (part.attachMode == AttachModes.SRF_ATTACH && part.parent != null) { surfaceMountParent = nodeLookup[part.parent]; } } }
//If we still have fuel, don't drain through the parent unless the parent node is a stack node. //This just seems to be an idiosyncracy of the KSP fuel flow system, which we faithfully simulate. bool drainFromSourceBeforeSelf(FuelNode source) { if (this.fuel == 0) { return(true); } if (source.part != this.part.parent) { return(true); } if (this.part.parent == null) { return(true); } foreach (AttachNode attachNode in this.part.parent.attachNodes) { if (attachNode.attachedPart == this.part && attachNode.nodeType != AttachNode.NodeType.Stack) { return(false); } } return(true); }
public void addSource(FuelNode source) { if (!sources.Contains(source)) { sources.Add(source); } }
void AssignFuelDrainRateStagePriorityFlow(int type, float amount, List <FuelNode> vessel) { int maxInverseStage = -1; List <FuelNode> sources = new List <FuelNode>(); for (int i = 0; i < vessel.Count; i++) { FuelNode n = vessel[i]; if (n.resources[type] > DRAINED) { if (n.inverseStage > maxInverseStage) { maxInverseStage = n.inverseStage; sources.Clear(); sources.Add(n); } else if (n.inverseStage == maxInverseStage) { sources.Add(n); } } } for (int i = 0; i < sources.Count; i++) { sources[i].resourceDrains[type] += amount / sources.Count; } }
//Takes a list of parts so that the simulation can be run in the editor as well as the flight scene public void Init(List <Part> parts, bool dVLinearThrust) { KpaToAtmospheres = PhysicsGlobals.KpaToAtmospheres; // Create FuelNodes corresponding to each Part nodes.Clear(); nodeLookup.Clear(); //Dictionary<Part, FuelNode> nodeLookup = parts.ToDictionary(p => p, p => FuelNode.Borrow(p, dVLinearThrust)); for (int index = 0; index < parts.Count; index++) { Part part = parts[index]; FuelNode node = FuelNode.Borrow(part, dVLinearThrust); nodeLookup[part] = node; nodes.Add(node); } // Determine when each part will be decoupled Part rootPart = parts[0]; // hopefully always correct nodeLookup[rootPart].AssignDecoupledInStage(rootPart, nodeLookup, -1); // Set up the fuel flow graph if (HighLogic.LoadedSceneIsFlight) { for (int i = 0; i < parts.Count; i++) { Part p = parts[i]; nodeLookup[p].SetupFuelLineSourcesFlight(p, nodeLookup); } } else { for (int i = 0; i < parts.Count; i++) { Part p = parts[i]; nodeLookup[p].SetupFuelLineSourcesFlight(p, nodeLookup); nodeLookup[p].SetupFuelLineSourcesEditor(p, nodeLookup); } } for (int i = 0; i < parts.Count; i++) { Part p = parts[i]; nodeLookup[p].SetupRegularSources(p, nodeLookup); nodeLookup[p].SetupSurfaceMountSources(p, nodeLookup); } simStage = Staging.lastStage + 1; // Add a fake stage if we are beyond the first one // Mostly usefull for the Node Executor who use the last stage info // and fail to get proper info when the ship was never staged and // some engine were activated manually if (Staging.CurrentStage > Staging.lastStage) { simStage++; } }
//assemble the representation of the ship in terms of a set of FuelNodes and fuel source relationships public void rebuildFuelFlowGraph(Environment enviro) { nodes = new List <FuelNode>(); //a useful tool to let us look up the fuel node corresponding to a given part: Dictionary <Part, FuelNode> nodeLookup = new Dictionary <Part, FuelNode>(); //create a FuelNode for each part foreach (Part p in parts) { FuelNode n = new FuelNode(p, enviro); nodes.Add(n); nodeLookup[p] = n; } //figure out where each FuelNode can draw fuel from foreach (FuelNode n in nodes) { //solid rockets can only draw on internal fuel if (n.part is SolidRocket) { n.addSource(n); continue; //skip all the code below, which applies only to liquid fuel } //first draw fuel from any fuel lines that point to this part foreach (FuelNode m in nodes) { if (m.part is FuelLine && ((FuelLine)m.part).target == n.part) { n.addSource(m); } } //then draw fuel from stacked parts foreach (AttachNode attachNode in n.part.attachNodes) { //decide if it's possible to draw fuel through this node: if (attachNode.attachedPart != null && //if there is a part attached here attachNode.nodeType == AttachNode.NodeType.Stack && //and the attached part is stacked (rather than surface mounted) (attachNode.attachedPart.fuelCrossFeed || //and the attached part allows fuel flow attachNode.attachedPart is FuelTank) && // or the attached part is a fuel tank !(n.part.NoCrossFeedNodeKey.Length > 0 && //and this part does not forbid fuel flow attachNode.id.Contains(n.part.NoCrossFeedNodeKey))) // through this particular node { n.addSource(nodeLookup[attachNode.attachedPart]); } } //then draw fuel from the parent part. For example, fuel tanks can draw //fuel from a fuel tank to which they are surface mounted. if (n.part.parent != null && n.part.parent.fuelCrossFeed) { n.addSource(nodeLookup[n.part.parent]); } } }
//call this when a node no longer exists, so that this node knows that it's no longer a valid source public void RemoveSourceNode(FuelNode n) { if (fuelLineSources.Contains(n)) { fuelLineSources.Remove(n); } if (stackNodeSources.Contains(n)) { stackNodeSources.Remove(n); } if (surfaceMountParent == n) { surfaceMountParent = null; } }
//Build up the representation of the vessel in terms of FuelNodes, and let them //figure out how to draw fuel from each other void rebuildVesselRepresenation(List <Part> parts, float atmospheres, bool current_thrust) { nodes = new List <FuelNode>(); Dictionary <Part, FuelNode> nodeLookup = new Dictionary <Part, FuelNode>(); foreach (Part p in parts) { FuelNode node = new FuelNode(p, atmospheres, current_thrust); nodes.Add(node); nodeLookup[p] = node; } foreach (FuelNode n in nodes) { n.findSourceNodes(nodeLookup); } }
//Find the set of nodes from which we can draw resources according to the STACK_PRIORITY_SEARCH flow scheme. //This gets called after all the FuelNodes have been constructed in order to set up the fuel flow graph public void FindSourceNodes(Part part, Dictionary <Part, FuelNode> nodeLookup) { //we can draw fuel from any fuel lines that point to this part foreach (Part p in nodeLookup.Keys) { if (p is FuelLine && ((FuelLine)p).target == part) { sourceNodes.Add(nodeLookup[p]); } } surfaceMounted = true; if (part.parent != null) { this.parent = nodeLookup[part.parent]; } //we can (sometimes) draw fuel from stacked parts foreach (AttachNode attachNode in part.attachNodes) { //decide if it's possible to draw fuel through this node: if (attachNode.attachedPart != null && //if there is a part attached here attachNode.nodeType == AttachNode.NodeType.Stack && //and the attached part is stacked (rather than surface mounted) !(part.NoCrossFeedNodeKey.Length > 0 && //and this part does not forbid fuel flow attachNode.id.Contains(part.NoCrossFeedNodeKey))) // through this particular node { if (part.fuelCrossFeed) { sourceNodes.Add(nodeLookup[attachNode.attachedPart]); } if (attachNode.attachedPart == part.parent) { surfaceMounted = false; } } } //Parts can draw resources from their parents //(exception: surface mounted fuel tanks cannot) if (part.parent != null && part.fuelCrossFeed) { sourceNodes.Add(nodeLookup[part.parent]); } //Debug.Log("source nodes for part " + partName); //foreach (FuelNode n in sourceNodes) Debug.Log(" " + n.partName); }
/* * //Find fuel nodes that are expected to supply fuel but can't. These nodes * //have run out of fuel and will never be able to supply fuel again. Remove them * //as possible sources for other fuel nodes. This way, when an engine no longer * //has any sources, we know it has run out of fuel. * void killEmptySources(List<FuelNode> engines) * { * bool sourceKilled = true; * while (sourceKilled) * { * //figure out where fuel is being drained from * assignFuelDrainRates(); * * //check if any nodes are expected to supply fuel but can't. if so they have been * //drained; remove them from the fuel flow graph * sourceKilled = false; * foreach (FuelNode n in nodes) * { * if (n.fuelDrainRate > 0 && n.fuel < 1.0F) * { * if (killSource(n)) sourceKilled = true; * } * } * * //If we killed a source, it might have been at the end of a long fuel chain. We may now need * //to kill some or all of the rest of the chain. Hence the while loop. * } * } */ //Remove the given FuelNode from any source lists in which it appears. //Should be called when the given FuelNode becomes no longer capable of supplying //fuel to anyone. Returns true if the given FuelNode was actually removed from //at least one source list. bool killSource(FuelNode source) { bool wasSource = false; source.fuel = 0; foreach (FuelNode n in nodes) { if (n.sources.Contains(source)) { n.sources.Remove(source); wasSource = true; } } return(wasSource); }
void AssignFuelDrainRateStagePriorityFlow(int type, double amount, bool usePrio, List <FuelNode> vessel) { int maxPrio = int.MinValue; using (var dispoSources = ListPool <FuelNode> .Instance.BorrowDisposable()) { var sources = dispoSources.value; //print("AssignFuelDrainRateStagePriorityFlow for " + partName + " searching for " + amount + " of " + PartResourceLibrary.Instance.GetDefinition(type).name + " in " + vessel.Count + " parts "); for (int i = 0; i < vessel.Count; i++) { FuelNode n = vessel[i]; if (n.resources[type] > n.resourceRequestRemainingThreshold) { if (usePrio) { if (n.resourcePriority > maxPrio) { maxPrio = n.resourcePriority; sources.Clear(); sources.Add(n); } else if (n.resourcePriority == maxPrio) { sources.Add(n); } } else { sources.Add(n); } } } //print(partName + " drains resource from " + sources.Count + " parts "); for (int i = 0; i < sources.Count; i++) { if (!freeResources[type]) { sources[i].resourceDrains[type] += amount / sources.Count; } } } }
void AssignFuelDrainRateAllVessel(int type, float amount, List <FuelNode> vessel) { //I don't know how this flow scheme actually works but I'm going to assume //that it drains from the part with the highest possible inverseStage FuelNode source = null; foreach (FuelNode n in vessel) { if (n.resources[type] > DRAINED) { if (source == null || n.inverseStage > source.inverseStage) { source = n; } } } if (source != null) { source.resourceDrains[type] += amount; } }
//Takes a list of parts so that the simulation can be run in the editor as well as the flight scene public void Init(List <Part> parts, bool dVLinearThrust) { KpaToAtmospheres = PhysicsGlobals.KpaToAtmospheres; // Create FuelNodes corresponding to each Part nodes.Clear(); nodeLookup.Clear(); for (int index = 0; index < parts.Count; index++) { Part part = parts[index]; FuelNode node = FuelNode.Borrow(part, dVLinearThrust); nodeLookup[part] = node; nodes.Add(node); } // Determine when each part will be decoupled Part rootPart = parts[0]; // hopefully always correct nodeLookup[rootPart].AssignDecoupledInStage(rootPart, nodeLookup, -1); // Set up the fuel flow graph for (int i = 0; i < parts.Count; i++) { Part p = parts[i]; nodeLookup[p].AddCrossfeedSouces(p.crossfeedPartSet.GetParts(), nodeLookup); } simStage = StageManager.LastStage + 1; // Add a fake stage if we are beyond the first one // Mostly usefull for the Node Executor who use the last stage info // and fail to get proper info when the ship was never staged and // some engine were activated manually if (StageManager.CurrentStage > StageManager.LastStage) { simStage++; } }
void assignFuelDrainRateAllVessel(int type, float amount, List <FuelNode> vessel) { //print("Assigning " + amount + " drain of " + PartResourceLibrary.Instance.GetDefinition(type).name + " from " + part); //I don't know how this flow scheme actually works but I'm going to assume //that it drains from the part with the highest possible inverseStage FuelNode source = null; foreach (FuelNode n in vessel) { //print(" -- looking at " + n.part + " - amount = " + n.resources.Amount(type)); if (n.resources.Amount(type) > 1.0F) { if (source == null || n.part.inverseStage > source.part.inverseStage) { source = n; } } } //print("draining from source = " + part); if (source != null) { source.resourceDrains.Add(type, amount); } }
/* //Find fuel nodes that are expected to supply fuel but can't. These nodes //have run out of fuel and will never be able to supply fuel again. Remove them //as possible sources for other fuel nodes. This way, when an engine no longer //has any sources, we know it has run out of fuel. void killEmptySources(List<FuelNode> engines) { bool sourceKilled = true; while (sourceKilled) { //figure out where fuel is being drained from assignFuelDrainRates(); //check if any nodes are expected to supply fuel but can't. if so they have been //drained; remove them from the fuel flow graph sourceKilled = false; foreach (FuelNode n in nodes) { if (n.fuelDrainRate > 0 && n.fuel < 1.0F) { if (killSource(n)) sourceKilled = true; } } //If we killed a source, it might have been at the end of a long fuel chain. We may now need //to kill some or all of the rest of the chain. Hence the while loop. } } */ //Remove the given FuelNode from any source lists in which it appears. //Should be called when the given FuelNode becomes no longer capable of supplying //fuel to anyone. Returns true if the given FuelNode was actually removed from //at least one source list. bool killSource(FuelNode source) { bool wasSource = false; source.fuel = 0; foreach (FuelNode n in nodes) { if (n.sources.Contains(source)) { n.sources.Remove(source); wasSource = true; } } return wasSource; }
//assemble the representation of the ship in terms of a set of FuelNodes and fuel source relationships public void rebuildFuelFlowGraph(Environment enviro) { nodes = new List<FuelNode>(); //a useful tool to let us look up the fuel node corresponding to a given part: Dictionary<Part, FuelNode> nodeLookup = new Dictionary<Part, FuelNode>(); //create a FuelNode for each part foreach (Part p in parts) { FuelNode n = new FuelNode(p, enviro); nodes.Add(n); nodeLookup[p] = n; } //figure out where each FuelNode can draw fuel from foreach (FuelNode n in nodes) { //solid rockets can only draw on internal fuel if (n.part is SolidRocket) { n.addSource(n); continue; //skip all the code below, which applies only to liquid fuel } //first draw fuel from any fuel lines that point to this part foreach (FuelNode m in nodes) { if (m.part is FuelLine && ((FuelLine)m.part).target == n.part) { n.addSource(m); } } //then draw fuel from stacked parts foreach (AttachNode attachNode in n.part.attachNodes) { //decide if it's possible to draw fuel through this node: if (attachNode.attachedPart != null //if there is a part attached here && attachNode.nodeType == AttachNode.NodeType.Stack //and the attached part is stacked (rather than surface mounted) && (attachNode.attachedPart.fuelCrossFeed //and the attached part allows fuel flow || attachNode.attachedPart is FuelTank) // or the attached part is a fuel tank && !(n.part.NoCrossFeedNodeKey.Length > 0 //and this part does not forbid fuel flow && attachNode.id.Contains(n.part.NoCrossFeedNodeKey))) // through this particular node { n.addSource(nodeLookup[attachNode.attachedPart]); } } //then draw fuel from the parent part. For example, fuel tanks can draw //fuel from a fuel tank to which they are surface mounted. if (n.part.parent != null && n.part.parent.fuelCrossFeed) { n.addSource(nodeLookup[n.part.parent]); } } }
//Whether we've used up the current stage public bool AllowedToStage() { //print("Checking whether allowed to stage at t = " + t); List <FuelNode> activeEngines = FindActiveEngines(); //print(" activeEngines.Count = " + activeEngines.Count); //if no engines are active, we can always stage if (activeEngines.Count == 0) { //print("Allowed to stage because no active engines"); return(true); } List <int> burnedResources = activeEngines.SelectMany(eng => eng.BurnedResources()).Distinct().ToList(); //if staging would decouple an active engine or non-empty fuel tank, we're not allowed to stage for (int i = 0; i < nodes.Count; i++) { FuelNode n = nodes[i]; //print(n.partName + " is sepratron? " + n.isSepratron); if (n.decoupledInStage == (simStage - 1) && !n.isSepratron) { if (activeEngines.Contains(n) || n.ContainsResources(burnedResources)) { //print("Not allowed to stage because " + n.partName + " either contains resources or is an active engine"); return(false); } } } // We are not allowed to stage if the stage does not decouple anything, and there is an active engine that still has access to resources { bool activeEnginesWorking = false; bool partDecoupledInNextStage = false; for (int i = 0; i < nodes.Count; i++) { FuelNode n = nodes[i]; if (activeEngines.Contains(n)) { if (n.CanDrawNeededResources(nodes)) { //print("Part " + n.partName + " is an active engine that still has resources to draw on."); activeEnginesWorking = true; } } if (n.decoupledInStage == (simStage - 1)) { //print("Part " + n.partName + " is decoupled in the next stage."); partDecoupledInNextStage = true; } } if (!partDecoupledInNextStage && activeEnginesWorking) { //print("Not allowed to stage because nothing is decoupled in the enst stage, and there are already other engines active."); return(false); } } //if this isn't the last stage, we're allowed to stage because doing so wouldn't drop anything important if (simStage > 0) { //print("Allowed to stage because this isn't the last stage"); return(true); } //print("Not allowed to stage because there are active engines and this is the last stage"); //if this is the last stage, we're not allowed to stage while there are still active engines return(false); }
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.GetModuleMassNoAlloc(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 < 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 = Vector3d.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; 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); } } }
//Find the set of nodes from which we can draw resources according to the STACK_PRIORITY_SEARCH flow scheme. //This gets called after all the FuelNodes have been constructed in order to set up the fuel flow graph public void FindSourceNodes(Part part, Dictionary<Part, FuelNode> nodeLookup) { //we can draw fuel from any fuel lines that point to this part foreach (Part p in nodeLookup.Keys) { if (p is FuelLine && ((FuelLine)p).target == part) { sourceNodes.Add(nodeLookup[p]); } } surfaceMounted = true; if (part.parent != null) this.parent = nodeLookup[part.parent]; //we can (sometimes) draw fuel from stacked parts foreach (AttachNode attachNode in part.attachNodes) { //decide if it's possible to draw fuel through this node: if (attachNode.attachedPart != null //if there is a part attached here && attachNode.nodeType == AttachNode.NodeType.Stack //and the attached part is stacked (rather than surface mounted) && attachNode.attachedPart.fuelCrossFeed //and the attached part allows fuel flow && !(part.NoCrossFeedNodeKey.Length > 0 //and this part does not forbid fuel flow && attachNode.id.Contains(part.NoCrossFeedNodeKey))) // through this particular node { sourceNodes.Add(nodeLookup[attachNode.attachedPart]); if (attachNode.attachedPart == part.parent) surfaceMounted = false; } } //Parts can draw resources from their parents //(exception: surface mounted fuel tanks cannot) if (part.parent != null && part.parent.fuelCrossFeed) sourceNodes.Add(nodeLookup[part.parent]); }
private static void Reset(FuelNode obj) { }
public void addSource(FuelNode source) { if (!sources.Contains(source)) sources.Add(source); }
// Find the set of nodes from which we can draw resources according to the STACK_PRIORITY_SEARCH flow scheme. // This gets called after all the FuelNodes have been constructed in order to set up the fuel flow graph. // Note that fuel flow through fuel lines and docked docking nodes is set up separately in // SetupFuelLineSources*() public void SetupRegularSources(Part part, Dictionary<Part, FuelNode> nodeLookup) { // When fuelCrossFeed is enabled we can draw fuel through stack and surface attachment if (part.fuelCrossFeed) { // Stack nodes: for (int i = 0; i < part.attachNodes.Count; i++) { AttachNode attachNode = part.attachNodes[i]; if (attachNode.attachedPart != null) { // For stack nodes, we can draw fuel unless this node is specifically // labeled as having crossfeed disabled (Kashua rule #4) FuelNode fuelnode; if (attachNode.id != "Strut" && attachNode.ResourceXFeed && !(part.NoCrossFeedNodeKey.Length > 0 && attachNode.id.Contains(part.NoCrossFeedNodeKey)) && nodeLookup.TryGetValue(attachNode.attachedPart, out fuelnode)) { stackNodeSources.Add(fuelnode); } } } // If we are surface-mounted to our parent we can draw fuel from it (Kashua rule #7) if (part.attachMode == AttachModes.SRF_ATTACH && part.parent != null) { surfaceMountParent = nodeLookup[part.parent]; } } }
//If we still have fuel, don't drain through the parent unless the parent node is a stack node. //This just seems to be an idiosyncracy of the KSP fuel flow system, which we faithfully simulate. bool drainFromSourceBeforeSelf(FuelNode source) { if (this.fuel == 0) return true; if (source.part != this.part.parent) return true; if (this.part.parent == null) return true; foreach (AttachNode attachNode in this.part.parent.attachNodes) { if (attachNode.attachedPart == this.part && attachNode.nodeType != AttachNode.NodeType.Stack) return false; } return true; }
//call this when a node no longer exists, so that this node knows that it's no longer a valid source public void RemoveSourceNode(FuelNode n) { if (fuelLineSources.Contains(n)) fuelLineSources.Remove(n); if (stackNodeSources.Contains(n)) stackNodeSources.Remove(n); if (surfaceMountParent == n) surfaceMountParent = null; }
//Find the set of nodes from which we can draw resources according to the STACK_PRIORITY_SEARCH flow scheme. //This gets called after all the FuelNodes have been constructed in order to set up the fuel flow graph public void FindSourceNodes(Part part, Dictionary<Part, FuelNode> nodeLookup) { //we can draw fuel from any fuel lines that point to this part foreach (Part p in nodeLookup.Keys) { if (p is FuelLine) print("FuelLine "); if (p is FuelLine && ((FuelLine)p).target == part) { sourceNodes.Add(nodeLookup[p]); print("FuelLine " + nodeLookup[p].partName + "_" + p.uid); } } surfaceMounted = true; if (part.parent != null) this.parent = nodeLookup[part.parent]; ModuleDockingNode dockNode; // In-flight docked ports attach only one way. This finds the docked port in the other direction. // However, this doesn't work in the VAB/SPH (as there is no vessel), and isn't needed there anyway. if (part.vessel && (dockNode = part.Modules.OfType<ModuleDockingNode>().FirstOrDefault())) { uint dockedPartUId = dockNode.dockedPartUId; Part p = part.vessel[dockedPartUId]; print(String.Format("docking port {0} {1}", part, p)); if (p) sourceNodes.Add(nodeLookup[p]); } //we can (sometimes) draw fuel from stacked parts foreach (AttachNode attachNode in part.attachNodes) { //decide if it's possible to draw fuel through this node: if (attachNode.attachedPart != null //if there is a part attached here && attachNode.nodeType == AttachNode.NodeType.Stack //and the attached part is stacked (rather than surface mounted) && attachNode.id != "Strut" //and it's not a Strut && !(part.NoCrossFeedNodeKey.Length > 0 //and this part does not forbid fuel flow && attachNode.id.Contains(part.NoCrossFeedNodeKey))) // through this particular node { print("attachNode.id " + attachNode.id); if (part.fuelCrossFeed) { sourceNodes.Add(nodeLookup[attachNode.attachedPart]); print("AttachedPart " + nodeLookup[attachNode.attachedPart].partName + "_" + attachNode.attachedPart.uid); } if (attachNode.attachedPart == part.parent) surfaceMounted = false; } } //Parts can draw resources from their parents //(exception: surface mounted fuel tanks cannot) if (part.parent != null && part.fuelCrossFeed) { sourceNodes.Add(nodeLookup[part.parent]); print("Parent Part " + nodeLookup[part.parent].partName + "_" + part.parent.uid); } print("source nodes for part " + partName); foreach (FuelNode n in sourceNodes) print(" " + n.partName); }
//Find the set of nodes from which we can draw resources according to the STACK_PRIORITY_SEARCH flow scheme. //This gets called after all the FuelNodes have been constructed in order to set up the fuel flow graph public void FindSourceNodes(Part part, Dictionary <Part, FuelNode> nodeLookup) { //we can draw fuel from any fuel lines that point to this part foreach (Part p in nodeLookup.Keys) { if (p is FuelLine) { print("FuelLine "); } if (p is FuelLine && ((FuelLine)p).target == part) { sourceNodes.Add(nodeLookup[p]); print("FuelLine " + nodeLookup[p].partName + "_" + p.uid); } } surfaceMounted = true; if (part.parent != null) { this.parent = nodeLookup[part.parent]; } ModuleDockingNode dockNode; // In-flight docked ports attach only one way. This finds the docked port in the other direction. // However, this doesn't work in the VAB/SPH (as there is no vessel), and isn't needed there anyway. if (part.vessel && (dockNode = part.Modules.OfType <ModuleDockingNode>().FirstOrDefault())) { uint dockedPartUId = dockNode.dockedPartUId; Part p = part.vessel[dockedPartUId]; print(String.Format("docking port {0} {1}", part, p)); if (p) { sourceNodes.Add(nodeLookup[p]); } } //we can (sometimes) draw fuel from stacked parts foreach (AttachNode attachNode in part.attachNodes) { //decide if it's possible to draw fuel through this node: if (attachNode.attachedPart != null && //if there is a part attached here attachNode.nodeType == AttachNode.NodeType.Stack && //and the attached part is stacked (rather than surface mounted) attachNode.id != "Strut" && //and it's not a Strut !(part.NoCrossFeedNodeKey.Length > 0 && //and this part does not forbid fuel flow attachNode.id.Contains(part.NoCrossFeedNodeKey))) // through this particular node { print("attachNode.id " + attachNode.id); if (part.fuelCrossFeed) { sourceNodes.Add(nodeLookup[attachNode.attachedPart]); print("AttachedPart " + nodeLookup[attachNode.attachedPart].partName + "_" + attachNode.attachedPart.uid); } if (attachNode.attachedPart == part.parent) { surfaceMounted = false; } } } //Parts can draw resources from their parents //(exception: surface mounted fuel tanks cannot) if (part.parent != null && part.fuelCrossFeed) { sourceNodes.Add(nodeLookup[part.parent]); print("Parent Part " + nodeLookup[part.parent].partName + "_" + part.parent.uid); } print("source nodes for part " + partName); foreach (FuelNode n in sourceNodes) { print(" " + n.partName); } }
//call this when a node no longer exists, so that this node knows that it's no longer a valid source public void RemoveSourceNode(FuelNode n) { crossfeedSources.Remove(n); }
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); } } }
//call this when a node no longer exists, so that this node knows that it's no longer a valid source public void RemoveSourceNode(FuelNode n) { if (sourceNodes.Contains(n)) sourceNodes.Remove(n); }